From 4a6c7e64e1a5fb11ac12968cfaa5d3ea0b134aab 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, 13 Jun 2026 20:19:57 +0800 Subject: [PATCH] =?UTF-8?q?glm2.5=20=E4=BC=98=E5=8C=96=EF=BC=8C=E6=9A=82?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fonteditor-core/lib/ttf/otf2ttfobject.js | 3 + vendor/fonteditor-core/lib/ttf/otfreader.js | 71 +++- vendor/fonteditor-core/lib/ttf/table/CFF.js | 334 +++++++++++++++--- .../lib/ttf/table/cff/parseCFFCharset.js | 100 +++++- .../lib/ttf/table/cff/parseCFFDict.js | 5 +- .../lib/ttf/table/cff/parseCFFGlyph.js | 5 +- .../lib/ttf/table/cmap/parse.js | 30 +- .../lib/ttf/util/readWindowsAllCodes.js | 48 ++- vendor/fonteditor-core/woff2/woff2-encode.js | 246 ++++++++----- 9 files changed, 650 insertions(+), 192 deletions(-) diff --git a/vendor/fonteditor-core/lib/ttf/otf2ttfobject.js b/vendor/fonteditor-core/lib/ttf/otf2ttfobject.js index 698a1bc..80ef4dd 100644 --- a/vendor/fonteditor-core/lib/ttf/otf2ttfobject.js +++ b/vendor/fonteditor-core/lib/ttf/otf2ttfobject.js @@ -21,6 +21,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de * @return {Object} ttfObject对象 */ function otf2ttfobject(otfBuffer, options) { + var __t0 = process.hrtime.bigint(); var otfObject; if (otfBuffer instanceof ArrayBuffer) { var otfReader = new _otfreader.default(options); @@ -56,5 +57,7 @@ function otf2ttfobject(otfBuffer, options) { /** 优化245: delete → null 赋值,避免 V8 隐藏类转换 */ otfObject.CFF = null; otfObject.VORG = null; + var __t1 = process.hrtime.bigint(); + console.error('otf2ttfobject: ' + Number(__t1 - __t0) / 1e6 + 'ms'); return otfObject; } \ No newline at end of file diff --git a/vendor/fonteditor-core/lib/ttf/otfreader.js b/vendor/fonteditor-core/lib/ttf/otfreader.js index 06b74c5..5da8411 100644 --- a/vendor/fonteditor-core/lib/ttf/otfreader.js +++ b/vendor/fonteditor-core/lib/ttf/otfreader.js @@ -41,6 +41,7 @@ var OTFReader = exports.default = /*#__PURE__*/function () { return _createClass(OTFReader, [{ key: "readBuffer", value: function readBuffer(buffer) { + var __t0 = process.hrtime.bigint(); var reader = new _reader.default(buffer, 0, buffer.byteLength, false); var font = {}; @@ -84,6 +85,8 @@ var OTFReader = exports.default = /*#__PURE__*/function () { _error.default.raise(10303); } reader.dispose(); + var __t1 = process.hrtime.bigint(); + console.error('OTFREADER.readBuffer: ' + Number(__t1 - __t0) / 1e6 + 'ms'); return font; } @@ -95,25 +98,42 @@ var OTFReader = exports.default = /*#__PURE__*/function () { }, { key: "resolveGlyf", value: function resolveGlyf(font) { + var __t0 = process.hrtime.bigint(); var codes = font.cmap; var glyf = font.CFF.glyf; var subsetMap = font.readOptions.subset ? font.subsetMap : null; - /** 优化290: subsetMap 检查提到循环外,消除每次迭代的分支判断 */ - var cmapKeys = Object.keys(codes); - if (subsetMap) { - for (var ki = 0, kl = cmapKeys.length; ki < kl; ki++) { - var c = cmapKeys[ki]; - var i = codes[c]; - if (!subsetMap[i]) continue; - if (!glyf[i].unicode) glyf[i].unicode = []; - glyf[i].unicode.push(+c); + /** + * 优化298: subset 模式下只遍历 subset unicode 列表(O(S)),避免全 cmap 遍历(O(U)) + * 思源等大 CID 字体 cmap 有数万映射,原 Object.keys + 全量循环开销显著 + */ + if (subsetMap && font.readOptions.subset && font.readOptions.subset.length > 0) { + var subsetList = font.readOptions.subset; + for (var si = 0, sl = subsetList.length; si < sl; si++) { + var cp = subsetList[si]; + var gid = codes[cp]; + if (gid === undefined) continue; + if (!subsetMap[gid]) continue; + if (!glyf[gid].unicode) glyf[gid].unicode = []; + glyf[gid].unicode.push(cp); } } else { - for (var ki = 0, kl = cmapKeys.length; ki < kl; ki++) { - var c = cmapKeys[ki]; - var i = codes[c]; - if (!glyf[i].unicode) glyf[i].unicode = []; - glyf[i].unicode.push(+c); + /** 优化290: subsetMap 检查提到循环外,消除每次迭代的分支判断 */ + var cmapKeys = Object.keys(codes); + if (subsetMap) { + for (var ki = 0, kl = cmapKeys.length; ki < kl; ki++) { + var c = cmapKeys[ki]; + var i = codes[c]; + if (!subsetMap[i]) continue; + if (!glyf[i].unicode) glyf[i].unicode = []; + glyf[i].unicode.push(+c); + } + } else { + for (var ki2 = 0, kl2 = cmapKeys.length; ki2 < kl2; ki2++) { + var c2 = cmapKeys[ki2]; + var i2 = codes[c2]; + if (!glyf[i2].unicode) glyf[i2].unicode = []; + glyf[i2].unicode.push(+c2); + } } } @@ -121,8 +141,25 @@ var OTFReader = exports.default = /*#__PURE__*/function () { var hmtxData = font.hmtx; var isFlat = hmtxData instanceof Int32Array; var hLen = isFlat ? hmtxData.length / 2 : hmtxData.length; - /** 优化290: subsetMap 检查提到循环外,消除每次迭代的分支判断 */ - if (subsetMap) { + /** + * 优化298: subset 模式下遍历 subsetGids(O(S)),避免全 hmtx 遍历(O(U)) + */ + if (subsetMap && font.subsetGids) { + var sGids = font.subsetGids; + if (isFlat) { + for (var gi = 0, gl = sGids.length; gi < gl; gi++) { + var gid2 = sGids[gi]; + glyf[gid2].advanceWidth = hmtxData[gid2 * 2] || 0; + glyf[gid2].leftSideBearing = hmtxData[gid2 * 2 + 1]; + } + } else { + for (var gi2 = 0, gl2 = sGids.length; gi2 < gl2; gi2++) { + var gid3 = sGids[gi2]; + glyf[gid3].advanceWidth = hmtxData[gid3].advanceWidth || 0; + glyf[gid3].leftSideBearing = hmtxData[gid3].leftSideBearing; + } + } + } else if (subsetMap) { if (isFlat) { for (var hi = 0, j = 0; hi < hLen; hi++, j += 2) { if (!subsetMap[hi]) continue; @@ -170,6 +207,8 @@ var OTFReader = exports.default = /*#__PURE__*/function () { glyf = subGlyf; } font.glyf = glyf; + var __t1 = process.hrtime.bigint(); + console.error('OTFREADER.resolveGlyf: ' + Number(__t1 - __t0) / 1e6 + 'ms'); } /** diff --git a/vendor/fonteditor-core/lib/ttf/table/CFF.js b/vendor/fonteditor-core/lib/ttf/table/CFF.js index 40dff4d..9aa8111 100644 --- a/vendor/fonteditor-core/lib/ttf/table/CFF.js +++ b/vendor/fonteditor-core/lib/ttf/table/CFF.js @@ -140,6 +140,112 @@ function parseCFFIndexOffsets(reader, offset) { return { offsets: offsets, count: count, dataStart: reader.offset, endOffset: reader.offset }; } +/** + * 优化303: 子集模式下的 CFF 索引按需预读 + * 大 CID 字体(如思源)的 charstring index 含数万字形,parseCFFIndexOffsets 全量读取 + * offset 表(思源 65535×3≈196KB,1.97ms)对 subset 是纯浪费——仅需其中极少数字形。 + * + * 本函数只读取被引用字形及其后一个 offset(用于界定字节范围),并按需读最后一个 + * offset 计算 totalSize 供 prepareCFFIndexView 建立全量大视图。 + * 返回的 indexInfo 兼容 readCFFIndexObject:subsetGids 命中的槽位有真实 offset, + * 其余为 undefined(按需 seek 读取)。 + * + * @param {Reader} reader 读取器 + * @param {number} offset 索引偏移 + * @param {Array} neededGids 需要名字/数据的 GID 升序列表(0-based,不含越界) + * @return {Object} { offsets, count, dataStart, endOffset } + */ +function parseCFFIndexOffsetsSubset(reader, offset, neededGids) { + reader.seek(offset); + var count = reader.readUint16(); + var offsetSize = reader.readUint8(); + /** offset 数组起始(紧跟 count+offsetSize 之后) */ + var offsetArrayBase = reader.offset; + /** + * 优化307: 直接用 DataView 读取 offset,绕过 reader 的原型方法调用 + seek 边界检查。 + * reader.view 是覆盖整个 buffer 的 DataView,offset 坐标系与 reader.offset 一致。 + * 实测 reader.seek + readUint8 链对 15 次读取达 0.5ms(ES5 class 方法开销), + * 直连 DataView 后降至可忽略。 + */ + var view = reader.view; + function readOffAt(pos) { + var off = offsetArrayBase + pos * offsetSize; + if (offsetSize === 1) return view.getUint8(off); + if (offsetSize === 2) return view.getUint16(off, false); + if (offsetSize === 4) return view.getUint32(off, false); + /** offsetSize === 3 */ + return view.getUint8(off) << 16 | view.getUint8(off + 1) << 8 | view.getUint8(off + 2); + } + /** + * 优化308: subset 模式 offsets 用普通对象而非 new Array(count+1)。 + * 思源 charstring index count=65535,new Array(65536) 单次分配 0.32ms, + * 而 subset 仅命中个位数槽位。对象按需添加属性,零分配开销。 + * readCFFIndexObject 的 off[idx] 索引对对象同样有效。 + */ + var offsets = {}; + /** 读取每个所需 GID 及其后一个 offset(界定数据范围) */ + for (var gi = 0; gi < neededGids.length; gi++) { + var gid = neededGids[gi]; + if (gid < 0 || gid > count) continue; + if (offsets[gid] === undefined) offsets[gid] = readOffAt(gid); + if (gid + 1 <= count && offsets[gid + 1] === undefined) offsets[gid + 1] = readOffAt(gid + 1); + } + /** 读最后一个 offset 计算 totalSize,供 prepareCFFIndexView 建全量大视图 */ + var lastOffset = readOffAt(count); + var dataStart = offsetArrayBase + (count + 1) * offsetSize; + /** 同步 reader.offset 到 dataStart,保持后续 reader 读取的坐标连续性 */ + reader.offset = dataStart; + return { + offsets: offsets, + count: count, + dataStart: dataStart, + /** 标记子集模式并提供按需读取所需信息 */ + _subsetMode: true, + _offsetArrayBase: offsetArrayBase, + _offsetSize: offsetSize, + _totalSize: lastOffset - 1 + }; +} + +/** + * 优化304: 完全惰性的 CFF 索引预读(用于 local subrs) + * local subrs 的引用 idx 在 charstring 解析时动态决定,无法预知,故不能像 + * parseCFFIndexOffsetsSubset 那样只读所需 offset。但大 subrs 表(思源单 FD 含 26550 subrs) + * 全量读取 offset 表(26550×3≈80KB,~10万次 readUint8)对 subset 仍是纯浪费—— + * 实际被引用的 subr 通常是个位数。 + * + * 本函数只读 count + offsetSize + 最后一个 offset(算 totalSize 建 view), + * offsets 数组保持稀疏,readCFFIndexObject 按需 seek 填充命中的 idx。 + * + * @param {Reader} reader 读取器 + * @param {number} offset 索引偏移 + * @return {Object} { offsets, count, dataStart, _subsetMode, ... } + */ +function parseCFFIndexOffsetsLazy(reader, offset) { + reader.seek(offset); + var count = reader.readUint16(); + var offsetSize = reader.readUint8(); + var offsetArrayBase = reader.offset; + /** 优化307: 直接用 DataView 读末尾 offset,绕过 reader.seek + 原型方法 */ + var view = reader.view; + var lastOffPos = offsetArrayBase + count * offsetSize; + var lastOffset; + if (offsetSize === 1) lastOffset = view.getUint8(lastOffPos);else if (offsetSize === 2) lastOffset = view.getUint16(lastOffPos, false);else if (offsetSize === 4) lastOffset = view.getUint32(lastOffPos, false);else lastOffset = view.getUint8(lastOffPos) << 16 | view.getUint8(lastOffPos + 1) << 8 | view.getUint8(lastOffPos + 2); + var dataStart = offsetArrayBase + (count + 1) * offsetSize; + /** 同步 reader.offset 到 dataStart,保持后续 reader 读取坐标连续 */ + reader.offset = dataStart; + return { + /** 优化308: 用普通对象替代 new Array(count+1),避免大 subrs 表(26550)的数组分配 */ + offsets: {}, + count: count, + dataStart: dataStart, + _subsetMode: true, + _offsetArrayBase: offsetArrayBase, + _offsetSize: offsetSize, + _totalSize: lastOffset - 1 + }; +} + /** * 根据 parseCFFIndexOffsets 的结果,按需读取第 idx 个 object * @@ -155,6 +261,30 @@ function parseCFFIndexOffsets(reader, offset) { function readCFFIndexObject(reader, indexInfo, idx) { var off = indexInfo.offsets; var view = indexInfo._view; + /** + * 优化303+307: 子集模式下 off[idx]/off[idx+1] 可能为 undefined(未预读), + * 直接用 DataView 读取(绕过 reader 原型方法 + seek 边界检查)。命中槽位直接复用。 + */ + if (off && off[idx] === undefined) { + var base = indexInfo._offsetArrayBase; + var os = indexInfo._offsetSize; + var dv = reader.view; + var o1 = base + idx * os; + var o2 = base + (idx + 1) * os; + if (os === 1) { + off[idx] = dv.getUint8(o1); + off[idx + 1] = dv.getUint8(o2); + } else if (os === 2) { + off[idx] = dv.getUint16(o1, false); + off[idx + 1] = dv.getUint16(o2, false); + } else if (os === 4) { + off[idx] = dv.getUint32(o1, false); + off[idx + 1] = dv.getUint32(o2, false); + } else { + off[idx] = dv.getUint8(o1) << 16 | dv.getUint8(o1 + 1) << 8 | dv.getUint8(o1 + 2); + off[idx + 1] = dv.getUint8(o2) << 16 | dv.getUint8(o2 + 1) << 8 | dv.getUint8(o2 + 2); + } + } if (view) { /** 使用预创建的大视图 + subarray,baseOffset 已含 -1 修正 */ return view.subarray(off[idx] - 1, off[idx + 1] - 1); @@ -170,8 +300,9 @@ function readCFFIndexObject(reader, indexInfo, idx) { */ function prepareCFFIndexView(reader, indexInfo) { var off = indexInfo.offsets; - if (!off || off.length < 2) return; - var totalSize = off[off.length - 1] - 1; + /** 优化303: 子集模式下 off[last] 未读,totalSize 由 parseCFFIndexOffsetsSubset 预先算好 */ + var totalSize = indexInfo._subsetMode ? indexInfo._totalSize : off && off.length >= 2 ? off[off.length - 1] - 1 : 0; + if (totalSize <= 0) return; /** baseOffset 对齐原始 readCFFIndexObject 中的 byteOffset + dataStart + off[idx] - 1 */ var baseOffset = reader.view.byteOffset + indexInfo.dataStart; indexInfo._view = new Uint8Array(reader.view.buffer, baseOffset, totalSize); @@ -241,12 +372,15 @@ function parseFDSelect(reader, offset) { var format = reader.readUint8(); if (format === 0) { - /** format 0:每个 glyph 一个 uint8,存储为扁平数组 */ + /** format 0:每个 glyph 一个 uint8 */ var count = reader.readUint16(); - var flatData = new Uint8Array(count); - for (var i = 0; i < count; i++) { - flatData[i] = reader.readUint8(); - } + /** + * 优化297: 直接以 buffer 视图引用整段 FDSelect0 数据,替代 count 次 readUint8 循环 + * 思源等大 CID 字体 count 可达 6 万+,逐字节读取占可观耗时 + */ + var dataStart = reader.view.byteOffset + reader.offset; + var flatData = new Uint8Array(reader.view.buffer, dataStart, count); + reader.offset += count; return { format: 0, ranges: null, flatData: flatData }; } @@ -320,9 +454,33 @@ function parseFDPrivate(reader, cffOffset, fdDictData, strings) { /** 修复:subrs 偏移量可能为 0(CFF 规范允许), * 原代码用 if (privDict.subrs) 检查,0 是 falsy 导致跳过 subrs 读取 */ if (privDict.subrs != null && privDict.subrs > 0) { - var subrIndex = parseCFFIndex(reader, privOffset + privDict.subrs); - result.subrs = subrIndex.objects; - result.subrsBias = calcCFFSubroutineBias(result.subrs); + /** + * 优化296+304: 完全惰性解析 local subrs + * 思源等 CID 字体的单个 FD 可能含数万 subrs(实测 26550),全量读取 offset 表 + * (~10万次 readUint8)对 subset 是纯浪费——实际被引用的 subr 通常是个位数。 + * 改用 parseCFFIndexOffsetsLazy 只读 count + 末尾 offset(建大视图), + * offsets 数组保持稀疏,readCFFIndexObject 按需 seek 填充命中的 subr。 + */ + var subrIndexInfo = parseCFFIndexOffsetsLazy(reader, privOffset + privDict.subrs); + prepareCFFIndexView(reader, subrIndexInfo); + var subrCount = subrIndexInfo.count; + /** + * 优化311: lazySubrs 用普通对象替代 new Array(subrCount)。 + * 思源单 FD subrs 可达 26550,new Array(26550) 分配 0.15ms;subset 仅引用个位数。 + * _resolveSubr 的按需填充对对象同样有效,bias 用 subrCount 单独计算。 + */ + var lazySubrs = {}; + result.subrs = lazySubrs; + result.subrsBias = calcCFFSubroutineBias({ length: subrCount }); + /** 暴露按需解码器,parseCFFGlyph 访问 subrs[idx] 时调用 */ + result._resolveSubr = function (idx) { + var s = lazySubrs[idx]; + if (s === undefined) { + s = readCFFIndexObject(reader, subrIndexInfo, idx); + lazySubrs[idx] = s; + } + return s; + }; } } } @@ -349,33 +507,75 @@ var _default = exports.default = _table.default.create('cff', [], { // 顶级字典数据 var topDictData = topDictIndex.objects[0]; + /** 优化302: 复用 parseTopDict 内部的 parseCFFDict 结果,避免重复解析一遍 Top DICT */ var dictReader = new _reader.default(new Uint8Array(topDictData).buffer); - var rawTopDict = _parseCFFDict.default.parseCFFDict(dictReader, 0, dictReader.length); - /** 复用同一个 Reader 和解析结果构建 topDict,避免创建第二个 Reader */ - dictReader.seek(0); var topDict = _parseCFFDict.default.parseTopDict(dictReader, 0, dictReader.length, stringIndex.objects); cff.topDict = topDict; - /** 从已解析的原始 Top DICT 获取 CID-keyed 字段 (FDArray/FDSelect) */ - var fdArrayOffset = rawTopDict[1236]; // 12 36 - var fdSelectOffset = rawTopDict[1237]; // 12 37 + /** 从 parseTopDict 保留的原始 dict 获取 CID-keyed 字段 (FDArray=12 36 / FDSelect=12 37) */ + var rawTopDict = topDict._raw; + var fdArrayOffset = rawTopDict[1236]; + var fdSelectOffset = rawTopDict[1237]; var isCID = !!(fdArrayOffset && fdSelectOffset); + /** + * 优化303: subset 模式下提前构建 subsetGids(unicode→gid 映射,含 0=.notdef,升序)。 + * 后续 charstring index 与 charset 均复用此列表,避免各处重复构建。 + * 大 CID 字体(思源 65535 字形)的 charstring index 全量预读 offset 表需 ~2ms, + * subset 仅引用极少字形,改用 parseCFFIndexOffsetsSubset 按需 seek 读取。 + */ + var subset = font.readOptions.subset; + var subsetGids = null; + if (subset && subset.length > 0) { + var _codes = font.cmap; + var _subsetMap = { 0: true }; + var _subsetGids = [0]; + for (var sci = 0, scl = subset.length; sci < scl; sci++) { + var sGid = _codes[subset[sci]]; + if (sGid !== undefined && !_subsetMap[sGid]) { + _subsetMap[sGid] = true; + _subsetGids.push(sGid); + } + } + subsetGids = _subsetGids.length > 1 ? _subsetGids.sort(function (a, b) { + return a - b; + }) : null; + } + /** 解析 FDSelect 和 FDArray(CID-keyed 字体) */ var fdSelect = null; var fdPrivates = null; if (isCID) { - /** 优化:只读取偏移表,不读取全部 charstring 数据 */ - var charStringsInfo = parseCFFIndexOffsets(reader, offset + topDict.charStrings); + /** 优化303: CID 字体的 charstring index 在 subset 模式按需预读,避免全量 offset 表扫描 */ + var charStringsInfo = subsetGids ? parseCFFIndexOffsetsSubset(reader, offset + topDict.charStrings, subsetGids) : parseCFFIndexOffsets(reader, offset + topDict.charStrings); var nGlyphs = charStringsInfo.count; fdSelect = parseFDSelect(reader, offset + fdSelectOffset); - /** 解析 FDArray */ + /** + * 优化306: subset 模式下只解析被引用字形所属 FD 的 Private DICT + local subrs。 + * 思源等大 CID 字体含十余个 FD,subset 仅命中其中少数(常见 1-2 个), + * 全量解析所有 FD 的 Private + 惰性 subrs index 是纯浪费。 + * 非 subset 模式仍全量解析(glyf 全量遍历会引用任意 FD)。 + */ var fdArrayIndex = parseCFFIndex(reader, offset + fdArrayOffset); - fdPrivates = []; - for (var fi = 0; fi < fdArrayIndex.objects.length; fi++) { - fdPrivates.push(parseFDPrivate(reader, offset, fdArrayIndex.objects[fi], stringIndex.objects)); + fdPrivates = new Array(fdArrayIndex.objects.length); + if (subsetGids) { + /** 收集 subsetGids 涉及的 FD index(含 0,.notdef 通常属 FD 0) */ + var neededFds = {}; + neededFds[0] = true; + for (var fgi = 1; fgi < subsetGids.length; fgi++) { + neededFds[lookupFD(fdSelect, subsetGids[fgi])] = true; + } + for (var fi = 0; fi < fdArrayIndex.objects.length; fi++) { + if (neededFds[fi]) { + fdPrivates[fi] = parseFDPrivate(reader, offset, fdArrayIndex.objects[fi], stringIndex.objects); + } + } + } else { + for (var fi = 0; fi < fdArrayIndex.objects.length; fi++) { + fdPrivates.push(parseFDPrivate(reader, offset, fdArrayIndex.objects[fi], stringIndex.objects)); + } } } @@ -396,9 +596,22 @@ var _default = exports.default = _table.default.create('cff', [], { // 私有子glyf数据(非 CID 字体使用) if (privateDict.subrs != null && privateDict.subrs > 0) { var subrOffset = privateDictOffset + privateDict.subrs; - var subrIndex = parseCFFIndex(reader, subrOffset); - cff.subrs = subrIndex.objects; - cff.subrsBias = calcCFFSubroutineBias(cff.subrs); + /** 优化296+304: 完全惰性解析 local subrs,offset 表按需 seek 读取 */ + var subrIndexInfo = parseCFFIndexOffsetsLazy(reader, subrOffset); + prepareCFFIndexView(reader, subrIndexInfo); + var nonCidSubrCount = subrIndexInfo.count; + /** 优化311: 同 CID 路径,用对象替代 new Array 避免大 subrs 表的数组分配 */ + var nonCidLazySubrs = {}; + cff.subrs = nonCidLazySubrs; + cff.subrsBias = calcCFFSubroutineBias({ length: nonCidSubrCount }); + cff._resolveSubr = function (idx) { + var s = nonCidLazySubrs[idx]; + if (s === undefined) { + s = readCFFIndexObject(reader, subrIndexInfo, idx); + nonCidLazySubrs[idx] = s; + } + return s; + }; } else { cff.subrs = []; cff.subrsBias = 0; @@ -407,7 +620,8 @@ var _default = exports.default = _table.default.create('cff', [], { // 解析glyf数据和名字(统一使用延迟读取,避免大字体一次性读取全部 charstring) if (!isCID) { - var charStringsInfo = parseCFFIndexOffsets(reader, offset + topDict.charStrings); + /** 优化303: 非 CID 字体同样在 subset 模式按需预读 charstring index */ + var charStringsInfo = subsetGids ? parseCFFIndexOffsetsSubset(reader, offset + topDict.charStrings, subsetGids) : parseCFFIndexOffsets(reader, offset + topDict.charStrings); } /** 优化244: 预创建全量 charstring 大视图,后续 readCFFIndexObject 用 subarray 替代 new Uint8Array */ prepareCFFIndexView(reader, charStringsInfo); @@ -416,7 +630,11 @@ var _default = exports.default = _table.default.create('cff', [], { if (topDict.charset < 3) { cff.charset = _cffStandardStrings.default; } else { - cff.charset = (0, _parseCFFCharset.default)(reader, offset + topDict.charset, nGlyphs, stringIndex.objects); + /** + * 优化299+303: subset 模式下复用已构建的 subsetGids 传给 parseCFFCharset, + * 使其只填充被引用 GID 的名字槽位,跳过数万无关 SID 的展开 + */ + cff.charset = (0, _parseCFFCharset.default)(reader, offset + topDict.charset, nGlyphs, stringIndex.objects, subsetGids); } // Standard encoding @@ -440,9 +658,12 @@ var _default = exports.default = _table.default.create('cff', [], { fdGlyphFonts = new Array(fdPrivates.length); for (var fi = 0; fi < fdPrivates.length; fi++) { var fd = fdPrivates[fi]; + if (!fd) continue; fdGlyphFonts[fi] = { subrs: fd.subrs, subrsBias: fd.subrsBias, + /** 优化296: 透传惰性 subrs 解码器 */ + _resolveSubr: fd._resolveSubr, defaultWidthX: fd.defaultWidthX, nominalWidthX: fd.nominalWidthX, gsubrs: cff.gsubrs, @@ -452,7 +673,30 @@ var _default = exports.default = _table.default.create('cff', [], { } function getGlyphFont(glyphIndex) { if (fdGlyphFonts) { - return fdGlyphFonts[lookupFD(fdSelect, glyphIndex)]; + var fdIdx = lookupFD(fdSelect, glyphIndex); + var gfont = fdGlyphFonts[fdIdx]; + /** + * 优化306: subset 模式下未预先解析的 FD 按需惰性解析。 + * 正常 subset 流程中所需 FD 已预先解析,此分支仅作 .notdef 等边界情况兜底。 + */ + if (!gfont && fdArrayIndex) { + var fdData = fdArrayIndex.objects[fdIdx]; + if (fdData) { + var lazyFd = parseFDPrivate(reader, offset, fdData, stringIndex.objects); + fdPrivates[fdIdx] = lazyFd; + gfont = { + subrs: lazyFd.subrs, + subrsBias: lazyFd.subrsBias, + _resolveSubr: lazyFd._resolveSubr, + defaultWidthX: lazyFd.defaultWidthX, + nominalWidthX: lazyFd.nominalWidthX, + gsubrs: cff.gsubrs, + gsubrsBias: cff.gsubrsBias + }; + fdGlyphFonts[fdIdx] = gfont; + } + } + return gfont || cff; } return cff; } @@ -460,43 +704,35 @@ var _default = exports.default = _table.default.create('cff', [], { // only parse subset glyphs var subset = font.readOptions.subset; if (subset && subset.length > 0) { - // subset map - var subsetMap = { - 0: true // 设置.notdef - }; - /** 优化251: 构建 subsetMap 时同步收集 subsetGids,避免全量 nGlyphs 遍历 */ - var subsetGids = [0]; - var codes = font.cmap; - - // unicode to index - for (var si = 0, sl = subset.length; si < sl; si++) { - var code = subset[si]; - var ci = codes[code]; - if (ci !== undefined && !subsetMap[ci]) { - subsetMap[ci] = true; - subsetGids.push(ci); - } + /** + * 优化303: 复用外层已构建的 subsetGids 与 subsetMap,避免重复扫描 cmap。 + * subsetGids 为 null 表示仅有 .notdef(subset 未命中任何字形),退化为 [0]。 + */ + var finalSubsetGids = subsetGids || [0]; + var subsetMap = { 0: true }; + for (var smi = 1; smi < finalSubsetGids.length; smi++) { + subsetMap[finalSubsetGids[smi]] = true; } font.subsetMap = subsetMap; /* 优化258: CID/non-CID 分支到循环外,消除每 glyph 的三元分支 */ if (fdGlyphFonts) { - for (var si = 0, sl = subsetGids.length; si < sl; si++) { - var i = subsetGids[si]; + for (var si = 0, sl = finalSubsetGids.length; si < sl; si++) { + var i = finalSubsetGids[si]; var charstring = readCFFIndexObject(reader, charStringsInfo, i); var glyf = (0, _parseCFFGlyph.default)(charstring, getGlyphFont(i), i); glyf.name = cff.charset[i]; cff.glyf[i] = glyf; } } else { - for (var si = 0, sl = subsetGids.length; si < sl; si++) { - var i = subsetGids[si]; + for (var si = 0, sl = finalSubsetGids.length; si < sl; si++) { + var i = finalSubsetGids[si]; var charstring = readCFFIndexObject(reader, charStringsInfo, i); var glyf = (0, _parseCFFGlyph.default)(charstring, cff, i); glyf.name = cff.charset[i]; cff.glyf[i] = glyf; } } - font.subsetGids = subsetGids; + font.subsetGids = finalSubsetGids; } // parse all else { diff --git a/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFCharset.js b/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFCharset.js index d08afd3..2230f0f 100644 --- a/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFCharset.js +++ b/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFCharset.js @@ -21,9 +21,10 @@ var STD_STRINGS = _cffStandardStrings.default; * @param {number} start 起始偏移 * @param {number} nGlyphs 字形个数 * @param {Object} strings cff字符串字典 + * @param {Array=} subsetGids 可选,subset 模式下需要名字的 GID 升序列表(含 0) * @return {Array} 字符集 */ -function parseCFFCharset(reader, start, nGlyphs, strings) { +function parseCFFCharset(reader, start, nGlyphs, strings, subsetGids) { if (start) { reader.seek(start); } @@ -31,32 +32,109 @@ function parseCFFCharset(reader, start, nGlyphs, strings) { var sid; var count; nGlyphs -= 1; - /** 优化250: 预分配 charset 数组,避免 push 扩容 */ - var charset = new Array(nGlyphs + 1); - charset[0] = '.notdef'; + /** + * 优化310: subset 模式下 charset 用普通对象替代 new Array(nGlyphs+1)。 + * 思源 nGlyphs=65535,new Array(65536) 单次分配 0.32ms;subset 仅命中个位数槽。 + * 调用方仅用 charset[gid] 索引访问,对象同样有效。非 subset 模式仍用数组(全量填充)。 + */ + var hasSubsetPre = subsetGids && subsetGids.length > 1; + var charset = hasSubsetPre ? { 0: '.notdef' } : new Array(nGlyphs + 1); + if (!hasSubsetPre) charset[0] = '.notdef'; var ci = 1; + /** + * 优化299: subset 模式下只填充被引用 GID 的名字槽位 + * 思源等大 CID 字体 charset 含数万 SID,但 subset 仅引用极少数。 + * subsetGids 已升序(含 0),用指针 sgp 扫描:每个 range 只在覆盖某个目标 GID 时展开, + * 否则 ci += count+1 跳过,避免 O(nGlyphs) 的逐项 SID 写入。 + */ + var hasSubset = subsetGids && subsetGids.length > 1; + /** sgp 指向 subsetGids 中第一个 > 0 的项(0 已固定为 .notdef) */ + var sgp = 1; + /** 返回当前 range 内是否有目标 GID 尚未消费;无 subset 时恒真走全量路径 */ + function rangeHasTarget(rangeStart, rangeCount) { + if (!hasSubset) return true; + var rangeEnd = rangeStart + rangeCount; + while (sgp < subsetGids.length) { + var g = subsetGids[sgp]; + if (g > rangeEnd) return false; + if (g >= rangeStart) return true; + sgp++; + } + return false; + } var format = reader.readUint8(); if (format === 0) { + /** format 0 每项一个 uint16 SID,reader 顺序读,无法跳过中间字节;仅按需赋值 */ for (i = 0; i < nGlyphs; i += 1) { sid = reader.readUint16(); - charset[ci++] = sid <= 390 ? STD_STRINGS[sid] : strings[sid - 391]; + if (!hasSubset || (sgp < subsetGids.length && subsetGids[sgp] === ci)) { + charset[ci] = sid <= 390 ? STD_STRINGS[sid] : strings[sid - 391]; + if (hasSubset) sgp++; + } + ci++; } } else if (format === 1) { while (ci <= nGlyphs) { + /** + * 优化305: subset 模式下,所有目标 GID 已命中后即可终止扫描。 + * charset range 按 first GID 升序排列,subsetGids 亦升序, + * sgp 走到末尾后后续 range 不可能再命中。 + */ + if (hasSubset && sgp >= subsetGids.length) break; sid = reader.readUint16(); count = reader.readUint8(); - for (i = 0; i <= count; i += 1) { - charset[ci++] = sid <= 390 ? STD_STRINGS[sid] : strings[sid - 391]; - sid += 1; + if (rangeHasTarget(ci, count)) { + /** + * 优化309: subset 模式下 range 命中时,只对落在本 range 的目标 GID 赋值, + * 不遍历整个 range(思源单 range 可覆盖数万字形,全遍历是 charset 阶段主要耗时)。 + * range 内第 k 个字形 SID = sid + k,可直接计算,无需 reader 逐项读取。 + */ + if (hasSubset) { + var rangeEnd = ci + count; + while (sgp < subsetGids.length && subsetGids[sgp] <= rangeEnd) { + var tg = subsetGids[sgp]; + var tsid = sid + (tg - ci); + charset[tg] = tsid <= 390 ? STD_STRINGS[tsid] : strings[tsid - 391]; + sgp++; + } + ci += count + 1; + } else { + for (i = 0; i <= count; i += 1) { + charset[ci] = sid <= 390 ? STD_STRINGS[sid] : strings[sid - 391]; + ci++; + sid += 1; + } + } + } else { + ci += count + 1; } } } else if (format === 2) { while (ci <= nGlyphs) { + /** 优化305: 同 format 1,subset 目标全部命中后提前终止 */ + if (hasSubset && sgp >= subsetGids.length) break; sid = reader.readUint16(); count = reader.readUint16(); - for (i = 0; i <= count; i += 1) { - charset[ci++] = sid <= 390 ? STD_STRINGS[sid] : strings[sid - 391]; - sid += 1; + if (rangeHasTarget(ci, count)) { + /** 优化309: 同 format 1,subset 命中 range 时只赋值目标 GID,跳过 range 其余部分 */ + if (hasSubset) { + var _rangeEnd = ci + count; + while (sgp < subsetGids.length && subsetGids[sgp] <= _rangeEnd) { + var _tg = subsetGids[sgp]; + var _tsid = sid + (_tg - ci); + charset[_tg] = _tsid <= 390 ? STD_STRINGS[_tsid] : strings[_tsid - 391]; + sgp++; + } + ci += count + 1; + } else { + for (i = 0; i <= count; i += 1) { + charset[ci] = sid <= 390 ? STD_STRINGS[sid] : strings[sid - 391]; + ci++; + sid += 1; + } + } + } else { + ci += count + 1; } } } else { diff --git a/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFDict.js b/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFDict.js index 26200d1..ba61a79 100644 --- a/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFDict.js +++ b/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFDict.js @@ -278,7 +278,10 @@ function parseCFFDict(reader, offset, length) { */ function parseTopDict(reader, start, length, strings) { var dict = parseCFFDict(reader, start || 0, length || reader.length); - return interpretDict(dict, TOP_DICT_META, strings); + var topDict = interpretDict(dict, TOP_DICT_META, strings); + /** 优化302: 保留原始 dict 供调用方取 CID 字段(FDArray=1236/FDSelect=1237),避免重复解析 parseCFFDict */ + topDict._raw = dict; + return topDict; } /** diff --git a/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFGlyph.js b/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFGlyph.js index 41a15a8..af169b5 100644 --- a/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFGlyph.js +++ b/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFGlyph.js @@ -29,6 +29,8 @@ function parseCFFCharstring(code, font, index) { /** 优化211: 解构 font 热路径属性,消除重复属性查找 */ var subrs = font.subrs; var subrsBias = font.subrsBias; + /** 优化296: 惰性 local subrs 解码器(CID 字体单 FD 可能含数万 subrs) */ + var resolveSubr = font._resolveSubr; var gsubrs = font.gsubrs; var gsubrsBias = font.gsubrsBias; var nominalWidthX = font.nominalWidthX; @@ -190,7 +192,8 @@ function parseCFFCharstring(code, font, index) { case 10: // callsubr codeIndex = stack[--sp] + subrsBias; - subrCode = subrs[codeIndex]; + /** 优化296: 优先用惰性解码器,未设置时回退直接索引 */ + subrCode = resolveSubr ? resolveSubr(codeIndex) : subrs[codeIndex]; if (subrCode) { parse(subrCode); } diff --git a/vendor/fonteditor-core/lib/ttf/table/cmap/parse.js b/vendor/fonteditor-core/lib/ttf/table/cmap/parse.js index b044154..bed1b79 100644 --- a/vendor/fonteditor-core/lib/ttf/table/cmap/parse.js +++ b/vendor/fonteditor-core/lib/ttf/table/cmap/parse.js @@ -160,16 +160,28 @@ function readSubTable(reader, ttf, subTable, cmapOffset) { format12.language = view.getUint32(vOffset, false); vOffset += 4; format12.nGroups = view.getUint32(vOffset, false); vOffset += 4; var nGroups = format12.nGroups; - /* 优化88: 扁平数组存储 groups,减少对象创建 [start, end, startId, ...] */ - var groups = new Array(nGroups * 3); - for (var h = 0, gi = 0; h < nGroups; h++, gi += 3) { - groups[gi] = view.getUint32(vOffset, false); - groups[gi + 1] = view.getUint32(vOffset + 4, false); - groups[gi + 2] = view.getUint32(vOffset + 8, false); - vOffset += 12; + /** + * 优化300: subset 模式下 format12 延迟解析,不展开 nGroups 个 group + * 思源等大 CID 字体 format12 有 1.5 万+ group,全量展开需 4.5 万次 getUint32。 + * subset 仅查找少数 cp,lookupFormat12 直接从 view 二分查找(group 已升序、每项 12 字节)。 + */ + var isSubset12 = ttf.readOptions && ttf.readOptions.subset; + if (isSubset12) { + format12._cmapView = view; + format12._groupsOffset = vOffset; + format12._lazyGroups = true; + } else { + /* 优化88: 扁平数组存储 groups,减少对象创建 [start, end, startId, ...] */ + var groups = new Array(nGroups * 3); + for (var h = 0, gi = 0; h < nGroups; h++, gi += 3) { + groups[gi] = view.getUint32(vOffset, false); + groups[gi + 1] = view.getUint32(vOffset + 4, false); + groups[gi + 2] = view.getUint32(vOffset + 8, false); + vOffset += 12; + } + format12.groups = groups; + format12._flatGroups = true; } - format12.groups = groups; - format12._flatGroups = true; } else if (subTable.format === 14) { /* 优化93: subset 模式下跳过 format14 完整解析 */ diff --git a/vendor/fonteditor-core/lib/ttf/util/readWindowsAllCodes.js b/vendor/fonteditor-core/lib/ttf/util/readWindowsAllCodes.js index a8a6127..d3cbee8 100644 --- a/vendor/fonteditor-core/lib/ttf/util/readWindowsAllCodes.js +++ b/vendor/fonteditor-core/lib/ttf/util/readWindowsAllCodes.js @@ -13,20 +13,42 @@ exports.default = readWindowsAllCodes; /** * 优化65+88: format12 二分查找,支持扁平数组格式 + * 优化300: 支持 _lazyGroups 延迟模式,直接从 view 二分查找(每 group 12 字节、已升序) */ -function lookupFormat12(groups, unicode) { - var lo = 0, hi = (groups.length / 3) - 1; - while (lo <= hi) { - var mid = (lo + hi) >> 1; - var gi = mid * 3; - var gStart = groups[gi]; - var gEnd = groups[gi + 1]; - if (unicode < gStart) { - hi = mid - 1; - } else if (unicode > gEnd) { - lo = mid + 1; +function lookupFormat12(format12, unicode) { + /** 延迟模式:直接从 view 读取,避免展开数万 group */ + if (format12._lazyGroups) { + var view = format12._cmapView; + var base = format12._groupsOffset; + var lo = 0, hi = format12.nGroups - 1; + while (lo <= hi) { + var mid = (lo + hi) >> 1; + var gOff = base + mid * 12; + var gStart = view.getUint32(gOff, false); + var gEnd = view.getUint32(gOff + 4, false); + if (unicode < gStart) { + hi = mid - 1; + } else if (unicode > gEnd) { + lo = mid + 1; + } else { + return view.getUint32(gOff + 8, false) + (unicode - gStart); + } + } + return -1; + } + var groups = format12.groups; + var lo2 = 0, hi2 = (groups.length / 3) - 1; + while (lo2 <= hi2) { + var mid2 = (lo2 + hi2) >> 1; + var gi = mid2 * 3; + var gStart2 = groups[gi]; + var gEnd2 = groups[gi + 1]; + if (unicode < gStart2) { + hi2 = mid2 - 1; + } else if (unicode > gEnd2) { + lo2 = mid2 + 1; } else { - return groups[gi + 2] + (unicode - gStart); + return groups[gi + 2] + (unicode - gStart2); } } return -1; @@ -107,7 +129,7 @@ function readWindowsAllCodes(tables, ttf) { var gid = lookupFormat4(format4, u, f4GIAO); if (gid >= 0) { codes[u] = gid; continue; } } - var gid12 = lookupFormat12(format12.groups, u); + var gid12 = lookupFormat12(format12, u); if (gid12 >= 0) { codes[u] = gid12; } } } else if (format4) { diff --git a/vendor/fonteditor-core/woff2/woff2-encode.js b/vendor/fonteditor-core/woff2/woff2-encode.js index 35306cf..6a698e1 100644 --- a/vendor/fonteditor-core/woff2/woff2-encode.js +++ b/vendor/fonteditor-core/woff2/woff2-encode.js @@ -20,15 +20,17 @@ const BROTLI_PARAM_QUALITY = zlib.constants?.BROTLI_PARAM_QUALITY ?? 3; const BROTLI_PARAM_SIZE_HINT = zlib.constants?.BROTLI_PARAM_SIZE_HINT ?? 4; /** - * Brotli 压缩参数:quality 8 - * 测试表明 FONT 模式对小数据集(<200KB)无速度优势且增加 0.14% 体积,保持 GENERIC + * Brotli 压缩参数:quality 6 + * 实测在 woff2 变换数据(~120KB glyf transform)上: + * q8 = 4.47ms / 77111B,q6 = 2.65ms / 77227B(-1.8ms 即 -40% 时间,体积仅 +0.15%) + * 中文字体子集体积几乎不变,SSIM 不受影响(WOFF2 为无损容器),性能收益显著 */ const BROTLI_OPTIONS_BASE = { - params: { [BROTLI_PARAM_QUALITY]: 8 }, + params: { [BROTLI_PARAM_QUALITY]: 6 }, }; /** 优化: 预分配 options 模板,避免每次 encode 创建 computed property name 对象 */ const BROTLI_OPTIONS_WITH_HINT = { - params: { [BROTLI_PARAM_QUALITY]: 8, [BROTLI_PARAM_SIZE_HINT]: 0 }, + params: { [BROTLI_PARAM_QUALITY]: 6, [BROTLI_PARAM_SIZE_HINT]: 0 }, }; /* ======== 大端序读写工具函数(模块级,消除闭包分配) ======== */ @@ -242,6 +244,15 @@ function calcTripletAndWrite(curveBit, dx, dy, buf, offset) { /* ======== glyf + loca 表变换 ======== */ +/** + * 优化301: 模块级复用坐标缓冲区,避免每个简单 glyph 分配 xCoords/yCoords Int32Array + * transformGlyfAndLoca 同步单线程调用,复用安全;按需扩容,不释放 + */ +let _reuseXCoords = new Int32Array(256); +let _reuseYCoords = new Int32Array(256); +/** 优化301: 复用 encode255UInt16 的 3 字节编码缓冲区 */ +const _reuseEnc255 = [0, 0, 0]; + /** * 对 glyf + loca 表执行 WOFF2 变换 */ @@ -269,22 +280,33 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { const ONCURVE_FLAG = 1; /** - * 优化:合并第 1 遍(解码 glyph)和第 2 遍(预计算 stream 大小 + 缓存 triplet flag) - * 消除一次完整的 numGlyphs 遍历,利用解码后数据仍在 L1 cache 的优势 + * 优化294: flagStream/glyphStream/instructionStream 数据在 Pass 1 直接追加写入连续累积缓冲区, + * 消除每个简单 glyph 的 flagsArr / glyphStreamBuf 分配,以及 Pass 2 的逐 glyph set 拷贝。 + * Pass 2 仅整体 set 三个累积缓冲区到 result 对应区域。 + * 优化295: 初始容量按 glyf 表大小预分配(triplet 数据 + flag 数据均不会超过原始 glyf 字节数), + * 避免双倍扩容触发的多次分配+拷贝 */ + const initialCap = glyfData.length; + let flagAccumCap = initialCap; + let flagAccum = new Uint8Array(flagAccumCap); + let flagAccumLen = 0; + let glyphAccumCap = initialCap; + let glyphAccum = new Uint8Array(glyphAccumCap); + let glyphAccumLen = 0; + let instrAccumCap = 256; + let instrAccum = new Uint8Array(instrAccumCap); + let instrAccumLen = 0; + let totalNPointsSize = 0; - let flagStreamSize = 0; let glyphStreamSize = 0; let bboxStreamSize = 0; - let instructionStreamSize = 0; let hasOverlapBitmap = false; - let totalPoints = 0; const bboxBitmapSize = ((numGlyphs + 31) >>> 5) << 2; const bboxBitmap = new Uint8Array(bboxBitmapSize); const overlapBitmap = new Uint8Array(bboxBitmapSize); - /* 收集每个 glyph 的信息 */ + /* 收集每个 glyph 的元数据(精简:仅 Pass 2 写 nContour/nPoints/bbox 所需字段) */ const glyphInfos = new Array(numGlyphs); for (let gi = 0; gi < numGlyphs; gi++) { @@ -307,7 +329,7 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { let compOff = glyphStart + 10; let haveInstructions = false; let instrLength = 0; - let instructions = null; + let instrOffset = 0; const MORE_COMPONENTS = 0x0020; const WE_HAVE_INSTRUCTIONS = 0x0100; @@ -334,28 +356,59 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { instrLength = readU16(glyfData, compOff); compOff += 2; if (instrLength > 0 && compOff + instrLength <= glyphEnd) { - instructions = { offset: compOff, length: instrLength }; + instrOffset = compOff; + } else { + instrLength = 0; } } + const rawOffset = glyphStart + 10; const rawLength = componentDataEnd - glyphStart - 10; glyphInfos[gi] = { composite: true, xMin, yMin, xMax, yMax, - rawOffset: glyphStart + 10, + rawOffset, rawLength, - instructions, - haveInstructions, + instrOffset, + instrLength, }; - /* ★ 合并:复合 glyph 的统计量 */ + /* ★ 复合 glyph 的统计量 + glyphStream 追加 rawData */ bboxBitmap[gi >> 3] |= (0x80 >> (gi & 7)); bboxStreamSize += 8; + + /* glyphAccum: 追加 raw 组件数据 */ + if (glyphAccumLen + rawLength > glyphAccumCap) { + while (glyphAccumLen + rawLength > glyphAccumCap) glyphAccumCap *= 2; + const nb = new Uint8Array(glyphAccumCap); + nb.set(glyphAccum.subarray(0, glyphAccumLen)); + glyphAccum = nb; + } + glyphAccum.set(glyfData.subarray(rawOffset, rawOffset + rawLength), glyphAccumLen); + glyphAccumLen += rawLength; glyphStreamSize += rawLength; - if (haveInstructions) { - instructionStreamSize += instrLength; - glyphStreamSize += size255UInt16(instrLength); + + if (instrLength > 0) { + /* instructionAccum 追加 */ + if (instrAccumLen + instrLength > instrAccumCap) { + while (instrAccumLen + instrLength > instrAccumCap) instrAccumCap *= 2; + const ib = new Uint8Array(instrAccumCap); + ib.set(instrAccum.subarray(0, instrAccumLen)); + instrAccum = ib; + } + instrAccum.set(glyfData.subarray(instrOffset, instrOffset + instrLength), instrAccumLen); + instrAccumLen += instrLength; + /* glyphAccum 追加 encode255UInt16(instrLength) */ + const n = encode255UInt16(instrLength, _reuseEnc255, 0); + if (glyphAccumLen + n > glyphAccumCap) { + while (glyphAccumLen + n > glyphAccumCap) glyphAccumCap *= 2; + const nb2 = new Uint8Array(glyphAccumCap); + nb2.set(glyphAccum.subarray(0, glyphAccumLen)); + glyphAccum = nb2; + } + for (let e = 0; e < n; e++) glyphAccum[glyphAccumLen++] = _reuseEnc255[e]; + glyphStreamSize += n; } continue; } @@ -363,7 +416,6 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { /* 简单 glyph */ let dataOff = glyphStart + 10; - /** 优化291: Pass 1 只计算 nPointsBytes + 存储 delta,延迟编码到 Pass 2,消除临时 buffer 分配和 memcpy */ const nPointsDeltas = new Int16Array(numberOfContours); let nPointsBytes = 0; let prevEnd = -1; @@ -381,47 +433,74 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { const instructionLength = readU16(glyfData, dataOff); dataOff += 2; - const instructions = instructionLength > 0 ? { offset: dataOff, length: instructionLength } : null; + const instrOffset = dataOff; dataOff += instructionLength; - /* ★ 合并:instructionStreamSize 累加 */ - instructionStreamSize += instructionLength; + /* instructionAccum 追加(简单 glyph 的指令) */ + if (instructionLength > 0) { + if (instrAccumLen + instructionLength > instrAccumCap) { + while (instrAccumLen + instructionLength > instrAccumCap) instrAccumCap *= 2; + const ib = new Uint8Array(instrAccumCap); + ib.set(instrAccum.subarray(0, instrAccumLen)); + instrAccum = ib; + } + instrAccum.set(glyfData.subarray(instrOffset, instrOffset + instructionLength), instrAccumLen); + instrAccumLen += instructionLength; + } const numPoints = numberOfContours > 0 ? lastEndPt + 1 : 0; - /* 优化183: flagsArr 复用为 cachedFlags,消除每个 glyph 一次 Uint8Array 分配 */ - const flagsArr = new Uint8Array(numPoints); + /** + * 优化294: flagsArr 直接写入 flagAccum(连续累积),不再分配独立 Uint8Array + * 解码 flags 的同时检查 overlap 并写入 flagAccum + */ + if (numberOfContours > 0) { + /* 确保 flagAccum 容量 >= flagAccumLen + numPoints */ + if (flagAccumLen + numPoints > flagAccumCap) { + while (flagAccumLen + numPoints > flagAccumCap) flagAccumCap *= 2; + const fb = new Uint8Array(flagAccumCap); + fb.set(flagAccum.subarray(0, flagAccumLen)); + flagAccum = fb; + } + } let hasOverlap = false; let fi = 0; + /** flagWriteBase: 当前 glyph 的 flag 在 flagAccum 的起始位置(供 triplet 循环回写 triplet flag) */ + let flagWriteBase = flagAccumLen; while (fi < numPoints) { const flag = glyfData[dataOff++]; if (flag & OVERLAP_FLAG) hasOverlap = true; - flagsArr[fi++] = flag; + flagAccum[flagAccumLen++] = flag; + fi++; if (flag & REPEAT_FLAG && fi < numPoints) { const repeat = glyfData[dataOff++]; const count = repeat < numPoints - fi ? repeat : numPoints - fi; - flagsArr.fill(flag, fi, fi + count); + flagAccum.fill(flag, flagAccumLen, flagAccumLen + count); + flagAccumLen += count; fi += count; } } - /* ★ 合并:overlapBitmap + flagStreamSize + totalPoints */ if (numberOfContours > 0) { if (hasOverlap) { hasOverlapBitmap = true; overlapBitmap[gi >> 3] |= (0x80 >> (gi & 7)); } - flagStreamSize += numPoints; - totalPoints += numPoints; } - /** 优化293: coords 拆分为独立 xCoords/yCoords,顺序内存访问更利于 CPU 缓存预取 */ - const xCoords = new Int32Array(numPoints); - const yCoords = new Int32Array(numPoints); + /** 优化301: 复用模块级坐标缓冲区,避免每字形分配 Int32Array */ + if (numPoints > _reuseXCoords.length) { + const cap = _reuseXCoords.length; + const newCap = cap * 2 > numPoints ? cap * 2 : numPoints; + _reuseXCoords = new Int32Array(newCap); + _reuseYCoords = new Int32Array(newCap); + } + const xCoords = _reuseXCoords; + const yCoords = _reuseYCoords; let px = 0; let calcXMin, calcXMax; for (let xi = 0; xi < numPoints; xi++) { - const f = flagsArr[xi]; + const f = flagAccum[flagWriteBase + xi]; if (f & XSHORT_FLAG) { const b = glyfData[dataOff++]; px += (f & XSAME_FLAG) ? b : -b; @@ -440,7 +519,7 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { let py = 0; let calcYMin, calcYMax; for (let yi = 0; yi < numPoints; yi++) { - const f = flagsArr[yi]; + const f = flagAccum[flagWriteBase + yi]; if (f & YSHORT_FLAG) { const b = glyfData[dataOff++]; py += (f & YSAME_FLAG) ? b : -b; @@ -456,9 +535,6 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { else if (py > calcYMax) calcYMax = py; } - /* 优化282: bbox 检查 + triplet 计算 + glyphStreamSize 累加 */ - let glyphStreamBuf = null; - let gsbi = 0; if (numberOfContours > 0) { const bboxMatches = calcXMin === xMin && calcYMin === yMin && calcXMax === xMax && calcYMax === yMax; if (!bboxMatches) { @@ -466,38 +542,54 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { bboxStreamSize += 8; } + /** + * 优化294: triplet 数据直接追加写入 glyphAccum(连续累积),不再分配 per-glyph glyphStreamBuf + * 每个 point 最多 4 数据字节,先确保容量再写入 + */ + const maxAdd = numPoints * 4; + if (glyphAccumLen + maxAdd > glyphAccumCap) { + while (glyphAccumLen + maxAdd > glyphAccumCap) glyphAccumCap *= 2; + const nb = new Uint8Array(glyphAccumCap); + nb.set(glyphAccum.subarray(0, glyphAccumLen)); + glyphAccum = nb; + } + const gsBase = glyphAccumLen; + let gsbi = 0; let prevX = 0, prevY = 0; - const maxGlyphStreamBytes = numPoints * 4 + 3; - glyphStreamBuf = new Uint8Array(maxGlyphStreamBytes); - /** 优化291: 使用合并函数,消除重复 absDx/absDy + 二次分支 + 重复坐标读取 */ for (let pi = 0; pi < numPoints; pi++) { const cx = xCoords[pi]; const cy = yCoords[pi]; - /** 优化293: 内联 curveBit 计算,消除 !! onCurve + 函数内三元运算 */ - const curveBit = (flagsArr[pi] & 1) ? 0 : 128; + const curveBit = (flagAccum[flagWriteBase + pi] & 1) ? 0 : 128; const dx = cx - prevX; const dy = cy - prevY; - const flag = calcTripletAndWrite(curveBit, dx, dy, glyphStreamBuf, gsbi); - flagsArr[pi] = flag; + const flag = calcTripletAndWrite(curveBit, dx, dy, glyphAccum, gsBase + gsbi); + /** triplet flag 回写到 flagAccum(flagStream 存 triplet flag 而非原始 flag) */ + flagAccum[flagWriteBase + pi] = flag; gsbi += TRIPLET_DATA_SIZES[flag & 0x7F]; prevX = cx; prevY = cy; } - glyphStreamSize += gsbi + size255UInt16(instructionLength); + glyphAccumLen += gsbi; + glyphStreamSize += gsbi; + + /* glyphAccum 追加 encode255UInt16(instructionLength) */ + const n = encode255UInt16(instructionLength, _reuseEnc255, 0); + if (glyphAccumLen + n > glyphAccumCap) { + while (glyphAccumLen + n > glyphAccumCap) glyphAccumCap *= 2; + const nb2 = new Uint8Array(glyphAccumCap); + nb2.set(glyphAccum.subarray(0, glyphAccumLen)); + glyphAccum = nb2; + } + for (let e = 0; e < n; e++) glyphAccum[glyphAccumLen++] = _reuseEnc255[e]; + glyphStreamSize += n; } - /* 优化277: 存储 glyphStreamBuf,Pass 2 直接 set 拷贝,不再需要 writePointDataByFlag */ glyphInfos[gi] = numberOfContours > 0 ? { composite: false, numberOfContours, - /** 优化291: 存储 nPointsDeltas 替代 nPointsEncoded,延迟编码到 Pass 2 */ nPointsDeltas, - nPointsBytes, - instructions, - flags: flagsArr, calcXMin, calcYMin, calcXMax, calcYMax, - glyphStreamBuf, glyphStreamBytes: gsbi, } : { composite: false, @@ -507,6 +599,8 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { const nContourStreamSize = numGlyphs * 2; const headerSize = 36; + const flagStreamSize = flagAccumLen; + const instructionStreamSize = instrAccumLen; const overlapBitmapSize = hasOverlapBitmap ? bboxBitmapSize : 0; const totalSize = headerSize + nContourStreamSize @@ -519,7 +613,6 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { + overlapBitmapSize; const result = new Uint8Array(totalSize); - /** 优化222: 使用模块级 writeU16/writeI16/writeU32,消除闭包分配 */ let pos = 0; /* Header */ @@ -535,10 +628,6 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { writeU32(result, bboxBitmapSize + bboxStreamSize, pos); pos += 4; writeU32(result, instructionStreamSize, pos); pos += 4; - /** - * 优化:合并原第 3/4/5 次遍历为 1 次 - * nContourStream + nPointsStream + 所有子流写入合并在单次 glyph 遍历中 - */ const nContourEnd = pos + nContourStreamSize; let nContourPos = pos; let nPointsPos = nContourEnd; @@ -556,16 +645,12 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { pos += instructionStreamSize; const overlapBitmapStart = pos; - let flagPos = flagStreamStart; - let glyphPos = glyphStreamStart; + /** 优化294: Pass 2 仅写 nContourStream/nPointsStream/bboxStream,flag/glyph/instruction 整体 set */ let bboxPos = bboxStreamStart; - let instrPos = instructionStreamStart; for (let gi = 0; gi < numGlyphs; gi++) { const g = glyphInfos[gi]; - /* nContourStream: 每个 glyph 写入 numberOfContours */ - /** 优化291: writeI16 内联为直接数组写入 */ if (!g) { nContourPos += 2; continue; @@ -579,53 +664,23 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { nContourPos += 2; if (g.composite) { - result.set(glyfData.subarray(g.rawOffset, g.rawOffset + g.rawLength), glyphPos); - glyphPos += g.rawLength; - /** 优化291: bbox 四次 writeI16 内联为直接 view 写入 */ result[bboxPos] = g.xMin >> 8; result[bboxPos + 1] = g.xMin & 0xFF; result[bboxPos + 2] = g.yMin >> 8; result[bboxPos + 3] = g.yMin & 0xFF; result[bboxPos + 4] = g.xMax >> 8; result[bboxPos + 5] = g.xMax & 0xFF; result[bboxPos + 6] = g.yMax >> 8; result[bboxPos + 7] = g.yMax & 0xFF; bboxPos += 8; - if (g.haveInstructions) { - const instrLen = g.instructions ? g.instructions.length : 0; - if (instrLen > 0) { - result.set(glyfData.subarray(g.instructions.offset, g.instructions.offset + instrLen), instrPos); - instrPos += instrLen; - } - glyphPos += encode255UInt16(instrLen, result, glyphPos); - } continue; } if (g.numberOfContours === 0) continue; - /* 优化291: nPointsStream 直接编码到 result,消除临时 buffer 和 memcpy */ const deltas = g.nPointsDeltas; const nc = g.numberOfContours; for (let c = 0; c < nc; c++) { nPointsPos += encode255UInt16(deltas[c], result, nPointsPos); } - const instrLen = g.instructions ? g.instructions.length : 0; - if (instrLen > 0) { - result.set(glyfData.subarray(g.instructions.offset, g.instructions.offset + instrLen), instrPos); - instrPos += instrLen; - } - - /** - * 优化291: flag + glyph 子流 — Pass 1 已预写入 glyphStreamBuf,直接 set 拷贝 - * flag stream 使用 TypedArray.set 替代逐字节循环 - */ - result.set(g.flags, flagPos); - flagPos += g.flags.length; - result.set(g.glyphStreamBuf.subarray(0, g.glyphStreamBytes), glyphPos); - glyphPos += g.glyphStreamBytes; - - glyphPos += encode255UInt16(instrLen, result, glyphPos); - if (bboxBitmap[gi >> 3] & (0x80 >> (gi & 7))) { - /** 优化291: bbox 四次 writeI16 内联 */ result[bboxPos] = g.calcXMin >> 8; result[bboxPos + 1] = g.calcXMin & 0xFF; result[bboxPos + 2] = g.calcYMin >> 8; result[bboxPos + 3] = g.calcYMin & 0xFF; result[bboxPos + 4] = g.calcXMax >> 8; result[bboxPos + 5] = g.calcXMax & 0xFF; @@ -634,6 +689,13 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { } } + /** 优化294: 三个累积缓冲区整体拷贝到 result 对应区域(单次 set 替代 per-glyph set) */ + result.set(flagAccum.subarray(0, flagStreamSize), flagStreamStart); + result.set(glyphAccum.subarray(0, glyphStreamSize), glyphStreamStart); + if (instructionStreamSize > 0) { + result.set(instrAccum.subarray(0, instructionStreamSize), instructionStreamStart); + } + result.set(bboxBitmap, bboxBitmapStart); if (hasOverlapBitmap) {