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

时间:2015-02-19 05:54:54

标签: pdf itext pdfbox

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

但是可能存在一个文件,例如14MB。我可以按页面拆分该文档。但这也不是一个好的解决方案,因为页面大小也不是均匀分布在各个页面上。我使用过iText和PDFBox。任何帮助/指针都将受到高度赞赏!

2 个答案:

答案 0 :(得分:2)

PDF Clown 支持 page data size prediction without need of trial and error :自2010年以来,它一直采用专用方法(org.pdfclown.tools.PageManager.getSize(Page)),在内存中计算实际页面数据大小,无需将其写入文件进行试用。

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

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

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)
. . .

其中:

  • 完整是包含其所有依赖项(如共享资源)的页面数据大小 - 这是提取为单页文档时页面的大小;
  • 差异是额外的页面数据大小 - 这是与以前的页面不共享的额外内容;
  • incremental 是包含所有先前页面和当前页面的页面子列表的数据大小。

答案 1 :(得分:1)

想象一下包含10个页面的3000 KB文档和以下对象:

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

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

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

此示例是猜测工作的结果(基于经验),并假设PDF是可预测的。大多数PDF不是:

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

你在写作时注意到了自己:我可以按页面拆分该文档。但这也不是一个好的解决方案,因为页面大小也不是均匀分布在各个页面上。

这就是为什么你的问题没有其他答案了:你必须做试验和错误。没有软件可以预测一个页面需要多少空间才能看到什么是该页面需要。

<强>更新

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

我写了一个小例子:

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可以检测到这一点,并将重用它遇到的第一个对象,而不是添加额外对象的冗余字节。
  3. 我们目前reader.getTotalPageBytes()中没有PdfReader因为PdfReader尝试使用尽可能少的内存。只要不需要这些对象,它就不会将任何对象加载到内存中。因此,在导入页面之前,它不知道每个对象的大小。

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

    <强>更新

    在下一个版本中,您将找到一个名为SmartPdfSplitter的工具,该工具依赖于名为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()将返回true,您必须找到另一种方法来缩小PDF。