☰
fontTools 中的 MATH 数学排版表:结构解析与 Python 编程式构建指南
2026/10/14 17:27:20 网站建设 项目流程
  • 开发工具

【免费下载链接】fonttools

A library to manipulate font files from Python.

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

本文以 fontTools 的MATH表(Mathematical Typesetting,数学排版表)官方文档页为切入点,完整梳理该 OpenType 表在 fontTools 中的加载、反编译与编译机制,逐层解读MathConstants、MathGlyphInfo、MathVariants三大子表的字段定义,并给出通过otlLib.builder.buildMathTable()以纯 Python 字典构建 MATH 表的完整参数说明与可复现示例,帮助字体开发/调试人员在 fontTools 生态中读写和验证数学字体(如 XITS Math 类字体)的 MATH 表数据。

MATH 表是什么

MATH是一张 OpenType 表,用于存放"将字形排印进数学公式与表达式"所需的各类度量与结构信息,例如上下标位置、分数分数线厚度、根号粗细、括号的可伸缩变体等。在 fontTools 文档体系中,它对应的文档页是 Doc/source/ttLib/tables/M_A_T_H_.rst,该页通过 Sphinx 的automodule指令直接渲染 table_M_A_T_H_ 类的文档字符串:

# Lib/fontTools/ttLib/tables/M_A_T_H_.py from .otBase import BaseTTXConverter class table_M_A_T_H_(BaseTTXConverter): """Mathematical Typesetting table The ``MATH`` table contains a variety of information needed to typeset glyphs in mathematical formulas and expressions. See also https://learn.microsoft.com/en-us/typography/opentype/spec/math """ pass

从源码结构看,table_M_A_T_H_类本身没有任何方法体——它的全部行为都继承自BaseTTXConverter。这是 fontTools 处理 OpenType 表(GSUB、GPOS、MATH 等)的典型模式:表类只是"标签名 + 文档"的壳,真正的二进制读写由基类完成。

fontTools 如何加载和编译 MATH 表

MATH表被注册在字体表模块列表中,见 Lib/fontTools/ttLib/ttFont.py,并且TTFont为"MATH"标签提供了类型重载(Lib/fontTools/ttLib/ttFont.py):

def __getitem__(self, tag: Literal["MATH"]) -> M_A_T_H_.table_M_A_T_H_: ...

这意味着font["MATH"]在类型检查器眼中会精确返回table_M_A_T_H_实例。

实际的二进制转换由 BaseTTXConverter 实现,其decompile/compile两个方法的调用链是:

  1. 反编译(首次访问font["MATH"]时惰性触发):decompile(data, font)创建OTTableReader,然后按表标签从fontTools.ttLib.tables.otTables动态取到对应的表类(getattr(otTables, self.tableTag)),调用该类的decompile(reader, font)把二进制还原成 Python 对象树;
  2. 编译(保存字体时自动触发):compile(font)创建OTTableWriter,把对象树逐层写入,最终生成二进制。对 GSUB/GPOS 还包含 harfbuzz 重打包与偏移溢出重试状态机,而MATH走的是纯 fontTools 序列化路径(RepackerState.PURE_FT)。

otTables中所有的 MATH 相关类并非手写,而是由 otData.py 中的声明式字段规范在导入时动态生成。这正是理解 MATH 表结构的最佳入口:每个字段都带有类型和取自 OpenType 规范的描述文本。

MATH 表的三级结构与字段详解

顶层 MATH 记录

otData.py 定义了顶层四个字段:

字段类型说明
Versionuint32表版本,当前固定为0x00010000(1.0)
MathConstantsOffset指向MathConstants子表
MathGlyphInfoOffset指向MathGlyphInfo子表
MathVariantsOffset指向MathVariants子表

其中大量字段基于通用的MathValueRecord结构,即"一个设计单位数值 + 一个可选的 device table 偏移":

# otData.py: MathValueRecord ("int16", "Value", ...) # X 或 Y 方向的设计单位数值 ("Offset", "DeviceTable", ...) # 可 NULL,建议 device table format 1

MathConstants:公式排版的"全局常量"

otData.py 中的MathConstants包含约 50 个字段,可归为几组语义:

  • 缩放:ScriptPercentScaleDown(上/下标级别 1 的缩放百分比,建议 80%)、ScriptScriptPercentScaleDown(级别 2,建议 60%);
  • 行距与轴:MathLeading(数学公式间的行距留白)、AxisHeight(公式轴线高度);
  • 重音:AccentBaseHeight(无需抬升重音的基字最大高度,建议为 x-height)、FlattenedAccentBaseHeight(无需压平重音的基字最大高度,建议为大写高度);
  • 上/下标:SubscriptShiftDown、SubscriptTopMax、SuperscriptShiftUp、SuperscriptBottomMin、SubSuperscriptGapMin等约 10 个常量,规范中给出了具体建议值(如SubscriptTopMax建议 4/5 x-height);
  • 堆栈/分数:StackTopShiftUp、FractionNumeratorShiftUp、FractionRuleThickness(分数线粗细)、SkewedFractionHorizontalGap等;
  • 根号:RadicalRuleThickness、RadicalDegreeBottomRaisePercent(根指数底部抬升比例,建议 60%)、RadicalKernAfterDegree(负值,规范原文即如此)等。

除两个百分比字段(ScriptPercentScaleDown、ScriptScriptPercentScaleDown为 int16)和RadicalDegreeBottomRaisePercent(uint16)外,其余全部是MathValueRecord。

MathGlyphInfo:逐字形的数学信息

MathGlyphInfo 内含四个可空偏移:

  • MathItalicsCorrectionInfo:Coverage表 +ItalicsCorrectionCount+MathValueRecord数组,为每个覆盖字形提供斜体修正值(italics correction);
  • MathTopAccentAttachment:TopAccentCoverage+ 逐字形重音附着点高度;
  • ExtendedShapeCoverage:扩展形状(extended shape)字形覆盖表。规范说明:当 box 的左右两侧字形为扩展形状时,垂直定位应使用墨迹盒(ink box)而非MathConstants中的默认值;
  • MathKernInfo:数学字距信息,见下文。

otData.py 中MathKernInfoRecord为每个字形的四个角(TopRight、TopLeft、BottomRight、BottomLeft)各留一个可空MathKern偏移;MathKern本身是分段函数式的两级数组:CorrectionHeight(按高度排序的修正高度点)与KernValue(对应字距值,负值表示将字形彼此拉近),首值覆盖不大于第一个高度的所有情形,末值覆盖大于最后一个高度的所有情形。这套结构让上下标字距能随基字高度连续插值。

MathVariants:可伸缩字形与组装

MathVariants 支持两种"生长"方式:

  1. 变体字形:VertGlyphCoverage/HorizGlyphCoverage加计数,再为每个字形指向MathGlyphConstruction表;后者包含可选的GlyphAssembly偏移和一组MathGlyphVariantRecord(VariantGlyph+AdvanceMeasurement,即该方向上的全前进值);
  2. 字形组装:GlyphAssembly由ItalicsCorrection+PartCount+GlyphPartRecord数组构成,部件按"从左到右、从下到上"排列。GlyphPartRecord 字段为:glyph、StartConnectorLength、EndConnectorLength、FullAdvance,以及PartFlags——目前仅使用一个位:0x0001fExtender(表示该部件可被跳过或重复,0xFFFE为保留位)。

此外MathVariants.MinConnectorOverlap定义连接部件在组装时的最小重叠量(设计单位)。

TTX 文本形态

MATH 表可以像其他 OpenType 表一样导出为 TTX(XML)文本。仓库中带真实 MATH 数据的样本是 Tests/subset/data/TestMATH-Regular.ttx(一份 XITS Math 字体的完整 TTX 转储,<tableVersion value="0x00010000"/>),可直接对照上文结构验证字段命名。查看任意字体中 MATH 表的文本形态,可运行 ttx 工具:

ttx -t MATH font.otf

导出后即可在编辑器中查看/核对MathConstants、MathGlyphInfo等节点。

用 buildMathTable() 编程式构建 MATH 表

fontTools 提供了纯 Python 的构建入口 buildMathTable(),签名如下:

builder.buildMathTable( ttFont, constants=None, # dict:常量名 -> 数值 italicsCorrections=None, # dict:字形名 -> 斜体修正值 topAccentAttachments=None,# dict:字形名 -> 重音附着点高度 extendedShapes=None, # set:扩展形状字形 mathKerns=None, # dict:字形名 -> 四角字距 minConnectorOverlap=0, # 连接部件最小重叠 vertGlyphVariants=None, # dict:字形名 -> [(变体字形, 前进高度), ...] horizGlyphVariants=None, # dict:字形名 -> [(变体字形, 前进宽度), ...] vertGlyphAssembly=None, # dict:字形名 -> ([部件], 斜体修正) horizGlyphAssembly=None, # dict:字形名 -> ([部件], 斜体修正) )

各参数在源码 docstring 中有完整说明(Lib/fontTools/otlLib/builder.py),要点如下:

  • constants的键使用 MATH 规范中的常量名(首字母大写,如"ScriptPercentScaleDown"),值为数字;未给出的常量在_buildMathConstants()中按constants.get(conv.name, 0)填 0;
  • mathKerns的值是嵌套字典,键为角名('TopRight'、'TopLeft'、'BottomRight'、'BottomLeft'),值为两个列表的元组:修正高度列表与字距值列表,且要求len(correctionHeights) == len(kernValues) - 1(源码中的断言保证分段函数合法性);
  • vertGlyphAssembly/horizGlyphAssembly的部件是五元组:(glyph_name, flags, start_connector_length, end_connector_length, full_advance);
  • 所有"期望数值"的参数接受 int 或 float,float 会被otRound舍入为整数。

实现逻辑简洁清晰:函数创建ot.MATH()对象,设Version = 0x00010000并populateDefaults(),然后依次调用_buildMathConstants、_buildMathGlyphInfo、_buildMathVariants三个辅助函数(Lib/fontTools/otlLib/builder.py)。三者都遵循"入参为空则返回 None、对应偏移置 NULL"的策略——例如只给了italicsCorrections时,MathConstants和MathVariants均为空,只有MathGlyphInfo被填充。

最小可运行示例

import fontTools.ttLib.ttLib as ttLib from fontTools import otlLib ttFont = ttLib.TTFont() ttFont.setGlyphOrder(["A", "B", "parenleft", "parenleft.size1"]) otlLib.builder.buildMathTable( ttFont, constants={ "ScriptPercentScaleDown": 70, "ScriptScriptPercentScaleDown": 50, "DelimitedSubFormulaMinHeight": 24, "DisplayOperatorMinHeight": 60, "AxisHeight": 262, "FractionRuleThickness": 66, }, italicsCorrections={"A": 100, "B": 300}, topAccentAttachments={"A": 300}, extendedShapes={"parenleft"}, minConnectorOverlap=20, vertGlyphVariants={ "parenleft": [("parenleft", 700), ("parenleft.size1", 1000)], }, ) mathTable = ttFont["MATH"].table assert mathTable.Version == 0x00010000 assert mathTable.MathConstants.AxisHeight.Value == 262 print(mathTable.MathGlyphInfo.MathItalicsCorrectionInfo.Coverage.glyphs)

该示例的入参形态直接取自源码 docstring 中给出的官方示例(Lib/fontTools/otlLib/builder.py)。

测试用例:构建、合并与回环验证

fontTools 的测试套件为 MATH 表提供了三层可验证依据:

  1. 构建正确性:Tests/otlLib/builder_test.py 中,test_buildMathTable_empty验证空参调用后MathConstants/MathGlyphInfo/MathVariants均为None而Version == 0x00010000;test_buildMathTable_constants传入 58 个常量的字典并逐一断言写入值;test_buildMathTable_italicsCorrection等逐项验证各字形字典与Coverage.glyphs的顺序一致性(coverage 顺序即数组顺序);
  2. 字体合并:Tests/merge/merge_test.py 的test_merge_MATH用FontBuilder+buildMathTable合成三个含 MATH 表的 TTF 再走fontTools.merge流程,断言合并后 coverage 并集(如italics.Coverage.glyphs == ["a", "b"])、MathKernInfo记录数与MathVariants中变体序列均正确,说明 MATH 表已接入 fontTools 的字体合并管线;
  3. 子集化数据:Tests/subset/data/TestMATH-Regular.ttx 作为 subset 模块的数据集,保证含 MATH 表的真实字体在 TTX 回环(round trip)路径上被持续覆盖。

小结

在 fontTools 中处理 MATH 表可以归纳为三条主线:

  • 读:font["MATH"]触发BaseTTXConverter.decompile(),由 otData.py 声明式字段规范动态驱动反编译,得到可直接按属性访问的对象树;
  • 写:font.save()或compile()时自动回写二进制;新增/重建则用otlLib.builder.buildMathTable(),以普通 Python 字典表达全部数学度量;
  • 验证:结合 TTX 导出(ttx -t MATH font.otf)与 Tests/otlLib/builder_test.py 的断言模式,可以逐字段核对常量、coverage 顺序与变体记录是否符合 OpenType MATH 表规范。
  • 开发工具

【免费下载链接】fonttools

A library to manipulate font files from Python.

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询