3. 字体基础概念
做嵌入式 GUI 这么多年,我接触过的字体格式少说也有七八种。说实话,每种格式都有自己的脾气。你如果不摸清它们的底细,后面做字库移植时肯定会踩坑。今天我就把 BDF、FONT、TTF/OTF 这三种常见格式,以及 LVGL 的字体转换工具,掰开了跟你聊聊。
核心观点:选对字体格式,等于省掉一半的调试时间。LVGL 官方推荐用 TTF/OTF 转内部格式,但老项目里 BDF 和 FONT 格式依然常见。
BDF 格式:开源世界的点阵老兵
BDF(Bitmap Distribution Format)是 Adobe 在 80 年代推出的点阵字体格式。它用纯文本描述每个字符的像素点阵。你想想看,一个字体文件打开后全是 ASCII 字符,每行都写着「BITMAP」「ENDCHAR」—— 这就是 BDF。
我个人习惯在调试阶段用 BDF 字体。为什么?因为它可读性极强。你可以直接用文本编辑器打开,看到每个字符的像素排列。我曾经在一个老项目里,客户要求显示 16x16 的汉字点阵,当时手头没有现成的字库,我就用 BDF 格式手写了一个小字库。虽然累,但确实管用。
小技巧:BDF 文件里每个字符的编码是 Unicode 值。你搜索 ENCODING 0x4E2D 就能找到汉字「中」的定义。这在调试字符映射时特别方便。
BDF 的缺点也很明显:文件体积大。一个 16x16 的 GB2312 字库,BDF 格式可能要几 MB。而且它是纯文本,解析速度比二进制格式慢不少。所以量产产品里很少直接用 BDF。
FONT 格式:嵌入式老前辈的二进制遗产
FONT 格式是 Linux 控制台和早期嵌入式系统里常用的二进制点阵格式。它没有 BDF 那么啰嗦,直接把像素数据打包成二进制。结构很简单:一个文件头描述字体属性,后面跟着每个字符的位图数据。
我记得有一次接手一个十年前的产品维护,里面用的就是 FONT 格式。当时想加几个特殊符号,结果发现没有现成的转换工具,只能自己写脚本从 BDF 转成 FONT。嗯,那两天过得挺充实。
注意:FONT 格式没有统一的规范。不同系统、不同工具生成的 FONT 文件,文件头结构可能不一样。跨平台使用时一定要确认字节序和字段偏移。
FONT 格式的优势是紧凑。同样一个 16x16 点阵字库,FONT 格式比 BDF 小 5-8 倍。但它不支持矢量缩放,放大后锯齿明显。现在新项目里已经很少用了。
TTF/OTF 格式:现代字体的标准答案
TTF(TrueType Font)和 OTF(OpenType Font)是矢量字体格式。它们用数学曲线描述字形轮廓,而不是像素点阵。这意味着你可以任意缩放,不会失真。
LVGL 官方推荐使用 TTF/OTF 作为输入源。为什么?因为 LVGL 的字体引擎可以直接解析 TTF 的轮廓数据,然后按你需要的像素大小渲染出点阵。说白了,你只需要一个 TTF 文件,就能生成 12px、16px、24px 等任意大小的字库。
我在项目中遇到过最典型的场景:产品需要支持中英文混排,英文用 12px,中文用 16px。如果用点阵字库,你得准备两套。但用 TTF,一个文件就搞定。省事不少。
性能提醒:TTF 解析需要浮点运算和曲线插值。如果你的 MCU 没有 FPU,渲染速度会明显下降。建议在 PC 端提前转成 LVGL 内部格式,不要在运行时实时解析 TTF。
LVGL 字体转换工具:把一切变成内部格式
不管你用 BDF、FONT 还是 TTF,最终都要转成 LVGL 能直接用的内部格式。LVGL 官方提供了一个在线转换工具(LVGL Font Converter),也提供了命令行工具 lv_font_conv。
这个工具的核心能力就三件事:
- 读取输入:支持 TTF/OTF、BDF、FONT、WOFF 等格式
- 指定字符范围:你可以只转换需要的字符,比如 ASCII + 常用汉字,而不是整个 Unicode 表
- 输出格式:生成 C 源码文件(.c)或二进制字库文件(.bin)
我建议你优先用命令行工具。在线工具虽然方便,但文件大了容易超时。命令行工具稳定得多。下面是一个典型用法:
lv_font_conv --font NotoSansSC-Regular.ttf \
--size 16 \
--range 0x20-0x7F \
--range 0x4E00-0x9FFF \
--format lvgl \
--output my_font.c
这个命令做了什么事?它读取 NotoSansSC-Regular.ttf,生成 16px 大小的字库,只包含 ASCII 和常用汉字(Unicode 范围 0x4E00-0x9FFF),输出为 LVGL 的 C 源码格式。
经验之谈:字符范围不要贪多。一个 16px 的 GB2312 字库(约 6763 个汉字)转成 C 文件大概 200-300KB。如果你把整个 Unicode 都转进去,文件轻松上 MB。嵌入式 Flash 很贵的,够用就好。
转换工具还支持一些高级选项:
| 选项 | 说明 | 我的建议 |
|---|---|---|
--size |
字体像素大小 | 一般用 12-24,太大占用 Flash 多 |
--bpp |
每个像素的位数(1/2/4/8) | 黑白屏用 1,彩屏用 4 或 8 |
--no-compress |
禁用压缩 | 调试时用,量产建议开启压缩 |
--symbols |
直接指定字符列表 | 适合只显示固定字符的场景 |
说到压缩,LVGL 内部格式支持 RLE 压缩。我曾经对比过:一个 16px 的汉字字库,不压缩 280KB,压缩后 120KB。压缩率超过 50%。所以量产时一定要开压缩。
避坑指南:我曾经在项目里用了一个第三方 TTF 字体,转换时没报错,但显示时某些字符偏移了。后来发现是 TTF 文件的度量信息(metrics)不规范。建议用 Google Fonts 或思源系列字体,它们经过大量验证,兼容性最好。
最后说一句:不管你选哪种格式,最终都要在目标硬件上跑一遍显示测试。别偷懒,把常用字符、边界字符(比如空格、标点、全角/半角)都显示一遍。我吃过这个亏,一次显示测试没做,结果客户发现「一」和「—」显示不出来,紧急修了一版固件才搞定。