4. 字体与字符集配置:理解Unicode与字符集、为不同语言配置字体、处理字体回退、优化字体文件大小

做多语言界面,说白了就是让屏幕能正确显示各种文字。中文、日文、韩文、阿拉伯文……每种语言都有自己的字符集。我刚开始接触TouchGFX时,以为只要把字库文件扔进去就行,结果一运行,满屏的方框。嗯,那感觉,就像你写好了代码却忘了加分号。

这一章,我们就来彻底搞定字体和字符集。我会把我在项目中踩过的坑、总结的经验,都摊开来讲。

4.1 理解Unicode与字符集

先问一个问题:为什么你的屏幕显示不了中文?

因为你的字体文件里没有中文字符的“形状”。每个字符在计算机里都有一个唯一的编号,这个编号就是Unicode。而字体文件,就是一张“编号→形状”的映射表。你想想看,如果表里没有“中”这个编号,那屏幕当然只能画个方框了。

TouchGFX支持Unicode,这是好事。但Unicode本身不包含字形,它只是标准。真正干活的是字体文件。常见的字符集编码方式有几种:

编码方式 特点 典型应用
UTF-8 变长编码,兼容ASCII,存储效率高 Web、现代嵌入式系统
UTF-16 固定2字节或4字节,Windows和Java常用 Windows API、Java内部
UTF-32 固定4字节,简单但浪费空间 内部处理,极少用于存储

我个人习惯在TouchGFX项目里用UTF-8编码的源文件。为什么呢?因为UTF-8对ASCII字符只占1字节,而中文占3字节。虽然比GB2312的2字节多了一点,但胜在通用——你不需要为每种语言单独切换编码。

小提示: 在TouchGFX Designer中,所有文本资源默认使用UTF-8。如果你从别处复制文本,记得确认源文件的编码。我曾经因为从Word复制了一段带弯引号的文本,结果编译出来全是乱码——Word的弯引号不是标准ASCII。

4.2 为不同语言配置字体

配置字体,不是简单地把一个.ttf文件拖进去就完事了。你需要考虑三件事:字体格式、字符覆盖范围、以及字体渲染效果。

4.2.1 字体格式选择

TouchGFX支持两种字体格式:位图字体和矢量字体。

  • 位图字体:每个字符预先渲染成像素点阵。优点是渲染速度快,缺点是放大后模糊,且文件体积随字符数线性增长。
  • 矢量字体:存储字符的轮廓曲线。优点是缩放不失真,文件体积相对较小,但渲染时需要计算,稍微慢一点。

我在一个低端MCU项目上用过位图字体,因为那个芯片没有FPU,矢量字体的贝塞尔曲线计算太慢了。但如果你用的是Cortex-M4以上,放心用矢量字体,性能完全扛得住。

4.2.2 中文字体配置

中文字体是最大的坑。一个完整的中文字体文件,比如宋体或黑体,动辄十几MB。你的MCU Flash才几MB,根本放不下。

怎么办?只包含你需要的字符。

TouchGFX的字体配置工具允许你指定一个“字符范围”。比如,你只需要显示“设置”、“确定”、“取消”这几个词,那就只把这几个字加进去。我见过有人把整个GB2312字符集都加进去,结果字体文件占了8MB,Flash直接爆了。

核心原则: 只包含界面中实际出现的字符。不要贪多,不要想着“万一以后用得到”。以后用得到的时候再加。

配置步骤很简单:

  1. 在TouchGFX Designer中,打开“Texts”面板。
  2. 选择你需要配置的字体,点击“Edit”。
  3. 在“Character Set”选项卡中,选择“Custom”,然后手动输入你需要的字符。
  4. 或者,选择“Range”,指定Unicode范围。比如中文常用字在0x4E00-0x9FFF之间。

我个人更推荐“Custom”方式。因为“Range”方式会把整个区间的字符都包含进去,哪怕你只用了其中10个字。比如0x4E00-0x9FFF包含了2万多个汉字,而你的界面可能只需要200个。

4.2.3 日文字体配置

日文比中文更复杂。它包含三种字符集:平假名、片假名、以及汉字(日文汉字)。平假名和片假名加起来不到200个字符,但日文汉字和中文汉字有重叠,也有差异。

比如“画”这个字,中文和日文写法一样,但Unicode编码不同。中文的“画”是U+753B,日文的“画”是U+753B(同一个编码)。但有些字,比如“働”,中文里没有,日文里有。

所以,配置日文字体时,我建议:

  • 平假名和片假名:用日文字体文件,比如Noto Sans JP。
  • 日文汉字:如果和中文汉字重叠,可以用中文字体;如果不重叠,必须用日文字体。

我曾经犯过一个错误:用中文字体显示日文界面,结果“働”字显示不出来,用户反馈说“这个按钮没有文字”。后来我加了一个日文字体作为回退,问题才解决。

4.3 处理字体回退(Font Fallback)

字体回退,说白了就是“这个字体没有这个字符,那就换另一个字体试试”。

TouchGFX支持字体回退机制。你可以在一个文本区域里指定多个字体,按优先级排列。当第一个字体找不到某个字符时,自动尝试第二个字体,以此类推。

配置方法:

  1. 在TouchGFX Designer中,选中一个文本控件。
  2. 在属性面板中,找到“Font”设置。
  3. 点击“+”号,添加多个字体。排在前面的优先级高。

举个例子:

字体列表:
1. NotoSansSC_Regular(中文字体,包含常用汉字)
2. NotoSansJP_Regular(日文字体,包含平假名、片假名、日文汉字)
3. NotoSans_Regular(英文字体,包含ASCII字符)

当显示“Hello 世界 こんにちは”时:

  • “Hello” → 字体1没有这些英文字符?不对,中文字体通常也包含ASCII。所以字体1能显示。
  • “世界” → 字体1能显示。
  • “こんにちは” → 字体1没有平假名,回退到字体2。字体2能显示。
注意: 字体回退会增加渲染时间。因为每次遇到一个字符,都要检查当前字体是否包含它。如果回退链太长,性能会下降。我建议回退链不要超过3层。

4.4 优化字体文件大小

字体文件是嵌入式系统里的“体积大户”。优化字体文件,是每个嵌入式UI工程师的必修课。

4.4.1 只包含需要的字符

这是最有效的方法。前面已经说过,不再重复。但我要强调一点:不要用“All Characters”选项。那个选项会把字体文件里所有字符都包含进去,哪怕你只用了1%。

4.4.2 使用子集化工具

TouchGFX自带的字体工具已经做了子集化。但如果你需要更精细的控制,可以用第三方工具,比如FontForge或pyftsubset。

举个例子,用pyftsubset提取一个字体文件中你需要的字符:

pyftsubset NotoSansSC-Regular.ttf \
    --text="设置确定取消返回保存删除编辑添加" \
    --output-file=NotoSansSC-Subset.ttf

这个命令只保留“设置确定取消返回保存删除编辑添加”这12个字符。生成的字体文件可能只有几KB。

4.4.3 降低字体精度

矢量字体在渲染时,需要将轮廓曲线转换成像素点阵。这个转换的精度越高,渲染效果越好,但字体文件也越大。

TouchGFX允许你设置“Glyph Precision”。默认是“High”,你可以改成“Medium”或“Low”。对于小字号(比如12px以下),降低精度几乎看不出区别,但文件体积能减少30%以上。

精度等级 文件体积 渲染质量 适用场景
High 100% 最佳 大标题、高DPI屏幕
Medium ~70% 良好 正文、按钮文字
Low ~50% 可接受 小字号、低分辨率屏幕

4.4.4 合并字体文件

如果你有多个字体文件,比如一个中文字体和一个英文字体,可以考虑合并成一个。但要注意:合并后的字体文件体积是两个文件之和,而且管理起来更复杂。我个人不推荐合并,除非你的Flash空间实在紧张到按字节算。

我的经验: 在项目初期,先用完整字体文件开发,确保所有字符都能显示。到了优化阶段,再逐个检查哪些字符是多余的,然后生成子集字体。这样既保证了开发效率,又控制了最终体积。

4.5 本章知识体系

下面这张图,是我自己总结的字体与字符集配置流程。你可以把它当作一个检查清单:

字体与字符集配置流程 字体与字符集配置流程 1. 理解Unicode与字符集 2. 选择字体格式 位图字体(低性能MCU) 矢量字体(高性能MCU) 3. 配置字符集(子集化) 4. 设置字体回退(Fallback) 优化技巧:降低精度、合并字体、只包含界面实际出现的字符

这张图从理解Unicode开始,到选择字体格式,再到配置字符集和设置回退,最后用优化技巧收尾。你每次做多语言界面时,都可以按这个流程走一遍,基本不会漏掉什么。

好了,字体和字符集这块,核心就是这些。记住:不要贪多,够用就好。你的Flash空间是有限的,用户的耐心也是有限的。把每个字节都用在刀刃上,这才是嵌入式UI工程师的本事。