1. 字体渲染基础:像素与矢量字体的区别、点阵字体原理、TrueType/OpenType 字体结构简介
做嵌入式 UI 这么多年,我踩过最大的坑就是「字体」。你想想看,一个好好的界面,布局、颜色、交互都调完了,一上屏,文字糊成一团,或者缺笔画,或者干脆显示不出来。嗯,这种问题十有八九出在字体渲染上。
说白了,字体渲染就是把字库里的字形数据,变成屏幕上一个个像素点的过程。听起来简单,但嵌入式环境里资源有限,CPU 主频低、内存小、屏幕分辨率还五花八门,稍不注意就翻车。
这一章我们先打好基础。把像素字体、矢量字体、点阵原理、TrueType/OpenType 结构这些概念理清楚。后面再讲优化,你才能知道优化到底在优化什么。
1.1 像素字体 vs 矢量字体
先问个问题:你手机上的「宋体」和 LED 广告牌上的「16×16 点阵字」,本质区别在哪?
答案是——描述方式不同。
| 对比维度 | 像素字体(点阵) | 矢量字体(轮廓) |
|---|---|---|
| 存储方式 | 每个字对应一张位图 | 存储数学曲线和指令 |
| 缩放效果 | 放大后锯齿严重 | 理论上无限缩放不失真 |
| 渲染速度 | 极快,直接拷贝像素 | 慢,需要实时计算轮廓并填充 |
| 内存占用 | 固定大小,字库体积大 | 字库小,但渲染时需要临时分配内存 |
| 典型场景 | 低端 MCU、小屏、固定字号 | 高端 SoC、大屏、多字号 |
我在一个温控器项目里用过矢量字体。那款芯片主频只有 72MHz,RAM 64KB。每次切换界面,FreeType 库光渲染一个「温」字就要 30 多毫秒,界面卡得不行。后来换成预先生成的点阵字库,渲染时间降到 1 毫秒以内。你看,选对类型比什么都重要。
1.2 点阵字体原理
点阵字体,说白了就是「画好格子,填颜色」。每个字对应一个固定尺寸的位图,比如 16×16 像素,那就是 256 个格子。每个格子要么是 0(不显示),要么是 1(显示)。
存储结构很简单:
// 16x16 点阵字模,每个字 32 字节
// 每行 2 字节,共 16 行
const uint8_t font_16x16_zhong[] = {
0x00, 0x00, // 第 1 行
0x7F, 0xFC, // 第 2 行
0x40, 0x04, // 第 3 行
// ... 共 16 行
};
渲染时,直接把这 32 个字节按位映射到显存对应位置。没有计算,没有插值,就是纯粹的「内存拷贝 + 位操作」。所以点阵字体在低端 MCU 上特别吃香。
但点阵字体有个硬伤——不能缩放。你做一个 16×16 的字库,就只能用在 16 像素的场景。想用 24 像素?重新生成一套。所以点阵字库的体积会随着字号和字符集数量线性增长。一个 GB2312 全集的 16×16 点阵字库,大概 256KB 左右。对于 64KB Flash 的芯片来说,这就是灾难。
1.3 TrueType/OpenType 字体结构简介
矢量字体就不一样了。它存的不是像素,而是「怎么画这个字」的指令。TrueType 和 OpenType 是目前最主流的两种矢量字体格式。
我刚开始接触 TrueType 时,被它的结构绕晕过。其实核心就三部分:
- 字形轮廓(glyph outlines):用二次贝塞尔曲线描述每个字的形状
- 度量信息(metrics):每个字的宽度、高度、基线偏移等
- 指令表(hinting instructions):告诉渲染器在小字号时怎么「对齐像素」
OpenType 在 TrueType 基础上扩展了 CFF(Compact Font Format)轮廓,支持 PostScript 曲线,还加入了排版特性(连字、变体、上下标等)。但底层逻辑是一样的。
一个典型的 TrueType 字形渲染流程:
- 从字库中读取字形索引对应的轮廓数据
- 将轮廓坐标从设计空间缩放到目标像素尺寸
- 执行 hinting 指令,微调轮廓位置
- 扫描线填充,生成像素位图
- 将位图输出到显存
你看,步骤 2 和 3 是最耗时的。尤其是 hinting,它本质上是一个微型虚拟机在执行字节码。我在一个项目里试过,关闭 hinting 后渲染速度提升了 40%,但小字号文字边缘变得模糊。这就是典型的「质量 vs 速度」取舍。
1.4 知识体系总览
下面这张图,把这一章的核心逻辑串起来了。你可以把它当作后续章节的「地图」。
从这张图你可以看到,无论是点阵还是矢量,最终都要落到「质量 vs 资源」这个天平上。后面 29 章,我们就是在教你怎么在这个天平上找到最优解。