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>
This commit is contained in:
崮生(子虚) 2026-07-24 17:58:15 +08:00
parent 7b5d03391f
commit 6c59c3e7e6

View File

@ -837,22 +837,42 @@ function parseChainRuleFormat1or2(
/** count 异常大(偏移错位读到垃圾值)则放弃该规则,返回 null。实际规则序列长度很小<256 */
if (backCount > 255) return null;
let p = ruleOff + 2;
const backRaw: number[] = [];
for (let k = 0; k < backCount; k++) backRaw.push(r.u16(p + k * 2));
/**
* format1gid gid null backRaw/inputRaw/lookRaw
* remap raw FiraCode fmt1 input gid
* format2class index
*/
const readSeq = (count: number): number[] | null => {
if (count === 0) return [];
if (isGidFormat) {
const out: number[] = [];
for (let k = 0; k < count; k++) {
const m = remapGid(origToNew, r.u16(p + k * 2));
if (m === null) return null;
out.push(m);
}
return out;
}
const out2: number[] = [];
for (let k = 0; k < count; k++) out2.push(r.u16(p + k * 2));
return out2;
};
const back = readSeq(backCount);
if (back === null) return null;
p += backCount * 2;
const inputCount = r.u16(p);
/** inputCount 为 0 是异常ChainSubRule 至少含第一分量inputCount>=1返回 null 跳过该规则 */
if (inputCount === 0 || inputCount > 255) return null;
p += 2;
/** input 序列不含第一分量(第一分量由 coverage/class 决定) */
const inputRaw: number[] = [];
for (let k = 0; k < inputCount - 1; k++) inputRaw.push(r.u16(p + k * 2));
const input = readSeq(inputCount - 1);
if (input === null) return null;
p += (inputCount - 1) * 2;
const lookCount = r.u16(p);
if (lookCount > 255) return null;
p += 2;
const lookRaw: number[] = [];
for (let k = 0; k < lookCount; k++) lookRaw.push(r.u16(p + k * 2));
const look = readSeq(lookCount);
if (look === null) return null;
p += lookCount * 2;
const seqCount = r.u16(p);
if (seqCount > 255) return null;
@ -861,23 +881,6 @@ function parseChainRuleFormat1or2(
for (let k = 0; k < seqCount; k++) {
records.push({ seq: r.u16(p + k * 4), lookup: r.u16(p + k * 4 + 2) });
}
/** format1gid 重映射,子集外 gid 则规则失效format2class index 原样保留 */
const remapSeq = (arr: number[]): number[] | null => {
if (!isGidFormat) return arr.slice();
const out: number[] = [];
for (const v of arr) {
const m = remapGid(origToNew, v);
if (m === null) return null;
out.push(m);
}
return out;
};
const back = remapSeq(backRaw);
const input = remapSeq(inputRaw);
const look = remapSeq(lookRaw);
if (back === null || input === null || look === null) return null;
return { back, input, look, records };
}