- 开发工具
【免费下载链接】fonttools
A library to manipulate font files from Python.
VORG(Vertical Origin)是 OpenType 规范中专为 CFF/CFF2 字体设计的表,用于显式记录每个字形的垂直原点(vertical origin)坐标,是竖排排版与垂直度量(vmtx/VVAR)正确工作的基础。本文以 fontTools 源码为据,完整讲解 VORG 的二进制结构、字典式 API、TTX 表示,并延伸到 FontBuilder 创建、subset 裁剪、scaleUpem 缩放、varLib 变体构建与实例化等工程场景,帮助你直接上手读写与维护这一表。
VORG 是什么:CFF/CFF2 字体为什么需要它
TrueType 字体的glyf轮廓坐标直接定义在字形坐标系中,字形的垂直原点信息隐含在轮廓数据里,无需额外表。而 CFF/CFF2(Type 2 字符程序)字体的字符程序可能被多个字形共享,垂直原点需要显式元数据来提供,VORG表正是承担这一职责的 OpenType 表。
fontTools 对该表的定位在 Lib/fontTools/ttLib/tables/V_O_R_G_.py 的类文档中写得很清楚:
The
VORGtable contains the vertical origin of each glyph in aCFForCFF2font.
也就是说,VORG提供“每个字形内部坐标系原点在 em 方块中所处的 y 值”。在竖排排版时,文本沿垂直方向推进,各字形需要按统一的垂直原点对齐,因此该表是垂直排版的度量基础,也与vmtx(垂直度量)和VVAR(垂直原点增量)表协同工作。从源码结构看,varLib构建VVAR时正是直接读取 master 字体的VORG数据(见 Lib/fontTools/varLib/init.py),而ttGlyphSet中仍留有TODO VVAR, VORG的待办标记(Lib/fontTools/ttLib/ttGlyphSet.py),说明该表是垂直度量链路上关键但仍在演进的一环。
二进制结构:8 字节表头 + 4 字节记录
VORG的磁盘布局由 Lib/fontTools/ttLib/tables/V_O_R_G_.py 的decompile与compile方法体现,全部按大端序(>)解析:
表头(8 字节,struct.unpack(">HHhH"))
| 字段 | 类型 | 含义 |
|---|---|---|
majorVersion | uint16 | 主版本号,当前为 1 |
minorVersion | uint16 | 次版本号,当前为 0 |
defaultVertOriginY | FWORD(int16) | 未在记录中列出的字形所采用的默认垂直原点 |
numVertOriginYMetrics | uint16 | 记录条数 |
记录数组(每条 4 字节,struct.unpack(">Hh"))
| 字段 | 类型 | 含义 |
|---|---|---|
gid | uint16 | 字形索引(glyph ID) |
vOrigin | FWORD(int16) | 该字形的垂直原点 y 值 |
两个值得注意的细节:
- 按 GID 升序存储:
compile中先把(gid, vOrigin)记录排序,再逐个struct.pack(">Hh", ...)写入,源码注释明确要求 “must be in ascending GID order”(Lib/fontTools/ttLib/tables/V_O_R_G_.py)。 - 版本号防御:
decompile会断言majorVersion <= 1,否则抛出 “Major version of VORG table is higher than I know how to handle” 错误(Lib/fontTools/ttLib/tables/V_O_R_G_.py)。
数值单位为字体单位(font units),与unitsPerEm同量纲——这一点由scaleUpem的等比缩放行为可证:当 em 翻倍时,defaultVertOriginY800 变为 1600、记录值 900 变为 1800、-321 变为 -642(见 Tests/ttLib/scaleUpem_test.py),垂直原点值可以是负数。
字典式 API:按字形名(或 GID)读写垂直原点
table_V_O_R_G_在设计上“结构化为可按字形名索引的字典”(见 Lib/fontTools/ttLib/tables/V_O_R_G_.py),支持__getitem__、__setitem__、__delitem__,且键既可以是字形名也可以是 GID(整数会自动经getGlyphName转为字形名,见 Lib/fontTools/ttLib/tables/V_O_R_G_.py):
from fontTools.ttLib import TTFont font = TTFont("MyCFF.otf") vorg = font["VORG"] print(vorg.defaultVertOriginY) # 默认垂直原点 print(vorg["A"]) # 按字形名读取;无记录时返回默认值 print(vorg[5]) # 也支持按 GID 读取 vorg["A"] = 880 # 设置垂直原点 del vorg["A"] # 删除记录三个关键行为:
- 读取兜底:
__getitem__在VOriginRecords中没有该字形时,返回defaultVertOriginY,因此任意字形总能取到有效值(Lib/fontTools/ttLib/tables/V_O_R_G_.py)。 - 写入去冗余:
__setitem__在值等于defaultVertOriginY时不会存储该记录(若已存在则删除),保证表中只保留“与默认值不同的”字形,这正是该表压缩记录数、发挥默认值作用的正确用法(Lib/fontTools/ttLib/tables/V_O_R_G_.py)。 - GID/名字双入口:内部统一先把整数 GID 翻译成字形名,再操作
VOriginRecords字典。
TTX 文本表示与真实示例
在 TTX 转储中,VORG呈现为表格名<VORG>下的 4 个标量标签与若干<VOriginRecord>子记录。仓库测试数据中有大量真实范例,例如 Tests/varLib/data/master_vvar_cff2/TestVVAR.0.ttx:
<VORG> <majorVersion value="1"/> <minorVersion value="0"/> <defaultVertOriginY value="880"/> <numVertOriginYMetrics value="6"/> <VOriginRecord> <glyphName value="c30719"/> <vOrigin value="660"/> </VOriginRecord> <VOriginRecord> <glyphName value="c30790"/> <vOrigin value="660"/> </VOriginRecord> <!-- 其余记录省略 --> </VORG>toXML会按 GID 排序输出记录,并断言记录中的字形名必须存在于字体中(Lib/fontTools/ttLib/tables/V_O_R_G_.py);fromXML则逐个还原VOriginRecord(Lib/fontTools/ttLib/tables/V_O_R_G_.py),记录类的glyphName/vOrigin字段读写见 Lib/fontTools/ttLib/tables/V_O_R_G_.py。若所有字形都等于默认值,记录数组为空,numVertOriginYMetrics为 0——例如 Tests/cffLib/data/TestSparseCFF2VF.ttx 所示的稀疏 CFF2 变体字体。
用 FontBuilder 从零创建 VORG
FontBuilder 提供了setupVerticalOrigins方法(Lib/fontTools/fontBuilder.py),签名与行为如下:
def setupVerticalOrigins(self, verticalOrigins, defaultVerticalOrigin=None): """Create a new `VORG` table. The `verticalOrigins` argument must be a dict, mapping glyph names to vertical origin values. The `defaultVerticalOrigin` argument should be the most common vertical origin value. If omitted, this value will be derived from the actual values in the `verticalOrigins` argument. """verticalOrigins:{字形名: 垂直原点值}字典,逐项通过vorgTable[glyphName] = value写入;defaultVerticalOrigin:缺省时自动统计verticalOrigins中出现频率最高的值作为默认垂直原点;- 表头固定初始化为
majorVersion = 1、minorVersion = 0。
实际调用示例:
from fontTools.fontBuilder import FontBuilder fb = FontBuilder(1000) # ... 设置字体其他表 ... fb.setupVerticalOrigins( {"A": 880, "B": 880, "C": 860, "D": 880}, # 最常见的 880 将自动成为默认值 )注意:由于__setitem__的去冗余逻辑,与默认值相同的字形不会生成记录,最终 TTX 中看到的记录数可能少于传入字典的条目数。
与工具链的协同:subset、scaleUpem、varLib
subset:随字形裁剪并永不丢弃
Lib/fontTools/subset/init.py 为VORG注册了subset_glyphs:只保留s.glyphs集合内的字形记录,并同步numVertOriginYMetrics;由于存在defaultVertOriginY兜底度量,该方法始终返回True(“Never drop; has default metrics”),即裁剪后VORG表永不删除。裁剪后的真实结果可参考 Tests/subset/data/NotoSansCJKjp-Regular.subset.ttx,其中defaultVertOriginY仍为 880。
scaleUpem:随 em 尺寸等比缩放
Lib/fontTools/ttLib/scaleUpem.py 通过ScalerVisitor把VORG.defaultVertOriginY注册为可缩放属性;Lib/fontTools/ttLib/scaleUpem.py 又注册了VOriginRecords,使字典中的每个记录值按相同比例因子缩放。scaleUpem测试(Tests/ttLib/scaleUpem_test.py)验证了 em 翻倍后默认值与记录值全部等比更新,包括负值 -321 → -642。
varLib:VVAR 构建与实例化中的 VORG 维护
- 构建 VVAR 时读取 VORG:Lib/fontTools/varLib/init.py 在生成
VVAR表时,若 master 字体含有VORG,则收集各 master 的VOriginRecords与defaultVertOriginY作为垂直原点增量插值的数据源。 - 实例化时重建 VORG:Lib/fontTools/varLib/instancer/init.py 在
instantiateVVAR中,若有VOrgMap,会对每个字形计算vorg[glyphName] + round(delta);随后统计新值出现频率,选出最常出现的值作为新的defaultVertOriginY(当旧默认值同样常见时保留旧值),最终VOriginRecords只保留与新默认值不同的字形。
对应的测试在 Tests/varLib/instancer/instancer_test.py 中验证了:实例化后按 GID 顺序读取的垂直原点数组正确、默认值选择符合预期、且编译—解编译往返一致(decompile(compile(...))后再读取结果相同);test_uniform_origins_and_half_unit_rounding还覆盖了“全部字形同原点 + 半单位增量取整”的边界场景,此时应把取整后的值全部并入defaultVertOriginY、记录表清空。
WOFF2 支持
VORG已列入 WOFF2 的已知表集合(Lib/fontTools/ttLib/woff2.py),可参与 WOFF2 编解码流程。
工程实践要点小结
- 善用默认值压缩记录:绝大多数字形的垂直原点一致时,应把众数设为
defaultVertOriginY,只记录差异项——这正是__setitem__自动去冗余与 FontBuilder 自动推导默认值的意义所在; - 保持 GID 升序与版本一致:手动构造表时使用
majorVersion=1, minorVersion=0,并让编译器(compile)负责排序,不要手工乱序写入; - GID 与字形名转换有兜底:解编译时若 glyphOrder 缺失索引,
decompile会回退到getGlyphName映射(Lib/fontTools/ttLib/tables/V_O_R_G_.py),编译时若反查 GID 失败则会重建反向映射(Lib/fontTools/ttLib/tables/V_O_R_G_.py); - 工具链已全面集成:subset 裁剪、scaleUpem 缩放、varLib 的 VVAR 构建与实例化、WOFF2 编解码均已处理
VORG,在你自己的字体处理流水线中可直接复用这些入口,无需另行维护垂直原点逻辑。
核心实现集中于 Lib/fontTools/ttLib/tables/V_O_R_G_.py,文档入口见 Doc/source/ttLib/tables/V_O_R_G_.rst,相关测试与真实字体数据分布在Tests/ttLib、Tests/varLib/instancer、Tests/subset/data与Tests/cffLib/data目录下,可作为进一步研究垂直度量链路的起点。
- 开发工具
【免费下载链接】fonttools
A library to manipulate font files from Python.
相关推荐
fontTools 的 CFF 表模块详解:table_C_F_F_ 如何读取与写入 OpenType 的 CFF 表
fontTools 的 CFF 表模块详解:table_C_F_F_ 如何读取与写入 OpenType 的 CFF 表 本文围绕 fontTools.ttLib
开发工具Hasklig开发工具链详解:AFDKO与FontTools的协同使用
Hasklig开发工具链详解:AFDKO与FontTools的协同使用 Hasklig是一款专为编程设计的等宽字体,其独特的连字特性让代码更具可读性。在Hask
开发工具fontTools 中 GDEF(Glyph Definition)表的完整解析:结构、读写、构建与实战指南
fontTools 中 GDEF(Glyph Definition)表的完整解析:结构、读写、构建与实战指南 导读 GDEF(Glyph Definition)
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考