- 开发工具
【免费下载链接】fonttools
A library to manipulate font files from Python.
tfmLib是 fontTools 中专用于只读解析 TFM(TeX Font Metrics)文件的模块。TFM 是 TeX 排版系统用来存储字体度量信息(字宽、高度、深度、斜切修正、连字、字偶距、字体参数等)的二进制格式。读完本文,你将能够:用TFM类从路径或文件对象加载任意 TFM 文件;通过属性访问字体元数据、字符度量、kerning 与 ligature 表;理解 TFM 二进制文件布局、12.20 定点数编码以及模块内部的结构校验逻辑,并可用测试数据文件自行验证所有示例。
一、TFM 文件在磁盘上长什么样
TFM 格式的定义源自 TeX 配套工具 TFtoPL 的 WEB 源码(模块 docstring 中给出了格式出处说明)。从 Lib/fontTools/tfmLib.py 的源码结构看,一个 TFM 文件由若干**32 位大端字(big-endian word)**顺序拼接而成,文件开头是 6 个字的“尺寸表”(SIZES_FORMAT,Lib/fontTools/tfmLib.py#L42-L56):
lf: h # 整个文件的长度(字数) lh: h # 头部数据的长度(字数) bc: h # 字体中最小的字符代码 ec: h # 字体中最大的字符代码 nw: h # 字宽表字数 nh: h # 高度表字数 nd: h # 深度表字数 ni: h # italic correction 表字数 nl: h # ligature/kern 表字数 nk: h # kern 表字数 ne: h # 可伸缩字符表字数 np: h # 字体参数字数尺寸表之后依次是:头部数据(lh个字)、逐字符信息(ec - bc + 1个字,每字符 4 字节)、各度量表、ligature/kern 指令表、kern 值表、可伸缩字符 recipe 表、字体参数表。解析器会先校验这些子文件大小之和必须等于声明的总长lf,否则抛出异常(见后文第五节)。
TFM 中所有浮点度量值(字宽、高度、designsize 等)都以12.20 定点数存储:12 位整数位、20 位小数位,源码中定义为FIXED_FORMAT = "12.20F"(Lib/fontTools/tfmLib.py#L60),读取时自动转换为 Python float。所有度量值都是相对于designsize的 em 单位,例如designsize为 10 的字体,QUAD值约为 1.0,表示一个 em 宽。
二、API 入口:TFM 类
模块只暴露两个符号:TFM类与TFMException异常(Lib/fontTools/tfmLib.py#L153-L160)。TFM.__init__接受一个参数file,既可以是文件路径(字符串),也可以是任何带read()方法的文件对象(如io.BytesIO),构造时立即完成全部解析:
from fontTools.tfmLib import TFM tfm = TFM("Tests/tfmLib/data/cmr10.tfm") print(tfm) # <TFM for CMR in TeX text at 10pt>模块 docstring(也就是 Doc/source/tfmLib.rst 通过automodule指令自动渲染的内容)给出了标准用法示例,仓库中的 doctest 数据文件 Tests/tfmLib/data/cmr10.tfm 可直接复现:
>>> from fontTools.tfmLib import TFM >>> tfm = TFM("Tests/tfmLib/data/cmr10.tfm") >>> >>> # Accessing an attribute gets you metadata. >>> tfm.checksum 1274110073 >>> tfm.designsize 10.0 >>> tfm.codingscheme 'TeX text' >>> tfm.family 'CMR' >>> tfm.seven_bit_safe_flag False >>> tfm.face 234__repr__会输出<TFM for {family} in {codingscheme} at {designsize}pt>,方便调试时一眼确认字体身份。
三、读取文件头部元数据
TFM 头部支持多个“版本”,解析器按lh(头部字数)从大到小匹配四种头部格式(Lib/fontTools/tfmLib.py#L62-L89):
| 格式 | 最小 lh(字) | 包含字段 |
|---|---|---|
HEADER_FORMAT1 | 2 | checksum、designsize |
HEADER_FORMAT2 | 12 | 上述 +codingscheme(40 字节字符串) |
HEADER_FORMAT3 | 17 | 上述 +family(20 字节字符串) |
HEADER_FORMAT4 | 18 | 上述 +seven_bit_safe_flag、两个保留字节、face |
无论实际头部多大,解析器会先把所有头部属性初始化为空值,再按实际长度填充;lh超过 18 的额外头部字会按HEADER19、HEADER20…… 的键名存入tfm.extraheader字典(cmr10 的extraheader为空字典{})。
face字段是一个单字节值。从源码结构看,当face < 18时会被解码为一个 3 字符样式码:"MBL"[b % 3] + "RI"[s] + "RCE"[b // 3](s = face % 2,b = face // 2),分别表示字体族类型、罗马/斜体、文本/大写/数学扩展;face >= 18时(如 cmr10 的 234)则原样保留为整数。
模块解析后还会根据codingscheme推导tfm.fonttype:以TEX MATH SY开头为MATHSY(1),以TEX MATH EX开头为MATHEX(2),其余为VANILLA(0)。这个值直接决定fontdimens中哪些参数会被命名(见第四节)。
四、字符度量与字体参数
4.1tfm.chars:逐字符度量字典
chars是以字符代码(整数)为键的字典,值是该字符的度量字典。源码中每个字符由 4 个字节描述:第 0 字节是字宽表索引(为 0 表示该字符不存在),第 1 字节高 4 位/低 4 位分别是高度表、深度表索引,第 2 字节高 6 位是 italic 表索引、低 2 位是 tag,第 3 字节是 remainder(Lib/fontTools/tfmLib.py#L359-L392)。由此得到如下规则:
width始终存在;height、depth、italic仅在对应索引非 0(即“适用”)时出现;- tag 为
LIST_TAG时提供nextlarger(下一个更大同类字符的代码); - tag 为
EXT_TAG时提供varchar字典,含top/mid/bot/rep四个键,值为可伸缩字符 recipe 的各部分字符代码(某部分索引为 0 时回退为字符自身代码c); - tag 为
LIG_TAG时该字符进入 ligature/kern 表解析流程。
cmr10 中的g字符实测输出:
>>> tfm.chars[ord("g")] {'width': 0.5000019073486328, 'height': 0.4305553436279297, 'depth': 0.1944446563720703, 'italic': 0.013888359069824219}4.2tfm.fontdimens:命名化的字体参数
np个字体的参数字按顺序解析,前 7 个固定为“基础参数”(BASE_PARAMS,Lib/fontTools/tfmLib.py#L99-L107):SLANT、SPACE、STRETCH、SHRINK、XHEIGHT、QUAD、EXTRASPACE。超出部分按字体类型命名:
- VANILLA(普通字体):通常只有上述 7 个参数;
- MATHSY(TeX math sy,如 cmsy10):额外最多 15 个参数(
NUM1…AXISHEIGHT,共 22 个字); - MATHEX(TeX math ex,如 cmex10):额外最多 6 个参数(
DEFAULTRULETHICKNESS、BIGOPSPACING1..5,共 13 个字)。
仓库测试 Tests/tfmLib/tfmLib_test.py 用三个样本文件固化了三类字体的精确期望值,可直接作为参考:
# test_read_fontdimens_vanilla: cmr10.tfm assert tfm.fontdimens == { "SLANT": 0.0, "SPACE": 0.33333396911621094, "STRETCH": 0.16666698455810547, "SHRINK": 0.11111164093017578, "XHEIGHT": 0.4305553436279297, "QUAD": 1.0000028610229492, "EXTRASPACE": 0.11111164093017578, }MATHSY(cmsy10)在 7 个基础参数之外包含NUM1、DENOM1、SUP1、SUB1、DELIM1、DELIM2、AXISHEIGHT等 15 个数学排版参数;MATHEX(cmex10)则包含DEFAULTRULETHICKNESS与BIGOPSPACING1..5,完整数值见 Tests/tfmLib/tfmLib_test.py#L44-L90。
五、kerning 与 ligatures 表解析
TFM 用一个混合的 ligature/kern 指令表存储连字与字偶距。每条指令 4 字节(LIG_KERN_COMMAND,Lib/fontTools/tfmLib.py#L91-L97):skip_byte、next_char、op_byte、remainder。解析循环(Lib/fontTools/tfmLib.py#L396-L432)的语义如下:
skip_byte == 128(STOP_FLAG):该字符的列表结束;skip_byte > 128:先按256 * op_byte + remainder跳转到新地址,随后结束;op_byte >= 128(KERN_FLAG):该字为kern,值为kern(256 * (op_byte - 128) + remainder),写入tfm.kerning[c][next_char];- 其余为ligature,
op_byte是连字编码,标准编码被解码为形如LIG、LIG/、/LIG、LIG/>的名字,非标准编码原样输出,结果写入tfm.ligatures[c][next_char] = (lig, remainder)。
cmr10 的实测输出:
>>> tfm.kerning[ord("c")] {104: -0.02777862548828125, 107: -0.02777862548828125} # c→h / c→k >>> tfm.ligatures[ord("f")] {105: ('LIG', 12), 102: ('LIG', 11), 108: ('LIG', 13)} # fi / ff / fl另外,lig/kern 表的首条与末条指令若skip_byte == 255,则分别解析为tfm.right_boundary_char/tfm.left_boundary_char(左边界字符代码固定记为 256)。没有边界字符时两者为None,cmr10 即属此情况。
5.1 结构校验与 TFMException
与 TeX/TFtoPL 不同(它们会做功能性检查甚至“纠正”坏文件),fontTools 的策略是尽可能按原样输出文件,只要它能被解析;对于结构性错误则抛出TFMException(Lib/fontTools/tfmLib.py#L179-L227),具体检查包括:
- 文件短于 6 个字(尺寸表)——“Too short input file”;
lf为负或零——“claims to have negative or zero length”;- 实际字节数少于
lf * 4——“fewer bytes than it claims”; - 任一子文件长度为负;
- 头部长度
lh < 2; - 字符代码区间
bc..ec非法(bc > ec + 1或ec > 255); - 字宽/高度/深度/italic 任一表为空——“Incomplete subfiles for character dimensions”;
- 可伸缩 recipe 数量
ne > 256; - 各子文件大小之和 ≠
lf——“Subfile sizes don't add up to the stated total”。
六、命令行快速检查
Lib/fontTools/tfmLib.py#L435-L459 内置了一个__main__入口,直接把 TFM 文件的全部属性打印出来,适合在没有完整环境时快速查看某个 TFM 文件内容:
python3 Lib/fontTools/tfmLib.py Tests/tfmLib/data/cmr10.tfm输出逐行包含tfm.checksum、tfm.designsize、tfm.codingscheme、tfm.fonttype、tfm.family、tfm.seven_bit_safe_flag、tfm.face、tfm.extraheader、tfm.fontdimens、tfm.right_boundary_char、tfm.left_boundary_char、tfm.kerning、tfm.ligatures、tfm.chars,最后一行是repr(tfm)。
七、实现细节:基于 sstruct 的二进制读取
tfmLib不直接使用标准库struct,而是建立在 fontTools 的 SuperStruct 层 Lib/fontTools/misc/sstruct.py 之上,导入calcsize、unpack、unpack2(Lib/fontTools/tfmLib.py#L40)。sstruct 支持“每行一个名称:格式符”的声明式格式串(对应 TFM 各固定布局的定义),并原生支持n.mF定点数格式——这正是12.20F能被自动转成 float 的原因。定点数转换本身由 Lib/fontTools/misc/fixedTools.py 的fixedToFloat/floatToFixed完成。逐字符度量则绕开了解包开销,直接以偏移函数(char_info、width_index、height_index等闭包)按字节寻址,源码注释坦言这种“非惯用 Python”的写法是阅读 TFtoPL literate WEB 文档后的产物。
八、测试与样本数据
测试套件 Tests/tfmLib/tfmLib_test.py 基于 Tests/tfmLib/data/ 下的 4 个真实 TFM 文件:
cmr10.tfm、cmsy10.tfm、cmex10.tfm:Computer Modern 的正文/数学符号/数学扩展三类字体,分别覆盖 VANILLA、MATHSY、MATHEX 三种fontdimens命名路径;dummy-space.tfm:FontForge 生成的最小字体(152 字节),专测边界字符解析。
关键断言包括:glob参数化地读取所有cm*.tfm并断言designsize == 10.0、extraheader == {}、len(chars) == 128;边界字符用例断言right_boundary_char == 1、left_boundary_char == 256;三类fontdimens用例给出全字典精确比对(见第四节)。
tfmLib是通过 NEWS.rst 中 “Added new library for parsing TeX Font Metric (TFM) files (#2354)” 一条记录引入的新库;它是纯读取模块——仓库中没有对应的 TFM 写出功能,因此它适合用于“从 TeX 字体度量提取数据”的场景(如研究字体度量、生成对照数据),而不是 TFM 的生产/修改流程。
- 开发工具
【免费下载链接】fonttools
A library to manipulate font files from Python.
相关推荐
mujoco-py核心组件深度解析:MjSim、MjViewer与Modder
mujoco py核心组件深度解析:MjSim、MjViewer与Modder mujoco py是一个强大的Python接口,用于MuJoCo物理引擎,它提供
物理引擎机器人科研FontTools项目全面解析:Python字体处理工具库指南
FontTools项目全面解析:Python字体处理工具库指南 什么是FontTools FontTools是一套基于Python开发的字体处理工具库集合,它为
开发工具DistroAV终极配置指南:免费实现专业级多机位直播
DistroAV终极配置指南:免费实现专业级多机位直播 DistroAV(原名OBS NDI)是一个开源插件,为OBS Studio提供完整的NDI技术集成,让
音视频视频直播
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考