在iText中访问OpenType字形变体

27

在使用iText构建OpenType字体的PDF文档时,我想从字体内部访问字形变体,特别是制表符数字。由于OpenType字形变体没有Unicode索引,我不确定如何指定要使用特定的变体集(制表符数字)或按其字形ID调用特定的字形。只是寻找相关的iText类名称,如果存在的话。


1
也许值得一看Class GlyphSubstitutionTableReader。它使用OpenTypeFontTableReader,但是乍一看,似乎你必须事先知道要读取哪个表格。(仍然不太清楚您可以用获取的原始“字形”做什么。) - Jongware
这对你有帮助吗?GlyphList类有一个静态方法:String unicodeToName(int) [http://developers.itextpdf.com/reference/com.itextpdf.text.pdf.GlyphList] - J. V. A.
1个回答

10

在最新的标签5.5.8和iText的主代码分支中,似乎都不可能实现。

正如本文和微软的OpenType字体文件规范所解释的那样,字形变体存储在字体文件的Glyph Substitution Table (GSUB)中。要访问字形变体需要从文件中读取此表格,这实际上是在类com.itextpdf.text.pdf.fonts.otf.GlyphSubstitutionTableReader中实现的,尽管目前该类被禁用。

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

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

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

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

更新

最初的答案是关于使用iText API直接访问字形变体的可能性,目前还不存在。但是,低级代码已经就位,可以通过一些黑客技巧来访问字形替换映射表。

当调用read()时,GlyphSubstitutionTableReader读取GSUB表,并将所有特征的替换展开到一个地图Map<Integer, List<Integer>> rawLigatureSubstitutionMap中。特征的符号名称当前由OpenTypeFontTableReader丢弃。 rawLigatureSubstitutionMapglyphId变体映射到基本glyphId,或将连字glyphId映射到glyphIds序列,如下所示:

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

通过反向映射可以获得所有基础字形 glyphId 的所有变体。因此,通过它们与基础字形或一系列字形的关联,可以找出所有具有未知Unicode值的扩展字形。

接下来,为了能够将字形写入PDF,我们需要知道该 glyphId 对应的Unicode值。在 TrueTypeFont 中,cmap31 字段映射了一个关系 unicode -> glyphId。反向映射可以通过glyphId得到Unicode值。

调整

GlyphSubstitutionTableReader 中无法访问 rawLigatureSubstitutionMap,因为它是一个 private 成员,没有 getter 方法。最简单的方法是复制原始类并添加映射表的getter方法:

public class HackedGlyphSubstitutionTableReader extends OpenTypeFontTableReader {

    // copy-pasted code ...

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

下一个问题是GlyphSubstitutionTableReader需要TrueTypeFont类的protected HashMap<String, int[]> tables中存储的GSUB表的偏移量信息。将一个辅助类放到同一个包中,可以访问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() 不兼容,因为在创建字体时依赖硬编码的类名。


你说话和引用时很有权威性,但我很难相信用Java无法解析GSUB表。你的意思是说,即使你可以解析GSUB表,iText也不包含处理“原始”字形索引的基础设施,因此,在添加GSUB替换的过程中,iText当前的文本处理范例需要完全替换吗? - Jongware
1
抱歉,我的回答过于绝对,甚至有些愚蠢。它并没有反映全部真相。我的意思是,在当前版本的iText中,使用字形替换的API尚未完成。但基础设施主要已经就位。可以通过复制粘贴和调整iText类来使用它。我将会用更多的细节更新我的回答。 - nolexa
1
我认为GSUB的低级别还可以。数据的每个部分似乎都是从文件中解析出来的(尽管没有针对此功能进行测试)。但API层未完成,原始数据仍然封装在基础设施类中,没有通过有意义的用例传递到API。同时,这个故事中的关键iText类没有设计为可扩展的,这使得定制解决方案变得困难。 - nolexa
我认为你混淆了masterdevelop分支。master分支的最新版本总是与最新的发布标签相同。develop分支包含当前(预发布)的开发版本。我应该知道,因为我是创建这些标签的人。 :) - Amedee Van Gasse
@AmedeeVanGasse 是的,你说得对,我确实这样做了 :) 但是对于这个故事涉及到的代码部分并不重要。它在最新的发布标签之前7-10个月就没有改变过了。 - nolexa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接