From b7dbe3fe1d05d923ba9c757f44a2cf3d22182686 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: Fri, 24 Jul 2026 10:52:30 +0800 Subject: [PATCH] =?UTF-8?q?perf(hmtx):=20subset=20=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E7=B4=A7=E5=87=91=E5=AD=98=E5=82=A8=EF=BC=8C=E6=B6=88=E9=99=A4?= =?UTF-8?q?=20numGlyphs*2=20=E5=A4=A7=E5=AF=B9=E8=B1=A1=E5=88=86=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit subset 模式下 hmtx 改为按 subsetGids 顺序紧凑存储 Int32Array(S*2), 下标用 gi*2(subsetGids 内位置)替代 gid*2。原实现分配 Int32Array(numGlyphs*2), CJK 字体 numGlyphs 达 3.5 万→284KB 大对象,V8 大对象分配+zero-fill 单次 ~54μs。 resolveGlyf subset 分支同步改用 gi*2 索引。OTF 路径不变(表顺序 hmtx 在 CFF 前, subsetGids 未就绪,仍走全量数组)。 字节逐字节一致(令东/思源/初夏/得意黑/otf 全 cmp 通过),SSIM 全程不变。 初夏 hmtx 0.056→0.002ms(-96%),create -15%,fontSubset 总 -7%。 Co-Authored-By: Claude Opus 4.8 (1M context) --- vendor/fonteditor-core/lib/ttf/table/hmtx.js | 20 ++++++++++++-------- vendor/fonteditor-core/lib/ttf/ttfreader.js | 6 ++++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/vendor/fonteditor-core/lib/ttf/table/hmtx.js b/vendor/fonteditor-core/lib/ttf/table/hmtx.js index 116b604..9972c50 100644 --- a/vendor/fonteditor-core/lib/ttf/table/hmtx.js +++ b/vendor/fonteditor-core/lib/ttf/table/hmtx.js @@ -18,22 +18,24 @@ var _default = exports.default = _table.default.create('hmtx', [], { reader.seek(offset); var numOfLongHorMetrics = ttf.hhea.numOfLongHorMetrics; var numGlyphs = ttf.maxp.numGlyphs; - /* 优化10+82: 扁平数组 [advW, lsb, advW, lsb, ...],消除对象分配 */ - var hMetrics = new Int32Array(numGlyphs * 2); var view = reader.view; var vOffset = view.byteOffset + offset; - /* 优化(subset 按需读):subset 模式下 ttf.subsetGids 已由 glyf.read(表顺序在 hmtx 之前)构建, - * resolveGlyf 也只访问 subsetGids 对应的 hmtx[gid*2]/[gid*2+1]。 - * 原实现全量解析 numOfLongHorMetrics 项(思源 30888,占 Font.create ~10%),对 8 字子集是纯浪费。 - * 改为只读 subsetGids 需要的项,其余位置保持 0(不会被访问)。 */ + /* 优化(subset 紧凑存储):subset 模式下 ttf.subsetGids 已由 glyf.read(表顺序在 hmtx 之前)构建。 + * 按 subsetGids 顺序紧凑存储为 Int32Array(S*2),下标用 gi*2(subsetGids 内的位置),而非 gid*2。 + * resolveGlyf/otfreader 的 subset 分支同样用 gi*2 索引。 + * 原实现分配 Int32Array(numGlyphs*2)(CJK 字体 numGlyphs 达 3.5 万→ 284KB 大对象, + * V8 大对象分配+zero-fill 单次 ~54μs,占初夏 hmtx 全部 0.056ms);紧凑存储仅 S*2 项(初夏 13→104B), + * 分配降到 ~0.2μs。只读 subsetGids 需要的项,全量解析 numOfLongHorMetrics 对小子集是纯浪费。 */ var subsetGids = ttf.readOptions && ttf.readOptions.subset && ttf.readOptions.subset.length > 0 ? ttf.subsetGids : null; if (subsetGids) { + var sl = subsetGids.length; + var hMetrics = new Int32Array(sl * 2); /* Last 段(gid >= numOfLongHorMetrics)的起始字节偏移;共享 advW = 最后一个 LongHorMetric 的 advW */ var lastSegOff = vOffset + numOfLongHorMetrics * 4; var lastAdvWOff = vOffset + (numOfLongHorMetrics - 1) * 4; - for (var si = 0, sl = subsetGids.length; si < sl; si++) { + for (var si = 0; si < sl; si++) { var gid = subsetGids[si]; - var idx = gid * 2; + var idx = si * 2; if (gid < numOfLongHorMetrics) { /* LongHorMetric 段:advW + lsb 各 2 字节 */ var gOff = vOffset + gid * 4; @@ -48,6 +50,8 @@ var _default = exports.default = _table.default.create('hmtx', [], { reader.offset = offset + numOfLongHorMetrics * 4 + (numGlyphs - numOfLongHorMetrics) * 2; return hMetrics; } + /* 全量模式:扁平数组 [advW, lsb, advW, lsb, ...],下标 gid*2(与 resolveGlyf 全量分支一致) */ + var hMetrics = new Int32Array(numGlyphs * 2); /* 优化(TypedArray 批量读+内联翻转):hmtx 是大端,原 DataView.getUint16/getInt16 逐次调用 * 有边界检查开销。LongHorMetric 段(每项 u16 advW + i16 lsb)用 Uint16Array view 共享 buffer * 读取后内联翻转,实测思源黑体(30888 项)快 2×(198→98μs)。offset 需 2 字节对齐, diff --git a/vendor/fonteditor-core/lib/ttf/ttfreader.js b/vendor/fonteditor-core/lib/ttf/ttfreader.js index f1818cd..cd26cb1 100644 --- a/vendor/fonteditor-core/lib/ttf/ttfreader.js +++ b/vendor/fonteditor-core/lib/ttf/ttfreader.js @@ -127,12 +127,14 @@ var TTFReader = exports.default = /*#__PURE__*/function () { } } - /* 优化13+82+118: advanceWidth 遍历优化,使用密集数组 */ + /* 优化13+82+118: advanceWidth 遍历优化,使用密集数组 + * 优化(hmtx 紧凑存储):subset 模式下 hmtx 按 subsetGids 顺序存储(Int32Array(S*2)), + * 下标用 gi*2(subsetGids 内位置),而非原 gid*2。初夏 hmtx 0.056→~0.002ms。 */ var hmtx = ttf.hmtx; if (subsetGids) { for (var gi = 0, gl = subsetGids.length; gi < gl; gi++) { var idxNum = subsetGids[gi]; - var hIdx = idxNum * 2; + var hIdx = gi * 2; glyf[idxNum].advanceWidth = hmtx[hIdx]; glyf[idxNum].leftSideBearing = hmtx[hIdx + 1]; }