对Fontforge中单个字符map自动校验中的错误的分析
Error的出现
之前二月份的时候我在做一个关于字体的项目,主要是利用Fontforge以及Fontforge Script对一些MacOS下的比较老的字体进行修复(原先这些字体在其他平台下会出现格式不匹配,或是同样的格式换了平台后检测不合标准的问题),使得这些本来只能工作在Mac下的字体能完全正确地工作在其他操作系统平台下。
在我预处理Mac下的Monaco字体时,发现了一个有趣的问题(后来发现是Fontforge的错误)。
因为Monaco字体是dfont格式的,首先我编写了一个简单的Fontforge Script来将Monaco字体转换为ttf格式,Script如下:
Open($argv[1])
Print($order)
Print($em)
ScaleToEm(2048)
RoundToInt()
Generate($argv[1]:r + "-New" + ".ttf")
运行Script
fontforge -script ~/convert.pe Monaco.dfont
显示如下,并且产生了Monaco-New.ttf文件
The following table(s) in the font have been ignored by FontForge
Ignoring 'Zapf' glyph reference table
Ignoring 'fdsc' font descriptor table
Ignoring 'fond'
Ignoring 'hdmx' horizontal device metrics table
Ignoring 'just' justification table (AAT version)
Ignoring 'meta'
Bad lookup table: format=4 (40/41), first=65535 last=65535 total glyphs in font=1678
The glyph named Tcommaaccent is mapped to U+021A.
But its name indicates it should be mapped to U+0162.
The glyph named tcommaaccent is mapped to U+021B.
But its name indicates it should be mapped to U+0163.
The glyph named zerowidthjoiner is mapped to U+200D.
But its name indicates it should be mapped to U+FEFF.
The glyph named Ghemiddlehookcyrillic is mapped to U+0496.
But its name indicates it should be mapped to U+0494.
The glyph named ghemiddlehookcyrillic is mapped to U+0497.
But its name indicates it should be mapped to U+0495.
The glyph named sixroman is mapped to U+2179.
But its name indicates it should be mapped to U+2175.
The glyph named uni2094 is mapped to U+2071.
But its name indicates it should be mapped to U+2094.
The glyph named uni20C8 is mapped to U+20A0.
But its name indicates it should be mapped to U+20C8.
2
2048
这里我要先解释一下为什么要转换为ttf格式(而不是otf或者其他的格式),请注意在Script中的这两句话:
Print($order)
Print($em)
第一行会告诉我们原字体(Monaco.dfont)是使用2次贝塞尔曲线还是3次贝塞尔曲线控制的,我们知道TTF格式下的字体是使用2次贝塞尔曲线控制的,而OTF格式下的字体是使用3次贝塞尔曲线控制的(这也是为什么一般OTF格式的字体质量要优于TTF)。
第二行会告诉我们原字体(Monaco.dfont)的控制点的个数,一般TTF字体拥有2048或是1024个控制点,而OTF字体拥有1000个控制点。
从后来的输出我们看到Monaco.dfont使用2次贝塞尔曲线控制,拥有2048个控制点,所以我选择把其转换为能最大程度上保真的TTF格式。
现在我们把目光集中到Fontforge报出的一系列关于单个字符map自动校验的Warnning。
The glyph named Tcommaaccent is mapped to U+021A.
But its name indicates it should be mapped to U+0162.
The glyph named tcommaaccent is mapped to U+021B.
But its name indicates it should be mapped to U+0163.
The glyph named zerowidthjoiner is mapped to U+200D.
But its name indicates it should be mapped to U+FEFF.
The glyph named Ghemiddlehookcyrillic is mapped to U+0496.
But its name indicates it should be mapped to U+0494.
The glyph named ghemiddlehookcyrillic is mapped to U+0497.
But its name indicates it should be mapped to U+0495.
The glyph named sixroman is mapped to U+2179.
But its name indicates it should be mapped to U+2175.
The glyph named uni2094 is mapped to U+2071.
But its name indicates it should be mapped to U+2094.
The glyph named uni20C8 is mapped to U+20A0.
But its name indicates it should be mapped to U+20C8.
我先解释一下这些错误信息是什么意思,由于在ISO UTF-8规定中,每个特定的字符(比如上面出现的Tcommaaccent)一定是绝对对应一个UTF-8数码的(比如说U+021A),Fontforge会自动检测每个具有特定名称的字符是否被map到了UTF-8规定的UTF-8数码上,如果不是,则会报出错误信息。
Error的确认
这些报错信息乍一看还真是那么回事,所以我的第一个想法就是要把这些报错的字符重新map到正确的UTF-8数码上。在这里我使用了一个名为fonttools的工具包。
使用如下命令将ttf文件转换为xml格式,方便进行编辑。
ttx Monaco-New.ttf
产生了Monaco-New.ttx文件,这其实是一个xml文件,用文本编辑器打开即可。
现在我们在Monaco-New.ttx中查找Tcommaaccent和tcommaaccent的map记录。
<map code="0x21a" name="Tcommaaccent"/><!-- LATIN CAPITAL LETTER T WITH COMMA BELOW -->
<map code="0x21b" name="tcommaaccent"/><!-- LATIN SMALL LETTER T WITH COMMA BELOW -->
而Fontforge提示我们Tcommaaccent和tcommaaccent应该被map到U+0162和U+0163,而不是上面的map记录里的那样。
就是在这里,我发现Monaco字体文件其实并没有错误,而是Fontforge错了。(后来在对其它一些字体测试的时候也发现了很多类似的误报,最典型的就是Microsoft大名鼎鼎的Consolas字体在Fontforge的自动检测下一大批错误信息说map记录不对,但是其实大部分是误报。)
查阅相关权威资料,Tcommaaccent,即文件里提到的LATIN CAPITAL LETTER T WITH COMMA BELOW,即T字母下有一个逗号的图案,这个字符的真实名称叫做T-comma,其确实是被编码到U+021A的,而不是Fontforge所说的U+0162。
那么U+0162到底是什么呢?
在Monaco-New.ttx中查找U+0162的记录,结果如下。
<map code="0x162" name="Tcedilla"/><!-- LATIN CAPITAL LETTER T WITH CEDILLA -->
<map code="0x163" name="tcedilla"/><!-- LATIN SMALL LETTER T WITH CEDILLA -->
这个字符其实是LATIN CAPITAL LETTER T WITH CEDILLA,即T字母下有一个下加符的图案(小写字母t类似)。在权威资料中,这个字符确实是被map到U+0162的。
总的来看,其实Monaco字体文件对Tcommaaccent、tcommaaccent、Tcedilla以及tcedilla的map都没有错误。
所以结论是,Fontforge在对字符Tcommaaccent的map记录的校验上出错了。
Error的分析
Fontforge为什么会出现这样一种错误呢?
一个可能的原因是:Tcommaaccent和tcommaaccent并不存在于上世纪早期的UTF-8标准里,所以Fontforge把Tcommaaccent和tcommaaccent识别为了Tcedilla和tcedilla。
其实这在逻辑上确实情有可原,因为这实际上是把T字母下有一个逗号的图案识别为了T字母下有一个下加符的图案。确实,从逻辑上来说,T字母下有一个逗号的图案确实是一种T字母下有一个下加符的图案,比较类似于程序设计里的(is a)的关系。
但是问题在于在实际上,T字母下有一个逗号的图案和T字母下有一个下加符的图案在现代UTF-8标准里是两个并存的东西。这个问题是由于UTF-8的历史原因留下来的,所以作为程序设计者,一个正确的逻辑应该是这样子的:
- 存在Tcommaaccent和tcommaaccent,不存在Tcedilla和tcedilla,则Tcommaaccent和tcommaaccent被编码为U+0162和U+0163或是U+021A和U+021B均可,对于编码为U+0162和U+0163的要给出警告,说明这是老UTF-8标准。
- 存在Tcommaaccent和tcommaaccent,也存在Tcedilla和tcedilla,则Tcommaaccent和tcommaaccent必须被编码U+021A和U+021B,而Tcedilla和tcedilla必须被编码为U+0162和U+0163。
不幸的是,Fontforge很可能采用了把Tcommaaccent一刀切看成Tcedilla的内部实现(很多其他字符也有类似的问题),所以就带来了一系列误报。
(完)