我已经创建了合并并添加字体 https://kb.itextpdf.com/home/it5kb/examples/fonts-and-merging-documents示例来解释不同的选项。
我们将使用以下代码片段创建 PDF:
public void createPdf(String filename, String text, boolean embedded, boolean subset) throws DocumentException, IOException {
// step 1
Document document = new Document();
// step 2
PdfWriter.getInstance(document, new FileOutputStream(filename));
// step 3
document.open();
// step 4
BaseFont bf = BaseFont.createFont(FONT, BaseFont.WINANSI, embedded);
bf.setSubset(subset);
Font font = new Font(bf, 12);
document.add(new Paragraph(text, font));
// step 5
document.close();
}
我们使用此代码创建 3 个测试文件 1、2、3,我们将执行 3 次:A、B、C。
第一次,我们使用参数embedded = true
and subset = true
,产生文件测试A1.pdf https://github.com/itext/i5js-sandbox/raw/master/cmpfiles/fonts/cmp_testA1.pdf有文字"abcdefgh"
(3.71 KB),测试A2.pdf https://github.com/itext/i5js-sandbox/raw/master/cmpfiles/fonts/cmp_testA2.pdf有文字"ijklmnopq"
(3.49 KB) 和测试A3.pdf https://github.com/itext/i5js-sandbox/raw/master/cmpfiles/fonts/cmp_testA3.pdf有文字"rstuvwxyz"
(3.55 KB)。字体是嵌入的,并且文件大小相对较小,因为我们只嵌入了字体的子集。
现在我们使用以下代码合并这些文件,使用smart
参数来指示我们是否要使用PdfCopy
or 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
or PdfSmartCopy
,相同字体的不同子集将作为单独的对象复制到生成的 PDF 中testA_merged1.pdf https://github.com/itext/i5js-sandbox/raw/master/cmpfiles/fonts/cmp_testA_merged1.pdf / testA_merged2.pdf https://github.com/itext/i5js-sandbox/raw/master/cmpfiles/fonts/cmp_testA_merged2.pdf(均为 9.75 KB)。
这是您遇到的问题:PdfSmartCopy
可以检测并重复使用相同的对象,但同一字体的不同子集不相同,并且 iText 无法将同一字体的不同子集合并为一种字体。
第二次,我们使用参数embedded = true
and subset = false
,产生文件测试B1.pdf https://raw.githubusercontent.com/itext/i5js-sandbox/master/cmpfiles/fonts/cmp_testB1.pdf(21.38 KB),测试B2.pdf https://raw.githubusercontent.com/itext/i5js-sandbox/master/cmpfiles/fonts/cmp_testB2.pdf(21.38 KB) 和测试A3.pdf https://raw.githubusercontent.com/itext/i5js-sandbox/master/cmpfiles/fonts/cmp_testB.pdf(21.38 KB)。字体是完全嵌入的,并且由于嵌入了完整的字体,单个文件的文件大小比以前大很多。
如果我们使用合并文件PdfCopy
,字体会多余地出现在合并后的文档中,导致文件臃肿testB_merged1.pdf https://github.com/itext/i5js-sandbox/raw/master/cmpfiles/fonts/cmp_testB_merged1.pdf(63.16 KB)。这绝对不是你想要的!
但是,如果我们使用PdfSmartCopy
,iText 检测到相同的字体流并重用它,导致testB_merged2.pdf https://github.com/itext/i5js-sandbox/raw/master/cmpfiles/fonts/cmp_testB_merged2.pdf(21.95 KB) 比我们使用的小得多PdfCopy
。它仍然比带有子集字体的文档大,但如果您要连接大量文件,则嵌入完整字体的结果会更好。
第三次,我们使用参数embedded = false
and subset = false
,产生文件测试C1.pdf https://github.com/itext/i5js-sandbox/raw/master/cmpfiles/fonts/cmp_testC1.pdf(2.04 KB),测试C2.pdf https://github.com/itext/i5js-sandbox/raw/master/cmpfiles/fonts/cmp_testC2.pdf(2.04 KB) 和测试C3.pdf https://github.com/itext/i5js-sandbox/raw/master/cmpfiles/fonts/cmp_testC3.pdf(2.04 KB)。该字体未嵌入,因此文件大小非常好,但如果与之前的结果之一进行比较,您会发现该字体看起来完全不同。
我们使用合并文件PdfSmartCopy
, 导致testC_merged1.pdf https://github.com/itext/i5js-sandbox/raw/master/cmpfiles/fonts/cmp_testC_merged1.pdf(2.6 KB)。同样,我们有一个很好的文件大小,但我们再次遇到字体无法正确显示的问题。
为了解决这个问题,我们需要嵌入字体:
private void embedFont(String merged, String fontfile, String result) throws IOException, DocumentException {
// the font file
RandomAccessFile raf = new RandomAccessFile(fontfile, "r");
byte fontbytes[] = new byte[(int)raf.length()];
raf.readFully(fontbytes);
raf.close();
// create a new stream for the font file
PdfStream stream = new PdfStream(fontbytes);
stream.flateCompress();
stream.put(PdfName.LENGTH1, new PdfNumber(fontbytes.length));
// create a reader object
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 https://github.com/itext/i5js-sandbox/raw/master/cmpfiles/fonts/cmp_testC_merged2.pdf(22.03 KB) 这实际上是您问题的答案。正如您所看到的,第二个选项比第三个选项更好。
Caveats:此示例使用 Gravitas One 字体作为简单的字体。一旦您使用该字体作为复合字体(您通过选择编码来告诉 iText 将其用作复合字体IDENTITY-H
or IDENTITY-V
),您无法再选择是否嵌入字体、是否对字体进行子集化。根据 ISO-32000-1 中的定义,iText 将始终嵌入复合字体并始终将其子集化。
这意味着当您需要特殊字体(中文、日文、韩文)时,您不能使用上述解决方案。在这种情况下,您不应嵌入字体,而应使用所谓的 CJK 字体。他们的CJK字体将使用可以通过Adobe Reader下载的字体包。