Skip to content
SharpDotNUTPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

fonts-cdn

四个命令,用于运行自建字体 CDN。分片器位于 git submodule font-split。

  • fonts-download 下载选定的六个上游项目到 .cache/fonts-raw/。
  • fonts-build 读取 cdn.toml,构建十二个配置家族到 dist/<Family>/。
  • fonts-upload 按 manifest.json 校验本地产物,再上传到 Cloudflare R2 桶。
  • font-sharder 把单个源字体拆分为 Unicode-aware WOFF2 分片、CSS 与 manifest.json。

分片是静态且完整的,不按页面内容生成。浏览器只请求 unicode-range 命中页面字符的分片。四个 Source Han 与四个 Resource Han Rounded 语言家族各自把本语言子集与完整 SC 字体中缺失的码点合并到同一 CSS family 下。

上游字体 ──fonts-download──▶ .cache/fonts-raw/ ──fonts-build──▶ dist/<Family>/ ──fonts-upload──▶ R2

要求 Python ≥ 3.11 与 uv。克隆时必须初始化子模块:

git clone --recurse-submodules https://github.com/SharpDotNUT/fonts-cdn.git
cd fonts-cdn
uv sync

uv run fonts-download --list
uv run fonts-download
uv run fonts-build --dry-run
uv run fonts-build --skip-baseline
uv run fonts-upload --dry-run

已经克隆但缺少子模块时执行 git submodule update --init。uv 通过 workspace 同时安装主包与 font-split,因此 font-sharder 命令也可直接使用。

每个 dist/<Family>/ 包含 <Family>.css、manifest.json 与内容哈希命名的 WOFF2 分片。构建产物上传到 Cloudflare R2 后由 https://fonts-cdn.sharpdotnut.com 对外提供。例如:

<link rel="stylesheet" href="https://fonts-cdn.sharpdotnut.com/SourceHanSansCN/SourceHanSansCN.css">

浏览器按 CSS 里的 unicode-range 自动请求命中的分片,页面只需引这一个 <link>。家族 slug、完整地址清单、缓存与 CORS 配置、排错方法见 USAGE.md。

可变家族会在 CSS 中发布 fvar 字重范围。Maple Mono NF 与 LXGW WenKai 是仅 Regular 的静态构建;Inter 不包含 italic。

家族

cdn.toml 定义十二个输出家族:

CSS family 源文件 Fallback
Source Han Sans CN/TW/HK/JP SourceHanSans<lang>-VF.ttf SourceHanSansSC-VF.ttf
Resource Han Rounded CN/TW/HK/JP ResourceHanRounded<lang>-VF.otf ResourceHanRoundedSC-VF.otf
Inter InterVariable.ttf —
Maple Mono MapleMono[wght].ttf —
Maple Mono NF MapleMono-NF-Regular.ttf —
LXGW WenKai LXGWWenKai-Regular.ttf —

每个语言家族都是独立的 CSS family。fallback 只补充该语言源 cmap 中不存在的码点,因此浏览器仍能从同一个 font-family 解析仅 SC 才有的字符。

使用方法

从 CDN 引用一个 CSS 文件,再用其中的 family 名设置 font-family:

<link rel="stylesheet" href="https://fonts-cdn.sharpdotnut.com/Inter/Inter.css">

<style>
  body {
    font-family: "Inter", system-ui, sans-serif;
  }
</style>

CSS 内的分片使用相对路径,浏览器按 unicode-range 只为页面实际出现的字符加载对应 WOFF2,不需要手工引 <link rel="preload">。地址为 https://fonts-cdn.sharpdotnut.com/<slug>/<slug>.css,slug 与上表家族同名(如 SourceHanSansCN、MapleMonoNF)。

完整的家族地址列表、缓存与 CORS 配置、自托管流程与排错见 USAGE.md。

fonts-download — 下载源字体

参数 含义
-c, --config PATH 配置文件(默认 fonts.toml)
-o, --output DIR 覆盖 settings.output_dir
--cache-dir DIR 覆盖 settings.cache_dir
-j, --jobs N 并行下载数(默认 settings.concurrency)
--only ID 匹配字体 id(可重复,逗号分隔的 glob)
--exclude ID 跳过匹配的字体 id
-f, --force 重新下载并解压已存在的文件
-n, --dry-run 只解析计划,不下载
-l, --list 列出配置的字体

退出码:0 成功,1 配置错误,2 一个或多个字体失败,130 中断。除非指定 --force,已存在的文件会被跳过;下载与解压均为原子操作。GITHUB_TOKEN(或 GH_TOKEN)可提高 GitHub API 速率限制。

fonts.toml 恰好声明六个下载项:inter、source-han-sans-vf、resource-han-rounded-vf、maple-mono、maple-mono-nf、lxgw-wenkai。其路径相对于配置文件。压缩包的 include/exclude 模式匹配成员文件名。

fonts-build — 构建配置的 CDN 家族

uv run fonts-build [--config cdn.toml] [--only ID] [--skip-baseline]
参数 含义
-c, --config PATH 构建配置(默认 cdn.toml)
-o, --output DIR 覆盖 settings.output_dir
--only ID / --exclude ID 用可重复、逗号分隔的 fnmatch 模式选择家族 id
-f, --force 重建所选家族的全部分片
-n, --dry-run 打印计划,不写输出
-j, --jobs N 覆盖并行 worker 数
--skip-baseline / --no-skip-baseline 覆盖 baseline 编码设置
--no-verify 跳过产物契约检查
--report PATH Markdown 报告路径(默认 <output_dir>/build-report.md)
--no-report 不写构建报告

配置加载器会拒绝未知键、重复的家族 id/slug、非法类型与非法值。家族路径相对于 cdn.toml;-o/--output 相对于当前工作目录。请先运行 fonts-download 再运行 fonts-build;两个命令刻意保持分离。

除非指定 --no-report,每次运行都会写入 <output_dir>/build-report.md,包括 dry-run 与部分失败。报告记录每个家族的状态、分片数、字节数与第一条校验错误。汇总中的 reused 表示该家族没有编码任何分片;built 表示至少编码了一个分片。复用受构建指纹约束(主源与 fallback 源摘要、影响字节的配置、流水线版本),一次完成的重建还会删除该家族目录中的过期分片。

fonts-upload — 上传到 Cloudflare R2

uv run fonts-upload [--config cdn.toml] [--only ID] [--dry-run] [--force]
参数 含义
-c, --config PATH 构建配置(默认 cdn.toml)
-o, --output DIR 覆盖 settings.output_dir
--only ID / --exclude ID 用可重复、逗号分隔的 fnmatch 模式选择家族 id
-n, --dry-run 列出远端缺失的对象,不上传
-f, --force 即便远端分片大小一致也重传
-j, --jobs N 并发 PUT 数(默认 8)

凭据从进程环境读取;当前工作目录下的 .env(已在 .gitignore 中)会被自动加载,但已存在的进程环境变量优先:

uv run fonts-upload --only Inter          # 自动读取 ./.env
uv run --env-file ../secrets/.env fonts-upload --only Inter   # 或显式指定文件

.env 每行一个 KEY=VALUE,支持 # 注释、export 前缀与成对引号;未知键会被忽略。也可用 setx 把变量持久化到当前用户环境。

变量 含义
R2_ENDPOINT https://<account-id>.r2.cloudflarestorage.com,必须是 http(s) URL
R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY R2 API token 的 Access Key ID 与 Secret
R2_BUCKET 目标桶名
R2_REGION 可选,默认 auto

上传前先按 manifest 校验本地目录:schema 必须为 2、slug 必须与目录名一致、每个分片的 bytes 与 blake2b 必须同时匹配。任何不一致都会在发出第一个 PUT 之前失败。对象键为 <slug>/<file>,与 dist/<Family>/ 同构;分片带 font/woff2 与 Metadata.blake2b,CSS 为 text/css; charset=utf-8,manifest 为 application/json; charset=utf-8。

分片文件名本身含内容哈希,因此远端已存在且大小一致的分片默认跳过;CSS 与 manifest.json 是稳定路径,每次运行都会覆盖。该命令不设置 Cache-Control、不删除远端对象、不配置 ACL 或 CORS——缓存策略与桶级配置请另行处理。

上传期间为每个家族显示一条进度条:已完成对象数 / 总数、已上传字节、已用时间与预计剩余时间;对象失败不会中断其余对象,失败明细在家族结束时逐条列出。

退出码:0 成功,1 配置或客户端错误,2 至少一个对象或家族失败,130 中断。

font-sharder — 构建单个家族

该命令由子模块 font-split 提供;它也可以脱离本仓库独立使用。

uv run font-sharder <font.ttf> [-o dist] [--css-family "Family Name"] [--fallback full-coverage.ttf]

选项包括 --family、--css-family、--fallback、--chunk-size、--features、--deps、--areas、--include-cjk-ext、--jobs、--force、--skip-baseline、--no-verify、--dry-run、--manifest-out 与 --font-number。--fallback 只补充 fallback cmap 中存在、而主 cmap 中缺失的码点;这些分片排在主分片之后,位于同一目录并属于同一 CSS family。

分片命名为 <Family>-<order>-<tag>.<content-hash>.woff2。内容哈希使分片 URL 在多次构建之间保持 immutable。manifest 记录每个分片的 origin(primary 或 fallback)以及 fallback 源元数据。

部署产物

WOFF2 分片使用:

Content-Type: font/woff2
Cache-Control: public, max-age=31536000, immutable

CSS 与 manifest.json 使用 Cache-Control: public, max-age=300;稳定的家族路径会在重建时覆盖,而内容哈希分片名可避免残留旧字体字节。为浏览器字体请求配置 CORS。

fonts-upload 只设置 Content-Type(分片另加 Metadata.blake2b),不写入上述 Cache-Control;这两条缓存头需要在桶侧用缓存规则或自定义域策略配置。

开发检查

项目没有单元测试套件。sharder 在每次真实构建中校验自身产物(覆盖、互斥、unicode-range 往返、布局约束、manifest 摘要、字体可读性与 shaping、CSS 引用、体积预测)。全量构建即是端到端检查:

uv run fonts-build --dry-run                    # 只规划,不写文件
uv run fonts-build --skip-baseline              # 构建全部十二个家族
uv run fonts-build --skip-baseline              # 再跑一次:应全部复用
uv tool run ruff check src font-split/src font-split/tools
uv tool run ruff format --check src font-split/src font-split/tools

--no-verify 会跳过八项契约检查,仅在调试失败家族时使用。用 --only <id> 重建单个家族。

分片器的完整文档见 font-split README;更细的契约与实现说明见 产物契约、实现说明 与 背景研究。主仓库开发约定见 AGENTS.md。

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages