【问题标题】:How to split a PDF based on a size limit?如何根据大小限制拆分 PDF?
【发布时间】:2015-04-20 09:24:48
【问题描述】:

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

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

【问题讨论】:

    标签: pdf itext pdfbox


    【解决方案1】:

    想象一个 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 不是:

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

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

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

    更新:

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

    我写了一个小例子:

    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 的工具,它依赖于一个名为 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。

    【讨论】:

    • 先生,您刚刚证实了我的怀疑!非常感谢!我会改变我的设计。
    • 我不明白为什么你会说这是不可能的——你说得对,这很难,但软件实际上可以进行这些计算并得出正确的答案,不是吗?
    • @DavidvanDriessche 也许布鲁诺肥大的自我认为在 PDF 软件的世界中只有 iText:任何 iText 不能做的事情都不能由任何其他软件完成,根据定义! ;-)(开个玩笑!)
    • "如果您不在场,也没有来自您公司的任何人,我会假设 PDF 对您来说不是那么重要,因为您没有为即将到来的 PDF 2.0 标准做出贡献;-) “那太低级了,布鲁诺,简直是一种侮辱。它在 Stackoverflow 上肯定没有任何位置。
    • @DavidvanDriessche 我曾经担任 Stefano 的职位:我有一个简单的 PDF 库,但没有商业模式(2000 年)。我用我的日常工作赚钱,而不是用 iText。成千上万的人在使用它,但只有少数人做出了贡献。 AFAIK 现在 PdfClown 也是如此。然后我儿子得了癌症(2008 年),iText 几乎被抛弃了。模仿者出现了。那伤害。但后来我找到了一个商业模式(2009 年),我开始用 iText 赚钱。我聘请了一名员工(我们现在大约有 20 人),iText 成为使用最广泛的 PDF 库。当 Stefano 取笑这件事时,我回敬了 ;-)
    【解决方案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 案例的控制台输出示例:

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

    【讨论】:

    • 考虑到页面可能共享资源,是否还有一种方法可以计算选择的页面的内存需求但只计算一次共享资源?
    • @mkl 是的,可以肯定:org.pdfclown.tools.PageManager.getSize (getSize(Page page, SetvisitedReferences),请参阅clown.sourceforge.net/docs/api/org/pdfclown/tools/…)避免重复的共享资源(它也被 org.pdfclown.tools.PageManager.split(long) 的实现所使用)。
    • 好的,那么这个功能听起来很有用。
    • 顺便说一句,我认为这意味着 PDF Clown 不使用对象流进行最佳压缩。 PDF Box 上下文中的recent question 表明,它们的使用会对结果大小产生很大影响。不过,我不确定是否有任何通用 PDF 库完全使用了该 PDF 功能。
    • PDF Clown 支持 R/W 对象流,并且可以在保存文件时保留它们(如您引用的情况)。但是,考虑到有关主题的情况,您的假设是正确的: PageManager.split() 不使用对象流来实现进一步压缩(顺便说一句,我在 PDF Box 案例的 6MB 示例文件上进行了尝试,结果非常好: 6 个 1MB 的文件——可能很大一部分开销是由于数据与页面没有直接关联(我没有进一步调查)。无论如何,你提出了一个很好的观点:我将评估通过对象流挤压最后一个字节的便利性。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-08
    • 2017-07-23
    • 2022-01-07
    • 2019-07-30
    • 1970-01-01
    • 1970-01-01
    • 2017-09-16
    相关资源
    最近更新 更多