字体文件格式解析:TTF/OTF 结构、字距调整与 Unicode 映射
做嵌入式 GUI 这么多年,我拆过的字体文件少说也有上百个。说实话,很多人觉得字体就是个「黑盒子」——把 .ttf 扔进去,能显示字就行。但真到了 TouchGFX 里做资源压缩,你会发现不懂字体结构,连优化方向都找不到。
这一章,咱们就把 TTF/OTF 的底裤扒开看看。你不需要成为字体工程师,但得知道里面到底装了些什么。
TTF 与 OTF:核心差异在哪?
先解决一个基础问题:TTF 和 OTF 到底有什么区别?
TTF(TrueType Font)用的是二次贝塞尔曲线,数学上简单,渲染快。OTF(OpenType Font)用的是三次贝塞尔曲线,曲线更平滑,而且可以内嵌 PostScript 轮廓。我早期在 STM32F4 上做项目时,发现 OTF 的渲染计算量比 TTF 大不少——说白了,MCU 算力有限,选错格式会卡顿。
| 对比项 | TTF (TrueType) | OTF (OpenType) |
|---|---|---|
| 轮廓曲线 | 二次贝塞尔 | 三次贝塞尔 / PostScript |
| 渲染性能 | 较快 | 较慢(尤其小字号) |
| 字形容量 | 最多 65535 个 | 最多 65535 个 |
| 高级排版 | 有限 | 支持连字、变体等 |
| TouchGFX 推荐 | ✅ 首选 | ⚠️ 需测试 |
字体文件的「骨架」:关键表结构
一个 TTF/OTF 文件,本质上是一堆「表」的集合。每个表都有 4 字节的标签名。你不需要记住所有表,但下面这几个必须认识——它们直接决定了字体在 TouchGFX 里的表现。
cmap 表:Unicode 到字形索引的「翻译官」
cmap 表是字体里最重要的表之一。它的任务很简单:给你一个 Unicode 码点,返回对应的字形索引(glyph index)。
举个例子,字符 'A' 的 Unicode 是 U+0041,cmap 表会告诉你它在 glyf 表里的索引是 36(具体数字因字体而异)。
cmap 表有多种子格式,最常见的是 Format 4(BMP 范围,U+0000 ~ U+FFFF)和 Format 12(完整 Unicode 范围)。
字形度量:每个字符的「尺寸说明书」
字形度量决定了字符在屏幕上怎么摆放。每个字符都有以下几个关键参数:
- advanceWidth:字符占用的水平宽度。说白了就是「这个字占多宽」。
- lsb (left side bearing):左侧空白距离。
- rsb (right side bearing):右侧空白距离。
- bbox (bounding box):字形实际轮廓的包围盒。
这些数据存在 hmtx 表和 glyf 表里。我遇到过一个问题:某个字体在 PC 上显示正常,但到嵌入式设备上文字挤在一起。查了半天,发现是 hmtx 表里的 advanceWidth 被 TouchGFX 的字体生成工具重新计算了——因为原始字体的度量单位是 2048 UPM,而 TouchGFX 内部用的是 1024 UPM,缩放时精度丢失了。
字距调整(Kerning):让文字「呼吸」
字距调整解决的是特定字符对之间的间距问题。比如 "AV" 这两个字母,如果按默认的 advanceWidth 排列,A 和 V 之间会有一个明显的空隙。字距调整表(kern 表)会告诉渲染引擎:当 A 后面跟着 V 时,V 需要向左移动 50 个单位。
在 TouchGFX 里,字距调整默认是开启的。但这里有个取舍:
| 场景 | 建议 | 原因 |
|---|---|---|
| 英文界面,字号 ≥ 16px | 保留字距调整 | 视觉效果好,尤其标题 |
| 中文界面 | 关闭字距调整 | 中文字符几乎不需要 kern |
| 小字号(≤ 12px) | 关闭字距调整 | 像素级渲染,字距影响不明显 |
| 资源极度紧张 | 关闭字距调整 | kern 表可能占 5%~15% 体积 |
Unicode 编码与字符映射:别被「乱码」坑了
Unicode 编码是字符的唯一身份证。但字体文件里怎么存这些编码?
cmap 表里维护了一个映射表。比如:
Unicode U+0041 → Glyph Index 36
Unicode U+0042 → Glyph Index 37
Unicode U+4E2D → Glyph Index 1234 (中)
Unicode U+6587 → Glyph Index 1235 (文)
TouchGFX 在渲染文本时,会先把字符串里的每个字符转成 Unicode 码点,然后查 cmap 表拿到 glyph index,最后从 glyf 表里取出轮廓数据去渲染。
这里有个常见的坑:字体里没有你需要的字符。比如你用了 "Hello 世界",但字体只包含 ASCII 字符,没有中文字形。TouchGFX 会显示一个空白方块(tofu)。
实战:如何用工具查看字体结构?
光说不练假把式。我推荐两个工具:
- FontForge:开源,功能强大。可以查看每个表的原始数据。
- ttx(fonttools):Python 工具,能把 TTF/OTF 转成 XML,方便分析。
用 ttx 查看 cmap 表的命令:
# 安装 fonttools
pip install fonttools
# 导出 cmap 表为 XML
ttx -t cmap myfont.ttf
# 查看生成的 myfont.ttx 文件
# 你会看到类似这样的内容:
# <cmap>
# <tableVersion version="0"/>
# <cmap_format_4 platformID="3" platEncID="1" language="0">
# <map code="0x41" name="A"/> <!-- U+0041 -->
# <map code="0x42" name="B"/> <!-- U+0042 -->
# </cmap_format_4>
# </cmap>
你可以数一下有多少个 <map> 条目。如果字体有 20000 个字符,但你的项目只用 100 个——嗯,这就是压缩空间所在。
小结:这一章你该记住什么?
- TTF 和 OTF 的核心区别在于曲线类型,TouchGFX 优先用 TTF。
- cmap 表是 Unicode 到字形索引的桥梁,子集化就是裁剪这个表。
- 字形度量(advanceWidth、lsb、rsb)决定了文本布局,注意缩放精度。
- 字距调整(kern)对英文有用,对中文和小字号可以关闭以节省空间。
- 用 ttx 或 FontForge 查看字体结构,是优化前的必要步骤。