← 返回生成页

嵌入式点阵字库方案详解 —— 优缺点分析与 LVGL 官方字库对比

数据格式规格、设备端引擎设计与 LVGL 官方字库的逐维度对比(SVG 图解)

📝 说明:文中提到的 *.py / tests/* 等源码文件为工具内部实现的落点记录,源码暂未公开;不影响阅读方案本身。

本文面向使用/评估 Py_FontMaker 的嵌入式开发者,回答三个问题:

  1. 当前点阵字库方案是什么(数据格式、生成管线、设备端引擎);
  2. 它的优点和缺点分别是什么(逐条给依据,含代码位置);
  3. 它和 LVGL 官方点阵字库(lv_font_conv 产物)的差异在哪里(逐维度对比 + 选型建议)。

文中所有结论均以本仓库代码为准(ttf_conver_FontCvtST_xbf.pyxbf_to_fontlib_bin.pyfont_engine_code.py 等);LVGL 侧以 v8.3 的 lv_font_fmt_txt 与 lv_font_conv 官方文档为参照,v9 的接口差异在 §7.4 单独说明。


1. 方案总览

一句话概括:PC 端把 TTF 逐字栅格化成灰度点阵,打包成一个 Font_Lib.bin 烧进外挂 SPI Flash;设备端用自动生成的 C 引擎按需逐字形读取、解压、渲染,向上以 lv_font_t(LVGL)或裸机回调两种接口暴露。

总体方案架构

整条链路分两段:

PC 端工具链(Python,跨平台,另有 Web 在线生成与本地 GUI):

阶段 脚本 输入 → 输出
① 栅格化 ttf_freetype_to_xbf.py(默认,FreeType 直连;--renderer pygame 退回 ttf_conver_FontCvtST_xbf.py TTF → .xbf + .kern.json(每字号/每 bpp 一组)
② 打包 xbf_to_fontlib_bin.py 多个 .xbf + 码点配置 → Font_Lib.bin + 全套 C 代码
配套 ttf_to_fake_font_c.py / ttf_icon_to_c.py / emoji_to_c.py / ttf_to_inner_font_c.py / excel_multi_language_to_c.py fake 兜底字库 / 图标字库 / emoji 图片字库 / 内部字库 / 多语言表

变形字体(Variable Font)先用 fontTools instancer 按轴(如 wght 字重)固化成静态实例再进管线,一个 VF 文件可产出任意字重的点阵字库。

设备端(自动生成的 C 代码,见 font_engine_code.py 模板):


2. 数据格式详解

2.1 XBF 中间格式

XBF 是①阶段的产物,格式兼容 FontCvtST(ST 提供的 emWin 字体转换工具),因此也可以脱离本方案单独给 emWin/裸机工程使用。本工具直接生成 XBF,不依赖任何第三方 exe。

XBF 文件布局

要点(对应 ttf_conver_FontCvtST_xbf.py:34-157):

2.2 Font_Lib.bin 外挂字库格式

②阶段把若干 XBF 按各自的码点配置文件(font_type/*.txt,"范围段 + 文本段取并集")裁剪后打进一个 bin。

Font_Lib.bin 布局

要点(对应 xbf_to_fontlib_bin.py:85-111, 732-741, 1164-1361):

区域 起始地址 内容
字库头 0x0000 魔数 "John_LIB"(8B) + 随机版本 magic(u32) + 字体数(u32) + 首字体偏移 u16(=0x1000)
字体信息表 0x0100 每字体 64 B:44 B 名称 + bpp/cmap_continued/y_size/y_dist/baseline/l_hight/c_hight/first_code/last_code/cmap_offset
字体数据区 0x1000 依次为每个字体的 cmap 区 + 字形记录区

cmap 有两种形态,打包时自动判定(码点集合连续 → 连续表,否则稀疏表,xbf_to_fontlib_bin.py:722-727):

字形记录 = 6 字节属性头u8 adv_w/box_w/box_hs8 ofs_x/ofs_yu8 bytes_per_line,比 XBF 的 12 B 减半)+ 位图(可 RLE)。

版本闭环是这个格式的特色:每次打包生成随机 magic,同时写进 bin 头与 Font_Lib.hFONT_LIB_MAGIC 宏;设备启动时引擎校验二者一致,不一致(固件与字库脱节)则全部退回 fake 字库并可由 app_font_version_ok() 触发字库 OTA,保证字库可以独立于固件发布/升级而不会花屏

2.3 位图打包方式

2026-09 更新--bitstream(默认开)已支持字形内连续位流存储——打包进 bin 时去掉行尾补位,box_w 上报真实宽度,与 LVGL 原生打包方式一致;--bitstream False 退回下图左侧的行字节对齐布局。下图描述的是两种可选布局的差异。

XBF 中间格式每行按字节对齐(bytes_per_line = ceil(box_w × bpp / 8),保持 FontCvtST/emWin 兼容);打包为 Font_Lib.bin 时默认重打包为连续位流,LVGL 官方也是连续位流:

位图打包对比

历史行为说明:旧版(--bitstream False)设备端上报的 box_wbytes_per_line × 8 / bpp 反推,即对齐后的宽度——真实 13 列、2 bpp 会上报 16 列,右侧 3 列为补 0 空白列。位流模式下该问题不复存在(Font_Lib.hBITMAP_BIT_CONTINUOUS_ENABLE 宏由工具随 bin 写死,两者必须一致)。

2.4 RLE 压缩

--rle True(默认开)时对每个字形的位图单独做字节级 RLE(run_length_encoding.py),cmap 中的 size 记录压缩后长度,解码器约 30 行 C(RLE_glyph_bitmap_decode):

RLE 编码

2.5 基线与坐标系

2026-09 更新:基线坐标系已做成可选--baseline top|bottom)。top 为 FontCvtST 顶部基准(默认,兼容 emWin 生态);bottom 直接按 LVGL 底部基准存储(ofs_y = yMinbaseline 从行框底起算),LVGL 路径零换算。约定标记写在 XBF 头 0x0C 保留字段,打包器自动检测并生成 BASELINE_TOP_REFERENCED 宏,引擎按宏对齐换算;同一个 bin 内混用两种约定会在打包时报错。

top 模式下,存储侧的垂直量以行框顶部为原点向下计(baseline = ascentofs_y = ascent − yMax + 1);而 LVGL 以基线为原点向上计,引擎在 get_glyph_dsc 里做一次整数换算:

基线坐标系


3. 设备端引擎

取一个字形的完整路径如下(font_engine_code.py 生成的 ext_font_engine.c):

设备端取字流程

几个关键设计:

  1. 两级缓存

    • 字形描述缓存:全局 font_cache[100]EXT_FONT_DSC_CACHE_CNT,可改),按 (unicode, font_type) 线性扫描命中则 0 次 Flash 读——界面上反复出现的字符(数字、标点、固定文案)基本全命中;
    • 单条 cmap 缓存:每个字体记住上一次查过的 {code, offset, size}get_glyph_dsc 和紧随其后的 get_glyph_bitmap 查同一个字时第二次 cmap 查找免掉。
    • 位图本体没有缓存:每次绘制都要从 Flash 读一次位图(详见 §5)。
  2. 三种 cmap 查找

    • 连续(全字库/连续区间):直算偏移 + 读 Flash 6 B;
    • 稀疏 + hash(默认):XIP C 表开放寻址,平均接近 O(1);
    • 稀疏 + 二分:XIP 排序数组,O(log n)。
  3. fake 字库兜底:码点越界、cmap 查不到、size == 0(生成时就缺字)、bin 版本 magic 不匹配——统统落到编译进固件的 fake 字库显示占位字形,任何情况下不花屏

  4. 与存储解耦:引擎只通过 pfunc_get_dataoffset/buf/size)拉数据,SPI NOR、QSPI、eMMC、甚至文件系统都能接。

  5. LVGL / 裸机双接口LVGL_FONT_ENABLE=1public_font_t 就是 lv_font_text_font_create() 填好 get_glyph_dsc/get_glyph_bitmap/line_height/base_line 后可直接赋给 style;=0 时是等价的自定义结构体,裸机直接调两个回调。

  6. LVGL v8 / v9 双版本支持(2026-09 新增):生成代码通过 LVGL_VERSION_MAJOR 自动识别大版本。v8 下 get_glyph_bitmap 直接返回位流位图(v8 渲染器原生按 width_bit = box_w × bpp 位寻址);v9(按 v9.3 官方发布版契约)下走包装回调——从 g_dsc->gid.index 取码点、读 Flash 解码后,由共享的 ext_font_bitmap_to_a8_draw_buf() 展开为 A8(每像素 1 字节,按 draw_buf->header.stride 对齐)写入 LVGL 分配的 draw_buf 并返回,与官方 lv_font_fmt_txt 同一行为;glyph_dsc 按版本填 bpp(v8)或 format + gid(v9)。fake/内部/图标/emoji 四类 C 字库同样双版本适配。


4. 方案优点

按重要性排序:

  1. 内部 Flash / RAM 占用极小,CJK 全量字库可用。字库数据全部在外挂 SPI Flash;固件里只有引擎(~2 KB 代码)、稀疏 cmap C 表(仅裁剪字库需要)和 fake 字库。RAM 只需 1~2 个字形位图缓冲(MAX_STATIC_BITMAP_BUF_SIZE = 最大字号的 y_dist²·bpp/8,如 24px/2bpp 约 256 B;56px/2bpp 约 900 B)+ 100 条 dsc 缓存。这是 LVGL 官方两条路径(C 数组占内部 Flash、binfont 占 RAM)都做不到的。

  2. 字库独立升级的版本闭环。bin 与 C 头文件用随机 magic 配对,版本错落 fake 字库 + app_font_version_ok() 上报,OTA 时字库和固件可以分开发布、分开烧写,工程上非常实用。

  3. 多字体多字号一包。一个 Font_Lib.bin 打包 N 个字体×M 个字号(含图标字库、emoji 字库),信息表统一管理,应用层用枚举取字体;固件与字库的对应关系只有一处。

  4. 全套 C 代码自动生成,拿来即用。用户唯一要写的是一个 Flash 读函数;LVGL/裸机、hash/二分、RLE 开关、缓存条数都是 Font_Lib.h 里的宏。

  5. 缺字兜底。fake 字库保证任何异常路径都有占位显示,不会出现乱码/花屏——对量产设备是刚需。

  6. 按需裁剪,体积可控。码点配置支持"范围 + 界面文案文本"取并集,"只打包界面用到的几百个字"能把字库从几 MB 降到几十 KB,生成时间也降一个量级。

  7. 查表结构为外挂场景优化。连续区间直算 O(1);稀疏区间 hash 平均 O(1) 且表在 XIP 不占 RAM;dsc 缓存吸收热点字符的重复查询。

  8. RLE 逐字形压缩,随机访问不受影响,1 bpp 字库收益显著;解码器极简(无状态、无查找表)。

  9. 工具链友好:纯 Python 跨平台(服务器无头环境可跑)、Web 在线生成(上传 TTF 选参数下载 zip)、变形字体一个文件出全字重、XBF 兼容 emWin 生态、附带 iconfont/emoji/多语言 Excel 一条龙。

  10. emoji 图片字库:把 Noto Emoji PNG 转成同一 bin 里的一个"字体",用同一引擎渲染——LVGL 官方点阵字库路线没有对应能力(需另接 lv_imgfont)。


5. 方案缺点与限制

同样按影响面排序,均给出代码依据:

  1. 渲染吞吐受 SPI 总线限制,位图无缓存。每个字形每次绘制都要 1~2 次 Flash 读(dsc 未命中缓存时 6 B 属性 + 位图本体);滚动长文本、跑马灯时同一个字反复读取。dsc 缓存只缓存 6 B 描述,不缓存位图(__user_font_get_bitmap 每次都走 Flash)。对比 LVGL C 数组的零 IO,这是外挂方案的本质代价,但位图 LRU 缓存是明确的补强空间(见 §9)。

  2. 无 kerning(字距调整)(2026-09 已解决)。freetype 渲染器自动从 TTF 的 kern/GPOS 表提取字偶对(含 PairPos format1/2 与 Extension),按目标字号缩放为 1/16 px 定点写入 XIP kern 表;引擎在 get_glyph_dsc(left<<16)|right 二分查找修正 adv_w(与 lv_font_fmt_txt 同款舍入),dsc 缓存存基础值不受污染。

  3. advance 存储为整数像素(bin 中 u8,2026-09 部分改进)。freetype 渲染器按 26.6 定点四舍五入,逐字形精确;kern 修正按 1/16 px 定点计算后舍入——这已达到 LVGL 自定义字体回调接口(dsc->adv_w 为整数)能表达的精度上限。与官方剩余差距仅在无 kern 长串的亚像素累积(官方 fmt_txt 同样逐字形舍入输出,实际无差);u8 存储仍限制单字形 advance ≤255 px(72 px 字号内安全)。

  4. 码点仅支持 BMP。XBF 头与 cmap 都是 u16(ttf_conver_FontCvtST_xbf.py:43-44),增补平面(含大多数 emoji)必须走图片字库产线;bin 的稀疏 cmap 条目也是 u16 码点(emoji 字体单独用 u32 变体)。

  5. 部分字体度量硬编码xbf_to_fontlib_bin.py:1300-1331 里 16/20/22/24/40/48/56/72 号字的 y_size/y_dist/baseline 是一张为 HarmonyOS Sans 调好的经验值表;不在表内的字号会沿用上一个字体的值(潜在 bug),换其他字体最好核对/修改这张表。

  6. 强依赖文件命名约定。bpp 从文件名 ..._2Bpp 解析、字号取 name[-7:-5] 两位数字(xbf_to_fontlib_bin.py:1112-1113, 1295);文件名不合规会产出错误的元数据,且字号只支持两位数。

  7. hash 表装载因子为 1.0。生成时 hash 表大小 = 字符数(xbf_to_fontlib_bin.py:1436),满载的开放寻址线性探测在聚簇处会退化,未命中查找最坏遍历整表(先被 first/last 区间过滤挡掉大部分)。README 所说 O(1) 是平均意义上的。

  8. 位图行对齐带来的宽度虚报(2026-09 已解决)。--bitstream(默认开)改为字形内连续位流存储,box_w 上报真实宽度;仅 --bitstream False 的兼容模式仍是旧行为。实测 ASCII 测试字库(16px/2bpp + 24px/1bpp)位流化单独可省约 25% 位图体积(1bpp 收益最大;RLE 开启时行尾 0 本就可压缩,叠加后净收益缩小)。

  9. 静态缓冲非线程安全glyph_bitmap_buf 是全局静态,get_glyph_bitmap 返回其指针,多线程同时取字会互相踩;LVGL 单线程模型下没问题,裸机双核/RTOS 多任务需自行加锁。RLE 静态模式还要双倍缓冲。

  10. 生成侧渲染质量依赖 pygame(2026-09 已解决)。XBF 默认由 ttf_freetype_to_xbf.py 直连 FreeType:FT_Set_Pixel_Sizes 精确像素字号(不再 pt 扫描)、原生覆盖度灰度(不再亮度反推)、hinting 五档可选(1bpp 默认 mono 渲染更锐利)、生成速度快一个量级。fake/内部/图标字库生成器仍走 pygame(占位/图标字形对渲染精度不敏感),--renderer pygame 可整体退回旧链路。

  11. 生成的 LVGL 接口是 v8 签名(2026-09 已解决)。生成代码按 LVGL_VERSION_MAJOR 自动适配 v8 与 v9(v9.3 官方发布版契约),见 §7.4。

  12. 工程细节类:字体信息区 0x100~0x1000 上限约 60 个字体;cmap_*.c 稀疏表同时以二分数组 + hash 表两份形态生成(XIP 体积 ×2,好在仅裁剪字库有此表);bin 内稀疏 cmap 为预留未被引擎使用。


6. LVGL 官方点阵字库简介

LVGL 官方的静态点阵字库由 lv_font_conv(Node.js CLI,亦有官方在线转换器)生成,运行时格式是 lv_font_fmt_txt

lv_font_t
 └─ lv_font_fmt_txt_dsc_t
     ├─ glyph_bitmap[]           位流紧凑打包的位图池
     ├─ glyph_dsc[]              每字形 {bitmap_index:20, adv_w:12(8.4 定点), box_w/h, ofs_x/y}
     ├─ cmaps[]                  多段 cmap(FORMAT0_FULL/TINY、SPARSE_FULL/TINY 四种)
     ├─ kern_dsc                 kerning:pair(字偶对)或 class(字距类矩阵),1/16 px 精度
     ├─ bpp: 1/2/3/4             抗锯齿位深(--lcd/--lcd-v 另有 ×3 子像素)
     └─ bitmap_format            RAW 或 COMPRESSED(XOR 行预滤波 + 位级 RLE)

两条使用路径:

另有运行时矢量方案(Tiny TTF、FreeType 接口),不属于点阵字库,此处不展开。


7. 与 LVGL 官方字库逐维度对比

存储与访问路径对比

7.1 存储与资源占用

维度 Py_FontMaker 外挂字库 LVGL 官方 lv_font_conv
字库数据位置 外挂 SPI Flash(Font_Lib.bin 内部 Flash C 数组(XIP)或 binfont 载入 RAM
内部 Flash 占用 引擎 + 稀疏 cmap 表 + fake 字库(KB 级) 整个字库(CJK 24px/2bpp 全量 ≈ 3 MB+)
RAM 占用 位图缓冲(几百 B~1 KB)+ dsc 缓存(100×~12 B) C 数组 0;binfont ≈ 字库体积
渲染时 IO 每字形 1~2 次 SPI 读(dsc 缓存命中 0 次) 0
字库独立升级 ✅ bin 单独烧写/OTA,magic 版本闭环 C 数组随固件;binfont 可换文件但无版本机制
多字体打包 ✅ 一个 bin 装 N 字体 M 字号 + icon + emoji 每字体独立 .c/.bin

7.2 格式能力

维度 Py_FontMaker lv_font_conv
码点范围 BMP(u16);增补平面走 emoji 图片字库 全 Unicode(u32),支持多 range 与码点重映射
cmap 单区间;连续直算 / 稀疏 hash(平均 O(1))或二分 多段 cmap,段内二分;无 hash
bpp 1/2/4/8 1/2/3/4(另有 --lcd/--lcd-v 子像素 ×3)
advance 精度 整数像素(bin 内 u8) 12 bit 8.4 定点(1/16 px)
kerning ✅ kern/GPOS 提取,XIP 表二分查找,1/16 px ✅ pair / class 两种,1/16 px
位图打包 默认字形内连续位流(--bitstream False 可退回行字节对齐) 默认位流紧凑(可 --byte-align 切回)
压缩 逐字形字节级 RLE(随机访问友好) XOR 行预滤波 + 位级 RLE(--no-compress 可关)
缺字处理 fake 字库兜底 + 版本校验,永不花屏 lv_font_t.fallback 字体链(需自行配置)
彩色 emoji ✅ PNG → 图片字库,同一引擎渲染 ❌ 点阵路线不支持(需 lv_imgfont 另做)

7.3 工具链与生态

维度 Py_FontMaker lv_font_conv
实现语言 Python(pygame + fontTools) Node.js
使用形态 CLI / 本地 GUI / Web 在线生成(自部署) CLI / LVGL 官网在线转换器
变形字体 ✅ 轴固化(Web 端拖滑条) 需自行先固化
中间格式互通 XBF 兼容 FontCvtST / emWin 生态
字符集配置 范围段 + 界面文案文本取并集 -r 范围 / --symbols 字符列表
附加产线 iconfont、emoji、多语言 Excel、内部字库、fake 字库 仅字体转换(icon 可把 FontAwesome 当字体转)
渲染引擎 FreeType 直连(hinting 五档可选;--renderer pygame 退回旧链路) FreeType 系(--autohint-off/--autohint-strong

7.4 LVGL 版本兼容性

2026-09 更新:生成代码已同时支持 LVGL v8 与 v9 官方发布版,编译期通过 LVGL_VERSION_MAJOR 自动切换,无需手工适配。

两个大版本的回调契约不同:

/* v8:get_glyph_bitmap 返回位流位图指针, 渲染器按 width_bit = box_w*bpp 位寻址 */
const uint8_t * (*get_glyph_bitmap)(const lv_font_t *, uint32_t);

/* v9(按 v9.3 契约):由字体把字形展开为 A8(每像素 1 字节)写入 LVGL 分配的
 * draw_buf(行按 header.stride 对齐), 返回 draw_buf; 码点从 g_dsc->gid.index 取 */
const void * (*get_glyph_bitmap)(lv_font_glyph_dsc_t *, lv_draw_buf_t *);

引擎的实现方式:内部保留 v8 风格的裸回调(ext_font_get_bitmap_raw / fake_font_get_bitmap_raw,跨字库兜底委托统一走裸回调,任何版本下都可直接调用);v9 下再套一层包装回调,用共享的 ext_font_bitmap_to_a8_draw_buf() 完成 A8 展开——与官方 lv_font_fmt_txt 的行为一致。glyph_dsc 填充由 FONT_GLYPH_DSC_SET_FORMAT 宏按版本落到 bpp(v8/裸机)或 format + gid.index(v9)。fake/内部/图标/emoji 字库同样适配。v9 的 release_glyph 置 NULL(引擎从不设置 g_dsc->entry,LVGL 不会调用它)。

参考工程当前基于 RT-Thread + LVGL v8 验证(https://gitee.com/woowill/ZJ_RT_Thread_LVGL_Font_Nordic.git);v9 路径已在宿主机用 v9.3 结构桩做过运行时逐位验证(tests/test_host_c.py),上板验证欢迎反馈。

7.5 一图流总结

字符集小(拉丁/数百字) 字符集大(CJK 全量/多字号)
内部 Flash 充足 LVGL C 数组(官方,最优) LVGL C 数组(能塞下也可)
内部 Flash 紧张 / 字库需独立升级 两者皆可,看是否要 OTA Py_FontMaker 外挂字库(主场)

8. 选型建议

选 LVGL 官方 lv_font_conv,当:

选本方案(Py_FontMaker 外挂字库),当:

两者混用也常见:界面骨架用 LVGL 内置 Montserrat(C 数组),中文正文/大字号用本方案外挂字库——lv_font_t 层面完全等价,可在同一个 label 样式体系里共存。


9. 可优化方向

结合 §5 的缺点,按投入产出排序:

  1. 字形位图 LRU 缓存:在 dsc 缓存旁加 N 条位图缓存(内存允许时按字号分池),滚动场景可把 SPI 读次数降一个数量级——收益最大的单点优化。
  2. hash 表预留空槽:生成时把表大小设为字符数 × 1.25~1.5,消除满载线性探测的退化,代价是 XIP 体积略增。
  3. 度量表去硬编码y_size/y_dist/baseline 直接取 XBF 头的实测值(头里本来就有),删掉按字号查的经验值表,同时消除"字号不在表内沿用旧值"的隐患。
  4. LVGL v9 适配层:✅ 已完成(2026-09),见 §7.4。
  5. kerning 支持:✅ 已完成(2026-09),kern 表生成在 XIP C 文件(自动按打包字符集过滤),引擎二分查找修正。
  6. adv_w 升级为 8.4 定点 u16:属性头从 6 B 变 7~8 B,换取西文排版精度。
  7. 码点扩展到 u32:打通增补平面文字(非 emoji 的生僻字/少数民族文字场景)。
  8. box_w 如实上报:✅ 已完成(2026-09),随 --bitstream 位流存储一并实现,见 §2.3。

本文档由工具代码分析生成,与当前实现保持一致;若代码更新导致描述失配,以代码为准。

微信公众号
微信公众号
QQ群 BLE5.4 学习讨论 177341833
QQ群 177341833
QQ群 BLE 开发学习 498676838
QQ群 498676838