字的研究(3)fontTools-TrueType轮廓坐标的获取以及基于TrueType的Glyph实例的构建

前言

本文主要介绍如果使用Python第三方库fontTools提取OpenType字体文件中的TrueType轮廓坐标以及如何构建基于TrueType的Glyph实例

TrueType轮廓坐标的获取

对于TrueType轮廓描述的OpenType文件,除了前文提到的利用ttx组件将表结构转化为XML文件方法,利用如下代码也可以直接获取具体的轮廓数据:

from fontTools.ttLib import TTFont

font = TTFont("Resources/simsun.ttf")
glyph = font.getGlyphSet()["uni70E0"] # 获取_TTGlyph实例
print(glyph._glyph.coordinates) # 坐标
print(glyph._glyph.endPtsOfContours) # 轮廓结束点
print(list(glyph._glyph.flags)) # 点类型flag

运行结果如下:

GlyphCoordinates([(138, 118),(138, 86),(206, 86),(206, 118),(138, 80),(138, 49),(206, 49),(206, 80),(138, 43),(138, -19),(123, -26),(124, -5),(124, 16),(124, 99),(110, 81),(86, 67),(84, 70),(118, 100),(142, 158),(125, 158),(112, 158),(101, 155),(92, 164),(144, 164),(154, 192),(156, 209),(176, 197),(169, 192),(161, 170),(159, 164),(207, 164),(221, 177),(238, 158),(157, 158),(151, 142),(140, 124),(205, 124),(214, 134),(229, 119),(220, 114),(220, 1),(220, -17),(199, -25),(197, -9),(168, -4),(168, 0),(195, -2),(206, 0),(206, 8),(206, 43),(52, 206),(74, 194),(67, 187),(67, 123),(87, 148),(91, 161),(105, 147),(99, 146),(90, 137),(81, 128),(67, 115),(67, 91),(64, 57),(87, 46),(103, 29),(103, 22),(103, 18),(99, 7),(92, 9),(87, 22),(82, 34),(63, 52),(56, 8),(12, -26),(11, -23),(41, 13),(53, 74),(53, 149),(33, 140),(34, 126),(33, 104),(25, 92),(13, 88),(10, 95),(10, 97),(10, 102),(14, 105),(19, 109),(28, 128),(29, 140)])
[3, 7, 49, 77, 89]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1]

描述TrueType轮廓的数据主要由坐标、轮廓结束点以及各点的类型flag组成。其中,轮廓结束点为各轮廓的最后一个点的坐标;点类型flag则和坐标一一对应,说明该点是普通点还是贝塞尔曲线的控制点,0为控制点,1为普通点(注意,TrueType轮廓只包含二次贝塞尔曲线)。

相比之下,我个人更倾向将数据转化为如下由三元组组成的二维数组的形式,更方便理解和处理:

coordinates = list(glyph._glyph.coordinates)
endPts = glyph._glyph.endPtsOfContours
flags = list(glyph._glyph.flags)

contours = []
contour = []
for i, (x,y) in enumerate(coordinates):
    contour.append((x,y,flags[i]))
    if i in endPts:
        contours.append(contour)
        contour = []

print(contours)

运行结果如下:

[[(138, 118, 1), (138, 86, 1), (206, 86, 1), (206, 118, 1)], 
[(138, 80, 1), (138, 49, 1), (206, 49, 1), (206, 80, 1)], 
[(138, 43, 1), (138, -19, 1), (123, -26, 1), (124, -5, 0), (124, 16, 1), (124, 99, 1), (110, 81, 0), (86, 67, 1), (84, 70, 1), (118, 100, 0), (142, 158, 1), (125, 158, 1), (112, 158, 0), (101, 155, 1), (92, 164, 1), (144, 164, 1), (154, 192, 0), (156, 209, 1), (176, 197, 1), (169, 192, 1), (161, 170, 0), (159, 164, 1), (207, 164, 1), (221, 177, 1), (238, 158, 1), (157, 158, 1), (151, 142, 0), (140, 124, 1), (205, 124, 1), (214, 134, 1), (229, 119, 1), (220, 114, 1), (220, 1, 1), (220, -17, 0), (199, -25, 1), (197, -9, 0), (168, -4, 1), (168, 0, 1), (195, -2, 0), (206, 0, 0), (206, 8, 1), (206, 43, 1)], 
[(52, 206, 1), (74, 194, 1), (67, 187, 1), (67, 123, 1), (87, 148, 0), (91, 161, 1), (105, 147, 1), (99, 146, 0), (90, 137, 1), (81, 128, 0), (67, 115, 1), (67, 91, 0), (64, 57, 1), (87, 46, 0), (103, 29, 0), (103, 22, 1), (103, 18, 0), (99, 7, 0), (92, 9, 0), (87, 22, 1), (82, 34, 0), (63, 52, 1), (56, 8, 0), (12, -26, 1), (11, -23, 1), (41, 13, 0), (53, 74, 0), (53, 149, 0)],
[(33, 140, 1), (34, 126, 0), (33, 104, 0), (25, 92, 0), (13, 88, 0), (10, 95, 0), (10, 97, 1), (10, 102, 0), (14, 105, 1), (19, 109, 0), (28, 128, 0), (29, 140, 1)]]

基于TrueType的Glyph实例的构建

构建fontTools中的Glyph实例主要可以用于后续建立新的基于TrueType轮廓的字体文件。所采用的方法是基于前文所提到的Pen对象的子类TTGlyphPointPen,输入坐标、轮廓结束点以及各点的类型flag三项数据,输出Glyph实例:

from fontTools.pens.ttGlyphPen import TTGlyphPointPen

coordinates = [(138, 118),(138, 86),(206, 86),(206, 118),(138, 80),(138, 49),(206, 49),(206, 80),(138, 43),(138, -19),(123, -26),(124, -5),(124, 16),(124, 99),(110, 81),(86, 67),(84, 70),(118, 100),(142, 158),(125, 158),(112, 158),(101, 155),(92, 164),(144, 164),(154, 192),(156, 209),(176, 197),(169, 192),(161, 170),(159, 164),(207, 164),(221, 177),(238, 158),(157, 158),(151, 142),(140, 124),(205, 124),(214, 134),(229, 119),(220, 114),(220, 1),(220, -17),(199, -25),(197, -9),(168, -4),(168, 0),(195, -2),(206, 0),(206, 8),(206, 43),(52, 206),(74, 194),(67, 187),(67, 123),(87, 148),(91, 161),(105, 147),(99, 146),(90, 137),(81, 128),(67, 115),(67, 91),(64, 57),(87, 46),(103, 29),(103, 22),(103, 18),(99, 7),(92, 9),(87, 22),(82, 34),(63, 52),(56, 8),(12, -26),(11, -23),(41, 13),(53, 74),(53, 149),(33, 140),(34, 126),(33, 104),(25, 92),(13, 88),(10, 95),(10, 97),(10, 102),(14, 105),(19, 109),(28, 128),(29, 140)]
endPts = [3, 7, 49, 77, 89]
flags = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1]

pen = TTGlyphPointPen(None)
beginflag = 0
for i, pt in enumerate(coordinates):
    if pen._isClosed():
        pen.beginPath()
    if flags[i] == 1:
        pen.addPoint(pt,segmentType="line")
    else:
        pen.addPoint(pt)
    if i in endPts:
        pen.endPath()
glyph = pen.glyph()

返回的glyph即为Glyph实例,可直接用于构建基于TrueType轮廓的字体文件。注意,Glyph对象只包含字形轮廓数据,属于glyf表,对应前一节提到的_TTGlyph中的_glyph属性,_TTGlyph中的字宽和上下沿等数据则来自在字体文件的其他表格。

posted @ 2022-01-28 11:06  鬼斯通  阅读(1357)  评论(0编辑  收藏  举报