1. 字体资源概述:为什么需要压缩字体?

各位做嵌入式GUI的同行,咱们开门见山。你辛辛苦苦调好了一个界面,中文字体一放进去,Flash直接爆了。这种事我遇到过不下五次。说白了,字体资源是嵌入式GUI里最容易被低估的“存储杀手”。

今天这章,我们就来聊聊字体资源的本质。为什么它这么占地方?TouchGFX是怎么处理字体的?以及——我们到底为什么要费劲去压缩它。

1.1 字体在嵌入式GUI中的角色

字体不是“显示文字”那么简单。它是GUI的骨架。你想想看,一个没有文字的界面,跟一张图片有什么区别?按钮上的标签、传感器的读数、菜单的选项……这些全得靠字体来承载。

在TouchGFX里,字体承担了三个核心角色:

  • 信息载体:所有用户能读懂的文本内容,都得通过字体渲染出来
  • 视觉元素:字体的粗细、字号、风格直接影响UI的美观度
  • 资源消耗大户:尤其是中文字体,动辄几MB甚至十几MB

一个残酷的现实:在MCU上,Flash资源通常只有几MB到十几MB。一套完整的中文字体(GB2312,约6763个汉字),不压缩的情况下,光字形数据就能吃掉2~4MB。你剩下的空间还要放代码、图片、其他资源……

所以,字体压缩不是“锦上添花”,而是“不得不做”。

1.2 TouchGFX字体引擎的工作原理

要理解为什么需要压缩,得先搞清楚TouchGFX是怎么处理字体的。我刚开始用TouchGFX时,也踩过不少坑,后来才慢慢摸清它的脾气。

TouchGFX的字体引擎,本质上是一个字形渲染管线。它不直接使用操作系统的字体服务,而是自己管理一套预渲染好的字形数据。流程大致是这样的:

  1. 字体文件输入:你指定一个.ttf或.otf文件
  2. 字形提取:TouchGFX的字体生成工具(TextConverter)从字体文件中提取你需要的字符
  3. 栅格化:将矢量字形转换成固定大小的位图(像素点阵)
  4. 打包存储:将位图数据按特定格式打包成二进制资源
  5. 运行时渲染:MCU上的字体引擎读取打包数据,直接绘制到屏幕上

这里有个关键点:TouchGFX不实时做矢量渲染。它用的是预栅格化的位图。好处是运行时性能极高,坏处是——每个字号、每种样式都得单独生成一套位图数据。这就是字体资源膨胀的根本原因。

我的经验:如果你在项目中用了多个字号(比如12px、16px、24px),每个字号都会生成独立的字形位图。我曾经接手过一个项目,设计师用了7种字号,结果字体资源占了整个固件的一半。后来我建议统一到3种字号,资源直接砍掉60%。

1.3 为什么需要压缩字体?

好,现在我们来回答核心问题:为什么非压缩不可?

我列几个数字,你感受一下:

字体类型 字符数 未压缩大小(16px) 压缩后大小 节省比例
ASCII(英文字母+数字) 95 ~30KB ~15KB 50%
GB2312(常用中文) 6763 ~2.8MB ~1.2MB 57%
GBK(扩展中文) 21886 ~9MB ~3.5MB 61%
Unicode CJK(完整中文) 20992+ ~12MB ~4.5MB 62%

看到没?压缩后能省一半以上。对于Flash只有2MB的MCU来说,这1~2MB的差距,可能就是“能跑”和“跑不了”的区别。

压缩的好处不止是省空间:

  • 减少Flash占用:最直接的好处,省下来的空间可以放更多功能代码
  • 降低烧录时间:固件小了,生产线上烧录速度更快
  • 减少OTA升级包大小:如果产品支持远程升级,字体压缩能显著减小升级包体积
  • 提高缓存效率:压缩后的字形数据在解压后,更容易被缓存命中

注意:压缩不是免费的午餐。运行时解压需要消耗CPU周期和RAM。如果你的MCU主频很低(比如几十MHz),或者RAM非常紧张(比如只有几十KB),就需要在压缩率和解压开销之间做权衡。我曾在STM32F103上做过测试,LZW压缩的解压时间大约增加5~10%的渲染开销,但Flash节省了40%。这个取舍,值不值,得看你的项目。

1.4 TouchGFX支持的压缩算法

TouchGFX内置了几种压缩方式,我简单列一下:

压缩方式 压缩比 解压速度 RAM开销 适用场景
无压缩(RAW) 1:1 最快 无 Flash充足,追求极致性能
RLE(游程编码) 1.5:1 ~ 3:1 快 低 字形简单、大面积空白多的字体
LZW(Lempel-Ziv-Welch) 2:1 ~ 4:1 中等 中等 通用场景,平衡压缩率和性能
LZ4 1.5:1 ~ 2.5:1 极快 低 对渲染帧率要求高的场景

我个人习惯是:默认用LZW。它在压缩率和解压速度之间取得了不错的平衡。如果项目对性能要求极高,我会换成LZ4。如果Flash实在不够用,才会考虑RLE——但RLE对复杂字形的压缩效果一般。

1.5 字体资源压缩的核心逻辑

说了这么多,我们来画一张图,把整个逻辑串起来。

TouchGFX字体资源压缩核心逻辑 字体文件 (.ttf/.otf) 字形提取(指定字符集) 栅格化(位图生成) 压缩决策(RLE / LZW / LZ4 / 无压缩) 打包为二进制资源 关键影响因素 • 字符集大小 • 字号/DPI • 字体复杂度 • 压缩算法选择 压缩收益 • Flash节省40~60% • OTA包体积减小 • 烧录时间缩短

这张图把整个流程串起来了。从字体文件开始,经过字形提取、栅格化,到压缩决策,最后打包成二进制资源。每一步都有优化的空间,但压缩这一步,是性价比最高的。

1.6 一个真实的项目案例

最后,我分享一个真实案例。去年我做了一个智能家居面板的项目,主控是STM32H750,Flash 2MB,RAM 1MB。UI需要显示中文菜单和传感器数据。

一开始,设计师选了思源黑体,用了16px和20px两个字号,字符集覆盖GB2312。TextConverter生成出来的字体资源,足足3.2MB。而我的固件总大小限制在1.8MB以内(还要留空间给Bootloader和OTA)。

怎么办?

我做了三件事:

  1. 精简字符集:实际用到的汉字只有1200多个,我手动提取了这些字符,而不是用完整的GB2312
  2. 启用LZW压缩:在TouchGFX的字体配置里,把压缩方式从None改成LZW
  3. 合并字号:把16px和20px统一成18px,用缩放来适配

结果:字体资源从3.2MB降到了680KB。省下来的空间,我甚至多加了两个动画页面。

避坑指南:我曾经犯过一个错误——为了极致压缩,选了RLE算法,结果发现某些复杂汉字(比如“龘”)渲染出来有锯齿。后来才意识到,RLE对笔画密集的字形压缩效果差,而且解压后质量会下降。所以,压缩算法的选择一定要结合你的实际字形来测试,别想当然。