2、字库文件格式解析:BDF、PCF、FON、TTF等常见格式的结构与适用场景
做嵌入式GUI,选字库格式是个绕不开的坎。我记得刚入行那会儿,项目里用了个8MB的SPI Flash,结果一个TTF字库就占了6MB,剩下那点空间连个图标都塞不进去。后来我才明白——选对格式,比闷头优化更重要。
今天咱们就把BDF、PCF、FON、TTF这四种常见格式掰开揉碎聊一聊。说白了,每种格式背后都对应着不同的硬件资源和应用场景,没有绝对的好坏,只有合不合适。
2.1 BDF格式——最“老实”的字库
BDF(Bitmap Distribution Format)是Adobe搞出来的纯文本点阵字库格式。你拿记事本打开就能看,每个字符的像素点阵直接写成十六进制数。我最早接触它是在一个黑白屏的温控器项目上,当时觉得这格式太原始了,后来才发现——原始有原始的好处。
核心特点:
- 纯文本存储,人类可读,方便调试
- 每个字符独立定义,包含宽度、高度、偏移量
- 解析需要逐行扫描,速度慢
- 文件体积大(同样的点阵,比PCF大3~5倍)
举个例子,一个ASCII字符'A'在BDF里长这样:
STARTCHAR A
ENCODING 65
SWIDTH 500 0
DWIDTH 8 0
BBX 8 11 0 -2
BITMAP
00
00
18
24
24
42
7E
42
42
00
00
ENDCHAR
你看,每个像素点用1个bit表示,一行8个像素凑成一个字节。BBX那行定义了字符的边界框——宽8像素,高11像素,水平偏移0,垂直偏移-2。嗯,这里要注意,偏移量处理不好,字符就会上下乱跳。我踩过这个坑,后面会细说。
我的建议:BDF只适合在开发阶段用。你可以在PC上生成BDF,用脚本验证每个字符的位图是否正确,确认无误后再转成PCF或直接生成C数组。别把它带到产品里,Flash吃不消。
2.2 PCF格式——嵌入式点阵的“标准答案”
PCF(Portable Compiled Format)是X Window系统搞出来的二进制点阵格式。说白了,它就是BDF的编译版本——把文本解析成二进制结构,解析速度直接起飞。
我个人习惯在MCU项目里首选PCF。为什么?
- 解析快:二进制结构,直接内存映射,不需要逐行扫描
- 体积小:去掉注释和冗余信息,同样点阵比BDF小60%以上
- 无版权包袱:不像TTF那样有字体许可证问题
- 工具链成熟:bdftopcf一条命令搞定
PCF的内部结构大致分几个表:
| 表类型 | 作用 | 大小(典型值) |
|---|---|---|
| GLYPH_NAMES | 字符名称索引 | 几百字节 |
| GLYPH_METRICS | 每个字符的宽度、高度、偏移 | 每个字符8字节 |
| GLYPH_BITMAPS | 像素点阵数据 | 取决于点阵大小 |
| ENCODING | 字符编码到索引的映射 | 每个字符4字节 |
我曾经在一个STM32F103的项目里,用PCF格式加载了一个12x12的GB2312字库,总共才120KB。同样的内容如果用BDF,得奔着400KB去了。你想想看,对于只有64KB RAM的芯片,这差距意味着什么。
注意:PCF虽然好,但它只支持点阵。如果你需要缩放、旋转、抗锯齿,PCF就无能为力了。另外,PCF的字节序是大端(Big Endian),在ARM Cortex-M这类小端处理器上读取时,记得做字节序转换。我曾经因为这个原因,在移植字库驱动时折腾了一整天。
2.3 FON格式——Windows时代的“遗老”
FON是Windows 3.x/9x时代的点阵字库格式。说实话,在嵌入式领域,它已经基本被淘汰了。但如果你在维护一些老旧的工控设备,或者需要兼容Windows CE系统,还是可能会碰到它。
FON本质上是一个NE格式的可执行文件(.exe)改了个后缀名,里面嵌入了点阵字库资源。它的结构比较特殊:
- 文件头是NE(New Executable)格式
- 字库数据存储在资源段里
- 每个字符的点阵按Windows GDI的规范排列
我个人不建议在新项目里用FON。原因有三:
- 解析复杂:你得先解析NE文件头,再找资源段,再解析字库表——太绕了
- 跨平台差:离开Windows生态,基本没法用
- 工具链稀缺:能生成FON的工具少之又少
避坑指南:如果你不得不处理FON,建议先用工具把它转成BDF或PCF,再集成到嵌入式系统里。我曾经用Windows自带的fontview.exe配合一个开源转换脚本,把一批老工控设备的FON字库全转成了PCF,省了不少事。
2.4 TTF格式——矢量字库的“双刃剑”
TTF(TrueType Font)是Apple和Microsoft联合推出的矢量字库格式。它用二次贝塞尔曲线来描述字形轮廓,理论上可以缩放到任意大小而不失真。听起来很美好,对吧?但在嵌入式世界里,它是个“甜蜜的负担”。
TTF的核心结构包括:
| 表名 | 作用 | 是否必须 |
|---|---|---|
| cmap | 字符编码到字形索引的映射 | 是 |
| glyf | 字形轮廓数据(贝塞尔曲线) | 是 |
| head | 字体全局信息(单位、边界等) | 是 |
| hhea | 水平排版度量 | 是 |
| hmtx | 每个字符的水平步进宽度 | 是 |
| loca | 字形数据在glyf表中的偏移索引 | 是 |
| kern | 字符间距微调(可选) | 否 |
TTF在嵌入式里的痛点很明显:
- 解析开销大:需要实现TrueType解释器,解析贝塞尔曲线,做栅格化
- 内存占用高:一个中文字体TTF动辄3~10MB
- 实时性难保证:第一次渲染某个字符时,需要实时栅格化,可能卡顿
但话说回来,如果你的硬件够强——比如Cortex-A系列处理器配了GPU,或者有硬件矢量加速单元——TTF带来的视觉效果是点阵字库没法比的。我见过一个高端HMI项目,用FreeType渲染TTF,配合硬件加速,实现了平滑缩放和抗锯齿,效果确实惊艳。
我的经验:在MCU上跑TTF,除非你有硬件加速,否则别碰。如果实在需要矢量效果,可以考虑预处理——在PC上把TTF栅格化成多个点阵尺寸,存成PCF或C数组。这样既保留了矢量字体的美观,又避免了运行时的解析开销。
2.5 格式对比与选型建议
说了这么多,咱们直接上对比表,一目了然:
| 特性 | BDF | PCF | FON | TTF |
|---|---|---|---|---|
| 存储方式 | 文本 | 二进制 | 二进制(NE格式) | 二进制(SFNT格式) |
| 解析速度 | 慢 | 快 | 中等 | 慢(需栅格化) |
| 文件体积 | 大 | 小 | 中等 | 大 |
| 缩放能力 | 无 | 无 | 无 | 有(矢量) |
| 抗锯齿 | 不支持 | 不支持 | 不支持 | 支持 |
| 跨平台 | 好 | 好 | 差(仅Windows) | 好 |
| 适用硬件 | 任何(仅调试) | MCU/RTOS | 老旧工控 | MPU/Linux |
| 工具链 | 丰富 | 丰富 | 稀缺 | 丰富(FreeType等) |
选型其实就一句话:资源越受限,格式越“原始”。如果你的MCU只有几十KB RAM,老老实实用PCF或者直接生成C数组。如果你的系统跑着Linux,有足够的内存和CPU,TTF是更好的选择。至于BDF和FON,除非有特殊原因,否则尽量避开。
最后分享一个实用技巧:不管选哪种格式,都建议在字库文件里加上一个简单的校验头(比如CRC32)。我曾经在一个产品里,因为Flash老化导致字库数据损坏,屏幕上的汉字全变成了乱码。加了校验后,每次加载字库先做校验,数据损坏时自动回退到内置的“安全字体”,再也不会出现满屏“口口口”的尴尬了。