From d3d96d1735c2f5c1ed5b771d762fdd11776bad59 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: Thu, 23 Jul 2026 20:09:18 +0800 Subject: [PATCH] =?UTF-8?q?perf(loca):=20subset=20=E6=A8=A1=E5=BC=8F=20Pro?= =?UTF-8?q?xy=20=E6=8C=89=E9=9C=80=E8=AF=BB=EF=BC=8C=E8=B7=B3=E8=BF=87?= =?UTF-8?q?=E5=85=A8=E9=87=8F=2030907=20=E9=A1=B9=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit subset 模式下 glyf.read 只访问 subsetGids 相关的 ~40 个 loca 项(含 compound 展开),原全量读 numGlyphs 项(思源 30907)+ 分配 Uint32Array(30907)=124KB 是纯浪费。 loca 表读取顺序在 glyf 之前,subsetGids 尚未构建,无法像 hmtx 那样按已知 gid 列表读。 改用 Proxy 拦截 glyf.read 的 loca[index] 访问,首次访问某 gid 时按需从 view 读并缓存。 实测 loca.read 从 profile 12.2% 降至 0%(思源标点),千字文 fontSubset -6.5%。 非 subset 路径保持原 TypedArray(全量访问下 Proxy trap 反慢)。基准 SSIM/ink/字节全无回归。 Co-Authored-By: Claude Opus 4.8 (1M context) --- vendor/fonteditor-core/lib/ttf/table/loca.js | 31 ++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/vendor/fonteditor-core/lib/ttf/table/loca.js b/vendor/fonteditor-core/lib/ttf/table/loca.js index e52bce5..6d8ebe0 100644 --- a/vendor/fonteditor-core/lib/ttf/table/loca.js +++ b/vendor/fonteditor-core/lib/ttf/table/loca.js @@ -16,6 +16,37 @@ var _default = exports.default = _table.default.create('loca', [], { var offset = this.offset; var indexToLocFormat = ttf.head.indexToLocFormat; var numGlyphs = ttf.maxp.numGlyphs; + /* 优化(subset Proxy 按需读):subset 模式下 glyf.read(在 loca 之后读取)只访问 + * subsetGids 相关的 ~20~40 个 loca 项(含 compound 展开后的 gid),而原全量读 + * numGlyphs 项(思源 30907)+ 分配 Uint32Array(30907)=124KB 是纯浪费。 + * loca 表读取顺序在 glyf 之前,subsetGids 尚未构建,故无法像 hmtx 那样按已知 gid 列表读—— + * 改用 Proxy 拦截 glyf.read 的 `loca[index]` 访问,首次访问某 gid 时按需从 view 读并缓存。 + * 实测思源 8 字:全量读 33μs → Proxy 按需 2μs(15×)。Proxy 仅在 subset 模式启用: + * 非 subset(全量字体读取)glyf 访问所有 gid,Proxy trap 开销反而比 TypedArray 慢。 */ + var subset = ttf.readOptions && ttf.readOptions.subset && ttf.readOptions.subset.length > 0; + if (subset) { + var viewSub = reader.view; + var dvOff = viewSub.byteOffset + offset; + /** gid → 字节偏移缓存,避免 compound 多轮重复读同一 gid */ + var cache = new Map(); + /** format0: u16×2(偏移以 2 字节为单位存储);format1: u32 */ + var getLoca = function (gid) { + var v = cache.get(gid); + if (v !== undefined) return v; + v = indexToLocFormat === 0 ? viewSub.getUint16(dvOff + gid * 2, false) * 2 : viewSub.getUint32(dvOff + gid * 4, false); + cache.set(gid, v); + return v; + }; + reader.offset = offset + (indexToLocFormat === 0 ? numGlyphs * 2 : numGlyphs * 4); + /** Proxy 拦截 `loca[index]`:glyf.read 只读访问,无需 set/iterate。 + * target 用空对象,get trap 对整数键按需读 view,其余(如 symbol)返回 undefined。 */ + return new Proxy({}, { + get: function (_t, key) { + var g = typeof key === 'string' ? +key : key; + return typeof g === 'number' && g >= 0 && g < numGlyphs ? getLoca(g) : undefined; + } + }); + } /* 优化(TypedArray 批量读+内联翻转):loca 偏移是大端,原 DataView.getUint16/32 逐次 * 调用有边界检查开销。改用 Uint16/32Array 直接 view 共享 buffer + 内联字节翻转, * 实测 format0 快 46%、format1 快 39%(思源 30907 条 157→84μs / 152→93μs)。