65.9K
CodeProject 正在变化。 阅读更多。
Home

GIOS PDF 分割器和合并器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (43投票s)

2005年12月20日

LGPL3

4分钟阅读

viewsIcon

945532

downloadIcon

4087

第一个用 C# 编写的开源 PDF 分割器和合并器工具。

这是 GIOS PDF 分割器和合并器 v1.0 的屏幕截图,这是第一个用 C# .NET 编写的开源 PDF 分割器和合并器工具。

引言

继 2005 年 4 月发布的 GIOS PDF .NET 库成功之后,我决定投入更多时间为社区做贡献。扩展和改进 PDF 库是我能做的事情之一,但新功能该怎么添加呢?

嗯,我必须感谢我的朋友 Charles。上个月我们正在讨论为 PDF 库添加新功能。他说:“如果你需要新的挑战,何不开发一个 PDF 合并程序?”他的话触动了我:目前没有免费的 Windows 应用程序能做到这一点。而且,也没有 C# 编写的开源项目。于是,我花了大量时间研究 Acrobat 的 PDF 参考资料,以评估实现这一功能的可能性。

背景

阅读 Adobe 的 Portable Document Format (PDF) Specification,第三版,版本 1.4,第 3.4 节,你会发现 PDF 由以下几部分组成:

  • Header,提供文件的类型信息(通常是 %PDF-1.4)。
  • Body,包含对象的实际数据。它占 PDF 大小的 99%。
  • Cross reference table(交叉引用表),它使阅读器能够无需解析整个文件即可索引对象。这是快速导航大型文档的秘密。损坏的交叉引用表不会影响文档的阅读,但 Acrobat 会花费大量时间即时重建它。
  • Trailer,包含打开文档所需的信息,例如名为 Catalog 的根对象的 ID。

Body 由一个“通用对象的节点结构”组成。RootCatalog 是页面容器的容器(Pages objects)。

如何实现(基本概念)

我们需要指出为了分割(合并)PDF 需要改变什么。

  • Header 保持不变,并且几乎所有 PDF 的 Header 都相同。
  • 我们需要重新组织 Body,丢弃新文档不需要的对象。
  • 重建交叉引用表,但在测试阶段 Acrobat 会即时为我们完成。所以这不是一个大问题。
  • 覆盖 Trailer 中的设置,但这个对象非常简单,只需花很少的时间即可完全重写。

这是将一个包含三页的文档分割成一个新 PDF 的示意图,该新 PDF 由原文档的第三页和第一页(按顺序)组成。

  1. 对象 1、2、4 和 5 将被丢弃,因为它们是旧文档结构的描述符。
  2. 对象 7、12、13 和 14 将被丢弃,因为它们是我们想要丢弃的页面的父对象和子对象。
  3. 对象 17 和 18 将被创建,以描述新结构。

如何实现(通过编码)

该应用程序使用以下引擎:

  1. 用于原始文档的对象解析器PdfFile.csPdfFileObject.cs
  2. 分割器PdfSplitter.cs
  3. 合并器PsdSplitterMerger.cs

对象解析器

对象解析器解析 PDF 的行并将其对象存储在内存中,识别它们的类型。

我对我的对象解析器并不感到自豪。它不是最好的,但它有效。这是我的代码片段,其中对象本身在其内容中搜索一些匹配项,以了解其自身类型。我在其他地方看到过一些更好的解析器,例如在文章 A pdf Forms parser 中。如果您是纯粹主义编码者,请不要看里面!;-)

这里使用 Regex 是不必要的,但它肯定是一种更优雅的搜索字符串匹配的方式。

if (Regex.IsMatch(s, @"/Page")&!Regex.IsMatch(s, @"/Pages"))
{ 
    this.type = PdfObjectType.Page;
    return this.type;
} 
if (Regex.IsMatch(s,@"stream"))
{
    this.type = PdfObjectType.Stream;
    return this.type;
}
if (Regex.IsMatch(s, @"(/Creator)|(/Author)|(/Producer)")) 
{
    this.type = PdfObjectType.Info;
    return this.type; 
} 
this.type = PdfObjectType.Other;

分割器

分割器接收一个对象集合(input)并返回一个对象集合(output)。

input 由对象解析器提供,而 output basically 是原始对象的过滤列表。工作原理如下:

  1. 获取文档的原始对象(由对象解析器提供)。
  2. 获取选定页面的索引。
  3. 使用某种蜘蛛来填充选定页面所需的对象列表。
  4. 从原始集合中删除蜘蛛未访问的对象。
  5. 重新编号对象(合并器需要的功能)。

这是 PdfFileObject.cs 中用于探索其子对象的递归方法。

internal void PopulateRelatedObjects(PdfFile PdfFile, 
                                    Hashtable container)
{ 
    Match m = Regex.Match(this.OriginalText, @"\d+ 0 R[^G]");
    while (m.Success)
     {
        int num=int.Parse(
                  m.Value.Substring(0,m.Value.IndexOf(" ")));
        bool notparent = !Regex.IsMatch(this.OriginalText, 
                                   @"/Parent\s+"+num+" 0 R"); 
        if (notparent &! container.Contains(num))
        {
            PdfFileObject pfo = PdfFile.LoadObject(num);
            if (pfo != null & !container.Contains(pfo.number))
            {
                container.Add(num,null);
                pfo.PopulateRelatedObjects(PdfFile, container);
            }
        }
    m = m.NextMatch();
    }
}

合并器

merger 是一个简单的类,用于附加每个分割器的输出并写入必要对象(在我们上面的例子中,是对象 17 和 18)。它还写入headercross reference tabletrailer。查看 PdfSplitterMerger.cs,它非常简单。

结论

我希望这个项目对非程序员有用。文档的分割和合并应该是免费的。希望这些能够揭开 PDF 神秘面纱的项目在不久的将来能取得好的成果。

历史

  • 2005 年 12 月 21 日 - v1.0 发布。
  • 2006 年 1 月 4 日 - v1.1
    • 修复了一些小的 bug。
    • 通过一些 Regex 优化,性能得到了很大提升。
  • 2006 年 11 月 24 日 - v1.12
    • 修复 Regex 以支持 SQL Reporting Services。
© . All rights reserved.