中文字体替换之旅
起源
一直以来,我对怀旧复古风格情有独钟,甚至一度想做一个能带来读报纸体验的 RSS 阅读器。于是在折腾博客的期间,我不断地寻找合适的字体,想为自己的数字空间增添几分书卷气。我们都知道,在写作体验和阅读体验上,字体与配色都扮演着举足轻重的角色。
直到最近,才发现了一款让我感到特惊艳的字体 【京华老宋体】, 整个字体看起来复古又有印刷的美感,更可贵的是允许免费商用,于是忍不住就立马把博客的中文字体换成了它。
换的过程不算复杂,由于我的博客架构使用的是 Astro + Tailwind,只需把字体文件放到 public/fonts
文件夹下,再在全局 css 文件夹里注册 @font-face
,最后在 tailwind.config.ts
完成注册即可。
但很快就发现了问题。原版字体 KingHwa_OldSong.ttf
大小高达 36M 之多!
这显然是不合适放在网站上加载的,不仅会极大消耗服务器的负载和带宽,对于网站加载速度的影响也不容小觑。于是我开始尝试在网上找精简版,却一无所获。
解决之道
后来在 Claude.ai 的帮助下,找到了两种解决方案。
- 为原版字体做子集
- 将字体压缩成体积更小的 woff2 格式
对于第一种方案,最理想的做法是扫描网站所有文件,提取全部汉字文本,再通过 pyftsubset
制作子集。但由于我博客中 notepad 对最近阅读文章和收藏推文的展示是通过接口请求实现的,不在本地源代码里,所以暂时放弃了这个想法,而是偷懒地选择尝试先使用 中文常用3000字
和 中文常用7500字
进行精简。
然而,不出意外的事与愿违了。中文常用7500字
的字体子集仍高达 6M, 中文常用3000字
的子集也有 3M。虽然已经比原版缩减了 90%,但和几百 K 的英文字体相比,还是太太太大了。
不得已,只能老老实实地基于网站中的所有中文文本来制作字体子集了。
整体思路是在执行 npm run build
之后,在生成的 dist
文件夹中找到所有中文汉字,然后执行脚本制作子集即可。
脚本展示
以下是我所使用的 optimize-font.sh
脚本:
#!/bin/bash
# 如果命令失败则停止脚本
set -e
# 打印彩色提示信息
printf "Optimizing KingHwa-oldsong.ttf for the Astro blog..\n"
# 确保 fonttools, brotli, ripgrep 已安装
if ! pip list | grep -q fonttools; then
echo "fonttools is not installed in the virtual environment. Installing now..."
pip install fonttools
fi
if ! pip list | grep -q brotli; then
echo "brotli is not installed in the virtual environment. Installing now..."
pip install brotli
fi
if ! pip list | grep -q ripgrep; then
echo "ripgrep is not installed in the virtual environment. Installing now..."
pip install ripgrep
fi
# 设置字体目录
FONT_DIR="dist/fonts"
INPUT_FONT="KingHwa-oldsong.ttf"
SUBSET_FONT="KingHwa-oldsong.subset.ttf"
OUTPUT_FONT="KingHwa-subset.woff2"
# 优化字体
echo "[+] Optimizing font: $INPUT_FONT"
text_content=$(rg -e '[\w\d]' -oN --no-filename dist | sort | uniq | tr -d '\n')
echo "[+] Length of Text content: ${#text_content}"
pyftsubset "$FONT_DIR/$INPUT_FONT" \
--text="$text_content" \
--no-hinting \
--output-file="$FONT_DIR/$SUBSET_FONT"
echo "[+] Compressing font: $SUBSET_FONT"
# 使用 fonttools 压缩成 woff2 格式
fonttools ttLib.woff2 compress -o "$FONT_DIR/$OUTPUT_FONT" "$FONT_DIR/$SUBSET_FONT"
echo "[+] Removing intermediate font: $SUBSET_FONT"
rm "$FONT_DIR/$SUBSET_FONT"
echo "[+] Font optimized: $OUTPUT_FONT"
printf "Font optimization completed!\n"
脚本解析
让我们对其中最关键的 rg 文本提取部分做一番解释:
-
rg -e '[\w\d]'
:使用 ripgrep 搜索所有字母数字字符。 -
-o
:只输出匹配的部分。 -
-N
:不要为每个匹配输出行号。 -
--no-filename
:不输出文件名。 -
|sort|uniq
:对结果进行排序并去重。 -
|tr -d '\n'
:删除所有换行符,将结果合并为一行。 -
--no-hinting
:在生成的子集字体中禁用字体微调。
尾声
经过这样一通折腾,最终输出的字体大小仅有 600K,完全不会给服务器和加载速度带来负担了。
回过头看,为了一个赏心悦目的字体,不知不觉又耗费了不少时间,期间也是不断踩坑不断尝试,所幸最后的结果还是令人满意的。特别是在 light mode
下面,我特意把原本的背景色调得更暗了些,看起来也颇有看报之味了。
在这期间,也学习了很多新的东西,颇有成就。
又想起了那句话:生命不息,折腾不止。