perf(gsub): readClassDefMap format2 range 改子集 gid 二分

原实现遍历每个 ClassDef range 的 [start..end] 全部 gid 查 gidLookup
(FiraCode 实测 33 次 readClassDefMap 展开共 9686 gid 仅命中 19,命中率
0.2%,子集仅 89 gid)。改为对每个 range 在升序子集 gid 数组上二分定位
[start,end] 内的 gid,仅对命中的 set class——从「展开 range 全部 gid」转为
「只处理落在 range 内的子集 gid」。

subsetGSUB: 0.330ms → 0.284ms (-14%),FiraCode woff2 min 2.1→1.8ms
GSUB 输出 sha256 三字体全一致,SSIM 不变(FiraCode 0.9923)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
崮生(子虚) 2026-07-24 16:24:25 +08:00
parent 7f983ff969
commit 06c8fcf18c

View File

@ -82,6 +82,14 @@ const EMPTY_GIDS: number[] = [];
*/
type GidLookup = Int32Array;
/**
* subsetGSUB gid readClassDefMap format2
*
* subsetGSUB 线 sortedSubsetGids 穿
* serializeSubtable serializeChainedContextSubst writeChainFormat2 readClassDefMap
*/
let currentSortedSubsetGids: number[] = [];
/** Coverage gid
* cache coverage off
* coverage subtable dv.getUint16
@ -578,10 +586,14 @@ function serializeLigatureSubst(
}
/**
* ClassDef (origGid classIndex) map gid
* format1: gid classformat2: 区间赋 class
* ClassDef (gid classIndex) mapclass index
*
* format2 range range [start..end] gid gidLookup
* FiraCode 33 readClassDefMap 9686 gid 19 0.2% 89 gid
* range gid [start,end] gid set class
* range gid range gidOpenType ClassDef format2
* ranges start FiraCode currentSortedSubsetGids
*/
/** 读取 ClassDef 表为 (新gid → classIndex) mapclass index 原样保留(不紧致重编号) */
function readClassDefMap(r: Reader, off: number, gidLookup: GidLookup): Map<number, number> {
const result = new Map<number, number>();
if (off === 0) return result;
@ -592,24 +604,40 @@ function readClassDefMap(r: Reader, off: number, gidLookup: GidLookup): Map<numb
for (let i = 0; i < count; i++) {
const origGid = startGid + i;
/** 333gidLookupInt32Array ~1ns origToNew.getMap.get ~9ns
* FiraCode fmt2 ClassDef 9699 gid Map.get fmt2 86μs/11
* gidLookup[g] >= 0 origToNew.has(g) */
const newGid = gidLookup[origGid];
if (newGid >= 0) result.set(newGid, r.u16(off + 6 + i * 2));
}
} else if (format === 2) {
const rangeCount = r.u16(off + 2);
let p = off + 4;
const subsetGids = currentSortedSubsetGids;
const subsetLen = subsetGids.length;
if (subsetLen === 0) return result;
/** ClassDef format2 class range 数组起始(每个 range 6 字节startGid/endGid/startClassIndex */
const rangesBase = off + 4;
const minSubset = subsetGids[0];
const maxSubset = subsetGids[subsetLen - 1];
for (let i = 0; i < rangeCount; i++) {
const p = rangesBase + i * 6;
const start = r.u16(p);
const end = r.u16(p + 2);
/** range 内无 gid 可能时跳过end < 最小子集 gid 或 start > 最大子集 gid */
if (end < minSubset || start > maxSubset) continue;
const cls = r.u16(p + 4);
for (let g = start; g <= end; g++) {
/** 同上gidLookup 数组索引替代 Map.get */
const newGid = gidLookup[g];
if (newGid >= 0) result.set(newGid, cls);
/** 二分定位第一个 >= start 的子集 gid顺序遍历到 > end 为止(子集数组升序) */
let lo = 0;
let hi = subsetLen;
while (lo < hi) {
const mid = (lo + hi) >>> 1;
if (subsetGids[mid] < start) lo = mid + 1;
else hi = mid;
}
for (let j = lo; j < subsetLen; j++) {
const g = subsetGids[j];
if (g > end) break;
/** gidLookup[g] 必 >= 0g 来自子集数组),但仍查以保持与 format1 路径一致 */
result.set(gidLookup[g], cls);
}
p += 6;
}
}
return result;
@ -1188,6 +1216,9 @@ export function subsetGSUB(
const gidLookup: GidLookup = new Int32Array(maxOrigGid + 1).fill(-1);
for (const [g, n] of origToNew) gidLookup[g] = n;
/** 升序子集原始 gid 数组,供 readClassDefMap format2 range 二分优化(替代展开 [start..end] 全部 gid */
currentSortedSubsetGids = Array.from(origToNew.keys()).sort((a, b) => a - b);
/** ---- GSUB Header ---- */
const major = r.u16(0);
const minor = r.u16(2);