GIOS PDF 分割器和合并器






4.92/5 (43投票s)
第一个用 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 由一个“通用对象的节点结构”组成。Root 或 Catalog 是页面容器的容器(Pages objects)。
如何实现(基本概念)
我们需要指出为了分割(合并)PDF 需要改变什么。
- Header 保持不变,并且几乎所有 PDF 的 Header 都相同。
- 我们需要重新组织 Body,丢弃新文档不需要的对象。
- 重建交叉引用表,但在测试阶段 Acrobat 会即时为我们完成。所以这不是一个大问题。
- 覆盖 Trailer 中的设置,但这个对象非常简单,只需花很少的时间即可完全重写。
这是将一个包含三页的文档分割成一个新 PDF 的示意图,该新 PDF 由原文档的第三页和第一页(按顺序)组成。
- 对象 1、2、4 和 5 将被丢弃,因为它们是旧文档结构的描述符。
- 对象 7、12、13 和 14 将被丢弃,因为它们是我们想要丢弃的页面的父对象和子对象。
- 对象 17 和 18 将被创建,以描述新结构。
如何实现(通过编码)
该应用程序使用以下引擎:
- 用于原始文档的对象解析器(PdfFile.cs 和 PdfFileObject.cs)
- 分割器(PdfSplitter.cs)
- 合并器(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 是原始对象的过滤列表。工作原理如下:
- 获取文档的原始对象(由对象解析器提供)。
- 获取选定页面的索引。
- 使用某种蜘蛛来填充选定页面所需的对象列表。
- 从原始集合中删除蜘蛛未访问的对象。
- 重新编号对象(合并器需要的功能)。
这是 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)。它还写入header、cross reference table 和 trailer。查看 PdfSplitterMerger.cs,它非常简单。
结论
我希望这个项目对非程序员有用。文档的分割和合并应该是免费的。希望这些能够揭开 PDF 神秘面纱的项目在不久的将来能取得好的成果。
历史
- 2005 年 12 月 21 日 - v1.0 发布。
- 2006 年 1 月 4 日 - v1.1
- 修复了一些小的 bug。
- 通过一些 Regex 优化,性能得到了很大提升。
- 2006 年 11 月 24 日 - v1.12
- 修复 Regex 以支持 SQL Reporting Services。