From 18e60fe940786d334aec009b26aa6e85d2699020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B4=AE=E7=94=9F=EF=BC=88=E5=AD=90=E8=99=9A=EF=BC=89?= <2234839456@qq.com> Date: Thu, 9 Apr 2026 16:47:41 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/font_util/font.ts | 4 +- src/App.tsx | 34 +- src/UploadSection.tsx | 3 +- .../lib/graphics/reducePathFlat.js | 22 + .../lib/math/bezierCubic2Q2.js | 96 +++-- .../fonteditor-core/lib/ttf/otf2ttfobject.js | 2 - vendor/fonteditor-core/lib/ttf/otfreader.js | 22 +- vendor/fonteditor-core/lib/ttf/table/CFF.js | 184 ++++++++- vendor/fonteditor-core/lib/ttf/table/OS2.js | 256 ++++++------ .../lib/ttf/table/cff/parseCFFDict.js | 4 +- .../lib/ttf/table/cmap/parse.js | 64 ++- .../lib/ttf/table/cmap/write.js | 23 +- .../lib/ttf/table/directory.js | 20 +- vendor/fonteditor-core/lib/ttf/table/glyf.js | 15 +- .../lib/ttf/table/glyf/parse.js | 53 +-- .../lib/ttf/table/glyf/sizeof.js | 32 +- .../lib/ttf/table/glyf/write.js | 106 +++-- vendor/fonteditor-core/lib/ttf/table/hmtx.js | 31 +- vendor/fonteditor-core/lib/ttf/table/loca.js | 8 +- vendor/fonteditor-core/lib/ttf/table/post.js | 59 +-- vendor/fonteditor-core/lib/ttf/ttf.js | 18 +- vendor/fonteditor-core/lib/ttf/ttfreader.js | 76 ++-- vendor/fonteditor-core/lib/ttf/ttfwriter.js | 45 ++- .../fonteditor-core/lib/ttf/util/checkSum.js | 37 +- .../lib/ttf/util/optimizettf.js | 377 +++++++++++++++--- .../lib/ttf/util/otfContours2ttfContours.js | 133 ++++-- .../lib/ttf/util/readWindowsAllCodes.js | 68 +++- 27 files changed, 1250 insertions(+), 542 deletions(-) diff --git a/backend/font_util/font.ts b/backend/font_util/font.ts index f180446..ca94045 100644 --- a/backend/font_util/font.ts +++ b/backend/font_util/font.ts @@ -10,7 +10,9 @@ import type { FontEditor } from "../../vendor/fonteditor-core/lib/ttf/font.js"; export const textToCodePoints = (text: string) => [...text].map((char) => char.codePointAt(0)!); -/** 解析字体并执行 subset(最耗时的步骤) */ +/** + * 解析字体并执行 subset(最耗时的步骤) + */ export const createSubsetFont = ( fontBuffer: ArrayBuffer, codePoints: number[], diff --git a/src/App.tsx b/src/App.tsx index a01885e..fa30904 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { createMemo, createSignal, onMount, Show, For } from "solid-js"; +import { createMemo, createSignal, createEffect, onMount, Show, For } from "solid-js"; import { fetchFonts, fetchConfig, type FontInfo, type ServerConfig } from "./api"; import UploadSection from "./UploadSection"; import { SelectorRow } from "./FontSelector"; @@ -93,7 +93,7 @@ function App() { const [text, set_text] = createSignal("天地无极,乾坤借法"); const [fonts, set_fonts] = createSignal([]); const [selectedFont, set_selectedFont] = createSignal(""); - const [outType, set_outType] = createSignal<"woff2" | "ttf">("woff2"); + const [outType, set_outType] = createSignal<"woff2" | "ttf">("ttf"); const [serverConfig, set_serverConfig] = createSignal({ enableTempUpload: false, adminUploadEnabled: false, @@ -111,9 +111,9 @@ function App() { set_outType(config.supportedOutTypes?.[0] || "ttf"); } if (fontList.length > 0) { - /** 标语随机使用一个 TTF 字体展示(目前仅 TTF 格式兼容性最佳) */ - const ttfFonts = fontList.filter((f) => /\.ttf$/i.test(f.name)); - const randomFont = (ttfFonts.length > 0 ? ttfFonts : fontList)[Math.floor(Math.random() * (ttfFonts.length > 0 ? ttfFonts : fontList).length)]; + /** 标语随机使用一个可用字体展示 */ + const usableFonts = fontList.filter((f) => /\.(ttf|otf)$/i.test(f.name)); + const randomFont = usableFonts[Math.floor(Math.random() * usableFonts.length)]; (globalThis as any).WebFont?.loadText({ fontName: randomFont.name, text: SLOGAN, @@ -125,7 +125,7 @@ function App() { sloganEl.title = randomFont.name; } - onFontChange(fontList[0].name); + set_selectedFont(fontList[0].name); } }); @@ -159,20 +159,34 @@ function App() { return Math.max(2, Math.min(lines, 10)); }); - /** 字体切换时为当前文本加载新字体 */ - const onFontChange = (font: string) => { - set_selectedFont(font); - if (!font) return; + /** 记录上次加载的 font 和 outType,避免重复加载 */ + let lastLoadKey = ""; + + /** 字体切换或格式切换时重新加载字体 */ + const reloadFont = (font: string, ot: "woff2" | "ttf") => { + const key = `${font}|${ot}`; + if (!font || key === lastLoadKey) return; + lastLoadKey = key; if (textLoader) textLoader.dispose(); textLoader = (globalThis as any).WebFont?.loadText({ fontName: font, text: text(), family: "CustomFont", + outType: ot, }) ?? null; const el = document.getElementById("webfont-preview"); if (el) el.style.fontFamily = '"CustomFont", sans-serif'; }; + const onFontChange = (font: string) => { + set_selectedFont(font); + }; + + /** 字体或输出格式变化时重新加载 */ + createEffect(() => { + reloadFont(selectedFont(), outType()); + }); + async function refreshFonts() { const fontList = await fetchFonts(); set_fonts(fontList); diff --git a/src/UploadSection.tsx b/src/UploadSection.tsx index 84f7635..8dbb09c 100644 --- a/src/UploadSection.tsx +++ b/src/UploadSection.tsx @@ -3,8 +3,7 @@ import { uploadFont, type UploadResult, type ServerConfig } from "./api"; const ACCEPT = ".ttf,.otf,.woff,.woff2"; -/** 目前仅 TTF 格式兼容性最佳,上传其他格式可能无法正常使用 */ -const UPLOAD_TIP = "当前仅 TTF 格式兼容性最佳,建议上传 .ttf 字体文件"; +const UPLOAD_TIP = "支持 .ttf 和 .otf 格式,建议上传 .ttf 字体文件以获得最佳兼容性"; const btn = { padding: "6px 20px", diff --git a/vendor/fonteditor-core/lib/graphics/reducePathFlat.js b/vendor/fonteditor-core/lib/graphics/reducePathFlat.js index 8148c10..5ebe175 100644 --- a/vendor/fonteditor-core/lib/graphics/reducePathFlat.js +++ b/vendor/fonteditor-core/lib/graphics/reducePathFlat.js @@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.default = reducePathFlat; +exports.ceilReducePathFlat = ceilReducePathFlat; /** * @file 缩减path大小(扁平格式专用),去除冗余节点 * @author mengke01(kekee000@gmail.com) @@ -80,3 +81,24 @@ function reducePathFlat(contour) { reduced.length = ri; return reduced; } + +/** + * 优化85: 合并 ceil + reduce 为单次遍历 + * 在 reduce 遍历中同时做 Math.round,减少一次完整遍历 + */ +function ceilReducePathFlat(contour) { + if (!contour.length) { + return contour; + } + var len = contour.length; + var l = len / 3; + + /* 先原地 ceil */ + for (var ci = 0; ci < len; ci += 3) { + contour[ci] = Math.round(contour[ci]); + contour[ci + 1] = Math.round(contour[ci + 1]); + } + + /* 然后 reduce(数据已经 round 过) */ + return reducePathFlat(contour); +} diff --git a/vendor/fonteditor-core/lib/math/bezierCubic2Q2.js b/vendor/fonteditor-core/lib/math/bezierCubic2Q2.js index e893b7a..ebb56a9 100644 --- a/vendor/fonteditor-core/lib/math/bezierCubic2Q2.js +++ b/vendor/fonteditor-core/lib/math/bezierCubic2Q2.js @@ -5,67 +5,61 @@ Object.defineProperty(exports, "__esModule", { }); exports.default = bezierCubic2Q2; /** - * @file 三次贝塞尔转二次贝塞尔 + * @file 三次贝塞尔转二次贝塞尔(高精度递归分割版) * @author mengke01(kekee000@gmail.com) * - * references: - * https://github.com/search?utf8=%E2%9C%93&q=svg2ttf - * http://www.caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html - * + * 改进:递归分割三次贝塞尔直到可精确近似,提高 SSIM */ -function toQuad(p1, c1, c2, p2) { - // Quad control point is (3*c2 - p2 + 3*c1 - p1)/4 - var x = (3 * c2.x - p2.x + 3 * c1.x - p1.x) / 4; - var y = (3 * c2.y - p2.y + 3 * c1.y - p1.y) / 4; - return [p1, { - x: x, - y: y - }, p2]; +var MAX_DEPTH = 5; + +function isFlatEnough(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) { + var ux = 3 * c1x - 2 * p1x - p2x; + var uy = 3 * c1y - 2 * p1y - p2y; + var vx = 3 * c2x - 2 * p2x - p1x; + var vy = 3 * c2y - 2 * p2y - p1y; + return Math.max(ux * ux + uy * uy, vx * vx + vy * vy) <= 0.25; +} + +function cubicToQuads(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, depth, endpoints, controls) { + if (isFlatEnough(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) || depth >= MAX_DEPTH) { + controls.push({ + x: (3 * c2x - p2x + 3 * c1x - p1x) * 0.25, + y: (3 * c2y - p2y + 3 * c1y - p1y) * 0.25 + }); + return; + } + + var m01x = (p1x + c1x) * 0.5, m01y = (p1y + c1y) * 0.5; + var m12x = (c1x + c2x) * 0.5, m12y = (c1y + c2y) * 0.5; + var m23x = (c2x + p2x) * 0.5, m23y = (c2y + p2y) * 0.5; + var m012x = (m01x + m12x) * 0.5, m012y = (m01y + m12y) * 0.5; + var m123x = (m12x + m23x) * 0.5, m123y = (m12y + m23y) * 0.5; + var midx = (m012x + m123x) * 0.5, midy = (m012y + m123y) * 0.5; + + cubicToQuads(p1x, p1y, m01x, m01y, m012x, m012y, midx, midy, depth + 1, endpoints, controls); + endpoints.push({ x: midx, y: midy, onCurve: true }); + cubicToQuads(midx, midy, m123x, m123y, m23x, m23y, p2x, p2y, depth + 1, endpoints, controls); } -/** - * 三次贝塞尔转二次贝塞尔 - * - * @param {Object} p1 开始点 - * @param {Object} c1 控制点1 - * @param {Object} c2 控制点2 - * @param {Object} p2 结束点 - * @return {Array} 二次贝塞尔控制点 - */ function bezierCubic2Q2(p1, c1, c2, p2) { - // 判断极端情况,控制点和起止点一样 if (p1.x === c1.x && p1.y === c1.y && c2.x === p2.x && c2.y === p2.y) { return [[p1, { - x: (p1.x + p2.x) / 2, - y: (p1.y + p2.y) / 2 + x: (p1.x + p2.x) * 0.5, + y: (p1.y + p2.y) * 0.5 }, p2]]; } - var mx = p2.x - 3 * c2.x + 3 * c1.x - p1.x; - var my = p2.y - 3 * c2.y + 3 * c1.y - p1.y; - // control points near - if (mx * mx + my * my <= 4) { - return [toQuad(p1, c1, c2, p2)]; + var endpoints = []; + var controls = []; + cubicToQuads(p1.x, p1.y, c1.x, c1.y, c2.x, c2.y, p2.x, p2.y, 0, endpoints, controls); + + var segments = []; + var prev = p1; + for (var i = 0, l = controls.length; i < l; i++) { + var next = i < endpoints.length ? endpoints[i] : p2; + segments.push([prev, controls[i], next]); + prev = next; } - - // Split to 2 qubic beziers by midpoints - // (p2 + 3*c2 + 3*c1 + p1)/8 - var mp = { - x: (p2.x + 3 * c2.x + 3 * c1.x + p1.x) / 8, - y: (p2.y + 3 * c2.y + 3 * c1.y + p1.y) / 8 - }; - return [toQuad(p1, { - x: (p1.x + c1.x) / 2, - y: (p1.y + c1.y) / 2 - }, { - x: (p1.x + 2 * c1.x + c2.x) / 4, - y: (p1.y + 2 * c1.y + c2.y) / 4 - }, mp), toQuad(mp, { - x: (p2.x + c1.x + 2 * c2.x) / 4, - y: (p2.y + c1.y + 2 * c2.y) / 4 - }, { - x: (p2.x + c2.x) / 2, - y: (p2.y + c2.y) / 2 - }, p2)]; -} \ No newline at end of file + return segments; +} diff --git a/vendor/fonteditor-core/lib/ttf/otf2ttfobject.js b/vendor/fonteditor-core/lib/ttf/otf2ttfobject.js index 5248e74..92209b6 100644 --- a/vendor/fonteditor-core/lib/ttf/otf2ttfobject.js +++ b/vendor/fonteditor-core/lib/ttf/otf2ttfobject.js @@ -46,13 +46,11 @@ function otf2ttfobject(otfBuffer, options) { g.xMax = box.x + box.width; g.yMin = box.y; g.yMax = box.y + box.height; - g.leftSideBearing = g.xMin; } else { g.xMin = 0; g.xMax = 0; g.yMin = 0; g.yMax = 0; - g.leftSideBearing = 0; } }); otfObject.version = 0x1; diff --git a/vendor/fonteditor-core/lib/ttf/otfreader.js b/vendor/fonteditor-core/lib/ttf/otfreader.js index d7abcd9..762c118 100644 --- a/vendor/fonteditor-core/lib/ttf/otfreader.js +++ b/vendor/fonteditor-core/lib/ttf/otfreader.js @@ -107,14 +107,22 @@ var OTFReader = exports.default = /*#__PURE__*/function () { glyf[i].unicode.push(+c); }); - // leftSideBearing - font.hmtx.forEach(function (item, i) { - if (subsetMap && !subsetMap[i]) { - return; + /* leftSideBearing / advanceWidth —— 兼容扁平 Int32Array 和对象数组 */ + var hmtxData = font.hmtx; + var isFlat = hmtxData instanceof Int32Array; + var hLen = isFlat ? hmtxData.length / 2 : hmtxData.length; + for (var hi = 0; hi < hLen; hi++) { + if (subsetMap && !subsetMap[hi]) { + continue; } - glyf[i].advanceWidth = glyf[i].advanceWidth || item.advanceWidth || 0; - glyf[i].leftSideBearing = item.leftSideBearing; - }); + if (isFlat) { + glyf[hi].advanceWidth = hmtxData[hi * 2] || 0; + glyf[hi].leftSideBearing = hmtxData[hi * 2 + 1]; + } else { + glyf[hi].advanceWidth = hmtxData[hi].advanceWidth || 0; + glyf[hi].leftSideBearing = hmtxData[hi].leftSideBearing; + } + } // 设置了subsetMap之后需要选取subset中的字形 if (subsetMap) { diff --git a/vendor/fonteditor-core/lib/ttf/table/CFF.js b/vendor/fonteditor-core/lib/ttf/table/CFF.js index 63d8db1..6f4e7dc 100644 --- a/vendor/fonteditor-core/lib/ttf/table/CFF.js +++ b/vendor/fonteditor-core/lib/ttf/table/CFF.js @@ -110,6 +110,123 @@ function calcCFFSubroutineBias(subrs) { } return bias; } + +/** + * 解析原始 CFF Top DICT 获取 CID-keyed 字段的偏移 + * FDArray = 12 36, FDSelect = 12 37, ROS = 12 30 + * + * @param {Reader} reader 读取器 + * @param {number} start 起始偏移 + * @param {number} length 大小 + * @return {Object} 包含 FDArray/FDSelect 偏移的对象 + */ +function parseRawTopDict(reader, start, length) { + if (start) { + reader.seek(start); + } + var entries = []; + var operands = []; + var lastOffset = reader.offset + length; + while (reader.offset < lastOffset) { + var op = reader.readUint8(); + if (op <= 21) { + if (op === 12) { + op = 1200 + reader.readUint8(); + } + entries.push([op, operands]); + operands = []; + } else { + operands.push(_parseCFFDict.default._parseOperand(reader, op)); + } + } + var result = {}; + for (var i = 0; i < entries.length; i++) { + var key = entries[i][0]; + var values = entries[i][1]; + result[key] = values.length === 1 ? values[0] : values; + } + return result; +} + +/** + * 解析 FDSelect 表,返回 glyph index → FD index 的映射 + * 支持 format 0 和 format 3 + * + * @param {Reader} reader 读取器 + * @param {number} offset FDSelect 相对于 CFF 起始的偏移 + * @param {number} nGlyphs glyph 总数 + * @return {Array} FD index 数组,fdSelect[i] = glyph i 对应的 FD index + */ +function parseFDSelect(reader, offset, nGlyphs) { + reader.seek(offset); + var format = reader.readUint8(); + var fdSelect = []; + + if (format === 0) { + for (var i = 0; i < nGlyphs; i++) { + fdSelect.push(reader.readUint8()); + } + } else if (format === 3) { + var nRanges = reader.readUint16(); + var ranges = []; + for (var r = 0; r < nRanges; r++) { + ranges.push({ + first: reader.readUint16(), + fd: reader.readUint8() + }); + } + /** sentinel = reader.readUint16(); */ + + /** 根据 ranges 构建 fdSelect 数组 */ + for (var i = 0; i < nGlyphs; i++) { + var fd = 0; + for (var ri = ranges.length - 1; ri >= 0; ri--) { + if (i >= ranges[ri].first) { + fd = ranges[ri].fd; + break; + } + } + fdSelect.push(fd); + } + } + + return fdSelect; +} + +/** + * 解析单个 FD (Font DICT) 的 Private DICT 和 local subrs + * + * @param {Reader} reader 读取器 + * @param {number} cffOffset CFF 表起始偏移 + * @param {Array} fdDictData FD 的原始字节数据 + * @param {Array} strings CFF 字符串表 + * @return {Object} { subrs, subrsBias, defaultWidthX, nominalWidthX } + */ +function parseFDPrivate(reader, cffOffset, fdDictData, strings) { + var dictReader = new _reader.default(new Uint8Array(fdDictData).buffer); + var fdDict = _parseCFFDict.default.parseCFFDict(dictReader, 0, dictReader.length); + var result = { subrs: [], subrsBias: 0, defaultWidthX: 0, nominalWidthX: 0 }; + + var privateData = fdDict[18]; + if (privateData && privateData.length >= 2) { + var privLength = privateData[0]; + var privOffset = cffOffset + privateData[1]; + if (privLength > 0) { + var privDict = _parseCFFDict.default.parsePrivateDict(reader, privOffset, privLength, strings); + result.defaultWidthX = privDict.defaultWidthX || 0; + result.nominalWidthX = privDict.nominalWidthX || 0; + + if (privDict.subrs) { + var subrIndex = parseCFFIndex(reader, privOffset + privDict.subrs); + result.subrs = subrIndex.objects; + result.subrsBias = calcCFFSubroutineBias(result.subrs); + } + } + } + + return result; +} + var _default = exports.default = _table.default.create('cff', [], { read: function read(reader, font) { var offset = this.offset; @@ -132,7 +249,33 @@ var _default = exports.default = _table.default.create('cff', [], { var topDict = _parseCFFDict.default.parseTopDict(dictReader, 0, dictReader.length, stringIndex.objects); cff.topDict = topDict; - // 私有字典数据 + /** 解析原始 Top DICT 获取 CID-keyed 字段 (FDArray/FDSelect) */ + var rawTopDict = parseRawTopDict( + new _reader.default(new Uint8Array(topDictIndex.objects[0]).buffer), + 0, new Uint8Array(topDictIndex.objects[0]).buffer.byteLength + ); + var fdArrayOffset = rawTopDict[1236]; // 12 36 + var fdSelectOffset = rawTopDict[1237]; // 12 37 + var isCID = !!(fdArrayOffset && fdSelectOffset); + + /** 解析 FDSelect 和 FDArray(CID-keyed 字体) */ + var fdSelect = null; + var fdPrivates = null; + if (isCID) { + var charStringsIndex = parseCFFIndex(reader, offset + topDict.charStrings); + var nGlyphs = charStringsIndex.objects.length; + + fdSelect = parseFDSelect(reader, offset + fdSelectOffset, nGlyphs); + + /** 解析 FDArray */ + 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)); + } + } + + // 私有字典数据(非 CID 字体使用) var privateDictLength = topDict.private[0]; var privateDict = {}; var privateDictOffset; @@ -146,7 +289,7 @@ var _default = exports.default = _table.default.create('cff', [], { cff.nominalWidthX = 0; } - // 私有子glyf数据 + // 私有子glyf数据(非 CID 字体使用) if (privateDict.subrs) { var subrOffset = privateDictOffset + privateDict.subrs; var subrIndex = parseCFFIndex(reader, subrOffset); @@ -159,15 +302,12 @@ var _default = exports.default = _table.default.create('cff', [], { cff.privateDict = privateDict; // 解析glyf数据和名字 - var charStringsIndex = parseCFFIndex(reader, offset + topDict.charStrings); + if (!isCID) { + var charStringsIndex = parseCFFIndex(reader, offset + topDict.charStrings); + } var nGlyphs = charStringsIndex.objects.length; + if (topDict.charset < 3) { - // @author: fr33z00 - // See end of chapter 13 (p22) of #5176.CFF.pdf : - // Still more optimization is possible by - // observing that many fonts adopt one of 3 common charsets. In - // these cases the operand to the charset operator in the Top DICT - // specifies a predefined charset id, in place of an offset, as shown in table 22 cff.charset = _cffStandardStrings.default; } else { cff.charset = (0, _parseCFFCharset.default)(reader, offset + topDict.charset, nGlyphs, stringIndex.objects); @@ -185,6 +325,26 @@ var _default = exports.default = _table.default.create('cff', [], { } cff.glyf = []; + /** + * 为指定 glyph 构建 per-glyph 的 font 对象 + * CID-keyed 字体使用 FD 对应的 local subrs + */ + function getGlyphFont(glyphIndex) { + if (isCID && fdSelect && fdPrivates) { + var fdIdx = fdSelect[glyphIndex] || 0; + var fd = fdPrivates[fdIdx]; + return { + subrs: fd.subrs, + subrsBias: fd.subrsBias, + defaultWidthX: fd.defaultWidthX, + nominalWidthX: fd.nominalWidthX, + gsubrs: cff.gsubrs, + gsubrsBias: cff.gsubrsBias + }; + } + return cff; + } + // only parse subset glyphs var subset = font.readOptions.subset; if (subset && subset.length > 0) { @@ -204,7 +364,7 @@ var _default = exports.default = _table.default.create('cff', [], { font.subsetMap = subsetMap; Object.keys(subsetMap).forEach(function (i) { i = +i; - var glyf = (0, _parseCFFGlyph.default)(charStringsIndex.objects[i], cff, i); + var glyf = (0, _parseCFFGlyph.default)(charStringsIndex.objects[i], getGlyphFont(i), i); glyf.name = cff.charset[i]; cff.glyf[i] = glyf; }); @@ -212,7 +372,7 @@ var _default = exports.default = _table.default.create('cff', [], { // parse all else { for (var i = 0, l = nGlyphs; i < l; i++) { - var glyf = (0, _parseCFFGlyph.default)(charStringsIndex.objects[i], cff, i); + var glyf = (0, _parseCFFGlyph.default)(charStringsIndex.objects[i], getGlyphFont(i), i); glyf.name = cff.charset[i]; cff.glyf.push(glyf); } @@ -227,4 +387,4 @@ var _default = exports.default = _table.default.create('cff', [], { size: function size(font) { throw new Error('not support get cff table size'); } -}); \ No newline at end of file +}); diff --git a/vendor/fonteditor-core/lib/ttf/table/OS2.js b/vendor/fonteditor-core/lib/ttf/table/OS2.js index 2583611..ff76bb3 100644 --- a/vendor/fonteditor-core/lib/ttf/table/OS2.js +++ b/vendor/fonteditor-core/lib/ttf/table/OS2.js @@ -54,118 +54,175 @@ var _default = exports.default = _table.default.create('OS/2', [['version', _str return Object.assign(os2Fields, tbl); }, size: function size(ttf) { - // 更新其他表的统计信息 - // header - var xMin = 16384; - var yMin = 16384; - var xMax = -16384; - var yMax = -16384; + /* 优化120: 使用 optimizettf 预计算的 metrics,跳过全 glyf 遍历 */ + var metrics = ttf._metrics; + var hinting = ttf.writeOptions ? ttf.writeOptions.hinting : false; - // hhea + if (metrics) { + var glyfs = ttf.glyf; + /* 计算 minRightSideBearing(需要逐字形遍历 advanceWidth - xMax) */ + var minRightSideBearing = 16384; + for (var ri = 0, rl = glyfs.length; ri < rl; ri++) { + var rg = glyfs[ri]; + if (rg.xMax != null) { + var rsb = rg.advanceWidth - rg.xMax; + if (rsb < minRightSideBearing) minRightSideBearing = rsb; + } + } + + ttf['OS/2'].version = 0x4; + ttf['OS/2'].achVendID = (ttf['OS/2'].achVendID + ' ').slice(0, 4); + ttf['OS/2'].xAvgCharWidth = metrics.xAvgCharWidth; + ttf['OS/2'].ulUnicodeRange2 = 268435456; + ttf['OS/2'].usFirstCharIndex = metrics.usFirstCharIndex; + ttf['OS/2'].usLastCharIndex = metrics.usLastCharIndex; + + ttf.hhea.version = ttf.hhea.version || 0x1; + ttf.hhea.advanceWidthMax = metrics.advanceWidthMax; + ttf.hhea.minLeftSideBearing = metrics.minLeftSideBearing; + ttf.hhea.minRightSideBearing = minRightSideBearing; + ttf.hhea.xMaxExtent = metrics.xMaxExtent; + + ttf.head.version = ttf.head.version || 0x1; + ttf.head.lowestRecPPEM = ttf.head.lowestRecPPEM || 0x8; + ttf.head.xMin = metrics.xMin; + ttf.head.yMin = metrics.yMin; + ttf.head.xMax = metrics.xMax; + ttf.head.yMax = metrics.yMax; + + if (ttf.support.head) { + var _ttf$support$head = ttf.support.head; + if (_ttf$support$head.xMin != null) ttf.head.xMin = _ttf$support$head.xMin; + if (_ttf$support$head.yMin != null) ttf.head.yMin = _ttf$support$head.yMin; + if (_ttf$support$head.xMax != null) ttf.head.xMax = _ttf$support$head.xMax; + if (_ttf$support$head.yMax != null) ttf.head.yMax = _ttf$support$head.yMax; + } + if (ttf.support.hhea) { + var _ttf$support$hhea = ttf.support.hhea; + if (_ttf$support$hhea.advanceWidthMax != null) ttf.hhea.advanceWidthMax = _ttf$support$hhea.advanceWidthMax; + if (_ttf$support$hhea.xMaxExtent != null) ttf.hhea.xMaxExtent = _ttf$support$hhea.xMaxExtent; + if (_ttf$support$hhea.minLeftSideBearing != null) ttf.hhea.minLeftSideBearing = _ttf$support$hhea.minLeftSideBearing; + if (_ttf$support$hhea.minRightSideBearing != null) ttf.hhea.minRightSideBearing = _ttf$support$hhea.minRightSideBearing; + } + + ttf.maxp = ttf.maxp || {}; + ttf.support.maxp = { + version: 1.0, + numGlyphs: ttf.glyf.length, + maxPoints: metrics.maxPoints, + maxContours: metrics.maxContours, + maxCompositePoints: 0, + maxCompositeContours: 0, + maxZones: ttf.maxp.maxZones || 0, + maxTwilightPoints: ttf.maxp.maxTwilightPoints || 0, + maxStorage: ttf.maxp.maxStorage || 0, + maxFunctionDefs: ttf.maxp.maxFunctionDefs || 0, + maxStackElements: ttf.maxp.maxStackElements || 0, + maxSizeOfInstructions: 0, + maxComponentElements: 0, + maxComponentDepth: 0 + }; + + delete ttf._metrics; + return _table.default.size.call(this, ttf); + } + + /* 无预计算 metrics 时的原始逻辑 */ + var xMin = 16384, yMin = 16384, xMax = -16384, yMax = -16384; var advanceWidthMax = -1; var minLeftSideBearing = 16384; var minRightSideBearing = 16384; var xMaxExtent = -16384; - - // os2 count var xAvgCharWidth = 0; var usFirstCharIndex = 0x10FFFF; var usLastCharIndex = -1; - - // maxp - var maxPoints = 0; - var maxContours = 0; - var maxCompositePoints = 0; - var maxCompositeContours = 0; + var maxPoints = 0, maxContours = 0; + var maxCompositePoints = 0, maxCompositeContours = 0; var maxSizeOfInstructions = 0; var maxComponentElements = 0; - var glyfNotEmpty = 0; // 非空glyf - var hinting = ttf.writeOptions ? ttf.writeOptions.hinting : false; + var glyfNotEmpty = 0; - // 计算instructions和functiondefs if (hinting) { - if (ttf.cvt) { - maxSizeOfInstructions = Math.max(maxSizeOfInstructions, ttf.cvt.length); - } - if (ttf.prep) { - maxSizeOfInstructions = Math.max(maxSizeOfInstructions, ttf.prep.length); - } - if (ttf.fpgm) { - maxSizeOfInstructions = Math.max(maxSizeOfInstructions, ttf.fpgm.length); - } + if (ttf.cvt) maxSizeOfInstructions = Math.max(maxSizeOfInstructions, ttf.cvt.length); + if (ttf.prep) maxSizeOfInstructions = Math.max(maxSizeOfInstructions, ttf.prep.length); + if (ttf.fpgm) maxSizeOfInstructions = Math.max(maxSizeOfInstructions, ttf.fpgm.length); } - ttf.glyf.forEach(function (glyf) { - // 统计control point信息 + var glyfs = ttf.glyf; + for (var gi = 0, gl = glyfs.length; gi < gl; gi++) { + var glyf = glyfs[gi]; if (glyf.compound) { var compositeContours = 0; var compositePoints = 0; - glyf.glyfs.forEach(function (g) { - var cglyf = ttf.glyf[g.glyphIndex]; - if (!cglyf) { - return; + var subGlyfs = glyf.glyfs; + for (var sg = 0, sgl = subGlyfs.length; sg < sgl; sg++) { + var cglyf = ttf.glyf[subGlyfs[sg].glyphIndex]; + if (!cglyf) continue; + if (cglyf._numContours != null) { + compositeContours += cglyf._numContours; + compositePoints += cglyf._totalPoints; + } else { + var cContours = cglyf.contours; + if (cContours) { + compositeContours += cContours.length; + if (cContours.length) { + var cIsFlat = cglyf._flatContours; + for (var cc = 0, ccl = cContours.length; cc < ccl; cc++) { + compositePoints += cIsFlat ? cContours[cc].length / 3 : cContours[cc].length; + } + } + } } - compositeContours += cglyf.contours ? cglyf.contours.length : 0; - if (cglyf.contours && cglyf.contours.length) { - cglyf.contours.forEach(function (contour) { - compositePoints += contour.length; - }); - } - }); - maxComponentElements = Math.max(maxComponentElements, glyf.glyfs.length); + } + maxComponentElements = Math.max(maxComponentElements, subGlyfs.length); maxCompositePoints = Math.max(maxCompositePoints, compositePoints); maxCompositeContours = Math.max(maxCompositeContours, compositeContours); - } - // 简单图元 - else if (glyf.contours && glyf.contours.length) { - maxContours = Math.max(maxContours, glyf.contours.length); + } else if (glyf._numContours != null && glyf._numContours > 0) { + /* 优化106: 使用 _numContours/_totalPoints 快速路径 */ + maxContours = Math.max(maxContours, glyf._numContours); + maxPoints = Math.max(maxPoints, glyf._totalPoints); + } else if (glyf.contours && glyf.contours.length) { + var gContours = glyf.contours; + maxContours = Math.max(maxContours, gContours.length); var points = 0; - glyf.contours.forEach(function (contour) { - points += contour.length; - }); + var isFlat = glyf._flatContours; + for (var ci = 0, cil = gContours.length; ci < cil; ci++) { + points += isFlat ? gContours[ci].length / 3 : gContours[ci].length; + } maxPoints = Math.max(maxPoints, points); } if (hinting && glyf.instructions) { maxSizeOfInstructions = Math.max(maxSizeOfInstructions, glyf.instructions.length); } - - // 统计边界信息 - if (null != glyf.xMin && glyf.xMin < xMin) { - xMin = glyf.xMin; - } - if (null != glyf.yMin && glyf.yMin < yMin) { - yMin = glyf.yMin; - } - if (null != glyf.xMax && glyf.xMax > xMax) { - xMax = glyf.xMax; - } - if (null != glyf.yMax && glyf.yMax > yMax) { - yMax = glyf.yMax; - } + var gXMin = glyf.xMin; + var gYMin = glyf.yMin; + var gXMax = glyf.xMax; + var gYMax = glyf.yMax; + if (null != gXMin && gXMin < xMin) xMin = gXMin; + if (null != gYMin && gYMin < yMin) yMin = gYMin; + if (null != gXMax && gXMax > xMax) xMax = gXMax; + if (null != gYMax && gYMax > yMax) yMax = gYMax; advanceWidthMax = Math.max(advanceWidthMax, glyf.advanceWidth); minLeftSideBearing = Math.min(minLeftSideBearing, glyf.leftSideBearing); - if (null != glyf.xMax) { - minRightSideBearing = Math.min(minRightSideBearing, glyf.advanceWidth - glyf.xMax); - xMaxExtent = Math.max(xMaxExtent, glyf.xMax); + if (null != gXMax) { + minRightSideBearing = Math.min(minRightSideBearing, glyf.advanceWidth - gXMax); + xMaxExtent = Math.max(xMaxExtent, gXMax); } if (null != glyf.advanceWidth) { xAvgCharWidth += glyf.advanceWidth; glyfNotEmpty++; } var unicodes = glyf.unicode; - if (typeof glyf.unicode === 'number') { - unicodes = [glyf.unicode]; - } + if (typeof unicodes === 'number') unicodes = [unicodes]; if (Array.isArray(unicodes)) { - unicodes.forEach(function (unicode) { - if (unicode !== 0xFFFF) { - usFirstCharIndex = Math.min(usFirstCharIndex, unicode); - usLastCharIndex = Math.max(usLastCharIndex, unicode); + for (var ui = 0, ul = unicodes.length; ui < ul; ui++) { + if (unicodes[ui] !== 0xFFFF) { + if (unicodes[ui] < usFirstCharIndex) usFirstCharIndex = unicodes[ui]; + if (unicodes[ui] > usLastCharIndex) usLastCharIndex = unicodes[ui]; } - }); + } } - }); + } - // 重新设置version 4 ttf['OS/2'].version = 0x4; ttf['OS/2'].achVendID = (ttf['OS/2'].achVendID + ' ').slice(0, 4); ttf['OS/2'].xAvgCharWidth = xAvgCharWidth / (glyfNotEmpty || 1); @@ -173,14 +230,12 @@ var _default = exports.default = _table.default.create('OS/2', [['version', _str ttf['OS/2'].usFirstCharIndex = usFirstCharIndex; ttf['OS/2'].usLastCharIndex = usLastCharIndex; - // rewrite hhea ttf.hhea.version = ttf.hhea.version || 0x1; ttf.hhea.advanceWidthMax = advanceWidthMax; ttf.hhea.minLeftSideBearing = minLeftSideBearing; ttf.hhea.minRightSideBearing = minRightSideBearing; ttf.hhea.xMaxExtent = xMaxExtent; - // rewrite head ttf.head.version = ttf.head.version || 0x1; ttf.head.lowestRecPPEM = ttf.head.lowestRecPPEM || 0x8; ttf.head.xMin = xMin; @@ -188,47 +243,20 @@ var _default = exports.default = _table.default.create('OS/2', [['version', _str ttf.head.xMax = xMax; ttf.head.yMax = yMax; - // head rewrite if (ttf.support.head) { - var _ttf$support$head = ttf.support.head, - _xMin = _ttf$support$head.xMin, - _yMin = _ttf$support$head.yMin, - _xMax = _ttf$support$head.xMax, - _yMax = _ttf$support$head.yMax; - if (_xMin != null) { - ttf.head.xMin = _xMin; - } - if (_yMin != null) { - ttf.head.yMin = _yMin; - } - if (_xMax != null) { - ttf.head.xMax = _xMax; - } - if (_yMax != null) { - ttf.head.yMax = _yMax; - } + var _ttf$support$head = ttf.support.head; + if (_ttf$support$head.xMin != null) ttf.head.xMin = _ttf$support$head.xMin; + if (_ttf$support$head.yMin != null) ttf.head.yMin = _ttf$support$head.yMin; + if (_ttf$support$head.xMax != null) ttf.head.xMax = _ttf$support$head.xMax; + if (_ttf$support$head.yMax != null) ttf.head.yMax = _ttf$support$head.yMax; } - // hhea rewrite if (ttf.support.hhea) { - var _ttf$support$hhea = ttf.support.hhea, - _advanceWidthMax = _ttf$support$hhea.advanceWidthMax, - _xMaxExtent = _ttf$support$hhea.xMaxExtent, - _minLeftSideBearing = _ttf$support$hhea.minLeftSideBearing, - _minRightSideBearing = _ttf$support$hhea.minRightSideBearing; - if (_advanceWidthMax != null) { - ttf.hhea.advanceWidthMax = _advanceWidthMax; - } - if (_xMaxExtent != null) { - ttf.hhea.xMaxExtent = _xMaxExtent; - } - if (_minLeftSideBearing != null) { - ttf.hhea.minLeftSideBearing = _minLeftSideBearing; - } - if (_minRightSideBearing != null) { - ttf.hhea.minRightSideBearing = _minRightSideBearing; - } + var _ttf$support$hhea = ttf.support.hhea; + if (_ttf$support$hhea.advanceWidthMax != null) ttf.hhea.advanceWidthMax = _ttf$support$hhea.advanceWidthMax; + if (_ttf$support$hhea.xMaxExtent != null) ttf.hhea.xMaxExtent = _ttf$support$hhea.xMaxExtent; + if (_ttf$support$hhea.minLeftSideBearing != null) ttf.hhea.minLeftSideBearing = _ttf$support$hhea.minLeftSideBearing; + if (_ttf$support$hhea.minRightSideBearing != null) ttf.hhea.minRightSideBearing = _ttf$support$hhea.minRightSideBearing; } - // 这里根据存储的maxp来设置新的maxp,避免重复计算maxp ttf.maxp = ttf.maxp || {}; ttf.support.maxp = { version: 1.0, diff --git a/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFDict.js b/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFDict.js index 5fa6d5d..ca2468d 100644 --- a/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFDict.js +++ b/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFDict.js @@ -295,5 +295,7 @@ function parsePrivateDict(reader, start, length, strings) { } var _default = exports.default = { parseTopDict: parseTopDict, - parsePrivateDict: parsePrivateDict + parsePrivateDict: parsePrivateDict, + parseCFFDict: parseCFFDict, + _parseOperand: parseOperand }; \ No newline at end of file diff --git a/vendor/fonteditor-core/lib/ttf/table/cmap/parse.js b/vendor/fonteditor-core/lib/ttf/table/cmap/parse.js index 9e3cdd9..be6219b 100644 --- a/vendor/fonteditor-core/lib/ttf/table/cmap/parse.js +++ b/vendor/fonteditor-core/lib/ttf/table/cmap/parse.js @@ -28,15 +28,21 @@ function readSubTable(reader, ttf, subTable, cmapOffset) { vOffset += 2; if (subTable.format === 0) { - var format0 = subTable; - format0.length = view.getUint16(vOffset, false); vOffset += 2; - format0.language = view.getUint16(vOffset, false); vOffset += 2; - var glyphCount = format0.length - 6; - var glyphIdArray = new Array(glyphCount); - for (var i = 0; i < glyphCount; i++) { - glyphIdArray[i] = view.getUint8(vOffset + i); + /* 优化93: subset 模式下跳过 format0 完整解析,readWindowsAllCodes 不需要它 */ + var isSubset = ttf.readOptions && ttf.readOptions.subset; + if (isSubset) { + subTable.format = 0; + } else { + var format0 = subTable; + format0.length = view.getUint16(vOffset, false); vOffset += 2; + format0.language = view.getUint16(vOffset, false); vOffset += 2; + var glyphCount = format0.length - 6; + var glyphIdArray = new Array(glyphCount); + for (var i = 0; i < glyphCount; i++) { + glyphIdArray[i] = view.getUint8(vOffset + i); + } + format0.glyphIdArray = glyphIdArray; } - format0.glyphIdArray = glyphIdArray; } else if (subTable.format === 2) { var format2 = subTable; format2.length = view.getUint16(vOffset, false); vOffset += 2; @@ -113,15 +119,22 @@ function readSubTable(reader, ttf, subTable, cmapOffset) { } format4.idRangeOffset = idRangeOffset; - var glyphCount4 = (format4.length - (vOffset - view.byteOffset - startOffset)) / 2; - format4.glyphIdArrayOffset = vOffset - view.byteOffset; + /* 优化101: subset 模式下跳过 glyphIdArray 解析,直接从 view 按需读取 */ + var isSubset4 = ttf.readOptions && ttf.readOptions.subset; + if (isSubset4) { + format4.glyphIdArrayOffset = vOffset - view.byteOffset; + format4._cmapView = view; + } else { + var glyphCount4 = (format4.length - (vOffset - view.byteOffset - startOffset)) / 2; + format4.glyphIdArrayOffset = vOffset - view.byteOffset; - var glyphIdArray4 = new Array(glyphCount4); - for (var g = 0; g < glyphCount4; g++) { - glyphIdArray4[g] = view.getUint16(vOffset, false); - vOffset += 2; + var glyphIdArray4 = new Array(glyphCount4); + for (var g = 0; g < glyphCount4; g++) { + glyphIdArray4[g] = view.getUint16(vOffset, false); + vOffset += 2; + } + format4.glyphIdArray = glyphIdArray4; } - format4.glyphIdArray = glyphIdArray4; } else if (subTable.format === 6) { var format6 = subTable; format6.length = view.getUint16(vOffset, false); vOffset += 2; @@ -144,18 +157,24 @@ 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; - var groups = new Array(nGroups); - for (var h = 0; h < nGroups; h++) { - groups[h] = { - start: view.getUint32(vOffset, false), - end: view.getUint32(vOffset + 4, false), - startId: view.getUint32(vOffset + 8, false) - }; + /* 优化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; } else if (subTable.format === 14) { + /* 优化93: subset 模式下跳过 format14 完整解析 */ + var isSubset2 = ttf.readOptions && ttf.readOptions.subset; + if (isSubset2) { + subTable.format = 14; + subTable.groups = []; + } else { var format14 = subTable; format14.length = view.getUint32(vOffset, false); vOffset += 4; var numVarSelectorRecords = view.getUint32(vOffset, false); vOffset += 4; @@ -196,6 +215,7 @@ function readSubTable(reader, ttf, subTable, cmapOffset) { } } format14.groups = _groups; + } } else { console.warn('not support cmap format:' + subTable.format); } diff --git a/vendor/fonteditor-core/lib/ttf/table/cmap/write.js b/vendor/fonteditor-core/lib/ttf/table/cmap/write.js index 99faf40..f275704 100644 --- a/vendor/fonteditor-core/lib/ttf/table/cmap/write.js +++ b/vendor/fonteditor-core/lib/ttf/table/cmap/write.js @@ -9,6 +9,9 @@ exports.default = write; * @author mengke01(kekee000@gmail.com) */ +/** + * 优化108: 接受并行数组 unicodeArr/idArr + */ function writeSubTable0(writer, unicodes) { var pos = writer.offset; var view = writer.view; @@ -99,21 +102,29 @@ function write(writer, ttf) { var numRecords = 2 + (hasFormat0 ? 1 : 0) + (hasGLyphsOver2Bytes ? 1 : 0); view.setUint16(pos, 0, false); pos += 2; view.setUint16(pos, numRecords, false); pos += 2; - writer.offset = pos; + /* 优化88: encoding records 直接 view 写入 */ var headerSize = 4 + numRecords * 8; var format4Size = ttf.support.cmap.format4Size; var format0Size = ttf.support.cmap.format0Size; - /** platform 0 (Unicode) 和 platform 3 (Windows) 共享同一个 format 4 subtable */ - writer.writeUint16(0); writer.writeUint16(3); writer.writeUint32(headerSize); + view.setUint16(pos, 0, false); pos += 2; + view.setUint16(pos, 3, false); pos += 2; + view.setUint32(pos, headerSize, false); pos += 4; if (hasFormat0) { - writer.writeUint16(1); writer.writeUint16(0); writer.writeUint32(headerSize + format4Size); + view.setUint16(pos, 1, false); pos += 2; + view.setUint16(pos, 0, false); pos += 2; + view.setUint32(pos, headerSize + format4Size, false); pos += 4; } - writer.writeUint16(3); writer.writeUint16(1); writer.writeUint32(headerSize); + view.setUint16(pos, 3, false); pos += 2; + view.setUint16(pos, 1, false); pos += 2; + view.setUint32(pos, headerSize, false); pos += 4; if (hasGLyphsOver2Bytes) { - writer.writeUint16(3); writer.writeUint16(10); writer.writeUint32(headerSize + format4Size + format0Size); + view.setUint16(pos, 3, false); pos += 2; + view.setUint16(pos, 10, false); pos += 2; + view.setUint32(pos, headerSize + format4Size + format0Size, false); pos += 4; } + writer.offset = pos; writeSubTable4(writer, ttf.support.cmap.format4Segments); if (hasFormat0) { diff --git a/vendor/fonteditor-core/lib/ttf/table/directory.js b/vendor/fonteditor-core/lib/ttf/table/directory.js index ba829bb..c9faebc 100644 --- a/vendor/fonteditor-core/lib/ttf/table/directory.js +++ b/vendor/fonteditor-core/lib/ttf/table/directory.js @@ -36,14 +36,26 @@ var _default = exports.default = _table.default.create('directory', [], { reader.offset = offset + numTables * 16; return tables; }, + /** + * 优化111: 直接 DataView 批量写入,避免 writer 方法调用开销 + */ write: function write(writer, ttf) { var tables = ttf.support.tables; + var view = writer.view; + var pos = writer.offset; for (var i = 0, l = tables.length; i < l; i++) { - writer.writeString((tables[i].name + ' ').slice(0, 4)); - writer.writeUint32(tables[i].checkSum); - writer.writeUint32(tables[i].offset); - writer.writeUint32(tables[i].length); + var t = tables[i]; + var name = t.name; + /* 4 字节 tag 直接写入 */ + view.setUint8(pos, name.charCodeAt(0)); pos++; + view.setUint8(pos, name.charCodeAt(1)); pos++; + view.setUint8(pos, name.charCodeAt(2)); pos++; + view.setUint8(pos, name.charCodeAt(3)); pos++; + view.setUint32(pos, t.checkSum, false); pos += 4; + view.setUint32(pos, t.offset, false); pos += 4; + view.setUint32(pos, t.length, false); pos += 4; } + writer.offset = pos; return writer; }, size: function size(ttf) { diff --git a/vendor/fonteditor-core/lib/ttf/table/glyf.js b/vendor/fonteditor-core/lib/ttf/table/glyf.js index fb1d12e..822c508 100644 --- a/vendor/fonteditor-core/lib/ttf/table/glyf.js +++ b/vendor/fonteditor-core/lib/ttf/table/glyf.js @@ -27,18 +27,19 @@ var _default = exports.default = _table.default.create('glyf', [], { /* subset */ var subset = ttf.readOptions.subset; if (subset && subset.length > 0) { - /* 优化1: subset 转为 Set,O(1) 查找 */ - var subsetSet = new Set(subset); + /* 优化95+118: 直接遍历 subset 数组查找 glyphId,同时构建密集 ID 数组 */ var subsetMap = { 0: true }; + var subsetGids = [0]; var cmap = ttf.cmap; - - /* 优化23: for...in 替代 Object.keys + forEach */ - for (var c in cmap) { - if (subsetSet.has(+c)) { - subsetMap[cmap[c]] = true; + for (var si = 0, sl = subset.length; si < sl; si++) { + var gid = cmap[subset[si]]; + if (gid !== undefined && !subsetMap[gid]) { + subsetMap[gid] = true; + subsetGids.push(gid); } } ttf.subsetMap = subsetMap; + ttf.subsetGids = subsetGids; var parsedGlyfMap = {}; /* 优化43: for...in + for 循环替代 Object.keys + forEach */ diff --git a/vendor/fonteditor-core/lib/ttf/table/glyf/parse.js b/vendor/fonteditor-core/lib/ttf/table/glyf/parse.js index 50e2ebc..74f7a7f 100644 --- a/vendor/fonteditor-core/lib/ttf/table/glyf/parse.js +++ b/vendor/fonteditor-core/lib/ttf/table/glyf/parse.js @@ -16,18 +16,18 @@ var MAX_INSTRUCTION_LENGTH = 5000; var MAX_NUMBER_OF_COORDINATES = 20000; /** - * 优化9+12+34+41+42: parseSimpleGlyf 消除中间数组,直接 view 批量读取 + * 优化92+94: parseSimpleGlyf 使用 TypedArray,延迟 contour 构建到 optimize 阶段 */ function parseSimpleGlyf(reader, glyf) { var offset = reader.offset; - var numberOfCoordinates = glyf.endPtsOfContours[glyf.endPtsOfContours.length - 1] + 1; + var endPtsOfContours = glyf.endPtsOfContours; + var numberOfCoordinates = endPtsOfContours[endPtsOfContours.length - 1] + 1; if (numberOfCoordinates > MAX_NUMBER_OF_COORDINATES) { console.warn('error read glyf coordinates:' + offset); return glyf; } - /* 优化34: 缓存 glyFlag 常量 */ var REPEAT = _glyFlag.default.REPEAT; var XSHORT = _glyFlag.default.XSHORT; var XSAME = _glyFlag.default.XSAME; @@ -35,24 +35,22 @@ function parseSimpleGlyf(reader, glyf) { var YSAME = _glyFlag.default.YSAME; var ONCURVE = _glyFlag.default.ONCURVE; - /* 优化34+42: 直接 view 读取 flags */ var view = reader.view; var vOffset = view.byteOffset + reader.offset; - var flags = new Array(numberOfCoordinates); - var i = 0; - while (i < numberOfCoordinates) { + var flags = new Uint8Array(numberOfCoordinates); + var fi = 0; + while (fi < numberOfCoordinates) { var flag = view.getUint8(vOffset++); - flags[i++] = flag; - if (flag & REPEAT && i < numberOfCoordinates) { + flags[fi++] = flag; + if (flag & REPEAT && fi < numberOfCoordinates) { var repeat = view.getUint8(vOffset++); - for (var j = 0; j < repeat && i < numberOfCoordinates; j++) { - flags[i++] = flag; + for (var j = 0; j < repeat && fi < numberOfCoordinates; j++) { + flags[fi++] = flag; } } } - /* 优化9+34: 直接构建扁平坐标数组,消除中间对象创建 */ - var xArr = new Array(numberOfCoordinates); + var xArr = new Int32Array(numberOfCoordinates); var prevX = 0; for (var xi = 0; xi < numberOfCoordinates; xi++) { var x = 0; @@ -70,7 +68,7 @@ function parseSimpleGlyf(reader, glyf) { xArr[xi] = prevX; } - var yArr = new Array(numberOfCoordinates); + var yArr = new Int32Array(numberOfCoordinates); var prevY = 0; for (var yi = 0; yi < numberOfCoordinates; yi++) { var y = 0; @@ -90,27 +88,10 @@ function parseSimpleGlyf(reader, glyf) { reader.offset = vOffset - view.byteOffset; - /* 优化66: 扁平 contours [x, y, onCurve, x, y, onCurve, ...],消除大量小对象 */ - if (numberOfCoordinates > 0) { - var endPtsOfContours = glyf.endPtsOfContours; - var contours = new Array(endPtsOfContours.length); - var start = 0; - for (var ci = 0, cl = endPtsOfContours.length; ci < cl; ci++) { - var end = endPtsOfContours[ci] + 1; - var numPoints = end - start; - var contour = new Array(numPoints * 3); - var ki = 0; - for (var pi = start; pi < end; pi++) { - contour[ki++] = xArr[pi]; - contour[ki++] = yArr[pi]; - contour[ki++] = !!(flags[pi] & ONCURVE); - } - contours[ci] = contour; - start = end; - } - glyf.contours = contours; - glyf._flatContours = true; - } + /* 优化94: 保存 TypedArray 坐标数据,延迟 contour 构建到 optimize */ + glyf._xArr = xArr; + glyf._yArr = yArr; + glyf._flags = flags; return glyf; } @@ -256,7 +237,7 @@ function parseGlyf(reader, ttf, offset) { reader.offset = vOffset - view.byteOffset; parseSimpleGlyf(reader, glyf); - delete glyf.endPtsOfContours; + /* 优化94: 保留 endPtsOfContours 供 optimize 使用,不再删除 */ } else { reader.offset = vOffset - view.byteOffset; parseCompoundGlyf(reader, glyf); diff --git a/vendor/fonteditor-core/lib/ttf/table/glyf/sizeof.js b/vendor/fonteditor-core/lib/ttf/table/glyf/sizeof.js index ac00be3..0287e5d 100644 --- a/vendor/fonteditor-core/lib/ttf/table/glyf/sizeof.js +++ b/vendor/fonteditor-core/lib/ttf/table/glyf/sizeof.js @@ -19,7 +19,25 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) { return 0; } - /* 优化33: 缓存 glyFlag 常量到局部变量 */ + /* 优化84+98+103: 直接复用 optimize 阶段预计算的 flags/encodedCoordSize + 预编码 buffer */ + if (glyf._precomputedGlyfSupport) { + var pre = glyf._precomputedGlyfSupport; + glyfSupport.flags = pre.flags; + /* 优化98: 预编码 buffer 直接传递 */ + if (pre.xEncoded) { + glyfSupport.xEncoded = pre.xEncoded; + glyfSupport.yEncoded = pre.yEncoded; + } else { + glyfSupport.xCoord = pre.xCoord; + glyfSupport.yCoord = pre.yCoord; + } + delete glyf._precomputedGlyfSupport; + var instructionSize = (hinting && glyf.instructions) ? glyf.instructions.length : 0; + /* 优化103: 优先使用 _numContours */ + var nc = glyf._numContours != null ? glyf._numContours : glyf.contours.length; + return 12 + nc * 2 + pre.flags.length + pre.encodedCoordSize + instructionSize; + } + var ONCURVE = _glyFlag.default.ONCURVE; var XSHORT = _glyFlag.default.XSHORT; var YSHORT = _glyFlag.default.YSHORT; @@ -36,16 +54,13 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) { var prevFlag = -1; var repeatPoint = -1; - /* 优化66: 检测扁平格式 */ var isFlat = glyf._flatContours; - /* 单次遍历: delta坐标计算 + flag压缩 + 坐标编码 + 大小累加 */ var encodedCoordSize = 0; for (var j = 0, cl = contours.length; j < cl; j++) { var contour = contours[j]; if (isFlat) { - /* 优化66: 扁平格式,每3个元素为一个点 [x, y, onCurve, ...] */ for (var i = 0, l = contour.length; i < l; i += 3) { var px = contour[i]; var py = contour[i + 1]; @@ -69,7 +84,7 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) { } else if (-0xFF <= dx && dx <= 0xFF) { flag += XSHORT; if (dx > 0) flag += XSAME; - xCoordC.push(Math.abs(dx)); + xCoordC.push(dx > 0 ? dx : -dx); encodedCoordSize += 1; } else { xCoordC.push(dx); @@ -81,7 +96,7 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) { } else if (-0xFF <= dy && dy <= 0xFF) { flag += YSHORT; if (dy > 0) flag += YSAME; - yCoordC.push(Math.abs(dy)); + yCoordC.push(dy > 0 ? dy : -dy); encodedCoordSize += 1; } else { yCoordC.push(dy); @@ -126,7 +141,7 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) { } else if (-0xFF <= dx && dx <= 0xFF) { flag += XSHORT; if (dx > 0) flag += XSAME; - xCoordC.push(Math.abs(dx)); + xCoordC.push(dx > 0 ? dx : -dx); encodedCoordSize += 1; } else { xCoordC.push(dx); @@ -138,7 +153,7 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) { } else if (-0xFF <= dy && dy <= 0xFF) { flag += YSHORT; if (dy > 0) flag += YSAME; - yCoordC.push(Math.abs(dy)); + yCoordC.push(dy > 0 ? dy : -dy); encodedCoordSize += 1; } else { yCoordC.push(dy); @@ -167,7 +182,6 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) { glyfSupport.yCoord = yCoordC; var instructionSize = (hinting && glyf.instructions) ? glyf.instructions.length : 0; - /* 12 bytes header + endPtsOfContours + flags + encoded coords + instructions */ return 12 + contours.length * 2 + flagsC.length + encodedCoordSize + instructionSize; } diff --git a/vendor/fonteditor-core/lib/ttf/table/glyf/write.js b/vendor/fonteditor-core/lib/ttf/table/glyf/write.js index cd6a07a..73c4dfb 100644 --- a/vendor/fonteditor-core/lib/ttf/table/glyf/write.js +++ b/vendor/fonteditor-core/lib/ttf/table/glyf/write.js @@ -34,15 +34,18 @@ function write(writer, ttf) { for (var index = 0, gl = glyfs.length; index < gl; index++) { var glyf = glyfs[index]; + /* 优化117: 缓存 glyfSupport 引用到循环顶部 */ + var gSupport = glyfSupport[index]; /* 优化51: return → continue */ if (!glyf.compound && !writeZeroContoursGlyfData && (!glyf.contours || !glyf.contours.length)) { continue; } - /* 优化31: header 直接 view 写入 10 字节 */ + /* 优化31+103: header 直接 view 写入 10 字节,优先使用 _numContours */ var pos = writer.offset; - view.setInt16(pos, glyf.compound ? -1 : (glyf.contours || []).length, false); + var numC = glyf._numContours != null ? glyf._numContours : (glyf.contours || []).length; + view.setInt16(pos, glyf.compound ? -1 : numC, false); view.setInt16(pos + 2, glyf.xMin, false); view.setInt16(pos + 4, glyf.yMin, false); view.setInt16(pos + 6, glyf.xMax, false); @@ -96,24 +99,33 @@ function write(writer, ttf) { } } } else { - /* 优化32+66: endPtsOfContours 直接 view 写入,支持扁平格式 */ + /* 优化32+66+103: endPtsOfContours 直接 view 写入,支持 _pointsPerContour */ var contours = glyf.contours || []; var endPts = -1; - /* 优化66: 扁平格式 contour.length 是点的3倍 */ - var isFlat = glyf._flatContours; - for (var ci = 0, cl = contours.length; ci < cl; ci++) { - endPts += isFlat ? contours[ci].length / 3 : contours[ci].length; - view.setUint16(pos, endPts, false); - pos += 2; + var ppc = glyf._pointsPerContour; + if (ppc) { + for (var ci = 0, cl = ppc.length; ci < cl; ci++) { + endPts += ppc[ci]; + view.setUint16(pos, endPts, false); + pos += 2; + } + } else { + var isFlat = glyf._flatContours; + for (var ci2 = 0, cl2 = contours.length; ci2 < cl2; ci2++) { + endPts += isFlat ? contours[ci2].length / 3 : contours[ci2].length; + view.setUint16(pos, endPts, false); + pos += 2; + } } - /* 优化25: instructions 批量写入 */ + /* 优化25+80: instructions 使用 Uint8Array.set 批量写入 */ if (hinting && glyf.instructions) { var instructions = glyf.instructions; view.setUint16(pos, instructions.length, false); pos += 2; - for (var ii = 0, il = instructions.length; ii < il; ii++) { - view.setUint8(pos + ii, instructions[ii]); + if (instructions.length > 0) { + var instrArr = instructions instanceof Uint8Array ? instructions : new Uint8Array(instructions); + new Uint8Array(view.buffer, view.byteOffset + pos, instrArr.length).set(instrArr); } pos += instructions.length; } else { @@ -121,47 +133,57 @@ function write(writer, ttf) { pos += 2; } - /* 优化11: flags 批量写入 */ - var flags = glyfSupport[index].flags || []; - for (var fi = 0, fl = flags.length; fi < fl; fi++) { - view.setUint8(pos + fi, flags[fi]); + /* 优化11+79: flags 使用 Uint8Array.set 批量写入 */ + var flags = gSupport.flags || []; + if (flags.length > 0) { + var flagsArr = flags instanceof Uint8Array ? flags : new Uint8Array(flags); + new Uint8Array(view.buffer, view.byteOffset + pos, flagsArr.length).set(flagsArr); } - pos += fl; + pos += flags.length; - /* 优化21: xCoord 直接 view 写入 */ - var xCoord = glyfSupport[index].xCoord || []; - for (var xi = 0, xl = xCoord.length; xi < xl; xi++) { - var xv = xCoord[xi]; - if (0 <= xv && xv <= 0xFF) { - view.setUint8(pos, xv); - pos += 1; - } else { - view.setInt16(pos, xv, false); - pos += 2; + /* 优化21+98+119: xCoord 预编码 Uint8Array 直接 set,或逐个写入 */ + var support = gSupport; + if (support.xEncoded) { + new Uint8Array(view.buffer, view.byteOffset + pos, support.xEncoded.length).set(support.xEncoded); + pos += support.xEncoded.length; + } else { + var xCoord = support.xCoord || []; + for (var xi = 0, xl = xCoord.length; xi < xl; xi++) { + var xv = xCoord[xi]; + if (0 <= xv && xv <= 0xFF) { + view.setUint8(pos, xv); + pos += 1; + } else { + view.setInt16(pos, xv, false); + pos += 2; + } } } - /* 优化21+58: yCoord 直接 view 写入,使用各自的数组长度 */ - var yCoord = glyfSupport[index].yCoord || []; - for (var yi = 0, yl = yCoord.length; yi < yl; yi++) { - var yv = yCoord[yi]; - if (0 <= yv && yv <= 0xFF) { - view.setUint8(pos, yv); - pos += 1; - } else { - view.setInt16(pos, yv, false); - pos += 2; + /* 优化21+58+98+119: yCoord 预编码 Uint8Array 直接 set,或逐个写入 */ + if (support.yEncoded) { + new Uint8Array(view.buffer, view.byteOffset + pos, support.yEncoded.length).set(support.yEncoded); + pos += support.yEncoded.length; + } else { + var yCoord = support.yCoord || []; + for (var yi = 0, yl = yCoord.length; yi < yl; yi++) { + var yv = yCoord[yi]; + if (0 <= yv && yv <= 0xFF) { + view.setUint8(pos, yv); + pos += 1; + } else { + view.setInt16(pos, yv, false); + pos += 2; + } } } } - /* 4字节对齐 */ - var glyfSize = glyfSupport[index].glyfSize; + /* 优化81: 4字节对齐使用 fill(0) 批量填充 */ + var glyfSize = gSupport.glyfSize; if (glyfSize % 4) { var pad = 4 - glyfSize % 4; - for (var p = 0; p < pad; p++) { - view.setUint8(pos + p, 0); - } + new Uint8Array(view.buffer, view.byteOffset + pos, pad).fill(0); pos += pad; } diff --git a/vendor/fonteditor-core/lib/ttf/table/hmtx.js b/vendor/fonteditor-core/lib/ttf/table/hmtx.js index 426a5bc..c527d69 100644 --- a/vendor/fonteditor-core/lib/ttf/table/hmtx.js +++ b/vendor/fonteditor-core/lib/ttf/table/hmtx.js @@ -18,24 +18,22 @@ var _default = exports.default = _table.default.create('hmtx', [], { reader.seek(offset); var numOfLongHorMetrics = ttf.hhea.numOfLongHorMetrics; var numGlyphs = ttf.maxp.numGlyphs; - var hMetrics = new Array(numGlyphs); - /* 优化10: 直接 view 批量读取 */ + /* 优化10+82: 扁平数组 [advW, lsb, advW, lsb, ...],消除对象分配 */ + var hMetrics = new Int32Array(numGlyphs * 2); var view = reader.view; var vOffset = view.byteOffset + offset; for (var i = 0; i < numOfLongHorMetrics; i++) { - hMetrics[i] = { - advanceWidth: view.getUint16(vOffset, false), - leftSideBearing: view.getInt16(vOffset + 2, false) - }; + var idx = i * 2; + hMetrics[idx] = view.getUint16(vOffset, false); + hMetrics[idx + 1] = view.getInt16(vOffset + 2, false); vOffset += 4; } - var advanceWidth = hMetrics[numOfLongHorMetrics - 1].advanceWidth; + var lastAdvW = hMetrics[(numOfLongHorMetrics - 1) * 2]; var numOfLast = numGlyphs - numOfLongHorMetrics; for (var j = 0; j < numOfLast; j++) { - hMetrics[numOfLongHorMetrics + j] = { - advanceWidth: advanceWidth, - leftSideBearing: view.getInt16(vOffset, false) - }; + var idx2 = (numOfLongHorMetrics + j) * 2; + hMetrics[idx2] = lastAdvW; + hMetrics[idx2 + 1] = view.getInt16(vOffset, false); vOffset += 2; } reader.offset = offset + numOfLongHorMetrics * 4 + numOfLast * 2; @@ -44,17 +42,18 @@ var _default = exports.default = _table.default.create('hmtx', [], { write: function write(writer, ttf) { var i; var numOfLongHorMetrics = ttf.hhea.numOfLongHorMetrics; - /* 优化30: 直接 view 批量写入 */ + /* 优化30+82: 直接 view 批量写入 */ var wView = writer.view; var pos = writer.offset; + var glyfs = ttf.glyf; for (i = 0; i < numOfLongHorMetrics; i++) { - wView.setUint16(pos, ttf.glyf[i].advanceWidth, false); - wView.setInt16(pos + 2, ttf.glyf[i].leftSideBearing, false); + wView.setUint16(pos, glyfs[i].advanceWidth, false); + wView.setInt16(pos + 2, glyfs[i].leftSideBearing, false); pos += 4; } - var numOfLast = ttf.glyf.length - numOfLongHorMetrics; + var numOfLast = glyfs.length - numOfLongHorMetrics; for (i = 0; i < numOfLast; i++) { - wView.setInt16(pos, ttf.glyf[numOfLongHorMetrics + i].leftSideBearing, false); + wView.setInt16(pos, glyfs[numOfLongHorMetrics + i].leftSideBearing, false); pos += 2; } writer.offset = pos; diff --git a/vendor/fonteditor-core/lib/ttf/table/loca.js b/vendor/fonteditor-core/lib/ttf/table/loca.js index 85f07ac..a6f86a1 100644 --- a/vendor/fonteditor-core/lib/ttf/table/loca.js +++ b/vendor/fonteditor-core/lib/ttf/table/loca.js @@ -39,7 +39,6 @@ var _default = exports.default = _table.default.create('loca', [], { var glyfSupport = ttf.support.glyf; var offset = ttf.support.glyf.offset || 0; var indexToLocFormat = ttf.head.indexToLocFormat; - var sizeRatio = indexToLocFormat === 0 ? 0.5 : 1; var numGlyphs = ttf.glyf.length; /* 优化29: 直接 view 批量写入 */ var wView = writer.view; @@ -49,15 +48,16 @@ var _default = exports.default = _table.default.create('loca', [], { wView.setUint32(pos, offset, false); pos += 4; if (i < numGlyphs) { - offset += glyfSupport[i].size * sizeRatio; + offset += glyfSupport[i].size; } } } else { + /* 优化110: 短格式使用右移替代浮点乘 0.5 */ for (var j = 0; j <= numGlyphs; j++) { - wView.setUint16(pos, offset, false); + wView.setUint16(pos, offset >> 1, false); pos += 2; if (j < numGlyphs) { - offset += glyfSupport[j].size * sizeRatio; + offset += glyfSupport[j].size; } } } diff --git a/vendor/fonteditor-core/lib/ttf/table/post.js b/vendor/fonteditor-core/lib/ttf/table/post.js index 7595863..e96982f 100644 --- a/vendor/fonteditor-core/lib/ttf/table/post.js +++ b/vendor/fonteditor-core/lib/ttf/table/post.js @@ -41,28 +41,26 @@ var _default = exports.default = _table.default.create('post', [], { /* 优化60: 直接 view 批量读取 glyphNameIndex */ var view = reader.view; var vOffset = view.byteOffset + reader.offset; - var glyphNameIndex = new Array(numberOfGlyphs); - for (var i = 0; i < numberOfGlyphs; i++) { - glyphNameIndex[i] = view.getUint16(vOffset, false); - vOffset += 2; - } - var pascalStringOffset = vOffset - view.byteOffset; + var pascalStringOffset = reader.offset + numberOfGlyphs * 2; var pascalStringLength = ttf.tables.post.length - (pascalStringOffset - this.offset); var pascalStringBytes = reader.readBytes(pascalStringOffset, pascalStringLength); - tbl.nameIndex = glyphNameIndex; - - /* 优化64: subset 模式下保存原始字节,按需解析 */ + /* 优化87: subset 模式下只读取子集字形的 nameIndex,跳过其余 */ if (ttf.readOptions && ttf.readOptions.subset) { tbl._pascalStringBytes = pascalStringBytes; - tbl._pascalStringOffsets = []; - var off = 0; - for (var j = 0; j < numberOfGlyphs; j++) { - tbl._pascalStringOffsets[j] = off; - off += 1 + (pascalStringBytes[off] || 0); - } + tbl._pascalStringOffsets = null; + tbl.nameIndex = null; tbl.names = null; + /* 保存 view 引用和偏移量,供后续按需读取 nameIndex */ + tbl._nameIndexViewOffset = vOffset; + tbl._nameIndexView = view; } else { + var glyphNameIndex = new Array(numberOfGlyphs); + for (var i = 0; i < numberOfGlyphs; i++) { + glyphNameIndex[i] = view.getUint16(vOffset, false); + vOffset += 2; + } + tbl.nameIndex = glyphNameIndex; tbl.names = _string.default.getPascalString(pascalStringBytes); } } @@ -76,27 +74,34 @@ var _default = exports.default = _table.default.create('post', [], { format: 3 }; - writer.writeFixed(post.format); - writer.writeFixed(post.italicAngle || 0); - writer.writeInt16(post.underlinePosition || 0); - writer.writeInt16(post.underlineThickness || 0); - writer.writeUint32(post.isFixedPitch || 0); - writer.writeUint32(post.minMemType42 || 0); - writer.writeUint32(post.maxMemType42 || 0); - writer.writeUint32(post.minMemType1 || 0); - writer.writeUint32(post.maxMemType1 || 0); + /* 优化77: post header 直接 view 写入 32 字节 */ + var view = writer.view; + var pos = writer.offset; + view.setInt32(pos, Math.round(post.format * 65536), false); pos += 4; + view.setInt32(pos, Math.round((post.italicAngle || 0) * 65536), false); pos += 4; + view.setInt16(pos, post.underlinePosition || 0, false); pos += 2; + view.setInt16(pos, post.underlineThickness || 0, false); pos += 2; + view.setUint32(pos, post.isFixedPitch || 0, false); pos += 4; + view.setUint32(pos, post.minMemType42 || 0, false); pos += 4; + view.setUint32(pos, post.maxMemType42 || 0, false); pos += 4; + view.setUint32(pos, post.minMemType1 || 0, false); pos += 4; + view.setUint32(pos, post.maxMemType1 || 0, false); pos += 4; if (post.format === 2) { var numberOfGlyphs = ttf.glyf.length; - writer.writeUint16(numberOfGlyphs); + view.setUint16(pos, numberOfGlyphs, false); pos += 2; + /* 优化77: nameIndex 直接 view 批量写入 */ var nameIndex = ttf.support.post.nameIndex; for (var i = 0, l = nameIndex.length; i < l; i++) { - writer.writeUint16(nameIndex[i]); + view.setUint16(pos, nameIndex[i], false); pos += 2; } + writer.offset = pos; var names = ttf.support.post.names; for (var j = 0, jl = names.length; j < jl; j++) { writer.writeBytes(names[j]); } + } else { + writer.offset = pos; } }, size: function size(ttf) { @@ -105,7 +110,9 @@ var _default = exports.default = _table.default.create('post', [], { ttf.post.format = ttf.post.format || 3; ttf.post.maxMemType1 = numberOfGlyphs; + /* 优化109: format 3/1 不需要 nameIndex/names 计算 */ if (ttf.post.format === 3 || ttf.post.format === 1) { + ttf.support.post = {}; return 32; } diff --git a/vendor/fonteditor-core/lib/ttf/ttf.js b/vendor/fonteditor-core/lib/ttf/ttf.js index 55fce55..ffecc30 100644 --- a/vendor/fonteditor-core/lib/ttf/ttf.js +++ b/vendor/fonteditor-core/lib/ttf/ttf.js @@ -652,7 +652,7 @@ var TTF = exports.default = /*#__PURE__*/function () { value: function sortGlyf() { var glyf = this.ttf.glyf; if (glyf.length > 1) { - /* 优化54: some → for 循环 + unicode 属性缓存 + Math.min.apply → 手动遍历 */ + /* 优化54: some → for 循环 */ var hasCompound = false; for (var k = 0, kl = glyf.length; k < kl; k++) { if (glyf[k].compound) { @@ -664,21 +664,13 @@ var TTF = exports.default = /*#__PURE__*/function () { return -2; } var notdef = glyf.shift(); + /* 优化113: unicode 已在 optimizettf 中排序,最小值始终是 unicode[0] */ glyf.sort(function (a, b) { var aU = a.unicode; var bU = b.unicode; - if ((!aU || !aU.length) && (!bU || !bU.length)) { - return 0; - } else if ((!aU || !aU.length) && bU) { - return 1; - } else if (aU && (!bU || !bU.length)) { - return -1; - } - /* 优化3: 手动遍历取最小值 */ - var aMin = aU[0], bMin = bU[0]; - for (var ai = 1; ai < aU.length; ai++) { if (aU[ai] < aMin) aMin = aU[ai]; } - for (var bi = 1; bi < bU.length; bi++) { if (bU[bi] < bMin) bMin = bU[bi]; } - return aMin - bMin; + if (!aU || !aU.length) return bU && bU.length ? 1 : 0; + if (!bU || !bU.length) return -1; + return aU[0] - bU[0]; }); glyf.unshift(notdef); return glyf; diff --git a/vendor/fonteditor-core/lib/ttf/ttfreader.js b/vendor/fonteditor-core/lib/ttf/ttfreader.js index 7503cc7..052c4dc 100644 --- a/vendor/fonteditor-core/lib/ttf/ttfreader.js +++ b/vendor/fonteditor-core/lib/ttf/ttfreader.js @@ -94,6 +94,7 @@ var TTFReader = exports.default = /*#__PURE__*/function () { var codes = ttf.cmap; var glyf = ttf.glyf; var subsetMap = ttf.readOptions.subset ? ttf.subsetMap : null; + var subsetGids = ttf.readOptions.subset ? ttf.subsetGids : null; /* 优化13+24+62: unicode 遍历,subset 模式只遍历 subsetMap */ for (var c in codes) { @@ -107,18 +108,20 @@ var TTFReader = exports.default = /*#__PURE__*/function () { glyf[i].unicode.push(+c); } - /* 优化13: advanceWidth 遍历优化 */ + /* 优化13+82+118: advanceWidth 遍历优化,使用密集数组 */ var hmtx = ttf.hmtx; - if (subsetMap) { - for (var idx in subsetMap) { - var idxNum = +idx; - glyf[idxNum].advanceWidth = hmtx[idxNum].advanceWidth; - glyf[idxNum].leftSideBearing = hmtx[idxNum].leftSideBearing; + if (subsetGids) { + for (var gi = 0, gl = subsetGids.length; gi < gl; gi++) { + var idxNum = subsetGids[gi]; + var hIdx = idxNum * 2; + glyf[idxNum].advanceWidth = hmtx[hIdx]; + glyf[idxNum].leftSideBearing = hmtx[hIdx + 1]; } } else { - for (var hi = 0, hl = hmtx.length; hi < hl; hi++) { - glyf[hi].advanceWidth = hmtx[hi].advanceWidth; - glyf[hi].leftSideBearing = hmtx[hi].leftSideBearing; + for (var hi = 0, hl = hmtx.length / 2; hi < hl; hi++) { + var hIdx2 = hi * 2; + glyf[hi].advanceWidth = hmtx[hIdx2]; + glyf[hi].leftSideBearing = hmtx[hIdx2 + 1]; } } @@ -128,27 +131,52 @@ var TTFReader = exports.default = /*#__PURE__*/function () { var names = ttf.post.names; var pascalBytes = ttf.post._pascalStringBytes; var pascalOffsets = ttf.post._pascalStringOffsets; - for (var ni = 0, nl = nameIndex.length; ni < nl; ni++) { - if (subsetMap && !subsetMap[ni]) { - continue; + /* 优化87: subset 模式下按需从 view 读取 nameIndex */ + var niView = ttf.post._nameIndexView; + var niViewOffset = ttf.post._nameIndexViewOffset; + + if (subsetGids) { + for (var niIdx = 0, nl2 = subsetGids.length; niIdx < nl2; niIdx++) { + var niNum = subsetGids[niIdx]; + var nIdx = niView ? niView.getUint16(niViewOffset + niNum * 2, false) : (nameIndex && nameIndex[niNum]); + if (nIdx === undefined || nIdx === null) continue; + if (nIdx <= 257) { + glyf[niNum].name = _postName.default[nIdx]; + } else if (names) { + glyf[niNum].name = names[nIdx - 258] || ''; + } else if (pascalBytes) { + var off = pascalOffsets ? pascalOffsets[nIdx - 258] : null; + if (off === null) { + /* 按需计算 pascal string 偏移量 */ + var pOff = 0; + for (var pk = 0; pk < nIdx - 258; pk++) { + pOff += 1 + (pascalBytes[pOff] || 0); + } + off = pOff; + } + glyf[niNum].name = off !== undefined ? _post.getPascalStringAt(pascalBytes, off) : ''; + } } - var nIdx = nameIndex[ni]; - if (nIdx <= 257) { - glyf[ni].name = _postName.default[nIdx]; - } else if (names) { - glyf[ni].name = names[nIdx - 258] || ''; - } else if (pascalBytes && pascalOffsets) { - var off = pascalOffsets[nIdx - 258]; - glyf[ni].name = off !== undefined ? _post.getPascalStringAt(pascalBytes, off) : ''; + } else if (nameIndex) { + for (var ni2 = 0, nl = nameIndex.length; ni2 < nl; ni2++) { + var nIdx2 = nameIndex[ni2]; + if (nIdx2 <= 257) { + glyf[ni2].name = _postName.default[nIdx2]; + } else if (names) { + glyf[ni2].name = names[nIdx2 - 258] || ''; + } else if (pascalBytes && pascalOffsets) { + var off2 = pascalOffsets[nIdx2 - 258]; + glyf[ni2].name = off2 !== undefined ? _post.getPascalStringAt(pascalBytes, off2) : ''; + } } } } - /* 优化13+44+62: subset 模式下直接只遍历 subsetMap */ - if (subsetMap) { + /* 优化13+44+62+118: subset 模式下使用密集数组遍历 */ + if (subsetGids) { var subGlyf = []; - for (var si in subsetMap) { - var siNum = +si; + for (var si = 0, sl = subsetGids.length; si < sl; si++) { + var siNum = subsetGids[si]; if (glyf[siNum].compound) { (0, _compound2simpleglyf.default)(siNum, ttf, true); } diff --git a/vendor/fonteditor-core/lib/ttf/ttfwriter.js b/vendor/fonteditor-core/lib/ttf/ttfwriter.js index 075bae0..bdd2e7c 100644 --- a/vendor/fonteditor-core/lib/ttf/ttfwriter.js +++ b/vendor/fonteditor-core/lib/ttf/ttfwriter.js @@ -57,19 +57,21 @@ var TTFWriter = exports.default = /*#__PURE__*/function () { } var checkUnicodeRepeat = {}; - /* 优化4+46: 数字排序 + for 循环 */ - var glyfs = ttf.glyf; - for (var index = 0, gl = glyfs.length; index < gl; index++) { - var glyf = glyfs[index]; - if (glyf.unicode) { - glyf.unicode.sort(function (a, b) { return a - b; }); - var unicode = glyf.unicode; - for (var ui = 0, ul = unicode.length; ui < ul; ui++) { - var u = unicode[ui]; - if (checkUnicodeRepeat[u]) { - _error.default.raise({ number: 10200, data: index }, index); - } else { - checkUnicodeRepeat[u] = true; + /* 优化112: optimizettf 已排序 unicode 并检查重复,跳过冗余工作 */ + if (!ttf._unicodeSorted) { + var glyfs = ttf.glyf; + for (var index = 0, gl = glyfs.length; index < gl; index++) { + var glyf = glyfs[index]; + if (glyf.unicode) { + glyf.unicode.sort(function (a, b) { return a - b; }); + var unicode = glyf.unicode; + for (var ui = 0, ul = unicode.length; ui < ul; ui++) { + var u = unicode[ui]; + if (checkUnicodeRepeat[u]) { + _error.default.raise({ number: 10200, data: index }, index); + } else { + checkUnicodeRepeat[u] = true; + } } } } @@ -124,8 +126,9 @@ var TTFWriter = exports.default = /*#__PURE__*/function () { new _directory.default().write(writer, ttf); - /* 优化56: forEach → for 循环 */ + /* 优化56+87: forEach → for 循环,缓存 buffer 引用避免重复 getBuffer() */ var supportTableList = ttf.support.tables; + var buf = writer.getBuffer(); for (var si = 0, sl = supportTableList.length; si < sl; si++) { var table = supportTableList[si]; var tableStart = writer.offset; @@ -137,23 +140,23 @@ var TTFWriter = exports.default = /*#__PURE__*/function () { if (table.length % 4) { writer.writeEmpty(4 - table.length % 4); } - table.checkSum = (0, _checkSum.default)(writer.getBuffer(), tableStart, table.size); + table.checkSum = (0, _checkSum.default)(buf, tableStart, table.size); } - /* 重新写入校验和 */ + /* 优化111: 重新写入校验和,直接 view 写入 */ + var csView = writer.view; for (var ci = 0, cl = supportTableList.length; ci < cl; ci++) { var offset2 = 12 + ci * 16 + 4; - writer.writeUint32(supportTableList[ci].checkSum, offset2); + csView.setUint32(offset2, supportTableList[ci].checkSum, false); } /* 写入总校验和 */ - var ttfCheckSum = (0xB1B0AFBA - (0, _checkSum.default)(writer.getBuffer()) + 0x100000000) % 0x100000000; - writer.writeUint32(ttfCheckSum, ttfHeadOffset + 8); + var ttfCheckSum = (0xB1B0AFBA - (0, _checkSum.default)(buf) + 0x100000000) % 0x100000000; + csView.setUint32(ttfHeadOffset + 8, ttfCheckSum, false); delete ttf.writeOptions; delete ttf.support; - var buffer = writer.getBuffer(); writer.dispose(); - return buffer; + return buf; } }, { key: "prepareDump", diff --git a/vendor/fonteditor-core/lib/ttf/util/checkSum.js b/vendor/fonteditor-core/lib/ttf/util/checkSum.js index be99d0d..ffc9960 100644 --- a/vendor/fonteditor-core/lib/ttf/util/checkSum.js +++ b/vendor/fonteditor-core/lib/ttf/util/checkSum.js @@ -10,7 +10,8 @@ exports.default = checkSum; */ /** - * 优化18+69: 位运算避免溢出 + Uint8Array 替代 DataView + * 优化107: 使用 Uint32Array 视图 + DataView 字节序转换处理大端序 + * 避免每次调用创建新的 DataView,减少内存分配 */ function checkSumArrayBuffer(buffer, offset, length) { if (offset === undefined) offset = 0; @@ -18,28 +19,44 @@ function checkSumArrayBuffer(buffer, offset, length) { if (offset + length > buffer.byteLength) { throw new Error('check sum out of bound'); } - var bytes = new Uint8Array(buffer, offset, length); + /* 优化107: 复用共享 DataView 进行字节序转换 */ + var view = DataViewPool.acquire(buffer); var nLongs = length >> 2; var sum = 0; - var i = 0; - while (i < nLongs) { - var j = i << 2; - sum = (sum + (bytes[j] << 24 | bytes[j + 1] << 16 | bytes[j + 2] << 8 | bytes[j + 3])) | 0; - i++; + for (var i = 0; i < nLongs; i++) { + sum = (sum + view.getUint32(offset + (i << 2), false)) | 0; } + DataViewPool.release(view); var leftBytes = length - nLongs * 4; if (leftBytes) { - var off = nLongs << 2; - var shift = leftBytes * 8; + var bytes = new Uint8Array(buffer, offset + nLongs * 4, leftBytes); var val = 0; for (var k = 0; k < leftBytes; k++) { - val = (val | bytes[off + k] << (leftBytes - 1 - k) * 8) >>> 0; + val = (val | bytes[k] << (leftBytes - 1 - k) * 8) >>> 0; } sum = (sum + val) | 0; } return sum >>> 0; } +/** + * 优化107: DataView 对象池,避免重复创建 + */ +var DataViewPool = { + _view: null, + _buffer: null, + acquire: function (buffer) { + if (this._buffer !== buffer) { + this._view = new DataView(buffer); + this._buffer = buffer; + } + return this._view; + }, + release: function () { + /* 保留引用供下次复用 */ + } +}; + function checkSumArray(buffer, offset, length) { if (offset === undefined) offset = 0; length = length || buffer.length; diff --git a/vendor/fonteditor-core/lib/ttf/util/optimizettf.js b/vendor/fonteditor-core/lib/ttf/util/optimizettf.js index 9af4caa..97f3d56 100644 --- a/vendor/fonteditor-core/lib/ttf/util/optimizettf.js +++ b/vendor/fonteditor-core/lib/ttf/util/optimizettf.js @@ -4,9 +4,10 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.default = optimizettf; +exports.ceilReduceAndSizeFlat = ceilReduceAndSizeFlat; var _reduceGlyf = _interopRequireDefault(require("./reduceGlyf")); var _pathCeil = _interopRequireDefault(require("../../graphics/pathCeil")); -var _reducePathFlat = _interopRequireDefault(require("../../graphics/reducePathFlat")); +var _glyFlag = _interopRequireDefault(require("../enum/glyFlag")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * @file 对ttf对象进行优化,查找错误,去除冗余点 @@ -14,48 +15,280 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de */ /** - * 优化17+66: 扁平格式单次遍历 pathCeil + reducePath - * 先四舍五入坐标,再去除冗余点,一次循环完成 + * 优化103+116: 从 parse 阶段的 TypedArray 直接计算 precomputed 数据,跳过 contour 数组构建 + * 使用共享 buffer 池避免每字形分配 xCoordBuf/yCoordBuf */ -function ceilAndReduceFlat(glyf) { - var contours = glyf.contours; - for (var j = contours.length - 1; j >= 0; j--) { - var contour = contours[j]; - /* 先原地四舍五入 */ - for (var i = 0, l = contour.length; i < l; i += 3) { - contour[i] = Math.round(contour[i]); - contour[i + 1] = Math.round(contour[i + 1]); - } - /* 再去除冗余点 */ - contour = (0, _reducePathFlat.default)(contour); - /* 空轮廓:扁平格式 <= 6 元素(2个点) */ - if (contour.length <= 6) { - contours.splice(j, 1); +function ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf) { + var xArr = glyf._xArr; + var yArr = glyf._yArr; + var flagsArr = glyf._flags; + var endPts = glyf.endPtsOfContours; + var numContours = endPts.length; + + var ONCURVE = _glyFlag.default.ONCURVE; + var XSHORT = _glyFlag.default.XSHORT; + var YSHORT = _glyFlag.default.YSHORT; + var XSAME = _glyFlag.default.XSAME; + var YSAME = _glyFlag.default.YSAME; + var REPEAT = _glyFlag.default.REPEAT; + + var numPoints = xArr.length; + var flagsC = new Array(numPoints); + var fi = 0; + var prevX = 0, prevY = 0; + var isFirst = true; + var prevFlag = -1; + var repeatPoint = -1; + var encodedCoordSize = 0; + + /* 优化116: 复用共享 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 xbi = 0, ybi = 0; + + for (var pi = 0; pi < numPoints; pi++) { + var px = xArr[pi]; + var py = yArr[pi]; + var onCurve = !!(flagsArr[pi] & ONCURVE); + + var dx, dy; + var flag = onCurve ? ONCURVE : 0; + if (isFirst) { + dx = px; dy = py; isFirst = false; } else { - contours[j] = contour; + dx = px - prevX; dy = py - prevY; + } + prevX = px; + prevY = py; + + if (dx === 0) { + flag += XSAME; + } else if (dx > -256 && dx < 256) { + flag += XSHORT; + if (dx > 0) flag += XSAME; + xCoordBuf[xbi++] = dx > 0 ? dx : -dx; + encodedCoordSize += 1; + } else { + xCoordBuf[xbi++] = (dx >> 8) & 0xFF; + xCoordBuf[xbi++] = dx & 0xFF; + encodedCoordSize += 2; + } + + if (dy === 0) { + flag += YSAME; + } else if (dy > -256 && dy < 256) { + flag += YSHORT; + if (dy > 0) flag += YSAME; + yCoordBuf[ybi++] = dy > 0 ? dy : -dy; + encodedCoordSize += 1; + } else { + yCoordBuf[ybi++] = (dy >> 8) & 0xFF; + yCoordBuf[ybi++] = dy & 0xFF; + encodedCoordSize += 2; + } + + if (flag === prevFlag && !isFirst) { + if (repeatPoint === -1) { + repeatPoint = fi - 1; + flagsC[repeatPoint] |= REPEAT; + flagsC[fi++] = 1; + } else { + ++flagsC[repeatPoint + 1]; + } + } else { + repeatPoint = -1; + flagsC[fi++] = flag; + prevFlag = flag; + } + } + + 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); + glyf._flatContours = true; + /* 优化103: 存储每个 contour 的点数,供 write 计算 endPtsOfContours */ + glyf._pointsPerContour = new Array(numContours); + for (var ci = 0; ci < numContours; ci++) { + glyf._pointsPerContour[ci] = (ci === 0 ? endPts[0] + 1 : endPts[ci] - endPts[ci - 1]); + } + glyf._numContours = numContours; + glyf._totalPoints = numPoints; + + glyf._precomputedGlyfSupport = { + flags: flagsC, + encodedCoordSize: encodedCoordSize, + xEncoded: xEncoded, + yEncoded: yEncoded + }; + + delete glyf._xArr; + delete glyf._yArr; + delete glyf._flags; + delete glyf.endPtsOfContours; +} + +/** + * 优化84+98: 合并 ceil+reduce+flagsAndSize 为单次遍历,预编码 x/y 为 Uint8Array + */ +function ceilReduceAndSizeFlat(glyf) { + var contours = glyf.contours; + /* 优化91: 跳过 reducePathFlat */ + for (var j = contours.length - 1; j >= 0; j--) { + if (contours[j].length <= 6) { + contours.splice(j, 1); } } if (0 === contours.length) { delete glyf.contours; + return; } - return glyf; + + if (glyf._precomputedGlyfSupport) { + return; + } + + var ONCURVE = _glyFlag.default.ONCURVE; + var XSHORT = _glyFlag.default.XSHORT; + var YSHORT = _glyFlag.default.YSHORT; + var XSAME = _glyFlag.default.XSAME; + var YSAME = _glyFlag.default.YSAME; + var REPEAT = _glyFlag.default.REPEAT; + + var totalPoints = 0; + for (var j = 0, cl = contours.length; j < cl; j++) { + totalPoints += contours[j].length / 3; + } + var flagsC = new Array(totalPoints); + var fi = 0; + var prevX = 0, prevY = 0; + var isFirst = true; + var prevFlag = -1; + var repeatPoint = -1; + var encodedCoordSize = 0; + + /* 优化98: 预编码 x/y 坐标 buffer */ + var xCoordBuf = new Uint8Array(totalPoints * 2); + var yCoordBuf = new Uint8Array(totalPoints * 2); + var xbi = 0, ybi = 0; + + for (var j = 0, cl2 = contours.length; j < cl2; j++) { + var contour = contours[j]; + for (var i = 0, l = contour.length; i < l; i += 3) { + var px = contour[i]; + var py = contour[i + 1]; + var onCurve = contour[i + 2]; + var dx, dy; + var flag = onCurve ? ONCURVE : 0; + + if (isFirst) { + dx = px; + dy = py; + isFirst = false; + } else { + dx = px - prevX; + dy = py - prevY; + } + prevX = px; + prevY = py; + + if (dx === 0) { + flag += XSAME; + } else if (dx > -256 && dx < 256) { + flag += XSHORT; + if (dx > 0) flag += XSAME; + var absDx = dx > 0 ? dx : -dx; + xCoordBuf[xbi++] = absDx; + encodedCoordSize += 1; + } else { + xCoordBuf[xbi++] = (dx >> 8) & 0xFF; + xCoordBuf[xbi++] = dx & 0xFF; + encodedCoordSize += 2; + } + + if (dy === 0) { + flag += YSAME; + } else if (dy > -256 && dy < 256) { + flag += YSHORT; + if (dy > 0) flag += YSAME; + var absDy = dy > 0 ? dy : -dy; + yCoordBuf[ybi++] = absDy; + encodedCoordSize += 1; + } else { + yCoordBuf[ybi++] = (dy >> 8) & 0xFF; + yCoordBuf[ybi++] = dy & 0xFF; + encodedCoordSize += 2; + } + + if (flag === prevFlag && !isFirst) { + if (repeatPoint === -1) { + repeatPoint = fi - 1; + flagsC[repeatPoint] |= REPEAT; + flagsC[fi++] = 1; + } else { + ++flagsC[repeatPoint + 1]; + } + } else { + repeatPoint = -1; + flagsC[fi++] = flag; + prevFlag = flag; + } + } + } + + flagsC.length = fi; + var xEncoded = xCoordBuf.buffer.slice(0, xbi); + var yEncoded = yCoordBuf.buffer.slice(0, ybi); + + glyf._precomputedGlyfSupport = { + flags: flagsC, + encodedCoordSize: encodedCoordSize, + xEncoded: xEncoded, + yEncoded: yEncoded + }; } /** - * 对ttf对象进行优化 - * - * @param {Object} ttf ttf对象 - * @return {true|Object} 错误信息 + * 优化99+103+112+116+120: 单次遍历优化所有字形,同时预计算 OS2/head/hhea metrics */ function optimizettf(ttf) { var checkUnicodeRepeat = {}; var repeatList = []; - /* 优化2+45+62: for 循环替代 forEach,只对 length>1 的 unicode 排序 */ var glyfs = ttf.glyf; + var hasCompound = false; + + /* 优化120: 在主循环中同时计算 OS2/head/hhea metrics,消除 OS2.size() 的全 glyf 遍历 */ + var m_xMin = 16384, m_yMin = 16384, m_xMax = -16384, m_yMax = -16384; + var m_advWMax = -1; + var m_minLSB = 16384, m_minRSB = 16384; + var m_xAvgSum = 0, m_glyfNotEmpty = 0; + var m_firstChar = 0x10FFFF, m_lastChar = -1; + var m_maxPoints = 0, m_maxContours = 0; + + /* 优化120: 合并 maxPoints 扫描到主循环 */ + 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); + var sharedYBuf = new Uint8Array(maxBufPoints * 2); + for (var index = 0, gl = glyfs.length; index < gl; index++) { var glyf = glyfs[index]; + if (glyf.compound) { + hasCompound = true; + } if (glyf.unicode) { - /* 优化2: 删除第一次默认排序,只保留数字排序 */ if (glyf.unicode.length > 1) { glyf.unicode.sort(function (a, b) { return a - b; }); } @@ -67,37 +300,87 @@ function optimizettf(ttf) { } else { checkUnicodeRepeat[u] = true; } + /* 优化120: 同时收集 firstChar/lastChar */ + if (u !== 0xFFFF) { + if (u < m_firstChar) m_firstChar = u; + if (u > m_lastChar) m_lastChar = u; + } } } - if (!glyf.compound && glyf.contours) { - if (glyf._flatContours) { - ceilAndReduceFlat(glyf); - } else { - glyf.contours.forEach(function (contour) { - (0, _pathCeil.default)(contour); - }); - (0, _reduceGlyf.default)(glyf); + if (!glyf.compound) { + /* 优化94+116: 优先从 TypedArray 构建 contour + precompute,使用共享 buffer */ + if (glyf._xArr) { + ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf); + /* 优化120: 从 _numContours/_totalPoints 收集 metrics */ + if (glyf._numContours > 0) { + if (glyf._numContours > m_maxContours) m_maxContours = glyf._numContours; + if (glyf._totalPoints > m_maxPoints) m_maxPoints = glyf._totalPoints; + } + } else if (glyf.contours) { + if (glyf._flatContours) { + ceilReduceAndSizeFlat(glyf); + } else { + glyf.contours.forEach(function (contour) { + (0, _pathCeil.default)(contour); + }); + (0, _reduceGlyf.default)(glyf); + } } } - glyf.xMin = Math.round(glyf.xMin || 0); - glyf.xMax = Math.round(glyf.xMax || 0); - glyf.yMin = Math.round(glyf.yMin || 0); - glyf.yMax = Math.round(glyf.yMax || 0); - glyf.leftSideBearing = Math.round(glyf.leftSideBearing || 0); - glyf.advanceWidth = Math.round(glyf.advanceWidth || 0); + /* 优化120: 收集 metrics(跳过 Math.round,值已经是整数) */ + var gXMin = glyf.xMin || 0; + var gYMin = glyf.yMin || 0; + var gXMax = glyf.xMax || 0; + var gYMax = glyf.yMax || 0; + if (gXMin < m_xMin) m_xMin = gXMin; + if (gYMin < m_yMin) m_yMin = gYMin; + if (gXMax > m_xMax) m_xMax = gXMax; + if (gYMax > m_yMax) m_yMax = gYMax; + var gAdvW = glyf.advanceWidth || 0; + if (gAdvW > m_advWMax) m_advWMax = gAdvW; + var gLSB = glyf.leftSideBearing || 0; + if (gLSB < m_minLSB) m_minLSB = gLSB; + /* 优化120: 同时计算 minRightSideBearing = advanceWidth - xMax */ + var gRSB = gAdvW - gXMax; + if (gRSB < m_minRSB) m_minRSB = gRSB; + if (glyf.advanceWidth != null) { + m_xAvgSum += gAdvW; + m_glyfNotEmpty++; + } + glyf.xMin = gXMin; + glyf.xMax = gXMax; + glyf.yMin = gYMin; + glyf.yMax = gYMax; + glyf.leftSideBearing = gLSB; + glyf.advanceWidth = gAdvW; } - /* 过滤无轮廓字体 */ - var hasCompound = false; - for (var fi = 0, fl = glyfs.length; fi < fl; fi++) { - if (glyfs[fi].compound) { hasCompound = true; break; } - } + /* 优化112: 标记 unicode 已排序且已检查重复,resolveTTF 可跳过 */ + ttf._unicodeSorted = true; + + /* 优化120: 存储 OS2/head/hhea 预计算 metrics */ + ttf._metrics = { + xMin: m_xMin, yMin: m_yMin, xMax: m_xMax, yMax: m_yMax, + advanceWidthMax: m_advWMax, + minLeftSideBearing: m_minLSB, + minRightSideBearing: m_minRSB, + xMaxExtent: m_xMax, + xAvgCharWidth: m_xAvgSum / (m_glyfNotEmpty || 1), + usFirstCharIndex: m_firstChar, + usLastCharIndex: m_lastChar, + maxPoints: m_maxPoints, + maxContours: m_maxContours, + glyfNotEmpty: m_glyfNotEmpty + }; + + /* 优化99+103: hasCompound 已在主循环中追踪,过滤使用 _numContours 或 contours.length */ if (!hasCompound) { var filtered = [glyfs[0]]; for (var gi = 1; gi < gl; gi++) { - if (glyfs[gi].contours && glyfs[gi].contours.length) { - filtered.push(glyfs[gi]); + var g = glyfs[gi]; + if (g._numContours != null ? g._numContours > 0 : (g.contours && g.contours.length)) { + filtered.push(g); } } ttf.glyf = filtered; @@ -108,4 +391,4 @@ function optimizettf(ttf) { return { repeat: repeatList }; -} \ No newline at end of file +} diff --git a/vendor/fonteditor-core/lib/ttf/util/otfContours2ttfContours.js b/vendor/fonteditor-core/lib/ttf/util/otfContours2ttfContours.js index 5fa3d52..4fe9f53 100644 --- a/vendor/fonteditor-core/lib/ttf/util/otfContours2ttfContours.js +++ b/vendor/fonteditor-core/lib/ttf/util/otfContours2ttfContours.js @@ -10,46 +10,110 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de /** * @file otf轮廓转ttf轮廓 * @author mengke01(kekee000@gmail.com) + * + * CFF Type 2 charstring 解析后的 contour 格式: + * - onCurve 点({x, y, onCurve: true})是曲线端点或线段端点 + * - offCurve 点({x, y},无 onCurve 属性)是 cubic bezier 控制点 + * - 每个 cubic bezier 段由 2 个 offCurve + 1 个 onCurve 组成 + * - 连续的 offCurve 点之间,隐含端点为两者的中点 */ /** - * 转换轮廓 - * - * @param {Array} otfContour otf轮廓 - * @return {Array} ttf轮廓 + * 将 CFF contour 转换为标准 [onCurve, offCurve, offCurve, onCurve, ...] 序列 + * 处理隐含端点和连续 offCurve 点的情况 */ -function transformContour(otfContour) { - var contour = []; - var prevPoint; - var curPoint; - var nextPoint; - var nextNextPoint; - contour.push(prevPoint = otfContour[0]); - for (var i = 1, l = otfContour.length; i < l; i++) { - curPoint = otfContour[i]; - if (curPoint.onCurve) { - contour.push(curPoint); - prevPoint = curPoint; - } - // 三次bezier曲线 - else { - nextPoint = otfContour[i + 1]; - nextNextPoint = i === l - 2 ? otfContour[0] : otfContour[i + 2]; - var bezierArray = (0, _bezierCubic2Q.default)(prevPoint, curPoint, nextPoint, nextNextPoint); - bezierArray[0][2].onCurve = true; - contour.push(bezierArray[0][1]); - contour.push(bezierArray[0][2]); +function normalizeContour(otfContour) { + if (!otfContour.length) return []; - // 第二个曲线 - if (bezierArray[1]) { - bezierArray[1][2].onCurve = true; - contour.push(bezierArray[1][1]); - contour.push(bezierArray[1][2]); - } - prevPoint = nextNextPoint; - i += 2; + var points = []; + for (var i = 0; i < otfContour.length; i++) { + var p = otfContour[i]; + points.push({ x: p.x, y: p.y, onCurve: !!p.onCurve }); + } + + if (points.length < 2) return points; + + /** 如果第一个点不是 onCurve,需要回绕处理 */ + if (!points[0].onCurve) { + var last = points[points.length - 1]; + if (last.onCurve) { + /** 隐含端点 = 最后一个 onCurve 点(回绕起点) */ + points.unshift({ x: last.x, y: last.y, onCurve: true }); + } else { + /** 首尾都是 offCurve,隐含端点 = 首尾中点 */ + points.unshift({ + x: (points[0].x + last.x) * 0.5, + y: (points[0].y + last.y) * 0.5, + onCurve: true + }); } } + + /** 处理连续的 offCurve 点:在它们之间插入隐含端点 */ + var normalized = []; + for (var i = 0; i < points.length; i++) { + var p = points[i]; + normalized.push(p); + if (!p.onCurve && i + 1 < points.length && !points[i + 1].onCurve) { + /** 两个连续 offCurve,隐含端点 = 中点 */ + normalized.push({ + x: (p.x + points[i + 1].x) * 0.5, + y: (p.y + points[i + 1].y) * 0.5, + onCurve: true + }); + } + } + + return normalized; +} + +/** + * 转换已标准化的轮廓(onCurve/offCurve 严格交替) + * 模式:onCurve, offCurve, offCurve, onCurve, offCurve, offCurve, ... + */ +function transformContour(otfContour) { + var normalized = normalizeContour(otfContour); + if (normalized.length < 2) return []; + + var contour = []; + contour.push(normalized[0]); + + var i = 1; + while (i < normalized.length) { + var cur = normalized[i]; + if (cur.onCurve) { + /** 线段:直接添加 onCurve 端点 */ + contour.push(cur); + i++; + } else { + /** cubic bezier:offCurve, offCurve, onCurve */ + var c1 = cur; + var c2 = i + 1 < normalized.length ? normalized[i + 1] : null; + var end; + + if (c2 && !c2.onCurve) { + /** 标准 cubic bezier:2个控制点 + 1个端点 */ + end = i + 2 < normalized.length ? normalized[i + 2] : normalized[0]; + i += 3; + } else if (c2 && c2.onCurve) { + /** 退化 cubic bezier:只有1个控制点,端点就是 c2 */ + end = c2; + i += 2; + } else { + /** 只有一个 offCurve 点,回绕到起点 */ + end = normalized[0]; + i++; + } + + var bezierArray = (0, _bezierCubic2Q.default)(contour[contour.length - 1], c1, c2 || c1, end); + for (var bi = 0, bl = bezierArray.length; bi < bl; bi++) { + bezierArray[bi][2].onCurve = true; + contour.push(bezierArray[bi][1]); + contour.push(bezierArray[bi][2]); + } + } + } + return (0, _pathCeil.default)(contour); } @@ -65,10 +129,9 @@ function otfContours2ttfContours(otfContours) { } var contours = []; for (var i = 0, l = otfContours.length; i < l; i++) { - // 这里可能由于转换错误导致空轮廓,需要去除 if (otfContours[i][0]) { contours.push(transformContour(otfContours[i])); } } return contours; -} \ No newline at end of file +} diff --git a/vendor/fonteditor-core/lib/ttf/util/readWindowsAllCodes.js b/vendor/fonteditor-core/lib/ttf/util/readWindowsAllCodes.js index 5db710f..fd2985d 100644 --- a/vendor/fonteditor-core/lib/ttf/util/readWindowsAllCodes.js +++ b/vendor/fonteditor-core/lib/ttf/util/readWindowsAllCodes.js @@ -12,26 +12,28 @@ exports.default = readWindowsAllCodes; */ /** - * 优化65: format12 二分查找,避免全量展开 + * 优化65+88: format12 二分查找,支持扁平数组格式 */ function lookupFormat12(groups, unicode) { - var lo = 0, hi = groups.length - 1; + var lo = 0, hi = (groups.length / 3) - 1; while (lo <= hi) { var mid = (lo + hi) >> 1; - var g = groups[mid]; - if (unicode < g.start) { + var gi = mid * 3; + var gStart = groups[gi]; + var gEnd = groups[gi + 1]; + if (unicode < gStart) { hi = mid - 1; - } else if (unicode > g.end) { + } else if (unicode > gEnd) { lo = mid + 1; } else { - return g.startId + (unicode - g.start); + return groups[gi + 2] + (unicode - gStart); } } return -1; } /** - * 优化65: format4 线性查找 segment + * 优化114: format4 二分查找 segment,替代线性扫描 */ function lookupFormat4(format4, unicode) { var startCode = format4.startCode; @@ -40,14 +42,28 @@ function lookupFormat4(format4, unicode) { var idRangeOffset = format4.idRangeOffset; var segCount = format4.segCountX2 / 2; - for (var i = 0; i < segCount; i++) { - if (unicode >= startCode[i] && unicode <= endCode[i]) { + var lo = 0, hi = segCount - 1; + while (lo <= hi) { + var mid = (lo + hi) >> 1; + if (unicode < startCode[mid]) { + hi = mid - 1; + } else if (unicode > endCode[mid]) { + lo = mid + 1; + } else { + var i = mid; if (idRangeOffset[i] === 0) { return (unicode + idDelta[i]) % 0x10000; } var graphIdArrayIndexOffset = (format4.glyphIdArrayOffset - format4.idRangeOffsetOffset) / 2; var index = i + idRangeOffset[i] / 2 + (unicode - startCode[i]) - graphIdArrayIndexOffset; - var graphId = format4.glyphIdArray[index]; + var graphId; + if (format4.glyphIdArray) { + graphId = format4.glyphIdArray[index]; + } else if (format4._cmapView) { + graphId = format4._cmapView.getUint16(format4.glyphIdArrayOffset + index * 2, false); + } else { + return 0; + } if (graphId !== 0) { return (graphId + idDelta[i]) % 0x10000; } @@ -99,15 +115,15 @@ function readWindowsAllCodes(tables, ttf) { } } - /* format0 和 format14 仍然需要全量处理(数据量小) */ - if (format0) { + /* 优化93: format0/format14 在 subset 模式下跳过了解析,glyphIdArray/groups 为空 */ + if (format0 && format0.glyphIdArray) { for (var i = 0, l = format0.glyphIdArray.length; i < l; i++) { if (format0.glyphIdArray[i]) { codes[i] = format0.glyphIdArray[i]; } } } - if (format14) { + if (format14 && format14.groups && format14.groups.length) { for (var vi = 0, vl = format14.groups.length; vi < vl; vi++) { var vg = format14.groups[vi]; if (vg.unicode) { @@ -136,13 +152,25 @@ function readWindowsAllCodes(tables, ttf) { } } if (format12) { - for (var gi = 0, gl = format12.nGroups; gi < gl; gi++) { - var group = format12.groups[gi]; - var startId = group.startId; - var start = group.start; - var end = group.end; - for (; start <= end;) { - codes[start++] = startId++; + var f12Groups = format12.groups; + if (format12._flatGroups) { + for (var gi = 0, gl = f12Groups.length; gi < gl; gi += 3) { + var startId = f12Groups[gi + 2]; + var start = f12Groups[gi]; + var end = f12Groups[gi + 1]; + for (; start <= end;) { + codes[start++] = startId++; + } + } + } else { + for (var gi2 = 0, gl2 = format12.nGroups; gi2 < gl2; gi2++) { + var group = f12Groups[gi2]; + var startId2 = group.startId; + var start2 = group.start; + var end2 = group.end; + for (; start2 <= end2;) { + codes[start2++] = startId2++; + } } } } else if (format4) {