news 2026/10/13 6:06:41

fontTools 中的 VORG 表:CFF/CFF2 字体垂直原点的读取、构建与工具链协同实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
fontTools 中的 VORG 表:CFF/CFF2 字体垂直原点的读取、构建与工具链协同实战
  • 开发工具

【免费下载链接】fonttools

A library to manipulate font files from Python.

项目地址:https://gitcode.com/gh_mirrors/fo/fonttools
点击查看免费下载

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 的类文档中写得很清楚:

TheVORGtable 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"))

字段类型含义
majorVersionuint16主版本号,当前为 1
minorVersionuint16次版本号,当前为 0
defaultVertOriginYFWORD(int16)未在记录中列出的字形所采用的默认垂直原点
numVertOriginYMetricsuint16记录条数

记录数组(每条 4 字节,struct.unpack(">Hh"))

字段类型含义
giduint16字形索引(glyph ID)
vOriginFWORD(int16)该字形的垂直原点 y 值

两个值得注意的细节:

  1. 按 GID 升序存储:compile中先把(gid, vOrigin)记录排序,再逐个struct.pack(">Hh", ...)写入,源码注释明确要求 “must be in ascending GID order”(Lib/fontTools/ttLib/tables/V_O_R_G_.py)。
  2. 版本号防御: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 编解码流程。

工程实践要点小结

  1. 善用默认值压缩记录:绝大多数字形的垂直原点一致时,应把众数设为defaultVertOriginY,只记录差异项——这正是__setitem__自动去冗余与 FontBuilder 自动推导默认值的意义所在;
  2. 保持 GID 升序与版本一致:手动构造表时使用majorVersion=1, minorVersion=0,并让编译器(compile)负责排序,不要手工乱序写入;
  3. GID 与字形名转换有兜底:解编译时若 glyphOrder 缺失索引,decompile会回退到getGlyphName映射(Lib/fontTools/ttLib/tables/V_O_R_G_.py),编译时若反查 GID 失败则会重建反向映射(Lib/fontTools/ttLib/tables/V_O_R_G_.py);
  4. 工具链已全面集成: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.

项目地址:https://gitcode.com/gh_mirrors/fo/fonttools
点击查看免费下载
上一篇:开源中国Android客户端部署与发布:构建流程与版本管理最佳实践
下一篇:攻克Android测试难关:RxTool中的JUnit与Mockito实战指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 6:04:43

用 LinuxKit 构建 Kubernetes 系统镜像并在 Mac 上部署最小集群

云原生教程 【免费下载链接】kubernetes-handbook Kubernetes Handbook &#xff08;Kubernetes指南&#xff09; https://kubernetes.feisky.xyz 项目地址&#xff1a; https://gitcode.com/gh_mirrors/kub/kubernetes-handbook 点击查看 免费下载 LinuxKit 是 Docker 生态中…

作者头像 李华
网站建设 2026/10/13 6:04:39

uni-app验证码输入框实现与优化实践

1. 项目背景与需求分析在移动端应用开发中&#xff0c;验证码输入是一个高频出现的交互场景。传统的单个输入框粘贴方式虽然简单&#xff0c;但存在安全隐患且不符合部分业务场景需求。而多输入框分段验证的方式既能提升安全性&#xff0c;又能给用户更直观的反馈。在uni-app框…

作者头像 李华
网站建设 2026/10/13 6:00:47

Mesos Mini 实战与源码剖析:一条 docker run 命令拉起本地 Mesos 集群

集群管理任务调度后端 【免费下载链接】mesos Apache Mesos 项目地址&#xff1a; https://gitcode.com/gh_mirrors/mesos1/mesos 点击查看 免费下载 Mesos Mini 是 Apache Mesos 社区维护的 Docker 镜像&#xff0c;它让你在本地用一条 docker run 命令获得一个“真集群”&am…

作者头像 李华