我已经创建了
合并和添加字体示例来解释不同的选项。
我们将使用这个代码片段来创建PDF:
public void createPdf(String filename, String text, boolean embedded, boolean subset) throws DocumentException, IOException {
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream(filename));
document.open();
BaseFont bf = BaseFont.createFont(FONT, BaseFont.WINANSI, embedded);
bf.setSubset(subset);
Font font = new Font(bf, 12);
document.add(new Paragraph(text, font));
document.close();
}
我们使用这段代码创建了3个测试文件,分别是1、2、3,我们将会执行3次:A、B、C。
第一次,我们使用参数
embedded = true
和
subset = true
,生成的文件为
testA1.pdf,其中包含文本
"abcdefgh"
(3.71 KB),
testA2.pdf,其中包含文本
"ijklmnopq"
(3.49 KB)和
testA3.pdf,其中包含文本
"rstuvwxyz"
(3.55 KB)。字体被嵌入,并且文件大小相对较小,因为我们只嵌入了字体的子集。
现在我们使用以下代码将这些文件合并,使用
smart
参数指示我们是否要使用
PdfCopy
或
PdfSmartCopy
:
public void mergeFiles(String[] files, String result, boolean smart) throws IOException, DocumentException {
Document document = new Document();
PdfCopy copy;
if (smart)
copy = new PdfSmartCopy(document, new FileOutputStream(result));
else
copy = new PdfCopy(document, new FileOutputStream(result));
document.open();
PdfReader[] reader = new PdfReader[3];
for (int i = 0; i < files.length; i++) {
reader[i] = new PdfReader(files[i]);
copy.addDocument(reader[i]);
}
document.close();
for (int i = 0; i < reader.length; i++) {
reader[i].close();
}
}
当我们使用
PdfCopy
或
PdfSmartCopy
合并文档时,相同字体的不同子集将作为单独的对象复制到生成的PDF文件中
testA_merged1.pdf /
testA_merged2.pdf(两者均为9.75 KB)。这是您遇到的问题:
PdfSmartCopy
可以检测和重用相同的对象,但相同字体的不同子集并不相同,iText无法将相同字体的不同子集合并为一个字体。
第二次,我们使用参数
embedded = true
和
subset = false
,生成文件
testB1.pdf(21.38 KB),
testB2.pdf(21.38 KB)和
testA3.pdf(21.38 KB)。字体已完全嵌入,单个文件的文件大小比之前大得多,因为完整字体已嵌入。
如果我们使用
PdfCopy
合并文件,则字体将在合并后的文档中冗余存在,导致膨胀的文件
testB_merged1.pdf(63.16 KB)。这绝不是您想要的!
然而,如果我们使用
PdfSmartCopy
,iText会检测到相同的字体流并重复使用它,导致生成的
testB_merged2.pdf (21.95 KB) 比使用
PdfCopy
生成的文件要小得多。虽然仍比包含子集字体的文档要大,但如果您要连接大量文件,则嵌入完整字体将获得更好的结果。
第三次,我们使用参数
embedded = false
和
subset = false
,得到的文件为
testC1.pdf (2.04 KB),
testC2.pdf (2.04 KB) 和
testC3.pdf (2.04 KB)。字体未嵌入,因此文件大小很小,但是如果与之前的结果进行比较,您会发现字体看起来完全不同。
我们使用
PdfSmartCopy
合并文件,得到
testC_merged1.pdf(2.6 KB)的结果。再次,文件大小很好,但是字体仍然无法正确显示。
为了解决这个问题,我们需要嵌入字体:
private void embedFont(String merged, String fontfile, String result) throws IOException, DocumentException {
RandomAccessFile raf = new RandomAccessFile(fontfile, "r");
byte fontbytes[] = new byte[(int)raf.length()];
raf.readFully(fontbytes);
raf.close();
PdfStream stream = new PdfStream(fontbytes);
stream.flateCompress();
stream.put(PdfName.LENGTH1, new PdfNumber(fontbytes.length));
PdfReader reader = new PdfReader(merged);
int n = reader.getXrefSize();
PdfObject object;
PdfDictionary font;
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(result));
PdfName fontname = new PdfName(BaseFont.createFont(fontfile, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED).getPostscriptFontName());
for (int i = 0; i < n; i++) {
object = reader.getPdfObject(i);
if (object == null || !object.isDictionary())
continue;
font = (PdfDictionary)object;
if (PdfName.FONTDESCRIPTOR.equals(font.get(PdfName.TYPE))
&& fontname.equals(font.get(PdfName.FONTNAME))) {
PdfIndirectObject objref = stamper.getWriter().addToBody(stream);
font.put(PdfName.FONTFILE2, objref.getIndirectReference());
}
}
stamper.close();
reader.close();
}
现在,我们有文件testC_merged2.pdf(22.03 KB),这实际上就是您问题的答案。正如您所看到的,第二个选项比第三个选项更好。
注意事项:此示例使用Gravitas One字体作为简单字体。一旦您将该字体用作复合字体(通过选择编码IDENTITY-H
或IDENTITY-V
告诉iText将其用作复合字体),您就不能再选择是否嵌入字体,是否对字体进行子集化。根据ISO-32000-1中定义,iText将始终嵌入复合字体并始终对它们进行子集化。
这意味着当您需要特殊字体(中文,日文,韩文)时,您无法使用上述解决方案。在这种情况下,您不应嵌入字体,而应使用所谓的CJK字体。CJK字体将使用可以通过Adobe Reader下载的字体包。