崮生(子虚)
a095cd0590
feat: 详情页全面接入 font-config + 防闪烁
...
- font-config.json: 新增 bodyTitle/bodyText/charsetPreview 字段
为每个字体配置通过 lint 检查的专属文案
- FontDetail.vue: 正文/字符预览使用 config 字段替代写死文案
大标题/正文/字符预览加 v-if=meta 守卫,消除 hydrate 闪烁
h1 标题使用 displayName
- lint-font-config.ts: 扩展检查范围至所有文本字段
- FontUserConfig 类型同步更新(后端+前端)
2026-07-30 23:30:40 +08:00
崮生(子虚)
23728ba02b
refactor: 回退内存闸门为固定并发(保留分组调度) + 前端改进
...
后端:
- subset_queue.ts: 移除基于内存的门控(LLRT GC 不释放 RSS),
回退到固定并发模型,保留分组调度优化(相同字体批处理重用缓存)
- config.ts: 恢复 subsetConcurrency / subsetQueueTimeoutSeconds,
移除 subsetMemSoftLimitMB
- routes/subset.ts, app.ts, routes/config.ts: 同步恢复
- api.ts, README: 同步更新
前端:
- FontSelector.vue: 下拉项和触发器标记临时字体(橙色徽章)
- CodeBlock.vue: 新增轻量代码块组件,内置 CSS/HTML/JS 分词高亮(0依赖)
- Home.vue: 集成 CodeBlock 替换原始 <pre> 代码块
2026-07-30 23:11:54 +08:00
崮生(子虚)
d2f6717348
feat: 排队请求按字体分组,同字体连续处理
...
- withMemoryGate 新增 groupKey 参数,subset.ts 传入 fontPath
- pickNext: 优先唤醒与上一个相同 groupKey 的等待者
- 同字体请求连续处理:字体 buffer/解析对象在 GC 窗口内被复用
- 大幅降低排队积压时的内存峰值(避免反复加载不同字体)
2026-07-30 22:53:39 +08:00
崮生(子虚)
7403414439
feat: 用内存水位闸门替代固定并发限制
...
- subset_queue: 新增 withMemoryGate,基于 RSS 决定排队/执行
- getRssMB: Node 用 process.memoryUsage(),LLRT 用 /proc/self/statm
- 请求完成后主动 __gc() 加速内存回收
- config: SUBSET_MEM_SOFT_LIMIT_MB=600 替代 SUBSET_CONCURRENCY=4
- 小请求可高并发(内存够),大请求自动排队(防 OOM)
2026-07-30 22:51:37 +08:00
崮生(子虚)
b86d2fcb53
fix: 并发默认调到4,队列超时改为可配(30s)
...
- SUBSET_CONCURRENCY 默认 2 → 4(900M 内存够用)
- 新增 SUBSET_QUEUE_TIMEOUT 环境变量(默认30秒,原硬编码10秒)
- 压测发现10秒超时太短:50并发只有8成功,其余排队超时
2026-07-30 21:46:21 +08:00
崮生(子虚)
65ba984972
feat: 字体子集化并发队列控制(服务端+客户端)
...
后端:
- subset_queue.ts: Semaphore 并发控制器,排队超时返回 503
- subset.ts: fontSubset 调用包裹在 withConcurrencyLimit 中
- config.ts: 新增 SUBSET_CONCURRENCY 环境变量(默认 2)
- routes/config.ts: 暴露 subsetConcurrency 到配置 API
前端:
- api.ts: ServerConfig 新增 subsetConcurrency 字段
- webfont-sdk.js: 客户端并发池(默认 4),setMaxConcurrent API
通过 document.fonts.load 精确追踪字体加载完成释放槽位
2026-07-30 21:33:45 +08:00
崮生(子虚)
abd75985b7
feat: 临时字体保留时限改为秒为单位(TEMP_RETENTION_SECONDS)
...
- TEMP_RETENTION_HOURS → TEMP_RETENTION_SECONDS,默认 10800(3小时)
- 前端显示自适应:秒/分钟/小时
- README docker-compose 示例同步更新
2026-07-30 21:24:38 +08:00
崮生(子虚)
19b14f58ea
feat: 字体元数据三层架构 + 临时字体保留机制 + 列表页覆盖率排序
...
- font_meta.ts: cmap 解析覆盖率 + name 表提取字体信息(版权/作者/许可) + 人工配置(font-config.json)
- 三层缓存: 进程内存 → .meta.json 磁盘 → font-config.json stat mtime 热更新
- 临时字体保留机制: TEMP_RETENTION_HOURS(默认3h), 超时未使用自动删除
- 列表页: 覆盖率标签 + 按字符集覆盖率排序
- 详情页: 标签/开源链接/简介 + 字体信息面板(设计师/版权/许可)
- 首页: 红色警告提示切勿上传非商用字体 + 显示保留时限
- SSG noindex bug 修复
2026-07-30 21:19:09 +08:00
崮生(子虚)
cdffd4cb57
feat: 字体列表页 + 详情页 SSG + LazyTrigger 懒加载组件
...
- FontList.vue: 字体列表页,卡片网格布局,支持拼音搜索,点击进入详情
- FontDetail.vue: 字体详情页,SSG 模板 + 服务端占位符替换
- LazyTrigger.vue: 可复用懒触发组件,IntersectionObserver 进入视口回调
- 首页添加字体列表入口,FontDetail 返回列表
- placeholders.ts: 前后端共享的占位符常量
- 后端 font_detail.ts: 读取 SSG 模板并替换占位符
2026-07-30 20:32:42 +08:00
崮生(子虚)
8273508c79
feat: StatsPanel 视口内才轮询 + 底部进度条动画
...
- IntersectionObserver + document.visibilityState 双条件控制轮询
- 卡片底部进度条 (scaleX 动画),每次刷新数据后重启动画
- 避免不可见时浪费请求
- bump version to 1.11.1
2026-07-30 17:26:24 +08:00
崮生(子虚)
27c91bcdc2
perf(observability): /api subset 响应头加 X-Timing-* 分阶段耗时
...
38ms 请求定位用:X-Timing-Find/Read/Subset/Total 分别对应 findFontPath、
readFontBuffer、fontSubset、总计。本地实测 60ms 中 58ms 在读字体文件、
裁剪仅 1ms,证伪「裁剪慢」假设。缓存命中只返回 X-Timing-Total。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 10:39:31 +08:00
崮生(子虚)
519ce0473b
perf(gpos/gsub): 部分命中 lookup 跳过空 subtable(subCount 只数命中的)
...
此前「部分命中」的 lookup(anyHit=true 但非全命中)仍把每个未命中 subtable
序列化为空 subtable(writeEmpty(Pos)Subtable,~10B/个)。这些空 subtable 的
coverage 必为空,浏览器查不到字形即跳过——直接不输出渲染等价:lookup 的
subtable 按序遍历,第一个匹配 coverage 应用,删掉必然空的 subtable 不改变
非空 subtable 的匹配结果。
GPOS:hits[j]=false 的 subtable 跳过;初夏 lookup[7] 285 subtable 仅 6 命中,
省 (279-1)×10B。
GSUB:subtableSkipable[j]=true 的 subtable 跳过(format2 永远 not skipable
故含 class 驱动 subtable 的 lookup 不受影响);not skipable 但序列化失败的
仍走 fallback 写空(保留在输出)。
与优化330/337(全空 lookup 折叠 subCount=1)互补:那些处理「全部不命中」,
本次处理「部分不命中」。anyHit=true 保证至少 1 个命中,subCount>=1 合法。
收益(30 用例 SSIM 全不变,总体积 -2408B):
- 初夏明朝-汉字标点 woff2 6012→4900 (-1112B)
- 初夏明朝-纯标点 woff2 6252→5260 (-992B)
- FiraCode woff2 8672→8560 (-112B)
- 思源黑体-标点 woff2 2940→2884 (-56B)
- 霞鹜文楷-汉字标点/纯标点 woff2 -20/-12B
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 13:42:00 +08:00
崮生(子虚)
4520a901ef
perf(otf): hmtx/vmtx 尾部相同 advance 压缩(补齐 TTF 侧 fonteditor 早做的压缩)
...
subsetOTF 的 buildSubsetMetrics 原把所有 subsetGids 展开为完整 4 字节
(advance+lsb),丢失 hmtx 规范的 numberOfHMetrics 压缩语义。CJK 全角字体
子集后几乎所有 advance 相同(如 1000),尾部 N-1 个 glyph 可只写 lsb(2字节)。
从尾部向前找最长连续相同 advance 段,numberOfHMetrics 收缩为最后一个不同
advance 的位置+1,hhea/vhea 的 numberOfHMetrics 同步 patch。
渲染等价(浏览器对 gid>=numberOfHMetrics 复用最后一个 advance),SSIM 不变。
收益(3 otf 用例 woff2 共 -144B):
- otf-白狐千字文 35880→35792 (-88B)
- otf-五个汉字 15020→14984 (-36B)
- otf-思源黑体 10120→10100 (-20B)
TTF 路径 fonteditor-core 已做此压缩(思源/霞鹜/FiraCode numHM=1),
OTF 路径手写 buildSubsetMetrics 漏做,属 TTF/OTF 对称补齐(模式③)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 13:37:17 +08:00
崮生(子虚)
ade5d279a0
fix(gsub): emitCoverage format 选择改精确字节比较(原判据过宽)
...
emitCoverage 选 format1(list) vs format2(range) 的判据从 rangeCount < n
改为精确字节比较 rangeCount*6 < n*2(即 rangeCount < n/3),与 subsetGPOS
emitCoverageFromGids 一致。
format1=4+n*2,format2=4+rangeCount*6。原判据 rangeCount<n 在 rangeCount
∈ [n/3, n)(中等长度 range,avg 1~3 gid/range)时误选 format2 致体积更大。
coverage 的 format1/format2 是等价表达(同一 gid 集合),选 format 不影响渲染。
体积(30 用例 SSIM 全不变,总体积 -168B):
- FiraCode woff2 -64B / 初夏纯标点 -32B / otf-思源 ttf -20B。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 13:17:49 +08:00
崮生(子虚)
1adbf2e0d1
refactor(gsub): unsupported lookup 也折叠 subCount=1(与 GPOS/supported 对称)
...
GSUB unsupported lookup(type5 ReverseChain 等)原逐 subtable 写空 subtable,
现折叠 subCount=1 单空 subtable,与 supported 分支(优化337) + subsetGPOS 优化330 一致。
N 个空 subtable 与 1 个渲染语义等价(都查不到 coverage 跳过)。
基准字体无 unsupported lookup(type5 罕见),30 用例 SSIM+体积完全不变,
此为模式③对称补齐,对未来含 type5 的字体严格更优。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 13:15:16 +08:00
崮生(子虚)
665a089019
perf(gsub): 全空 lookup 折叠 subCount=1(补齐 GPOS 优化330)
...
GSUB supported lookup 全空(allEmpty)时,把 N 个空 subtable 折叠为 subCount=1
的单个空 subtable,与 subsetGPOS 优化330 同类。feature 仅按 lookup index 引用,
subCount 改变不影响 feature;浏览器遍历 subtable 查 coverage,单个空 subtable
与 N 个全空 subtable 渲染语义等价(都查不到字形跳过)。
与 gsub-lookup-deletion-failed-fira 区别:不删 lookup(index 不变、feature 不受影响),
仅把全空 lookup 内的 N 个空 subtable 折叠为 1 个。
体积(30 用例 SSIM 全不变,总体积 -4520B):
- otf-思源黑体 ttf -3204B / woff2 -532B(GSUB 全空 lookup 多)
- 初夏纯标点 woff2 -520B / FiraCode woff2 -124B
FiraCode SSIM 0.9923 不变(连字字形完整保留,ink 3856/3695 一致)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 13:13:07 +08:00
崮生(子虚)
2a224b1df7
refactor(cff): origFd→privInfo 查找改 fdRemap 下标 O(1)
...
subsetCFF 重建 CharStrings 循环里,原用 usedFds 线性扫描找 origFd 对应的
privInfo(O(glyphs×FDs))。改为复用已有的 fdRemap(origFd→newFdIndex) 直接
下标取 fdInfos[fdIdx].priv(O(1))。gidOrigFids 与 fdRemap 同源构建,每个 fd
必命中;缺失则 null 走透传(与原线性扫描未命中等价)。
字节一致(思源/白狐 3 OTF 用例 sha 全同)。基准无性能收益(思源 usedFds 仅
1-2 个 FD,线性扫描 break 在第一步),纯清理 + 对未来多 FD CID 字体严格更优。
otf-subset.ts: buildSubsetGids 补充 gidToNewGid Map vs Int32Array 边界判据注释
(CID 字体 numGlyphs=65535 致 fill 成本远超 Map 低频查询),防未来重复尝试。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 12:54:41 +08:00
崮生(子虚)
cda0215b36
perf(otf): woff2 输出跳过 OTF sfnt checksum 计算
...
assembleSfnt 在 OTF→woff2 路径原总计算各表 checksum(calcTableChecksum 遍历每表全部字节,CFF 是大头)+ 目录 dirSum + head.checkSumAdjustment 回填。但 encodeTTFToWOFF2 解析 sfnt 目录只读 tag/offset/length(line 742-748 不读 off+4 checksum),也不消费 adjustment,故 woff2 输出时这些全是浪费。
与 [[woff2-skip-ttf-checksum]] 的 TTFWriter.skipCheckSum(font.js:175 woff2 分支早做)完全同类,OTF 侧原遗漏,本轮补齐。裸 OTF 输出(otf/ttf)仍算 checksum(规范要求)。
otf-思源黑体 woff2 avg 1.2→0.7ms,所有 OTF woff2 case SSIM 不变(1.0000),TTF/裸 OTF 用例不受影响。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 09:54:44 +08:00
崮生(子虚)
01f920f3f0
perf(gsub): currentSortedSubsetGids 延迟构造,无 format2 ClassDef 时省排序
...
sort 仅 ClassDef format2 / coverage range 路径需要,原入口点无条件 Array.from(origToNew.keys()).sort()。
改首次访问时惰性构造(getter),无 format2 ClassDef 的字体(令东无 GSUB / 霞鹜/初夏 GSUB 多为 format3)永不算。
小字集收益不可测(sort <70μs),全字符集场景省 ~3.2ms 固定开销。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 08:33:26 +08:00
崮生(子虚)
7b3b0ef592
perf(otf): buildSubsetGids 用 gidToNewGid Map 替代 indexOf O(n) 查找
...
subsetOTF 的 buildSubsetGids 原 `cpToNewGid.set(cp, subsetGids.indexOf(gid))`
对每个 codepoint 线性遍历 subsetGids 找索引,O(N×M),全字符集场景退化为 O(N²)。
改用 gidToNewGid Map(gid→新gid)在 push 时记录,O(1) 查找;同时替代 seenGid
Set(gidToNewGid.has 等价)。
基准测试 OTF 用例(小字集)SSIM 全一致、输出字节 IDENTICAL(otf-五个汉字
26536/15024、otf-思源 19188/10664、otf-白狐千字文 35896)。算法缺陷修复,
大字集 OTF 子集化(完整 CJK OTF)有实质收益。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 08:23:01 +08:00
崮生(子虚)
71105374ee
perf(gsub/gpos): lookup 偏移槽改 writeUint16 占位 + writeInt16At 回填
...
subsetGSUB/subsetGPOS 的 lookup 偏移槽(lookupCount 个)与 gsub unsupported
分支的 subtable 偏移槽,从 reserveOffset16 闭包模式改为 writeUint16(0) 占位 +
记录 slot 起点 + 序列化后 writeInt16At 回填——与已做的 subtable 槽优化329、
writeChainFormat3 coverage 槽(gsub-writechainformat3)统一。
消除 lookupCount 个 per-lookup 闭包 + patch 对象分配。思源 GSUB 56 lookup、
FiraCode/霞鹜同。gsub unsupported 分支 subtable 槽也随之统一(原仅 supported
分支用优化329)。
收益不可测(FiraCode min 1.8346 vs 1.8439、思源 1.505 vs 1.479 均噪声内)——
lookupCount 级闭包非瓶颈,被 brotli/glyf transform 淹没。但消除语义冗余 +
统一两侧偏移槽写法。基准测试全 SSIM 一致、输出字节 IDENTICAL。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 08:05:55 +08:00
崮生(子虚)
2ed666ec58
perf(gsub): parseChainFormat3 coverage/records 数组按 count 预分配
...
arr: number[][] 与 records 对象数组原用空数组 + push,fmt3 高频路径(每 subtable
三组 coverage + records)触发 GrowFastSmiOrObjectElements。改按已知 count/seqCount
预分配容量 + 下标赋值。
node --prof GrowFast 4.3%→3.6%、ArrayPrototypePush 3.7%→2.5%。FiraCode 输出字节
不变(acc 一致),29 用例 SSIM 全通过。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 07:42:40 +08:00
崮生(子虚)
98102bc3be
perf(gsub): writeChainFormat3 coverage 槽改 writeInt16At 回填
...
type6 format3 的 coverage 偏移槽原用 allHolders: number[][] + reserveOffset16
闭包模式(每槽 [0] 单元素数组 + patches push + flush 回填)。改为 writeUint16(0)
占位 + covSlotPositions 记录字节位置 + writeInt16At 直接回填(与优化329 subtable
slot 同思路),消除每槽的 [0] 数组分配 + reserveOffset16 闭包。
node --prof 显示 GrowFastSmiOrObjectElements 3.7% + ArrayPrototypePush 3.2%
主来自 writeChainFormat3/serializeChainedContextSubst 的 allHolders grow。
FiraCode subsetGSUB min 0.229→0.216ms(-5.7%),端到端 avg 3.4→2.9ms(-15%),
29 用例输出字节全相同 SSIM 一致。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 07:39:09 +08:00
崮生(子虚)
e8989c7fa4
perf(cff): rewriteCharstring 分支按热度重排
...
与 collectSubrRefs 同构的字节分派,operand(b0>=32)提前到首判定。思源千字文
subsetCFF 0.4920→0.4819ms(-2.1%),CFF 输出 sha 一致。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 07:27:00 +08:00
崮生(子虚)
ca34a9e648
perf(cff): collectSubrRefs 分支按热度重排
...
operand(b0>=32)占 CJK charstring 绝大多数字节,提前到首判定。旧顺序按字节值
升序(255→28→29→32-246),最热的 32-246 单字节 operand 要先 3 次比较不命中才进入;
现 b0>=32 一次比较命中热路径。
思源千字文 subsetCFF 0.5137→0.4884ms(-4.9%),CFF 输出 sha 一致(纯分支顺序调整)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 07:25:37 +08:00
崮生(子虚)
d2b3b1939f
perf(cff): 跳过无 CALLSUBR 字形的 rewriteCharstring
...
collectSubrRefs 返回是否含 CALLSUBR,subsetCFF 据此标记每个字形。重建 CharStrings
时仅含 CALLSUBR 的字形走 rewriteCharstring(重映射 local subr 编号),无 CALLSUBR 的
直接透传原字节——含 CALLGSUBR 但无 CALLSUBR 的透传等价(global subr 不子集化,
operand 原样)。
思源千字文 241 字形仅 65(27%)含 callsubr,176 个无 callsubr 字形原先白白跑一遍
rewriteCharstring 完整字节扫描。subsetCFF 0.74→0.51ms(-30%)。
三 OTF case CFF 输出 sha 全 IDENTICAL(纯等价变换)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 07:17:12 +08:00
崮生(子虚)
7a76208558
perf(gsub): coverageAllOutOfSubset range 相交判定改二分
...
COV_RANGE 分支逐 gid 展开 range 查 gidLookup(O(range length)),
对全子集外的 coverage 要遍历完所有 gid 才确认 outOfSubset。初夏 fmt3
range 展开总 20388 gid(300 range / 12 子集 gid),逐 gid 是预扫描主开销。
改用 currentSortedSubsetGids 二分:range [start,end] 是否含任一子集 gid
等价于排序子集数组存在 gid ∈ [start,end](gidLookup[g]>=0 ⟺ g 是子集 gid)。
O(log n) per range。微基准 10×(0.012→0.001ms),subsetGSUB 端到端初夏 -6%。
等价性:end clamp/越界语义保留(sortedGids 全 <numGlyphs,二分天然限范围);
COVERAGE_MAX_EXPAND 守卫保留(与 readCoverageRemapped origNonEmpty 一致);
origNonEmpty 对所有有效 range 非空标记(含越界 range,原版同等价)。
字节一致性:11 用例(含 FiraCode 两文本)subsetGSUB 输出 sha 全 IDENTICAL。
SSIM 30 用例全通过(最低 0.9876 篆体固有)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 07:01:35 +08:00
崮生(子虚)
6d224c2405
perf(gpos): writeEmptyPosSubtable 空 subtable coverageOff 直接写常量
...
supported lookup 未命中 subtable(初夏 577 个)走空路径,原用
reserveOffset16(subStart, () => coverageAfter) 写 coverageOff——但 SinglePos/PairPos/
Cursive 的 coverageOff 是相对 subStart 的编译期固定常量(6/10),无需 patch 回填。
改直接 writeUint16(常量),省 per-subtable 的 patch 对象 + 闭包分配 + flush 遍历回填。
字节一致性:8 字体 GPOS 输出 sha 全 IDENTICAL。
性能 best-of 6000(min):初夏纯标点 0.0354→0.0303(-14%)、初夏汉字标点 -14%、
FiraCode -14%、霞鹜/得意黑 -3~5%;思源/鸿蒙噪声级(绝对 <0.0003ms)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 06:46:55 +08:00
崮生(子虚)
bda73ccea8
perf(gsub-reachable): format3 失败快判内联主循环,省完整 collectChainRefs 调用
...
初夏纯标点 280 个 format3 首轮全失败(276 个 backtrack 段为空,失败在 input 段),
FiraCode 207 个 format3 中 121 个 backtrack 首 gid 即不在 reachable。原实现每个失败
format3 都要走完整 collectChainRefs(函数调用 + refsReuse/ctxGidsReuse.clear +
format3 分支进入 + coverageFirstExcludedGid),是首轮固定开销。
format3 触发需 backtrack/input/lookahead 三段全部 gid 在子集,故任一非空段的首个
coverage 首 gid 不在 reachable 即可判定失败。内联读 format + 依次查三段第一个非空段
首 gid,命中失败则直接记 failGidMap 跳过 collectChainRefs。
字节一致性:5 字体 reachable sha 全 IDENTICAL。
性能 best-of 5000(min):初夏 0.0557→0.0435(-22%)、FiraCode 0.0999→0.0903(-10%)、
霞鹜 -3%、得意黑 -7%、鸿蒙 -8%。快判未命中走完整路径(含 errorFlag 安全网),越界保护
subAbs+6/covOff+6 ≤ byteLength,损坏表降级 collectChainRefs。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 06:41:02 +08:00
崮生(子虚)
0cfd9851df
perf(gpos): subsetGPOS 全模块 origToNew(Map)→gidLookup(Int32Array)
...
套用 gsub 侧已验证的 Int32Array gidLookup 模式到 GPOS:
- subsetGPOS 入口构建 gidLookup(动态容量 maxOrigGid+1,fill -1)
- remapGid 的 Map.get(7处)+ subtableCoverageHits 的 Map.has 改数组索引
- 涉及函数签名全部 origToNew:Map → gidLookup:GidLookup
A/B 11 字体字节全 IDENTICAL;基准测试 SSIM 全通过。
性能 best-of N=2000:
- 思源千字文中段 min -2.6%
- 思源标点 min -2.5%
- 千字文96 min -1.2%
- FiraCode 持平(GSUB 已用 gidLookup,GPOS 增量小)
关键:gidLookup[越界] 返回 undefined(非 -1),GPOS coverage 常引用
大于子集 maxGid 的 gid,remapGid 须 origGid < length 保护(否则
undefined < 0 = false 误判有效,同 gsub type234 undefined bug)。
容量必须动态,65536 固定容量 fill 在小 GPOS 用例倒贴(篆体 +12.5%)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 05:36:49 +08:00
崮生(子虚)
ae41a99e95
perf(gsub): serialize type2/3/4 大 coverage 反转遍历 + 修越界 undefined bug
...
serializeMultipleSubst/serializeAlternateSubst/serializeLigatureSubst 原实现
readCoverageGids 全量展开 coverage 再逐个查 gidLookup,与 serializeSingleSubst
([[gsub-serialize-single-bigcov-reverse]] 早修)同类的 serialize 侧浪费。
probe 实测:白狐千字文 type3 coverage=1000、初夏 type3=329/type4=97、霞鹜 type4=94,
但子集仅命中个位数。CJK 字体(令东/思源 TTF+OTF)type2/3/4 全无调用。
优化:套用 serializeSingleSubst 的反转模式——coverageCount 判大小,coverage 远大于
子集(covGidCount > subsetLen*4 && >16)时遍历 currentSortedSubsetGids 用
coverageIndexOf 二分定位 coverage 下标 idx(即 set 偏移数组下标),替代全量展开。
附带修复原版越界 bug:原版 gidLookup[covGids[i]] 当 covGids[i] 超出 numGlyphs
(损坏 coverage,白狐实测含)返回 undefined,`undefined < 0` 为 false(NaN 比较)漏过,
产生 from=undefined 的非法 AlternateSet entry(emitCoverage 写无效 gid)。
两路径统一改 `!(fromNew >= 0)`(undefined>=0 也是 false → 跳过),与反转路径行为一致。
区别于 reachable 侧 readCoverageRemapped 早有 numGlyphs 越界保护(line 221-261)。
A/B:初夏/霞鹜千字文(type3/4 触发反转)字节与基准线 IDENTICAL;白狐千字文字节变小
(-12B ttf/-16B woff2,即去掉的非法 entry)。
基准测试新增「otf-白狐千字文」用例验证:SSIM=1.0000 ink=19735/19735(渲染像素级一致,
证明去掉的是浏览器本就忽略的无效 gid entry)。
性能 best-of N=2000:白狐千字文 min -5.4%/p10 -6.7%、初夏 -5.1%/-3.9%、霞鹜 -5.6%/-3.5%;
令东千字文(无 type2/3/4)持平。29 用例全 SSIM PASS。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 04:58:34 +08:00
崮生(子虚)
01b7ee432d
perf(ot-bytes): toUint8Array 返回 subarray view 省去 size 字节 memcpy
...
原 new Uint8Array(buf.subarray(0,size)) 会把已写入字节再拷贝到新 buffer,
而所有调用方(serializeScriptList/FeatureList、subsetGPOS/GSUB 主 writer、
pairSet writer)都是只读消费(writeBytes.set / 存数组后读),toUint8Array 后
writer 不再写入,故直接返回 subarray view(零拷贝共享 buf)完全等价。
24 产物(12 字体 × ttf/woff2)A/B 逐字节 IDENTICAL。
性能 best-of N=3000:霞鹜文楷 p10 -3.2%、初夏明朝 p10 -3.0%、思源OTF p10 -1.6%、
千字文持平(小子集 list/subtable 多、toUint8Array 调用频繁场景收益明显)。
基准测试全 SSIM 通过。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 04:39:22 +08:00
崮生(子虚)
1bc87763ad
perf(gsub): fmt3 预检改用 coverageAllOutOfSubset 内联快判,跳过 readCoverageRemapped
...
isSubtableSkipableByCoverage 的 ChainContext format3 分支原对每个 coverage 调
readCoverageRemapped(判全子集外)——它 new Array+sort+Map.set 填 covCache,但思源
fmt3 预检 281 个 coverage 全子集外且 avg 仅 1.3 gid:数组分配/sort/cache 操作是纯固定开销,
且这些 skipable coverage 的 cache entry 永不被命中(skipable subtable 不深度解析)。
改用 coverageAllOutOfSubset 内联遍历字节判「全子集外」(不分配数组、不读写 covCache),
预检只判 skipable 不再填 cache;非 skipable subtable 深度解析时 readCoverageRemapped
自行首次填 cache(命中率不变,无双重遍历)。同时从 isSubtableSkipableByCoverage 移除
不再使用的 covCache 参数。
一致性([[gsub-subset-fmt3-prefetch-consistency]]):对齐 coverageAllOutOfSubset 的
COV_RANGE 分支与 readCoverageRemapped——end clamp 到 numGlyphs-1、start>=numGlyphs
的越界 range 计 origNonEmpty 不展开 gid。两者「全子集外」判定完全等价,故预检 skipable
⟺ 深度解析 coverage 失效,输出逐字节一致。
A/B:6 字体 × ttf/woff2 共 12 产物逐字节 IDENTICAL(含 FiraCode calt)。
性能(best-of-min,两轮一致):otf-思源 -12%、思源ttf -6%、FiraCode -2%、初夏 -3%。
基准测试全通过,SSIM 维持。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 03:32:15 +08:00
崮生(子虚)
49337c72bc
perf(gsub): 缓存 isSubtableSkipableByCoverage per-subtable 结果,serialize 复用
...
subsetGSUB 预扫描对每个子表调 isSubtableSkipableByCoverage(判全空),serialize 阶段
serializeSubtable 内部对同一子表再调一次——重复。预扫描原 allEmpty=false 时 break 早退,
仅记录 lookup 级 allEmpty 布尔,serialize 无法复用逐子表结果。
改为预扫描完整扫描填 subtableSkipable: boolean[](与 subtableAbsOffs 同序),serializeSubtable
加 preCheckedSkipable 参数,serialize 主循环传 lk.subtableSkipable[j] 跳过内部重复预检。
allEmpty 从数组派生(全 true)。
安全性依赖 [[covCache 修复]](上一提交):isSubtableSkipableByCoverage 对 fmt3 调
readCoverageRemapped,covCache 顺序依赖 bug 已修复后结果真正顺序无关,故完整预扫描
改变填充顺序不再触发 FiraCode calt 输出变化。
A/B:6 字体(思源/白狐/令东/思源ttf/FiraCode/初夏)× ttf/woff2 共 12 产物逐字节 IDENTICAL。
性能(best-of-min):otf-思源 -9.2%、FiraCode -2.9%、初夏纯标点 -3.8%、白狐 -2.0%、
思源ttf -1.3%、令东持平(无大 GSUB)。基准测试全通过,SSIM 维持。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 03:20:05 +08:00
崮生(子虚)
d0dbbece82
fix(gsub): readCoverageRemapped 命中 readCoverageGids 的 cache entry 时正确重映射
...
covCache 被 readCoverageRemapped(fmt3 用,产 newGids)与 readCoverageGids
(type1/2/3/4 用,需原始 gid)共享同一 CoverageCacheEntry。readCoverageGids 填的
entry remapped=null/outOfSubset=false,readCoverageRemapped 命中时原直接
`return entry.outOfSubset ? null : entry.remapped` 会返回 null——null 被当作
「outOfSubset」语义,误判 coverage 失效。
修复:命中 entry 时若 gids!==EMPTY_GIDS(readCoverageGids 填的,非 readCoverageRemapped
自己的占位),从已缓存的原始 gids 现场重映射(gidLookup 过滤),并回填 remapped/outOfSubset
供后续命中。仅 readCoverageRemapped 自己的 entry(gids===EMPTY_GIDS)才走原快路径。
此为潜在顺序依赖隐患:改变 covCache 填充顺序(如预扫描完整扫描)会暴露它致 FiraCode
calt 输出变化。修复使 covCache 真正顺序无关。
A/B:思源/白狐/令东/思源ttf/初夏 逐字节 IDENTICAL;FiraCode ttf +32B(一个被误判空的
fmt3 subtable 现正确序列化)、woff2 不变(brotli 压掉)。基准测试全通过,FiraCode SSIM
维持 0.9923(多保留的规则不影响 => !== 渲染)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 03:18:19 +08:00
崮生(子虚)
a87f2eb0ff
perf(cff): rewriteCharstring 用标量替代 stackStart[]/stackVal[] 两数组
...
对栈的所有读取仅需栈顶:HSTEM 用 stackLen 算 stemCount,CALLSUBR 用
lastVal 取调用编号、用 lastStart 截断回栈顶 operand 起始处重写。其余
operand 的值/位置从不被读取,故 stackStart/stackVal 两数组可标量化为
stackLen+lastVal+lastStart 三标量(与 collectSubrRefs 同构),消除每个
operand 的 push 装箱 + length=0 重置。
A/B:otf 思源/白狐 × ttf/woff2 共 4 产物逐字节完全一致。
性能:subsetOTF 思源 min 0.266→0.235ms(-11.6%)。
基准测试全通过,SSIM 与基线一致。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 03:03:54 +08:00
崮生(子虚)
94f89ddbb8
perf(cff): collectSubrRefs 用 stackLen+lastVal 替代 stack:number[]
...
仅需栈长度(HSTEM 算 stemCount)与栈顶值(CALLSUBR/CALLGSUBR 取调用编号),
无需完整栈数组。消除每 operand 的 push(装箱)与 length=0 重置。
otf-思源8字 subsetOTF min 0.319→0.297ms(-7%),p50 0.401→0.390ms。
输出逐字节相同。与 rewriteCharstring/replaceDictOffsets 累计 otf-思源 -19%。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 02:53:41 +08:00
崮生(子虚)
efe24d9312
perf(cff): rewriteCharstring/replaceDictOffsets 用 Uint8Array+写指针替代 number[]
...
rewriteCharstring(18.7%) 与 replaceDictOffsets(7.8%) 原 out/chunks: number[] 逐字节 push
有装箱开销 + new Uint8Array(out) 二次遍历 / Array.from+spread 多次拷贝。改预分配 Uint8Array
+ 写指针 wp(CALLSUBR 截断即 wp=stackStart[]),encodeDictInt 内联到写入处消除临时 number[]。
otf-思源8字 subsetOTF min 0.352→0.322ms(-9%)、p50 0.483→0.421ms(-13%);
otf-白狐5字 min 0.058→0.055ms。输出逐字节完全相同(19188B/26536B A/B 一致)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 02:52:13 +08:00
崮生(子虚)
db33685475
perf(gsub): serializeSingleSubst 大 coverage 反转遍历,思源 -63%
...
思源 GSUB 56 lookup 中 4 个非空 type1 lookup(locl 地区变体)的 coverage
极大(lookup#43 = 8881 gid,#0=729,#42=636),但子集仅命中个位数。
原 serializeSingleSubst 用 readCoverageGids 全量展开 coverage 再逐个查
gidLookup(8881 次几乎全未命中),占 subsetGSUB 84%(0.184ms/call)。
改为遍历子集原始 gid(currentSortedSubsetGids,仅 19 项)+ coverageIndexOf
在 coverage 中二分定位下标(format2 下标对应 substituteGlyphIDs 数组),
O(subsetSize × log covCount) 替代 O(covCount)。与 reachable 侧
[[gsub-reachable-bigcov-byte-bsearch]] 同一类「展开全量消费极少」浪费。
仅当 covGidCount > subsetSize×4 且 >16 时启用反转,短 coverage(FiraCode
等)走原路径避免二分开销。导出 coverageIndexOf/coverageCount 供复用。
subsetGSUB 思源 min 0.204→0.075ms(-63%),otf-思源 woff2 avg 2.7→1.0ms。
输出逐字节一致(思源 GSUB 6560 bytes 差异 0),全字体 SSIM 不变。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 02:00:05 +08:00
崮生(子虚)
baa3a026ed
refactor(gsub): 去除 subtableAbsPositions 中间数组
...
优化329 已用 writeInt16At 直接回填,subtableAbsPositions 数组仅为
中间存储起点供回填使用,改为直接用 w.length(allEmpty)/ before(else)
计算相对 lookupStart 偏移。纯重构,输出字节逐字节一致。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 01:39:10 +08:00
崮生(子虚)
58fc647880
perf(gsub): subtable 偏移槽 writeUint16 占位回填替代 reserveOffset16 闭包
...
subsetGSUB 的 serialize 循环里,每个 lookup 的 subtable 偏移槽原先用
reserveOffset16(per-slot 闭包 + patch push)。思源 GSUB 56 lookup × 323
subtable,分配 323 次闭包。
移植 subsetGPOS 的优化329:writeUint16(0) 占位 + 记录 subtableSlotsStart,
序列化后统一 writeInt16At(subtableSlotsStart+j*2, pos-lookupStart) 回填。
rollback 失败回退后 pos=before(两种路径 subtable 起点一致)。
收益:思源 subsetGSUB 0.2145→0.2021ms (-5.8%),FiraCode 持平(387→378μs 噪声内)。
输出字节完全一致(otf-思源 19188/10664 等),所有字体 SSIM 不变。
与 subsetGPOS 的优化329 同构,统一两处 serialize 实现。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 01:35:50 +08:00
崮生(子虚)
2834c4d4ab
perf(cff): Local Subr INDEX 改轻量头部按需读 offset,subsetCFF -59%
...
subsetCFF 之前对命中的 Local Subr INDEX 做全量 readIndex(解析 count+1 个
offset),思源 FD12 26550 subr 这步耗 0.181ms,占 subsetCFF 一半。
但引用收集与重建只按 subr 编号读个别 offset(思源仅引用 13 个)。
新增 readIndexHeader(只读 count/offSize/dataStart + 末尾哨兵算 end)+
readIndexOffset(按编号 i 读单个 offSize 字节 offset),替换全量解析。
收益:
- subsetCFF 0.3466→0.1411ms (-59%)
- otf-思源黑体 woff2 min 1.50→1.30ms (-13%)、ttf 1.75→1.20ms (-31%)
- 输出字节完全一致(19188/10664 bytes),SSIM 保持 1.0000
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 01:05:35 +08:00
崮生(子虚)
70d3a2a3dc
perf(cff): Local Subr INDEX 子集化,思源 OTF woff2 641KB→10KB(-98%)
...
思源 OTF 的 FD12 Local Subr INDEX 含 26550 个子程序(716KB)被完整透传,
但子集 18 字形仅引用 13 个。新增 Type 2 栈模拟扫描 collectSubrRefs
(精确跟踪 stem 计数以正确跳过 hintmask/cntrmask 掩码字节)递归收集引用,
按 Private 池构建 localRemap + writeIndex 重建紧凑 INDEX,
rewriteCharstring 重写 callsubr 操作数到新编号。
收益(otf-思源黑体子集):
- woff2 641480→10664 bytes (-98.3%)
- ttf 752040→19188 bytes (-97.4%)
- min 5.69ms→1.50ms (brotli 输入缩小,-73.6%)
- SSIM 保持 1.0000
global subr 不子集化(思源引用=0,仅 6KB);privSegCache 去重保留。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 00:59:40 +08:00
崮生(子虚)
f1c8315b0c
perf(gsub-reachable): 大 coverage 反转路径用字节二分省掉数组展开
...
collectReachableGsubTargets 的 SingleSubst 反转遍历路径(reachable 小、coverage 大)
原 readCoverageGids 先把 coverage 全部 gid 展开到数组再二分。思源 locl lookup 的 coverage
达 8881/12000/11632 gid,展开是 reach 阶段 #1 热点(0.060ms / 0.165ms)。
新增 coverageIndexOf(format1 二分 / format2 range 二分+累计)与 coverageCount(不展开得
gid 总数),反转路径直接对 coverage 字节二分得 index,完全省掉大数组展开:
- 思源 collectReachable 0.165→0.042ms(-74%)
- 白狐 0.026→0.007ms(-73%)
- FiraCode 持平(coverage 小不走反转路径)
reachable 结果逐字体一致(思源 19 / 白狐 4 / FiraCode 89),基准测试全过:
思源 OTF ssim=1.0000、FiraCode 0.9923 保持。
otf-subset 顺带 findTable 去重(reachable 收集与布局表子集化共用提前解析的 GSUB/GPOS)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 00:27:56 +08:00
崮生(子虚)
07432852cf
fix(otf-subset): 保留 GSUB locl 变体 target 字形,思源 otf SSIM 0.9530→1.0000
...
otf 子集化路径此前完全跳过 probeGsubAndCmap/collectReachableGsubTargets,
subsetGids 仅含 cmap 查到的 codepoint→基础 gid。思源黑体等 CID 字体含 locl
feature,浏览器按中文 locale 渲染时 GSUB 把基础字形替换为地区变体(如
"天" gid A → CN 变体 gid A+1)。subsetGids 不含这些 target,子集 GSUB 重
映射后 target gid 指向不存在/错误字形,渲染出错误变体。
逐字 ink 对比定位(地/黄/洪/波一致,天/玄/宇/法/海多 ink、宙少 ink)正是
locl 变体丢失特征。subsetOTF 内部复用 collectReachableGsubTargets 收集 seed
经 GSUB 可达的 target gid 合并进 subsetGids,subsetCFF/FDSelect/charset 与
GSUB/GPOS 重映射统一基于扩展后的 subsetGids,变体字形正确保留。
实测思源 9 字 otf/woff2 输出均 ink 6724/6724 SSIM 1.0000(修复前 0.9530),
白狐 5 字保持 1.0000,其余用例无回归。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 23:50:37 +08:00
崮生(子虚)
94e75ea2ba
perf(cff): Name/String/Global Subr INDEX 改 indexByteRange,删 getIndexBytes
...
与 Local Subr 同理:Name/String/Global Subr INDEX 仅需字节范围透传 + end
(下一 INDEX 起始),readIndex 全量解析 count+1 个 offset 是浪费。统一改用
indexByteRange,删除仅服务于这三个的 getIndexBytes。
思源场景 offset 数组不大故实测持平(subsetOTF min 0.49ms 不变),但消除潜在
全量解析浪费,与 Local Subr 处理一致;白狐 5字 min 0.052ms。
字节级零回归(cmp 输出逐字节一致);基准测试全字体 SSIM 与优化前完全一致。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 23:28:37 +08:00
崮生(子虚)
e8ae79a58c
perf(cff): Local Subr INDEX 改 indexByteRange 不全量解析 offset,subsetCFF -46%
...
subsetCFF 0.313ms 中 0.202ms 集中在 fdaParse 段:readIndex(b, privOrigOff+subrRel)
全量解析 Local Subr INDEX 的 count+1 个 offset 数组,但后续只用 b.subarray(start,end)
取整体字节切片透传——offset 数组从未被消费。思源等 CID 字体 Local Subr 可达数千 subr,
全量解析是 subsetCFF 主要耗时。
新增 indexByteRange:仅读 count/offSize + 第 count 个 offset(位于 offset 数组末尾)算
end,跳过中间全部 offset 解析。Local Subr 透传改用它。
顺带合并 FDSelect 两处 lookupFDSelect 遍历为单次(首次遍历同时记录 gidOrigFds 供
newGidToFd 复用,消除第二次对 newSubsetGids 的二分查找)。
subsetOTF(思源8字) keepGSUB=true 0.704→0.504ms(-28%)、keepGSUB=false 0.428→0.232ms(-46%);
fdaParse 0.202→0.003ms;字节级零回归(cmp 输出逐字节一致);
基准测试全字体 SSIM 与优化前完全一致。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 23:24:18 +08:00
崮生(子虚)
7cd960617a
perf(otf): calcTableChecksum 改 DataView.getUint32 批量读,assemble -83%
...
原逐字节 bytes[i]<<24|bytes[i+1]<<16|... 手工拼装 uint32,对思源 742KB CFF
表是 assembleSfnt 主要开销(assembleSfnt min 0.643ms 中绝大部分)。
改 DataView.getUint32 一次读 4 字节大端 uint32(V8 对 DataView 专门内联,
[[dataview-getuint32-fastest]] 验证其快于 Uint32Array+swap 与手工位拼装),
尾部 1~3 字节补 0 单独处理保持规范语义。
assembleSfnt(思源8字): min 0.643→0.110ms(-83%);
subsetOTF: min 1.321→0.725ms(-45%)、p50 1.720→1.079ms(-37%);
(叠加上一 commit wholeSum 复用,相对原版 min 1.836→0.725 累计 -60%)
字节级零回归(cmp 优化前后输出逐字节一致,含 checksum/checkSumAdjustment);
基准测试全字体 SSIM 与优化前完全一致。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 23:18:37 +08:00
崮生(子虚)
98b56b4f35
perf(otf): assembleSfnt wholeSum 复用各表 checksum,消除全文件二次遍历
...
head.checkSumAdjustment 原先两遍遍历 ~750KB 输出:
1. calcTableChecksum 对每表(CFF 742KB 最大)算 checksum
2. wholeSum 再次遍历整个 out 文件 4 字节步进算全文件 sum
数学等价:全文件 sum = 目录区 sum + Σ 各表数据段 checksum,
而各表数据段 padded sum 正是写表记录时已算的 calcTableChecksum。
故 wholeSum 改为「dirSize(156B) 目录区 sum + 累加的 tablesDataSum」,
消除数百 KB 大表的第二次整段遍历。
思源 otf 8字 subsetOTF:min 1.836→1.321ms(-28%)、p50 2.433→1.720ms(-29%);
字节级零回归(cmp 优化前后输出逐字节一致,含 checkSumAdjustment);
基准测试全字体 SSIM 与优化前完全一致。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 23:12:05 +08:00
崮生(子虚)
e37593a552
perf(cff-subset): 三处全量解析改按需查询,思源 subsetCFF -77%
...
CID CFF 子集化原先对 numGlyphs(思源 65535) 全量解析三处,子集仅 11 字时是纯浪费:
1. readIndex(CharStrings) 遍历 65536 个 offset(0.435ms)→ 直接读 count+offSize,
按 subsetGid 随机读 2 个 offset 取字节区间。
2. readCharsetCIDs 填充 65535 CID 数组 → lookupCharsetCID 遍历 range 查单个 gid
(思源 format2 约 6 千 range,省去 65535 项填充;range 起始 gid 累积计算)。
3. parseFDSelect 填充 65535 FD 数组 → lookupFDSelect format3 二分 range 查单个 gid。
subsetCFF 1.577→0.357ms(-77%);subsetOTF 思源 3.56→1.89ms(-47%),基准 avg 5.5→3.7ms。
字节级零回归(思源/白狐子集字节与优化前 cmp 完全一致),SSIM 不变。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 23:05:54 +08:00