当通过代码合并 PDF 文件时,我见过的每个实现都需要在合并为一个文件之前打开所有文件。这适用于小文件,但对于足够大的文件,我们可能会超出 azure 函数可用的资源,导致函数重新启动。
我想知道是否有一种方法可以简单地将一个文件从外部附加到另一个文件,而不必打开它们。作为类比,考虑可以在命令行上执行的命令:“copy *.txt newfile.txt”。它将获取所有以 .txt 结尾的文件,并创建一个将它们全部组合在一起的新文件。如果我理解正确的话,我认为它不需要将任何单个文件的全部内容加载到内存中,因为它只是在进行文件系统复制,而是在一个文件结束而另一个文件开始的地方加入文件。是否有类似的方法可以用于 pdfs?
我们尝试了各种合并实现,您可以将每个文件的页面加载到 for 循环中,然后将每个页面一个接一个地添加到渲染器中。显然,在这种方法中,内存使用量与文件大小成正比。
实际上,这可以使用增量更新功能,您可以修改 PDF 文档而不影响其原始内容,只需在文件末尾附加更改即可。
这是增量更新规范。
此外,这里还有一个使用 GemBox.Pdf 合并 PDF 文件和增量更新的示例:
合并大量PDF文件
重点是先用
PdfDocument.Save()
没有参数的重载方法再用PdfDocument.Unload()
方法。这样在任何给定时间只会加载一个文件。
using (var document = new PdfDocument())
{
document.Save("Merged Files.pdf");
foreach (var file in files)
{
using (var source = PdfDocument.Load(file))
document.Pages.Kids.AddClone(source.Pages);
// Save the new pages.
document.Save();
// Clear previously parsed pages and thus free the memory.
document.Unload();
}
}
我们可以更进一步,在 X 页之后卸载任何已解析的对象。
int counter = 0;
using (var destination = new PdfDocument())
{
destination.Save("Merged Files.pdf");
foreach (var file in files)
using (var source = PdfDocument.Load(file))
for (int index = 0, count = source.Pages.Count; index < count; counter++, index++)
{
destination.Pages.AddClone(source.Pages[index]);
// Unload after 100 pages.
if (counter % 100 == 0)
{
destination.Save();
destination.Unload();
source.Unload();
}
}
destination.Save();
}
TL;DR: 不幸的是,没有。
长版: Pdf 是一种文件格式,其结构如下:
[File header]
PDF objects
[XRef Table]
[Trailer object]
[startxref offset]
文件头是几个简单的字节表示它是一个PDF文件,然后一些>128字节表示一个二进制文件。 所有 PDF 对象看起来像这样:
1 0 obj
[object]
endobj
其中 1 是对象编号。所以对象 2 将开始为:
2 0 obj
等
XRef 表是文件中对象及其字节偏移量的列表。
对象可以引用其他对象。例如,trailer 对象是一个包含对根元素的引用的字典。根元素包含对页面对象的引用。页面对象包含一个或多个(可能是一个数组)对页面对象的引用。
所以读取PDF文件的时候,你从最后开始,读取startxref值。这指向外部参照表的开始。该表告诉您对象的字节偏移量,从对象
1 0 obj
开始。
所以如果你将 2 个文件连接在一起,当读取文件时,你将读取 startxref 值。但偏移量不正确,因为偏移量偏离了第一个文件的长度。即使在纠正这一点时。整个外部参照表偏离第一个文件长度。即使更正它,您仍然只能阅读最后一个 PDF。要实际连接页面,您需要将所有对象编号(以及文件中使用该对象的引用)编辑为
[last object number from file 1 + n]
。然后你需要覆盖之前的页面对象以包含第二个文件的所有页面。
这不是最难的,因为您可以创建一个新的 PageTree 对象,该对象将引用两个 pagetree 对象编号。您确实需要更新/覆盖 Catalog 对象以将新创建的 pagetree 引用为树的新根。
然后您需要创建一个新的外部参照表,该表使用两个文件中的所有对象,以正确的顺序和字节偏移列出它们。