Skip to content

Commit be143c7

Browse files
chaxusclaude
andcommitted
docs(fonts): add the one-pager, the live numbers, and the purge that is still owed
A new session should not have to read three exploration notes to learn the one rule the catalog runs on. docs/changelogs/2026-08-22-font-licensing.md is the page that holds it, plus the negative list (what #170 and #178 each got wrong), the measurements, and the commands. Records what the deployed site actually returns: the removed slots 404 at the origin, the new ones serve, and /fonts/017 without a cache-buster still hands back 4 MB of SimSun from a three-day-old cached copy. That last line is the step nobody can take from the repository -- a Purge Cache in the Cloudflare dashboard -- so it is now written where the next person will see it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 0802ca6 commit be143c7

3 files changed

Lines changed: 112 additions & 3 deletions

File tree

‎CLAUDE.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,12 @@ v7 代码分支(OO_VARIANT、页面级 x2t 打开转换、empty_bin 模板、v
804804
开源字体——**换的是注册表不是字节**:专有 family 的 `__fonts_infos` 行改指到替代
805805
字体已经占着的位置上,文件不改名、不复制。`public/fonts/` 327 MB → 184 MB,
806806
文档里写着"宋体" / "Arial" 照常解析。改这块前先读
807-
[docs/explorations/2026-08-22-font-substitution-solved.md](docs/explorations/2026-08-22-font-substitution-solved.md)。
807+
[docs/changelogs/2026-08-22-font-licensing.md](docs/changelogs/2026-08-22-font-licensing.md)(一页纸)
808+
与 [docs/explorations/2026-08-22-font-substitution-solved.md](docs/explorations/2026-08-22-font-substitution-solved.md)。
809+
**未完成的一步:Cloudflare 面板 Purge Cache。** `_headers` 给 `/fonts/*` 一年
810+
`immutable`,被删掉的名字不会再有新内容覆盖缓存副本——上线实测 `/fonts/017`
811+
不带 cache-buster 仍返回 4 MB 的旧 SimSun(`age` 三天多),带 buster 是 404;
812+
清掉之前那批专有字体仍可按旧 URL 从本站取到。
808813
1. **唯一那条规则**:位置 P 上那个文件里写的 family 名,必须属于某个指向 P 的
809814
`__fonts_infos` 行。引擎排版时读的是加载文件里的 `m_pFaceInfo.family_name`,
810815
再拿它过一遍匹配器(`sdk-all.js` 的 `StringShaper.Shape`);名字指到别处,
@@ -863,7 +868,7 @@ v7 代码分支(OO_VARIANT、页面级 x2t 打开转换、empty_bin 模板、v
863868
docs/superpowers/plans/2026-08-15-v9-test-coverage-strategy.md,新用例
864869
按它落位,台账在 docs/test-matrix.md(空白格 = 待补);**新开会话先读
865870
docs/changelogs/2026-08-15-v9-regression-campaign.md**(战役一页纸:结论、
866-
数字、缺陷清单、文件位置、怎么跑、下一步)**与 docs/changelogs/2026-08-16-roadmap-sprint.md**(路线图冲刺一页纸:路由拆分 / 帮助中心 / 多语言 / WebMCP / PR 流程变化)**与 docs/changelogs/2026-08-20-issue-144-memory-and-delivery.md**(issue #144 一页纸:x2t 的 283 MB 内存要求为什么动不了、"别再试这些"负面清单、SW 更新投递为什么曾经完全失效、等报告人截图里的哪串字)。战役进展:第 1 天的"非 ASCII 文件名 P0"已被第 2 天推翻
871+
数字、缺陷清单、文件位置、怎么跑、下一步)**与 docs/changelogs/2026-08-16-roadmap-sprint.md**(路线图冲刺一页纸:路由拆分 / 帮助中心 / 多语言 / WebMCP / PR 流程变化)**与 docs/changelogs/2026-08-20-issue-144-memory-and-delivery.md**(issue #144 一页纸:x2t 的 283 MB 内存要求为什么动不了、"别再试这些"负面清单、SW 更新投递为什么曾经完全失效、等报告人截图里的哪串字)**与 docs/changelogs/2026-08-22-font-licensing.md**(字体版权一页纸:那条唯一的规则、"别再试这些"负面清单、上线后的实测数字、唯一未完成的 Purge Cache)。战役进展:第 1 天的"非 ASCII 文件名 P0"已被第 2 天推翻
867872
(跑道被 SW 击穿,见 docs/explorations/2026-08-15-corpus-harness-sw-route-bug-and-open-failure-guard.md),
868873
真正修掉的是"打开失败永久转圈"(`installOpenFailureGuard`)与
869874
"Save 按钮常灰"(守卫 5)。v9 release 公告冻结至战役通过。
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Changelog — 字体版权与多语言回退(一页纸)
2+
3+
日期:2026-08-22 · 分支:main(PR 制)· 相关 PR:#168 → #170(revert #174)→ #178 → **#184(本轮,已合并)**
4+
5+
给新开会话看的一页纸:**现在什么状态、规则是什么、别再试什么、数字在哪、还差谁一步。**
6+
逐步细节见 [替换是怎么做成的](../explorations/2026-08-22-font-substitution-solved.md),
7+
体系文档见 [docs/fonts.md](../fonts.md) 与 [docs/font-licenses.md](../font-licenses.md)。
8+
9+
## 一句话结论
10+
11+
**79 个专有字体(171 MB)已从 catalog 移除,全部由开源字体承接,文档里写着"宋体" /
12+
"Arial" 照常解析。** `public/fonts/` 327 MB → 184 MB。线上已部署并实测通过。
13+
**唯一未完成的一步在仓库外**:Cloudflare 面板做一次 Purge Cache(见文末)。
14+
15+
## 规则(这是全部要点)
16+
17+
> **位置 P 上那个文件里写的 family 名,必须属于某个指向 P 的 `__fonts_infos` 行。**
18+
19+
引擎排版时读的是**加载文件里**的 `m_pFaceInfo.family_name`,再拿它过一遍匹配器
20+
(`sdk-all.js` 的 `StringShaper.Shape`)。名字指到别的条目上,就会"用 A 排版、用 B
21+
画字形",整页 glyph 错位。原始 vendor catalog 的 267 个被引用位置全部满足这条。
22+
23+
配套两条,缺一条就是同一种错位或空白:
24+
25+
1. **新增 family = 位置 + `__fonts_infos` 行 + `g_fonts_selection_bin` 记录**,三样齐全。
26+
2. **CJK 字体必须是 TrueType(glyf)**,不能是 CFF(`OTTO`)——编辑器渲染正常,
27+
但 x2t 往 PDF 里一个字形都嵌不进去,导出的中文全是空白。
28+
29+
## 别再试这些(负面知识)
30+
31+
| 想法 | 为什么不行 |
32+
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
33+
| 把替代字体的**文件名**写进专有字体的位置(PR #170) | 破坏上面那条规则,`Hello` 渲染成 `Fcjjm`,线上一小时后被 revert |
34+
| 认为"一个位置就是一个字体身份,不能共享"(#178 结论) | 不成立。多行指向同一位置完全正常,只要该位置的文件名属于其中一行 |
35+
| 认为 `__fonts_ranges` 第三个数是运行时下标 | 不是。sdkjs 与 PDF 引擎两处消费者都按 `infos[triple[2]][0]` 读,就是 `__fonts_infos` 行号 |
36+
| 认为 `g_fonts_selection_bin` 不可写、必须整体重建 | 阅读器就在 `sdk-all.js` 里;`bin/lib/selection-bin.mjs` 双向实现,逐字节往返,188 个文件记录可复现 |
37+
| 删掉专有 family 名(而不是替换) | `sdk-all.js` 硬编码 Arial / Calibri / SimSun / Tahoma / Batang / MS Mincho,删掉直接白屏 |
38+
| 用 pan-CJK 的 Noto Sans/Serif **CJK** SC | CFF 轮廓,导出 PDF 中文空白(见上) |
39+
| 只用 `Hello` 验证 | 基本 ASCII 的 glyph 顺序在多数拉丁字体里恰好一致,坏了也看不出来。测试文本必须跨过 U+A0 |
40+
41+
## 数字
42+
43+
| 项 | 值 |
44+
| ---------------------- | ------------------------------------------------------------------------------- |
45+
| 删除 | 79 个文件 / 171.2 MB(华文、方正、中易、长城、Stone、微软、Monotype) |
46+
| 新增 | 15 个文件 / 28.6 MB(SIL OFL 1.1) |
47+
| `public/fonts/` | 327 MB → 184 MB |
48+
| catalog 位置 / family | 267 → 282 / 193 → 202 |
49+
| selection 记录 | 273 → 288 |
50+
| 回退 range | 3075 → 2984 |
51+
| 中文 family 得到真粗体 | 48 个 |
52+
| 中文文档首次下载 | 宋体一路 3.6 MB(压缩 2.1)、黑体一路 9.9 MB(压缩 5.9,同时是所有 CJK 的回退) |
53+
54+
## 怎么跑、怎么验
55+
56+
```bash
57+
node bin/font-license-sweep.mjs --check # 只报告方案
58+
node bin/font-license-sweep.mjs # 执行(源字体在 vendor-fonts/,不入库)
59+
npx vitest run test/unit/font-catalog-licensing.test.ts test/unit/font-selection-bin.test.ts
60+
npx playwright test test/e2e/font-substitution.spec.ts test/e2e/pdf-cjk-export.spec.ts
61+
E2E_BASE_URL=https://edit.chaxus.com npx playwright test test/e2e/font-substitution.spec.ts # 打线上
62+
```
63+
64+
四层用例都做过反向验证(去掉修复即变红),阈值与实测值写在探索文档里。
65+
**视觉 E2E 结构上看不见字体错误**(它比的是"原始 vs 存回",两侧同样的错字体),
66+
所以字体改动一律要看真实渲染,且用 `pluginMethod_PasteHtml` 灌文字(键盘会丢字符)。
67+
68+
## 上线后实测(2026-08-22,部署完成后)
69+
70+
```
71+
/fonts/017?cb=随机 404 ← SimSun,源站已无
72+
/fonts/016?cb=随机 404 ← 微软雅黑,源站已无
73+
/fonts/267?cb=随机 200 10,423,464 ← Noto Sans SC(新)
74+
/fonts/269?cb=随机 200 3,764,940 ← Noto Serif SC(新)
75+
/fonts/017(不带 cb)200 4,077,068 age=329273 ← 旧 SimSun 的缓存副本仍在发
76+
```
77+
78+
线上跑 `font-substitution` + `pdf-cjk-export`:6 条全过(逐像素差 0.000%~0.044%,
79+
PDF 墨迹 1.04%)。Production smoke、Docker 镜像构建均绿。
80+
81+
## 还差一步(仓库里做不到)
82+
83+
**在 Cloudflare 面板执行一次 Purge Cache。** `_headers` 给 `/fonts/*` 的是一年
84+
`immutable`,这在"文件名即内容"时是对的,但**删除**打破了那个前提:被删的名字不会
85+
再有新内容去覆盖缓存副本。清掉之前,那批专有字体仍可按旧 URL 从本站取到(新的
86+
`AllFonts.js` 已不再引用,正常使用不会请求到)。清完后 `/fonts/017`(不带
87+
cache-buster)也应变成 404。
88+
89+
下次批量移除 vendor 资源时,把这一步一并计划进去。

‎docs/explorations/2026-08-22-font-substitution-solved.md‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,20 @@ Droid Sans Fallback(TrueType)上,中文立刻回来了(PDF 76 KB)。
199199
**反向验证**:把 sweep 的 CJK 源换回那两个 CFF 子集重跑一遍,用例报
200200
`the exported PDF page is blank`(编辑器里 12.17% 墨迹,PDF 里 0.02%,正常 1.04%)。
201201

202+
## 上线后实测(PR #184 合并部署之后)
203+
204+
```
205+
/fonts/017?cb=随机 404 ← SimSun,源站已无
206+
/fonts/016?cb=随机 404 ← 微软雅黑,源站已无
207+
/fonts/267?cb=随机 200 10,423,464 ← Noto Sans SC(新)
208+
/fonts/269?cb=随机 200 3,764,940 ← Noto Serif SC(新)
209+
/fonts/017(不带 cb)200 4,077,068 age=329273(3.8 天) ← 旧 SimSun 的缓存副本
210+
```
211+
212+
`E2E_BASE_URL=https://edit.chaxus.com` 跑 `font-substitution` + `pdf-cjk-export`:
213+
6 条全过(逐像素差 0.000%~0.044%,PDF 墨迹 1.04%)。Production smoke 与 Docker
214+
镜像构建都绿。
215+
202216
## 部署之后还差一步:缓存里的旧字体
203217

204218
`_headers` 给 `/fonts/*` 设的是 `public, max-age=31536000, immutable`。这条规则在
@@ -207,7 +221,8 @@ Droid Sans Fallback(TrueType)上,中文立刻回来了(PDF 76 KB)。
207221
`/fonts/017` 是 404(源站确实没有了),不带 buster 拿到的是 4 MB 的旧 SimSun,
208222
`age` 逐秒增长。
209223

210-
所以合并部署后要**在 Cloudflare 面板做一次 Purge Cache**(仓库里做不到:`_headers`
224+
上面那张表就是它:源站 404、缓存里还在发 4 MB 的旧 SimSun。
225+
所以要**在 Cloudflare 面板做一次 Purge Cache**(本轮尚未执行)(仓库里做不到:`_headers`
211226
只能声明将来的缓存策略,改不了已经发出去的副本)。在此之前那些专有字体仍然可以从
212227
本站域名取到——新的 `AllFonts.js` 已经不再引用它们,正常使用不会请求到,但严格说
213228
仍在提供。

0 commit comments

Comments
 (0)