从活字到字体源码(8):字体源码藏在哪些文件里
下载一份 TTF 可以安装字体,拿到字体仓库以后却会看到 .glyphs、.nam、YAML 和 Python。它们分别保存设计数据、覆盖范围、构建配置和生成逻辑,作用与最终交付文件不同。
这里用 Ligconsolata Next 仓库说明这些入口。路径是这个项目的组织方式,其他字体可能使用 UFO、Designspace 或别的源格式。
.glyphs 保存设计数据
Glyphs Handbook 的 Source Formats介绍了 Glyphs 源文件。它可以记录字形轮廓、组件、master、宽度、Unicode 映射、feature 和导出参数。
项目的主要入口是 sources/Inconsolata.glyphs。文件名保留历史来源,内部公开 family name 使用 Ligconsolata Next。新增内部 glyph、改默认连字规则与调整 master 数据,最终都要落实到这里。
组件可以引用已有字形,减少重复绘制;但引用与变换关系也会影响构建。比如项目里的右方括号本来通过镜像组件得到,再嵌套到 [] 时,小字号观感曾出现不均衡,后来为方括号对生成了真实路径。能复用组件,不代表任何层数的组合都适合目标渲染。
config.yaml 指定构建入口和输出选项
sources/config.yaml 供 Google Fonts 构建链路使用。下面节选了源文件、轴顺序和命名相关配置,省略 hinting 与其他构建选项:
# Inconsolata/sources/config.yaml:1-8
sources:
- Inconsolata.glyphs
axisOrder:
- wdth
- wght
familyName: Ligconsolata Next
buildStatic: true
flattenComponents: false
buildStatic 表示还要构建静态实例,axisOrder 则指定构建器使用的轴顺序。不能据源码配置顺序,直接断言最终 fvar 表也以相同顺序保存;检查产物时应读取实际轴数据。
fork 还需要核对最终字体名称。源文件改名、配置改名与用户安装时看到的 name table 是不同检查点,漏掉其中一处可能让派生字体与上游混淆。
.nam 描述字符与 glyph 清单
sources/charset/*.nam 是本项目的字符集与 glyph 清单,真实文件包括 inconsolata_glyphlist.nam、inconsolata_unique.nam 等。它们用码位、字符或名称记录覆盖关系,轮廓与 feature 仍由设计源维护。
因此,清单适合回答「需要哪些字符与字形」,无法独自回答「这个字形长什么样」。内部连字 glyph 还可能没有独立 Unicode,只在排版替换中被调用。
生成脚本维护可重复的设计规则
scripts/update-ligature-glyphs.py 从当前字体的横线、斜线和端点等部件派生操作符,再把 glyph 与 calt、dlig、liga 写回源码。重复字符使用短名称,例如 underscore_run4.dlig,避免无限展开生产名。
默认运行会重建 glyph 和 feature;--features-only 跳过 glyph block,仅刷新相关 class 与 feature。这条快速路径适合规则调整,轮廓本身变化时仍需重建字形。
脚本保证同样输入可以重复生成,不负责判断小字号的 == 是否像三条线,也不会替维护者决定某项风格是否应该默认启用。
构建产物是验证对象
fonts/ 保存上游既有字体,开发过程另外构建临时字体。直接修改某个二进制文件,可能让下一次从源码构建时丢失修正,所以问题应回到源设计或生成脚本解决。
本项目用 fontmake 构建开发字体,fontTools 读取名称、轴、字形与宽度,HarfBuzz 的 hb-shape 检查实际替换,再生成 SVG specimen 和浏览器 demo。它们各自回答不同问题。
specimen 是供复核的字体样张。项目的 overview、catalog 与功能图库从字体轮廓生成,HTML demo 则加载真实字体,允许修改 ASCII 样例。概念图可以帮助解释构建链路,字体是否真的这样显示,要由这些产物证明。
总结
字体仓库中的源设计、字符清单、构建配置和生成脚本分别描述形状、范围、输出与重复逻辑。安装的是字体文件,维护和修正却要回到这些入口。源码与产物之间保持可追踪关系,才有可能解释一张样张来自哪次构建、一个视觉问题该改在哪一层。