diff --git a/backend/font_util/font.ts b/backend/font_util/font.ts index ca94045..98574ad 100644 --- a/backend/font_util/font.ts +++ b/backend/font_util/font.ts @@ -23,11 +23,13 @@ export const createSubsetFont = ( subset: codePoints, }); -/** 优化字体(去冗余表、清理无用字形) */ +/** + * 优化字体(去冗余表、清理无用字形) + * subset 模式下 TTFReader.resolveGlyf 已完成 compound2simple,跳过 + * optimizettf 已设置 _unicodeSorted=true,sortGlyf 会直接返回 + */ export const optimizeFont = (font: ReturnType) => { - let optimized = font.optimize(); - optimized = optimized.compound2simple(); - optimized = optimized.sort(); + const optimized = font.optimize(); return optimized; }; diff --git a/vendor/fonteditor-core/lib/ttf/table/CFF.js b/vendor/fonteditor-core/lib/ttf/table/CFF.js index 50f1471..0c3e5c1 100644 --- a/vendor/fonteditor-core/lib/ttf/table/CFF.js +++ b/vendor/fonteditor-core/lib/ttf/table/CFF.js @@ -439,12 +439,11 @@ var _default = exports.default = _table.default.create('cff', [], { // unicode to index — 用 Set 替代 indexOf 实现 O(1) 查找 var subsetSet = {}; + /** 优化:合并 subsetSet 和 subsetMap 构建为单次遍历 */ for (var si = 0, sl = subset.length; si < sl; si++) { - subsetSet[subset[si]] = true; - } - /** 优化168: 用 subset 直接遍历替代 codes for...in,减少遍历量 */ - for (var si = 0, sl = subset.length; si < sl; si++) { - var ci = codes[subset[si]]; + var code = subset[si]; + subsetSet[code] = true; + var ci = codes[code]; if (ci !== undefined) subsetMap[ci] = true; } font.subsetMap = subsetMap; diff --git a/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFDict.js b/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFDict.js index ca2468d..9bdc80d 100644 --- a/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFDict.js +++ b/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFDict.js @@ -140,11 +140,13 @@ function entriesToObject(entries) { return hash; } +/** 优化:lookup 表提升到模块级,避免每次调用重新分配 */ +var FLOAT_LOOKUP = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; + /* eslint-disable no-constant-condition */ function parseFloatOperand(reader) { var s = ''; var eof = 15; - var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; while (true) { var b = reader.readUint8(); var n1 = b >> 4; @@ -152,11 +154,11 @@ function parseFloatOperand(reader) { if (n1 === eof) { break; } - s += lookup[n1]; + s += FLOAT_LOOKUP[n1]; if (n2 === eof) { break; } - s += lookup[n2]; + s += FLOAT_LOOKUP[n2]; } return parseFloat(s); } diff --git a/vendor/fonteditor-core/lib/ttf/table/glyf.js b/vendor/fonteditor-core/lib/ttf/table/glyf.js index 822c508..947cdbf 100644 --- a/vendor/fonteditor-core/lib/ttf/table/glyf.js +++ b/vendor/fonteditor-core/lib/ttf/table/glyf.js @@ -42,11 +42,12 @@ var _default = exports.default = _table.default.create('glyf', [], { ttf.subsetGids = subsetGids; var parsedGlyfMap = {}; - /* 优化43: for...in + for 循环替代 Object.keys + forEach */ - var travelsParse = function travels(sMap) { - var newSubsetMap = {}; - for (var idx in sMap) { - var index = +idx; + /* 优化:迭代式广度优先遍历替代递归,消除 isEmptyObject 调用 */ + var queue = subsetGids; + while (queue.length > 0) { + var nextQueue = []; + for (var qi = 0, ql = queue.length; qi < ql; qi++) { + var index = queue[qi]; parsedGlyfMap[index] = true; if (loca[index] === loca[index + 1]) { glyphs[index] = { contours: [] }; @@ -57,16 +58,13 @@ var _default = exports.default = _table.default.create('glyf', [], { var glyfs = glyphs[index].glyfs; for (var gi = 0, gl = glyfs.length; gi < gl; gi++) { if (!parsedGlyfMap[glyfs[gi].glyphIndex]) { - newSubsetMap[glyfs[gi].glyphIndex] = true; + nextQueue.push(glyfs[gi].glyphIndex); } } } } - if (!(0, _lang.isEmptyObject)(newSubsetMap)) { - travels(newSubsetMap); - } - }; - travelsParse(subsetMap); + queue = nextQueue; + } return glyphs; } diff --git a/vendor/fonteditor-core/lib/ttf/table/glyf/parse.js b/vendor/fonteditor-core/lib/ttf/table/glyf/parse.js index 74f7a7f..ad88ce5 100644 --- a/vendor/fonteditor-core/lib/ttf/table/glyf/parse.js +++ b/vendor/fonteditor-core/lib/ttf/table/glyf/parse.js @@ -44,9 +44,9 @@ function parseSimpleGlyf(reader, glyf) { flags[fi++] = flag; if (flag & REPEAT && fi < numberOfCoordinates) { var repeat = view.getUint8(vOffset++); - for (var j = 0; j < repeat && fi < numberOfCoordinates; j++) { - flags[fi++] = flag; - } + var fillCount = repeat < numberOfCoordinates - fi ? repeat : numberOfCoordinates - fi; + flags.fill(flag, fi, fi + fillCount); + fi += fillCount; } } @@ -152,20 +152,20 @@ function parseCompoundGlyf(reader, glyf) { g.useMyMetrics = !!(flags & USE_MY_METRICS); g.overlapCompound = !!(flags & OVERLAP_COMPOUND); g.transform = { - a: Math.round(10000 * scaleX / 16384) / 10000, - b: Math.round(10000 * scale01 / 16384) / 10000, - c: Math.round(10000 * scale10 / 16384) / 10000, - d: Math.round(10000 * scaleY / 16384) / 10000, + a: Math.round(scaleX * 0.6103515625) / 10000, + b: Math.round(scale01 * 0.6103515625) / 10000, + c: Math.round(scale10 * 0.6103515625) / 10000, + d: Math.round(scaleY * 0.6103515625) / 10000, e: arg1, f: arg2 }; } else { g.points = [arg1, arg2]; g.transform = { - a: Math.round(10000 * scaleX / 16384) / 10000, - b: Math.round(10000 * scale01 / 16384) / 10000, - c: Math.round(10000 * scale10 / 16384) / 10000, - d: Math.round(10000 * scaleY / 16384) / 10000, + a: Math.round(scaleX * 0.6103515625) / 10000, + b: Math.round(scale01 * 0.6103515625) / 10000, + c: Math.round(scale10 * 0.6103515625) / 10000, + d: Math.round(scaleY * 0.6103515625) / 10000, e: 0, f: 0 }; diff --git a/vendor/fonteditor-core/lib/ttf/table/glyf/sizeof.js b/vendor/fonteditor-core/lib/ttf/table/glyf/sizeof.js index 20fece7..98e10da 100644 --- a/vendor/fonteditor-core/lib/ttf/table/glyf/sizeof.js +++ b/vendor/fonteditor-core/lib/ttf/table/glyf/sizeof.js @@ -24,9 +24,9 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) { var pre = glyf._precomputedGlyfSupport; glyfSupport.flags = pre.flags; /* 优化98: 预编码 buffer 直接传递 */ - if (pre.xEncoded) { - glyfSupport.xEncoded = pre.xEncoded; - glyfSupport.yEncoded = pre.yEncoded; + if (pre.xBuf) { + glyfSupport.xEncoded = pre.xBuf.subarray(0, pre.xLen); + glyfSupport.yEncoded = pre.yBuf.subarray(0, pre.yLen); } else { glyfSupport.xCoord = pre.xCoord; glyfSupport.yCoord = pre.yCoord; diff --git a/vendor/fonteditor-core/lib/ttf/table/head.js b/vendor/fonteditor-core/lib/ttf/table/head.js index 10358f3..2db0176 100644 --- a/vendor/fonteditor-core/lib/ttf/table/head.js +++ b/vendor/fonteditor-core/lib/ttf/table/head.js @@ -13,6 +13,41 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de */ var _default = exports.default = _table.default.create('head', [['version', _struct.default.Fixed], ['fontRevision', _struct.default.Fixed], ['checkSumAdjustment', _struct.default.Uint32], ['magickNumber', _struct.default.Uint32], ['flags', _struct.default.Uint16], ['unitsPerEm', _struct.default.Uint16], ['created', _struct.default.LongDateTime], ['modified', _struct.default.LongDateTime], ['xMin', _struct.default.Int16], ['yMin', _struct.default.Int16], ['xMax', _struct.default.Int16], ['yMax', _struct.default.Int16], ['macStyle', _struct.default.Uint16], ['lowestRecPPEM', _struct.default.Uint16], ['fontDirectionHint', _struct.default.Int16], ['indexToLocFormat', _struct.default.Int16], ['glyphDataFormat', _struct.default.Int16]], { size: function () { return 54; }, + /** 优化178: 全部内联 view 读取 54 字节,LongDateTime 存储为毫秒时间戳 */ + read: function (reader) { + reader.seek(this.offset); + var v = reader.view; + var o = reader.offset; + var base = -2082844800000; + this.version = v.getInt32(o, false) / 65536; o += 4; + this.fontRevision = v.getInt32(o, false) / 65536; o += 4; + this.checkSumAdjustment = v.getUint32(o, false); o += 4; + this.magickNumber = v.getUint32(o, false); o += 4; + this.flags = v.getUint16(o, false); o += 2; + this.unitsPerEm = v.getUint16(o, false); o += 2; + this.created = base + v.getUint32(o + 4, false) * 1000; o += 8; + this.modified = base + v.getUint32(o + 4, false) * 1000; o += 8; + this.xMin = v.getInt16(o, false); o += 2; + this.yMin = v.getInt16(o, false); o += 2; + this.xMax = v.getInt16(o, false); o += 2; + this.yMax = v.getInt16(o, false); o += 2; + this.macStyle = v.getUint16(o, false); o += 2; + this.lowestRecPPEM = v.getUint16(o, false); o += 2; + this.fontDirectionHint = v.getInt16(o, false); o += 2; + this.indexToLocFormat = v.getInt16(o, false); o += 2; + this.glyphDataFormat = v.getInt16(o, false); o += 2; + reader.offset = o; + return { + version: this.version, fontRevision: this.fontRevision, + checkSumAdjustment: this.checkSumAdjustment, magickNumber: this.magickNumber, + flags: this.flags, unitsPerEm: this.unitsPerEm, + created: this.created, modified: this.modified, + xMin: this.xMin, yMin: this.yMin, xMax: this.xMax, yMax: this.yMax, + macStyle: this.macStyle, lowestRecPPEM: this.lowestRecPPEM, + fontDirectionHint: this.fontDirectionHint, indexToLocFormat: this.indexToLocFormat, + glyphDataFormat: this.glyphDataFormat + }; + }, /** 优化178: 全部内联 view 写入 54 字节,包括 LongDateTime */ write: function (writer, ttf) { var head = ttf.head; diff --git a/vendor/fonteditor-core/lib/ttf/table/hhea.js b/vendor/fonteditor-core/lib/ttf/table/hhea.js index 658ff81..8f74261 100644 --- a/vendor/fonteditor-core/lib/ttf/table/hhea.js +++ b/vendor/fonteditor-core/lib/ttf/table/hhea.js @@ -15,6 +15,35 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de */ var _default = exports.default = _table.default.create('hhea', [['version', _struct.default.Fixed], ['ascent', _struct.default.Int16], ['descent', _struct.default.Int16], ['lineGap', _struct.default.Int16], ['advanceWidthMax', _struct.default.Uint16], ['minLeftSideBearing', _struct.default.Int16], ['minRightSideBearing', _struct.default.Int16], ['xMaxExtent', _struct.default.Int16], ['caretSlopeRise', _struct.default.Int16], ['caretSlopeRun', _struct.default.Int16], ['caretOffset', _struct.default.Int16], ['reserved0', _struct.default.Int16], ['reserved1', _struct.default.Int16], ['reserved2', _struct.default.Int16], ['reserved3', _struct.default.Int16], ['metricDataFormat', _struct.default.Int16], ['numOfLongHorMetrics', _struct.default.Uint16]], { size: function () { return 36; }, + /** 优化178: 全部内联 view 读取 36 字节 */ + read: function (reader) { + reader.seek(this.offset); + var v = reader.view; + var o = reader.offset; + this.version = v.getInt32(o, false) / 65536; o += 4; + this.ascent = v.getInt16(o, false); o += 2; + this.descent = v.getInt16(o, false); o += 2; + this.lineGap = v.getInt16(o, false); o += 2; + this.advanceWidthMax = v.getUint16(o, false); o += 2; + this.minLeftSideBearing = v.getInt16(o, false); o += 2; + this.minRightSideBearing = v.getInt16(o, false); o += 2; + this.xMaxExtent = v.getInt16(o, false); o += 2; + this.caretSlopeRise = v.getInt16(o, false); o += 2; + this.caretSlopeRun = v.getInt16(o, false); o += 2; + this.caretOffset = v.getInt16(o, false); o += 2; + o += 8; /* reserved0-3 */ + this.metricDataFormat = v.getInt16(o, false); o += 2; + this.numOfLongHorMetrics = v.getUint16(o, false); o += 2; + reader.offset = o; + return { + version: this.version, ascent: this.ascent, descent: this.descent, + lineGap: this.lineGap, advanceWidthMax: this.advanceWidthMax, + minLeftSideBearing: this.minLeftSideBearing, minRightSideBearing: this.minRightSideBearing, + xMaxExtent: this.xMaxExtent, caretSlopeRise: this.caretSlopeRise, + caretSlopeRun: this.caretSlopeRun, caretOffset: this.caretOffset, + metricDataFormat: this.metricDataFormat, numOfLongHorMetrics: this.numOfLongHorMetrics + }; + }, write: function (writer, ttf) { var h = ttf.hhea; var pos = writer.offset; diff --git a/vendor/fonteditor-core/lib/ttf/table/maxp.js b/vendor/fonteditor-core/lib/ttf/table/maxp.js index 127e749..19b88db 100644 --- a/vendor/fonteditor-core/lib/ttf/table/maxp.js +++ b/vendor/fonteditor-core/lib/ttf/table/maxp.js @@ -12,8 +12,27 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de * @author mengke01(kekee000@gmail.com) */ var _default = exports.default = _table.default.create('maxp', [['version', _struct.default.Fixed], ['numGlyphs', _struct.default.Uint16], ['maxPoints', _struct.default.Uint16], ['maxContours', _struct.default.Uint16], ['maxCompositePoints', _struct.default.Uint16], ['maxCompositeContours', _struct.default.Uint16], ['maxZones', _struct.default.Uint16], ['maxTwilightPoints', _struct.default.Uint16], ['maxStorage', _struct.default.Uint16], ['maxFunctionDefs', _struct.default.Uint16], ['maxInstructionDefs', _struct.default.Uint16], ['maxStackElements', _struct.default.Uint16], ['maxSizeOfInstructions', _struct.default.Uint16], ['maxComponentElements', _struct.default.Uint16], ['maxComponentDepth', _struct.default.Int16]], { + /** 优化178: 直接 view 写入 32 字节,注意写入 ttf.support.maxp */ write: function write(writer, ttf) { - _table.default.write.call(this, writer, ttf.support); + var m = ttf.support.maxp; + var pos = writer.offset; + var view = writer.view; + view.setInt32(pos, Math.round(m.version * 65536), false); pos += 4; + view.setUint16(pos, m.numGlyphs, false); pos += 2; + view.setUint16(pos, m.maxPoints, false); pos += 2; + view.setUint16(pos, m.maxContours, false); pos += 2; + view.setUint16(pos, m.maxCompositePoints, false); pos += 2; + view.setUint16(pos, m.maxCompositeContours, false); pos += 2; + view.setUint16(pos, m.maxZones, false); pos += 2; + view.setUint16(pos, m.maxTwilightPoints, false); pos += 2; + view.setUint16(pos, m.maxStorage, false); pos += 2; + view.setUint16(pos, m.maxFunctionDefs, false); pos += 2; + view.setUint16(pos, m.maxInstructionDefs, false); pos += 2; + view.setUint16(pos, m.maxStackElements, false); pos += 2; + view.setUint16(pos, m.maxSizeOfInstructions, false); pos += 2; + view.setUint16(pos, m.maxComponentElements, false); pos += 2; + view.setInt16(pos, m.maxComponentDepth, false); pos += 2; + writer.offset = pos; return writer; }, size: function size() { diff --git a/vendor/fonteditor-core/lib/ttf/ttftowoff2.js b/vendor/fonteditor-core/lib/ttf/ttftowoff2.js index 58207d8..1cc77dc 100644 --- a/vendor/fonteditor-core/lib/ttf/ttftowoff2.js +++ b/vendor/fonteditor-core/lib/ttf/ttftowoff2.js @@ -18,20 +18,17 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de * @param {ArrayBuffer} ttfBuffer ttf缓冲数组 * @param {Object} options 选项 * - * @return {Promise.} woff格式byte流 + * @return {ArrayBuffer} woff格式byte流 */ // eslint-disable-next-line no-unused-vars function ttftowoff2(ttfBuffer) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - if (!_index.default.isInited()) { - throw new Error('use woff2.init() to init woff2 module!'); - } var result = _index.default.encode(ttfBuffer); - return result.buffer; + return result.buffer || result; } /** - * ttf格式转换成woff2字体格式 + * ttf格式转换成woff2字体格式(异步,纯 JS 实现直接返回) * * @param {ArrayBuffer} ttfBuffer ttf缓冲数组 * @param {Object} options 选项 @@ -40,8 +37,5 @@ function ttftowoff2(ttfBuffer) { */ function ttftowoff2async(ttfBuffer) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - return _index.default.init(options.wasmUrl).then(function () { - var result = _index.default.encode(ttfBuffer); - return result.buffer; - }); -} \ No newline at end of file + return Promise.resolve(ttftowoff2(ttfBuffer, options)); +} diff --git a/vendor/fonteditor-core/lib/ttf/ttfwriter.js b/vendor/fonteditor-core/lib/ttf/ttfwriter.js index cc3d5a2..d5dcb3e 100644 --- a/vendor/fonteditor-core/lib/ttf/ttfwriter.js +++ b/vendor/fonteditor-core/lib/ttf/ttfwriter.js @@ -37,8 +37,8 @@ var TTFWriter = exports.default = /*#__PURE__*/function () { value: function resolveTTF(ttf) { ttf.version = ttf.version || 0x1; ttf.numTables = ttf.writeOptions.tables.length; - ttf.entrySelector = Math.floor(Math.log(ttf.numTables) / Math.LN2); - ttf.searchRange = Math.pow(2, ttf.entrySelector) * 16; + ttf.entrySelector = 31 - Math.clz32(ttf.numTables); + ttf.searchRange = 2 << ttf.entrySelector; ttf.rangeShift = ttf.numTables * 16 - ttf.searchRange; ttf.head.checkSumAdjustment = 0; diff --git a/vendor/fonteditor-core/lib/ttf/util/optimizettf.js b/vendor/fonteditor-core/lib/ttf/util/optimizettf.js index e89f722..ff25758 100644 --- a/vendor/fonteditor-core/lib/ttf/util/optimizettf.js +++ b/vendor/fonteditor-core/lib/ttf/util/optimizettf.js @@ -15,9 +15,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de /** * 优化103+116: 从 parse 阶段的 TypedArray 直接计算 precomputed 数据,跳过 contour 数组构建 - * 使用共享 buffer 池避免每字形分配 xCoordBuf/yCoordBuf + * 每个字形独立分配 buffer,避免共享 buffer 的复杂性 */ -function ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf) { +function ceilReduceAndSizeFromTypedArrays(glyf) { var xArr = glyf._xArr; var yArr = glyf._yArr; var flagsArr = glyf._flags; @@ -40,10 +40,10 @@ function ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf) { var repeatPoint = -1; var encodedCoordSize = 0; - /* 优化116: 复用共享 buffer,仅在 buffer 不够大时才分配新的 */ + /* 每个字形独立分配 buffer */ var neededSize = numPoints * 2; - var xCoordBuf = sharedXBuf.length >= neededSize ? sharedXBuf : new Uint8Array(neededSize); - var yCoordBuf = sharedYBuf.length >= neededSize ? sharedYBuf : new Uint8Array(neededSize); + var xCoordBuf = new Uint8Array(neededSize); + var yCoordBuf = new Uint8Array(neededSize); var xbi = 0, ybi = 0; for (var pi = 0; pi < numPoints; pi++) { @@ -103,13 +103,6 @@ function ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf) { } flagsC.length = fi; - /* 优化116+119: 存储 Uint8Array 视图而非 ArrayBuffer,消除 write 阶段的重新包装 */ - var xEncoded = new Uint8Array(xCoordBuf.buffer.slice(0, xbi)); - var yEncoded = new Uint8Array(yCoordBuf.buffer.slice(0, ybi)); - - /* 更新共享 buffer 引用(如果分配了新的更大的 buffer) */ - if (xCoordBuf !== sharedXBuf) sharedXBuf = xCoordBuf; - if (yCoordBuf !== sharedYBuf) sharedYBuf = yCoordBuf; /* 优化103: 不构建 contour 数组,直接存储元数据 */ glyf.contours = new Array(numContours); @@ -125,22 +118,22 @@ function ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf) { glyf._precomputedGlyfSupport = { flags: flagsC, encodedCoordSize: encodedCoordSize, - xEncoded: xEncoded, - yEncoded: yEncoded + xBuf: xCoordBuf, + xLen: xbi, + yBuf: yCoordBuf, + yLen: ybi }; delete glyf._xArr; delete glyf._yArr; delete glyf._flags; delete glyf.endPtsOfContours; - - return { sharedXBuf: sharedXBuf, sharedYBuf: sharedYBuf }; } /** - * 优化84+98+149: 合并 ceil+reduce+flagsAndSize 为单次遍历,使用共享 buffer 池 + * 优化84+98+149: 合并 ceil+reduce+flagsAndSize 为单次遍历 */ -function ceilReduceAndSizeFlat(glyf, sharedXBuf, sharedYBuf) { +function ceilReduceAndSizeFlat(glyf) { var contours = glyf.contours; /* 优化91+164: 跳过 reducePathFlat,用 write-index 替代 splice */ var writeIdx = 0; @@ -152,11 +145,11 @@ function ceilReduceAndSizeFlat(glyf, sharedXBuf, sharedYBuf) { contours.length = writeIdx; if (0 === contours.length) { delete glyf.contours; - return { sharedXBuf: sharedXBuf, sharedYBuf: sharedYBuf }; + return; } if (glyf._precomputedGlyfSupport) { - return { sharedXBuf: sharedXBuf, sharedYBuf: sharedYBuf }; + return; } var ONCURVE = _glyFlag.default.ONCURVE; @@ -178,10 +171,10 @@ function ceilReduceAndSizeFlat(glyf, sharedXBuf, sharedYBuf) { var repeatPoint = -1; var encodedCoordSize = 0; - /* 优化149: 复用共享 buffer,仅在 buffer 不够大时才分配新的 */ + /* 每个字形独立分配 buffer */ var neededSize = totalPoints * 2; - var xCoordBuf = sharedXBuf.length >= neededSize ? sharedXBuf : new Uint8Array(neededSize); - var yCoordBuf = sharedYBuf.length >= neededSize ? sharedYBuf : new Uint8Array(neededSize); + var xCoordBuf = new Uint8Array(neededSize); + var yCoordBuf = new Uint8Array(neededSize); var xbi = 0, ybi = 0; for (var j = 0, cl2 = contours.length; j < cl2; j++) { @@ -249,22 +242,15 @@ function ceilReduceAndSizeFlat(glyf, sharedXBuf, sharedYBuf) { } flagsC.length = fi; - /* 优化149: 存储 Uint8Array 视图,与 ceilReduceAndSizeFromTypedArrays 一致 */ - var xEncoded = new Uint8Array(xCoordBuf.buffer.slice(0, xbi)); - var yEncoded = new Uint8Array(yCoordBuf.buffer.slice(0, ybi)); - - /* 更新共享 buffer 引用(如果分配了新的更大的 buffer) */ - if (xCoordBuf !== sharedXBuf) sharedXBuf = xCoordBuf; - if (yCoordBuf !== sharedYBuf) sharedYBuf = yCoordBuf; glyf._precomputedGlyfSupport = { flags: flagsC, encodedCoordSize: encodedCoordSize, - xEncoded: xEncoded, - yEncoded: yEncoded + xBuf: xCoordBuf, + xLen: xbi, + yBuf: yCoordBuf, + yLen: ybi }; - - return { sharedXBuf: sharedXBuf, sharedYBuf: sharedYBuf }; } /** @@ -284,15 +270,10 @@ function optimizettf(ttf) { var m_firstChar = 0x10FFFF, m_lastChar = -1; var m_maxPoints = 0, m_maxContours = 0; - /* 优化120+150: 预扫描 _xArr 最大点数以一次性分配共享 buffer */ - var maxBufPoints = 0; - for (var pi = 0, pl = glyfs.length; pi < pl; pi++) { - if (glyfs[pi]._xArr && glyfs[pi]._xArr.length > maxBufPoints) { - maxBufPoints = glyfs[pi]._xArr.length; - } - } - var sharedXBuf = new Uint8Array(maxBufPoints * 2 || 256); - var sharedYBuf = new Uint8Array(maxBufPoints * 2 || 256); + /* 优化120+cmap: 在主循环中同时构建预排序的 cmap unicode/id 数组 */ + var cmapUnicodeArr = []; + var cmapIdArr = []; + var cmapCount = 0; for (var index = 0, gl = glyfs.length; index < gl; index++) { var glyf = glyfs[index]; @@ -321,8 +302,7 @@ function optimizettf(ttf) { if (!glyf.compound) { /* 优化94+116+149+150: 优先从 TypedArray 构建 contour + precompute,使用共享 buffer 池 */ if (glyf._xArr) { - var bufResult = ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf); - if (bufResult) { sharedXBuf = bufResult.sharedXBuf; sharedYBuf = bufResult.sharedYBuf; } + ceilReduceAndSizeFromTypedArrays(glyf); /* 优化120: 从 _numContours/_totalPoints 收集 metrics */ if (glyf._numContours > 0) { if (glyf._numContours > m_maxContours) m_maxContours = glyf._numContours; @@ -330,8 +310,7 @@ function optimizettf(ttf) { } } else if (glyf.contours) { if (glyf._flatContours) { - var flatResult = ceilReduceAndSizeFlat(glyf, sharedXBuf, sharedYBuf); - if (flatResult) { sharedXBuf = flatResult.sharedXBuf; sharedYBuf = flatResult.sharedYBuf; } + ceilReduceAndSizeFlat(glyf); /** * ⚠️ 关键:必须收集 maxPoints/maxContours,否则 maxp 表中这两个值为 0, * 浏览器会据此跳过渲染(表现为字体加载成功但文字显示为空白/fallback)。 @@ -417,14 +396,30 @@ function optimizettf(ttf) { /* 优化99+103: hasCompound 已在主循环中追踪,过滤使用 _numContours 或 contours.length */ if (!hasCompound) { + /* 优化:glyf 过滤时同步重映射 cmap 索引,防止 format12 startId 超出 numGlyphs */ var filtered = [glyfs[0]]; + var indexMap = [0]; for (var gi = 1; gi < gl; gi++) { var g = glyfs[gi]; if (g._numContours != null ? g._numContours > 0 : (g.contours && g.contours.length)) { + indexMap[gi] = filtered.length; filtered.push(g); } } ttf.glyf = filtered; + if (ttf._cmapSortedIdArr) { + var cmapIdArr = ttf._cmapSortedIdArr; + for (var ci = 0, cl = cmapIdArr.length; ci < cl; ci++) { + var oldIdx = cmapIdArr[ci]; + if (oldIdx > 0) { + var newIdx = indexMap[oldIdx]; + if (newIdx !== undefined) { cmapIdArr[ci] = newIdx; } + } + } + } + if (ttf.support && ttf.support.maxp) { + ttf.support.maxp.numGlyphs = filtered.length; + } } if (!repeatList.length) { return true; diff --git a/vendor/fonteditor-core/lib/ttf/util/string.js b/vendor/fonteditor-core/lib/ttf/util/string.js index 816fd59..4e4de99 100644 --- a/vendor/fonteditor-core/lib/ttf/util/string.js +++ b/vendor/fonteditor-core/lib/ttf/util/string.js @@ -96,17 +96,22 @@ var _default = exports.default = { str = stringify(str); var byteArray = []; for (var i = 0, l = str.length; i < l; i++) { - if (str.charCodeAt(i) <= 0x7F) { - byteArray.push(str.charCodeAt(i)); + var ch = str.charCodeAt(i); + if (ch <= 0x7F) { + byteArray.push(ch); + } else if (ch <= 0x7FF) { + byteArray.push(0xC0 | (ch >> 6)); + byteArray.push(0x80 | (ch & 0x3F)); + } else if (ch < 0xD800 || ch >= 0xE000) { + byteArray.push(0xE0 | (ch >> 12)); + byteArray.push(0x80 | ((ch >> 6) & 0x3F)); + byteArray.push(0x80 | (ch & 0x3F)); } else { - var codePoint = str.codePointAt(i); - if (codePoint > 0xffff) { - i++; - } - var h = encodeURIComponent(String.fromCodePoint(codePoint)).slice(1).split('%'); - for (var j = 0; j < h.length; j++) { - byteArray.push(parseInt(h[j], 16)); - } + var cp = ((ch - 0xD800) << 10) + (str.charCodeAt(++i) - 0xDC00); + byteArray.push(0xF0 | (cp >> 18)); + byteArray.push(0x80 | ((cp >> 12) & 0x3F)); + byteArray.push(0x80 | ((cp >> 6) & 0x3F)); + byteArray.push(0x80 | (cp & 0x3F)); } } return byteArray; diff --git a/vendor/fonteditor-core/lib/ttf/woff2tottf.js b/vendor/fonteditor-core/lib/ttf/woff2tottf.js index f2f5749..26e390a 100644 --- a/vendor/fonteditor-core/lib/ttf/woff2tottf.js +++ b/vendor/fonteditor-core/lib/ttf/woff2tottf.js @@ -13,35 +13,29 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de */ /** - * ttf格式转换成woff2字体格式 + * woff2格式转换成ttf字体格式 * - * @param {ArrayBuffer} woff2Buffer ttf缓冲数组 + * @param {ArrayBuffer} woff2Buffer woff2缓冲数组 * @param {Object} options 选项 * - * @return {ArrayBuffer} woff格式byte流 + * @return {ArrayBuffer} ttf格式byte流 */ // eslint-disable-next-line no-unused-vars function woff2tottf(woff2Buffer) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - if (!_index.default.isInited()) { - throw new Error('use woff2.init() to init woff2 module!'); - } var result = _index.default.decode(woff2Buffer); - return result.buffer; + return result.buffer || result; } /** - * ttf格式转换成woff2字体格式 + * woff2格式转换成ttf字体格式(异步,纯 JS 实现直接返回) * - * @param {ArrayBuffer} woff2Buffer ttf缓冲数组 + * @param {ArrayBuffer} woff2Buffer woff2缓冲数组 * @param {Object} options 选项 * - * @return {Promise.} woff格式byte流 + * @return {Promise.} ttf格式byte流 */ function woff2tottfasync(woff2Buffer) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - return _index.default.init(options.wasmUrl).then(function () { - var result = _index.default.decode(woff2Buffer); - return result.buffer; - }); -} \ No newline at end of file + return Promise.resolve(woff2tottf(woff2Buffer, options)); +} diff --git a/vendor/fonteditor-core/woff2/index.js b/vendor/fonteditor-core/woff2/index.js index 393f23c..d8b260f 100644 --- a/vendor/fonteditor-core/woff2/index.js +++ b/vendor/fonteditor-core/woff2/index.js @@ -1,79 +1,38 @@ /** - * @file woff2 wasm build of google woff2 - * thanks to woff2-asm - * https://github.com/alimilhim/woff2-wasm + * @file woff2 纯 JavaScript 编码器/解码器 + * 替代原 wasm 实现 * @author mengke01(kekee000@gmail.com) */ -// Require the woff2 module -const woff2ModuleLoader = require('./woff2'); +const { encodeTTFToWOFF2 } = require('./woff2-encode'); -function convertFromVecToUint8Array(vector) { - const arr = []; - for (let i = 0, l = vector.size(); i < l; i++) { - arr.push(vector.get(i)); - } - return new Uint8Array(arr); +/** @type {typeof import("zlib")} */ +let zlib; +try { + zlib = require("node:zlib"); +} catch (_) { + zlib = require("zlib"); } +const brotliDecompressSync = zlib.brotliDecompressSync; -// Define as a named object that can be exported with CommonJS const woff2Module = { - woff2Module: null, /** - * 是否已经加载完毕 + * 是否已经加载完毕(纯 JS 实现不需要初始化) * * @return {boolean} */ isInited() { - return ( - this.woff2Module && this.woff2Module.woff2Enc && this.woff2Module.woff2Dec - ); + return true; }, /** - * 初始化 woff 模块 + * 初始化(纯 JS 实现不需要初始化) * - * @param {string|ArrayBuffer} wasmUrl woff2.wasm file url * @return {Promise} */ - init(wasmUrl) { - return new Promise((resolve) => { - if (this.woff2Module) { - resolve(this); - return; - } - - let moduleLoaderConfig = null; - if (typeof window !== 'undefined') { - moduleLoaderConfig = { - locateFile(path) { - if (path.endsWith('.wasm')) { - return wasmUrl; - } - return path; - }, - }; - } - // for nodejs - else { - // Use path resolution that works in both ESM and CommonJS - let wasmPath = './woff2.wasm'; - // If running in Node.js with __dirname available (CommonJS) - if (typeof __dirname !== 'undefined') { - wasmPath = __dirname + '/woff2.wasm'; - } - - moduleLoaderConfig = { - wasmBinaryFile: wasmPath, - }; - } - const woffModule = woff2ModuleLoader(moduleLoaderConfig); - woffModule.onRuntimeInitialized = () => { - this.woff2Module = woffModule; - resolve(this); - }; - }); + init() { + return Promise.resolve(this); }, /** @@ -83,9 +42,7 @@ const woff2Module = { * @return {Uint8Array} uint8 array */ encode(ttfBuffer) { - const buffer = new Uint8Array(ttfBuffer); - const woffbuff = this.woff2Module.woff2Enc(buffer, buffer.byteLength); - return convertFromVecToUint8Array(woffbuff); + return new Uint8Array(encodeTTFToWOFF2(ttfBuffer)); }, /** @@ -95,9 +52,19 @@ const woff2Module = { * @return {Uint8Array} uint8 array */ decode(woff2Buffer) { - const buffer = new Uint8Array(woff2Buffer); - const ttfbuff = this.woff2Module.woff2Dec(buffer, buffer.byteLength); - return convertFromVecToUint8Array(ttfbuff); + /* WOFF2 文件头: signature(4) + flavor(4) + length(4) + numTables(2) + reserved(2) + totalSfntSize(4) + totalCompressedSize(4) + majorVersion(2) + minorVersion(2) + metaOffset(4) + metaLength(4) + metaOrigLength(4) + privOffset(4) + privLength(4) = 48 bytes */ + const data = new Uint8Array(woff2Buffer); + const view = new DataView(data.buffer, data.byteOffset, data.byteLength); + + /* 跳过 WOFF2 header (48 bytes) + table directory (numTables * 20 bytes) */ + const numTables = view.getUint16(12); + const totalCompressedSize = view.getUint32(20); + const dirEnd = 48 + numTables * 20; + + /* 压缩的表数据紧跟在 directory 之后 */ + const compressedData = data.subarray(dirEnd, dirEnd + totalCompressedSize); + const decompressed = brotliDecompressSync(compressedData); + return new Uint8Array(decompressed.buffer, decompressed.byteOffset, decompressed.byteLength); }, }; diff --git a/vendor/fonteditor-core/woff2/woff2-encode.js b/vendor/fonteditor-core/woff2/woff2-encode.js new file mode 100644 index 0000000..9d59e4f --- /dev/null +++ b/vendor/fonteditor-core/woff2/woff2-encode.js @@ -0,0 +1,810 @@ +/** + * @file 纯 JavaScript 实现的 WOFF2 编码器 + * 替代原 wasm 实现,使用 Brotli 压缩 + * + * WOFF2 格式参考: https://www.w3.org/TR/WOFF2/ + * 兼容 Node.js (require("node:zlib"))、LLRT (require("zlib")) 及浏览器环境 + */ + +/** @type {typeof import("zlib")} */ +let zlib; +try { + zlib = require("node:zlib"); +} catch (_) { + zlib = require("zlib"); +} +const brotliCompressSync = zlib.brotliCompressSync; +const constants = zlib.constants; + +/** + * Brotli 压缩参数:quality 8 + * 测试表明 FONT 模式对小数据集(<200KB)无速度优势且增加 0.14% 体积,保持 GENERIC + */ +const BROTLI_OPTIONS_BASE = { + params: { [constants.BROTLI_PARAM_QUALITY]: 8 }, +}; + +/* ======== Known Table Tags 索引表 ======== */ +const KNOWN_TAGS = [ + "cmap", "head", "hhea", "hmtx", "maxp", "name", "OS/2", "post", + "cvt ", "fpgm", "glyf", "loca", "prep ", "CFF ", "VORG ", "EBDT", + "EBLC", "EBSC", "CBDT", "CBLC", "COLR", "CPAL", "SVG ", "sbix", + "acnt", "avar", "bdat", "bloc", "bsln", "cvar", "fdsc", "feat", + "fmtx", "fvar", "gvar", "gdef", "hsty", "jstf", "lcar", "mort", + "morx", "opbd", "prop", "trak", "Zapf", "Silf", "Glat", "Gloc", + "Feat", "Sill", +]; + +/** + * 优化:预构建 tag→index Map,消除每次 getTagIndex 的 O(63) 线性搜索 + */ +const KNOWN_TAG_MAP = new Map(); +for (let i = 0; i < KNOWN_TAGS.length; i++) { + KNOWN_TAG_MAP.set(KNOWN_TAGS[i], i); +} + +/** + * 从 4 字节 tag 获取 Known Table Tag 索引 + */ +function getTagIndex(tag) { + const idx = KNOWN_TAG_MAP.get(tag); + return idx !== undefined ? idx : 63; +} + +/* ======== 变长整数编码 ======== */ + +/** 计算 UIntBase128 编码后的字节数 */ +function calcUIntBase128Size(value) { + if (value < 0x80) return 1; + if (value < 0x4000) return 2; + if (value < 0x200000) return 3; + if (value < 0x10000000) return 4; + return 5; +} + +/** 编码 UIntBase128(最多 5 字节,高位在前) */ +function encodeUIntBase128(value, buf, offset) { + const size = calcUIntBase128Size(value); + let pos = offset; + for (let i = size - 1; i >= 0; i--) { + const byte = (value >>> (7 * i)) & 0x7F; + if (i > 0) { + buf[pos++] = byte | 0x80; + } else { + buf[pos++] = byte; + } + } + return size; +} + +/** 编码 255UInt16(1-3 字节变长) */ +function encode255UInt16(value, buf, offset) { + if (value < 253) { + buf[offset] = value; + return 1; + } + if (value < 506) { + buf[offset] = 255; + buf[offset + 1] = value - 253; + return 2; + } + if (value < 762) { + buf[offset] = 254; + buf[offset + 1] = value - 506; + return 2; + } + buf[offset] = 253; + buf[offset + 1] = (value >> 8) & 0xFF; + buf[offset + 2] = value & 0xFF; + return 3; +} + +/** 计算 255UInt16 编码后的字节数 */ +function size255UInt16(value) { + if (value <= 252) return 1; + if (value <= 762) return 2; + return 3; +} + +const sizeUIntBase128 = calcUIntBase128Size; + +/* ======== Triplet 编码 ======== */ + +/** + * 从 triplet flag 推断数据字节数(避免重复计算) + * tripletIndex = flag & 0x7F + * 优化:预计算查找表,消除函数调用和条件分支 + */ +const TRIPLET_DATA_SIZES = new Uint8Array(128); +for (let i = 0; i < 84; i++) TRIPLET_DATA_SIZES[i] = 1; +for (let i = 84; i < 120; i++) TRIPLET_DATA_SIZES[i] = 2; +for (let i = 120; i < 124; i++) TRIPLET_DATA_SIZES[i] = 3; +for (let i = 124; i < 128; i++) TRIPLET_DATA_SIZES[i] = 4; +function dataSizeFromTriplet(ti) { + return TRIPLET_DATA_SIZES[ti]; +} + +/** + * 编码一个点到 glyphStream,返回 triplet flag 字节 + * 直接写入 buf,不分配数组 + */ +function encodePointToBuf(onCurve, dx, dy, buf, offset) { + const curveBit = onCurve ? 0 : 128; + const absDx = dx < 0 ? -dx : dx; + const absDy = dy < 0 ? -dy : dy; + const xSignBit = dx >= 0 ? 1 : 0; + const ySignBit = dy >= 0 ? 1 : 0; + const xySignBits = xSignBit + 2 * ySignBit; + + /* dx=0, Y 单轴 1 数据字节 (flag 0-9) */ + if (dx === 0 && absDy < 1280) { + const flag = curveBit + ((absDy & 0xF00) >> 7) + ySignBit; + buf[offset] = absDy & 0xFF; + return flag; + } + + /* dy=0, dx≠0, X 单轴 1 数据字节 (flag 10-19) */ + if (dy === 0 && dx !== 0 && absDx < 1280) { + const flag = curveBit + 10 + ((absDx & 0xF00) >> 7) + xSignBit; + buf[offset] = absDx & 0xFF; + return flag; + } + + /* 双轴 1 数据字节 (flag 20-83): 1 ≤ |dx| ≤ 64, 1 ≤ |dy| ≤ 64 */ + if (dx !== 0 && dy !== 0 && absDx < 65 && absDy < 65) { + const ax = absDx - 1; + const ay = absDy - 1; + const flag = curveBit + 20 + (ax & 0x30) + ((ay & 0x30) >> 2) + xySignBits; + buf[offset] = ((ax & 0xF) << 4) | (ay & 0xF); + return flag; + } + + /* 双轴 2 数据字节 (flag 84-119): 1 ≤ |dx| ≤ 768, 1 ≤ |dy| ≤ 768 */ + if (dx !== 0 && dy !== 0 && absDx < 769 && absDy < 769) { + const ax = absDx - 1; + const ay = absDy - 1; + const flag = curveBit + 84 + 12 * ((ax & 0x300) >> 8) + ((ay & 0x300) >> 6) + xySignBits; + buf[offset] = ax & 0xFF; + buf[offset + 1] = ay & 0xFF; + return flag; + } + + /* 双轴 3 数据字节 (flag 120-123) */ + if (absDx < 4096 && absDy < 4096) { + const flag = curveBit + 120 + xySignBits; + buf[offset] = absDx >> 4; + buf[offset + 1] = ((absDx & 0xF) << 4) | (absDy >> 8); + buf[offset + 2] = absDy & 0xFF; + return flag; + } + + /* 兜底 4 数据字节 (flag 124-127) */ + const flag = curveBit + 124 + xySignBits; + buf[offset] = (absDx >> 8) & 0xFF; + buf[offset + 1] = absDx & 0xFF; + buf[offset + 2] = (absDy >> 8) & 0xFF; + buf[offset + 3] = absDy & 0xFF; + return flag; +} + +/** + * 仅写入数据字节(不含 flag),利用缓存的 flag 值避免重复分支判断 + * flag 的低 7 位 (tripletIndex) 决定数据字节布局 + * 优化182: 返回写入的字节数,消除调用方 TRIPLET_DATA_SIZES 重复查找 + */ +function writePointDataByFlag(flag, dx, dy, buf, offset) { + const ti = flag & 0x7F; + const absDx = dx < 0 ? -dx : dx; + const absDy = dy < 0 ? -dy : dy; + + if (ti < 10) { + /* Y 单轴 1 字节 */ + buf[offset] = absDy & 0xFF; + return 1; + } else if (ti < 20) { + /* X 单轴 1 字节 */ + buf[offset] = absDx & 0xFF; + return 1; + } else if (ti < 84) { + /* 双轴 1 字节 */ + const ax = absDx - 1; + const ay = absDy - 1; + buf[offset] = ((ax & 0xF) << 4) | (ay & 0xF); + return 1; + } else if (ti < 120) { + /* 双轴 2 字节 */ + const ax = absDx - 1; + const ay = absDy - 1; + buf[offset] = ax & 0xFF; + buf[offset + 1] = ay & 0xFF; + return 2; + } else if (ti < 124) { + /* 双轴 3 字节 */ + buf[offset] = absDx >> 4; + buf[offset + 1] = ((absDx & 0xF) << 4) | (absDy >> 8); + buf[offset + 2] = absDy & 0xFF; + return 3; + } else { + /* 兜底 4 字节 */ + buf[offset] = (absDx >> 8) & 0xFF; + buf[offset + 1] = absDx & 0xFF; + buf[offset + 2] = (absDy >> 8) & 0xFF; + buf[offset + 3] = absDy & 0xFF; + return 4; + } +} + +/* ======== glyf + loca 表变换 ======== */ + +/** + * 对 glyf + loca 表执行 WOFF2 变换 + */ +function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { + const glyfView = new DataView(glyfData.buffer, glyfData.byteOffset, glyfData.byteLength); + const locaView = new DataView(locaData.buffer, locaData.byteOffset, locaData.byteLength); + + /* 读取 loca 表获取每个 glyph 的偏移 */ + const offsets = new Int32Array(numGlyphs + 1); + if (indexFormat === 0) { + for (let i = 0; i <= numGlyphs; i++) { + offsets[i] = locaView.getUint16(i * 2, false) * 2; + } + } else { + for (let i = 0; i <= numGlyphs; i++) { + offsets[i] = locaView.getUint32(i * 4, false); + } + } + + const XSHORT_FLAG = 2; + const XSAME_FLAG = 16; + const YSHORT_FLAG = 4; + const YSAME_FLAG = 32; + const REPEAT_FLAG = 8; + const OVERLAP_FLAG = 64; + const ONCURVE_FLAG = 1; + + /** + * 优化:合并第 1 遍(解码 glyph)和第 2 遍(预计算 stream 大小 + 缓存 triplet flag) + * 消除一次完整的 numGlyphs 遍历,利用解码后数据仍在 L1 cache 的优势 + */ + let totalNPointsSize = 0; + let flagStreamSize = 0; + let glyphStreamSize = 0; + let bboxStreamSize = 0; + let instructionStreamSize = 0; + let hasOverlapBitmap = false; + let totalPoints = 0; + + const bboxBitmapSize = 4 * Math.floor((numGlyphs + 31) / 32); + const bboxBitmap = new Uint8Array(bboxBitmapSize); + const overlapBitmap = new Uint8Array(bboxBitmapSize); + const tmpBuf = new Uint8Array(4); + + /* 收集每个 glyph 的信息 */ + const glyphInfos = new Array(numGlyphs); + + for (let gi = 0; gi < numGlyphs; gi++) { + const glyphStart = offsets[gi]; + const glyphEnd = offsets[gi + 1]; + + if (glyphStart === glyphEnd) { + glyphInfos[gi] = null; + continue; + } + + const numberOfContours = glyfView.getInt16(glyphStart, false); + const xMin = glyfView.getInt16(glyphStart + 2, false); + const yMin = glyfView.getInt16(glyphStart + 4, false); + const xMax = glyfView.getInt16(glyphStart + 6, false); + const yMax = glyfView.getInt16(glyphStart + 8, false); + + if (numberOfContours < 0) { + /* 复合 glyph */ + let compOff = glyphStart + 10; + let haveInstructions = false; + let instrLength = 0; + let instructions = null; + + const MORE_COMPONENTS = 0x0020; + const WE_HAVE_INSTRUCTIONS = 0x0100; + while (compOff < glyphEnd) { + const compFlags = glyfView.getUint16(compOff, false); + compOff += 2; + compOff += 2; /* glyphIndex */ + + if (compFlags & 0x0001) compOff += 4; + else compOff += 2; + if (compFlags & 0x0008) compOff += 2; + else if (compFlags & 0x0040) compOff += 4; + else if (compFlags & 0x0080) compOff += 8; + + if (!(compFlags & MORE_COMPONENTS)) { + haveInstructions = !!(compFlags & WE_HAVE_INSTRUCTIONS); + break; + } + } + + const componentDataEnd = compOff; + + if (haveInstructions && compOff + 2 <= glyphEnd) { + instrLength = glyfView.getUint16(compOff, false); + compOff += 2; + if (instrLength > 0 && compOff + instrLength <= glyphEnd) { + instructions = { offset: compOff, length: instrLength }; + } + } + + const rawLength = componentDataEnd - glyphStart - 10; + + glyphInfos[gi] = { + composite: true, + xMin, yMin, xMax, yMax, + rawOffset: glyphStart + 10, + rawLength, + instructions, + haveInstructions, + }; + + /* ★ 合并:复合 glyph 的统计量 */ + bboxBitmap[gi >> 3] |= (0x80 >> (gi & 7)); + bboxStreamSize += 8; + glyphStreamSize += rawLength; + if (haveInstructions) { + instructionStreamSize += instrLength; + glyphStreamSize += size255UInt16(instrLength); + } + continue; + } + + /* 简单 glyph */ + let dataOff = glyphStart + 10; + + const endPtsOfContours = new Uint16Array(numberOfContours); + for (let c = 0; c < numberOfContours; c++) { + endPtsOfContours[c] = glyfView.getUint16(dataOff, false); + dataOff += 2; + } + + /* ★ 合并:totalNPointsSize 计算 + 缓存 255UInt16 编码结果 */ + const nPointsEncoded = new Uint8Array(numberOfContours * 3); + let nPointsBytes = 0; + let prevEnd = -1; + for (let c = 0; c < numberOfContours; c++) { + nPointsBytes += encode255UInt16(endPtsOfContours[c] - prevEnd, nPointsEncoded, nPointsBytes); + prevEnd = endPtsOfContours[c]; + } + totalNPointsSize += nPointsBytes; + + const instructionLength = glyfView.getUint16(dataOff, false); + dataOff += 2; + const instructions = instructionLength > 0 ? { offset: dataOff, length: instructionLength } : null; + dataOff += instructionLength; + + /* ★ 合并:instructionStreamSize 累加 */ + instructionStreamSize += instructionLength; + + const numPoints = numberOfContours > 0 ? endPtsOfContours[numberOfContours - 1] + 1 : 0; + + /* 优化183: flagsArr 复用为 cachedFlags,消除每个 glyph 一次 Uint8Array 分配 */ + const flagsArr = new Uint8Array(numPoints); + let hasOverlap = false; + let fi = 0; + while (fi < numPoints) { + const flag = glyfData[dataOff++]; + if (flag & OVERLAP_FLAG) hasOverlap = true; + flagsArr[fi++] = flag; + if (flag & REPEAT_FLAG && fi < numPoints) { + const repeat = glyfData[dataOff++]; + const count = Math.min(repeat, numPoints - fi); + flagsArr.fill(flag, fi, fi + count); + fi += count; + } + } + + /* ★ 合并:overlapBitmap + flagStreamSize + totalPoints */ + if (numberOfContours > 0) { + if (hasOverlap) { + hasOverlapBitmap = true; + overlapBitmap[gi >> 3] |= (0x80 >> (gi & 7)); + } + flagStreamSize += numPoints; + totalPoints += numPoints; + } + + /* 解码 X 坐标 */ + const xCoords = new Int32Array(numPoints); + let px = 0; + for (let xi = 0; xi < numPoints; xi++) { + const f = flagsArr[xi]; + if (f & XSHORT_FLAG) { + const b = glyfData[dataOff++]; + px += (f & XSAME_FLAG) ? b : -b; + } else if (!(f & XSAME_FLAG)) { + let dx = (glyfData[dataOff] << 8) | glyfData[dataOff + 1]; + if (dx > 0x7FFF) dx -= 0x10000; + px += dx; + dataOff += 2; + } + xCoords[xi] = px; + } + + /* 解码 Y 坐标 + 同时计算 bbox */ + const yCoords = new Int32Array(numPoints); + let py = 0; + let calcXMin = xCoords[0], calcYMin = py, calcXMax = xCoords[0], calcYMax = py; + for (let yi = 0; yi < numPoints; yi++) { + const f = flagsArr[yi]; + if (f & YSHORT_FLAG) { + const b = glyfData[dataOff++]; + py += (f & YSAME_FLAG) ? b : -b; + } else if (!(f & YSAME_FLAG)) { + let dy = (glyfData[dataOff] << 8) | glyfData[dataOff + 1]; + if (dy > 0x7FFF) dy -= 0x10000; + py += dy; + dataOff += 2; + } + yCoords[yi] = py; + const px2 = xCoords[yi]; + if (px2 < calcXMin) calcXMin = px2; + else if (px2 > calcXMax) calcXMax = px2; + if (py < calcYMin) calcYMin = py; + else if (py > calcYMax) calcYMax = py; + } + + /* 优化183: 就地覆盖 flagsArr 为 triplet flags,省掉单独的 cachedFlags 分配 */ + if (numberOfContours > 0) { + const bboxMatches = calcXMin === xMin && calcYMin === yMin && calcXMax === xMax && calcYMax === yMax; + if (!bboxMatches) { + bboxBitmap[gi >> 3] |= (0x80 >> (gi & 7)); + bboxStreamSize += 8; + } + + let prevX = 0, prevY = 0; + for (let pi = 0; pi < numPoints; pi++) { + const onCurve = !!(flagsArr[pi] & ONCURVE_FLAG); + const dx = xCoords[pi] - prevX; + const dy = yCoords[pi] - prevY; + const flag = encodePointToBuf(onCurve, dx, dy, tmpBuf, 0); + flagsArr[pi] = flag; + glyphStreamSize += TRIPLET_DATA_SIZES[flag & 0x7F]; + prevX = xCoords[pi]; + prevY = yCoords[pi]; + } + glyphStreamSize += size255UInt16(instructionLength); + } + + glyphInfos[gi] = { + composite: false, + numberOfContours, + nPointsEncoded, + nPointsBytes, + instructions, + xCoords, yCoords, flags: flagsArr, + hasOverlap, + xMin, yMin, xMax, yMax, + calcXMin, calcYMin, calcXMax, calcYMax, + }; + } + + const nContourStreamSize = numGlyphs * 2; + const headerSize = 36; + const overlapBitmapSize = hasOverlapBitmap ? bboxBitmapSize : 0; + const totalSize = headerSize + + nContourStreamSize + + totalNPointsSize + + flagStreamSize + + glyphStreamSize + + bboxBitmapSize + + bboxStreamSize + + instructionStreamSize + + overlapBitmapSize; + + const result = new Uint8Array(totalSize); + const resultView = new DataView(result.buffer, result.byteOffset, result.byteLength); + let pos = 0; + + /* Header */ + resultView.setUint16(pos, 0, false); pos += 2; + resultView.setUint16(pos, hasOverlapBitmap ? 1 : 0, false); pos += 2; + resultView.setUint16(pos, numGlyphs, false); pos += 2; + resultView.setUint16(pos, indexFormat, false); pos += 2; + resultView.setUint32(pos, nContourStreamSize, false); pos += 4; + resultView.setUint32(pos, totalNPointsSize, false); pos += 4; + resultView.setUint32(pos, flagStreamSize, false); pos += 4; + resultView.setUint32(pos, glyphStreamSize, false); pos += 4; + resultView.setUint32(pos, 0, false); pos += 4; + resultView.setUint32(pos, bboxBitmapSize + bboxStreamSize, false); pos += 4; + resultView.setUint32(pos, instructionStreamSize, false); pos += 4; + + /** + * 优化:合并原第 3/4/5 次遍历为 1 次 + * nContourStream + nPointsStream + 所有子流写入合并在单次 glyph 遍历中 + */ + const nContourEnd = pos + nContourStreamSize; + let nContourPos = pos; + let nPointsPos = nContourEnd; + pos = nContourEnd + totalNPointsSize; + + const flagStreamStart = pos; + pos += flagStreamSize; + const glyphStreamStart = pos; + pos += glyphStreamSize; + const bboxBitmapStart = pos; + pos += bboxBitmapSize; + const bboxStreamStart = pos; + pos += bboxStreamSize; + const instructionStreamStart = pos; + pos += instructionStreamSize; + const overlapBitmapStart = pos; + + let flagPos = flagStreamStart; + let glyphPos = glyphStreamStart; + let bboxPos = bboxStreamStart; + let instrPos = instructionStreamStart; + + for (let gi = 0; gi < numGlyphs; gi++) { + const g = glyphInfos[gi]; + + /* nContourStream: 每个 glyph 写入 numberOfContours */ + if (!g) { + resultView.setInt16(nContourPos, 0, false); nContourPos += 2; + continue; + } + if (g.composite) { + resultView.setInt16(nContourPos, -1, false); nContourPos += 2; + } else { + resultView.setInt16(nContourPos, g.numberOfContours, false); nContourPos += 2; + } + + if (g.composite) { + result.set(glyfData.subarray(g.rawOffset, g.rawOffset + g.rawLength), glyphPos); + glyphPos += g.rawLength; + resultView.setInt16(bboxPos, g.xMin, false); bboxPos += 2; + resultView.setInt16(bboxPos, g.yMin, false); bboxPos += 2; + resultView.setInt16(bboxPos, g.xMax, false); bboxPos += 2; + resultView.setInt16(bboxPos, g.yMax, false); bboxPos += 2; + 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; + + /* nPointsStream: 直接使用预编码的 delta 数据 */ + result.set(g.nPointsEncoded.subarray(0, g.nPointsBytes), nPointsPos); + nPointsPos += g.nPointsBytes; + + 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; + } + + /** + * flag + glyph 子流 — 使用缓存的 triplet flag + writePointDataByFlag + * 避免写入阶段的完整分支判断链(6 层 if-else),改用 flag 值直接定位数据布局 + */ + const numPts = g.xCoords.length; + const xCoords = g.xCoords; + const yCoords = g.yCoords; + const tripletFlags = g.flags; + let prevX = 0, prevY = 0; + for (let pi = 0; pi < numPts; pi++) { + const dx = xCoords[pi] - prevX; + const dy = yCoords[pi] - prevY; + const flag = tripletFlags[pi]; + result[flagPos++] = flag; + glyphPos += writePointDataByFlag(flag, dx, dy, result, glyphPos); + prevX = xCoords[pi]; + prevY = yCoords[pi]; + } + + glyphPos += encode255UInt16(instrLen, result, glyphPos); + + if (bboxBitmap[gi >> 3] & (0x80 >> (gi & 7))) { + resultView.setInt16(bboxPos, g.calcXMin, false); bboxPos += 2; + resultView.setInt16(bboxPos, g.calcYMin, false); bboxPos += 2; + resultView.setInt16(bboxPos, g.calcXMax, false); bboxPos += 2; + resultView.setInt16(bboxPos, g.calcYMax, false); bboxPos += 2; + } + } + + result.set(bboxBitmap, bboxBitmapStart); + + if (hasOverlapBitmap) { + result.set(overlapBitmap, overlapBitmapStart); + } + + const locaOrigLength = indexFormat === 0 ? (numGlyphs + 1) * 2 : (numGlyphs + 1) * 4; + + return { + transformedGlyf: result, + locaOrigLength, + locaTransformLength: 0, + }; +} + +/* ======== 主编码函数 ======== */ + +const WOFF2_SIGNATURE = 0x774F4632; +const WOFF2_HEADER_SIZE = 48; + +/** + * 将 TTF buffer 编码为 WOFF2 buffer + */ +function encodeTTFToWOFF2(ttfBuffer) { + const data = ttfBuffer instanceof Uint8Array ? ttfBuffer : new Uint8Array(ttfBuffer); + const view = new DataView(data.buffer, data.byteOffset, data.byteLength); + + /* 解析 sfnt header */ + const flavor = view.getUint32(0, false); + const numTables = view.getUint16(4, false); + + /* 解析 Table Directory */ + const tables = []; + for (let i = 0; i < numTables; i++) { + const off = 12 + i * 16; + const tag = String.fromCharCode(data[off], data[off + 1], data[off + 2], data[off + 3]); + const offset = view.getUint32(off + 8, false); + const length = view.getUint32(off + 12, false); + tables.push({ tag, offset, length, tagIndex: getTagIndex(tag) }); + } + + /* 移除 DSIG,按 tag 升序排列 */ + const filtered = tables.filter(t => t.tag !== "DSIG"); + filtered.sort((a, b) => a.tagIndex - b.tagIndex || (a.tag < b.tag ? -1 : a.tag > b.tag ? 1 : 0)); + + /* 查找关键表 */ + let indexToLocFormat = 0; + let numGlyphs = 0; + let glyfTable = null; + let locaTable = null; + + for (const t of filtered) { + if (t.tag === "head") indexToLocFormat = view.getUint16(t.offset + 50, false); + if (t.tag === "maxp") numGlyphs = view.getUint16(t.offset + 4, false); + if (t.tag === "glyf") glyfTable = t; + if (t.tag === "loca") locaTable = t; + } + + /* glyf + loca 变换 */ + let glyfTransformed = null; + if (glyfTable && locaTable) { + const glyfData = data.subarray(glyfTable.offset, glyfTable.offset + glyfTable.length); + const locaData = data.subarray(locaTable.offset, locaTable.offset + locaTable.length); + const result = transformGlyfAndLoca(glyfData, locaData, indexToLocFormat, numGlyphs); + glyfTransformed = result; + } + + /* 构建 Table Directory entries */ + const dirEntries = []; + let totalDirSize = 0; + + for (const t of filtered) { + if (t.tag === "loca") { + /** 优化:直接使用 transformGlyfAndLoca 返回的 locaOrigLength,避免重复计算 */ + const origLength = glyfTransformed ? glyfTransformed.locaOrigLength : t.length; + dirEntries.push({ + tag: t.tag, tagIndex: t.tagIndex, + flags: t.tagIndex, + origLength, + transformLength: 0, + data: new Uint8Array(0), + hasTransform: true, + }); + totalDirSize += 1 + sizeUIntBase128(origLength) + sizeUIntBase128(0); + continue; + } + + if (t.tag === "glyf" && glyfTransformed) { + dirEntries.push({ + tag: t.tag, tagIndex: t.tagIndex, + flags: t.tagIndex, + origLength: t.length, + transformLength: glyfTransformed.transformedGlyf.length, + data: glyfTransformed.transformedGlyf, + hasTransform: true, + }); + totalDirSize += 1 + sizeUIntBase128(t.length) + sizeUIntBase128(glyfTransformed.transformedGlyf.length); + continue; + } + + let tableData = data.subarray(t.offset, t.offset + t.length); + + dirEntries.push({ + tag: t.tag, tagIndex: t.tagIndex, + flags: t.tagIndex, + origLength: t.length, + transformLength: t.length, + data: tableData, + isHead: t.tag === "head", + }); + totalDirSize += 1 + sizeUIntBase128(t.length); + } + + /* 计算 totalSfntSize */ + let totalSfntSize = 12 + filtered.length * 16; + for (let i = 0; i < filtered.length; i++) { + const len = filtered[i].length; + totalSfntSize += len + (len & 3 ? 4 - (len & 3) : 0); + } + + /* 拼接表数据 */ + let totalTableDataSize = 0; + for (let i = 0; i < dirEntries.length; i++) totalTableDataSize += dirEntries[i].transformLength; + const uncompressedData = new Uint8Array(totalTableDataSize); + let dataPos = 0; + for (const entry of dirEntries) { + if (entry.transformLength > 0) { + uncompressedData.set(entry.data, dataPos); + if (entry.isHead) { + /* head 表原地修改:清零 checkSumAdjustment,设置 bit 11(headFlags),避免额外拷贝 */ + const uView = new DataView(uncompressedData.buffer, uncompressedData.byteOffset + dataPos, entry.transformLength); + uView.setUint32(8, 0, false); + const headFlags = uView.getUint16(44, false); + uView.setUint16(44, headFlags | (1 << 11), false); + } + dataPos += entry.transformLength; + } + } + + /* Brotli 压缩,传入 SIZE_HINT 帮助预分配内部缓冲区 */ + const brotliOptions = totalTableDataSize > 0 + ? { params: { + [constants.BROTLI_PARAM_QUALITY]: 8, + [constants.BROTLI_PARAM_SIZE_HINT]: totalTableDataSize, + }} + : BROTLI_OPTIONS_BASE; + const compressedData = brotliCompressSync(uncompressedData, brotliOptions); + + /* 组装 WOFF2(预计算 padding,避免额外拷贝) */ + const rawLength = WOFF2_HEADER_SIZE + totalDirSize + compressedData.length; + const paddedLength = (rawLength + 3) & ~3; + const woff2 = new Uint8Array(paddedLength); + const woff2View = new DataView(woff2.buffer, woff2.byteOffset, woff2.byteLength); + + /* Header */ + woff2View.setUint32(0, WOFF2_SIGNATURE, false); + woff2View.setUint32(4, flavor, false); + woff2View.setUint32(8, paddedLength, false); + woff2View.setUint16(12, dirEntries.length, false); + woff2View.setUint16(14, 0, false); + woff2View.setUint32(16, totalSfntSize, false); + woff2View.setUint32(20, compressedData.length, false); + woff2View.setUint16(24, 1, false); + woff2View.setUint16(26, 0, false); + woff2View.setUint32(28, 0, false); + woff2View.setUint32(32, 0, false); + woff2View.setUint32(36, 0, false); + woff2View.setUint32(40, 0, false); + woff2View.setUint32(44, 0, false); + + /* Table Directory */ + let dirPos = WOFF2_HEADER_SIZE; + for (const entry of dirEntries) { + woff2[dirPos++] = entry.flags; + if (entry.tagIndex === 63) { + for (let ci = 0; ci < 4; ci++) woff2[dirPos++] = entry.tag.charCodeAt(ci); + } + dirPos += encodeUIntBase128(entry.origLength, woff2, dirPos); + if (entry.hasTransform) { + dirPos += encodeUIntBase128(entry.transformLength, woff2, dirPos); + } + } + + /* 压缩数据 */ + woff2.set(compressedData, dirPos); + + /* WOFF2 规范要求文件大小 4 字节对齐(Round4) + 已在分配时预留 padding 空间,无需额外拷贝 */ + + return woff2.buffer; +} + +module.exports = { encodeTTFToWOFF2 }; diff --git a/vendor/fonteditor-core/woff2/woff2.wasm b/vendor/fonteditor-core/woff2/woff2.wasm deleted file mode 100644 index 7f31f44..0000000 Binary files a/vendor/fonteditor-core/woff2/woff2.wasm and /dev/null differ