合并两个字体的实战
最近在填坑“某系列标题生成器”项目,需要把 一个日文为主的字体(简称1.otf) 和 一个中文为主的字体(简称2.ttf) 合并成一个字体文件。踩了一堆坑,故记录。
目标
合并两个字体:
1 | 1.otf |
目标:
- 保留
1.otf的字体(以日文为主) - 用
2.ttf补充字符(以中文为主) - 生成一个新的字体
字体的前置知识
字体文件后缀
常见的字体文件后缀有:
| 后缀 | 类型 | 说明 |
|---|---|---|
.ttf |
TrueType Font | 最常见的字体格式之一 |
.otf |
OpenType Font | TrueType 的扩展格式 |
.ttc |
TrueType Collection | 多个字体打包在一个文件里 |
.otc |
OpenType Collection | OpenType 的集合版本 |
.woff / .woff2 |
Web Open Font | 用于网页 |
TTF
特点:
- 使用 quadratic Bézier 曲线
- 字形数据存储在
glyf表中 - 结构相对简单
TTF 的核心结构大致是:
1 | glyf |
由于结构比较直接,因此 比较容易用脚本修改。
OTF
OTF 其实分为两种:
1 | OTF (TrueType outlines) |
区别在于 字形轮廓的存储方式。
OTF (TrueType)
和 TTF 类似:1
glyf
只是外层容器是 OpenType。
OTF (CFF)
使用:1
CFF
表存储字形。
特点:
- 使用 PostScript cubic Bézier 曲线
- 字形存在
CFF表 - 结构比 TTF 更复杂
很多专业字体(尤其是 CJK 字体)使用这种结构。
Glyph(字形)
字体里的每一个字符都叫:glyph,例如:A、B、中、あ
每个 glyph 包含:
- 轮廓
- 字宽
- unicode 映射
cmap
字体里的一个重要表:cmap
作用:Unicode → glyph
例如:
1 | U+0041 → A |
合并字体时,通常需要修改这个表。
CJK 字体的特殊结构
很多日文 / 中文字体不是普通 OTF,而是:
1 | CID-keyed OpenType CFF |
例如:
1 | 思源 |
这种字体结构是:
1 | CID |
与普通字体不同:
普通字体:
1 | glyphName → glyph |
CID 字体:
1 | CID → glyph |
这也是很多字体工具无法直接合并它们的原因。
为什么 CJK 字体使用 CID
原因很简单:字符数量太多
例如:
1 | GB2312 ~ 6000 |
如果使用普通结构,字体会非常庞大。
CID 结构可以:
- 压缩数据
- 共享字形
- 减少重复信息
因此 绝大多数东亚字体都采用这种结构。
小结
在合并字体之前,需要注意:
.ttf通常比较容易处理.otf可能包含 CFF 结构- CJK 字体往往是CID-keyed CFF
- 这种字体很多工具无法直接 merge
这也是本文后面踩坑的主要原因。
准备环境
首先需要安装两个工具:
1 | pip install afdko |
分别来自:
- Adobe Font Development Kit for OpenType
- fontTools
第一次尝试:fonttools merge
最直接想到的方法是:
1 | fonttools merge 1.otf 2.ttf |
结果报错:
1 | NotImplementedError: Merging CID-keyed CFF tables is not supported yet |
原因:
1.otf属于:
1 | CID-keyed OpenType CFF |
这种字体结构与普通 OTF 不同:
1 | CID |
fonttools 目前不支持合并这种字体。
第二次尝试:直接修改 CFF 表
尝试用 Python:
1 | TTF glyph → CFF charstring |
插入到字体里。
代码大致类似:
1 | charStrings[new_name] = charstring |
结果报错:
1 | KeyError: 'uni0102' |
原因:
CID 字体不是:
1 | glyphName → glyph |
而是:
1 | CID → glyph |
所以不能直接新增 glyph。
第三次尝试:AFDKO tx 转换字体
尝试使用 AFDKO 的 tx:
1 | tx -t1 font.otf > font.pfa |
结果又失败:
1 | bad font file |
原因:
很多 CID CFF 字体无法直接转换为 Type1。
第四次尝试:FontForge 转换
使用 FontForge:
1 | OTF → TTF |
虽然能生成 TTF,但会出现大量 warning:
1 | Self Intersecting |
生成的ttf字体在windows系统里显示不是正确的字体。
第五次尝试:最终解决方案
最终采用的方法:
1 | OTF → TTF |
思路:
- 把
1.otf转换为 TTF - 复制缺失的 glyph
- 更新 cmap / glyphOrder / metrics
转换命令
先安装库1
pip install afdko
有直接转换的命令1
otf2ttf 1.otf
会直接生成1.ttf
合并字体脚本
1 | from fontTools.ttLib import TTFont |
注意事项
1 不要直接 merge 整个字体
否则可能把几万 glyph 合并进去。
通常只需要:
1 | ASCII |
2 glyphOrder 必须同步
否则会报:
1 | AssertionError |
3 必须更新 maxp.numGlyphs
否则字体保存会失败。
参考
- fontTools
- AFDKO
- OpenType specification