1、字体基础认知:像素与点阵的概念、字符编码(ASCII/UTF-8/Unicode)、字库与字模的关系
做嵌入式GUI,说白了就是跟「字」打交道。你想想看,一个屏幕亮起来,用户第一眼看到的是什么?不是图标,不是动画,是文字。文字显示不好,整个界面就垮了。
我刚开始做LVGL项目时,就踩过一个大坑——字库没选对,中英文混排直接乱码。后来花了整整两天排查,才发现是编码方式没统一。所以这一章,咱们先把基础打牢。
1.1 像素与点阵——字是怎么被机器认识的
先问个问题:计算机里存的是0和1,那「你」这个字怎么存?
答案很简单——用点阵。把字画在一个网格里,有墨水的格子填1,没墨水的填0。这个网格,就是点阵。
核心概念:像素是屏幕的最小发光单元,点阵是字符的数字化表示方式。一个点阵字模,本质上就是一个二维的0/1矩阵。
举个例子,一个8×8的点阵,能存64个bit。每个bit代表一个像素点。显示的时候,把这64个bit映射到屏幕上对应的像素位置,字就出来了。
我习惯把点阵分成两类:
- 等宽点阵:每个字占同样大小的格子。比如16×16、24×24。适合做中文字库,因为汉字本身就是方块字。
- 比例点阵:不同字符宽度不同。比如字母'i'窄,'W'宽。英文排版常用,看起来更自然。
这里有个坑——点阵越大,字越精细,但占的内存也越大。16×16的点阵,一个字模要32字节;32×32就要128字节。一套GB2312的6763个汉字,用32×32点阵存,光字模就接近1MB。在资源受限的MCU上,这可不是小数目。
我的经验:在STM32这类MCU上,我一般用16×16点阵做中文显示。够清晰,内存占用也能接受。如果屏幕分辨率高,可以考虑用24×24。
1.2 字符编码——从ASCII到Unicode的进化史
点阵解决了「怎么画」的问题,但没解决「画哪个字」的问题。计算机要显示字符,得先知道这个字是哪个。这就引出了字符编码。
我刚开始学嵌入式时,觉得编码这东西太抽象。后来做项目被乱码折磨过几次,才真正重视起来。说白了,编码就是给每个字符分配一个唯一的数字ID。
1.2.1 ASCII——老祖宗
ASCII(美国信息交换标准代码)是最早的编码方案。它用7个bit表示一个字符,总共128个。包含英文字母、数字、标点符号和一些控制字符。
| 范围 | 内容 | 例子 |
|---|---|---|
| 0x00-0x1F | 控制字符 | 0x0A(换行)、0x0D(回车) |
| 0x20-0x7E | 可打印字符 | 0x41('A')、0x61('a') |
| 0x7F | DEL(删除) | — |
ASCII的问题很明显——只能表示英文。中文、日文、阿拉伯文统统不支持。所以后来各国都搞了自己的编码方案。中国有GB2312、GBK、GB18030,日本有Shift-JIS,韩国有EUC-KR。乱成一锅粥。
避坑指南:我曾经在一个项目里,用GB2312编码的字符串直接传给LVGL的label控件,结果日文字符全部显示成乱码。原因就是LVGL默认用UTF-8,编码不匹配。所以,统一编码是第一步。
1.2.2 Unicode——一统江湖
Unicode的出现,就是为了解决编码混乱的问题。它给全世界所有字符分配了一个唯一的数字编号,叫「码点」(Code Point)。比如:
- 'A'的Unicode码点是U+0041
- '中'的Unicode码点是U+4E2D
- '😊'的Unicode码点是U+1F60A
Unicode本身只是一个字符集,不规定怎么存储。这就引出了具体的编码实现方案。
1.2.3 UTF-8——嵌入式首选
UTF-8是Unicode的一种变长编码方案。它用1到4个字节表示一个字符。特点是:
- ASCII字符(U+0000-U+007F)用1个字节,和ASCII完全兼容
- 拉丁文、希腊文等用2个字节
- 常用汉字(U+4E00-U+9FFF)用3个字节
- 极少用的字符用4个字节
为什么嵌入式首选UTF-8?原因有三:
- 节省空间:英文多的时候,比UTF-16省一半空间
- 无字节序问题:不像UTF-16分大端小端
- 兼容性好:老系统只认ASCII,UTF-8也能用
关键点:LVGL默认使用UTF-8编码。你在代码里写字符串,一定要确保源文件保存为UTF-8格式。否则显示出来就是一堆问号。
1.3 字库与字模——从编码到像素的桥梁
好,现在我们知道:字符有编码(比如'中'是U+4E2D),也有点阵(16×16的0/1矩阵)。那怎么把编码和点阵对应起来?
答案就是——字库(Font Library)。
字库本质上是一个查找表。输入是字符编码,输出是字模数据。我画个图帮你理解:
字库的存储方式主要有两种:
| 类型 | 特点 | 适用场景 | 缺点 |
|---|---|---|---|
| 点阵字库 | 直接存储每个字符的点阵数据 | MCU、低资源设备 | 放大后锯齿明显,占用空间固定 |
| 矢量字库 | 存储字符的轮廓曲线(贝塞尔曲线) | PC、手机、高分辨率屏幕 | 需要渲染引擎,占用计算资源 |
在LVGL中,默认使用的是点阵字库。但LVGL也支持通过FreeType等库加载矢量字体。我个人建议:在资源受限的嵌入式设备上,优先用点阵字库。如果屏幕分辨率高、MCU性能强,再考虑矢量字体。
1.4 三者关系——一张图说清楚
像素、编码、字库这三者,到底怎么配合工作的?我再用一张SVG图帮你理清:
你看,整个过程其实就四步。但每一步都可能出问题。我见过最典型的错误是:
- 源文件保存为ANSI编码,但LVGL期望UTF-8 → 中文乱码
- 字库里没有某个字符的字模 → 显示为方框或问号
- 字模大小和屏幕像素不匹配 → 字被拉伸或压缩
我的习惯:每次新建LVGL项目,第一件事就是检查源文件编码。用VS Code的话,右下角能看到编码格式。确保是UTF-8。然后检查字库是否包含所有要显示的字符。这两步做好,能省掉后面80%的排错时间。
1.5 本章小结
这一章我们讲了三个核心概念:
- 像素与点阵:字在计算机里的基本表示方式,一个0/1矩阵
- 字符编码:从ASCII到Unicode再到UTF-8,编码的演进和选择
- 字库与字模:编码到像素的映射关系,点阵字库和矢量字库的区别
这些概念看起来基础,但确实是整个字体显示体系的根基。后面章节我们会深入LVGL的字库加载、字体创建、缓存优化等实战内容。但如果你现在把编码和点阵的关系搞清楚了,后面会轻松很多。