如何根据大小限制拆分 PDF?

How to split a PDF based on a size limit?

我找了很多地方都没有找到一个很好的解决方案。 所以我想要实现的目标如下: 我的程序将有很多 PDF 文档,我必须通过邮件发送这些文档。邮件服务器限制为 4 MB。因此,如果所有 PDF 都小于 4 MB,它将作为一封邮件发送。否则我将不得不创建多个文件,每个文件小于 4 MB。 现在我的程序适用于以下情况: 1:很多文件,但每个文件都小于 4MB,因此在合并期间保留一个选项卡,以便 none 个合并文件超过 4MB。 2:所有文件都非常小,因此将它们合并在一起不会达到 4MB 的限制。

但可能存在这样一种情况,即有一个文件,比方说,14MB。我可以按页拆分该文档。但这也不是一个好的解决方案,因为页面大小也没有均匀分布在页面上。我用过 iText 和 PDFBox。任何 help/pointer 将不胜感激!

假设一个 3000 KB 的文档有十页和以下对象:

  • 每页使用四个字体子集,每个大约 50 KB
  • 一页显示十张图片,每张约 200 KB(每页一张图片)
  • 每页四张图片,每张约 50 KB
  • 十页内容流,每页约 25 KB
  • 大约 350 KB 用于目录、信息字典、页面树、交叉引用等对象 table 等...

单个页面至少需要: - 四个字体子集:4 倍 50 KB - 单张图片:1 次 200 KB - 四张图片:4 次 50 KB - 单个内容流:1 次 50 KB - 略微减少的交叉引用 table,略微减少的页面树,几乎相同的目录,相同大小的信息字典,... 200 KB

总计 850 KB。这意味着如果将 10 页 3000 KB 的 PDF 文档拆分为 10 个单独的页面,最终将得到 8500 KB(850 KB 的 10 倍)。

这个例子是猜测的结果(根据经验),它假设 PDF 是 predictable。大多数 PDF 不是:

  • 一些页面需要高清图像(甚至可能是兆字节),其他页面不会有任何图像,
  • 一些页面将需要许多不同的字体和字体子集(很多千字节),其他页面将只包含一些矢量图(压缩后的内容流很小)。
  • 不同页面可以共享大量资源(Form XObjects,Image XObjects,...),其他页面不会共享任何资源。
  • 等等...

您自己已经注意到,当您写道:我可以按页拆分该文档。但这也不是一个好的解决方案,因为页面大小也没有均匀分布在页面上。

这就是为什么您的问题没有其他答案的原因:您必须进行 反复试验。 没有软件可以预测需要多少 space在查看该页面需要的内容之前先访问该页面。

更新:

正如大卫在评论中指出的那样,可以计算一个页面所需的所有资源,并检查当前资源加上所需资源是否超过最大文件大小。

我写了一个小例子:

public void manipulatePdf(String src, String dest)
    throws IOException, DocumentException {
    Document document = new Document();
    PdfCopy copy = new PdfSmartCopy(document, new FileOutputStream(dest));
    document.open();
    PdfReader reader = new PdfReader(src);
    for (int i = 1; i <= reader.getNumberOfPages(); i++) {
        // check resources needed for reader.getPageN(i);
        copy.addPage(copy.getImportedPage(reader, i));
        System.out.println("After adding page: " + copy.getOs().getCounter());
    }
    document.close();
    System.out.println("After closing document: " + copy.getOs().getCounter());
    reader.close();
}

我在 18 页的 PDF 示例上执行了示例,这是输出:

After adding page: 56165
After adding page: 111398
After adding page: 162691
After adding page: 210035
After adding page: 253419
After adding page: 273429
After adding page: 330696
After adding page: 351564
After adding page: 400351
After adding page: 456545
After adding page: 495321
After adding page: 523640
After adding page: 576468
After adding page: 633525
After adding page: 751504
After adding page: 907490
After adding page: 957164
After adding page: 999140
After closing document: 1002509

您会看到副本的文件大小是如何随着每一页的添加而逐渐增大的。所有页加完后,大小为999140字节,然后写页树和交叉引用流,又加了3369字节。

其中显示 // check resources needed for reader.getPageN(i);,您可以猜测将为页面添加的大小,如果超过最大值则跳出循环。

为什么这是一个猜测:

  1. 您可能正在计算已添加的对象。如果您跟踪对象(不是那么困难),您的猜测会更准确。
  2. 我正在使用 PdfSmartCopy。假设您的 PDF 中有两个相同的对象。糟糕的 PDF 软件通常会导致此类问题。例如:相同的图像字节被添加到文件中两次。 PdfSmartCopy 可以检测到这一点,并将重用它遇到的第一个对象,而不是添加额外对象的冗余字节。

我们目前在 PdfReader 中没有 reader.getTotalPageBytes(),因为 PdfReader 试图使用尽可能少的内存。只要不需要这些对象,它就不会将任何对象加载到内存中。因此在导入页面之前它不知道每个对象的大小。

但是,我会确保在下一个版本中添加这样的方法。

更新:

在下一版本中,您将找到一个名为 SmartPdfSplitter that depends on a new class named PdfResourceCounter 的工具。你可以这样使用它:

PdfReader reader = new PdfReader(src);
SmartPdfSplitter splitter = new SmartPdfSplitter(reader);
int part = 1;
while (splitter.hasMorePages()) {
    splitter.split(new FileOutputStream("results/merge/part_" + part + ".pdf"), 200000);
    part++;
}
reader.close();

请注意,这可能会导致单页 PDF 超出限制(在代码示例中设置为 200000 字节),以防单页无法减少到更少字节。在这种情况下,splitter.isOverSized() 将 return true,您将不得不找到另一种方法来缩小 PDF。

PDF Clown supports page data size prediction without need of trial and error: since 2010 it has been featuring a dedicated method (org.pdfclown.tools.PageManager.getSize(Page)) 即在内存中计算出实际的页面数据大小,而不需要写入文件进行试用。

此外,还有另一种方法 (org.pdfclown.tools.PageManager.split(long maxDataSize)) 专门用于解决您利用上述 PageManager.getSize 方法的场景:它会根据以下内容自动拆分文件大小限制,无需创建任何中间的、丑陋的、愚蠢的临时文件来进行试验和错误

您可以在可下载发行版中包含的 org.pdfclown.samples.cli.PageManagementSample(PageDataSizeCalculation 和 DocumentSplitOnMaximumFileSize 案例)中看到它的实际使用示例——这里是 PageDataSizeCalculation 案例的控制台输出示例:

Page 1: 29380 (full); 29380 (differential); 29380 (incremental)
Page 2: 30493 (full); 1501 (differential); 30881 (incremental)
Page 3: 21888 (full); 1432 (differential); 32313 (incremental)
Page 4: 33781 (full); 4789 (differential); 37102 (incremental)
. . .

其中:

  • full 是包含所有依赖项(如共享资源)的页面数据大小——这是作为单页文档提取时的页面大小;
  • differential 是额外的页面数据大小——这是不与之前页面共享的额外内容;
  • incremental 是页面子列表的数据大小,包括所有以前的页面和当前页面。