4、字体转换工具链:LVGL官方字体转换器、FreeType库集成、Python脚本批量转换
字体文件本身LVGL是读不懂的。你得把.ttf、.woff这些格式,转成LVGL能吃的二进制数据。说白了,就是给字体做一次「编译」。这一章我带你捋一遍市面上主流的三种转换路子,各有各的脾气。
4.1 LVGL官方字体转换器(Online & Offline)
LVGL官方提供了两种转换方式。在线版适合快速验证,离线版适合集成到构建流程里。
4.1.1 在线转换器(Online Converter)
地址我就不贴了,你搜「LVGL Font Converter」就能找到。它的操作很简单:上传字体文件,选好字号、字符范围、bpp(每像素位数),点一下按钮就生成.c文件。
我个人习惯在原型阶段用它。比如我想快速看看某个字体在16px下的显示效果,直接拖进去,几秒钟拿到代码,烧进去跑一下。省事。
| 参数 | 说明 | 我的建议 |
|---|---|---|
| BPP(Bits Per Pixel) | 每个像素用多少位表示透明度 | 1/2/4/8,常用4,兼顾清晰度和体积 |
| 字符范围 | 只转换你需要的字符 | 别全选,否则生成的数组能撑爆Flash |
| 字体大小 | 像素单位 | 建议按实际显示尺寸来,不要事后缩放 |
4.1.2 离线转换器(Offline Converter)
离线版其实是一个Python脚本,叫lv_font_conv。它比在线版灵活得多,支持命令行参数,可以集成到Makefile或CMake里。
基本用法长这样:
lv_font_conv --font NotoSansSC-Regular.ttf \
--size 16 \
--bpp 4 \
--range 0x20-0x7F \
--range 0x4E00-0x9FFF \
--format lvgl \
--output my_font.c
这里--range可以指定多个区间。中文字符的Unicode范围是0x4E00到0x9FFF,但实际你只需要常用字,别一股脑全转。我在项目里一般只转GB2312一级汉字(3755个),够用。
--symbols参数可以手动指定字符列表,比如只转「设置」「确认」「取消」这几个词。生成的字体文件极小。
4.2 FreeType库的集成
FreeType是一个开源的字体渲染引擎。LVGL本身不依赖它,但如果你想在运行时动态加载字体,或者做更精细的渲染控制,就得把它集成进来。
为什么要用FreeType?
- 动态加载:字体文件放在文件系统里,运行时读取,不用编译时固定死。
- 抗锯齿:FreeType的渲染质量比LVGL内置的位图字体好。
- 字体回退:一个字符在字体A里找不到,自动去字体B里找。
集成步骤其实不复杂:
- 把FreeType源码放到你的工程里,或者用包管理器安装。
- 在LVGL的配置文件中开启
LV_USE_FREETYPE。 - 初始化FreeType并注册字体。
代码示例:
#include "lvgl.h"
#include "lv_freetype.h"
lv_font_t *my_font;
void font_init(void) {
lv_freetype_init(0); // 0表示不缓存,性能敏感场景可以设大一点
my_font = lv_freetype_font_create(
"path/to/font.ttf", // 字体文件路径
LV_FREETYPE_FONT_RENDER_MODE_BITMAP, // 位图模式
16, // 字号
LV_FREETYPE_FONT_STYLE_NORMAL
);
if (my_font == NULL) {
// 处理错误
}
}
LV_FREETYPE_CACHE_SIZE控制。
4.3 Python脚本批量转换
当你需要管理几十个字体文件、多种字号、多语言时,手动敲命令就太蠢了。这时候就该上Python脚本了。
我的做法是写一个batch_convert.py,遍历字体目录,自动生成所有需要的.c文件。核心逻辑如下:
import os
import subprocess
FONT_DIR = "./fonts"
OUTPUT_DIR = "./generated_fonts"
LV_FONT_CONV = "lv_font_conv"
# 定义需要转换的字体配置
FONT_CONFIGS = [
{"name": "noto_sans_sc", "file": "NotoSansSC-Regular.ttf", "sizes": [14, 16, 20]},
{"name": "noto_sans", "file": "NotoSans-Regular.ttf", "sizes": [12, 14, 16]},
]
def convert_font(config):
for size in config["sizes"]:
output_file = f"{config['name']}_{size}px.c"
output_path = os.path.join(OUTPUT_DIR, output_file)
cmd = [
LV_FONT_CONV,
"--font", os.path.join(FONT_DIR, config["file"]),
"--size", str(size),
"--bpp", "4",
"--range", "0x20-0x7F", # ASCII
"--range", "0x4E00-0x9FFF", # 中文
"--format", "lvgl",
"--output", output_path
]
subprocess.run(cmd, check=True)
print(f"Generated: {output_file}")
if __name__ == "__main__":
os.makedirs(OUTPUT_DIR, exist_ok=True)
for config in FONT_CONFIGS:
convert_font(config)
这个脚本跑一次,所有字体文件就都准备好了。你只需要在代码里#include对应的.c文件就行。
4.4 三种方案对比
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 在线转换器 | 原型验证、小字体 | 无需安装,上手快 | 文件大小受限,不能批量 |
| 离线转换器 | 中等规模项目 | 命令行可控,可集成构建 | 需要Python环境 |
| FreeType集成 | 动态加载、多语言 | 运行时灵活,渲染质量高 | 内存占用大,集成复杂 |
| Python脚本批量 | 大规模生产 | 自动化,可定制 | 需要维护脚本 |
4.5 知识体系总览
下面这张图把本章的核心脉络画出来了。你可以看到从字体源文件到LVGL可用的字体数据,中间经过了哪些环节。
嗯,这张图你看懂了吗?从源文件出发,三条路径最终都汇聚到LVGL的.c字体数据。而Python脚本就像一条「传送带」,可以把任意一条路径自动化起来。
我个人建议:如果你的项目还在早期,先用在线转换器快速验证UI效果。等UI定稿了,再写Python脚本批量生成正式字体文件。别一上来就搞自动化,容易陷入工具链的坑里出不来。