perf(gsub): subsetGSUB 主 coverage 预检跳过空 lookup,FiraCode 33ms→22ms

FiraCode 含 403 个 lookup,子集仅命中少数字形,多数 lookup 主 coverage 引用
的 gid 全部不在子集内——深度序列化后必然得到空 entries、回退 writeEmptySubtable。
预检在深度解析前用 gidLookup 内联判定主 coverage 是否全子集外,命中即跳过
(type1-4 + type6-format1,共 ~216 个空 lookup),消除其 entries 构建/rule 解析开销。

关键正确性:
- format2(class 驱动)/format3(多 coverage)不预检——主 coverage 非充分条件,
  误判全空会丢连字(实测 FiraCode calt format2,SSIM 0.9923→0.9368)
- 预检不经 covCache——该缓存被 readCoverageRemapped(存 newGids)与
  readCoverageGids(format1 期望原始 gid)共享,语义不一致,预检写入会污染
  format1 深度解析(newGids 被当原始 gid 二次重映射,连字规则错位)
- 输出字节与预检关闭时完全等价(已 dump 对比验证),SSIM 全部无回归

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
崮生(子虚) 2026-07-23 16:45:43 +08:00
parent 771f4fc6de
commit 25da274c3a

View File

@ -917,6 +917,82 @@ function writeChainFormat3(
}
}
/**
* subtable coverage
*
* FiraCode lookup 403
* lookup coverage gid lookup
* entries退 writeEmptySubtable delta/sequence/ rule
* coverage gidLookup covCache
*
* coverage coverage subOff+2 Offset16
* - SingleSubst/Multiple/Alternate/Ligaturesource coverage entries
* - ChainContextSubst format1input coverage ruleSet
* coverage
* - ChainContextSubst format2 InputClassDef class coverage
* FiraCode calt format2 coverage class
* SSIM 0.99230.9368
* - ChainContextSubst format3 coveragebacktrack/input/lookahead coverage
*
* @returns true = coverage subtablefalse =
*/
function isPrimaryCoverageOutOfSubset(
r: Reader,
off: number,
type: number,
gidLookup: GidLookup,
): boolean {
/** ChainContextformat2class 驱动)和 format3多 coverage不预检 */
if (type === LT_CHAIN) {
if (off + 2 > r.dv.byteLength) return false;
const fmt = r.dv.getUint16(off, false);
if (fmt === 2 || fmt === 3) return false;
}
/**
* gidLookup coverage covCache
* covCache readCoverageRemapped newGids entry.gids readCoverageGids
* format1 entry.gids gid gids
* readCoverageRemapped format1 gids
* newGids gid FiraCode SSIM 0.99230.9368
* subtable coverage subtable
*/
const dv = r.dv;
const len = dv.byteLength;
if (off + 4 > len) return false;
const covOff = off + dv.getUint16(off + 2, false);
if (covOff + 4 > len) return false;
const format = dv.getUint16(covOff, false);
if (format === COV_LIST) {
const count = dv.getUint16(covOff + 2, false);
if (count === 0) return false; /** 原 coverage 本就空,不算 outOfSubset与 readCoverageRemapped 一致) */
const base = covOff + 4;
if (base + count * 2 > len) return false;
for (let i = 0; i < count; i++) {
if (gidLookup[dv.getUint16(base + i * 2, false)] >= 0) return false; /** 命中子集,不跳过 */
}
return true; /** 全子集外 */
}
if (format === COV_RANGE) {
const rangeCount = dv.getUint16(covOff + 2, false);
let p = covOff + 4;
let origNonEmpty = false;
for (let i = 0; i < rangeCount; i++) {
if (p + 6 > len) break;
const start = dv.getUint16(p, false);
const end = dv.getUint16(p + 2, false);
if (end >= start && end - start < COVERAGE_MAX_EXPAND) {
for (let g = start; g <= end; g++) {
origNonEmpty = true;
if (gidLookup[g] >= 0) return false; /** 命中子集,不跳过 */
}
}
p += 6;
}
return origNonEmpty; /** 原非空且无命中 → 全子集外 */
}
return false;
}
/** 单个 subtable 序列化分发。返回 false 表示该 subtable 无法重映射(调用方决定降级) */
function serializeSubtable(
w: Writer,
@ -928,6 +1004,10 @@ function serializeSubtable(
gidLookup: GidLookup,
): boolean {
r.clearError();
/** coverage subtable
* FiraCode 403 lookup 253 coverage lookuptype1-4 + type6-fmt1
* entries / rule format2/format3 coverage */
if (isPrimaryCoverageOutOfSubset(r, off, type, gidLookup)) return false;
let ok: boolean;
switch (type) {
case LT_SINGLE: