From a87f2eb0ff8933260c40a7d99aab152346d01234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B4=AE=E7=94=9F=EF=BC=88=E5=AD=90=E8=99=9A=EF=BC=89?= <2234839456@qq.com> Date: Sat, 25 Jul 2026 03:03:54 +0800 Subject: [PATCH] =?UTF-8?q?perf(cff):=20rewriteCharstring=20=E7=94=A8?= =?UTF-8?q?=E6=A0=87=E9=87=8F=E6=9B=BF=E4=BB=A3=20stackStart[]/stackVal[]?= =?UTF-8?q?=20=E4=B8=A4=E6=95=B0=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 对栈的所有读取仅需栈顶:HSTEM 用 stackLen 算 stemCount,CALLSUBR 用 lastVal 取调用编号、用 lastStart 截断回栈顶 operand 起始处重写。其余 operand 的值/位置从不被读取,故 stackStart/stackVal 两数组可标量化为 stackLen+lastVal+lastStart 三标量(与 collectSubrRefs 同构),消除每个 operand 的 push 装箱 + length=0 重置。 A/B:otf 思源/白狐 × ttf/woff2 共 4 产物逐字节完全一致。 性能:subsetOTF 思源 min 0.266→0.235ms(-11.6%)。 基准测试全通过,SSIM 与基线一致。 Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/font_util/cff-subset.ts | 70 ++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/backend/font_util/cff-subset.ts b/backend/font_util/cff-subset.ts index b1b0c04..78337fc 100644 --- a/backend/font_util/cff-subset.ts +++ b/backend/font_util/cff-subset.ts @@ -327,78 +327,89 @@ export function rewriteCharstring( const cap = ((end - start) << 1) + 16; const out = new Uint8Array(cap); let wp = 0; - /** 栈:记录每个 operand 在输出中的起始 wp(便于截断重写)。值为原始解析值。 */ - const stackStart: number[] = []; - const stackVal: number[] = []; + /** + * 优化:用 stackLen 计数器 + lastVal(栈顶值)+ lastStart(栈顶 operand 在 out 的起始 wp) + * 替代 stackStart[]/stackVal[] 两个数组。 + * 对栈的所有读取仅需栈顶:HSTEM 用 stackLen 算 stemCount;CALLSUBR 用 lastVal 取调用编号、 + * 用 lastStart 截断回栈顶 operand 起始处重写。其余 operand 的值/位置从不被读取。 + * 与 collectSubrRefs 同构(消除每 operand 的 push 装箱 + length=0 重置)。 + * lastVal/lastStart 仅在 stackLen>0 时有效(CALLSUBR 前必有 operand push)。 + */ + let stackLen = 0; + /** 栈顶 operand 的原始解析值(NaN=fixed point 坐标,不可作调用编号) */ + let lastVal = NaN; + /** 栈顶 operand 在 out 中的起始写指针(CALLSUBR 截断回退到此) */ + let lastStart = 0; let stemCount = 0; let p = start; while (p < end) { const b0 = b[p++]; if (b0 === 255) { - stackStart.push(wp); - stackVal.push(NaN); + lastStart = wp; lastVal = NaN; out[wp] = 255; out[wp + 1] = b[p]; out[wp + 2] = b[p + 1]; out[wp + 3] = b[p + 2]; out[wp + 4] = b[p + 3]; wp += 5; p += 4; + stackLen++; } else if (b0 === 28) { - stackStart.push(wp); - stackVal.push(((b[p] << 24) | (b[p + 1] << 16)) >> 16); + lastStart = wp; + lastVal = ((b[p] << 24) | (b[p + 1] << 16)) >> 16; out[wp] = 28; out[wp + 1] = b[p]; out[wp + 2] = b[p + 1]; wp += 3; p += 2; + stackLen++; } else if (b0 === 29) { - stackStart.push(wp); - stackVal.push(((b[p] << 24) | (b[p + 1] << 16) | (b[p + 2] << 8) | b[p + 3]) | 0); + lastStart = wp; + lastVal = ((b[p] << 24) | (b[p + 1] << 16) | (b[p + 2] << 8) | b[p + 3]) | 0; out[wp] = 29; out[wp + 1] = b[p]; out[wp + 2] = b[p + 1]; out[wp + 3] = b[p + 2]; out[wp + 4] = b[p + 3]; wp += 5; p += 4; + stackLen++; } else if (b0 >= 32 && b0 <= 246) { - stackStart.push(wp); - stackVal.push(b0 - 139); + lastStart = wp; + lastVal = b0 - 139; out[wp++] = b0; + stackLen++; } else if (b0 >= 247 && b0 <= 250) { - stackStart.push(wp); - stackVal.push((b0 - 247) * 256 + b[p] + 108); + lastStart = wp; + lastVal = (b0 - 247) * 256 + b[p] + 108; out[wp] = b0; out[wp + 1] = b[p]; wp += 2; p += 1; + stackLen++; } else if (b0 >= 251 && b0 <= 254) { - stackStart.push(wp); - stackVal.push(-(b0 - 251) * 256 - b[p] - 108); + lastStart = wp; + lastVal = -(b0 - 251) * 256 - b[p] - 108; out[wp] = b0; out[wp + 1] = b[p]; wp += 2; p += 1; + stackLen++; } else { /** 操作码 */ if (b0 === 12) { out[wp] = 12; out[wp + 1] = b[p]; wp += 2; p += 1; - stackStart.length = 0; - stackVal.length = 0; + stackLen = 0; } else if (b0 === T2_HSTEM || b0 === T2_VSTEM || b0 === T2_HSTEMHM || b0 === T2_VSTEMHM) { - stemCount += stackVal.length >> 1; + stemCount += stackLen >> 1; out[wp++] = b0; - stackStart.length = 0; - stackVal.length = 0; + stackLen = 0; } else if (b0 === T2_HINTMASK || b0 === T2_CNTRMASK) { out[wp++] = b0; const maskBytes = (stemCount + 7) >>> 3; out.set(b.subarray(p, p + maskBytes), wp); wp += maskBytes; p += maskBytes; - stackStart.length = 0; - stackVal.length = 0; + stackLen = 0; } else if (b0 === T2_CALLSUBR) { - const arg = stackVal[stackVal.length - 1]; - const oldSn = Number.isInteger(arg) ? arg + localBias : -1; + const oldSn = Number.isInteger(lastVal) ? lastVal + localBias : -1; const newSn = localRemap.get(oldSn); if (newSn === undefined) { /** subr 未保留(理论上引用 charstring 必命中)——保留原 operand 保底 */ out[wp++] = T2_CALLSUBR; } else { /** 截断到栈顶 operand 起始,写入新编号编码(直接写 Uint8Array,不分配临时数组) */ - wp = stackStart[stackStart.length - 1]; + wp = lastStart; const delta = newSn - newLocalBias; if (delta >= -107 && delta <= 107) { out[wp++] = delta + 139; @@ -419,20 +430,17 @@ export function rewriteCharstring( } out[wp++] = T2_CALLSUBR; } - stackStart.length = 0; - stackVal.length = 0; + stackLen = 0; } else if (b0 === T2_CALLGSUBR) { /** global subr 不子集化:operand(调用编号)原样保留,bias 不变 */ out[wp++] = T2_CALLGSUBR; - stackStart.length = 0; - stackVal.length = 0; + stackLen = 0; } else if (b0 === T2_ENDCHAR) { out[wp++] = b0; break; } else { out[wp++] = b0; - stackStart.length = 0; - stackVal.length = 0; + stackLen = 0; } } }