调整字符宽度后嵌入 PDFont

Embedding PDFont after adjusting character widths

我想使用 Apache PDFBox 创建一个符合 PDF/A 标准的 PDF 文件。为了符合 PDF/A,必须嵌入所有使用的字体。我可以使用标准字体或从文件中加载一个,但我需要调整几个字形的字符宽度。我可以通过加载字体(或使用标准字体)然后修改它来完成此操作,如下所示。

doc = new PDDocument();
PDPage page = new PDPage();
doc.addPage( page );
InputStream fontStream = PDFCreator.class.getResourceAsStream("ArialMT.ttf");
PDFont font = PDTrueTypeFont.loadTTF(doc, fontStream);
List<Float> test = font.getWidths();
test.set(101-32, 2000f);
font.setWidths(test);

但是如何嵌入修改后的字体呢?

将原始字体嵌入经过修补的 宽度 字体字典条目

如果您使用您操纵的字体,它将被嵌入。如果你例如像这样继续你的代码:

PDPageContentStream stream = new PDPageContentStream(doc, page);
stream.setFont(font, 12);
stream.beginText();
stream.moveTextPositionByAmount(30, 600);
stream.drawString("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
stream.moveTextPositionByAmount(0, -20);
stream.drawString("abcdefghijklmnopqrstuvwxyz");
stream.moveTextPositionByAmount(0, -20);
stream.drawString("0123456789");
stream.endText();
stream.close();

doc.save("embedFont.pdf");

您使用 PDFBox 1.8.8 获得这样的 PDF:

如你所见,你对宽度的操纵'e'

test.set(101-32, 2000f);

使该字母的 space 相当宽泛。

如果您查看 PDF,您会在字体字典中找到这个 Widths 数组:

/Widths [278.0 278.0 355.0 556.0 556.0 889.0 667.0 191.0 333.0 333.0
389.0 584.0 278.0 333.0 278.0 278.0 556.0 556.0 556.0 556.0
556.0 556.0 556.0 556.0 556.0 556.0 278.0 278.0 584.0 584.0
584.0 556.0 1015.0 667.0 667.0 722.0 722.0 667.0 611.0 778.0
722.0 278.0 500.0 667.0 556.0 833.0 722.0 778.0 667.0 778.0
722.0 667.0 611.0 722.0 667.0 944.0 667.0 667.0 611.0 278.0
278.0 278.0 469.0 556.0 333.0 556.0 556.0 500.0 556.0 2000.0 
...

你的 2000 没问题。就 PDF 而言,您的更改已存储。

不可否认,'e'在嵌入字体程序中的宽度没有改变。如果你想改变它,你应该预处理调整宽度的字体文件:

嵌入补丁字体

您可以使用例如Google 的 sfntly 即时修补字体。在那种情况下,模拟代码可能如下所示:

byte[] fontBytes = null;
try (   InputStream arialMtResource = getClass().getResourceAsStream("ArialMT.ttf");
        ByteArrayOutputStream baos = new ByteArrayOutputStream()   )
{
    patchAdvanceWidth(arialMtResource, baos, 101-29, 2000);
    fontBytes = baos.toByteArray();
}

try (   ByteArrayInputStream fontStream = new ByteArrayInputStream(fontBytes);   )
{
    PDDocument doc = new PDDocument();
    PDPage page = new PDPage();
    doc.addPage(page);
    PDFont font = PDTrueTypeFont.loadTTF(doc, fontStream);
    PDPageContentStream stream = new PDPageContentStream(doc, page);
    stream.setFont(font, 12);
    stream.beginText();
    stream.moveTextPositionByAmount(30, 600);
    stream.drawString("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
    stream.moveTextPositionByAmount(0, -20);
    stream.drawString("abcdefghijklmnopqrstuvwxyz");
    stream.moveTextPositionByAmount(0, -20);
    stream.drawString("0123456789");
    stream.endText();
    stream.close();

    doc.save("target/test-outputs/embedPatchedFont.pdf");
}

使用这个辅助方法:

void patchAdvanceWidth(InputStream is, OutputStream os, int entry, int newValue) throws IOException
{
    FontFactory fontFactory = FontFactory.getInstance();
    Builder[] builders = fontFactory.loadFontsForBuilding(is);
    Builder builder = builders[0];

    HorizontalMetricsTable.Builder hmtxBuilder = (HorizontalMetricsTable.Builder) builder.getTableBuilder(Tag.hmtx);
    WritableFontData hmtxData = hmtxBuilder.data();

    int offset = 0 + (entry * 4) + 0;
    hmtxData.writeUShort(offset, newValue);
    hmtxBuilder.setData(hmtxData);

    Font font = builder.build();
    fontFactory.serializeFont(font, os);
}