ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

lv_font_conv 本地安装与 LVGL 字体转换实战教程

lv_font_conv 本地安装与 LVGL 字体转换实战教程 LVGL 开发中有一个绕不开的环节字体。lv_font_conv是官方推出的字体转换工具能把 TTF / WOFF / OTF 等字体文件转换成 LVGL 可以直接调用的 C 源码格式。我早期做屏幕 UI 时最头疼的就是嵌入式设备上没有合适的中文字库字库动辄几 MB芯片 Flash 根本装不下后来换用 lv_font_conv 做子集化裁剪把常用汉字压到几十 KB才算真正解决了问题。不过这个工具默认是通过网页在线版使用的碰上网不好的时候真想骂人。这篇博文就把我在本地安装 lv_font_conv 的完整过程和踩过的坑整理出来给还在折腾字体的朋友一个可以直接照抄的方案。内容适合正在做 LVGL 界面开发、尤其是使用中文或特殊符号字体的开发者也适合想把字体转换流程固化到 CI 或本地脚本里的人。整体会比较细致从原理讲起到安装、使用、参数参考、坑位记录逐层展开你可以按需跳到对应的章节也可以一步步跟着做。1. 先搞清楚 lv_font_conv 到底做了什么1.1 为什么 LVGL 需要“字体转换”而不是直接加载 TTF刚接触 LVGL 的人经常有疑问我在 PC 上写界面系统字体随便用为什么到了 LVGL 这里还得先“转换”原因不复杂LVGL 是面向资源受限嵌入式环境的图形库它不依赖文件系统、不依赖操作系统更不可能像桌面系统那样去解析 TTF 内部的复杂曲线表。LVGL 的显示原理是直接把字形渲染成像素位图然后在绘制时把位图拷贝到显存或帧缓冲中。所以它需要的是“已经画好的像素”或者“能快速还原成像素的数据”而不是一套需要实时解算的矢量轮廓。这也解释了为什么 LVGL 的字体文件本质上是一大堆 C 数组和结构体里面存的是每个字符的位图数据、字符宽度、高度、偏移量、笔画粗细信息等等。开发者把.c文件编译进固件运行时就不用再做繁重的字体解析无论是速度还是内存占用都有保障。我见过有些刚入门的同学在 PC 模拟器上直接写lv_font_load(xxx.ttf)或者用lv_style_set_text_font传一个文件路径这在嵌入式环境里完全不成立。LVGL 里能加载外部字体文件的是少数平台特性大多数时候你得走“转换-编译-烧录”这条路。1.2 在线版和本地版的核心差异LVGL 官网提供了一个字体转换的在线页面你上传字体、勾选字符集、设置大小和参数点一下就能生成 C 文件。这个页面就是 lv_font_conv 的 Web 封装。在线版的优点是无脑、不用装环境但痛点也很明显字体文件可能涉密或比较大上传到第三方网站总有顾虑。在公司内网、隔离网络环境下在线页面根本打开。批量生成多套字重、多套尺寸时反复上传下载非常低效。在线工具版本更新滞后无法使用最新参数或者最新版 LVGL 的属性。本地版 lv_font_conv 则是用 Node.js 写的一个命令行工具你可以在自己的电脑上运行离线生成字体。它支持 LVGL 从 7.x 到 9.x 的格式还能合成多个字体文件为一张字体表、生成字体图标、裁剪子集、压缩位图数据等。只要能装上 Node.js跨平台跑起来很轻松Windows、Linux、macOS 都可以。实际用下来本地命令行比网页点按钮可控性高得多所有参数写成一条命令改起来方便放进 git 仓库里还能版本化管理换台机器一条命令跑出同样的字体。2. 本地安装前的环境准备与整体思路2.1 确认你的 Node.js 环境lv_font_conv 是基于 Node.js 的命令行应用所以第一步是装 Node.js。如果机器上已经有 Node.js可以在终端里跑node -v npm -v正常情况下会输出类似v18.16.0和9.5.1的版本号。如果没有输出或者提示command not found那就得先装 Node.js。建议使用比较新的 LTS 版本比如 18 或 20旧的 12、14 版本可能会遇到依赖包不支持的问题。我自己早期在 Windows 上装了很老的 Node 8结果 lv_font_conv 安装时直接报了一堆语法错误后来升级到 18 就一切正常。到 Node.js 官网下载 LTS 安装包是最省心的方式。macOS 用户也可以用 Homebrewbrew install node。Linux 用户可以用 apt、yum 或者 nvm 来装。装完记得重新开一个终端窗口让环境变量生效。这一步虽然基础但不检查清楚后面所有问题都会被误以为是 lv_font_conv 本身的问题。注意Node.js 版本过旧14或过新比如刚从官网下载的 21、22 某些预览版都可能有兼容性问题。推荐固定使用 18 LTS 或 20 LTS实测最稳。2.2 选择适合你的安装方式lv_font_conv 的安装有两种主流方式一种是作为项目依赖安装到本地目录另一种是全局安装成系统命令。这两种我都试过区别其实蛮大。本地安装的命令是npm install lv_font_conv装完后你需要在项目目录下通过npx lv_font_conv ...来执行或者写 npm 脚本调用。这样的好处是依赖随项目走不会污染全局环境适合你在某个具体工程里使用团队协作时也容易锁定版本。全局安装的命令是npm install -g lv_font_conv装完后可以在任意目录直接执行lv_font_conv --help。好处是方便坏处是版本管理靠自觉。如果电脑上同时有好几个 LVGL 项目不同项目可能依赖不同版本的 lv_font_conv这时候全局装一个容易犯迷糊。我个人的建议是如果你只是偶尔转一两个字体全局装省事如果你要定期生成多套字体并且希望构建可复现那就放进项目里做本地安装。另外lv_font_conv 官方也提供 Windows 的可执行文件发布包在 GitHub Releases 页面能找到lv_font_conv.exe或者对应平台的二进制。这种方式连 Node.js 都不用装。但我实际使用中发现二进制版本在部分老 CPU 或精简版 Windows 系统上可能缺少 VC 运行库反而更麻烦。所以如果你是开发机直接用 Node.js 方式最稳如果你是拿到一台临时机器就想转个字体那下载二进制可能更快。2.3 安装完成后的环境验证装完之后先别急着转字体跑一下版本号和帮助信息确保工具已经正常接入环境lv_font_conv --version lv_font_conv --help如果能看到命令用法列表基本就说明安装成功了。这里有几个值得留意的点Windows 上如果全局安装后提示找不到lv_font_conv大概率是 npm 的全局 bin 目录没有加入 PATH。可以通过npm config get prefix查看全局路径然后手动把prefix下的目录加入系统环境变量。macOS 上如果执行时提示Permission denied可能是安装时的权限问题可以试试sudo npm install -g lv_font_conv但尽量先解决 npm 本身的目录权限比直接 sudo 更规范。有些 Linux 发行版默认开启了 npm 的严格模式安装全局包时会因为权限不足报错推荐用 nvm 管理 Node.js 之后再安装这样全局包的路径在你的用户目录下不会碰系统目录权限问题基本消失。安装这个环节如果卡住不用怀疑别的基本都是环境变量和权限两个问题。按照我上面的思路去排查五分钟内能解决。3. lv_font_conv 核心概念与参数逐项拆解3.1 命令行结构长什么样lv_font_conv 的核心调用方式是lv_font_conv --font 字体文件路径 --output 输出C文件路径 [其他参数]就这么一条命令参数非常多但核心倒不复杂。我把最常用的一组分项说明一下方便你按字段理解--font输入字体文件路径可以传多个用空格分隔支持 TTF、OTF、WOFF、WOFF2。--output生成的.c文件保存路径同时会自动生成一个同名的.h头文件。--format输出格式早期有lvgl和raw两种LVGL 9 之后格式参数更细但一般不用手工指定。--size字体尺寸单位是像素这个参数决定了字体的实际显示大小。 注意LVGL 的字体是按像素大小定义的不是传统排版里的磅值。--bpp位深度也就是每个像素用几个 bit 来记录灰度或颜色常见取值为 1、2、4、8。--symbols要包含的字符列表可以是具体字符也可以用范围比如0x20-0x7F或者直接ABCabc123。--no-compress默认 lv_font_conv 会对位图数据进行 RLE 压缩某些情况下不需要压缩时可以关掉便于查看原始数据。这组参数是我觉得最开始需要理解的骨架。剩下的参数比如--fallback、--force-fast-kern-format、--lv-include、--subpx等都是在特定场景下才用得上先不用背用到再去查就行。3.2 字体尺寸与 bpp直接影响内存和显示效果字体转换里最重要的一组参数就是--size和--bpp。很多刚开始接触 LVGL 的朋友会误以为 bpp 越大越好其实不是这么简单。bpp 是 bit per pixel 的缩写表示字体位图的灰度级别。bpp1 时每个像素只有 0 或 1也就是纯黑白字体边缘锯齿明显bpp2 时有 4 级灰度边缘稍好bpp4 有 16 级灰度已经比较平滑bpp8 有 256 级灰度效果接近 PC 上的字体渲染。但是bpp 越大每个字符占用的内存也越大。直观地说同样尺寸的字bpp8 的位图体积是 bpp1 的 8 倍。所以这里有一个取舍问题。我的经验是显示英文和数字尺寸也不大比如 12px~16pxbpp2 足够了。显示中文尤其字体稍大时bpp4 是一个折中点边缘效果不错内存消耗可控。如果你在做比较高保真的界面比如数字时钟、特殊符号展示bpp8 也可以但要知道代价。小字号情况下 bpp1 带来的锯齿感极其明显能不用就别用。至于--size的选择需要结合 DPI 和实际屏幕分辨率考虑。LVGL 的坐标单位通常就是像素所以你想让字体在屏幕上显示 20 像素高--size就设成 20。还有个小技巧如果你在 PC 模拟器上看效果觉得不错但烧到 320x240 的小屏上发现字体发虚可以试试把 size 调大 1~2 个像素很多字体在小尺寸下轮廓会闭合得更干净。3.3 字符集过滤LVGL 中文字体的救命稻草LVGL 项目里最影响 Flash 容量的往往不是代码而是中文字体。一个完整的中文字体比如思源黑体包含两三万个汉字即使只转常用的 3500 字生成的 C 文件也可能到 1MB 甚至更多。如果用完整字符集STM32 内部 Flash 一般也就是 256KB~1MB直接就被字体占满了。lv_font_conv 提供了非常灵活的字符集控制方式。你可以直接在--symbols后写想要的汉字lv_font_conv --font SourceHanSansCN-Regular.otf --output output/my_font.c --size 16 --bpp 4 --symbols 你好世界123ABCabc也可以从文本文件读取字符集。这个我强烈推荐尤其是在做产品时。把 UI 上出现的所有文案汇总到一个 markdown 文件或者 txt 文件里然后写个小脚本提取唯一字符再传给 lv_font_conv这样生成的字体文件就会精准匹配界面所需内容体积能压到最小。用文本文件指定字符集的命令大致是lv_font_conv --font SourceHanSansCN-Regular.otf --output output/my_font.c --size 16 --bpp 4 --symbols $(cat chars.txt | tr -d \n)Linux/macOS 下这样写没问题Windows 的 PowerShell 就稍微麻烦点。更稳妥的方案是写一个 Node.js 脚本读取字符列表文件拼接然后调用命令行避免 shell 转义带来的各种奇怪问题。需要特别指出的是字体子集化不只是缩小体积还能加快系统启动时的字体初始化和渲染速度。因为字符数量少了字符索引表查找更快缓存命中率也更高。我做一个带时间、温度、湿度显示的桌面小屏时只用了 60 个字符生成的字体文件还不到 10KB效果非常理想。3.4 多字体合成图标和文字放在一起LVGL 项目中经常会用到图标字体比如 Font Awesome、Material Icons。图标本质上也是一个字符只是用字体来表现矢量图形。如果界面中既要用普通中文显示文案又要用图标字体显示电池、Wi-Fi、箭头那就要用到 lv_font_conv 的多字体合成功能。合成命令长这样lv_font_conv \ --font NotoSansSC-Regular.otf \ --font fontawesome-webfont.ttf \ --output output/my_icon_font.c \ --size 20 \ --bpp 4 \ --symbols 中文测试ABC \ --symbols \uf001\uf002\uf003这里第一个--font对应中文主字体第二个--font对应图标字体。--symbols可以写多个分别对应不同字体文件的字符。lv_font_conv 会尝试从第一个字体中找字符找不到再从第二个字体中找以此类推。这样生成的 C 文件里既包含汉字字形也包含图标字形而且通过同一个字体对象就可以引用不需要在 UI 代码里频繁切换字体。我在实际项目中用 Font Awesome 5 的免费图标集加上思源黑体合成过一个“中文图标”的字体界面代码简洁不少编译进固件后字体体积也没有失控。不过多字体合成有个细节容易踩坑不同字体文件之间的基线和对齐方式可能不一样。比如中文的 ascender 和 descender 跟英文字体就不一致合成后可能出现上下偏移。解决办法是统一设置--size大小并且在选字体时就挑风格比较接近的。如果你发现图标在 LVGL 里显示的位置偏上或偏下可以在调用 lv_font_conv 时调整字体自己的--font参数比如逐个使用--font加--size的写法或者直接调整 UI 代码里的text_y偏移具体哪个省事视场景而定。4. 手把手实操把本地字体转成 LVGL 能用的.c文件4.1 准备字体文件和字符清单为了演示完整流程我从网上下载了一个开源中文字体“站酷快乐体”的 TTF 文件放在fonts/目录下然后创建一个chars_demo.txt。里面准备了一句话你好LVGL离线字体转换测试0123456789ABCabc这是最朴素的字符集方式。如果要在真实项目里使用按我前面的做法最好是从 UI 文案里自动提取字符集避免手写漏字。比如你界面上一共有 120 个不同文案把所有文案拼接去重后就是一个完整的字符集。当然要注意标点符号像“。”这类全角符号不少中文文案里都有字符集漏掉的话界面上会显示方块。4.2 生成 LVGL 字体文件的具体命令在终端中进入项目目录执行lv_font_conv --font fonts/zcool.ttf --output output/zcool_font.c --size 18 --bpp 4 --symbols $(cat chars_demo.txt | tr -d \n)这行命令的意思是使用fonts/zcool.ttf作为输入字体。输出到output/zcool_font.c同时自动生成output/zcool_font.h。字体大小设置为 18 像素。灰度级别设置为 4 bit也就是 16 级灰度。字符集从chars_demo.txt中读取并删掉换行符。执行成功后终端会显示类似Done的信息。打开生成的zcool_font.h会看到一个自定义的字体结构声明zcool_font.c里则是一个巨大的数组每个字符的位图数据、宽度、高度、偏移都被编码进去了。实际落地时如果字符太多直接在 shell 里拼接可能超长或者有特殊字符转义问题。我见过有同事在用 PowerShell 时因为$字符被转义导致命令失败。这种情况就不要硬掰命令行写一个 Node.js 脚本调用更可靠。核心代码就几行const { execSync } require(child_process); const fs require(fs); const chars fs.readFileSync(chars_demo.txt, utf-8).replace(/\n/g, ); const cmd lv_font_conv --font fonts/zcool.ttf --output output/zcool_font.c --size 18 --bpp 4 --symbols ${chars}; execSync(cmd, { stdio: inherit });这样就把字符读取、命令拼接、执行一次性搞定还能方便地循环处理多套字体。4.3 把生成的字体接入 LVGL 工程生成的zcool_font.c和zcool_font.h需要添加到你的 LVGL 工程中。如果你是 Keil MDK 用户把.c文件加入工程编译即可如果你是 CMake 或 ESP-IDF 工程把对应文件路径加入源码列表即可。在代码中使用时只需要在 C 文件里 include 头文件然后初始化一个样式#include ui/fonts/zcool_font.h static lv_style_t style_text; lv_style_init(style_text); lv_style_set_text_font(style_text, zcool_font);之后把样式挂到 label 上lv_obj_t *label lv_label_create(parent); lv_obj_add_style(label, style_text, 0); lv_label_set_text(label, 你好LVGL);这时你会看到中文和英文正常显示图标字符也能直接放进去。如果屏幕上出现了“口口”或者空白通常就是字符集没包含对应字符或者是字体文件本身不支持该字形。排查优先级是先查字符是否在--symbols里再查字体源文件是否缺字形最后查bpp和缓存设置。4.4 增量调整和重新生成的高效工作流转换字体不是一次性的界面文案改了、设计师换了字体、屏幕尺寸变了你都得重新生成。我这边比较推荐把字符集提取、字体转换、结果校验写成一个脚本放在项目目录的tools/下面定期维护。一个典型的脚本流程是扫描源码目录提取所有lv_label_set_text和字符串常量里的可见字符。把常用字符、换行符、特殊符号补充进去。去重排序后生成chars.txt。调用 lv_font_conv 生成多个尺寸和字重的字体文件。检查生成文件大小超过预期就报警。这样一个流程基本可以无脑执行。我把这个脚本配合 Git 使用每次界面文案变化后只提交新的字体文件就能保证固件体积可控、显示不乱码。5. 实际使用中常见的报错和问题排查5.1 安装阶段的典型报错安装 lv_font_conv 时最常见的错误之一是npm ERR! code EACCES npm ERR! syscall mkdir npm ERR! path /usr/lib/node_modules/lv_font_conv这个是因为当前用户没有全局 npm 目录的写权限。解决方案要么用sudo硬装要么更推荐地把 npm 的全局路径改到用户目录。后面这种做法更一劳永逸npm config set prefix ~/.npm-global export PATH$HOME/.npm-global/bin:$PATH把这个 export 写进.bashrc或.zshrc里再重装一次就好了。还有一个比较隐蔽的错误是在 Windows 环境下安装成功但执行lv_font_conv时弹出“无法加载文件因为在此系统上禁止运行脚本”之类的提示。这通常是 PowerShell 的执行策略限制。解决办法是用cmd打开执行或者在 PowerShell 里临时放开Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypasslv_font_conv本身是用 Node.js 写的不会做太出格的事放开执行策略在当前进程里是安全的。5.2 转换阶段的常见报错5.2.1 “Can’t find font file”这表示--font参数指向的路径不对。Windows 下要注意路径分隔符建议绝对路径或相对路径都使用正斜杠/避免\在命令行里被误解析。路径上如果带中文或者空格记得整体加双引号。5.2.2 “No characters were selected”碰到这个错误先检查--symbols是否为空以及从文本文件读取时是否真的拿到了内容。在 Linux 下用$(cat chars.txt | tr -d \n)时如果文件是 UTF-8 with BOM开头的 BOM 可能会被当成一个字符导致最终转换出的字体多出一个诡异字符。解决办法是在脚本里用sed -i 1s/^\xEF\xBB\xBF// chars.txt或者用 Node.js 读取时去掉 BOM。5.2.3 生成后字体显示为方块这种问题大多不是转换失败而是 LVGL 运行时不认识字体里的某个字形。排查时可以先看看生成的.c文件里有几个glyph条目确认目标字符是否在其中。另外一个容易被忽视的点是LV_FONT_FMT_TXT_LARGE这个宏配置。LVGL 的字体存储格式有分为LV_FONT_FMT_TXT_LARGE和LV_FONT_FMT_TXT_SMALL两种如果工程里的宏定义与 lv_font_conv 生成时的格式不匹配也可能出现某些字符无法显示。不过 9.x 之后这个问题不太常见了主要关注版本一致性即可。5.3 性能和体积问题的调优心得字体文件体积偏大时先别直接怀疑工具看看自己选了多大的字符集。如果必须支持 GB2312 全部字符那 6000 多个汉字跑不掉体积自然大。这时候可以考虑降低--bpp从 4 降到 2体积大概能缩一半。确认不需要粗体字形时用 Regular 字重而不是 Bold。只在真正需要大字的页面单独生成 24px、32px 字体其他页面用 16px 字体。优先使用项目里已存在的字体不要每改一次界面就引入一个新字体。我做过一个 STM32G0 的小项目Flash 总共 64KB最终给字体预留 16KB用 bpp2、size12、字符集 40 字生成的字体文件只有 4KB 左右显示效果完全能接受。所以说工具性能能释放多少取决于你的规划优化空间是很大的。5.4 与 LVGL 版本兼容问题LVGL 8.x 和 9.x 的字体结构有差异lv_font_conv 不同版本输出的代码也不一样。如果你用新版 lv_font_conv 生成的字体文件放到旧版 LVGL 工程里编译时可能报很多结构体字段不匹配。我在本地一直保持 lv_font_conv 和 LVGL 子模块同步更新别让版本差得太远。在项目里安装 lv_font_conv 时可以锁定版本号例如npm install lv_font_conv0.4.2这样团队协作时每个人生成的字体文件格式完全一致不会因为某个人手滑升级了工具而出现莫名其妙的编译错误。LVGL 官方的文档页一般会标注当前推荐使用的 lv_font_conv 版本升级前先去查一眼最稳。6. 几个能提高生产力的附加技巧6.1 用字体可视化和预览工具辅助校验字体转完后直接烧进板子如果效果不对来回烧写调试非常浪费时间。我习惯先在本机用 LVGL 的 PC 模拟器跑一遍生成的字体。LVGL 官方提供了基于 SDL 的模拟器工程也有 VS Code CMake 的方式。把你的字体文件替换进去运行后直接看显示效果效率比烧录到开发板高得多。热词里有人提到“lvgl 9.x pc 模拟器”这套流程确实值得掌握尤其是做字体调整时可以秒级检查。模拟器跑通后可以再处理bpp、size这些参数直到视觉上满意再生成最终文件。这样减少反复烧录开发体验会舒服很多。6.2 在 CI 里集成字体转换如果你的项目使用 GitHub Actions 或 GitLab CI完全可以把 lv_font_conv 安装和字体生成放进流水线。每次推送时自动从最新的文案提取字符集、重新生成字体、编译固件。这样就不会出现“代码改了但忘记重新转字体”这种低级问题。CI 脚本里一般需要安装 Node.js然后用 npm 安装 lv_font_conv再执行转换脚本。字体文件如果变化提交到产物或 release 中即可。这个工作流一旦跑起来字体维护就变成了一件非常机械且可靠的事。设计师给了新字体文件你只要替换源文件、重新跑一次流水线所有旧字体文件自动更新步骤和人的失误都大幅减少。6.3 批量生成多尺寸多字重字体通常一个完整界面上会有大标题、正文、小注释需要 24px、16px、12px 等不同大小的字体。如果分别写三条命令有点繁琐。我习惯用一个 for 循环批量处理for size in 12 16 24; do lv_font_conv --font fonts/regular.ttf --output output/font_${size}.c --size $size --bpp 4 --symbols $(cat chars.txt | tr -d \n) done在 Windows PowerShell 下就用foreach ($size in (12,16,24)) { lv_font_conv --font fonts/regular.ttf --output output/font_$size.c --size $size --bpp 4 --symbols (Get-Content chars.txt -Raw) }生成的多个字体文件在 LVGL 里可以分别注册为不同的字体对象UI 中按需切换。这样不同场景各取所需不会出现一个大字体被疯狂缩放导致内存浪费或清晰度下降的情况。7. 我个人的使用体会从第一次在网页上点按钮生成字体到后来把 lv_font_conv 完整接入本地工作流最大的变化其实是心态上的。以前总怕漏字体、怕字体太大、怕换字体耽误进度现在只要写好一个转换脚本所有这些都是几分钟的事。尤其是离线安装后在不能上网的内网开发环境里也能顺畅工作这一点对工业项目和车机项目尤其重要。另外我发现很多人误以为 lv_font_conv 只能处理英文字体实际上它对中文和各类符号的支持都非常成熟。关键是选对源字体格式并控制好字符集和 bpp。如果前期的字体规划做得好后期 UI 调整会省下大量的排障时间。最后分享一个小技巧保留好你的字体源文件和字符集清单并统一放进项目仓库一个固定目录里。每次界面文案更新用脚本重新生成一遍字体。这比任何“在线工具手工下载”的方式都更可靠也避免了字体散落在微信、网盘里找不到版本的问题。LVGL 的字体问题说白了就是一个流程问题流程理清楚了体量再大的字库都不值得害怕。
返回列表