1. 字体基础概念:点阵字体与矢量字体的区别、字库结构解析、字符编码在嵌入式中的应用
做嵌入式GUI,绕不开字体。我刚开始搞这块时,也以为字体就是“把字画出来”那么简单。结果第一个项目就栽了跟头——屏幕显示中文全是乱码,查了三天才发现是编码没对齐。今天咱们就把字体这块地基打牢。
1.1 点阵字体 vs 矢量字体
先说点阵字体。说白了,就是用一堆小方格拼出字形。每个像素要么亮要么灭,简单粗暴。
点阵字体的核心特点:
- 每个字符对应一个固定大小的位图矩阵
- 显示速度快,直接内存映射即可
- 缩放会锯齿严重,放大就糊
- 存储空间固定,16×16点阵一个汉字占32字节
矢量字体就不一样了。它存的是轮廓的数学描述——贝塞尔曲线、直线段这些。我记得第一次用FreeType渲染一个24pt的宋体,看到那个平滑的曲线,确实被惊艳到了。
| 对比项 | 点阵字体 | 矢量字体 |
|---|---|---|
| 存储方式 | 像素位图 | 数学曲线描述 |
| 缩放效果 | 锯齿明显 | 平滑无损 |
| 渲染速度 | 极快(直接拷贝) | 较慢(需计算轮廓) |
| 内存占用 | 固定,随字号增大 | 相对固定 |
| 典型应用 | 低端MCU、OLED屏 | 高端MPU、Linux设备 |
| 常用工具 | PCtoLCD、FontCreator | FreeType、stb_truetype |
我的经验:在STM32F103这类资源紧张的MCU上,我一般用12×12或16×16点阵。跑Linux的板子,比如全志V3s,直接上FreeType渲染矢量字体,效果完全不一样。
1.2 字库结构解析
字库不是简单地把所有字堆在一起。它有一套组织方式。我拆过一个点阵字库,结构大概是这样的:
// 点阵字库文件结构示例
typedef struct {
uint16_t font_height; // 字高
uint16_t font_width; // 字宽
uint16_t char_count; // 字符总数
uint16_t first_char; // 起始编码
uint8_t data[0]; // 位图数据
} FontHeader;
// 每个字符的位图数据排列
// 16×16点阵:每行2字节,共16行 = 32字节
// 字节内高位对应左侧像素
矢量字库的结构更复杂。TrueType字体文件里,有cmap表(字符映射)、glyf表(字形轮廓)、head表(全局信息)等等。我建议初学者先别钻太深,会用FreeType的API就够了。
注意:点阵字库的字节序问题。我踩过坑——在ARM大端和小端之间移植字库,显示全乱了。后来统一用小端存储,并在加载时做字节序判断。
1.3 字符编码在嵌入式中的应用
编码这块,是嵌入式GUI最容易出问题的地方。咱们一个一个说。
ASCII
7位编码,0x00-0x7F,128个字符。英文字母、数字、标点。在嵌入式里,ASCII基本不占空间,一个字节一个字符。但只能处理英文。
GB2312
国标简体中文编码。一个汉字占两个字节,范围从0xA1A1开始。我最早做中文菜单时用的就是GB2312。但有个问题——它和ASCII混在一起时,程序得判断当前字节是ASCII还是汉字的一部分。
// GB2312字符判断示例
int is_gb2312_char(uint8_t byte1, uint8_t byte2) {
// 第一个字节在0xA1-0xF7之间
// 第二个字节在0xA1-0xFE之间
return (byte1 >= 0xA1 && byte1 <= 0xF7) &&
(byte2 >= 0xA1 && byte2 <= 0xFE);
}
Unicode
统一编码,一个字符一个码点。嵌入式里常用UTF-8编码——变长存储,ASCII字符还是1字节,中文变成3字节。我建议新项目直接用UTF-8,省去编码转换的麻烦。
| 编码方式 | 英文字符 | 中文字符 | 嵌入式适用性 |
|---|---|---|---|
| ASCII | 1字节 | 不支持 | 仅英文场景 |
| GB2312 | 1字节 | 2字节 | 中文菜单、低端设备 |
| UTF-8 | 1字节 | 3字节 | 通用、推荐新项目 |
| UTF-16 | 2字节 | 2字节 | Windows CE、部分RTOS |
核心建议:如果你的MCU有256KB以上Flash,直接上UTF-8+矢量字体。如果资源紧张,用GB2312点阵字库,但要做好编码转换层。
1.4 知识体系总览
下面这张图,把本章的核心知识点串起来了。我画图时习惯把“编码”放在中间——因为它是连接“字库”和“显示”的桥梁。
这张图里,编码是核心枢纽。左边是点阵体系,右边是矢量体系,下面是字库结构。你想想看,不管用哪种字体,最终都要通过编码找到对应的字形数据。这个逻辑搞清楚了,后面做字体显示校准就顺了。
一个小技巧:做字库时,我习惯把ASCII和中文分开存储。ASCII用8×16点阵,中文用16×16点阵。这样英文显示紧凑,中文清晰,内存也省。