在游戏开发、UI 设计或嵌入式项目中,原始字体文件往往包含大量未使用的字符,导致包体臃肿。通过 Python 结合 FontTools 库,可以自动提取文本中的可见字符并生成精简后的字体子集,大幅缩小体积。下面一步步搭建这个工具。
一、项目环境搭建
创建项目目录并进入:
使用 Python 内置的 venv 创建虚拟环境:
激活虚拟环境(Windows):命令行出现 (venv) 前缀表示成功。
安装依赖:- pip install fonttools pyinstaller
复制代码
验证 FontTools 可正常调用:- python -c "from fontTools.subset import main; print('OK')"
复制代码 输出 OK 说明环境就绪。
二、核心脚本:font_subset_core.py
该脚本负责读取文本、去重排序、生成字符集文件,并调用 pyftsubset 生成子集字体。完整代码如下:
- import sys
- from pathlib import Path
- from fontTools.subset import main as pyftsubset_main
- FONT_EXTS = {".ttf", ".otf"}
- def read_text_auto(path):
- """自动尝试多种编码读取文本文件"""
- data = Path(path).read_bytes()
- for enc in ("utf-8-sig", "utf-8", "gbk", "gb18030"):
- try:
- return data.decode(enc), enc
- except UnicodeDecodeError:
- pass
- return data.decode("gb18030", errors="ignore"), "gb18030-ignore"
- def sort_key(c):
- code = ord(c)
- if '0' <= c <= '9':
- return (0, code)
- if 'A' <= c <= 'Z':
- return (1, code)
- if 'a' <= c <= 'z':
- return (2, code)
- if 0x4E00 <= code <= 0x9FFF:
- return (4, code)
- return (3, code)
- def make_unique_chars(text):
- chars = {c for c in text if not c.isspace() and ord(c) >= 32}
- return "".join(sorted(chars, key=sort_key))
- def build_subset(font_path, chars_path, out_font_path):
- pyftsubset_main([
- str(font_path),
- f"--text-file={chars_path}",
- f"--output-file={out_font_path}",
- "--layout-features=*",
- "--glyph-names",
- "--symbol-cmap",
- "--legacy-cmap",
- "--notdef-glyph",
- "--notdef-outline",
- "--recommended-glyphs",
- ])
- def main():
- if len(sys.argv) < 3:
- print("用法: font_subset_core.exe 字体.ttf 文本.txt")
- return
- font_path = Path(sys.argv[1])
- text_path = Path(sys.argv[2])
-
- if not font_path.exists():
- print("字体文件不存在:", font_path)
- return
- if not text_path.exists():
- print("文本文件不存在:", text_path)
- return
- if font_path.suffix.lower() not in FONT_EXTS:
- print("第一个参数必须是字体文件:", font_path)
- return
-
- text, enc = read_text_auto(text_path)
- chars = make_unique_chars(text)
- chars_path = text_path.with_name(text_path.stem + "_chars.txt")
- chars_path.write_text(chars, encoding="utf-8")
- out_font_path = font_path.with_name(font_path.stem + "_subset" + font_path.suffix)
- build_subset(font_path, chars_path, out_font_path)
-
- print("处理完成")
- print("识别编码 :", enc)
- print("字符数量 :", len(chars))
- print("字符文件 :", chars_path)
- print("子集字体 :", out_font_path)
- if __name__ == "__main__":
- main()
复制代码
关键函数说明:
- read_text_auto:依次尝试 UTF-8 with BOM、UTF-8、GBK、GB18030 解码,保证中文文本兼容。
- sort_key:将字符排序为数字、大写字母、小写字母、其他字符、CJK 汉字(按 Unicode 码位),输出符合日常阅读习惯。
- make_unique_chars:过滤空白和控制字符,返回去重排序后的字符串。
- build_subset:封装 pyftsubset 命令,保留所有布局特性、字形名称、符号映射及推荐字形。
三、运行测试
在虚拟环境中执行:- python font_subset_core.py test.ttf test.txt
复制代码
测试结果示例:- 处理完成
- 识别编码 : utf-8-sig
- 字符数量 : 247
- 字符文件 : test_chars.txt
- 子集字体 : test_subset.ttf
复制代码
过程自动完成:读取文本 → 去重排序 → 生成 chars.txt → 调用 pyftsubset → 生成 subset.ttf。
四、打包为独立 EXE
方便在没有 Python 环境的目标机器上直接运行,使用 PyInstaller 打包:- python -m PyInstaller -F --clean --collect-all fontTools --name FontSubset font_subset_core.py
复制代码
参数含义:
- -F:生成单个 exe 文件。
- --clean:清理上次打包缓存。
- --collect-all fontTools:强制收集 FontTools 全部模块及依赖。
- --name:指定输出 exe 名称。
打包后得到 dist/FontSubset.exe。使用方式:- FontSubset.exe FZBWKS.ttf all_text.txt
复制代码
执行后会在同目录生成 all_text_chars.txt(字符集)和 FZBWKS_subset.ttf(子集字体)。适用于传奇游戏、手游项目、UI 字体优化等场景,可大幅缩小字体体积。
五、补充:字体加粗处理
生成的子集字体可能需要加粗效果。可以借助 FontForge 工具手动调整:
1. 打开生成的 subset.ttf。
2. 按 Ctrl+A 全选所有字形。
3. 选择菜单“元素 → 字体加粗”,设置加粗单位(如 6 em units),中文字体建议保留间距。
4. 导出为字体文件即可。
此步骤属于字体后期微调,与 Python 脚本配合即可完成完整字体优化流程。
六、总结
本工具通过 Python + FontTools 实现了从文本到字体子集的自动化生成,支持 .ttf / .otf 格式。借助 PyInstaller 打包后可直接运行,无需额外环境。对于需要精简字体体积的项目,是一个实用的脚本方案。 |