blog

中文字体替换之旅

2024-08-16
技术 字体

起源

一直以来,我对怀旧复古风格情有独钟,甚至一度想做一个能带来读报纸体验的 RSS 阅读器。于是在折腾博客的期间,我不断地寻找合适的字体,想为自己的数字空间增添几分书卷气。我们都知道,在写作体验和阅读体验上,字体与配色都扮演着举足轻重的角色。

直到最近,才发现了一款让我感到特惊艳的字体 【京华老宋体】, 整个字体看起来复古又有印刷的美感,更可贵的是允许免费商用,于是忍不住就立马把博客的中文字体换成了它。

换的过程不算复杂,由于我的博客架构使用的是 Astro + Tailwind,只需把字体文件放到 public/fonts 文件夹下,再在全局 css 文件夹里注册 @font-face ,最后在 tailwind.config.ts 完成注册即可。

但很快就发现了问题。原版字体 KingHwa_OldSong.ttf 大小高达 36M 之多!

这显然是不合适放在网站上加载的,不仅会极大消耗服务器的负载和带宽,对于网站加载速度的影响也不容小觑。于是我开始尝试在网上找精简版,却一无所获。

解决之道

后来在 Claude.ai 的帮助下,找到了两种解决方案。

  1. 为原版字体做子集
  2. 将字体压缩成体积更小的 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 下面,我特意把原本的背景色调得更暗了些,看起来也颇有看报之味了。

在这期间,也学习了很多新的东西,颇有成就。

又想起了那句话:生命不息,折腾不止。