← 返回生成页

组合变形文字(复杂文本整形)支持方案 —— 技术评审文档

组合变形文字(阿拉伯/印度系/高棉/缅甸/蒙古等)支持方案的完整论证与决策记录

📝 说明:文中提到的 *.py / tests/* 等源码文件为工具内部实现的落点记录,源码暂未公开;不影响阅读方案本身。

评审对象:Py_FontMaker 点阵字库工具链(PC 生成端 + 设备端 C 引擎 + Web 界面) 评审议题:

  1. 如何支持"多个字符组合后字形发生变形"的语言(阿拉伯语、印地语、高棉语等);
  2. Web 界面能否做成按语言勾选、可选部分或全部;
  3. 全部勾选是否可行,边界在哪里。

文中现状结论均以本仓库代码为准,给出文件与行号。

实施状态(2026-09-03):方案已落地——shaping_langs.py(语言包)、shaping_arabic.py (选形表/参考实现)、shaping_prerender.py(语料簇提取/位置形/预变形合成)、 font_shaper_code.py(设备端 ext_font_shaper.c/h 生成),CLI --langs/--corpus/--pua_base, Web 勾选界面与 API 均可用;验证见 tests/test_shaping.py(10 项,含 C/Python 双端差分)。 相对本文的增强:方案甲在设备端增加了簇字典最长匹配器(生成进 ext_font_shaper.c), 因此 C 组语言的动态文本(如新闻)只要语料覆盖到的簇即可正确显示,未命中簇降级为 逐码点显示——"仅固定文案"的约束放宽为"语料字典命中率"(典型新闻语料 99%+)。 且仓库内置各语言簇字典(shaping_corpus/,由 Leipzig 新闻/维基 3 万句语料 + 维基导语/Tatoeba 提炼,构建工具 shaping_corpus_build.py),用户无需上传语料; PUA 默认起点相应调整为 0xE000(6400 槽)。


0. 评审结论(TL;DR)

  1. 可以做成按语言勾选,且可以全选。勾选粒度按"文字系统"而非国家,共约 15 个勾选项,分三个支持等级(A/B/C 组,见 §3)。
  2. A 组(希伯来、泰/老挝)与 B 组(阿拉伯字母系)勾选后动态文本完整可用:A 组现有管线几乎直接支持;B 组利用 Unicode 预变形区码点 + 设备端小查表(LVGL 用内建宏,裸机由工具生成约 2~3 KB 的 arabic_shaper.c),引擎与 bin 格式不动
  3. C 组(印度系、高棉、缅甸、藏文、蒙古文等)推荐"方案甲:PC 端 HarfBuzz 预整形 + 簇级预渲染 + PUA 码点映射":整形复杂度全部留在 PC,设备端引擎、字库格式、cmap、缓存零改动。代价是该组语言只支持固定 UI 文案(经过预整形的字符串),不支持运行时动态拼接的该类文本。
  4. "全部勾选"在此前提下可行:全选 ≈ A+B 组动态可用 + C 组固定文案可用。若产品要求 C 组语言的任意动态文本(如用户输入印地语),小 MCU 上不现实(需移植 HarfBuzz,数百 KB ROM + 数十 KB RAM),仅建议大内存平台按需立项(P3,方案乙铺路)。
  5. 建议分三期:P1 阿拉伯系/希伯来/泰(约 1~2 人周) → P2 C 组预整形产线(约 2~3 人周) → P3 字形 ID 化 / 设备端动态整形(按需)。

1. 问题定义:什么是"组合变形"

变形文字(需 text shaping 的文字)指:最终显示的字形不是"每个码点独立查表"能得到的,字形取决于上下文。两类典型机制:

为什么逐码点渲染无法支持变形文字

业界对第二类的标准答案只有一个:HarfBuzz 整形引擎(Android/Chrome/Firefox 同款)。本方案将其放在 PC 端(Python 绑定 uharfbuzz,pip 可装、跨平台 wheel),不上设备。

2. 现状盘点:当前管线卡在哪里

当前全链路一切以 Unicode 码点为索引,这是支持变形文字的唯一结构性障碍(也是好消息:障碍集中、边界清晰):

环节 现状 对变形文字的影响
栅格化 ttf_freetype_to_xbf.py:221-231:按码点 get_char_index() 逐字渲染 只能渲染"有码点的字形";GID-only 的合体字形取不到
XBF 格式 头部 FirstCode/LastCode 为 u16,密集 cmap 按码点下标(ttf_freetype_to_xbf.py:212-213) 仅 BMP;预变形区(FB50/FE70)在 BMP 内,天然装得下
打包/cmap xbf_to_fontlib_bin.py 码点稀疏表 + hash;cmap_sparse_table_t.code_point(font_engine_code.py) 同上,按码点索引
kerning (left<<16)|right 码点对二分(xbf_to_fontlib_bin.py:1197-1209) 整形路径的位置信息由 HarfBuzz 给出,不依赖此表
设备引擎 LVGL 回调 get_glyph_dsc(font, dsc, letter, letter_next) 逐码点(font_engine_code.py) LVGL 本身按码点驱动字体;这决定了"喂给 LVGL 之前就要完成变形"
多语言 excel_multi_language_to_c.py:Excel → 各语言 C 字符串表 现成的预整形挂接点:固定文案恰好都从这里出
Web webapp/static/index.html:82-90 参数行;webapp/server.py 表单 → 子进程 加"语言包"复选框卡片即可,结构现成

另有两个对方案有利的既有事实:

3. 语言分组与支持等级

设备端需要什么能力分为三组,每组对应 Web 界面上的一批复选框:

语言分组与支持等级

文字系统(→ 勾选项) 覆盖语言/国家(举例) 变形机制 动态文本 设备端代价
A 希伯来文 希伯来语(以色列) 仅 RTL,不连写 LVGL 开 LV_USE_BIDI;裸机加 bidi-lite
A 泰文 / 老挝文 泰语、老挝语 上下标符号定位 ✅(细节瑕疵) 零 advance 符号叠画,现状即可
B 阿拉伯字母系 阿拉伯语(20+ 国)、波斯语、达里/普什图语、乌尔都语、库尔德语、维吾尔语、信德语 位置选形 + 连字,RTL LVGL:内建宏;裸机:生成 arabic_shaper.c(~2‑3 KB)
C 天城文、孟加拉文、古木基文、古吉拉特文、奥里亚文、泰米尔文、泰卢固文、卡纳达文、马拉雅拉姆文、僧伽罗文、藏文 印度各邦、尼泊尔、孟加拉国、斯里兰卡、不丹 辅音合体 + 元音重排 ⚠️ 仅固定文案 零(方案甲)
C 高棉文、缅甸文 柬埔寨、缅甸 下标辅音 / 介音变形 + 重排 ⚠️ 仅固定文案 零(方案甲)
C 蒙古文(传统)、满文/锡伯文 中国内蒙古/新疆、蒙古国 连写选形(无预变形码点区)+ 竖排 ⚠️ 仅固定文案 零 + 应用层旋转 90°

泰米尔文变形程度较轻(合字少),若想省预整形流程也可降级按 A 组近似处理,但会缺 க்ஷ 等合字;默认仍归 C 组走预整形,质量稳妥。

4. 总体方案

设计原则:整形复杂度全部留在 PC 端;设备端拿到的仍是"码点 → 位图"的老问题,现有引擎与格式最大限度复用。

总体架构

4.1 路径①:码点直通(现状,覆盖简单文字 + A 组)

不变。希伯来只需书写方向处理:LVGL 开 LV_USE_BIDI;裸机场景由工具附带生成 bidi_lite.c(简化版:段落级 RTL + 数字/拉丁反向段,不实现完整 UAX#9,见 §8 决策点 3)。泰文/老挝文的组合符号按现状入库即可叠画。

4.2 路径②:轻量运行时整形(B 组,动态文本可用)

  1. 生成端:勾选阿拉伯语系后,自动把基础区(0600–06FF、0750–077F、08A0–08FF)与预变形区(FB50–FDFF、FE70–FEFF) 加入码点集合,按字体实有字形裁剪后入库。XBF/bin/引擎完全不动(码点都在 BMP 内)。
  2. 设备端·LVGL:开 LV_USE_BIDI + LV_USE_ARABIC_PERSIAN_CHARS,LVGL 在 label 渲染前自行把基础码点替换为预变形码点——正好落在我们字库的 cmap 上。
  3. 设备端·裸机:工具生成 arabic_shaper.c/h:输入码点串,查上下文选形表(每字母 4 形映射 + lam-alef 连字),输出预变形码点串,再走路径①。表体约 2 KB,纯查表无 malloc。
  4. 乌尔都语说明:以 Naskh 风格支持(与阿拉伯语同机制)。传统 Nastaliq 悬挂连写不可能用预变形码点表达,若产品要求 Nastaliq 观感,则乌尔都语改走 C 组预整形(见 §8 决策点 4)。

4.3 路径③:PC 预整形(C 组)—— 方案甲:簇级预渲染 + PUA 映射(推荐)

方案甲管线

核心思想:以"字位簇(grapheme cluster)"为最小显示单元,把每个簇在 PC 端整形并整体渲染成一张位图,当作一个"伪字符"分配 PUA 码点

新增 shaping_prerender.py,流程:

  1. 收集所有 C 组文案(多语言 Excel 各语言列 + Web 文本框中属于 C 组区间的文本);
  2. uharfbuzz 对每条文案整形(该语言 script/direction),得到 GID 串 + 位置,按 cluster 边界切分;
  3. FreeType 将每个簇的 1~N 个字形按位置叠加合成一张位图(含 advance/box/偏移度量),全局去重;
  4. 每个唯一簇分配一个 PUA 码点(默认 U+F000 起,可配置),作为普通字形注入 XBF(复用现有 12 B 属性 + 位图的存储,RLE/位流/基线约定照常);
  5. 改写输出:multi_language_*.c 中该文案替换为 PUA 码点串;同时产出 shaping_map.json(PUA ↔︎ 原文/GID 追溯,调试用)。

设备端零改动:lv_label_set_text() 收到 PUA 串 → 现有 cmap 查表 → 现有 Flash 读取/解压/绘制。缓存、fake 兜底、hash 查表全部照常工作。

约束(评审重点):

4.4 方案乙:字形 ID 索引 + 整形串表(P3 可选,不在本期)

方案乙格式 v2

当 PUA 簇方案遇到瓶颈(簇数超限、体积敏感、或要与大平台的设备端动态整形打通)时的升级路线:bin 格式 v2 增加 GID 直接下标表(与现有 cmap_continous_table_t 同构),文案表升级为 {gid, adv_fp4, dx, dy} 整形串,引擎新增约 200 行(按 GID 取形 + 逐字形出图)。比方案甲省空间(共用字形只存一次),且 LVGL v9 的 gid.index 通道天然对齐;代价是格式与引擎双端改动、LVGL v8 label 无法直接消费。评审建议:P2 以方案甲落地,方案乙按产品需求另行立项。

5. Web 界面勾选设计

Web 勾选界面与映射

6. "全部支持"可行性评审

6.1 结论

可行,但要把"支持"拆成两个层面向产品明确承诺:

层面 A 组 B 组 C 组
字库数据(字形都能生成并入库)
固定 UI 文案正确显示 ✅(方案甲)
运行时任意动态文本 ❌ 小 MCU / ⚠️ 大平台可 P3 立项

即:全选后,固定文案在所有语言上都正确;动态文本在 A+B 组上正确。这个承诺对绝大多数嵌入式产品(UI 文案固定、动态内容是数字/时间/拉丁)是完备的。

6.2 体积量级(每字号,2 bpp,24 px,估算)

勾选项 新增字形量级 体积量级
希伯来 ~90 ~10 KB
泰 + 老挝 ~180 ~20 KB
阿拉伯系(含预变形区,按字体裁剪) 600~900 60~90 KB
C 组单语言(取决于文案量,典型 UI 300~800 簇) 300~800 簇 40~120 KB
全选(9 个印度系 + 高棉/缅甸/藏/蒙 + A/B 组) 粗估 0.8~1.6 MB/字号

外挂 SPI Flash 场景可承受;体积敏感时按目标市场勾选子集即可——这正是做成复选框的价值。

6.3 主要风险与对策

# 风险 影响 对策
1 字体缺目标文字字形(如用 CJK 字体勾印地语) 生成空字库 生成前用 fontTools cmap 检查覆盖率,低于阈值即报错并提示换字体(如 Noto 系列)
2 PUA 与产品自用 PUA 冲突 显示串字 起始码点可配(默认 U+F000);shaping_map.json 可审计
3 藏文/天城合体纵向超行框,YSize 不够 上下裁切 预整形后按实测 bbox 回写 XBF 头 YSize/BaseLine;int8 偏移域校验告警
4 乌尔都 Nastaliq 观感争议 客诉 默认 Naskh 并在界面注明;需要 Nastaliq 时转 C 组预整形
5 蒙古文竖排 布局 工具只保证连写正确(横排簇串),竖排由应用层旋转 label;文档写明
6 簇位图不共用字形,体积偏大 体积 可接受(见 6.2);超限升级方案乙
7 新依赖 uharfbuzz 构建环境 纯 pip wheel,Win/mac/Linux 均有预编译包,与现有 freetype-py 同级依赖
8 LVGL 内建阿语选形覆盖面(个别连字/harakat) 个别字符不连 已覆盖主流场景;要求更高时阿语也可走 C 组预整形兜底

7. 分期实施建议

内容 涉及文件 工作量估计
P1 Web/CLI 语言勾选框架 + LANG_PACKS 表;A 组(希伯来/泰/老挝)码点包;B 组阿拉伯预变形区入库 + arabic_shaper.c/bidi_lite.c 模板;LVGL 配置文档 index.htmlserver.pyPy_FontMaker.pyfont_engine_code.py 1~2 人周
P2 方案甲预整形产线:shaping_prerender.py(uharfbuzz + 簇渲染 + PUA 分配 + 去重)、Excel 产线打通、shaping_map.json、覆盖率检查、C 组全部勾选项 新文件 + excel_multi_language_to_c.pyttf_freetype_to_xbf.py(PUA 注入口) 2~3 人周
P3(可选) 方案乙 GID 化(格式 v2 + 引擎 draw run);大平台设备端 HarfBuzz 动态整形评估 xbf_to_fontlib_bin.pyfont_engine_code.py 立项另评

每期验收建议沿用现有四层验证体系(单测 / bin 逐位校验 / clang 语法 / 宿主机运行时,tests/),P2 增加"整形金样比对":同一文案 uharfbuzz 整形结果与 FreeType 渲染簇位图的哈希快照回归。

8. 待评审决策点

# 决策点 建议
1 C 组接受"仅固定文案"的产品承诺? 接受。动态需求走 P3 另评,不阻塞 P1/P2
2 PUA 起始码点与分配策略 默认 U+F000 起、按生成批次连续分配;提供 --pua-base 覆盖
3 裸机 bidi 做简化版还是完整 UAX#9 简化版(段落 RTL + 数字/拉丁段反转);完整版收益低、成本高
4 乌尔都语默认风格 Naskh(B 组);Nastaliq 需求出现时按项目转 C 组
5 蒙古文竖排责任边界 工具保证连写,竖排由应用层旋转,不做竖排布局引擎
6 引入 uharfbuzz 依赖 同意(pip wheel,跨平台,业界唯一标准整形库)
7 泰米尔文归 C 组(默认)还是降级 A 组近似 默认 C 组;体积极敏感的项目可选降级开关

附:图目录

文件 内容
图 1 svg/shaping_problem.svg 逐码点渲染为何失败(阿拉伯文/天城文对照)
图 2 svg/shaping_script_tiers.svg 语言分组与支持等级(A/B/C)
图 3 svg/shaping_arch.svg 总体架构与设备端三条运行时路径
图 4 svg/shaping_pua_pipeline.svg 方案甲:簇级预渲染 + PUA 映射管线
图 5 svg/shaping_gid_format.svg 方案乙:GID 索引格式 v2(P3 备选)
图 6 svg/shaping_webui.svg Web 勾选界面与"勾选 → 生成动作"映射
微信公众号
微信公众号
QQ群 BLE5.4 学习讨论 177341833
QQ群 177341833
QQ群 BLE 开发学习 498676838
QQ群 498676838