嵌入式点阵字库:从零到跑通
免费使用Web 免安装LVGL v8/v9 + 裸机18 种变形语言
1三分钟看懂点阵字库
电脑和手机上的字体文件(TTF/OTF)存的是矢量轮廓——每个字是一组贝塞尔曲线, 显示时由 FreeType 这样的引擎实时"画"出来。这需要相当的 CPU 和内存,小型嵌入式设备(手表、仪表、 家电面板)跑不动,也没必要跑。
嵌入式的做法是预先把每个字"画"成小图片——点阵位图,存进 Flash; 显示时按编号查出来贴到屏幕上即可,查表 + 拷贝,任何单片机都能轻松胜任。这个"把字体变成点阵图集合"的 过程就是字库生成,Py_FontMaker 干的就是这件事:
两个马上会遇到的概念
| 概念 | 一句话解释 | 怎么选 |
|---|---|---|
| bpp(灰度位深) | 每个像素用几个 bit 表示。1bpp 非黑即白有锯齿;2/4bpp 有灰度过渡,边缘平滑(抗锯齿);8bpp 最细腻 | 彩屏选 2bpp(大小与效果的最佳平衡);墨水屏/单色屏选 1bpp;追求极致选 4bpp |
| 字号(像素) | 字的高度,直接按屏幕像素理解,如 24 就是 24 像素高的字 | 看你的屏幕分辨率与 UI 设计稿;常用 16/24/32 |
这套方案带来的效果
| 指标 | 效果 |
|---|---|
| 内部 Flash 占用 | 仅引擎(~2 KB) + 稀疏 cmap 表 + 兜底字库,字库本体 0 占用(放外挂 Flash) |
| RAM 占用 | 1~2 个字形缓冲(几百字节) + 100 条描述缓存,与字库大小无关 |
| 字库升级 | bin 与固件用随机版本号配对,字库可独立 OTA;版本不匹配自动落兜底字库不花屏 |
| 接口 | LVGL v8 / v9 官方版自动适配,也提供裸机回调;一个宏切换 |
2五分钟跑通第一个字库
不需要安装任何软件,打开 pyfontmaker.com 即可。
正式生成
- 选生成模式:保持默认的「外挂字库」(pyf_lib.bin + 全套 C 代码)。 每个模式下方有"做什么/什么场景选它"的说明,拿不准就用默认。
- 选字体:两种方式任选——上传你自己的 TTF,或直接勾选服务器内置的免费字体 (25 个 SIL OFL 授权字体,可商用可分发,一个文件都不用传)。 例如做中文产品:勾「Noto Sans CJK SC(简体中文全量)」。
- 勾字符集:按语系勾选即可——「基本拉丁(ASCII)」默认已勾,做中文产品再勾 「中文(简/繁通用)」;也可以把界面会显示的文字直接粘贴进"需要生成的文本"框—— 只生成用到的字,库最小。语系之外的特殊区间才需要用"自定义码点范围"手填。
- 字号列表填
16,24(要几个字号写几个),其余参数保持默认 (每个参数右侧都有说明)。点开始生成。 - 看结果:完成后页面直接显示两张预览图——「字形预览」(字库数据直绘)和
「LVGL 实机渲染」;可改预览文本实时刷新,也可用"查字符"确认某个字在不在库里。
浏览器同时自动下载
result.zip。
模式选型与功能一览
| 模式 | 产物 | 什么场景选它 |
|---|---|---|
| 外挂字库 | pyf_lib.bin + 全套引擎 C 代码 | 字符多(中文/多语言)、固件放不下字库——绝大多数产品的主力方案,烧外挂 SPI Flash |
| 内部字库 | pyf_inner_*.c(编译进固件) | 字符集很小(几十~几百字)、不想接外挂 Flash 的小项目 |
| XBF 字库 | .xbf + LVGL 官方 pyf_lvgl_*.c / pyf_lvgl_*.bin | emWin 工程;纯 LVGL 想要零集成官方格式(LV_FONT_DECLARE / lv_binfont_create);自行二次加工 |
| 图标字库 | pyf_icon_*.c + 图标宏定义 | iconfont.cn 图标当"字"渲染,与文本共用引擎,不用切图 |
拿不准就选「外挂字库」。另有两个命令行专属功能: emoji 图片字库(--EMOJI,Noto Emoji PNG 转灰度字库,绕开点阵码点只到 BMP 的限制)与 多语言 Excel 转 .c(--LANG,翻译表一键生成 pyf_language.c/h,依赖本机 Excel)。
预览与查字符(所见即设备所得)
- 生成结果预览:每个字库两张图——「字形预览」用 XBF 数据直绘(bpp 量化/整形簇与设备一致), 「LVGL 实机渲染」经官方 binfont + LVGL 排版(含 RTL 按行 bidi);预览文本改什么渲染什么,预览图同时打进 zip;
- 查字符:输入单个字符或码点(如
中或0xE010),立即返回是否在字库、 字形度量、参与了哪些预整形簇——"某个字显示不出来"先来这里查。
3生成了什么:产物逐个认识
解开 result.zip,每个文件的职责:
| 文件 | 它是什么 / 拿来干嘛 |
|---|---|
pyf_lib.bin | 字库数据本体,烧写到外挂 SPI Flash(或任何能提供"按偏移读"的存储) |
pyf_lib.h | 字库配置总头文件:LVGL 开关、hash/RLE/缓存等编译期开关都在这 |
pyf_engine.c/h | 字库引擎(自动生成):查表、读 Flash、RLE 解压、缓存、版本校验、缺字兜底 |
pyf_app.c/h | 应用层模板:你只需要在这里实现一个"读 Flash"函数(第 4 章) |
pyf_cmap_*.c | 各字库的码点索引表(XIP 常量,放固件里,查字 O(1)) |
pyf_fake_font_*.c | 兜底字库(编译进固件):字库损坏/缺字时显示占位框,不至于黑屏 |
pyf_shaper.c/h | 变形文字整形器(勾了语言包才有,第 6 章) |
pyf_lvgl_*.c / pyf_lvgl_*.bin | LVGL 官方格式字库(XBF 模式产物,第 8 章):纯 LVGL 用户零集成 |
preview_*.png | 生成时的渲染预览图,拿给 UI 设计师确认效果 |
xbf/*.xbf | 中间格式点阵文件(兼容 emWin 的 FontCvtST),可独立使用或二次加工 |
4集成到你的工程(四步)
- 烧字库:把
pyf_lib.bin烧进外挂 Flash(任意起始地址,记住它)。 - 加代码:把 zip 里所有
.c/.h加进工程编译。 - 实现读接口:在你自己的代码里写一个 Flash 读函数(生成的
.c/.h一行都不用改):
引擎与具体 Flash 型号完全解耦——SPI NOR、SD 卡、甚至文件系统都行。static uint32_t my_flash_read(uint32_t offset, uint8_t *pbuf, uint32_t size) { /* 从 pyf_lib.bin 烧写起始地址 + offset 处读 size 字节到 pbuf, 返回实际读到的字节数(成功即 size,失败返回 0) */ bsp_flash_read(pbuf, size, PYF_LIB_START_ADDR + offset); return size; } - 用起来:启动时把读函数注册给字库引擎:
pyf_font_init(my_flash_read, printf)(第二个参数是可选的日志输出回调,printf 风格,传NULL则引擎静默),之后——
LVGL(v8/v9 自动适配):字库就是标准lv_font_t:
裸机:引擎暴露"取字形描述 + 取位图"两个回调,按位图逐像素画即可 (lv_obj_set_style_text_font(label, &SourceHanCJK_24_2Bpp, 0); lv_label_set_text(label, "你好,世界");pyf_lib.h里LVGL_FONT_ENABLE设 0)。
5参数详解:每一项的含义与效果
页面上每个参数旁都有简要说明;这里给出完整含义、效果与选择建议。数据格式层面的图解见下篇。
灰度 bpp(1 / 2 / 4 / 8)
| bpp | 灰度级 | 相对体积 | 适用 |
|---|---|---|---|
| 1 | 2(黑白) | 1× | 单色屏 / 极致省空间;配合 mono hinting 边缘锐利 |
| 2 | 4 | 2× | 彩屏正文的性价比之选,抗锯齿已明显 |
| 4 | 16 | 4× | 大字号标题、对边缘平滑要求高 |
| 8 | 256 | 8× | 图标/emoji 字库,保留完整灰度 |
字号列表 / 字高(像素)
外挂字库一次可打包多个字号(如 16,24,32),其余模式填单一字高。
渲染器直接按像素设定字号(FreeType FT_Set_Pixel_Sizes),所得行高、基线为字体真实度量。
字符范围 + 文本(取并集)
范围段每行一对十六进制码点(如中文常用区 0x4e00, 0x9fa5);文本框里直接粘贴界面会出现的文字,两者取并集。
效果:只打包界面用到的几百个字,能把字库从几 MB 降到几十 KB,生成时间同步降一个量级。
码点连续时自动用"连续 cmap"(设备端 O(1) 直算),不连续时自动用"稀疏 cmap"(hash / 二分查表)。
LVGL 接口(True / False)
True:生成的 public_font_t 就是 lv_font_t,可直接赋给 style;
编译期通过 LVGL_VERSION_MAJOR 自动适配 v8 与 v9 官方版
(v9 按 v9.3 契约把字形展开为 A8 写入 draw_buf)。False:走裸机回调
get_glyph_dsc() + get_glyph_bitmap()。
hash 查表(True / False)
稀疏字库的 cmap 查找方式。True:开放寻址 hash 表,平均接近 O(1);False:排序数组二分,O(log n)。 两套表都生成在 C 文件里(XIP,不占 RAM),宏一键切换。
RLE 压缩(True / False)
效果:逐字形独立压缩,随机访问不受影响;1 bpp 字库(大片空白)收益最大,bpp 越高收益递减。 代价是解压需要双份字形缓冲(几百字节)。编码规则图解见 §11.5。此开关随 bin 写死到 pyf_lib.h,两者必须一致,不可手改。
字形缓存(True / False)
设备端缓存最近 100 条字形描述(可在 pyf_lib.h 改条数)。界面上反复出现的字符命中后 0 次 Flash 读,滚动列表明显更顺。
位流存储(True / False,默认 True)
True:打包时去掉每行末尾的补位,字形内按位连续,box_w 上报真实宽度——与 LVGL 官方打包方式一致。
效果:位图体积再省 5%~25%(1 bpp 收益最大)。False:退回行字节对齐布局(裸机代码不想处理位偏移时用)。两种布局图解见 §11.4。
基线坐标系(top / bottom)
| 选项 | 存储约定 | 适用 |
|---|---|---|
top(默认) | FontCvtST 顶部基准,引擎运行时换算给 LVGL | 要兼容 emWin/FontCvtST 生态、沿用既有工程 |
bottom | LVGL 原生(ofs_y=yMin、基线从底起算),LVGL 路径零换算 | 纯 LVGL 新项目 |
约定标记写在 XBF 头部,打包器自动生成 BASELINE_TOP_REFERENCED 宏;
同一个 bin 内混用两种约定会在打包时直接报错。坐标系图解见 §11.6。
hinting(auto / normal / light / mono / none / autohint)
| 选项 | 效果 |
|---|---|
auto(默认) | 1 bpp 自动用 mono,其余用 normal |
normal | FreeType 默认 hinting,笔画对齐像素网格,小字号更清晰 |
light | 轻 hinting,字形更忠实原设计,边缘更柔和 |
mono | 纯黑白渲染(仅 1 bpp),无灰度过渡,单色屏最锐利 |
none | 关闭 hinting,完全按矢量轮廓采样 |
autohint | 强制 FreeType 自动 hinter,字体自带 hint 质量差时可尝试 |
变形字体(Variable Font)轴参数
上传含 fvar 表的 VF 字体(如内置思源黑体,wght 250~900)后页面自动出现轴滑条,
服务器用 fontTools instancer 固化成静态实例再进管线。效果:一个 VF 文件产出任意字重的点阵字库。
kerning(自动,无需开关)
渲染时自动从 TTF 的 kern/GPOS 表提取字偶距(如 AV、To 的间距修正),
按目标字号缩放为 1/16 px 精度,随字库生成 XIP 查找表;设备端按"当前字符+下一字符"二分查找修正 advance。
效果:西文排版质量对齐 lv_font_conv;中文方块字无字偶距、零开销;字体没有 kerning 数据时表为空。
命令行打包可用 --kern False 关闭 XIP kern 表——全字库场景该表可达数百 KB 内部 Flash,空间紧张时建议关闭。
6变形文字:阿拉伯语、印地语、泰语……
有一大类语言,两个字符组合后字形会变——简单查表渲染必然出错:
6.1 背景:哪些语言需要变形,为什么
需要"组合变形"(专业上叫 text shaping / 复杂文本布局)的语言覆盖 40+ 个国家,集中在四大类文字系统:
- ① 阿拉伯字母系——连写变形最典型:每个字母有 独立/词首/词中/词尾 最多 4 种形态,相邻字母决定用哪种, 还有强制连字(如 ل+ا = لا)。语言:阿拉伯语(20 多国)、波斯语(伊朗)、达里语/普什图语(阿富汗)、 乌尔都语(巴基斯坦/印度,传统用 Nastaliq 体,本工具按 Naskh 风格支持)、库尔德语、维吾尔语/老哈萨克文/柯尔克孜语(中国新疆)、 信德语/俾路支语。同类还有叙利亚文和西非 N'Ko 文(提供字形直通,暂无整形语言包)。
- ② 蒙古文系——竖排 + 连写变形:传统蒙古文(内蒙古/蒙古国)、满文/锡伯文。 本工具生成横排位置形,竖排由应用层把 label 旋转 90°。
- ③ 印度婆罗米系——辅音簇合体 + 元音重排:两个辅音相拼生成合体字(如天城文 क+्+ष → क्ष), 有的元音显示时要跳到辅音前面。文字:天城文(印地语/马拉地语/尼泊尔语)、孟加拉文、古木基文、古吉拉特文、 奥里亚文、泰米尔文、泰卢固文、卡纳达文、马拉雅拉姆文、僧伽罗文(斯里兰卡)、藏文(纵向叠字)。
- ④ 东南亚系:高棉文(第二辅音下标 + 元音重排)、缅甸文(介音变形)、泰文/老挝文(声调与上下元音动态堆叠)、 爪哇文/巴厘文/占文(合体辅音)。
特殊情况:韩文谚文的音节已被 Unicode 全部预组合成独立码点,按普通字符处理即可; 希伯来文只是从右向左,字母不连写变形,不算变形文字;拉丁、西里尔、希腊、CJK、假名等都不需要变形。
6.2 三组语言包:机制与动态文本支持
| 组 | 语言 | 机制 | 动态文本 |
|---|---|---|---|
| A 组 | 希伯来语、泰语、老挝语 | 字形不变形,直接支持(RTL/组合符号零 advance + 位偏移叠画) | ✅ 完整支持 |
| B 组 | 阿拉伯语/波斯语/乌尔都语/维吾尔语等 | Unicode 预变形区(U+FB50–FDFF / FE70–FEFF)+ 设备端小查表选形(pyf_shaper.c,约 3KB); 字体缺预变形字形时 PC 端用 HarfBuzz 合成入库 | ✅ 完整支持 |
| C 组 | 天城文等印度系 9 文字、僧伽罗、藏、缅甸、高棉、蒙古 | 合体组合爆炸无取巧办法——把 HarfBuzz 放在 PC 端:按公开语料的高频字位簇整簇预渲染, 映射到 PUA 私用区码点,设备端最长匹配查表(引擎零改动) | ✅ 字典命中即正确(96~99%+);未命中生僻簇降级逐码点显示,不乱码 |
字体 vs 语言包的分工:字体决定"字形从哪来"(原材料),语言包决定"如何整形"(加工工艺)。 只勾字体不勾语言 → 合体/连写散开;只勾语言没有对应字形 → 方框。勾选语言时 Web 会自动勾上对应内置字体。
多个 C 组语言合打一个字库时,簇字典共享 6400 个 PUA 码位(物理上限), 工具会按覆盖率自动削减并打印明细;单语言生成可达 99.9% 覆盖。 完整的方案论证(语言分级/体积估算/风险对策)见变形文字方案技术评审。
6.3 在本工具里怎么用
支持它们只需要两个勾选:
- 在「变形语言支持」勾选目标语言(如 天城文/阿拉伯字母系)—— 对应的内置字体会自动勾上;
- 正常生成。完成后在预览框粘一段该语言文字,亲眼确认连写/合体正确。
设备端唯一的额外动作:送显前把字符串过一次整形函数(生成包里的
pyf_shaper.c,约 3KB 查表代码):
char shaped[256];
pyf_font_shape_utf8("مرحبا بالعالم", shaped, sizeof(shaped), FONT_SHAPE_FLAG_BIDI_VISUAL);
lv_label_set_text(label, shaped); /* LVGL 开了 LV_USE_BIDI 时 flags 传 0 */
7多字体组合(fallback 链)
没有任何一个 TTF 能覆盖所有文字(Noto 全家桶都是按文字系统分成上百个文件的)。 要做多语言产品,把多个字体组成一条链:
- 上传时按住 Cmd/Ctrl 一次选择多个字体,或直接勾选多个内置字体;
- 第一个是主字体:决定行高、基线、kerning、字库名和变形字体轴参数(上传框下方会列出链顺序);
- 普通文字(拉丁/中文/泰文等)缺的字,按顺序由后续字体补齐——最终合成单一字库对象,
一个
lv_font_t混排中文+印地语+阿拉伯语; - 变形语言(印度系/高棉/缅甸/藏文/僧伽罗等)不由顺序决定:生成时对链中每个覆盖该文字的 字体做标记定位健康检查(抽样几十个音节,看下标/元音是否落在基字外、叠加符号是否互相压住), 自动取第一个健康的字体来渲染该文字的全部字形(基字与合体簇同源);
- 结果区每个字库下方会显示
主字体(行高/基线): X | 高棉文: Y,看一眼就知道各语言实际用的是谁。
0_main.ttf)。看到"标记定位异常, 已跳过":说明那份 TTF 是改制/重打包版本(下标轮廓被挪动而 GPOS 锚点没跟着改, 任何整形引擎都会画错,不是本工具的问题),已自动改用链中下一个健康的字体;若提示"链中无其它可用字体", 勾一个对应文字的内置 Noto Sans 字体或改用官方原版即可。
空格:XBF 模式只生成勾选范围与预览文本里的字符,空格总会自动带上(LVGL 缺空格字形会画占位方框)。
内置免费字体清单(全部 SIL OFL,可商用/嵌入/分发)
Web 上传框下方可直接勾选这些服务器内置字体;勾选"变形语言"时自动勾上对应文字的字体。 OFL 授权唯一要求:随产品保留字体版权声明,不得单独出售字体文件本身。
| 字体 | 覆盖文字 | 官方页面 |
|---|---|---|
| 思源黑体 KR(SourceHanSansKR-VF) | 拉丁/韩文/假名/繁体汉字(变形字体,可调字重) | adobe-fonts/source-han-sans |
| Noto Sans CJK SC | 简体中文全量(含繁体/日韩汉字) | notofonts/noto-cjk |
| Noto Sans Devanagari / Bengali / Gurmukhi / Gujarati / Oriya / Tamil / Telugu / Kannada / Malayalam / Sinhala | 印度系 9 文字 + 僧伽罗文 | Google Fonts · Noto |
| Noto Serif Tibetan / Sans Myanmar / Sans Khmer / Sans Lao / Sans Thai | 藏文 / 缅甸文 / 高棉文 / 老挝文 / 泰文 | Google Fonts · Noto |
| Noto Naskh Arabic | 阿拉伯文(含波斯/乌尔都字母) | Google Fonts |
| Noto Sans Hebrew / Mongolian | 希伯来文 / 传统蒙古文·满文·锡伯文 | Google Fonts · Noto |
| Noto Sans Syriac / NKo / Javanese / Balinese / Cham | 叙利亚文 / NKo / 爪哇文 / 巴厘文 / 占文 | notofonts.github.io |
授权提醒:操作系统自带字体(macOS 冬青黑/GeezaPro/各 MN 系列、 Windows 微软雅黑等)多为商业授权,转成点阵字库分发给第三方通常超出授权范围——分发场景请使用本清单的 OFL 字体。
8进阶能力
8.1 LVGL 官方格式(零集成)
只想要标准 LVGL 字库、不想集成引擎?用「XBF 字库」模式,产物自动附带:
pyf_lvgl_*.c(LV_FONT_DECLARE 即用)与 pyf_lvgl_*.bin
(lv_binfont_create() 运行时从文件系统动态加载,可 OTA 换字库)。
官方格式同样携带变形文字簇——纯 LVGL 也能正确显示印地语。
注意位图超 1MB 的大字库需在 lv_conf.h 开 LV_FONT_FMT_TXT_LARGE。
8.2 BDF 导出:预览与逐字修饰
参数区勾「BDF 导出」,可用 BdfEditor/FontForge 打开预览、逐像素修饰个别字符 (小字号笔画粘连的手工优化),再用命令行回填:
python pyf_xbf_to_bdf.py --xbf xxx.xbf --patch edited.bdf --out-xbf new.xbf # 1bpp 无损
8.3 命令行与批量化
Web 的每个能力都有对应的命令行版本(CI/批量生成场景)。源码暂未公开, 如需命令行/批量集成请通过页尾的公众号或 QQ 群联系作者;参数形式如下,供了解能力边界:
# 环境(Python ≥ 3.9)
python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt
# 多字体链 + 变形语言, 生成 XBF(自动附带 LVGL 官方格式)
python Py_FontMaker.py --XBF True --min 0x20 --max 0xfeff \
--ttf main.ttf,NotoSansDevanagari.ttf,NotoNaskhArabic.ttf \
--langs devanagari,arabic --bpp 2 --height 24
# 打包外挂字库(先按字号生成 XBF, 再打包)
python Py_FontMaker.py --LIB True --lib_file ./font_type/my_lib.txt --ttf main.ttf --bpp 2 --height 24
全部参数详解见第 5 章;需要"全语言通用字库"(25 字体链、覆盖全部内置语言) 可直接联系作者获取成品 bin,不必自己生成。
与 LVGL 官方字库如何选型,见下篇第 14 章逐维度对比。
9排错手册与常见问题
| 现象 | 原因与解法 |
|---|---|
| 某些字显示空心方框 | 该字不在字库里:①字符范围/文本没包含它——补上重新生成; ②所选字体本身没有这个字形(如韩版思源缺大量简体字)——fallback 链里加一个覆盖它的字体。 用结果区的「查字符」可立即定位属于哪种。 |
| 印地语/高棉语个别组合散开显示 | 生僻字位簇不在语料字典(降级显示,不乱码)。把含它的文案粘进"补充文案"框重新生成即 100% 命中。 |
| 阿拉伯语显示成孤立字母或方向反了 | 送显前没过整形:调 pyf_font_shape_utf8();多行文本建议 LVGL 开
LV_USE_BIDI(此时 flags 传 0,别做二次 bidi)。 |
| 设备上全是兜底方框,PC 预览正常 | 固件与 bin 版本不配套(引擎有 magic 校验)——两者都用同一次生成的产物重新烧写。 |
| 换了 RLE/位流开关后乱码 | 这两个开关随 bin 写死,不能只改 pyf_lib.h——重新生成一套即可。 |
| 生成的字库太大 | ①只放用到的文本,别全量 0x4e00-0x9fa5;②开 RLE + 位流;③降 bpp(4→2); ④多语言时按目标市场拆分生成。 |
| 全量 CJK 生成很慢? | 0x4e00~0x9fa5 逐字栅格化需要一些时间属正常;只打包界面用到的文本能把时间和体积都降一个量级。 |
| 生成的 .c 里字体名不是合法 C 标识符? | TTF 文件名只用字母/数字/下划线;Web 端会自动清洗。 |
| emoji 怎么显示?码点超过 0xFFFF? | 点阵字库码点上限为 BMP(u16);增补平面的 emoji 用图片字库方案(命令行 --EMOJI)。 |
| 裸机下开了位流存储,位图怎么解析? | 字形内按位连续:第 y 行起始 bit = y×box_w×bpp,逐行取 box_w×bpp 位。不想处理位偏移就把「位流存储」设为 False。 |
| top 和 bottom 基线能混用吗? | 同一个 bin 内不能(打包会报错);不同工程/不同 bin 各选各的没问题。 |
| LVGL v9 需要额外适配吗? | 不需要。生成代码按 LVGL_VERSION_MAJOR 自动切换 v8/v9 回调契约(按 v9.3 官方发布版验证)。 |
| 多选字体时怎么确定谁是主字体? | 上传框下方会列出链顺序,第一个即主字体(行高/基线/kerning/字库名);浏览器多选一般按文件名排序,必要时给主字体加个排前面的文件名前缀。 变形语言的字形来源另按"标记定位健康检查"自动选,结果区会显示各语言实际用的字体。 |
| 结果区提示某字体"标记定位异常, 已跳过"是什么意思? | 那份 TTF 的 GPOS 锚点与字形轮廓不一致(常见于网上重打包的字体),下标/元音会画到基字外或互相压住,任何整形引擎都一样。 工具已自动改用链中下一个健康的字体;建议改用内置 Noto Sans 系列或官方原版。 |
| 结果 zip 能保留多久? | 服务器保留 2 小时,请及时下载;任务串行排队执行。 |
*.py / tests/* 等源码文件为工具内部实现的落点记录,源码暂未公开;不影响阅读方案本身。下篇面向使用/评估本方案的嵌入式开发者,回答三个问题:
- 当前点阵字库方案是什么(数据格式、生成管线、设备端引擎);
- 它的优点和缺点分别是什么(逐条给依据,含代码位置);
- 它和 LVGL 官方点阵字库(lv_font_conv 产物)的差异在哪里(逐维度对比 + 选型建议)。
文中所有结论均以本仓库代码为准(
pyf_ttf_to_fontcvtst_xbf.py、pyf_xbf_to_fontlib_bin.py、pyf_engine_code.py等);LVGL 侧以 v8.3 的lv_font_fmt_txt与 lv_font_conv 官方文档为参照,v9 的接口差异在 §14.5 单独说明。
10方案总览
一句话概括:PC 端把 TTF 逐字栅格化成灰度点阵,打包成一个
pyf_lib.bin 烧进外挂 SPI Flash;设备端用自动生成的 C
引擎按需逐字形读取、解压、渲染,向上以
lv_font_t(LVGL)或裸机回调两种接口暴露。
(总体架构图见第 1 章。)
整条链路分两段:
PC 端工具链(Python,跨平台,另有 Web 在线生成与本地 GUI):
| 阶段 | 脚本 | 输入 → 输出 |
|---|---|---|
| ① 栅格化 | pyf_ttf_freetype_to_xbf.py(默认,FreeType
直连;--renderer pygame 退回
pyf_ttf_to_fontcvtst_xbf.py) |
TTF → .xbf + .kern.json(每字号/每 bpp
一组) |
| ② 打包 | pyf_xbf_to_fontlib_bin.py |
多个 .xbf + 码点配置 → pyf_lib.bin + 全套
C 代码 |
| 配套 | pyf_ttf_to_fake_font_c.py / pyf_ttf_icon_to_c.py /
pyf_emoji_to_c.py / pyf_ttf_to_inner_font_c.py /
pyf_excel_multi_language_to_c.py |
fake 兜底字库 / 图标字库 / emoji 图片字库 / 内部字库 / 多语言表 |
变形字体(Variable Font)先用 fontTools instancer
按轴(如 wght 字重)固化成静态实例再进管线,一个 VF
文件可产出任意字重的点阵字库。
设备端(自动生成的 C 代码,见 pyf_engine_code.py 模板):各文件职责见第 3 章产物表,引擎内部机制见第 12 章。
11数据格式详解
11.1 字符集与 Unicode:字是怎么编号的
计算机里每个字符都有一个全球统一的编号——Unicode 码点,写作 U+XXXX。
比如 A 是 U+0041、中 是 U+4E2D。常用汉字集中在
U+4E00~U+9FA5(约 2 万个)。文件与网络传输时,码点按 UTF-8 规则变成 1~4 个字节存储;
你在网页上填的"字符范围"就是码点区间,粘贴的文本会被自动转回码点。
点阵字库本质上就是一张「码点 → 位图」的大表。
11.2 XBF 中间格式
XBF 是①阶段的产物,格式兼容 FontCvtST(ST 提供的 emWin 字体转换工具),因此也可以脱离本方案单独给 emWin/裸机工程使用。本工具直接生成 XBF,不依赖任何第三方 exe。
要点(对应 pyf_ttf_to_fontcvtst_xbf.py:34-157):
- 18 字节头:名称(4B) +
YSize/YDist/BaseLine(各 u16) +bpp(u8) +px(u8) + 保留(u16) +FirstCode/LastCode(各 u16),小端; - 密集 cmap:
[FirstCode, LastCode]区间内每个码点一条{u32 offset, u16 size}(6 B),缺字条目全 0。按码点直接下标寻址,O(1); - 字形记录 = 12
字节属性(
adv_w/box_w/box_h/ofs_x/ofs_y/bytes_per_line,各 u16/s16)+ 行对齐灰度位图; - 码点为 u16,只覆盖 BMP(≤ 0xFFFF);增补平面的 emoji 走图片字库产线;
- 栅格化默认由 freetype-py 直连
FreeType:精确像素字号、原生覆盖度灰度量化到目标
bpp(
gray >> (8 - bpp),MSB 在前),1bpp 默认 mono 渲染;头部 0x0C 保留字段标记坐标约定(0=legacy,1=freetype+top,2=freetype+bottom),并输出.kern.jsonkerning sidecar。
11.3 pyf_lib.bin 外挂字库格式
②阶段把若干 XBF
按各自的码点配置文件(font_type/*.txt,"范围段 +
文本段取并集")裁剪后打进一个 bin。
要点(对应
pyf_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 有两种形态,打包时自动判定(码点集合连续 →
连续表,否则稀疏表,pyf_xbf_to_fontlib_bin.py:722-727):
- 连续表:6 B/字
{u32 offset, u16 size},设备端按(code - first_code)直算偏移后读 Flash; - 稀疏表:bin 内 8 B/字
{u16 code, u32 offset, u16 size};同时生成 XIP 的 C 常量表(pyf_cmap_*.c),运行时实际查的是 C 表(排序数组走二分,或 hash 表走开放寻址线性探测,CMAP_HASH_ENABLE切换),bin 内的稀疏表当前为预留。
字形记录 = 6
字节属性头(u8 adv_w/box_w/box_h、s8 ofs_x/ofs_y、u8 bytes_per_line,比
XBF 的 12 B 减半)+ 位图(可 RLE)。
版本闭环是这个格式的特色:每次打包生成随机 magic,同时写进 bin 头与
pyf_lib.h 的 PYF_LIB_MAGIC
宏;设备启动时引擎校验二者一致,不一致(固件与字库脱节)则全部退回 fake
字库并可由 pyf_font_version_ok() 触发字库
OTA,保证字库可以独立于固件发布/升级而不会花屏。
11.4 位图打包方式
2026-09 更新:
--bitstream(默认开)已支持字形内连续位流存储——打包进 bin 时去掉行尾补位,box_w上报真实宽度,与 LVGL 原生打包方式一致;--bitstream False退回下图左侧的行字节对齐布局。下图描述的是两种可选布局的差异。
XBF
中间格式每行按字节对齐(bytes_per_line = ceil(box_w × bpp / 8),保持
FontCvtST/emWin 兼容);打包为 pyf_lib.bin
时默认重打包为连续位流,LVGL 官方也是连续位流:
历史行为说明:旧版(--bitstream False)设备端上报的
box_w 按 bytes_per_line × 8 / bpp
反推,即对齐后的宽度——真实 13 列、2 bpp 会上报 16
列,右侧 3 列为补 0
空白列。位流模式下该问题不复存在(pyf_lib.h 的
BITMAP_BIT_CONTINUOUS_ENABLE 宏由工具随 bin
写死,两者必须一致)。
11.5 RLE 压缩
--rle True(默认开)时对每个字形的位图单独做字节级
RLE(pyf_run_length_encoding.py),cmap 中的 size
记录压缩后长度,解码器约 30 行
C(pyf_rle_glyph_bitmap_decode):
- 连续 ≥3 个相同字节 →
[0x80|count][value];不重复串 →[count][字面量…];count ≤ 0x7F; - 逐字形独立压缩意味着随机访问不受影响(无需从头解压整个字库);
- 收益与 bpp 负相关:1 bpp(大片空白/整行重复)收益最大,4/8 bpp 抗锯齿灰度丰富时收益递减,最坏情况反而多出控制字节;
- 代价:静态缓冲模式需要双份位图缓冲(压缩流 +
解压结果,
pyf_glyph_bitmap_buf[2][MAX_STATIC_BITMAP_BUF_SIZE]),或改用 malloc 模式(--rlemalloc); - RLE 开关必须与生成 bin
时一致(
pyf_lib.h中BITMAP_RLE_ENCODE_ENABLE由工具写死,不可手改)。
11.6 基线与坐标系
2026-09 更新:基线坐标系已做成可选(
--baseline top|bottom)。top 为 FontCvtST 顶部基准(默认,兼容 emWin 生态);bottom 直接按 LVGL 底部基准存储(ofs_y = yMin、baseline从行框底起算),LVGL 路径零换算。约定标记写在 XBF 头 0x0C 保留字段,打包器自动检测并生成BASELINE_TOP_REFERENCED宏,引擎按宏对齐换算;同一个 bin 内混用两种约定会在打包时报错。
top
模式下,存储侧的垂直量以行框顶部为原点向下计(baseline = ascent、ofs_y = ascent − yMax + 1);而
LVGL 以基线为原点向上计,引擎在
get_glyph_dsc 里做一次整数换算:
12设备端引擎
取一个字形的完整路径如下(pyf_engine_code.py 生成的
pyf_engine.c):
几个关键设计:
两级缓存
- 字形描述缓存:全局
font_cache[100](PYF_FONT_DSC_CACHE_CNT,可改),按 (unicode, font_type) 线性扫描命中则 0 次 Flash 读——界面上反复出现的字符(数字、标点、固定文案)基本全命中; - 单条 cmap 缓存:每个字体记住上一次查过的
{code, offset, size},get_glyph_dsc和紧随其后的get_glyph_bitmap查同一个字时第二次 cmap 查找免掉。 - 位图本体没有缓存:每次绘制都要从 Flash 读一次位图(详见 §13.2)。
- 字形描述缓存:全局
三种 cmap 查找
- 连续(全字库/连续区间):直算偏移 + 读 Flash 6 B;
- 稀疏 + hash(默认):XIP C 表开放寻址,平均接近 O(1);
- 稀疏 + 二分:XIP 排序数组,O(log n)。
fake 字库兜底:码点越界、cmap 查不到、
size == 0(生成时就缺字)、bin 版本 magic 不匹配——统统落到编译进固件的 fake 字库显示占位字形,任何情况下不花屏。与存储解耦:引擎只通过
pfunc_get_data(offset/buf/size)拉数据,SPI NOR、QSPI、eMMC、甚至文件系统都能接。LVGL / 裸机双接口:
LVGL_FONT_ENABLE=1时public_font_t就是lv_font_t,pyf_font_create()填好get_glyph_dsc/get_glyph_bitmap/line_height/base_line后可直接赋给 style;=0时是等价的自定义结构体,裸机直接调两个回调。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 解码后,由共享的pyf_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 字库同样双版本适配。
13方案优点与缺点
13.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)都做不到的。字库独立升级的版本闭环。bin 与 C 头文件用随机 magic 配对,版本错落 fake 字库 +
pyf_font_version_ok()上报,OTA 时字库和固件可以分开发布、分开烧写,工程上非常实用。多字体多字号一包。一个
pyf_lib.bin打包 N 个字体×M 个字号(含图标字库、emoji 字库),信息表统一管理,应用层用枚举取字体;固件与字库的对应关系只有一处。全套 C 代码自动生成,拿来即用。用户唯一要写的是一个 Flash 读函数;LVGL/裸机、hash/二分、RLE 开关、缓存条数都是
pyf_lib.h里的宏。缺字兜底。fake 字库保证任何异常路径都有占位显示,不会出现乱码/花屏——对量产设备是刚需。
按需裁剪,体积可控。码点配置支持"范围 + 界面文案文本"取并集,"只打包界面用到的几百个字"能把字库从几 MB 降到几十 KB,生成时间也降一个量级。
查表结构为外挂场景优化。连续区间直算 O(1);稀疏区间 hash 平均 O(1) 且表在 XIP 不占 RAM;dsc 缓存吸收热点字符的重复查询。
RLE 逐字形压缩,随机访问不受影响,1 bpp 字库收益显著;解码器极简(无状态、无查找表)。
工具链友好:纯 Python 跨平台(服务器无头环境可跑)、Web 在线生成(上传 TTF 选参数下载 zip)、变形字体一个文件出全字重、XBF 兼容 emWin 生态、附带 iconfont/emoji/多语言 Excel 一条龙。
emoji 图片字库:把 Noto Emoji PNG 转成同一 bin 里的一个"字体",用同一引擎渲染——LVGL 官方点阵字库路线没有对应能力(需另接
lv_imgfont)。
13.2 方案缺点与限制
同样按影响面排序,均给出代码依据:
渲染吞吐受 SPI 总线限制,位图无缓存。每个字形每次绘制都要 1~2 次 Flash 读(dsc 未命中缓存时 6 B 属性 + 位图本体);滚动长文本、跑马灯时同一个字反复读取。dsc 缓存只缓存 6 B 描述,不缓存位图(
__user_font_get_bitmap每次都走 Flash)。对比 LVGL C 数组的零 IO,这是外挂方案的本质代价,但位图 LRU 缓存是明确的补强空间(见 §15)。无 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 缓存存基础值不受污染。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 字号内安全)。码点仅支持 BMP。XBF 头与 cmap 都是 u16(
pyf_ttf_to_fontcvtst_xbf.py:43-44),增补平面(含大多数 emoji)必须走图片字库产线;bin 的稀疏 cmap 条目也是 u16 码点(emoji 字体单独用 u32 变体)。部分字体度量硬编码(2026-09 已解决)。y_size/y_dist/baseline全部渲染路径(freetype / pygame / FontCvtST 导入)均直接取 XBF 头的实测值;按字号查的经验值表已删除,"字号不在表内沿用上一个字体度量"的隐患随之消除。 注意:legacy(pygame) 路径旧表输出的行高偏大(如 16px 为 18/18/16,实测为 16/16/13),依赖旧值排版的存量工程升级后需过一遍视觉确认。强依赖文件命名约定。bpp 从文件名
..._2Bpp解析、字号取name[-7:-5]两位数字(pyf_xbf_to_fontlib_bin.py:1112-1113, 1295);文件名不合规会产出错误的元数据,且字号只支持两位数。hash 表装载因子为 1.0。生成时 hash 表大小 = 字符数(
pyf_xbf_to_fontlib_bin.py:1436),满载的开放寻址线性探测在聚簇处会退化,未命中查找最坏遍历整表(先被 first/last 区间过滤挡掉大部分)。README 所说 O(1) 是平均意义上的。位图行对齐带来的宽度虚报(2026-09 已解决)。--bitstream(默认开)改为字形内连续位流存储,box_w上报真实宽度;仅--bitstream False的兼容模式仍是旧行为。实测 ASCII 测试字库(16px/2bpp + 24px/1bpp)位流化单独可省约 25% 位图体积(1bpp 收益最大;RLE 开启时行尾 0 本就可压缩,叠加后净收益缩小)。静态缓冲非线程安全。
pyf_glyph_bitmap_buf是全局静态,get_glyph_bitmap返回其指针,多线程同时取字会互相踩;LVGL 单线程模型下没问题,裸机双核/RTOS 多任务需自行加锁。RLE 静态模式还要双倍缓冲。生成侧渲染质量依赖 pygame(2026-09 已解决)。XBF 默认由pyf_ttf_freetype_to_xbf.py直连 FreeType:FT_Set_Pixel_Sizes精确像素字号(不再 pt 扫描)、原生覆盖度灰度(不再亮度反推)、hinting 五档可选(1bpp 默认 mono 渲染更锐利)、生成速度快一个量级。fake/内部/图标字库生成器仍走 pygame(占位/图标字形对渲染精度不敏感),--renderer pygame可整体退回旧链路。生成的 LVGL 接口是 v8 签名(2026-09 已解决)。生成代码按LVGL_VERSION_MAJOR自动适配 v8 与 v9(v9.3 官方发布版契约),见 §14.5。工程细节类:字体信息区 0x100~0x1000 上限约 60 个字体;
pyf_cmap_*.c稀疏表同时以二分数组 + hash 表两份形态生成(XIP 体积 ×2,好在仅裁剪字库有此表);bin 内稀疏 cmap 为预留未被引擎使用。
14与 LVGL 官方字库对比与选型
14.1 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)
两条使用路径:
- C
数组(
--format lvgl):.c文件编译进固件,内部 Flash XIP,渲染直接取址,零 IO 零额外 RAM;内置 Montserrat 等字体即此形态; - binfont(
--format bin):运行时lv_binfont_create()(v8 为lv_font_load())经文件系统整体载入 RAM。
另有运行时矢量方案(Tiny TTF、FreeType 接口),不属于点阵字库,此处不展开。
14.2 存储与资源占用
| 维度 | Py_FontMaker 外挂字库 | LVGL 官方 lv_font_conv |
|---|---|---|
| 字库数据位置 | 外挂 SPI Flash(pyf_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 |
14.3 格式能力
| 维度 | 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 另做) |
14.4 工具链与生态
| 维度 | 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) |
14.5 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
风格的裸回调(pyf_font_get_bitmap_raw /
pyf_fake_font_get_bitmap_raw,跨字库兜底委托统一走裸回调,任何版本下都可直接调用);v9
下再套一层包装回调,用共享的
pyf_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),上板验证欢迎反馈。
14.6 一图流总结
| 字符集小(拉丁/数百字) | 字符集大(CJK 全量/多字号) | |
|---|---|---|
| 内部 Flash 充足 | LVGL C 数组(官方,最优) | LVGL C 数组(能塞下也可) |
| 内部 Flash 紧张 / 字库需独立升级 | 两者皆可,看是否要 OTA | Py_FontMaker 外挂字库(主场) |
14.7 选型建议
选 LVGL 官方 lv_font_conv,当:
- 字符集小(拉丁/西里尔/固定文案),C 数组塞得进内部 Flash;
- 对西文排版质量敏感(需要 kerning 与 1/16 px advance);
- 不想引入外挂 Flash 读路径,追求零 IO 的极致渲染性能;
- 使用 3 bpp、LCD 子像素等 lv_font_conv 独有特性。
选本方案(Py_FontMaker 外挂字库),当:
- 需要 CJK 大字符集、多字号,内部 Flash 放不下(最典型场景:手表/手环/仪表);
- 字库需要独立于固件 OTA(版本闭环是现成的);
- 平台是裸机或非 LVGL GUI(双接口 + XBF/emWin 兼容);
- 需要 emoji 图片字库、iconfont、多语言表一条龙;
- 希望用 Web 服务给团队/客户提供在线字库生成。
两者混用也常见:界面骨架用 LVGL 内置 Montserrat(C
数组),中文正文/大字号用本方案外挂字库——lv_font_t
层面完全等价,可在同一个 label 样式体系里共存。
15可优化方向
结合 §13.2 的缺点,按投入产出排序。每一项都不是"免费的午餐",收益与代价并列如下:
| 优化项 | 优化后的收益 | 代价与风险 | 建议 |
|---|---|---|---|
| 1. 字形位图 LRU 缓存 dsc 缓存旁加 N 条位图缓存 |
滚动列表/跑马灯里反复出现的字形 0 次 SPI 读(当前每次绘制都读位图本体),IO 降一个数量级,帧率更稳——收益最大的单点优化 | RAM 显著增加:每条缓存存整张位图(24px/2bpp 约 150 B,56px/2bpp 约 800 B),起效需几十条即 KB~十 KB 级;LRU 管理增加引擎复杂度;字库 OTA 后需清缓存;"一次性滚过大量不同字"(电子书翻页)命中率低、收益有限 | 做成宏开关 + 可配条数,默认关,RAM 富余的项目自行开启 |
| 2. hash 表预留空槽 表大小 = 字符数 × 1.25~1.5 |
查找长度稳定接近 O(1),消除满载线性探测在聚簇处的退化;未命中遇空槽提前终止(现在最坏遍历整表)——缺字查询多的多语言混排提速明显 | XIP 的 cmap C 表体积 ×1.25~1.5(全量 CJK 约 200 KB → 250~300 KB 内部 Flash,与"省内部 Flash"的卖点相抵);生成器与引擎 hash 计算需同步修改,新旧产物不互通(magic 版本闭环可挡住错配) | 只有裁剪型稀疏字库受益(连续 cmap 不走 hash);多语言产品值得做 |
| 3. adv_w 升级 8.4 定点 u16 属性头 6 B → 7~8 B |
主要是解除单字形 advance ≤ 255 px 的上限(72 px 以上超大字号安全);理论精度对齐 lv_font_conv 的 12 bit 定点 | 属性区体积 +17~33%(位图占大头,整库约涨几个百分点);bin 格式 break,工具 + 引擎 + 测试全链路改 | 实际排版收益很小:kern 已按 1/16 px 修正,且 LVGL 自定义字体回调的 adv_w 本身是整数输出,当前已达接口精度上限——等超大字号需求出现再做 |
| 4. 码点扩展到 u32 打通增补平面 |
CJK 扩展 B~G 生僻字(人名/古籍)、少数民族文字、历史文字可直接进普通字库,对齐 lv_font_conv 的全 Unicode 能力 | 四项里最重:XBF 头的 First/LastCode 是 u16,扩位即破坏与 FontCvtST/emWin 的格式兼容(XBF 的立身之本),需引入新格式版本或双模式;稀疏 cmap 条目 8 B → 10 B;hash/二分/缓存/测试全链路改 u32 | BMP 已覆盖绝大多数产品需求,emoji 另有图片字库产线兜底——需求驱动再做 |
一句话排序:1 体验收益最大(做成可选项)→ 2 看产品形态(多语言裁剪字库才值得)→ 3、4 都会 break 格式,等真实需求出现再动,别白付兼容成本。
16深入阅读与交流
- 研究笔记——2022 年原始研究与开发记录全文(14 章,含 XBF/pyf_lib.bin 格式设计全过程)
- 工具质量:引擎与位流经四层验证(单元测试 / bin 逐位校验 / clang 双版本语法检查 / 宿主机运行时逐位比对), 并在 LVGL 9.3 官方模拟器实机验证;源码暂未公开,如需交流见下方联系方式。
本文由 2022 年的《嵌入式字库研究及 Py_FontMaker 工具编写》重写而来,内容与当前代码保持一致。 交流:公众号 Bluetooth-BLE · QQ 群 177341833 · 返回生成页