如何从 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 inspector 和font 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 表。有没有可用的软件可以做到这一点?