4、字体压缩技术:点阵字体 vs 矢量字体,字库裁剪与子集化

字体在嵌入式GUI里,一直是个让人又爱又恨的东西。爱的是它决定了界面好不好看,恨的是它太占地方了。我见过不少项目,前期UI设计得花里胡哨,一到移植阶段,发现Flash塞不下,只能回头砍字体——那叫一个心疼。

今天咱们就聊聊字体压缩这件事。说白了,就是怎么让字体在保持可读性的前提下,把体积降下来。核心就两条路:选对字体格式,以及做字库裁剪。

4.1 点阵字体 vs 矢量字体:选哪个?

先搞清楚这两个东西的本质区别。点阵字体,就是每个字都存一张像素图。矢量字体,存的是数学曲线,渲染时再算出来。

对比维度 点阵字体 矢量字体
存储方式 每个字模的像素矩阵 贝塞尔曲线 + 控制点
缩放效果 放大后锯齿明显 任意缩放不失真
渲染速度 直接拷贝像素,极快 需要光栅化,较慢
内存占用 固定大小,与字号成正比 字库小,但渲染需要临时缓冲区
适用场景 低分辨率屏、MCU、固定字号 高分辨率屏、MPU、动态字号

我个人习惯是:如果屏幕分辨率在320x240以下,或者主控是Cortex-M系列不带硬件浮点,我首选点阵字体。为什么?因为省事。点阵字体不需要解码,直接memcpy到显存就完事了。我曾经在一个STM32F103的项目上,用12x12的点阵显示中文,整个字库才几十KB,跑得飞快。

但如果你用的是Cortex-A系列,或者屏幕分辨率上了480x800,那就老老实实用矢量字体吧。否则点阵字体会膨胀到你怀疑人生——每个字号都要存一套,16px、18px、20px……存几套下来,Flash就没了。

核心结论:资源受限选点阵,追求效果选矢量。没有绝对的好坏,只有合不合适的场景。

4.2 字库裁剪:别把整个字库都塞进去

很多新手犯的错误,就是把Windows上的字体文件直接扔到嵌入式设备里。一个完整的宋体字库,动辄十几MB,MCU根本吃不消。其实你仔细想想,你的设备上真的需要显示所有汉字吗?

字库裁剪,说白了就是只保留你真正用到的字符。我管它叫「按需取字」。具体怎么做?

  • 静态裁剪:编译前确定好所有会用到的字符,生成一个最小字库。
  • 动态裁剪:运行时按需加载,但需要文件系统支持,适合资源稍丰富的平台。
  • 子集化:从完整字库中提取指定字符,重新打包成新的字体文件。

我记得有一次做智能家居面板,界面就显示几个数字和单位符号。客户非要支持全字库,说「万一以后要显示中文呢?」我跟他算了一笔账:全字库2MB,芯片Flash总共512KB。最后他妥协了,我们只裁剪了100多个常用字,字库缩到8KB。嗯,有时候技术问题其实是沟通问题。

小技巧:用Python脚本扫描源码中所有字符串,去重后生成字符列表,再用FontTools库做子集化。整个过程自动化,每次编译前跑一遍就行。

4.3 字库子集化实战:用工具说话

这里我以常用的FreeType + 自定义工具链为例,演示一下子集化的流程。假设我们只需要显示「温度湿度设置」这六个字。

# 使用 fonttools 做子集化
from fontTools.ttLib import TTFont
from fontTools.subset import Subsetter

font = TTFont('SourceHanSansSC-Regular.otf')
subsetter = Subsetter()
subsetter.populate(text='温度湿度设置')
subsetter.subset(font)
font.save('subset.otf')

这段代码跑完,原本5MB的思源黑体,直接缩到不到10KB。你想想看,这差距有多大。

但要注意一点:子集化后的字体文件,只能显示你指定的那几个字。如果运行时遇到没包含的字符,渲染结果就是空白或者方框。所以一定要确保字符列表覆盖全面。

避坑指南:我曾经在一个项目里,子集化时漏掉了空格和冒号。结果界面上所有时间显示都挤在一起,排查了半天才发现是字体里没有空格字符。所以记得把标点符号、数字、空格都加进去。

4.4 点阵字体的压缩技巧

如果你决定用点阵字体,压缩空间其实更大。点阵字模本质上是二值图像,每个像素只占1bit。但很多新手直接存成字节数组,白白浪费了空间。

常用的压缩方法:

  • RLE(游程编码):连续相同的像素合并记录,适合大面积空白的中文字符。
  • 差分编码:只记录相邻行的差异,适合笔画较细的字体。
  • 字典压缩:把重复出现的字模片段做成字典,适合有大量相同偏旁部首的汉字。

我试过用RLE压缩16x16的点阵字库,压缩率大概在40%~60%之间。解压速度也很快,就是逐行扫描一下,对MCU来说几乎不占时间。

4.5 本章知识体系

下面这张图,把字体压缩的核心决策路径画出来了。你可以对照自己的项目,走一遍这个流程。

字体压缩决策流程图 字体压缩决策流程 开始:评估资源 Flash > 1MB?RAM > 256KB? 是 否 矢量字体 + 子集化 点阵字体 + RLE压缩 裁剪字符列表 生成最小字库 说明:无论选哪种字体格式,裁剪字符列表都是必须的步骤。子集化工具可以自动完成提取。 点阵字体建议配合RLE压缩,矢量字体建议配合子集化工具。

4.6 总结一下

字体压缩这件事,说白了就是做减法。选对格式省一半,裁剪字符再省一半。我见过太多项目在字体上栽跟头,其实只要提前规划好,完全可以把字库控制在几十KB以内。

最后提醒一句:不管你用哪种方案,一定要在项目初期就确定字体策略。等UI都画好了再改字体,那代价可就大了。