在 iText 中访问 OpenType 字形变体

Accessing OpenType glyph variants in iText

在 iText 中使用 OpenType 字体构建 PDF 文档时,我想从字体中访问字形变体——特别是表格图形。由于 OpenType 字形变体没有 Unicode 索引,我不确定如何指定我要使用一组特定的变体(表格数字)或通过字形 ID 调用特定字形。只是寻找相关的 iText class 名称(如果存在的话)。

这在 iText 的最新标签 5.5.8, nor in the master 分支中似乎是不可能的。

this article and in the Microsoft's OpenType font file specification 中所述,字形变体存储在字体文件的 Glyph Substitution Table (GSUB) 中。访问字形变体需要从文件中读取这个 table,它实际上是在 class com.itextpdf.text.pdf.fonts.otf.GlyphSubstitutionTableReader 中实现的,尽管这个 class 现在被禁用了。

classcom.itextpdf.text.pdf.TrueTypeFontUnicode中的调用readGsubTable()被注释掉了。

void process(byte ttfAfm[], boolean preload) throws DocumentException, IOException {
    super.process(ttfAfm, preload);
    //readGsubTable();
}

事实证明,这条线被禁用是有原因的,因为如果您尝试激活它,代码实际上不起作用。

因此,不幸的是,无法使用字形变体,因为永远不会从字体文件中加载替换信息。

更新

最初的答案是关于使用 iText API 访问开箱即用的字形变体的可能性,目前还没有。但是,低级代码已经到位,经过一些黑客攻击后可以使用它来访问字形替换映射 table。

当调用 read() 时,GlyphSubstitutionTableReader 读取 GSUB table 并将所有特征的替换扁平化为一个映射 Map<Integer, List<Integer>> rawLigatureSubstitutionMapOpenTypeFontTableReader 当前已丢弃要素的符号名称。 rawLigatureSubstitutionMapglyphId 变体映射到碱基 glyphId,或将连字 glyphId 映射到 glyphIds 的序列,如下所示:

629 -> 66 // a.feature -> a
715 -> 71, 71, 77 // ffl ligature

可以反转此映射以获得碱基 glyphId 的所有变体。因此,所有具有未知 unicode 值的扩展字形都可以通过它们与基本字形或字形序列的连接来计算。

接下来,为了能够将字形写入 PDF,我们需要知道该 glyphId 的 unicode 值。关系 unicode -> glyphIdTrueTypeFont 中的 cmap31 字段映射。反转地图通过 glyphId 给出 unicode。

调整

rawLigatureSubstitutionMap 无法在 GlyphSubstitutionTableReader 中访问,因为它是 private 成员并且没有 getter 访问器。最简单的破解方法是复制粘贴原始 class 并为地图添加一个 getter:

public class HackedGlyphSubstitutionTableReader extends OpenTypeFontTableReader {

    // copy-pasted code ...

    public Map<Integer, List<Integer>> getRawSubstitutionMap() {
        return rawLigatureSubstitutionMap;
    }
}

下一个问题是 GlyphSubstitutionTableReader 需要 GSUB table 的偏移量,信息存储在 TrueTypeFont protected HashMap<String, int[]> tables class .放置在同一个包中的助手 class 将桥接对 TrueTypeFont.

的受保护成员的访问
package com.itextpdf.text.pdf;

import com.itextpdf.text.pdf.fonts.otf.FontReadingException;
import java.io.IOException;
import java.util.List;
import java.util.Map;

public class GsubHelper {
    private Map<Integer, List<Integer>> rawSubstitutionMap;

    public GsubHelper(TrueTypeFont font) {
        // get tables offsets from the font instance
        Map<String, int[]> tables = font.tables;
        if (tables.get("GSUB") != null) {
            HackedGlyphSubstitutionTableReader gsubReader;
            try {
                gsubReader = new HackedGlyphSubstitutionTableReader(
                        font.rf, tables.get("GSUB")[0], glyphToCharacterMap, font.glyphWidthsByIndex);
                gsubReader.read();
            } catch (IOException | FontReadingException e) {
                throw new IllegalStateException(e.getMessage());
            }
            rawSubstitutionMap = gsubReader.getRawSubstitutionMap();
        }
    }

    /** Returns a glyphId substitution map
     */
    public Map<Integer, List<Integer>> getRawSubstitutionMap() {
        return rawSubstitutionMap;
    }
}

扩展 TrueTypeFont 会更好,但这不适用于 BaseFont 的工厂方法 createFont(),后者在创建时依赖于硬编码的 class 名称一种字体。