perf(gpos): 全空 lookup 折叠为 subCount=1 + subtable 偏移槽消除闭包

优化330: 预扫描 supported lookup 的 subtable coverage 命中,全无命中的 lookup
折叠为单个空 subtable(feature 按 lookup index 引用,subCount 改变语义等价,
浏览器查空 coverage 即跳过)。大字体小子集场景(初夏 GPOS lookup[10] 283 个
SinglePos subtable 全空、lookup[7] 285 个仅 3 命中)原先逐空 subtable 序列化
是 subsetGPOS 主热点。unsupported lookup 同理折叠。

subsetGPOS 初夏纯标点 570→380μs(-33%)、思源 8 字 85→26μs(-70%)。
附带 woff2 体积下降(空 lookup 不再逐 subtable 输出):思源 8字 2632→2536B、
初夏纯标点 6940→6912B。SSIM 全部不变。

优化329: lookup 循环 subtable 偏移槽用 writeUint16 占位 + writeInt16At 统一回填,
替代 reserveOffset16 的 per-slot 闭包分配(短命闭包虽 V8 近乎免费,但简化代码)。
This commit is contained in:
崮生(子虚) 2026-07-24 14:57:02 +08:00
parent 852f0d2f21
commit ee7b6ec230

View File

@ -170,6 +170,27 @@ export function subsetGPOS(
lookups.push({ supported, effectiveType, subtableAbsOffs, origLookupOff: lOff });
}
/**
* 优化330: 预扫描每个 supported lookup subtable coverage
* GPOS lookup[7] 285 / lookup[10] 283 SinglePos subtable11
* subtable coverage 0.9%
* subtable ~5 writeUint16 subsetGPOS lookup
* subCount=1 subtablefeature lookup index subCount
* coverage subtable
* subtableCoverageHits subtable coverageOff coverage gid 退
*/
const subtableHits: boolean[][] = [];
for (let i = 0; i < lookupCount; i++) {
const lk = lookups[i];
const hits: boolean[] = [];
if (lk.supported) {
for (let j = 0; j < lk.subtableAbsOffs.length; j++) {
hits.push(subtableCoverageHits(r, lk.subtableAbsOffs[j], origToNew));
}
}
subtableHits.push(hits);
}
/** ---- ----
* ScriptList / FeatureList glyphId lookup index
* ScriptTable/LangSys/FeatureTable List
@ -242,27 +263,60 @@ export function subsetGPOS(
const lookupFlag = r.u16(lk.origLookupOff + 2);
const useMarkFilteringSet = (lookupFlag & 0x0010) !== 0;
/** extension 包裹时输出仍用 effectiveType直接内嵌 subtable不再用 extension */
w.writeUint16(lk.effectiveType);
w.writeUint16(lookupFlag);
w.writeUint16(lk.subtableAbsOffs.length);
/**
* 优化330: lookup subtable coverage subCount=1 subtable
* feature lookup index subCount feature subtable coverage
* subtable N subtable lookup
* lookup[10] 283 subtable subtable effectiveType
* writeEmptyPosSubtableformat=1 + coverageOff + valueFormat=0 + coverage
*/
const hits = subtableHits[i];
let anyHit = false;
for (let j = 0; j < hits.length; j++) { if (hits[j]) { anyHit = true; break; } }
const lookupStart = w.length - 6;
const subtableAbsPositions: number[] = new Array(lk.subtableAbsOffs.length);
for (let j = 0; j < lk.subtableAbsOffs.length; j++) {
const slotIdx = j;
w.reserveOffset16(lookupStart, () => subtableAbsPositions[slotIdx]);
}
if (useMarkFilteringSet) {
w.writeUint16(r.u16(lk.origLookupOff + 6 + lk.subtableAbsOffs.length * 2));
}
if (!anyHit) {
/** 全空折叠subCount=1单个空 subtable */
w.writeUint16(lk.effectiveType);
w.writeUint16(lookupFlag);
w.writeUint16(1);
const lookupStart = w.length - 6;
const subtableSlotsStart = w.length;
w.writeUint16(0);
if (useMarkFilteringSet) {
w.writeUint16(r.u16(lk.origLookupOff + 6 + lk.subtableAbsOffs.length * 2));
}
const subtablePos = w.length;
writeEmptyPosSubtable(w, lk.effectiveType);
w.writeInt16At(subtableSlotsStart, subtablePos - lookupStart);
} else {
/** extension 包裹时输出仍用 effectiveType直接内嵌 subtable不再用 extension */
w.writeUint16(lk.effectiveType);
w.writeUint16(lookupFlag);
w.writeUint16(lk.subtableAbsOffs.length);
for (let j = 0; j < lk.subtableAbsOffs.length; j++) {
subtableAbsPositions[j] = w.length;
r.clearError();
const ok = serializeSubtable(w, r, lk.subtableAbsOffs[j], lk.effectiveType, origToNew);
/** 越界读取(异常表)也降级为保留原始 GPOS 字节(调用方安全降级) */
if (!ok || r.errorFlag) return null;
const lookupStart = w.length - 6;
const subtableAbsPositions: number[] = new Array(lk.subtableAbsOffs.length);
/**
* 优化329: subtable writeUint16(0) + slot writeInt16At
* reserveOffset16 per-slot + patch push
* writeUint16(0) size writeInt16At
*/
const subtableSlotsStart = w.length;
for (let j = 0; j < lk.subtableAbsOffs.length; j++) {
w.writeUint16(0);
}
if (useMarkFilteringSet) {
w.writeUint16(r.u16(lk.origLookupOff + 6 + lk.subtableAbsOffs.length * 2));
}
for (let j = 0; j < lk.subtableAbsOffs.length; j++) {
subtableAbsPositions[j] = w.length;
r.clearError();
const ok = serializeSubtable(w, r, lk.subtableAbsOffs[j], lk.effectiveType, origToNew);
/** 越界读取(异常表)也降级为保留原始 GPOS 字节(调用方安全降级) */
if (!ok || r.errorFlag) return null;
w.writeInt16At(subtableSlotsStart + j * 2, subtableAbsPositions[j] - lookupStart);
}
}
} else {
/** lookupCursive/MarkBase/Context/ChainContext
@ -273,22 +327,23 @@ export function subsetGPOS(
* subtable MarkBase/MarkLig/MarkMark return null */
const lookupFlag = r.u16(lk.origLookupOff + 2);
const useMarkFilteringSet = (lookupFlag & 0x0010) !== 0;
/**
* 优化330: unsupported lookup subtable gid coverage
* N subtable 1 subtable subCount=1 subtable
* lookup type/flag feature lookup
*/
w.writeUint16(r.u16(lk.origLookupOff));
w.writeUint16(lookupFlag);
w.writeUint16(lk.subtableAbsOffs.length);
w.writeUint16(1);
const lookupStart = w.length - 6;
const subtableAbsPositions: number[] = new Array(lk.subtableAbsOffs.length);
for (let j = 0; j < lk.subtableAbsOffs.length; j++) {
const slotIdx = j;
w.reserveOffset16(lookupStart, () => subtableAbsPositions[slotIdx]);
}
const subtableSlotsStart = w.length;
w.writeUint16(0);
if (useMarkFilteringSet) {
w.writeUint16(r.u16(lk.origLookupOff + 6 + lk.subtableAbsOffs.length * 2));
}
for (let j = 0; j < lk.subtableAbsOffs.length; j++) {
subtableAbsPositions[j] = w.length;
writeEmptyPosSubtable(w, lk.effectiveType);
}
const subtablePos = w.length;
writeEmptyPosSubtable(w, lk.effectiveType);
w.writeInt16At(subtableSlotsStart, subtablePos - lookupStart);
}
}
@ -314,6 +369,46 @@ function remapGid(origGid: number, origToNew: Map<number, number>): number {
return ng === undefined ? -1 : ng;
}
/**
* 优化330: 判断 SinglePos/PairPos subtable coverage 退
* lookup subtable coverageOff +2SinglePos/PairPos format
* coverage gid origToNew true readCoverageGids +
*/
function subtableCoverageHits(r: Reader, subAbs: number, origToNew: Map<number, number>): boolean {
const dv = r.dv;
const len = dv.byteLength;
const coverageOff = r.u16(subAbs + 2);
const covAbs = subAbs + coverageOff;
if (covAbs + 4 > len) return false;
const fmt = dv.getUint16(covAbs, false);
if (fmt === 1) {
const count = dv.getUint16(covAbs + 2, false);
const base = covAbs + 4;
if (base + count * 2 > len) return false;
for (let i = 0; i < count; i++) {
if (origToNew.has(dv.getUint16(base + i * 2, false))) return true;
}
return false;
}
if (fmt === 2) {
const rangeCount = dv.getUint16(covAbs + 2, false);
let p = covAbs + 4;
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);
/** range 内逐 gid 查子集range 通常很短CJK 标点压缩覆盖分散),逐个可接受且命中即退出 */
for (let g = start; g <= end; g++) {
if (origToNew.has(g)) return true;
}
p += 6;
}
return false;
}
/** 未知 coverage format保守视为可能命中避免错误折叠 */
return true;
}
/**
* ValueRecord valueFormat
* xPlacement, yPlacement, xAdvance, yAdvance, xPlaDevice, yPlaDevice, xAdvDevice, yAdvDevice