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?原因有三:

  1. 节省空间:英文多的时候,比UTF-16省一半空间
  2. 无字节序问题:不像UTF-16分大端小端
  3. 兼容性好:老系统只认ASCII,UTF-8也能用

关键点:LVGL默认使用UTF-8编码。你在代码里写字符串,一定要确保源文件保存为UTF-8格式。否则显示出来就是一堆问号。

1.3 字库与字模——从编码到像素的桥梁

好,现在我们知道:字符有编码(比如'中'是U+4E2D),也有点阵(16×16的0/1矩阵)。那怎么把编码和点阵对应起来?

答案就是——字库(Font Library)。

字库本质上是一个查找表。输入是字符编码,输出是字模数据。我画个图帮你理解:

字库工作原理 字符编码 如:U+4E2D 字库 编码 → 字模 映射表 存储所有字符的点阵数据 字模数据 0/1 点阵矩阵 16×16 字模示例('中') 字模存储格式 • 每个点占1 bit • 16×16 = 256 bit = 32 字节 • 按行存储,每行2字节 • 1表示显示,0表示不显示

字库的存储方式主要有两种:

类型 特点 适用场景 缺点
点阵字库 直接存储每个字符的点阵数据 MCU、低资源设备 放大后锯齿明显,占用空间固定
矢量字库 存储字符的轮廓曲线(贝塞尔曲线) PC、手机、高分辨率屏幕 需要渲染引擎,占用计算资源

在LVGL中,默认使用的是点阵字库。但LVGL也支持通过FreeType等库加载矢量字体。我个人建议:在资源受限的嵌入式设备上,优先用点阵字库。如果屏幕分辨率高、MCU性能强,再考虑矢量字体。

1.4 三者关系——一张图说清楚

像素、编码、字库这三者,到底怎么配合工作的?我再用一张SVG图帮你理清:

像素 · 编码 · 字库 关系图 用户输入字符串 "你好 World" 编码解析 UTF-8 → Unicode码点 字库查找 码点 → 字模地址 像素渲染 字模数据 → 屏幕像素点 完整流程 1. 字符串 → 字节流 2. 字节流 → Unicode码点 3. 码点 → 字模数据 4. 字模 → 屏幕像素

你看,整个过程其实就四步。但每一步都可能出问题。我见过最典型的错误是:

  • 源文件保存为ANSI编码,但LVGL期望UTF-8 → 中文乱码
  • 字库里没有某个字符的字模 → 显示为方框或问号
  • 字模大小和屏幕像素不匹配 → 字被拉伸或压缩

我的习惯:每次新建LVGL项目,第一件事就是检查源文件编码。用VS Code的话,右下角能看到编码格式。确保是UTF-8。然后检查字库是否包含所有要显示的字符。这两步做好,能省掉后面80%的排错时间。

1.5 本章小结

这一章我们讲了三个核心概念:

  • 像素与点阵:字在计算机里的基本表示方式,一个0/1矩阵
  • 字符编码:从ASCII到Unicode再到UTF-8,编码的演进和选择
  • 字库与字模:编码到像素的映射关系,点阵字库和矢量字库的区别

这些概念看起来基础,但确实是整个字体显示体系的根基。后面章节我们会深入LVGL的字库加载、字体创建、缓存优化等实战内容。但如果你现在把编码和点阵的关系搞清楚了,后面会轻松很多。