news 2026/10/22 15:59:45

LVGL多国语言字库制作:TTF转C数组实现中文与阿拉伯文显示

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LVGL多国语言字库制作:TTF转C数组实现中文与阿拉伯文显示

简介:面向lvgl图形库开发者的多国语言字库制作工具,专为需要为嵌入式界面添加中文、日文、韩文、阿拉伯文等多语言支持的场景而设计。工具提供字库编辑、字符集转换、字体优化与跨平台操作能力,可将编辑好的字体直接生成lvgl可识别的格式,并辅助集成到现有项目中,适合有一定嵌入式基础的开发者快速完成国际化界面搭建。资源包共146个文件,压缩后约32.44MB,包含37个c源码、36个头文件、23个dll动态库、21个png图标、16个qm翻译文件以及工程配置文件、批处理脚本和可执行程序,覆盖STM32工程模板与字体生成配套模块,可直接参考或复用。当前已有427人下载学习。通过该工具与附带的示例工程,开发者能掌握lvgl字库制作全流程,理解字符集映射与字体文件生成原理,并将多语言支持能力快速移植到自己的项目中,减少重复开发成本。

1. LVGL 多国语言字库制作工具:让中文与阿拉伯文在同一块屏上正常显示的落地资源

在 STM32F103 上调试 LVGL 界面时,我第一次被“方框字”折腾到半夜——字符串里明明是中文,屏幕上却是一排豆腐块。原因并不玄学:LVGL 自带的字体只有 ASCII,要显示中文、日文、阿拉伯文这些多语言字符,必须先把 TTF 字库按 LVGL 认识的格式转换成一整份 C 数组。这套“lvgl多国语言字库制作工具”工程资源,核心就是 mcu_font_30.c 与 mcu_font_40.c 两个已转好的字库文件,配齐 lcd.c、stm32f10x_flash.c 等 STM32F10x 外设驱动,适合正在做小屏多语言菜单、又不想从头摸字库格式的开发者。把它拆开看完,你就能把同一套流程搬进自己的工程。

2. LVGL 字库加载机制:先看懂 glyph、cmap 和 bitmap,再上手转换

2.1 LVGL 显示一行文字时,内部做了什么

LVGL 的 label 组件显示字符串,并不是直接拿字体文件渲染。它走的是一条“查表—取位图—绘制”的固定链路。字符串在内存里按 UTF-8 编码存放,LVGL 每取到一个字符的 Unicode 码点,就会调用 lv_font_t 结构体里的 get_glyph_dsc 回调,去查这个字符在当前字库中是否存在,返回对应的字形描述;随后再调用 get_glyph_bitmap 从字库的位图区取出像素数据,最后按照字形描述里的宽高、偏移量画到屏幕上。整个过程中有两张核心表:cmap 负责把字符码点映射到字形编号,glyph_dsc 负责描述每个字形的绘制参数。

我一般在调试多语言显示问题时,会先确认 cmap 是否覆盖了目标字符。常见做法是写一段极简的查询代码来验证:

/* 示意代码:检查一个 Unicode 字符是否在字库 cmap 中 */ lv_font_t *font = &mcu_font_30; /* 已转换好的字库 */ uint32_t codepoint = 0x4E2D; /* 字符“中”的 Unicode 码点 */ lv_font_glyph_dsc_t dsc; lv_result_t found = lv_font_get_glyph_dsc(font, &dsc, codepoint, 0); if (found == LV_RES_OK) { /* 命中:dsc.adv_w 是横向步进,dsc.box_w 与 dsc.box_h 是字形框尺寸 */ } else { /* 未命中:屏幕上会画方框或空白替代符 */ }

这里的关键点是 lv_font_get_glyph_dsc 的第三个参数必须传 UTF-8 解码后的真正码点,而不是字符串的原始字节。如果字符不在字库里,回调直接返回失败,label 只能画一个方框。换句话说,转换字库时“字符范围”这个参数,直接决定了哪些字符能从字符串变成屏幕上的像素。

2.2 多语言字库的真正难点:字符集覆盖和字形变体

不少人以为多语言支持就是把字库文件换大一点。实际不是。ASCII 只有 95 个可打印字符,按 1bpp 存储不到 2KB;简体中文常用字有 6763 个(GB2312),就算 16px 高度、4bpp 抗锯齿,体积也会到 100KB 上下;日文要同时覆盖平假名、片假名和大量汉字;阿拉伯文更特殊,同一个字母在词首、词中、词尾会呈现三种不同形状,这不是多放几个字符就能解决的,而是需要能输出字形变体的转换流程。

因此,一个靠谱的多语言字库制作工具,至少要让你控制下面几项参数。我在实际转换时最常调的也就是这张表:

参数作用我常用的取值
字符范围决定字库包含哪些码点,直接关系体积ASCII + 所需语言区段
字体尺寸字形的像素高度,对应 line_height30、40 两套
bpp每像素位数,常用 1/2/4/84(抗锯齿)
base_line基线位置,影响西文与中文的中线对齐随字体尺寸自动计算
亚像素渲染子像素级平滑,依赖屏幕物理排列一般关闭
阿拉伯文整形把独立字符变成连写变体按需开启

把这些参数在转换前定好,生成的 C 文件才有实用价值。很多项目翻车都是因为只调了字体大小,字符范围还是默认的 ASCII,结果多语言字符全部显示成方框。

2.3 为什么不能直接把 TTF/OTF 扔给 LVGL

嵌入式设备上,MCU 的 Flash 和 RAM 都很紧张。TTF 需要字库解释器解析曲线并光栅化,LVGL 不内置 FreeType,也不会去读文件系统,它只认按固定格式排布的 C 数组。这就是字库制作工具的定位:把 TTF 里每个字形按固定 size 栅格化,压缩成像素位图,连同 cmap 映射表一起打包成 .c 文件,编译时直接链接进程序。这样做的好处是运行时不需要字体引擎,CPU 只做内存拷贝和绘图;代价是每个字号都要单独生成一份字库。

到这里你应该明白了:转换工具产出的 mcu_font_30.c 本质上是“拍扁了”的像素字库,改字号、改字符集都要重新走一遍转换。接下来我从这份资源里的实际文件出发,拆解字库文件里的数据布局,再给出重新生成的完整命令。

3. 解剖 mcu_font_30.c:字库 C 文件里的数据排布与体积账

3.1 从工程文件反推这套资源的构成

拿到这份资源时,文件名其实已经把工程结构交代得比较清楚了。mcu_font_40.c 和 mcu_font_30.c 明显是两套不同字号的 LVGL 字库文件;lcd.c 是屏幕驱动;stm32f10x_tim.c、stm32f10x_flash.c、stm32f10x_rcc.c 这些是 STM32F10x 标准外设库里的定时器、Flash、时钟、ADC、I2C、CAN 驱动;keilkilll.bat 是常用的 MDK 工程清理脚本。组合起来的判断是:这是一套在 STM32F10x 上跑 LVGL 的完整工程,多语言字库已经被提前转好并写进了工程。

文件作用
mcu_font_40.c / mcu_font_30.cLVGL 字库,对应两种字号
lcd.c屏幕驱动与画布初始化
stm32f10x_flash.c内部 Flash 读写,烧录和运行时加载字库
stm32f10x_rcc.c / tim.c / i2c.c时钟、定时器、I2C 外设驱动
keilkilll.bat清理 MDK 编译中间文件

两套字库的存在很合理。界面上的标题栏、状态栏、正文用不同字号,运行时不需要实时缩放位图,省下不少 CPU 开销。我一般会把界面涉及的两三个字号全部生成,按需引用,同时把转换命令行配置存一份,方便后面加字。

3.2 lv_font_t 结构体与三块核心数据

一个标准的 LVGL 字库 C 文件,内部大体由三部分组成:bitmap 位图数组、glyph_dsc 字形描述数组、cmaps 映射表数组,最后统一挂进一个 lv_font_t 结构体。我用常见的生成格式做一个简化示意,字段名称与 LVGL 转换器的输出保持一致:

/* 简化示意:LVGL 字库文件内部的三段式结构 */ static const uint8_t mcu_font_30_bitmap[] = { /* 所有字形像素连续排列,按 glyph 顺序存放 */ 0x00, 0x7E, 0x81, 0x81, ... }; static const lv_font_fmt_txt_glyph_dsc_t mcu_font_30_glyph_dsc[] = { /* 每个字形一段:adv_w 步进、box_w/box_h 字形框、ofs 偏移、bpp、位图索引 */ { .adv_w = 8, .box_w = 8, .box_h = 15, .ofs_x = 0, .ofs_y = 0, .bpp = 4 }, ... }; static const uint8_t mcu_font_30_cmaps[] = { /* cmap 表:把 Unicode 码点映射到 glyph_dsc 数组下标 */ 0x04, 0x00, 0x1F, 0x01, ... }; static lv_font_t mcu_font_30 = { .get_glyph_dsc = lv_font_fmt_txt_get_glyph_dsc, .get_glyph_bitmap = lv_font_fmt_txt_get_glyph_bitmap, .line_height = 30, /* 决定 label 的行高 */ .base_line = 7, /* 基线偏移,混排时关键 */ .subpx = LV_FONT_SUBPX_NONE, .dsc = &(lv_font_fmt_txt_dsc_t){ .cmaps = (void *)mcu_font_30_cmaps, .glyph_dsc = mcu_font_30_glyph_dsc, .bitmap = mcu_font_30_bitmap, } };

这里需要重点理解两个地方。第一,bitmap 是按字形顺序连续排列的原始像素,glyph_dsc 里不存实际像素,只存“这段像素在数组里的第几个字节”;第二,cmap 表负责把字符码点翻译成 glyph_dsc 的下标。LVGL 渲染时先走 cmap 再走 glyph_dsc,整个过程没有文件系统参与,所以它轻。line_height 和 base_line 这两个字段,直接决定多语言混排时行与行的对齐效果,后面避坑章节会再展开。

3.3 字库体积的估算账本

中文字库的体积焦虑,基本是每个做 MCU UI 的人都要面对的。算个账:30px 高的字,字形框按 30×30 估算,4bpp 表示一个像素占半个字节,单个字形最坏情况约 450 字节。如果转完整 GB2312 常用汉字 6763 个,体积约 3MB;STM32F103 内部 Flash 一般不超过 512KB,直接放不下。实际操作里,大家普遍采用“按需子集化”:只转界面中真实用到的字符,而不是一上来就转全字库。

这里有一个容易踩的盲区:很多人把字符范围直接写成 0x4E00-0x9FA5,以为这就是 GB2312 的 6763 个汉字。实际上那是 Unicode CJK 统一汉字基本区,包含 20902 个码位,中文字体大多能覆盖这些字形,转换器会老老实实把两万多个字形全做出来。所以字符范围清单一定要按实际用字维护,而不是甩一个超大区间。下一章的转换命令里,我会把字符清单文件的写法一起给出来。

4. 从 TTF 到 mcu_font_40.c:转换命令、参数与集成实战

4.1 转换前的准备工作

先准备一份干净的 TTF/OTF 字体文件。注意版权,找开源可商用的字体会省掉很多后续麻烦;另外中文字体和西文字体经常是分开的,界面里英文用西文字库、中文用中文字库,也可以在转换时把多个字体文件合并进同一份 LVGL 字库。字符清单文件也要在转换前维护好,下面是一份常见写法:

# char_range.txt:注释行以 # 开头,每行一个区间或单个码点 0x0020-0x007E # ASCII 0x00C0-0x00FF # 扩展拉丁 0x4E00-0x9FA5 # CJK 统一汉字(注意:这个区间有两万多个码位) 0x30A0-0x30FF # 日文片假名 0x0600-0x06FF # 阿拉伯文

这个文件会直接变成转换器的字符范围来源。我见过不少开发者忽略这个清单,直接用全量 Unicode,结果文件大到编译都过不去。建议先把界面涉及的语言区段和实际用字扫进清单,宁可多转几十个,不要一上来转几万个。如果只做中文环境,更稳妥的办法是放一个“界面汉字收集表”,把代码里出现的汉字去重后列进去。

4.2 执行转换与参数说明

常见做法是用 LVGL 维护的命令行转换器,基于 Node.js,命令直观。下面是我常用的转换命令:

npx lv_font_conv \ --font ./SourceHanSansSC-Regular.otf \ --size 40 \ --bpp 4 \ --range-file ./char_range.txt \ --format lvgl \ --output ./mcu_font_40.c \ --force-fast-kern-format \ --no-compress

参数拆开讲:--font 指定源字体文件路径;--size 是字形像素高度,我习惯对齐 UI 设计稿里的字号;--bpp 4 是为了给文字边缘留抗锯齿过渡,1bpp 的锯齿在 TFT 屏幕上非常明显;--range-file 比一串 --range 更好维护;--format lvgl 表示输出 LVGL 可直接加载的 C 字库;--force-fast-kern-format 会把字距表做成快速索引格式,界面有大量西文混排时建议开启;--no-compress 关闭位图压缩,MCU 上省去解压开销。如果你的转换器版本没有 --range-file,退化成一条条 --range 参数,效果一样。

提示:生成完成后检查输出文件,mcu_font_40.c 顶部应有 LV_FONT_DECLARE 声明,文件内部能看到 cmaps 与 glyph_dsc 段。如果只有一坨数组而没有这些结构,大概率是 --format 参数没配对。

4.3 把字库注册到 LVGL 界面里

转换完成后,把 .c 文件拖进 MDK 工程并加入编译,同时在需要引用字库的源文件里加声明和样式设置:

/* 在 main.c 或 ui 初始化处引用字库 */ LV_FONT_DECLARE(mcu_font_40); LV_FONT_DECLARE(mcu_font_30); /* 给 label 设置字体:样式里的 text_font 字段 */ lv_obj_t *label = lv_label_create(screen); lv_obj_set_style_text_font(label, &mcu_font_40, LV_STATE_DEFAULT); lv_label_set_text(label, "你好,世界 Hello LVGL");

这里有一个细节:lv_obj_set_style_text_font 的第二个参数要传 lv_font_t 指针,而不是数组名;LV_FONT_DECLARE 展开后就是 extern 声明。设置完字体后,最好调用一次 lv_obj_refresh_style,强制 label 按新字体重新排版,否则切换字体时行宽和行高可能还是旧值。

4.4 字符串编码:UTF-8 一条道走到底

LVGL 内部默认按 UTF-8 解码字符串。代码里的“你好,世界”如果是用 GBK 编码保存,显示就会乱码。要注意三个地方:源文件保存编码设为 UTF-8;编译器不要对中文字符串做本地代码页转码;串口打印调试时不要用系统本地编码去猜字符串内容。MDK 环境下,我习惯把多语言文本集中到一个头文件里管理:

/* lang.h 示意:集中管理多语言文本,源文件务必存成 UTF-8 */ #define TXT_TITLE u8"环境监控" #define TXT_TEMPER u8"温度: %d.%d°C"

u8 前缀在 C11 里表示 UTF-8 窄字符串字面量,能帮助编译器严格按编码存储;如果编译器不支持,就保证源文件另存为 UTF-8。多语言切换时,只需要切换这一组宏的取值,label 内部走同一套解码逻辑,不用改 UI 代码。这一步做扎实了,后续中英文切换基本就是换字符串指针的事。

5. 常见问题与避坑:大数组、阿拉伯文、基线与方框

5.1 编译报 Flash 超限或 RAM 溢出,字库成了体积炸弹

现象:工程一编译,MDK 报 Flash 空间不足,或者 ZI/RW 段暴涨,程序跑起来动不动 HardFault。

原因:字库 .c 文件里的数组如果没加 const 修饰,会被放进可读写数据段而不是只读数据段,几十上百 KB 直接扣进 RAM;另一个常见原因是字符范围转太全,全字库在 512KB Flash 的 STM32F103 上根本放不下。

解决:先确认生成的字库数组声明带 const,并在 lv_conf.h 里合理设置 LV_MEM_SIZE,避免 label 的缓存把内存池吃光;再按第 3.3 节的账本裁剪字符范围,把用不到的区段剔除。我一般保留 0x20-0x7E 加中文常用字、日文假名,生僻字单独做成小字库按需加载。

5.2 阿拉伯文显示成散装字符,没有连写效果

现象:阿拉伯文、波斯文区域的语言字符串显示出来是零散的单字符,完全不是从右往左的连笔排版。

原因:阿拉伯文每个字母在不同位置有不同字形变体,生成字库时如果没把变体字形和整形支持开起来,LVGL 拿到的只有独立字形,自然连不起来。

解决:转换时确认字符范围覆盖 0x0600-0x06FF 及必要的扩展区,并开启转换工具里的阿拉伯文整形选项;LVGL 侧要做右到左排版,label 的 base_dir 需要设置为 RTL。单靠字体文件不整形,显示永远是散的。

5.3 中文与西文混排时文字“飘起来”,基线对不齐

现象:一行里中文显示正常,但英文、数字明显偏高或偏低,相邻行的高度也不一致。

原因:中文字体的 base_line 和西文字体不一致,转换时用了不同源字体或不同尺寸,LVGL 以字库里的 base_line 和 line_height 为准排版,于是出现基线漂移。

解决:多语言混排建议用同一个源字体做转换,并统一 --size 和基线设置;如果必须混两个字库,把两个字库的 line_height 和 base_line 调成一致数值。界面样式上统一设 text_line_space 也能缓解视觉差。

5.4 字符显示成方框,但字库里明明有该语言

现象:某些字符在 PC 上正常,烧到设备上变方框,比如生僻汉字或特殊符号。

原因:两种情况最常见。一种是字符范围没覆盖到该字符的具体码点,比如常用汉字区段之外还有扩展区;另一种是源字符串本身不是 UTF-8 编码,LVGL 解码出错误码点,查不到字库里任何一个字形。

解决:用串口把待显示字符串逐字节打印出来,对照 UTF-8 表确认码点;再去查字库 cmap 是否包含这个码点。我一般会在转换参数里把界面用到的字符单独列一个清单,而不是依赖默认字符集。

5.5 升级 LVGL 小版本后,字库崩溃或花屏

现象:原本正常的字库,换了一个 LVGL 小版本后文字花屏、程序重启、定位不到字形。

原因:LVGL 字库二进制格式在不同版本之间有调整,旧字库按旧布局生成,新版本读取时字段解释错位。

解决:升级 LVGL 时同步用新版本转换器重新生成字库文件,别拿旧字库硬跑;同时备份转换命令,方便一键重出。这也是为什么我在第 4 章强调把字符清单和命令留在工程目录里——换版本、换字体时,后悔药就是这几条命令。

6. 进阶:运行时内存摸底与动态换字库

字库选型定下来之后,还有两件事值得在工程里落地。第一是内存摸底。LVGL 渲染位图时会在内存池里开缓冲,如果同时显示多个 label 且字体很大,内存池可能被短暂撑满。我习惯在 main 循环里定时打印一次内存池状态:

/* 打印 LVGL 内存池状态,用于验证字库与渲染开销 */ lv_mem_monitor_t mon; lv_mem_monitor(&mon); printf("total=%lu free=%lu max_free=%lu frag=%u%%\n", (unsigned long)mon.total_size, (unsigned long)mon.free_size, (unsigned long)mon.free_biggest_size, mon.frag_pct);

如果 free_biggest_size 小于 4KB,说明连续可用内存吃紧,优先减小 label 缓区或换小字号字库。

第二是动态换字库。多语言切换的本质,是运行时把 label 的 text_font 从一个字库指针换到另一个字库指针。工程里可以维护这样一张表:

/* 动态语言切换用的小结构 */ typedef struct { const lv_font_t *font; const char *lang_tag; } lang_font_t; static const lang_font_t lang_table[] = { { &mcu_font_30, "zh-CN" }, { &mcu_font_40, "ar" }, };

切换语言时只换指针,重新 set_text,代价极小。需要注意:阿拉伯文切换后,整个容器的 flex 流向要一起翻转,否则仅换字体、文字方向还是错的。从那以后,我每次做多语言界面都会在架构里预埋这套指针表,而不是把字体写死在样式里;字体改版、语言新增时,改动面被压缩到一行表项。这份字库工程资源的用法其实就藏在两个 mcu_font_*.c 文件的结构里,把转换命令、字符清单和字库文件一起留档,后面维护会轻松很多,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 15:59:33

SQL子查询全面解析:分类、执行原理与性能优化实战

SQL子查询是很多人的分水岭。会用它的人,能把一个复杂的业务需求拆成几个简单的小问题,一层层拼出优雅的查询;不会用的人,遇到复杂过滤就只有join、join、再join,最后结果对不对自己都说不清。子查询最大的价值&#x…

作者头像 李华
网站建设 2026/10/22 15:59:02

CC2530 ZigBee观景台控制系统:从多节点组网到手机APP远程控制

简介:基于CC2530(ZigBee)设计的观景台控制系统资料包,面向物联网嵌入式学习者和智能照明/景观控制方向开发者。系统通过ZigBee多节点组网完成数据采集与上报,温湿度、光照强度经ESP8266上传至手机APP和Windows上位机;任一节点掉线…

作者头像 李华
网站建设 2026/10/22 15:56:57

Spring事务消息原理与可靠投递实战指南

1. 为什么“事务消息”不是加个 Transactional 就完事了?在某次电商订单系统重构中,我负责把老的同步扣库存逻辑改成异步解耦方案。最开始的想法特别朴素:用户下单成功后,在Transactional方法里直接发 RabbitMQ 消息,认…

作者头像 李华
网站建设 2026/10/22 15:56:46

DirectShow虚拟摄像头vcam_dshow-master:原理、编译与避坑指南

简介:面向Windows平台多媒体开发者的DirectShow虚拟摄像头实现工程,借助VCam方案将RTSP/HTTP等网络视频流封装为系统可识别的本地摄像头设备,适用于视频会议、直播推流等需要多路虚拟视频源的场景。压缩包共18个文件,其中cpp/h源码…

作者头像 李华
网站建设 2026/10/22 15:56:22

自演进模型路由X-Router:Agent调用成本降低50%的架构与实操

1. 从“模型路由”说起:为什么你的Agent账单总是降不下来 做过Agent应用的人都有一个共同的痛:调用大模型API的费用像流水一样往外淌。一个稍微复杂点的任务链路,可能涉及意图识别、知识检索、推理规划、工具调用、结果生成等好几个环节&…

作者头像 李华
网站建设 2026/10/22 15:56:16

企业级AI编排层:2026年Agent落地的关键拐点

最近跟踪AI基础设施动态时,有个细节让我盯了很久:某两个主营业务方向完全不同的全球级科技头部企业,在同一个自然周里先后放出了与Agent编排层直接相关的产品动作。一家在国内企业级市场深耕多年,另一家以云服务和软件生态见长。表…

作者头像 李华