From 2a224b1df756ad24840199c05106509f7ef8b83a 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 12:54:41 +0800 Subject: [PATCH] =?UTF-8?q?refactor(cff):=20origFd=E2=86=92privInfo=20?= =?UTF-8?q?=E6=9F=A5=E6=89=BE=E6=94=B9=20fdRemap=20=E4=B8=8B=E6=A0=87=20O(?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit subsetCFF 重建 CharStrings 循环里,原用 usedFds 线性扫描找 origFd 对应的 privInfo(O(glyphs×FDs))。改为复用已有的 fdRemap(origFd→newFdIndex) 直接 下标取 fdInfos[fdIdx].priv(O(1))。gidOrigFids 与 fdRemap 同源构建,每个 fd 必命中;缺失则 null 走透传(与原线性扫描未命中等价)。 字节一致(思源/白狐 3 OTF 用例 sha 全同)。基准无性能收益(思源 usedFds 仅 1-2 个 FD,线性扫描 break 在第一步),纯清理 + 对未来多 FD CID 字体严格更优。 otf-subset.ts: buildSubsetGids 补充 gidToNewGid Map vs Int32Array 边界判据注释 (CID 字体 numGlyphs=65535 致 fill 成本远超 Map 低频查询),防未来重复尝试。 Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/font_util/cff-subset.ts | 11 +++++------ backend/font_util/otf-subset.ts | 6 +++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/backend/font_util/cff-subset.ts b/backend/font_util/cff-subset.ts index 48185bf..3f8f19f 100644 --- a/backend/font_util/cff-subset.ts +++ b/backend/font_util/cff-subset.ts @@ -864,12 +864,11 @@ export function subsetCFF(cffBytes: Uint8Array, subsetGids: number[]): Uint8Arra 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; } - } + /** O(1) 取该 gid 所属原 FD 的 Private 信息:fdRemap(origFd→newFdIndex) 与 fdInfos 同序,直接下标取, + * 替代原 usedFds 线性扫描(大字集多 FD 时 O(glyphs×FDs)→O(glyphs))。gidOrigFds 的每个 fd + * 必在 fdRemap(两者同源构建,见上方 fdRemap 填充循环),缺失则 null 走透传(与原线性扫描未命中等价)。 */ + const fdIdx = fdRemap.get(origFd); + const privInfo: PrivInfo | null = fdIdx !== undefined ? fdInfos[fdIdx].priv : null; if (privInfo && privInfo.localRemap && gidHasSubr[gi]) { const rewritten = rewriteCharstring(b, r.start, r.end, privInfo.localBias, privInfo.localRemap, privInfo.newLocalCount); newCharStringObjects.push({ bytes: rewritten, start: 0, len: rewritten.length }); diff --git a/backend/font_util/otf-subset.ts b/backend/font_util/otf-subset.ts index 35a2212..9c8ae96 100644 --- a/backend/font_util/otf-subset.ts +++ b/backend/font_util/otf-subset.ts @@ -143,7 +143,11 @@ function buildSubsetGids( /** origGid 集合(保持插入顺序,新 gid = 数组 index) */ const subsetGids: number[] = [0]; /** gid → 新 gid(subsetGids 索引),替代 subsetGids.indexOf(gid) 的 O(n) 线性查找。 - * 原 indexOf 对每个 codepoint 遍历 subsetGids,O(N×M);全字符集场景退化为 O(N²)。 */ + * 原 indexOf 对每个 codepoint 遍历 subsetGids,O(N×M);全字符集场景退化为 O(N²)。 + * Map vs Int32Array:此处查询密度 = codePoints 数(~200),而 numGlyphs 可达 65535(CID 字体), + * Int32Array(numGlyphs).fill 成本(65535 次)远超 Map 的 200 次 get,故 Map 更优。 + * 与 [[gpos-gidlookup-int32array]] 区别:那是 GSUB/GPOS 全模块高频数千次查询 + 按需容量,Int32Array 有效; + * 此处低频 + 固定大容量,Map 更优([[gpos-gidlookup-negative]] 旧直觉适用于此场景)。 */ const gidToNewGid = new Map([[0, 0]]); const cpToNewGid = new Map(); for (const cp of codePoints) {