« 手机游戏交互的一点想法 | 返回首页 | 有序数列的数据结构优化 »

ttf 字体的一点问题

我们的游戏引擎使用的是 stb 的 truetype 库 来处理 ttf 字体的。最近发现在使用公司提供的 阿里巴巴普惠体 时出了一点问题。

引擎渲染出来的汉字比标称的像素高度矮了不少,想渲染 100 像素高的汉字,结果只有 70+ 像素左右。我们之前测试使用的中文字体( Windows 自带默认字体)没有这个问题。

在网上翻了一下,找到这么一个帖子:https://github.com/nothings/stb/issues/689 以及 imgui 也遇到过类似问题

原来,stb truetype 库提供了两个计算字模大小的 api ,stbtt_ScaleForPixelHeight()stbtt_ScaleForMappingEmToPixels() 分别用两种方式计算字模高度比例。作者比较提倡用前一个 api ,但也保留了后一个 api 的使用场景。

computes a scale factor to produce a font whose EM size is mapped to 'pixels' tall. This is probably what traditional APIs compute, but I'm not positive.

另外在获取字体的 ascent 以及 decent 时,也有两个不同的 api : stbtt_GetFontVMetrics()stbtt_GetFontVMetricsOS2()

我很好奇的翻阅了这些 api 的实现,并查阅了能找到的关于 truetype 的文档,得出了下面的结论:

在字体文件中,有两处地方描述了字体的 ascent 和 descent 值。这个值应该是字体设计者创作时使用的像素大小。原本它们应该记录在字体文件头的 hhea 表中,它最初是由 apple 设计的。我们可以在 apple 的网站查到文档

后来,微软在设计字体时,可能是发现这些信息不够用,也或者是不赞成 apple 的某些设计决定。他们又设计了一张新的表叫 OS/2 ,里面也有类似的字段。文档在微软网站上可以找到

为什么两张表有很多重复的字段,在字体文件里放了两份(带来了冗余信息)?我猜恐怕和微软的代码实现风格有关:微软非常喜欢直接把内存结构映射为文件结构。所以他们选择加一整张表而不是只增加必要的字段在新的表中。

对于大部分字体来说,两张表的 ascent 和 descent 值是一样的。但正如 apple 文档中注释的,没有任何要求它们必须一致。而微软文档的一篇注解 则提到:

Some legacy platforms or applications may not use OS/2 fields at all. Thus, CJK fonts generally should have the same descender value recorded in hhea.descender, OS/2.sTypoDescender, and HorizAxis.ideo (if present) fields, and the same ascender value recorded in hhea.ascender, OS/2.sTypoAscender, and HorizAxis.idtp (if present) fields.

显然,阿里巴巴普惠体没有遵守这一点。

它在 hhea 表中,ascent = 1050 descent =-322 ,在 os/2 中,ascent = 850 descent = -150 。

stbtt_ScaleForPixelHeight() 计算缩放比时,使用的 pixel / (hhea.ascent - hhea.descent) 得到的值小于预期。

为何 stbtt_ScaleForMappingEmToPixels() 可以得到预期值?因为它使用的是 head 表中的 unitsPerEm 字段去计算的,这个值通常等于 ascent - descent (上面的文档中也提到了这一点)。在阿里巴巴普惠体文件中,它也等于 os2.sTypoAscender - os2.sTypoAscender 。

我不清楚为何这个字体会填入这样不同的数据。我猜测可能和 apple / ms 系统采用的默认屏幕 dpi 有关?它间接导致字体设计人员在打包字体时错误填写了某种参数?不过无论如何,我觉得是字体本身的 bug 。虽然,我通过修改我们引擎的代码绕开了这个问题。

Comments

原来还有这样的,感谢分享

Post a comment

非这个主题相关的留言请到:留言本