Rongbin Fan

Github Actions + fonttools 拉取字符并创建中文字体子集

帮助解决中文字体体积过大的问题

创建子集部分参考自谢益辉老师的做法。 如果你需要的中文字体在字图 CDN 有覆盖, 用这个也能一定程度上提升性能。

本文将通过解释写好的 workflow 文件来展开, 完整代码见最下方。

on:
  workflow_dispatch:
  workflow_run:
    workflows: ["deploy"]
    types:
      - completed

告诉 Github Actions 什么时候自动运行, workflow_dispatch:使得这个 workflow 能在对应页面手动点击运行, workflow_run:这里我定义了使其在名字为 deploy的 workflow 完成后运行。

steps:
  - name: Checkout repository
    uses: actions/checkout@v3

checkout 把仓库的代码从 Github 克隆到 workflow 构建的运行环境中。

steps:
  - name: Install fonttools
    run: sudo apt-get update && sudo apt-get install -y fonttools

安装 fonttools。

steps:
  - name: Extract used characters from content
    run: |
      mkdir -p font
      find . -name '*.md' -o -name '*.html' | xargs cat \
        | grep -oP '[\x{3000}-\x{30FF}\x{31F0}-\x{31FF}\x{4E00}-\x{9FFF}\x{AC00}-\x{D7AF}\x{FF00}-\x{FFEF}\x{FE10}-\x{FE1F}]' \
        | sort | uniq > used-chars.txt

从所在项目的.md 和.html 提取出出现过的中日韩字符 (CJK):在工作环境创建/font 文件夹, 查找当前目录及子目录下所有以.md 或.html 结尾的文件, 用 cat 拼接成一个文件, 用 grep 来匹配字符范围 (Unicode 区间可自行重新设定), sort 排序, uniq 去重, > used-chars.txt 保存到一个文本文件。

steps:
  - name: Download fonts
    run: |
      mkdir -p font/raw
      cd font/raw
      declare -A urls
      urls["SourceHanSansSC-Regular"]="https://github.com/adobe-fonts/source-han-sans/raw/release/OTF/SimplifiedChinese/SourceHanSansSC-Regular.otf"
      
      ...
      
      for name in "${!urls[@]}"; do
        echo "Downloading ${name}..."
        wget -nv --retry-connrefused --waitretry=1 --read-timeout=20 --timeout=15 -t 3 "${urls[$name]}" -O "${name}.otf"
      done
      
      echo "Copying local fonts STKaiti and DFKai-SB..."
      cp ../../resources/STKaiti.otf .
      cp ../../resources/DFKai-SB.otf .

创建/font/raw 目录来保存在线下载的中文字体文件 (.otf 格式)。 定义了一个关联数组 urls 来储存字体名和链接。 用 for 遍历, 用 wget 下载每个字体 (--retry-connrefused 访问被拒绝的话自动重试, --t 3 最多重试 3 次) 并保存到/font/raw 中。

如果手动准备了字体 (这里存储在仓库的/resources 下), 用 cp 也复制到/font/raw。

- name: Subset fonts
  run: |
    mkdir -p font/woff2
    for font in font/raw/*.otf; do
      name=$(basename "$font" .otf)
      pyftsubset "$font" \
        --text-file=used-chars.txt \
        --flavor=woff2 \
        --no-hinting \
        --layout-features='*' \
        --output-file="font/woff2/${name}-subset.woff2"
    done

创建/font/woff2 来存放生成的子集字体。

for 循环遍历.otf 字体, 使用 pyftsubset 生成子集字体, 只保留 used-chars.txt 列出的字符, 输出格式为 woff2, --no-hinting 不保留字体 hinting 信息, --layout-features="*"保留 OpenType 的功能, --output-file 指定输出文件路径。

- name: Move subset fonts to static/font
  run: |
    mkdir -p static/font
    mv font/woff2/*.woff2 static/font/

移动到仓库的/static/font 目录下。 (如果你不需要在这个仓库存一份, 可以省去这一步)

- name: Commit and push subset fonts to Github Pages
  env:
    TOKEN: ${{ secrets.PERSONAL_TOKEN }}
  run: |
    git clone --depth 1 https://x-access-token:${TOKEN}@github.com/rongbinf/rongbinf.github.io.git out
    cp -f static/font/*.woff2 out/font/
    cp -f used-chars.txt out/font/used-chars.txt
    cd out
    git config user.name github-actions
    git config user.email github-actions@github.com
    git add font/*.woff2
    git add font/used-chars.txt
    git commit -m "chore: update subset Source Han Sans fonts" || echo "No changes to commit"
    git push

推送到 Github Pages 仓库, 使用环境变量 TOKEN 认证 Git 操作 (可参考之前写的自动部署 Hugo 教程), 克隆下来到/out 后把子集字体放到/out/font 下, 然后配置并 push 变更。


完整 yml 如下:

name: Subset Source Han Sans

on:
  workflow_dispatch:
  workflow_run:
    workflows: ["deploy"]
    types:
      - completed

jobs:
  subset-fonts:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Install fonttools
        run: sudo apt-get update && sudo apt-get install -y fonttools

      - name: Extract used characters from content
        run: |
          mkdir -p font
          find . -name '*.md' -o -name '*.html' | xargs cat \
            | grep -oP '[\x{3000}-\x{30FF}\x{31F0}-\x{31FF}\x{4E00}-\x{9FFF}\x{AC00}-\x{D7AF}\x{FF00}-\x{FFEF}\x{FE10}-\x{FE1F}]' \
            | sort | uniq > used-chars.txt

      - name: Download fonts
        run: |
          mkdir -p font/raw
          cd font/raw
          declare -A urls
          urls["SourceHanSansSC-Regular"]="https://github.com/adobe-fonts/source-han-sans/raw/release/OTF/SimplifiedChinese/SourceHanSansSC-Regular.otf"
          urls["SourceHanSansTC-Regular"]="https://github.com/adobe-fonts/source-han-sans/raw/release/OTF/TraditionalChinese/SourceHanSansTC-Regular.otf"
          urls["SourceHanSans-Regular"]="https://github.com/adobe-fonts/source-han-sans/raw/release/OTF/Japanese/SourceHanSans-Regular.otf"
          urls["SourceHanSansK-Regular"]="https://github.com/adobe-fonts/source-han-sans/raw/release/OTF/Korean/SourceHanSansK-Regular.otf"
          urls["SourceHanSansSC-Bold"]="https://github.com/adobe-fonts/source-han-sans/raw/release/OTF/SimplifiedChinese/SourceHanSansSC-Bold.otf"
          urls["SourceHanSansTC-Bold"]="https://github.com/adobe-fonts/source-han-sans/raw/release/OTF/TraditionalChinese/SourceHanSansTC-Bold.otf"
          urls["SourceHanSans-Bold"]="https://github.com/adobe-fonts/source-han-sans/raw/release/OTF/Japanese/SourceHanSans-Bold.otf"
          urls["SourceHanSansK-Bold"]="https://github.com/adobe-fonts/source-han-sans/raw/release/OTF/Korean/SourceHanSansK-Bold.otf"
      
          for name in "${!urls[@]}"; do
            echo "Downloading ${name}..."
            wget -nv --retry-connrefused --waitretry=1 --read-timeout=20 --timeout=15 -t 3 "${urls[$name]}" -O "${name}.otf"
          done
      
          echo "Copying local fonts STKaiti and DFKai-SB..."
          cp ../../resources/STKaiti.otf .
          cp ../../resources/DFKai-SB.otf .

      - name: Subset fonts
        run: |
          mkdir -p font/woff2
          for font in font/raw/*.otf; do
            name=$(basename "$font" .otf)
            pyftsubset "$font" \
              --text-file=used-chars.txt \
              --flavor=woff2 \
              --no-hinting \
              --layout-features='*' \
              --output-file="font/woff2/${name}-subset.woff2"
          done

      - name: Move subset fonts to static/font
        run: |
          mkdir -p static/font
          mv font/woff2/*.woff2 static/font/

      - name: Commit and push subset fonts to Github Pages
        env:
          TOKEN: ${{ secrets.PERSONAL_TOKEN }}
        run: |
          git clone --depth 1 https://x-access-token:${TOKEN}@github.com/rongbinf/rongbinf.github.io.git out
          cp -f static/font/*.woff2 out/font/
          cp -f used-chars.txt out/font/used-chars.txt
          cd out
          git config user.name github-actions
          git config user.email github-actions@github.com
          git add font/*.woff2
          git add font/used-chars.txt
          git commit -m "chore: update subset Source Han Sans fonts" || echo "No changes to commit"
          git push