TrueType 字体的字形由二次贝塞尔组成。为什么字形轮廓中会出现多个连续的曲线外点?



我正在编写一个TTF解析器。为了更好地理解TTF格式,我使用TTX提取C:\Windows\calibri.TTF的".notdef"字形数据,如下所示。

<TTGlyph name=".notdef" xMin="0" yMin="-397" xMax="978" yMax="1294">
<contour>
<pt x="978" y="1294" on="1"/>
<pt x="978" y="0" on="1"/>
<pt x="44" y="0" on="1"/>
<pt x="44" y="1294" on="1"/>
</contour>
<contour>
<pt x="891" y="81" on="1"/>
<pt x="891" y="1213" on="1"/>
<pt x="129" y="1213" on="1"/>
<pt x="129" y="81" on="1"/>
</contour>
<contour>
<pt x="767" y="855" on="1"/>
<pt x="767" y="796" on="0"/>
<pt x="732" y="704" on="0"/>
<pt x="669" y="641" on="0"/>
<pt x="583" y="605" on="0"/>
<pt x="532" y="602" on="1"/>
<pt x="527" y="450" on="1"/>
many more points
</contour>
...some other xml
</TTGlyph>

可以在一行中看到多个曲线外控制点。但我了解到TrueType字体是由二次Bezier组成的,每个Bezier都有两个曲线上的点(端点)和一个曲线外的点(控制点)。如何解释这些连续的偏离曲线的点?

TTF解析需要应用http://www.microsoft.com/typography/otspec/glyf.htm以及微软网站上关于TTF格式的技术文档。这些告诉我们,曲线有两种类型的点:曲线上点和曲线外点。曲线上的点是"0";真实的";曲线通过的点和曲线外点是引导bezier曲率的控制点。

现在,你所描述的";"贝塞尔曲线";是正确的:一条(二次)贝塞尔曲线从1个实点(由1个控制点引导)到1个实点通过(三次曲线、四次曲线等高阶曲线在实点之间有更多的控制点)。然而,二次曲线在设计工作中通常很糟糕,因为它们在近似圆弧方面真的很糟糕,但比高阶曲线更便宜,所以对于使用TrueType进行字形轮廓的字体,我们只能使用它们。为了避免二次曲线的缺点,TrueType轮廓通常使用贝塞尔曲线序列,而不是单个曲线,以获得看起来不错的均匀曲线,并且这些序列往往具有一个很好的特性:曲线上和曲线下的点以一种我们不需要记录序列中每个点的方式间隔开。

考虑这个贝塞尔序列:

P1 - C1 - P2 - C2 - P3 - C3 - P4

如果我们添加on信息,我们会在TTF中将其编码为:

P1 - C1 - P2 - C2 - P3 - C3 - P4
1  -  0 -  1 -  0 -  1 -  0 -  1

现在的技巧是:如果每个Pn都是曲线上的点,每个Cn都是控制点,P2正好位于C1和C2之间的中间,P3正好位于C2和C3之间的中间等等,那么这个曲线表示可以压缩很多,因为如果我们知道C1和C2,我们就知道P2等等。我们不必明确列出任何中间点,我们可以把它留给解析字形轮廓的任何东西。

所以TTF允许你用上面的属性编码长bezier序列:

P1 - C1 - C2 - C3 - P4
1 -  0 -  0 -  0 -  1

正如你所看到的:我们节省了相当大的空间,同时又不损失精度。如果您查看TTX转储,您会发现这反映在每个点的on值中。为了获得P2、P3等,我们所做的就是:

def getPoints(glyph):
points = []
previous_point = None;
flags = glyph.flags
for (i, point) in enumerate(glyph.point_array):
(mask_for_point, mask_for_previous_point) = flags[i]
# do we have an implied on-curve point?
if (previous_point && mask_for_point == 0 && mask_for_previous_point == 0):
missing_point = midpoint(point, previous_point)
points.push(missing_point)
# add the explicitly encoded point
points.push(point)
previous_point = point
return points

运行此程序后,points阵列将具有交替的曲线上和曲线下点,并且边框被构造为:

for i in range(0, len(array), 2):
curve(array[i], array[i+1], array[i+2]) 

编辑经过一点搜索后,http://chanae.walon.org/pub/ttf/ttf_glyphs.htm非常详细地介绍了如何处理glyf表数据(ascii图形有点傻,但仍然足够清晰)

进一步编辑几年后,我设法在TTF上的苹果文档中找到了真正解释(或至少暗示)它的文档https://developer.apple.com/fonts/TrueType-Reference-Manual/RM01/Chap1.html#necessary,在";图13";声明:

特别是位于曲线切线中点的曲线上点,不会添加任何额外信息,可能会被忽略。

甚至进一步编辑ShreevatsaR指出,苹果文档中图2和图3之间的文本也是相关的:

也可以通过删除点p2来指定图2中所示的曲线,减少一个点。点p2并不严格需要定义曲线,因为它的存在是隐含的,并且它的位置可以从其他点给出的数据中重建。对其余各点重新编号后,我们得到了[图3]。

最新更新