如何将 PDF 合并到一个文件中而没有多个相同字体的副本?

jon*_*eok 4 pdf fonts itext

我创建 PDF 并将它们连接成一个 PDF。

我生成的 PDF 比我预期的文件大小要大得多。

我意识到我的输出 PDF 有大量重复的字体,这是文件大小意外大的原因。

在这里,我的问题是:

我想创建只嵌入字体信息的 PDF,所以让他们使用 Windows 系统字体。

当我将它们合并为一个 PDF 时,我会插入 PDF 需要的实际字体。

如果可能,请告诉我如何操作。

Bru*_*gie 6

我创建了MergeAndAddFont示例来解释不同的选项。

我们将使用以下代码片段创建 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();
}
Run Code Online (Sandbox Code Playgroud)

我们使用此代码创建 3 个测试文件,1、2、3,我们将执行 3 次:A、B、C。

第一次,我们使用参数embedded = truesubset = true,产生带有文本的文件testA1.pdf"abcdefgh" (3.71 KB)、带有文本的testA2.pdf"ijklmnopq" (3.49 KB) 和带有文本的testA3.pdf"rstuvwxyz" (3.55 KB)。字体是嵌入的,文件大小相对较小,因为我们只嵌入了字体的一个子集。

现在我们使用以下代码合并这些文件,使用smart参数来指示我们是否要使用PdfCopyPdfSmartCopy

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

当我们使用PdfCopy或合并文档时,PdfSmartCopy相同字体的不同子集将作为单独的对象复制到生成的 PDF testA_merged1.pdf / testA_merged2.pdf(均为 9.75 KB)中。

这是您遇到的问题:PdfSmartCopy可以检测和重用相同的对象,但相同字体的不同子集不相同,iText 无法将相同字体的不同子集合并为一种字体。

第二次,我们使用参数embedded = truesubset = 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 = falsesubset = 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 {
    // 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();
}
Run Code Online (Sandbox Code Playgroud)

现在,我们有了testC_merged2.pdf (22.03 KB)文件,这实际上就是您问题的答案。如您所见,第二个选项比第三个选项更好。

注意事项:本示例使用 Gravitas One 字体作为简单字体。一旦您将字体用作复合字体(您通过选择编码IDENTITY-H或告诉 iText 将其用作复合字体IDENTITY-V),您就无法再选择是否嵌入字体、是否对字体进行子集化。如 ISO-32000-1 中所定义,iText 将始终嵌入复合字体并始终对其进行子集化。

这意味着当您需要特殊字体(中文、日文、韩文)时,无法使用上述解决方案。在这种情况下,您不应嵌入字体,而应使用所谓的 CJK 字体。他们的 CJK 字体将使用可通过 Adob​​e Reader 下载的字体包。