3. 内存类型与占用来源
字体文件,往往是 TouchGFX 工程里最容易被忽视的「内存刺客」。我见过不少项目,UI 还没做几页,Flash 先被字体吃掉了大半。你想想看,一套完整的 GB2312 字库,少说也得几百 KB。要是用了全字库的矢量字体,那更是直接奔着 MB 级别去了。
但话说回来,你的产品真的需要显示几千个汉字吗?大部分嵌入式设备的交互界面,翻来覆去就那么几十个常用词。剩下的那些生僻字,说白了就是白白占着 Flash 不干活。
这一章,我们就来聊聊怎么给字体「瘦身」。核心思路就两条:剔除生僻字,以及生成自定义字体子集。
3.1 字体为什么这么「胖」?
先搞清楚一个问题:一个字符在字库里占多少空间?
对于 TouchGFX 常用的 位图字体(.bdf/.fon 转换而来),每个字符都是一个固定大小的点阵图。假设你用了 16x16 的字体,每个字符就是 32 字节。GB2312 有 6763 个汉字,光汉字部分就是 6763 × 32 ≈ 211 KB。再加上 ASCII、标点符号、字体头信息,轻松突破 250 KB。
如果是 矢量字体(.ttf/.otf 转换),虽然单个字符的轮廓数据比点阵更紧凑,但一套完整的矢量字库动辄 3-5 MB。就算 TouchGFX 只加载用到的字形,字库的索引表、度量信息、hinting 数据依然会占用大量 RAM。
核心结论: 字体占用的 Flash/RAM 大小,与「包含的字符数量」和「每个字符的复杂度」成正比。减少字符数量,是最直接有效的瘦身手段。
3.2 第一步:分析你的 UI 文本
别急着动手删字。先做一次「文本审计」。
我个人习惯的做法是:把整个工程里所有用到的字符串,全部提取到一个文本文件里。然后写个小脚本,统计出所有不重复的字符。
举个例子,一个温控器的界面,可能就用到这些字:
当前温度 设定值 模式 开关 制冷 制热 送风 自动 定时 风速 高 中 低 确认 取消 返回 设置 菜单 报警 故障 查询 历史 记录 清除 退出 开机 关机 运行 停止 暂停 恢复 默认 保存 加载 版本 序列号 日期 时间 星期 年 月 日 时 分 秒 摄氏度 华氏度 百分比 开 关 是 否 有 无 大 小 多 少 好 坏 新 旧 进 出 上 下 左 右 前 后 内 外 加 减 乘 除 等于 大于 小于 不等于 并且 或者 非 真 假 空 满 正常 异常 准备 就绪 等待 完成 失败 成功 错误 警告 提示 信息 输入 输出 通道 端口 地址 编号 类型 状态 参数 配置 校准 测试 诊断 维护 升级 重启 复位 锁定 解锁 允许 禁止 启用 禁用 使能 失能 手动 自动 本地 远程 主 从 单 双 三 四 五 六 七 八 九 十 百 千 万 亿 点 负 正 零 一 二 三 四 五 六 七 八 九 十 百 千 万 亿
数一数,不重复的汉字可能也就 200-300 个。加上数字、英文字母、标点符号,总共不超过 400 个字符。
小技巧: 可以用 Python 的 set() 函数快速去重。把提取出的字符串丢进去,一行代码就能拿到所有不重复字符的列表。
3.3 第二步:生成自定义字体子集
拿到字符列表后,下一步就是生成只包含这些字符的字体文件。
TouchGFX 官方推荐用 Font Converter 工具。但说实话,那个工具对自定义子集的支持比较有限。我更推荐用 FontForge 配合脚本,或者用 Python + fonttools 库来生成。
下面是我常用的一个流程:
- 准备原始字体文件:选择一个开源的、授权友好的字体,比如 Noto Sans CJK 或思源黑体。
- 准备字符列表:把上一步分析出的不重复字符,保存为一个文本文件,每行一个字符。
- 运行子集化脚本:用 fonttools 的
pyftsubset命令,生成只包含指定字符的子集字体。
命令示例:
pyftsubset NotoSansSC-Regular.ttf \
--text-file=my_chars.txt \
--output-file=MyFont_Subset.ttf \
--layout-features='*' \
--glyph-names \
--symbol-cmap \
--legacy-cmap \
--notdef-glyph \
--notdef-outline \
--recommended-glyphs \
--name-IDs='*' \
--name-legacy \
--drop-tables= \
--no-subset-tables+=GPOS,GSUB,GDEF
注意: 上面的命令保留了 GPOS/GSUB 表,用于支持字符间距调整和连字。如果你的界面不需要这些高级排版特性,可以去掉 --no-subset-tables+=GPOS,GSUB,GDEF,进一步减小文件体积。
3.4 第三步:在 TouchGFX 中使用子集字体
生成的子集字体文件(.ttf),需要先通过 TouchGFX 的 Font Converter 转换成 .fon 或 .bdf 格式,然后才能在工程里使用。
具体步骤:
- 打开 TouchGFX Designer,在 Texts 面板里,点击 Fonts 选项卡。
- 点击 Add Font,选择你生成的子集 .ttf 文件。
- 设置字体大小、样式(Regular/Bold/Italic)。
- 在 Character Range 里,选择 Custom,然后粘贴你的字符列表。
- 点击 Generate,TouchGFX 会自动生成只包含这些字符的位图字体。
嗯,这里要注意:TouchGFX Designer 的 Custom 字符范围功能,有时候会漏掉一些字符。我遇到过几次,明明在列表里写了「测」字,生成的字体里却没有。后来我学乖了,每次生成后都会写个小程序,遍历所有 UI 文本,检查每个字符是否都能在生成的字体里找到。找不到的就记录下来,手动补上。
3.5 效果对比:瘦身前 vs 瘦身后
拿我做过的一个智能家居中控屏项目来举例:
| 指标 | 瘦身前(全字库) | 瘦身后(自定义子集) | 节省比例 |
|---|---|---|---|
| 字体文件大小(.ttf) | 4.2 MB | 68 KB | 98.4% |
| TouchGFX 生成的 .fon 大小 | 1.8 MB | 32 KB | 98.2% |
| 运行时 RAM 占用(字体缓存) | ~120 KB | ~8 KB | 93.3% |
| 包含字符数 | ~7000 | 312 | 95.5% |
你看,从 4.2 MB 瘦到 68 KB,效果非常明显。而且 UI 上完全看不出区别——因为用到的字符一个都没少。
3.6 避坑指南
字体瘦身虽然效果好,但有几个坑得提前避开:
- 别忘了数字和标点:很多人只关注汉字,结果生成的字体里没有「.」、「:」、「%」这些符号,界面上一堆方框。
- 考虑多语言场景:如果你的产品要出口,记得把目标语言的字符也加进去。我曾经有个项目,中文界面跑得好好的,切换到英文后发现「™」符号显示不出来——因为当初只考虑了 ASCII 字符。
- 保留必要的空格和换行符:0x20(空格)、0x0A(换行)、0x0D(回车)这些控制字符,一定要包含在子集里,否则文本排版会乱。
- 测试所有 UI 页面:生成新字体后,务必在模拟器或真机上跑一遍所有界面。有些字符可能在代码里是动态拼接的,比如「设备 1」、「通道 2」里的数字,容易被遗漏。
我的经验: 在项目初期就建立一份「UI 文本字符清单」,每次新增文本时同步更新。这样后期做字体子集化时,就不会漏字符了。
3.7 知识体系:字体瘦身核心逻辑
下面这张图,概括了字体瘦身的完整流程和关键决策点:
说白了,字体瘦身就是一次「精准打击」——只保留你真正需要的字符,把那些永远不会用到的生僻字统统扔掉。这个思路不仅适用于 TouchGFX,任何嵌入式 GUI 框架都能用。
好了,这一章的内容就到这里。记住:字体文件的大小,往往决定了你的 Flash 够不够用。花半小时做一次字体子集化,可能比你在其他地方优化一个月代码的效果都好。