想象一个 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); 的地方,您可以猜测将为页面添加的大小,如果超过最大值则退出循环。
为什么这是一个猜测:
- 您可能正在计算已添加的对象。如果您跟踪对象(没那么难),您的猜测会更准确。
- 我正在使用
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。