perf(cff): Local Subr INDEX 子集化,思源 OTF woff2 641KB→10KB(-98%)

思源 OTF 的 FD12 Local Subr INDEX 含 26550 个子程序(716KB)被完整透传,
但子集 18 字形仅引用 13 个。新增 Type 2 栈模拟扫描 collectSubrRefs
(精确跟踪 stem 计数以正确跳过 hintmask/cntrmask 掩码字节)递归收集引用,
按 Private 池构建 localRemap + writeIndex 重建紧凑 INDEX,
rewriteCharstring 重写 callsubr 操作数到新编号。

收益(otf-思源黑体子集):
- woff2 641480→10664 bytes (-98.3%)
- ttf 752040→19188 bytes (-97.4%)
- min 5.69ms→1.50ms (brotli 输入缩小,-73.6%)
- SSIM 保持 1.0000

global subr 不子集化(思源引用=0,仅 6KB);privSegCache 去重保留。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
崮生(子虚) 2026-07-25 00:59:40 +08:00
parent f1c8315b0c
commit 70d3a2a3dc

View File

@ -138,6 +138,219 @@ function encodeDictInt(v: number): number[] {
return [29, (v >>> 24) & 0xff, (v >> 16) & 0xff, (v >> 8) & 0xff, v & 0xff];
}
/** Type 2 charstring 操作码Adobe Type 2 Charstring Format。 */
const T2_CALLSUBR = 10;
const T2_RETURN = 11;
const T2_ENDCHAR = 14;
const T2_HSTEM = 1;
const T2_VSTEM = 3;
const T2_HSTEMHM = 18;
const T2_VSTEMHM = 23;
const T2_HINTMASK = 19;
const T2_CNTRMASK = 20;
const T2_CALLGSUBR = 29;
/** subr INDEX biasCFF = subr + bias
* nSubrs < 1240 107< 33900 1131 32768 */
function subrBias(nSubrs: number): number {
if (nSubrs < 1240) return 107;
if (nSubrs < 33900) return 1131;
return 32768;
}
/**
* Type 2 charstring/subr local subr global subr
* operand callsubr(10)/callgsubr(29) bias
*
* hintmask(19)/cntrmask(20) ceil(stemCount/8)
* charstring 使 hint
* stemCount stem hstem/vstem/hstemhm/vstemhm stem = 2 operand
*
* @param b CFF
* @param start charstring/subr
* @param end charstring/subr
* @param localBias charstring FD local subr bias
* @param localCount local subr
* @param localRefs local subr
* @param gsubrRefs global subr
*/
export function collectSubrRefs(
b: Uint8Array,
start: number,
end: number,
localBias: number,
localCount: number,
localRefs: Set<number>,
gsubrRefs: Set<number>,
): void {
let p = start;
const stack: number[] = [];
let stemCount = 0;
while (p < end) {
const b0 = b[p++];
if (b0 === 255) {
/** fixed point坐标4 字节,不入栈编号判定 */
stack.push(NaN);
p += 4;
} else if (b0 === 28) {
stack.push(((b[p] << 24) | (b[p + 1] << 16)) >> 16);
p += 2;
} else if (b0 === 29) {
stack.push(((b[p] << 24) | (b[p + 1] << 16) | (b[p + 2] << 8) | b[p + 3]) | 0);
p += 4;
} else if (b0 >= 32 && b0 <= 246) {
stack.push(b0 - 139);
} else if (b0 >= 247 && b0 <= 250) {
stack.push((b0 - 247) * 256 + b[p] + 108);
p += 1;
} else if (b0 >= 251 && b0 <= 254) {
stack.push(-(b0 - 251) * 256 - b[p] - 108);
p += 1;
} else {
/** 操作码b0 <= 27含 12 双字节) */
if (b0 === 12) {
p += 1;
stack.length = 0;
} else if (b0 === T2_HSTEM || b0 === T2_VSTEM || b0 === T2_HSTEMHM || b0 === T2_VSTEMHM) {
stemCount += stack.length >> 1;
stack.length = 0;
} else if (b0 === T2_HINTMASK || b0 === T2_CNTRMASK) {
p += (stemCount + 7) >>> 3;
stack.length = 0;
} else if (b0 === T2_CALLSUBR) {
const arg = stack[stack.length - 1];
if (Number.isInteger(arg)) {
const sn = arg + localBias;
if (sn >= 0 && sn < localCount) localRefs.add(sn);
}
stack.length = 0;
} else if (b0 === T2_CALLGSUBR) {
const arg = stack[stack.length - 1];
if (Number.isInteger(arg)) gsubrRefs.add(arg);
stack.length = 0;
} else if (b0 === T2_ENDCHAR) {
break;
} else {
/** 其余操作码(运动/曲线等)消费栈 */
stack.length = 0;
}
}
}
}
/**
* Type 2 charstring/subr callsubr/callgsubr operand
* hintmask
*
* operand callsubr/callgsubr
* operand operand
*
* @param b CFF
* @param start charstring
* @param end charstring
* @param localBias local subr bias operand
* @param localRemap local subr -1 charstring
* @param newLocalCount local subr bias
* @returns global subr callgsubr operand bias
*/
export function rewriteCharstring(
b: Uint8Array,
start: number,
end: number,
localBias: number,
localRemap: Map<number, number>,
newLocalCount: number,
): Uint8Array {
const newLocalBias = subrBias(newLocalCount);
const out: number[] = [];
/** 栈:记录每个 operand 在 out 中的起始位置(便于截断重写)。值为原始解析值。 */
const stackStart: number[] = [];
const stackVal: number[] = [];
let stemCount = 0;
let p = start;
while (p < end) {
const b0 = b[p++];
if (b0 === 255) {
stackStart.push(out.length);
stackVal.push(NaN);
out.push(255, b[p], b[p + 1], b[p + 2], b[p + 3]);
p += 4;
} else if (b0 === 28) {
stackStart.push(out.length);
stackVal.push(((b[p] << 24) | (b[p + 1] << 16)) >> 16);
out.push(28, b[p], b[p + 1]);
p += 2;
} else if (b0 === 29) {
stackStart.push(out.length);
stackVal.push(((b[p] << 24) | (b[p + 1] << 16) | (b[p + 2] << 8) | b[p + 3]) | 0);
out.push(29, b[p], b[p + 1], b[p + 2], b[p + 3]);
p += 4;
} else if (b0 >= 32 && b0 <= 246) {
stackStart.push(out.length);
stackVal.push(b0 - 139);
out.push(b0);
} else if (b0 >= 247 && b0 <= 250) {
stackStart.push(out.length);
stackVal.push((b0 - 247) * 256 + b[p] + 108);
out.push(b0, b[p]);
p += 1;
} else if (b0 >= 251 && b0 <= 254) {
stackStart.push(out.length);
stackVal.push(-(b0 - 251) * 256 - b[p] - 108);
out.push(b0, b[p]);
p += 1;
} else {
/** 操作码 */
if (b0 === 12) {
out.push(12, b[p]);
p += 1;
stackStart.length = 0;
stackVal.length = 0;
} else if (b0 === T2_HSTEM || b0 === T2_VSTEM || b0 === T2_HSTEMHM || b0 === T2_VSTEMHM) {
stemCount += stackVal.length >> 1;
out.push(b0);
stackStart.length = 0;
stackVal.length = 0;
} else if (b0 === T2_HINTMASK || b0 === T2_CNTRMASK) {
out.push(b0);
const maskBytes = (stemCount + 7) >>> 3;
for (let i = 0; i < maskBytes; i++) out.push(b[p + i]);
p += maskBytes;
stackStart.length = 0;
stackVal.length = 0;
} else if (b0 === T2_CALLSUBR) {
const arg = stackVal[stackVal.length - 1];
const oldSn = Number.isInteger(arg) ? arg + localBias : -1;
const newSn = localRemap.get(oldSn);
if (newSn === undefined) {
/** subr 未保留(理论上引用 charstring 必命中)——保留原 operand 保底 */
out.push(T2_CALLSUBR);
} else {
/** 截断到栈顶 operand 起始,写入新编号编码 */
out.length = stackStart[stackStart.length - 1];
for (const eb of encodeDictInt(newSn - newLocalBias)) out.push(eb);
out.push(T2_CALLSUBR);
}
stackStart.length = 0;
stackVal.length = 0;
} else if (b0 === T2_CALLGSUBR) {
/** global subr 不子集化operand调用编号原样保留bias 不变 */
out.push(T2_CALLGSUBR);
stackStart.length = 0;
stackVal.length = 0;
} else if (b0 === T2_ENDCHAR) {
out.push(b0);
break;
} else {
out.push(b0);
stackStart.length = 0;
stackVal.length = 0;
}
}
}
return new Uint8Array(out);
}
/**
* CFF INDEXcount + offSize + (count+1)*offSize +
* offSize 1-based offset=1
@ -344,10 +557,11 @@ export function subsetCFF(cffBytes: Uint8Array, subsetGids: number[]): Uint8Arra
/** .notdefgid 0必须保留且 subsetGids[0] 应为 0 */
const newSubsetGids = subsetGids[0] === 0 ? subsetGids : [0, ...subsetGids];
/** CharStrings INDEX newSubsetGids charstring
* gid 2 offset offset */
const newCharStringObjects: { bytes: Uint8Array; start: number; len: number }[] = [];
for (const gid of newSubsetGids) {
/** 先记录每个子集字形在原 CharStrings 的字节区间 [start, end),供后续 subr 子集化重写。 */
const newSubsetNumGlyphs = newSubsetGids.length;
const charStringRanges: { start: number; end: number }[] = new Array(newSubsetNumGlyphs);
for (let gi = 0; gi < newSubsetNumGlyphs; gi++) {
const gid = newSubsetGids[gi];
/** 读 offset[gid] 与 offset[gid+1]offSize 字节大端) */
let o0 = 0;
let o1 = 0;
@ -355,17 +569,13 @@ export function subsetCFF(cffBytes: Uint8Array, subsetGids: number[]): Uint8Arra
const p1 = csOffArrStart + (gid + 1) * csOffSize;
for (let j = 0; j < csOffSize; j++) o0 = (o0 << 8) | b[p0 + j];
for (let j = 0; j < csOffSize; j++) o1 = (o1 << 8) | b[p1 + j];
const s = csDataStart + o0 - 1;
const e = csDataStart + o1 - 1;
newCharStringObjects.push({ bytes: b, start: s, len: e - s });
charStringRanges[gi] = { start: csDataStart + o0 - 1, end: csDataStart + o1 - 1 };
}
const newCharStrings = writeIndex(newCharStringObjects);
/** charsetCID-keyed charset gidCID 0/1/2 newSubsetGids CID
* CID 0 .notdefgid 0 charset charset 0
* format(1) + (numGlyphs-1)×CID(u16)charset gid 0 CID 0
* CIDlookupCharsetCID range gid 65535 */
const newSubsetNumGlyphs = newSubsetGids.length;
const newCharsetBody: number[] = [];
for (let i = 1; i < newSubsetNumGlyphs; i++) {
/** newSubsetGids[i] 是原始 gid取其原 CID */
@ -403,10 +613,24 @@ export function subsetCFF(cffBytes: Uint8Array, subsetGids: number[]): Uint8Arra
len: number;
/** Local Subr INDEX 原始字节(无则 null。思源等 CID 字体字形通过 callsubr 引用本地 subr */
localSubr: Uint8Array | null;
/** Local Subr INDEX 全量解析off→字节区间供引用收集与重建null 表示无 local subr */
localSubrIdx: CffIndex | null;
/** 原 local subr biaslocalSubrIdx.count 决定) */
localBias: number;
/** 子集 local subr 旧索引→新索引映射引用收集后填充null 表示无需重映射(无 subr 或全保留) */
localRemap: Map<number, number> | null;
/** 子集后 local subr 总数 */
newLocalCount: number;
/** 子集 local subr INDEX 字节(重建后;无 subr 为 null。最终拼入 Private 段 */
newLocalSubr: Uint8Array | null;
}
interface FdInfo { dictBytes: Uint8Array; priv: PrivInfo; }
/** 原始 Private 段去重:相同 origOff 的 Private 共享同一份(含其 Local Subr */
const privSegCache = new Map<number, PrivInfo>();
/** 每个 unique Private 池(按 privOrigOff收集的 local subr 引用集(多 FD 共享一池时合并) */
const privLocalRefs = new Map<number, Set<number>>();
/** global subr 不子集化collectSubrRefs 的 gsubrRefs 参数占位(引用不收集) */
const dummyGsubrRefs = new Set<number>();
const fdInfos: FdInfo[] = [];
for (const fd of usedFds) {
const s = fdArrayIndex.dataStart + fdArrayIndex.offsets[fd] - 1;
@ -416,7 +640,7 @@ export function subsetCFF(cffBytes: Uint8Array, subsetGids: number[]): Uint8Arra
const priv = fdDict.get(OP_Private);
/** 无 Private 的 FD极罕见原样透传 */
if (!priv || priv.length < 2) {
fdInfos.push({ dictBytes, priv: { origOff: -1, len: 0, localSubr: null } });
fdInfos.push({ dictBytes, priv: { origOff: -1, len: 0, localSubr: null, localSubrIdx: null, localBias: 0, localRemap: null, newLocalCount: 0, newLocalSubr: null } });
continue;
}
const privLen = priv[0];
@ -427,18 +651,105 @@ export function subsetCFF(cffBytes: Uint8Array, subsetGids: number[]): Uint8Arra
const privDict = parseDict(b, privOrigOff, privOrigOff + privLen);
const subrRel = privDict.get(OP_LocalSubr)?.[0];
let localSubr: Uint8Array | null = null;
let localSubrIdx: CffIndex | null = null;
let localBias = 0;
if (subrRel !== undefined) {
/** Local Subr INDEX 紧接 Private DICT 字节之后(绝对偏移 = privOrigOff + subrRel */
/** Local Subr INDEX 仅需整体字节切片透传,不全量解析 offset思源 Local Subr 可达数千 subr */
const subrRange = indexByteRange(b, privOrigOff + subrRel);
localSubr = b.subarray(subrRange.start, subrRange.end);
const subrAbs = privOrigOff + subrRel;
/** INDEX offset subr
* offset Private 1-2 FD subr */
localSubrIdx = readIndex(b, subrAbs);
localBias = subrBias(localSubrIdx.count);
localSubr = b.subarray(localSubrIdx.start, localSubrIdx.end);
}
info = { origOff: privOrigOff, len: privLen, localSubr };
info = { origOff: privOrigOff, len: privLen, localSubr, localSubrIdx, localBias, localRemap: null, newLocalCount: localSubrIdx ? localSubrIdx.count : 0, newLocalSubr: localSubr };
privSegCache.set(privOrigOff, info);
privLocalRefs.set(privOrigOff, new Set());
}
fdInfos.push({ dictBytes, priv: info });
/** 收集该 FD 所有子集字形的 local subr 引用到其 Private 池的引用集 */
const refs = privLocalRefs.get(info.origOff)!;
if (info.localSubrIdx) {
const idx = info.localSubrIdx;
for (let i = 0; i < newSubsetNumGlyphs; i++) {
if (gidOrigFds[i] !== fd) continue;
const r = charStringRanges[i];
collectSubrRefs(b, r.start, r.end, info.localBias, idx.count, refs, dummyGsubrRefs);
}
}
}
/** local subr local subr
* global subr gsubr collectSubrRefs gsubrRefs dummyGsubrRefs */
if (privLocalRefs.size > 0) {
let changed = true;
let guard = 0;
while (changed && guard < 64) {
changed = false;
guard++;
for (const [privOrigOff, refs] of privLocalRefs) {
const info = privSegCache.get(privOrigOff)!;
const idx = info.localSubrIdx;
if (!idx) continue;
const before = refs.size;
for (const sn of [...refs]) {
const ss = idx.dataStart + idx.offsets[sn] - 1;
const se = idx.dataStart + idx.offsets[sn + 1] - 1;
collectSubrRefs(b, ss, se, info.localBias, idx.count, refs, dummyGsubrRefs);
}
if (refs.size > before) changed = true;
}
}
}
/** 各 Private 池:构建旧→新 local subr 映射 + 重建子集 INDEX。引用为空则保留空 INDEX。 */
for (const [privOrigOff, refs] of privLocalRefs) {
const info = privSegCache.get(privOrigOff)!;
const idx = info.localSubrIdx;
if (!idx) continue;
const sortedRefs = [...refs].sort((a, c) => a - c);
const remap = new Map<number, number>();
for (let i = 0; i < sortedRefs.length; i++) remap.set(sortedRefs[i], i);
info.localRemap = remap;
info.newLocalCount = sortedRefs.length;
if (sortedRefs.length === 0) {
/** 无引用:输出空 INDEXcount=02 字节newLocalSubr 占位(下方 patchPrivateDict 后拼入) */
info.newLocalSubr = new Uint8Array(2);
} else {
/** 重建 INDEX按新顺序写出被引用的 subr 字节。subr 内部 callsubr 也需重映射(递归 patch */
const objects: { bytes: Uint8Array; start: number; len: number }[] = [];
for (const oldSn of sortedRefs) {
const ss = idx.dataStart + idx.offsets[oldSn] - 1;
const se = idx.dataStart + idx.offsets[oldSn + 1] - 1;
const rewritten = rewriteCharstring(b, ss, se, info.localBias, remap, sortedRefs.length);
objects.push({ bytes: rewritten, start: 0, len: rewritten.length });
}
info.newLocalSubr = writeIndex(objects);
}
}
/** CharStrings INDEX newSubsetGids charstring
* FD local subr patch callsubr operand subr - bias
* charstring global subr callgsubr operand */
const newCharStringObjects: { bytes: Uint8Array; start: number; len: number }[] = [];
for (let gi = 0; gi < newSubsetNumGlyphs; gi++) {
const r = charStringRanges[gi];
const origFd = gidOrigFds[gi];
/** 该 gid 所属原 FD 对应的 Private 信息(经 privSegCache 去重,按 privOrigOff 查) */
let privInfo: PrivInfo | null = null;
/** usedFds 顺序与 fdInfos 一致,反查 origFd→privInfo */
for (let fi = 0; fi < usedFds.length; fi++) {
if (usedFds[fi] === origFd) { privInfo = fdInfos[fi].priv; break; }
}
if (privInfo && privInfo.localRemap) {
const rewritten = rewriteCharstring(b, r.start, r.end, privInfo.localBias, privInfo.localRemap, privInfo.newLocalCount);
newCharStringObjects.push({ bytes: rewritten, start: 0, len: rewritten.length });
} else {
newCharStringObjects.push({ bytes: b, start: r.start, len: r.end - r.start });
}
}
const newCharStrings = writeIndex(newCharStringObjects);
/** FDSelect gid FD gidOrigFds lookupFDSelect
* FD 01+numGlyphs FD 3 ranges CID OTS */
const newGidToFd: number[] = new Array(newSubsetNumGlyphs);
@ -485,8 +796,8 @@ export function subsetCFF(cffBytes: Uint8Array, subsetGids: number[]): Uint8Arra
/** 先 patch 各唯一 Private DICTop19 指向新 DICT 长度Local Subr 紧跟其后) */
const curPatchedPriv: { dict: Uint8Array; subr: Uint8Array | null }[] = [];
for (const pi of uniquePrivInfos) {
const patchedPriv = patchPrivateDict(b, pi.origOff, pi.len, pi.localSubr !== null);
curPatchedPriv.push({ dict: patchedPriv, subr: pi.localSubr });
const patchedPriv = patchPrivateDict(b, pi.origOff, pi.len, pi.newLocalSubr !== null);
curPatchedPriv.push({ dict: patchedPriv, subr: pi.newLocalSubr });
}
/** patched 私有段总长(含各自 Local Subr */
let curPrivTotal = 0;