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

如何根据大小限制拆分PDF?,pdf,itext,pdfbox,Pdf,Itext,Pdfbox,我已经搜索了很多地方,但找不到一个很好的解决方案。 因此,我正在努力实现以下目标: 我的程序将有相当多的PDF文档,我将不得不通过邮件发送。邮件服务器限制为4 MB。因此,如果所有PDF小于4MB,它将作为单个邮件发送。否则,我将不得不创建多个文件,每个小于4 MB。 现在,我的程序在以下情况下运行良好: 1:有很多文件,但每个文件都小于4MB,因此在合并过程中保留一个选项卡,这样合并的文件都不会超过4MB。 2:所有文件都非常小,因此合并它们不会达到4MB的限制 但也可能有这样一种情况,即有一

我已经搜索了很多地方,但找不到一个很好的解决方案。 因此,我正在努力实现以下目标: 我的程序将有相当多的PDF文档,我将不得不通过邮件发送。邮件服务器限制为4 MB。因此,如果所有PDF小于4MB,它将作为单个邮件发送。否则,我将不得不创建多个文件,每个小于4 MB。 现在,我的程序在以下情况下运行良好: 1:有很多文件,但每个文件都小于4MB,因此在合并过程中保留一个选项卡,这样合并的文件都不会超过4MB。 2:所有文件都非常小,因此合并它们不会达到4MB的限制


但也可能有这样一种情况,即有一个文件,比如说14MB。我可以把那个文件分成几页。但这也不是一个好的解决方案,因为页面大小也不是均匀分布在页面上的。我使用了iText和PDFBox。如有任何帮助/指示,将不胜感激

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

每页使用四个字体子集,每个大约50KB 一页上有十幅图像,每幅约200KB,每页一幅 每个页面上有四个图像,每个约50KB 10个页面,每个页面的内容流约为25KB 目录、信息字典、页面树、交叉引用表等对象的大小约为350 KB。。。 单个页面至少需要: -四个字体子集:4乘以50KB -单个图像:1倍200KB -四幅图像:4乘以50KB -单个内容流:1倍50KB -一个稍微缩小的交叉引用表,一个稍微缩小的页面树,一个几乎相同的目录,一个相同大小的信息字典,。。。200 KB

总共是850 KB。这意味着,如果您将一个10页3000 KB的PDF文档拆分为10个单独的页面,那么最终的结果是8500 KB乘以850 KB

这个例子是基于经验的猜测工作的结果,它假设PDF是可预测的。大多数PDF不是:

有些页面需要高清图像,甚至可能是兆字节,其他页面没有任何图像, 一些页面将需要许多不同的字体和大量的千字节字体子集,其他页面将只包含一些矢量图形,如果压缩的话,这些内容流很小。 不同的页面可以共享大量资源,例如XObject、Image XObject等,其他页面不会共享任何资源。 等等 您已经注意到,在您编写时:我可以将该文档按页拆分。但这也不是一个好的解决方案,因为页面大小也不是均匀分布在页面上的

这就是为什么你的问题只能有一个答案:你必须反复试验。在查看页面所需的空间之前,没有软件可以预测页面所需的空间

更新:

正如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();
}
您可以看到副本的文件大小是如何随着添加的每个页面而逐渐增大的。添加所有页面后,大小为999140字节,然后写入页面树和交叉引用流,再添加3369字节

其中显示//检查reader.getPageNi;所需的资源;,您可以猜测将为页面添加的大小,如果该大小超过最大值,则可以中断循环

为什么这是一个猜测:

您可能正在计算已添加的对象。如果你跟踪那些不那么困难的物体,你的猜测就会更准确。 我正在使用PdfSmartCopy。假设PDF中有两个相同的对象。糟糕的PDF软件通常会导致此类问题。例如:相同的图像字节被添加到文件中两次。PdfSmartCopy可以检测到这一点,并将重用它遇到的第一个对象,而不是添加额外对象的冗余字节。 我们目前在PdfReader中没有reader.getTotalPageBytes,因为PdfReader试图使用尽可能少的内存。只要不需要这些对象,它就不会将任何对象加载到内存中。因此,在导入页面之前,它不知道每个对象的大小

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

更新:

在下一个版本中,您将发现一个名为的工具,它依赖于名为的新类。您可以这样使用它:

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。

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

每页使用四个字体子集,每个大约50KB 十张照片 i在单个页面上显示,每个页面大约200KB,每页一个图像 每个页面上有四个图像,每个约50KB 10个页面,每个页面的内容流约为25KB 目录、信息字典、页面树、交叉引用表等对象的大小约为350 KB。。。 单个页面至少需要: -四个字体子集:4乘以50KB -单个图像:1倍200KB -四幅图像:4乘以50KB -单个内容流:1倍50KB -一个稍微缩小的交叉引用表,一个稍微缩小的页面树,一个几乎相同的目录,一个相同大小的信息字典,。。。200 KB

总共是850 KB。这意味着,如果您将一个10页3000 KB的PDF文档拆分为10个单独的页面,那么最终的结果是8500 KB乘以850 KB

这个例子是基于经验的猜测工作的结果,它假设PDF是可预测的。大多数PDF不是:

有些页面需要高清图像,甚至可能是兆字节,其他页面没有任何图像, 一些页面将需要许多不同的字体和大量的千字节字体子集,其他页面将只包含一些矢量图形,如果压缩的话,这些内容流很小。 不同的页面可以共享大量资源,例如XObject、Image XObject等,其他页面不会共享任何资源。 等等 您已经注意到,在您编写时:我可以将该文档按页拆分。但这也不是一个好的解决方案,因为页面大小也不是均匀分布在页面上的

这就是为什么你的问题只能有一个答案:你必须反复试验。在查看页面所需的空间之前,没有软件可以预测页面所需的空间

更新:

正如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();
}
您可以看到副本的文件大小是如何随着添加的每个页面而逐渐增大的。添加所有页面后,大小为999140字节,然后写入页面树和交叉引用流,再添加3369字节

其中显示//检查reader.getPageNi;所需的资源;,您可以猜测将为页面添加的大小,如果该大小超过最大值,则可以中断循环

为什么这是一个猜测:

您可能正在计算已添加的对象。如果你跟踪那些不那么困难的物体,你的猜测就会更准确。 我正在使用PdfSmartCopy。假设PDF中有两个相同的对象。糟糕的PDF软件通常会导致此类问题。例如:相同的图像字节被添加到文件中两次。PdfSmartCopy可以检测到这一点,并将重用它遇到的第一个对象,而不是添加额外对象的冗余字节。 我们目前在PdfReader中没有reader.getTotalPageBytes,因为PdfReader试图使用尽可能少的内存。只要不需要这些对象,它就不会将任何对象加载到内存中。因此,在导入页面之前,它不知道每个对象的大小

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

更新:

在下一个版本中,您将发现一个名为的工具,它依赖于名为的新类。您可以这样使用它:

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。

支持:自2010年以来,它一直采用一种专用方法,在内存中计算实际页面数据大小,而无需将其写入文件进行试用

此外,还有另一种方法是专门实现的,用于解决您的这种情况,它利用了上述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是包含所有先前页面和当前页面的页面子列表的数据大小。 支持:自2010年以来,它一直采用一种专用方法,在不使用t的情况下,在内存中计算实际页面数据大小 他需要把它写进一个文件以供审判

此外,还有另一种方法是专门实现的,用于解决您的这种情况,它利用了上述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是包含所有先前页面和当前页面的页面子列表的数据大小。

先生,你刚刚证实了我的怀疑!谢谢!我会改变我的设计。我不明白为什么你会说这是不可能的-你是对的,这很难,但软件实际上可以做这些计算,并得出正确的答案,没有?@davidvandriesche也许布鲁诺过于自负的自我认为在PDF软件的世界里只有iText:iText做不到的任何事情都不能由任何其他软件来完成,顾名思义开玩笑!如果您不在,或者您公司的任何人都不在,我认为PDF对您来说并不重要,因为您没有为即将推出的PDF 2.0标准做出贡献;-这太卑鄙了,布鲁诺,简直是一种侮辱。“它在Stackoverflow上肯定没有任何位置。”Davidvandriesche我曾经担任Stefano的职务:我有一个简单的PDF库,没有2000年的商业模式。我靠我的日常工作赚钱,而不是靠iText。成千上万的人在使用它,但只有少数人在贡献。好了,现在PdfClown也是这样。2008年,我儿子得了癌症,iText几乎被遗弃。模仿者出现了。那很痛。但后来我在2009年找到了一个商业模式,我开始用iText赚钱。我雇佣了一名员工,我们现在大约有20人,iText成为了使用最广泛的PDF库。当斯蒂法诺取笑我时,我回报了他-先生,你刚刚证实了我的怀疑!谢谢!我会改变我的设计。我不明白为什么你会说这是不可能的-你是对的,这很难,但软件实际上可以做这些计算,并得出正确的答案,没有?@davidvandriesche也许布鲁诺过于自负的自我认为在PDF软件的世界里只有iText:iText做不到的任何事情都不能由任何其他软件来完成,顾名思义开玩笑!如果您不在,或者您公司的任何人都不在,我认为PDF对您来说并不重要,因为您没有为即将推出的PDF 2.0标准做出贡献;-这太卑鄙了,布鲁诺,简直是一种侮辱。“它在Stackoverflow上肯定没有任何位置。”Davidvandriesche我曾经担任Stefano的职务:我有一个简单的PDF库,没有2000年的商业模式。我靠我的日常工作赚钱,而不是靠iText。成千上万的人在使用它,但只有少数人在贡献。好了,现在PdfClown也是这样。2008年,我儿子得了癌症,iText几乎被遗弃。模仿者出现了。那很痛。但后来我在2009年找到了一个商业模式,我开始用iText赚钱。我雇佣了一名员工,我们现在大约有20人,iText成为了使用最广泛的PDF库。当斯蒂法诺取笑我时,我回报了他-考虑到页面可能共享资源,是否还有一种方法可以计算一组页面的内存需求,这些页面只计算一次共享资源?@mkl是的,可以肯定:org.pdfclown.tools.PageManager.getSize getSizePage、Set visitedReferences、,看哪一个跟踪共享资源以避免重复它也被org.pdfclown.tools.PageManager.splitlong.Ok的实现所使用,那么这个功能听起来是可用和有用的。顺便说一句,我假设这意味着对象流没有被PDF小丑用于最佳压缩。在PDF框的上下文中显示,它们的使用可以对结果大小产生很大的影响。不过,我不确定是否有任何通用PDF库使用了该PDF功能。PDF小丑支持R/W对象流,并且可以在保存文件时保留它们,就像您引用的案例一样。但是,考虑到主题的情况,您的假设是正确的:PageManager.split不使用对象流来实现进一步的压缩,我在PDF Box案例的6MB示例文件上进行了尝试,结果非常好:61MB文件-可能很大一部分开销是由于数据与我没有进一步研究的页面没有直接关联。不管怎样,你说得很好:我将评估使用sque的便利性
eze通过对象流计算最后的字节。考虑到页面可能共享资源,是否也有一种方法计算一组页面的内存需求,这些页面只计算一次共享资源?@mkl是的,可以肯定:org.pdfclown.tools.PageManager.getSize getSizePage页面、Set visitedReferences、,看哪一个跟踪共享资源以避免重复它也被org.pdfclown.tools.PageManager.splitlong.Ok的实现所使用,那么这个功能听起来是可用和有用的。顺便说一句,我假设这意味着对象流没有被PDF小丑用于最佳压缩。在PDF框的上下文中显示,它们的使用可以对结果大小产生很大的影响。不过,我不确定是否有任何通用PDF库使用了该PDF功能。PDF小丑支持R/W对象流,并且可以在保存文件时保留它们,就像您引用的案例一样。但是,考虑到主题的情况,您的假设是正确的:PageManager.split不使用对象流来实现进一步的压缩,我在PDF Box案例的6MB示例文件上进行了尝试,结果非常好:61MB文件-可能很大一部分开销是由于数据与我没有进一步研究的页面没有直接关联。无论如何,您提出了一个很好的观点:我将评估通过对象流压缩最后字节的便利性。