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>
This commit is contained in:
崮生(子虚) 2026-07-25 03:18:19 +08:00
parent a87f2eb0ff
commit d0dbbece82

View File

@ -174,7 +174,29 @@ function readCoverageRemapped(
): number[] | null {
let entry = cache.get(off);
if (entry !== undefined) {
/** 已缓存:失效返回 null否则返回重映射数组remapped 已在首次计算时填好) */
/** readCoverageRemapped entrygids===EMPTY_GIDSremapped
* readCoverageGids entrygids gid remapped=nulloutOfSubset=false
* entry.remappednull outOfSubset coverage
* fmt3 FiraCode calt gids
* covOff readCoverageGids/readCoverageRemapped entry */
if (entry.gids !== EMPTY_GIDS) {
const ogids = entry.gids;
const m = new Array<number>(ogids.length);
let w2 = 0;
for (let i = 0; i < ogids.length; i++) {
const ng = gidLookup[ogids[i]];
if (ng >= 0) m[w2++] = ng;
}
m.length = w2;
/** outOfSubsetentry readCoverageGids outOfSubset false
* coverage ogids.length>0 origNonEmpty */
const oos = w2 === 0 && ogids.length > 0;
/** 回填 remapped/outOfSubset 供后续 readCoverageRemapped 命中(保留 gids 不动供 readCoverageGids */
entry.remapped = m;
entry.outOfSubset = oos;
return oos ? null : m;
}
/** readCoverageRemapped 自己的 entry失效返回 null否则返回重映射数组 */
return entry.outOfSubset ? null : (entry.remapped as number[]);
}
const dv = r.dv;