字体文件格式:LVGL支持的字体格式与lv_font_conv详解
说到LVGL的字体系统,我刚开始接触时也有点懵。明明都是字体,怎么还有bin、ttf、woff这么多后缀?后来踩了几个坑才搞明白——LVGL并不直接使用你电脑上的字体文件,它需要经过一道转换工序。今天咱们就把这事儿彻底说清楚。
LVGL支持的三种字体格式
LVGL支持三种字体来源,但真正在嵌入式设备上跑起来的,只有一种。你想想看,MCU那点资源,哪能直接解析TTF?
| 格式 | 来源 | 是否直接用于LVGL | 典型场景 |
|---|---|---|---|
| TTF / OTF | 桌面字体文件 | ❌ 需转换 | 设计原型、字体源文件 |
| WOFF / WOFF2 | Web字体 | ❌ 需转换 | 从网络获取的字体资源 |
| LVGL专属bin | 转换后二进制 | ✅ 直接加载 | 嵌入式设备运行时使用 |
核心结论:LVGL最终只认两种东西——.bin字体文件,或者C源码数组。TTF/WOFF只是原材料。
为什么不能直接用TTF?
这个问题我当年也问过。说白了,TTF是矢量字体,它存的是贝塞尔曲线数学公式。LVGL要在MCU上渲染一个字符,得实时解析曲线、计算像素点——这对CPU和内存都是灾难。
转换后的bin文件是什么?它是预渲染好的位图字典。每个字符的像素数据、宽高、偏移量都算好了,LVGL只需要查表、拷贝,效率极高。
我的经验:曾经在一个STM32F4项目里,我试过用TinyTTF库直接解析TTF,结果渲染一个"中"字花了12ms。换成预渲染bin后,同样的字只要0.3ms。差距就是这么大。
lv_font_conv:核心转换工具
lv_font_conv是LVGL官方提供的字体转换器。它把TTF/WOFF转成LVGL能吃的bin或C数组。我建议你直接装Node.js版本,跨平台,更新也快。
安装方式
# 全局安装
npm install -g lv_font_conv
# 验证安装
lv_font_conv --version
最常用的转换命令
我平时用得最多的命令长这样:
lv_font_conv --font NotoSansSC-Regular.ttf \
--size 16 \
--bpp 4 \
--range 0x20-0x7F \
--range 0x4E00-0x9FFF \
--format lvgl \
--output my_font_16.bin
拆开来看每个参数:
--font:源字体文件路径,支持TTF、OTF、WOFF--size:像素大小,不是pt,是px。16就是16像素高--bpp:每个像素用多少位。可选1、2、4、8。值越大抗锯齿越好,但体积也越大--range:要包含的Unicode范围。可以写多个--format:输出格式。lvgl是bin,也可以选raw或c--output:输出文件名
注意:如果不指定--range,默认只包含ASCII(0x20-0x7E)。中文一个都不会有!我曾经因为这个在客户现场演示时,界面上全是方框……
bpp参数:性能与质量的平衡点
bpp(Bits Per Pixel)是调优的关键。我画了张图帮你理解:
Unicode范围:别让字体文件白白膨胀
很多人不注意这个。一个完整的思源黑体TTF有20MB,你把它全转成bin,MCU的Flash直接爆了。正确的做法是:只包含你实际用到的字符。
常用范围速查表:
| 范围 | 包含内容 | 字符数 |
|---|---|---|
0x20-0x7F |
基本ASCII(字母、数字、符号) | 96 |
0x4E00-0x9FFF |
常用汉字(CJK统一表意文字) | 约20,000 |
0x3000-0x303F |
CJK符号和标点 | 64 |
0xFF00-0xFFEF |
全角ASCII和半角片假名 | 240 |
我的做法:先跑一遍程序,把所有界面字符串收集起来,去重后生成一个字符列表。然后用--symbols参数指定这些字符,而不是用--range。这样生成的字体文件最小。
输出格式:bin vs C数组
lv_font_conv支持两种输出格式:
- bin文件:运行时从文件系统加载。适合有文件系统的设备,比如带SD卡或SPI Flash的
- C数组:编译进固件。适合没有文件系统的裸机环境
生成C数组的命令:
lv_font_conv --font NotoSansSC-Regular.ttf \
--size 16 --bpp 2 \
--range 0x20-0x7F --range 0x4E00-0x9FFF \
--format c \
--output my_font_16.c
生成的C文件里是一个const uint8_t数组,直接#include进项目就能用。
注意:C数组方式会让固件体积暴增。一个16px、包含2000个汉字的字体,C数组大约500KB。如果你的MCU Flash只有512KB……嗯,你懂的。
实战:完整转换流程
我整理了一个标准流程,照着做基本不会出错:
- 准备源字体:找好TTF文件,注意版权。我常用Noto Sans CJK或阿里巴巴普惠体
- 确定字符集:列出所有界面用到的字符,去重
- 选择bpp:16px以下用2,24px以上用4
- 执行转换:用lv_font_conv生成bin或C文件
- 验证:在LVGL中用
lv_font_load()加载,显示几个字符看看
// 加载bin字体
lv_font_t *my_font = lv_font_load("S:/fonts/my_font_16.bin");
if (my_font == NULL) {
LV_LOG_ERROR("字体加载失败!");
}
// 使用字体
lv_obj_set_style_text_font(label, my_font, 0);
常见问题与避坑
问题1:转换后字体显示为方框
原因:字符不在转换时指定的范围内。检查--range或--symbols是否包含了该字符。
问题2:字体文件太大,Flash放不下
解决方案:降低bpp、缩小字符集、或者改用bin文件从外部存储加载。
问题3:渲染速度慢
原因:bpp太高或字体太大。我建议先检查bpp,从2开始试。如果还慢,考虑用LVGL的字体缓存机制。
嗯,字体格式这块儿其实不难,关键是把转换工具用熟。我刚开始时每次都要翻文档查参数,后来写了个shell脚本一键转换,省了不少事。你也可以试试。