4. 字体与文本优化:Unicode范围裁剪、自定义字体缓存、动态文本与静态文本取舍

字体和文本,往往是嵌入式UI里最容易被忽视的内存大户。我见过不少项目,UI做得很漂亮,一跑起来内存就爆了,查到最后发现是字体文件占了上百KB甚至几百KB。你想想看,一个中文字体动辄几MB,就算只提取常用字,也得几十KB起步。这一章我们就来聊聊怎么把字体这块的内存压到最低。

4.1 Unicode范围裁剪——别让字体文件“虚胖”

TouchGFX的字体生成工具TextConverter,默认会把字体文件里所有字符都打包进去。但你的产品真的需要显示所有汉字吗?

我做过一个智能家居面板,只需要显示“开关、温度、湿度、模式”这几个词。结果字体文件里包含了GB2312的几千个汉字。嗯,这就是典型的“虚胖”。

核心原则:只包含你真正用到的字符。

具体做法很简单:

  1. 在.ttf或.otf字体文件上,用FontForge或High-Logic FontCreator打开。
  2. 删除所有不需要的字符(只保留你UI里出现的文字)。
  3. 导出为裁剪后的字体文件。
  4. 在TouchGFX Designer中重新生成字体。

举个例子,一个只显示数字和冒号的时钟界面:

// 你只需要这些字符
"0123456789:"
// 字体文件从几MB -> 不到1KB

小技巧:如果你不确定哪些字符会被用到,可以在TouchGFX Designer里先跑一遍UI,然后查看生成的GeneratedFont.cpp,里面会列出实际使用的字符范围。拿这个列表去裁剪,最精准。

4.2 自定义字体缓存——按需加载,用完就扔

有些场景下,你没法提前知道所有字符。比如用户输入、动态加载的菜单项。这时候如果还把所有字符都预加载到内存里,就太浪费了。

TouchGFX提供了自定义字体缓存机制。说白了就是:用到了才加载,用完了就释放。

我记得有个项目需要显示用户自定义的WiFi名称,里面可能有各种特殊字符。我总不能把所有Unicode字符都塞进去吧?

实现思路是这样的:

  1. 创建一个FontCache对象。
  2. 设置缓存大小(比如4KB)。
  3. 当需要显示某个字符时,检查缓存里有没有。
  4. 没有就去Flash里读取,然后缓存起来。
  5. 缓存满了,就淘汰最久没用的字符。

代码骨架:

// 头文件
#include <touchgfx/FontCache.hpp>

// 创建缓存,大小4KB
FontCache cache(4096);

// 设置字体文件在Flash中的地址
cache.setFontFileAddress(0x08040000); // 假设字体存在这个地址

// 使用缓存渲染文本
TypedText typedText(T_MyDynamicText);
cache.setCacheFor(typedText.getFont());

注意:缓存大小不是越大越好。我建议根据你的UI复杂度来定。一般2KB~8KB就够了。太大反而会占用宝贵的RAM。

4.3 动态文本 vs 静态文本——选对了省一半内存

这是很多新手容易踩的坑。TouchGFX里文本有两种存在形式:

类型 存储位置 内存占用 修改方式 典型场景
静态文本 Flash(只读) 低(只存一次) 不可修改 固定标签、标题、提示
动态文本 RAM(可读写) 高(每个实例独立拷贝) 运行时修改 用户输入、实时数据、多语言切换

我见过一个项目,所有文本都用动态文本,包括那些永远不会变的“设置”、“返回”、“确认”。结果RAM被文本缓冲区占了一大半。其实这些完全可以用静态文本,零RAM开销。

我的建议:能用静态文本的,坚决不用动态文本。只有那些运行时必须修改的文本,才用动态文本。

具体取舍原则:

  • 固定标题、菜单项、按钮文字 → 静态文本
  • 数值显示(温度、时间、进度) → 动态文本(因为值会变)
  • 多语言切换 → 动态文本(或者用静态文本+多语言表切换)
  • 用户输入 → 动态文本(没得选)

4.4 实战:一个文本优化的前后对比

拿我之前做的一个智能门锁界面举例:

  • 界面有10个固定标签(“请输入密码”、“验证中”、“开锁成功”等)
  • 1个动态密码输入框(6位数字)
  • 1个动态状态提示(“剩余尝试次数:3”)

优化前:

  • 所有文本都用动态文本
  • 每个动态文本分配64字节缓冲区
  • 总共11个动态文本 → 704字节RAM
  • 字体文件包含全部ASCII + 常用汉字 → 约120KB Flash

优化后:

  • 10个固定标签改为静态文本 → 0字节RAM
  • 密码输入框用动态文本,缓冲区缩小到8字节(只存6位数字+结束符)
  • 状态提示用动态文本,缓冲区32字节
  • 字体文件只裁剪出用到的字符(数字、冒号、汉字“请输入密码验证中开锁成功剩余尝试次数”)→ 约8KB Flash

结果:RAM从704字节降到40字节,Flash从120KB降到8KB。效果非常明显。

额外提醒:动态文本的缓冲区大小要精确计算。比如显示“剩余尝试次数:3”,最多也就20个字符。给32字节足够了,别给128字节。我曾经看到有人给每个动态文本都分配256字节,结果界面一多,RAM直接爆了。

4.5 知识体系总览

下面这张图总结了字体与文本优化的核心逻辑:

字体与文本优化知识体系 字体与文本优化 Unicode范围裁剪 自定义字体缓存 动态/静态文本取舍 只包含用到的字符 使用FontForge裁剪 按需加载 LRU淘汰策略 静态文本→Flash 动态文本→RAM 核心目标 减少Flash占用(裁剪+静态文本) + 减少RAM占用(缓存+精确缓冲区) 最终实现:字体相关内存占用降低80%~95%

4.6 避坑指南

最后分享几个我踩过的坑:

  • 别用全量中文字体:一个GB2312字体文件至少200KB+,你的Flash可能连UI都放不下。一定要裁剪。
  • 动态文本缓冲区别贪大:精确计算最大字符数,多一个字节都是浪费。
  • 字体缓存别设太小:2KB以下的话,频繁换页会导致缓存抖动,反而降低性能。
  • 静态文本也能“动态”:如果你需要多语言切换,可以用多个静态文本+条件显示,比动态文本省内存。

我曾经在一个项目里,把所有文本都改成静态文本,然后通过切换TypedText的ID来实现多语言。结果RAM占用直接降了60%。虽然代码稍微复杂了一点,但效果非常值。