From 6f96e2b1ddde04a4675e1b7d1ac74b2176972c99 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 18:16:56 +0800 Subject: [PATCH] =?UTF-8?q?perf(post):=20subset=20=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E8=B7=B3=E8=BF=87=20glyph=20name=20=E8=A7=A3=E6=9E=90=E9=99=8D?= =?UTF-8?q?=E7=BA=A7=20format=203=EF=BC=8CCJK=20=E5=AD=90=E9=9B=86?= =?UTF-8?q?=E5=8C=96=E6=8F=90=E9=80=9F=2045%~50%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit subset 模式下 post glyph name 对 web 字体渲染无用(浏览器靠 cmap→gid→glyf 渲染, glyph name 仅用于 PDF 嵌入/编辑器)。原实现 subset 模式保存 pascalString 区 readBytes(思源 278KB)+ view 引用,resolveGlyf 再按需读 nameIndex + 算 name, 实测 post 段占 Font.create 的 69%(思源 56 glyph 子集 1.19ms)。 优化:subset 模式直接降级 post.format=3,跳过 nameIndex 解析、pascalString readBytes、resolveGlyf 按需 name 读取。resolveGlyf 的 `2 === format` 条件不成立 整段跳过;write/size 走 format 3(32 字节,无 nameIndex/names)。 收益(CJK Font.create 是 fontSubset 主体): - 思源千字文中段 3.057→1.673ms(-45%) - 思源标点 2.705→1.361ms(-50%) - FiraCode 22.2→21.0ms(-5.5%,瓶颈在 GSUB 不在此) 体积:FiraCode 8724→8704B(post 无 name 更紧凑) 验证:基准测试 29 用例 SSIM 全部无回归(千字文 0.9986、思源标点 0.9977、 初夏纯标点 0.9920、FiraCode 0.9923、otf-思源 0.9310 均不变)。 Co-Authored-By: Claude Opus 4.8 (1M context) --- vendor/fonteditor-core/lib/ttf/table/post.js | 25 ++++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/vendor/fonteditor-core/lib/ttf/table/post.js b/vendor/fonteditor-core/lib/ttf/table/post.js index 5b9cd0e..b0fb7e6 100644 --- a/vendor/fonteditor-core/lib/ttf/table/post.js +++ b/vendor/fonteditor-core/lib/ttf/table/post.js @@ -41,23 +41,22 @@ var _default = exports.default = _table.default.create('post', [], { if (format === 2) { var numberOfGlyphs = reader.readUint16(); - /* 优化60: 直接 view 批量读取 glyphNameIndex */ - var view = reader.view; - var vOffset = view.byteOffset + reader.offset; - var pascalStringOffset = reader.offset + numberOfGlyphs * 2; - var pascalStringLength = ttf.tables.post.length - (pascalStringOffset - this.offset); - var pascalStringBytes = reader.readBytes(pascalStringOffset, pascalStringLength); - - /* 优化87: subset 模式下只读取子集字形的 nameIndex,跳过其余 */ + /* 优化(post name 跳过):subset 模式下 glyph name 对 web 字体渲染无用(浏览器靠 + * cmap→gid→glyf 渲染,不依赖 post glyph name;glyph name 仅用于 PDF 嵌入/编辑器)。 + * 强制降级 format 3(无 nameIndex/names),跳过 nameIndex 解析、pascalString 区 + * readBytes(思源 278KB)、resolveGlyf 的按需 name 读取(实测占 Font.create 69%)。 + * resolveGlyf 第 148 行 `2 === format` 不成立整段跳过;write/size 走 format 3(32B)。 */ if (ttf.readOptions && ttf.readOptions.subset) { - tbl._pascalStringBytes = pascalStringBytes; - tbl._pascalStringOffsets = null; + tbl.format = 3; tbl.nameIndex = null; tbl.names = null; - /* 保存 view 引用和偏移量,供后续按需读取 nameIndex */ - tbl._nameIndexViewOffset = vOffset; - tbl._nameIndexView = view; } else { + /* 优化60: 直接 view 批量读取 glyphNameIndex */ + var view = reader.view; + var vOffset = view.byteOffset + reader.offset; + var pascalStringOffset = reader.offset + numberOfGlyphs * 2; + var pascalStringLength = ttf.tables.post.length - (pascalStringOffset - this.offset); + var pascalStringBytes = reader.readBytes(pascalStringOffset, pascalStringLength); var glyphNameIndex = new Array(numberOfGlyphs); for (var i = 0; i < numberOfGlyphs; i++) { glyphNameIndex[i] = view.getUint16(vOffset, false);