#!/usr/bin/env bash # 生成 Noto Sans SC 子集,用于 client 离线打包(全平台)。 # # 策略:GB2312 一级+二级常用字(6763 字,经 Python 编解码确定性生成,无需联网) # ∪ 仓库 client/lib 实际用到的 CJK 字符 → 子集字表; # 源用 Noto Sans CJK SC 变量字体,先 instance 到 Regular(wght=400)再 subset # (变量 CFF2 的字重轴体积很大;单 Regular 静态 + 引擎合成粗体,显著缩小)。 # 产物:client/fonts/NotoSansSC-Regular-subset.otf (~3MB,6763 字)。 # # 依赖:python3 + fonttools(pip install fonttools brotli)。 # 用法:bash tools/fonts/make-cjk-subset.sh set -euo pipefail cd "$(dirname "$0")/../.." # 仓库根 SRC_DIR="tools/fonts/_src" CHARS="tools/fonts/common-hanzi.txt" OUT="client/fonts/NotoSansSC-Regular-subset.otf" mkdir -p "$SRC_DIR" # fonttools:优先用隔离 venv(避免污染系统 python),否则用 python3 -m。 PY="python3" SUB="python3 -m fontTools.subset" if [ -x "/tmp/fonttools-venv/bin/python" ]; then PY="/tmp/fonttools-venv/bin/python" SUB="/tmp/fonttools-venv/bin/pyftsubset" fi # 1) 字表:GB2312 6763 常用字 ∪ 仓库实际用字。 "$PY" - "$CHARS" <<'PYGEN' import sys, glob chars=set() for hi in range(0xB0,0xF8): for lo in range(0xA1,0xFF): try: ch=bytes([hi,lo]).decode('gb2312') if '一'<=ch<='鿿': chars.add(ch) except UnicodeDecodeError: pass # 仓库实际用到的 CJK(确保 UI/l10n 文案一定在子集内) for path in glob.glob('client/lib/**/*.dart', recursive=True): try: for ch in open(path,encoding='utf-8').read(): if '一'<=ch<='鿿': chars.add(ch) except (UnicodeDecodeError,OSError): pass open(sys.argv[1],'w',encoding='utf-8').write(''.join(sorted(chars))) print('charset hanzi:',len(chars)) PYGEN # 2) 源字体(变量 OTF);若已存在则跳过。失效时换 google/fonts 或 notofonts 最新路径。 SRC_TTF="$SRC_DIR/NotoSansSC.ttf" if [ ! -f "$SRC_TTF" ]; then curl -fsSL "https://github.com/notofonts/noto-cjk/raw/main/Sans/Variable/OTF/NotoSansCJKsc-VF.otf" -o "$SRC_TTF" fi # 3) instance 到 Regular(wght=400)。 STATIC="$SRC_DIR/NotoSC-Regular.otf" "$PY" -m fontTools.varLib.instancer "$SRC_TTF" wght=400 -o "$STATIC" --quiet # 4) subset(不用 --retain-gids,否则保留全部原 gid 致体积暴涨)。 "$SUB" "$STATIC" \ --text-file="$CHARS" \ --output-file="$OUT" \ --layout-features='*' \ --name-IDs='*' ls -la "$OUT"