如何从 Opentype 字体的 GPOS 表中使用和提取字距调整对,以在 Java 中将字形正确显示为 Path2D?

Posted

技术标签:

【中文标题】如何从 Opentype 字体的 GPOS 表中使用和提取字距调整对,以在 Java 中将字形正确显示为 Path2D?【英文标题】:How can I use and extract kerning pairs from from GPOS table in Opentype fonts to correctly show glyphs as Path2D in Java? 【发布时间】:2021-02-25 17:47:59 【问题描述】:

这个问题与很久以前提出的几个问题有关。我看到 cmets 说 Java 不支持 Opentype 字体,但这是 11 年前的事了。现在他们是。唯一的问题是 kerning pair 仅在 GPOS 表中给出。我已经看到它们在那里,但很难确保代码是正确的。

我目前正在转储 GPOS 表,试图跟随指针直到字距调整对。

到目前为止的代码如下,其中 GPOS 表之前已复制到数组gpos。转储表的函数是dumpGPOS()。我需要帮助来确定我所做的是否正确以及如何编写 TODO 部分:

byte[] gpos;

char[] hexasc( char[] hex, byte num ) 
    int up = num >> 4;
    int down = num & 15;
    hex[0] = (char) ((up < 10)? '0' + up : 'A' + up - 10);
    hex[1] = (char) ((down < 10)? '0' + down : 'A' + down - 10);
    return hex;


char[] hex =  '0', '0' ;
void printHex(byte b) 
    hexasc(hex, b);
    System.out.print(hex[0]);
    System.out.print(hex[1]);


void dumpGPOS() 
    int i, j;
    System.out.println("GPOS header");
    System.out.print("Version:        ");
    for ( i = 0; i < 4; i++ ) printHex(gpos[i]);
    System.out.println("    (" + (gpos[0] << 8 | gpos[1]) + "." + (gpos[2] << 8 | gpos[3]) + ")" );
    j = i;
    System.out.print("TheScriptList:        ");
    for ( i = 4; i < 6; i++ ) printHex(gpos[i]);
    System.out.println("        (" + (gpos[j] << 8 | gpos[j+1]) + ")" );
    j = i;
    System.out.print("TheFeatureList:        ");
    for ( i = 6; i < 8; i++ ) printHex(gpos[i]);
    System.out.println("        (" + (gpos[j] << 8 | gpos[j+1]) + ")" );
    j = i;
    System.out.print("TheLookupList:        ");
    for ( i = 8; i < 10; i++ ) printHex(gpos[i]);
    int lookup = (gpos[j] << 8 | gpos[j+1]);
    System.out.println("        (" + lookup + ")" );
    j = i;

    System.out.println("Lookup List Table");
    System.out.print("lookupCount:        ");
    for ( i = lookup; i <= lookup+1; i++ ) printHex(gpos[i]);
    System.out.print('\n');
    int count = (gpos[lookup] << 8 | gpos[lookup+1]);
    int tab = lookup + 2;
    int[] LookupList = new int[count];
    for ( j = 0; j < count; j++ ) 
        System.out.print("lookup[" + j + "] =         ");
        printHex(gpos[tab]);
        printHex(gpos[tab + 1]);
        System.out.println("        (" + ( LookupList[j] = (gpos[tab] << 8 | gpos[tab+1]) ) + ")" );
        tab += 2;
    
    int item, sub, size;
    for ( j = 0; j < count; j++ ) 
        item = lookup + LookupList[j];
        System.out.println("Lookup [" + j + "]");
        System.out.println("Lookup Type:        " + (gpos[item] << 8 | gpos[item + 1]) );
        System.out.print("Lookup flag:        ");
        printHex(gpos[item + 2]);
        printHex(gpos[item + 3]);
        size = (gpos[item + 4] << 8 | gpos[item + 5]);
        System.out.println("\nNumber of subtables:    "  + size);
        sub = item + 6;
        int[] subTable = new int[size];
        System.out.println("Subtable offsets");
        for ( i = 0; i < size; i++ ) 
            subTable[i] = (gpos[sub] << 8 | gpos[sub +1 ]);
            sub += 2;
            System.out.println( "    " + subTable[i] );
        
        for ( i = 0; i < size; i++ ) 
            System.out.println("Subtable [" + i + "]");
            sub = item + subTable[i];
            printSubtable(sub);
        
    


void printSubtable(int sub) 
    int format = gpos[sub] << 8 | gpos[sub + 1];
    System.out.println("Format:        " + format );
    if (format == 1) 
        /* TODO format 1*/
        return;
    
    /* TODO format 2*/

这个问题也和问题How to use kerning pairs extracted from a TTF file to correctly show glyphs as Path2D in Java?有关。

上下文是相同的,但由于Apache FOP 不读取 Opentype 字体,并且 Opentype 字体仅使用 GPOS 表中的字距调整信息,因此要困难得多。

我使用的是 Microsoft Opentype 参考,但它太过分了(太模糊,没有图纸,没有示例代码,也没有足够的示例)。还有什么提示、一些图纸、代码 sn-ps、更多示例(尤其是用于从 GPOS中提取字距调整表)? p>

我怎样才能 100% 确定这段代码确实在做它应该做的事情?

【问题讨论】:

虽然 GPOS 处理(而不是古老的 kern 表处理)可能看起来有点过头了,但非常困难,因为语言非常困难:GPOS 是脚本, langsys,并且依赖于功能,并且可能需要以特定顺序多次通过各种 GPOS 查找来执行正确的整形。最好的办法是查看其他人已经编写的解析器代码(Harfbuzz、Opentype.js 等)。但是,在正确的位置发帖也是明智之举:typedrawers.com 是迄今为止询问字体工程的更好论坛 =) 嗨@Mike'Pomax'Kamermans!自 80 年代末以来,我一直从事图形工作。您的贝塞尔曲线入门是一项了不起的工作!恭喜!我觉得语言并不难。可怕的是 Truetype 和 Opentype(尤其是第二个)的文档。我知道你是一个 JS 人,但那是我讨厌的语言。 Harfbuss 是用 C++ 编写的,我已经编写了很多年,但是 C++ 中的代码并不是特别清晰,除非它有很好的注释。感谢 typedrawers.com 的建议。我一定会检查出来的。我不知道它存在。太久没有认真做字体了。 我不反对讨厌 JS 的人,它可以在网络上运行,但网络只是网络(即使它有很深的影响力)。 Harfbuzz 基本上是解析方面的权威,Behdad 在这方面做得非常好,但如果它的来源不够,大多数 OG 字体的人都在打字机上,所以如果你想从字面上的人那里得到建议编写 OT 规范:这就是应该做的地方 =) @Mike'Pomax'Kamermans,我下载了Opentype.js 和font-inspector.html。我在本地使用 font-inspector.html 运行它。我还复制了 site.css 进行格式化。我修改了inspector.html 以显示GPOS 表。它有效,但很奇怪。我必须使用按钮加载文件。它无法加载我在代码中提供的文件。这就是我讨厌这种语言的原因,:-))) 虽然说起来非常奇怪,因为这与 javascript 无关,而与“该 html 文件的编写方式”有关。任何具有 JS 知识的人都可以更新该文件,这样您就无需使用“浏览”按钮加载文件。这有点像说你讨厌 C#,因为你不喜欢某些孩子的 CLI 实用程序需要你传递全部大写的运行时标志:这与 C# 本身无关 =) (现代 JS 永远不需要运行 HTML自从几年前 Node 发布以来,它是一种脚本语言,如 php、python 等,也可以在浏览器中运行) 【参考方案1】:

问题解决了!

一个建议:如果您尝试在 Java 中执行此操作,那么您正在浪费时间。只需使用Opentype.js 和网站https://opentype.js.org,尤其是glyph inspectorfont inspector,即可解决此问题。

我通过从页面源代码复制和粘贴下载了这两个代码。然后我修改了glyph inspector 来完成这项工作。不过,您需要深入了解 Opentype.js 以获得字距调整对,但它就在那里(检查下面的代码)。

我最终将整个程序移植到 JavaScript。我之前没有过多地编写 JavaScript。因此,对于已经使用 JavaScript 编程的人来说,这一定非常容易。该程序只生成一个带有字形、字距调整对和宽度(每个字形的 advanceWidth)的 Java 类。

这里,显示结果的图像:

JavaScript 中的以下代码将 GPOS 字距调整表转储到字符串 text 中,呈现包含每行中的第二个字符和字距值的集合列表,而该对的第一个字符显示为 Java 字符行的开头。请注意,使用字符的 ASCII 码可以避免字形索引。

这只会将' '(空格)转储到'',这对英语很有用。要扩展到其他字符,只需使用 Unicode。

<!-- indoc: true -> writes in HTML on this page  -->
const indoc = true;
const nl = (indoc) ? "<br>" : "\n";
var chars = font.tables.cmap.glyphIndexMap;
var g1,g2;
var i, j, kern;
var text = "";
for ( i = 32; i < 126; i++ ) 
    g1 = font.glyphs.get(chars[i]);
    text += "'" +
    (( i == 39 || i == 92) ? "\\" + String.fromCharCode(i) : String.fromCharCode(i) )+ "'";
    for ( j = 32; j < 126; j++ ) 
        g2 = font.glyphs.get(chars[j]);
        if ( (kern = font.getKerningValue(g1,g2)) !== 0 ) 
            text += ",  '" +
            (( j == 39 || j == 92) ? "\\" + String.fromCharCode(j) : String.fromCharCode(j) )+
            "', " + kern + " ";
        
    
    text += nl;

【讨论】:

以上是关于如何从 Opentype 字体的 GPOS 表中使用和提取字距调整对,以在 Java 中将字形正确显示为 Path2D?的主要内容,如果未能解决你的问题,请参考以下文章

OpenType GPOS LookupType 8 - 跳过标记

我需要解析 OpenType 文件中的 GSUB 和 GPOS 表。有没有可用的软件可以做到这一点?

如何比较不同粗细的 OpenType 字体是不是相同?

如何在 Directwrite 中为给定字体获取可用的 OpenType 功能?

以编程方式将CFF字体转换为OpenType字体

过时的实现如何优雅地处理新版本的 OpenType 字体?