191 Commits

Author SHA1 Message Date
崮生(子虚)
67d2eb5fa5 perf(woff2): ttf→woff2 跳过 TTF checksum 计算
woff2 重新编码,不消费 TTF directory 的 per-table checksum 与
head.checkSumAdjustment(encodeTTFToWOFF2 只读 directory 的 tag/offset/length,
重建自己的 directory)。TTFWriter 加 skipCheckSum 选项,font.write 的 woff2
分支传入,跳过 checkSumArrayBuffer 调用 + directory 回填 + head adjustment。

node --prof 思源 3500 字大字集:checkSumArrayBuffer 5.1%(JS nonlib 第二大)
从热点列表消失。端到端 min -2%(brotli 主导稀释收益),但消除的是语义上
纯无用的计算(被 woff2 丢弃)。head.checkSumAdjustment 保持 0 占位(resolveTTF
已置 0),浏览器渲染不依赖该字段。

ttf 直出(消费 directory checksum)与 woff(ttf2woff 读 directory checksum
写 WOFF 表目录)分支不受影响,仅 woff2 生效。

基准测试全 SSIM 与历史固有值一致(FiraCode 0.9923/初夏纯标点 0.9920/篆体
0.9876 等),woff2 输出字节 IDENTICAL(思源千字文中段 17052/FiraCode 8892/
霞鹜 3840/初夏 6164/6864/得意黑 2532/1628)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 08:02:11 +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
崮生(子虚)
e1190eeb82 perf(woff2): transformGlyfAndLoca simple glyph 跳过 bbox 读取
原每个 glyph 入口都读 numberOfContours + xMin/yMin/xMax/yMax 共 5 次 readI16,
但 simple glyph 的 bbox 由解码端从轮廓点重建(优化314,bboxBitmap 位恒为 0,
不写入 bboxStream),xMin/yMin/xMax/yMax 读取纯属浪费。仅复合 glyph 需显式存
bbox(raw 组件数据剥离 glyph 头,解码端无法重建)。

将 bbox 4 次 readI16 移入复合 glyph 分支(numberOfContours<0),simple glyph
路径只读 numberOfContours 一次。CJK 字体几乎全 simple,每字省 4 次 readI16。

7 产物 A/B 全 IDENTICAL。性能 best-of min N=4000:
篆体 -4.6%、思源8字 -2.3%、FiraCode -1.7%、令东8字 -1.4%、千字文 -1.1%。
基准测试全 SSIM 通过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 03:59:53 +08:00
崮生(子虚)
32afa17b5c perf(woff2): transformGlyfAndLoca x/y 直接用 TTF delta 编码 triplet
原 x 解码循环把 TTF delta(相对前点的有符号差)累积成绝对坐标 px,
y+triplet 循环又用 cx-prevX / cy-prevY 把绝对坐标还原回 delta——无意义的
往返计算。因 TTF xCoordinates/yCoordinates 本就是 delta,triplet 的 dx/dy
语义 = 当前点与前点坐标差 = TTF delta 本身,可直接用:
- x 循环存 delta 到 xCoords(省 px 累积)
- y+triplet 循环 dx=xCoords[yi]、dy=解码出的 y delta(省 py 累积 + cx-prevX
  + cy-prevY + prevX/prevY 维护,每点省 4 次 ALU)
case 分支逻辑不变(delta=0 ⟺ 绝对差=0),输出逐字节一致。

8 产物 A/B 全 IDENTICAL(含 ttf/otf 路径)。性能 best-of min:
千字文 woff2 -2.4%(1.56→1.52ms)、令东8字 -2.8%、FiraCode -1.1%。
基准测试全 SSIM 通过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 03:54:54 +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
崮生(子虚)
67439be486 fix(cff-subset): replaceDictOffsets 补全 op30(BCD) 跳过分支
CFF DICT 的 op30 是 BCD 实数,每字节两 nibble 遇 0xf 结束。replaceDictOffsets 原仅处理
28/29/247-254 整数编码,漏 op30,遇到 BCD 字节会逐字节误扫。当前思源 Top DICT 的
CIDFontVersion(BCD 2.004) 后紧跟非替换目标 operator 且 BCD 字节均 >21,operator 边界
未被破坏故输出恰好正确,但若 BCD 内出现 <=21 字节会致 operator 错乱——潜在 bug。

补 op30 分支完整跳过 nibbles 至 0xf。基准测试全用例 SSIM 无变化(思源 otf 0.9530、白狐 1.0)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 23:00:51 +08:00
崮生(子虚)
bebb8503f4 feat(otf): 独立 OTF(CFF) 子集化器,修复思源等 CID 字体 OTS 拒绝与 gid 错位
fonteditor-core 对含 idRangeOffset 的 CID cmap 解析有 bug,产出 gid 错乱子集(SSIM 0.93~0.97)。
新增独立 otf-subset + cff-subset,直接重建 CFF/cmap/hmtx,透传 charstring。

核心修复:
1. cff-subset 透传各 FD 的 Local Subr INDEX(op 19 指向)+ patch op19 新偏移。
   原仅透传 Private DICT 字节未带 Local Subr INDEX,思源等字形用 callsubr 引用本地 subr,
   子集 op19 指向越界致 OTS "Failed to parse Top DICT Data" 拒绝加载。
2. otf-subset 接入 subsetGSUB/subsetGPOS 按 subsetGids 做 gid 重映射。
   原透传原始 GSUB/GPOS 字节 gid 未重映射,coverage 引用旧 gid 指向错字,
   致 shaping 把字符替换成 .notdef(思源 otf SSIM 0.623,洪/法等字变空白)。
   OTF 与 TTF 的 GSUB/GPOS 表结构相同,字节级子集化器可直接复用。

结果:思源 otf OTS 拒绝→可加载,SSIM 0.623→0.953,白狐 otf SSIM 1.0,
逐字 charstring 字节级与原始一致,渲染像素级一致(5 种独立验证 diff=0)。
所有非 otf 用例无回归。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 22:48:14 +08:00
崮生(子虚)
f4af6f7b68 perf(glyf): subset simple 字形剥离 hinting instructions
subset 模式下 simple 字形走原始字节快路径([[glyf-origbytes-fastpath]]),完整保留
原始 hinting instructions。但 web 渲染场景浏览器用 autohint,原始 instructions 不参与
渲染——实测剥离后所有基准用例 SSIM 与 ink 逐位一致(含最敏感的小字号用例)。

read 快路径定位 instructionLength(紧接 endPtsOfContours,OpenType 规范布局,无需展开
flags),存 _instrOff/_instrLen。write 快路径跳过 instructions 段并写 instructionLength=0,
sizeof 同步扣减字节长度。_instrOff=-1(无 instructions)走原整段拷贝路径。

收益(SSIM 全不变):
- FiraCode woff2 10776→8892(-17.5%),端到端 avg 1.92→1.68ms(-12.5%)
- 千字文 woff2 79784→79736,ttf 体积同步减小
- CJK 字体(初夏/霞鹜/思源)instructions 极少,体积持平

brotli 是 write 阶段硬下限,减小 glyf 原始字节直接减小 brotli 输入与输出。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 21:11:36 +08:00
崮生(子虚)
eb3ed06d41 perf(glyf): simple 字形 fast-path glyfObj 改对象字面量消除隐藏类迁移
glyf.read 的 simple 字形快路径原用 `var glyfObj = {}` 后逐个赋值 9 个属性
(xMin/yMin/xMax/yMax/_origBuf/_origOff/_origLen/_numContours/_totalPoints),
每个字形产生 9 次隐藏类迁移。改为对象字面量一次性创建所有属性,V8 分配稳定隐藏类
(无迁移),且 numberOfContours>0 / =0 统一形状(_numContours 直接用值,
_totalPoints 用三元表达式)。

千字文(129 simple 字形)Font.create best-of p50 约 -3%(多轮 0.1527→0.1479)。
5 字体 woff2 sha 逐字节一致,SSIM 全部不变(仅对象创建方式变化,属性集完全相同)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 20:44:55 +08:00
崮生(子虚)
c6316378f2 perf(cmap): presetCmap 模式 parse 提前返回跳过 subtable 解析
承接优化315(readWindowsAllCodes 复用 presetCmap),cmap.parse 在 presetCmap 存在时
直接返回 presetCmap,跳过整个 cmap 解析:子表目录扫描 + readSubTable 建 format4._lazySegs
/format12._lazyGroups + readWindowsAllCodes 调用。

presetCmap 模式下 subTables 的 _lazySegs/_lazyGroups 不再被 lookupFormat4/12 使用
(readWindowsAllCodes 已跳过),cmap.write/sizeof 用 ttf.cmap 的 keys 重建不依赖
subTables,故 format4/12 subtable 的 header 解析纯粹是无用功。

收益(Font.create best-of,叠加优化315的总收益):千字文思源 -8.4%、初夏纯标点 -6.2%、
FiraCode -5.0%、思源标点 -5.5%。千字文前段 woff2 基准 avg 4.9→3.7ms。6 字体 woff2 sha
逐字节一致,SSIM 全部不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 20:32:39 +08:00
崮生(子虚)
ce893976f1 perf(cmap): subset 模式复用 probe 的 codepoint→gid 映射跳过二分查找
Font.create 内 readWindowsAllCodes 在 subset 模式下对每个 subset 字符做
lookupFormat4/lookupFormat12 二分查找产出 codepoint→gid,这与之前 probeGsubAndCmap
的 probe.lookup(算法一致)完全重复。subset 模式下 format0/format14 不解析
(glyphIdArray/groups 为空),codes 仅来自 format4/12,故 probe.lookup 与
readWindowsAllCodes 产出严格等价。

fontSubset 在 probe.ok 时将 probe.lookup(Map)转为普通对象经 readOptions.presetCmap
注入,readWindowsAllCodes 检测到 presetCmap 直接返回,跳过 N 次(subset 长度)二分查找。

收益(best-of Font.create):千字文 -10.2%、初夏纯标点 -5.5%、FiraCode -3.7%
(与 subset 长度成正比,每 codepoint 省一次二分)。5 字体 woff2 sha 逐字节一致,
SSIM 全部不变。probe 不 ok(otf 或无 format4/12)时不注入,走原逻辑。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 20:25:03 +08:00
崮生(子虚)
791ea0c89e perf(ot-writer): 主 Writer 按原表大小预分配容量消除 grow 全拷贝
OTWriter 加可选 initialCapacity 构造参数(默认 2048)。subsetGPOS/subsetGSUB
主 Writer 按原表 byteLength 预分配,避免大表多次 2× 扩容的全拷贝。
初夏纯标点 GPOS 单次 fontSubset 原 5 次 grow(cap 2048→4096→8192→16384),
改后 5 字体(初夏/FiraCode/令东千字文/思源/霞鹜)grow 全部 0。

12 字体 woff2 sha 逐字节一致,SSIM 全不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 19:54:07 +08:00
崮生(子虚)
4ae7e05334 perf(gsub-subset): emitCoverage 合并两遍区间扫描为单遍
原实现两遍扫描连续区间:第一遍统计 rangeCount 决定 format1/format2,
第二遍重新扫描写出 ranges。合并为单遍收集 rangeStarts/rangeEnds 两个并行
number[](替代对象数组),再据 rangeCount vs n 选 format 直接写出。

省第二遍重新扫描 + covIndex 重算。V8 短命小数组近乎免费。

7 字体 sha 逐字节一致,SSIM 全不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 19:48:40 +08:00
崮生(子虚)
77029f1090 perf(gsub-reachable): collectChainRuleRefs 循环内 r.u16 改 dv.getUint16
chain format1 规则的 backtrack/input/lookahead/substRecords 读取(6 个内层
循环)从 r.u16 改 dv.getUint16。FiraCode 180 个 fmt1 规则 × 多次密集调用。
count 读取(backtrackCount 等)保留 r.u16 维持原行为。

FiraCode reachable 集合 size=89 不变,5 字体 sha 逐字节一致,SSIM 全不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 19:44:08 +08:00
崮生(子虚)
7719110b32 perf(gsub-subset): subsetGSUB 主函数 header+lookup 解析 r.u16 改 dv.getUint16
subsetGSUB 顶部本就构造了 dv(gsubBytes 的 DataView),header offset
(0/2/4/6/8) 与 lookup 解析循环的 r.u16 改 dv.getUint16 省方法调用+边界检查。
extension 的 r.u32 保留(越界降级安全网)。

8 字体(FiraCode woff2+ttf、CJK 标点、千字文)sha 逐字节一致,SSIM 全不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 19:39:54 +08:00
崮生(子虚)
bd68995fde perf(gpos-subset): 循环内 r.u16 改 dv.getUint16
延续 gsub-reachable/gsub-subset 的已验证模式:subsetGPOS 主函数、
readCoverageGids、readClassDefMap、serializePairPosFormat1 的循环内
连续 u16 读取从 r.u16(方法调用+边界检查)改为 dv.getUint16(off,false)。
函数级 header offset 与 errorFlag 安全网保留 r.u16/scriptListSpan。

12 字体 woff2 输出 sha 逐字节一致,SSIM 全不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 19:36:31 +08:00
崮生(子虚)
17145e05fd perf(gsub-subset): serialize/parse 函数循环内 r.u16 改 dv.getUint16
延续 gsub-reachable 的 dv 缓存优化到 subsetGSUB 的序列化/解析函数:
serializeSingleSubst/Multiple/Alternate/Ligature、serializeChainedContextSubst
format1/format2、parseChainRuleFormat1or2、parseChainFormat3、readClassDefMap
顶部缓存 const dv = r.dv,循环内 gid/offset 连续读取改 dv.getUint16。
函数级 format/count 判定保留 r.u16 维持 errorFlag 安全网。

输出字节级等价(6 字体 sha 全一致),SSIM 全不变。
性能:初夏标点 -5.3%、霞鹜标点 -5.0%、FiraCode -2.8%(CJK 标点 GSUB 密集收益大)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 19:32:23 +08:00
崮生(子虚)
92d02c33bb perf(gsub-reachable): 循环内 r.u16 改 dv.getUint16 + coverage 批量读
精确 CPU profile(Session+Profiler API 排除模块加载)发现 GSUB 重字体
(FiraCode)brotli 不再主导,collectReachableGsubTargets 是 #1 热点,
其中 OTReader.u16(方法调用+边界检查)占 134Kμs。

collectReachableGsubTargets/collectSubtableTargets/collectChainRefs/
readCoverageGids/coverageFirstExcludedGid 顶部缓存 const dv = r.dv,
循环内连续 u16 读取改 dv.getUint16(off,false),省方法调用+边界检查。
函数级 format/count 判定保留 r.u16 维持 errorFlag 安全网。
readCoverageGids format1 加 Uint16Array 批量读(count>8 且对齐)。

collectReachable FiraCode 0.1525→0.1199ms(-21%),e2e 1.04→1.00ms。
基准测试全绿 SSIM 不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 19:28:56 +08:00
崮生(子虚)
fb721a216b perf(gpos): 预扫描 coverage 未命中的 subtable 直接写空跳过 serializeSinglePos/PairPos
anyHit lookup 内逐 subtable 序列化时,subtableCoverageHits 预扫描已判定
coverage 是否含子集字形。初夏 96~98% SinglePos subtable coverage 完全不含
子集字形(hits[j]==false),原实现仍 readCoverageGids + 构建 kept(空) +
emit 空 coverage 走完整 serializeSinglePos,纯浪费。

coverageHit=false 时直接 writeEmptyPosSubtable(与 lookup 全空折叠同性质
等价变换:空 coverage 浏览器查不到字形即跳过,valueFormat 0 与原值渲染等价),
跳过 readCoverageGids/kept/emit 全部开销。

A/B(min 微基准,checksum 因空 subtable valueFormat 0 替代原值变化,SSIM 全不变):
- 初夏纯标点 subsetGPOS 0.110→0.084ms (-24%)
- 初夏汉字标点 0.101→0.073ms (-28%)
- FiraCode 0.002ms(无 GPOS 热点,符合预期)

全量基准 SSIM 全部不变(初夏纯标点 0.9920/汉字标点 1.0000/霞鹜 0.9987/FiraCode 0.9923 等固有值)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 18:08:49 +08:00
崮生(子虚)
6c59c3e7e6 perf(gsub-subset): parseChainRuleFormat1or2 边读边 remap 提前失败
原实现对 back/input/look 先读到 backRaw/inputRaw/lookRaw 三个临时数组,
再二次遍历 remapSeq 重映射,format1 含子集外 gid 时规则失效(return null),
但 raw 数组已分配——FiraCode 多数 fmt1 规则因 input gid 不在子集而失效,raw 分配纯浪费。

改为 readSeq 边读边重映射(format1):遇子集外 gid 立即返回 null,省掉三个
临时数组与二次遍历。format2(class index 始终有效)原样读取不重映射,行为不变。

subsetGSUB min(checksum 与改动前完全一致):
- FiraCode 0.296→0.290ms(-2%)
- 初夏纯标点 0.075→0.071ms(-5%)

全字体 SSIM/输出字节均不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 17:58:15 +08:00
崮生(子虚)
7b5d03391f perf(gsub-reachable): type4 Ligature first gid 短路跳过整个 LigatureSet
LigatureSubst 第一分量(coverage gid)不在子集时整条 ligature 不会触发,
原实现在 first 不在子集时仍读取 setOff/ligCount/每条 ligature 的
ligOff/compCount/target 后才 continue。改为 first 检查上提到 LigatureSet
展开之前,不在子集直接跳过,省掉内部全部 u16 读取。

初夏明朝 type4 first gid 常全不在子集(首轮 coverage 多为非子集字形),
此短路消除每条 ligature 的无谓读取。

collectReachableGsubTargets min(checksum 与改动前完全一致):
- 初夏汉字标点 0.070→0.059ms(-16%)
- 初夏纯标点 0.081→0.068ms(-15%)
- FiraCode 0.105→0.101ms(-4%)

全字体 SSIM/输出字节均不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 17:49:42 +08:00
崮生(子虚)
799d284d4b perf(gsub): emitCoverage 跳过 slice+sort,readCoverageRemapped 缓存前统一排序
原 emitCoverage 对输入 newGids 做 slice().sort()——防御性复制+排序。
但 readCoverageRemapped 的输出(fmt3/writeChainFormat2 coverage)因 原gid→新gid
映射不保序而可能乱序,必须排序;type1/2/3/4 的 entries.map(e=>from) 虽已按 from
排序故升序,却仍付出 slice+sort 开销。

将排序职责上移到 readCoverageRemapped 缓存入口(covCache 命中率极高,FiraCode
604 引用/83 独立 coverage,排序分摊几乎免费),使所有调用方输出天然升序无重复。
emitCoverage 据此改为直接扫描区间(不 slice、不 sort、不分配 ranges 对象数组),
同时两遍扫描统一为 rangeCount 计数。

FiraCode subsetGSUB 417→390μs(-6.5%),输出 sha 全字体一致,SSIM 全不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 17:33:48 +08:00
崮生(子虚)
f053112e44 perf(gsub-reachable): type1 fmt1 + type3 条件反转遍历方向
延续 9d6e982(type1 fmt2 反转)的思路,对 SingleSubst format1 与
AlternateSubst(type3)同样反转遍历方向:遍历 reachable 二分查 covGids
(按 gid 升序)得 index,替代遍历 covGids 逐个查 reachable。

初夏明朝纯标点实测 fmt1 covLen 405、type3 covLen 987(命中率 2.3%),
均属「展开全量命中极少」模式,反转后遍历量从 covGids.length 降到
reachable.size。

关键:加条件 covGids.length > reachable.size 才反转。无条件反转在
霞鹜/FiraCode 上负优化(covGids 短,FiraCode type1 avg 5.5,二分开销
> Set.has 遍历)。条件反转使短 covGids 走原 Set.has 路径。

type4(Ligature)不反转:firstHit 判定反转后遍历 reachable(53)× 18
calls = 954 次二分 > 原 684 次 Set.has,必负。

A/B vs 9d6e982(3000 次 p50,reachable sha 全一致):
- 初夏标点 102→92μs(-10%)
- 初夏8字 54→51μs(-5%)
- 霞鹜标点 12→10μs(-12%,无条件版 +8% 已修正)
- 霞鹜8字 6.8→6.4μs(-7%)
- FiraCode 标点 70→69μs(-1%,无条件版 +2% 已修正)

相比最初 baseline(fee9e49 前)初夏标点 129→92μs(-29%)。基准测试
SSIM 全不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 17:07:13 +08:00