本博客中文字体采用了一段时间的字体分包方案,又尝试调整为按需字体子集化的方案,整理记录如下。
字体分包
博客使用 Oppo Sans 4.0 字体:https://www.coloros.com/article/A00000074/
使用 cn-font-split 工具分包
$ pnpx cn-font-split --input "OPPO Sans 4.0.ttf" --outDir dist-font
生成内容如下

其中
- index.html 是预览调试一个一个 Demo 网页
- index.proto 是 Protocol Buffers 接口定义文件
- reporter.bin 记录字体拆分的详细信
- result.css 是引入的拆分字体的入口定义样式文件
使用的话,将拆分后的众多 .woff2 字体文件放到 OPPOSans4(自定义)目录,添加到博客静态目录中,编辑 result.css 文件,注意批量编辑 URL 中的路径。
@font-face{font-family:"OPPO Sans 4.0";src:local("OPPO Sans 4.0"),url("./fonts/OPPOSans4/b8d45a4b1842853318f4cdd5ba6ef8a4.woff2")format("woff2");font-style:normal;font-display:swap;
例如我的字体放在 css 当前目录的 fonts/OPPOSans4 下,路径就是 ./fonts/OPPOSans4/b8d45a4b1842853318f4cdd5ba6ef8a4.woff2

博客加载过程中,会按需加载,以 OPPO Sans 字体为例,默认命令参数下拆分为 400+ 个字体文件,每篇文章动态加载了 10-30 个字体文件,比较零散。
按需字体子集化
这是另一个思路,把博客使用到文字汇总,将字体精简,精简后的字体文件只包含用到的字体,也能大幅减少字体文件的体积。
brew install fonttools
收集
find content -name "*.md" -exec cat {} \; > all_text.txt
过滤
sed -e 's/```.*//g' \
-e 's/`[^`]*`//g' \
-e 's/!\[[^]]*\](\([^)]*\))//g' \
-e 's/\[[^]]*\](\([^)]*\))/\1/g' \
all_text.txt > clean_text.txt
生成
pyftsubset "OPPO Sans 4.0.ttf" \
--text-file=clean_text.txt \
--output-file=opposans-blog.woff2 \
--flavor=woff2 \
--layout-features='*' \
--no-hinting \
--desubroutinize
使用
@font-face {
font-family: 'OPPO Sans 4.0';
src: url('fonts/OppoSans-Blog.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
:root {
--font-sans: 'OPPO Sans 4.0', 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif;
--font-mono: 'JetBrains Mono', 'Roboto Mono', Consolas, Monaco, 'Courier New', monospace;
--font-ui: var(--font-sans);
}
就只需要加载一个精简后字体文件。

字体子集化方案较麻烦的是需要定期执行脚本生成字体,好在未匹配到的字体会自动降级为系统字体,偶尔两个中文降级到系统字体显示并非不可接受。
P.S. 经过几次调整,OPPO Sans 中文字体 + JetBrains Mono 代码显示效果是我目前最喜欢的搭配!