From 58fc64788042c17aa14789d3a17da87e8be26089 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 01:35:50 +0800 Subject: [PATCH] =?UTF-8?q?perf(gsub):=20subtable=20=E5=81=8F=E7=A7=BB?= =?UTF-8?q?=E6=A7=BD=20writeUint16=20=E5=8D=A0=E4=BD=8D=E5=9B=9E=E5=A1=AB?= =?UTF-8?q?=E6=9B=BF=E4=BB=A3=20reserveOffset16=20=E9=97=AD=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit subsetGSUB 的 serialize 循环里,每个 lookup 的 subtable 偏移槽原先用 reserveOffset16(per-slot 闭包 + patch push)。思源 GSUB 56 lookup × 323 subtable,分配 323 次闭包。 移植 subsetGPOS 的优化329:writeUint16(0) 占位 + 记录 subtableSlotsStart, 序列化后统一 writeInt16At(subtableSlotsStart+j*2, pos-lookupStart) 回填。 rollback 失败回退后 pos=before(两种路径 subtable 起点一致)。 收益:思源 subsetGSUB 0.2145→0.2021ms (-5.8%),FiraCode 持平(387→378μs 噪声内)。 输出字节完全一致(otf-思源 19188/10664 等),所有字体 SSIM 不变。 与 subsetGPOS 的优化329 同构,统一两处 serialize 实现。 Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/font_util/gsub-subset.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/backend/font_util/gsub-subset.ts b/backend/font_util/gsub-subset.ts index ad8787f..a05db90 100644 --- a/backend/font_util/gsub-subset.ts +++ b/backend/font_util/gsub-subset.ts @@ -1443,9 +1443,14 @@ export function subsetGSUB( w.writeUint16(lk.subtableAbsOffs.length); const lookupStart = w.length - 6; const subtableAbsPositions: number[] = new Array(lk.subtableAbsOffs.length); + /** + * 优化329(与 subsetGPOS 一致):subtable 偏移槽用 writeUint16(0) 占位 + 记录 slot 起点, + * 序列化后统一 writeInt16At 回填,替代 reserveOffset16 的 per-slot 闭包分配 + patch push。 + * 思源 GSUB 56 lookup × 323 subtable,闭包消除省去 323 次对象分配。 + */ + const subtableSlotsStart = w.length; for (let j = 0; j < lk.subtableAbsOffs.length; j++) { - const slotIdx = j; - w.reserveOffset16(lookupStart, () => subtableAbsPositions[slotIdx]); + w.writeUint16(0); } if (useMarkFilteringSet) { w.writeUint16(r.u16(lk.origLookupOff + 6 + lk.subtableAbsOffs.length * 2)); @@ -1457,10 +1462,10 @@ export function subsetGSUB( for (let j = 0; j < lk.subtableAbsOffs.length; j++) { subtableAbsPositions[j] = w.length; writeEmptySubtable(w, lk.effectiveType); + w.writeInt16At(subtableSlotsStart + j * 2, subtableAbsPositions[j] - lookupStart); } } else { for (let j = 0; j < lk.subtableAbsOffs.length; j++) { - subtableAbsPositions[j] = w.length; /** 单个 subtable 重映射失败(coverage gid 全不在子集 / 解析异常)时, * 回退已写入字节,改为输出合法的空 subtable(空 coverage,浏览器跳过,不破坏字体)。 * 不再用 copyBytesBlock 按估算范围拷贝——原始 subtable 数据可能与其他 lookup 物理交错, @@ -1471,6 +1476,9 @@ export function subsetGSUB( w.rollback(before); writeEmptySubtable(w, lk.effectiveType); } + /** 记录实际 subtable 起点(成功=before,失败回退后=before 同值)并回填偏移槽 */ + subtableAbsPositions[j] = before; + w.writeInt16At(subtableSlotsStart + j * 2, before - lookupStart); } } } else {