2、字库文件格式解析:BDF、PCF、FON、TTF等常见格式的结构与适用场景

做嵌入式GUI,选字库格式是个绕不开的坎。我记得刚入行那会儿,项目里用了个8MB的SPI Flash,结果一个TTF字库就占了6MB,剩下那点空间连个图标都塞不进去。后来我才明白——选对格式,比闷头优化更重要。

今天咱们就把BDF、PCF、FON、TTF这四种常见格式掰开揉碎聊一聊。说白了,每种格式背后都对应着不同的硬件资源和应用场景,没有绝对的好坏,只有合不合适。

嵌入式GUI字库格式选型决策图 嵌入式GUI字库格式选型决策图 字库格式选型 BDF(文本点阵) PCF(二进制点阵) FON(Windows点阵) TTF(矢量轮廓) 纯文本,可读性强 解析慢,体积大 适合调试/原型 二进制,解析快 体积小,无版权 MCU首选格式 Windows生态 跨平台兼容差 嵌入式已淘汰 矢量缩放,美观 解析开销大 需硬件加速 调试/工具链 低端MCU/RTOS 遗留系统维护 高端MPU/Linux 核心原则:资源越受限,格式越“原始”

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。原因有三:

  1. 解析复杂:你得先解析NE文件头,再找资源段,再解析字库表——太绕了
  2. 跨平台差:离开Windows生态,基本没法用
  3. 工具链稀缺:能生成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老化导致字库数据损坏,屏幕上的汉字全变成了乱码。加了校验后,每次加载字库先做校验,数据损坏时自动回退到内置的“安全字体”,再也不会出现满屏“口口口”的尴尬了。