3、字体文件格式详解:BDF格式、FONT格式、TTF/OTF格式、LVGL字体转换工具

做嵌入式UI,字体这块儿绕不开。说白了,你得先搞清楚字体文件到底长什么样,才能在LVGL里用好它。我刚开始接触LVGL时,就被各种字体格式搞得一头雾水——BDF、FONT、TTF、OTF,每个都说是字体,但用法天差地别。今天咱们就把这几个格式掰开揉碎了讲清楚。

3.1 BDF格式:最原始的位图字体

BDF全称是Bitmap Distribution Format,上世纪80年代的老东西了。但它至今还在嵌入式领域活跃着,为什么?因为它足够简单。

BDF文件本质上是纯文本,你可以用记事本直接打开看。它把每个字符都描述成一个点阵,比如一个16x16的汉字,就是256个0和1的排列。0代表空白,1代表显示。

我个人习惯在调试阶段用BDF字体,因为出了问题可以直接看源码。有一次我在项目里发现某个汉字显示不全,打开BDF文件一查,原来是点阵数据少了一行。这种问题用二进制格式根本没法查。

BDF的核心特点:

  • 纯文本格式,可读性强
  • 每个字符固定大小(比如16x16、24x24)
  • 不支持抗锯齿,边缘有锯齿感
  • 文件体积小,适合资源紧张的MCU

BDF的缺点也很明显——不支持缩放。你做一个16x16的BDF字体,想放大到32x32?对不起,要么重新生成,要么忍受马赛克效果。我在一个128x64的OLED屏项目里用过BDF,效果还行,但换到320x240的TFT屏上就完全不能看了。

3.2 FONT格式:LVGL的亲儿子

FONT格式是LVGL自己定义的二进制字体格式。你想想看,LVGL要高效地在MCU上渲染文字,总不能每次都去解析BDF或者TTF吧?所以它搞了一套自己的格式。

FONT文件是编译好的二进制数据,包含了字体的所有信息:字符的位图数据、字符宽度、字符间距、甚至包括抗锯齿信息。LVGL加载FONT字体时,直接内存映射就能用,速度极快。

我的经验:在正式项目中,我几乎只用FONT格式。原因很简单——它和LVGL的渲染引擎是深度绑定的,性能最优。我曾经在STM32F407上做过测试,同样的字体,FONT格式的渲染速度比TTF快3倍以上。

FONT格式的生成通常依赖LVGL的字体转换工具。你不需要手写FONT文件,那是自找麻烦。后面我会详细讲转换工具怎么用。

3.3 TTF/OTF格式:矢量字体的王者

TTF(TrueType Font)和OTF(OpenType Font)是PC和手机上的主流格式。它们用数学曲线来描述字形,而不是点阵。这意味着——无限缩放不失真。

但问题来了:MCU能直接渲染TTF吗?答案是能,但代价很大。TTF的渲染需要解析贝塞尔曲线,计算量大,而且需要浮点运算。在Cortex-M4上勉强能跑,在Cortex-M0上基本别想。

我记得有个项目需要在320x240的屏上显示多种字号的中文。如果用BDF,每种字号都要单独生成一个字体文件,太占Flash了。后来我改用TTF,只存一个文件,运行时动态缩放。虽然渲染慢了点,但Flash省了一大半。

注意:LVGL对TTF的支持是通过第三方库实现的,比如FreeType或STB。你需要额外移植这些库,而且会占用不少RAM。如果你的MCU只有64KB RAM,建议慎重考虑。

OTF和TTF的区别不大,OTF支持更多的排版特性,比如连字、变体等。但在嵌入式场景下,这些特性基本用不上。我个人建议优先用TTF,兼容性更好。

3.4 LVGL字体转换工具:把一切变成FONT

前面说了这么多格式,最后在LVGL里真正好用的还是FONT。那怎么把BDF、TTF、OTF转成FONT呢?LVGL官方提供了一个在线工具——LVGL Font Converter

这个工具的使用流程很简单:

  1. 选择源字体文件(BDF、TTF、OTF都行)
  2. 设置目标字号(比如16px、24px)
  3. 选择要包含的字符范围(比如ASCII、中文、日文)
  4. 点击转换,生成C源文件或二进制FONT文件

我一般用命令行版本,方便集成到自动化构建脚本里。命令大概是这样的:

lv_font_conv --font NotoSansSC-Regular.ttf \
  --size 16 \
  --range 0x20-0x7F \
  --range 0x4E00-0x9FFF \
  --format lvgl \
  --output my_font.c

这个命令会生成一个C文件,里面包含了字体的所有数据。你把它编译进项目,然后在LVGL里注册一下就能用了。

关键参数说明:

参数 说明
--font 源字体文件路径
--size 目标字号(像素)
--range 字符范围,支持Unicode区间
--format 输出格式,lvgl或bin
--output 输出文件路径

我曾经踩过一个坑:转换中文时忘记指定--range 0x4E00-0x9FFF,结果生成的字体只有英文字母,中文全显示成方框。嗯,这个错误我犯了两次才记住。

3.5 如何选择字体格式?

说了这么多,到底该用哪种?我根据项目经验总结了一个简单的选择策略:

  • 资源极度紧张(Flash < 128KB,RAM < 32KB):用BDF,只包含需要的字符,体积最小
  • 资源适中(Flash 256KB-1MB,RAM 64KB-128KB):用FONT,性能和体积的平衡点
  • 资源充裕(Flash > 1MB,RAM > 128KB):用TTF,灵活性和显示效果最好

我个人最常用的方案是:FONT格式 + 预生成常用字符。比如一个中文菜单界面,我提前把用到的200个汉字转成FONT,既保证了渲染速度,又不会浪费Flash存用不到的字符。

避坑指南:我曾经在一个项目里把所有中文字符(约2万个)都转成了FONT,结果Flash直接爆了。后来才意识到,嵌入式UI用到的汉字通常不超过500个。所以,只转你需要的字符,别贪多。

好了,字体格式这块儿就讲到这里。下一章咱们聊聊如何在LVGL里加载和管理这些字体,包括动态切换、缓存策略这些实战技巧。