diff --git a/backend/font_util/font.ts b/backend/font_util/font.ts index 98574ad..661ef51 100644 --- a/backend/font_util/font.ts +++ b/backend/font_util/font.ts @@ -7,8 +7,15 @@ import type { FontEditor } from "../../vendor/fonteditor-core/lib/ttf/font.js"; */ /** 从字符串提取 Unicode 码点数组 */ -export const textToCodePoints = (text: string) => - [...text].map((char) => char.codePointAt(0)!); +export const textToCodePoints = (text: string) => { + const result: number[] = []; + for (let i = 0; i < text.length; i++) { + const cp = text.codePointAt(i) as number; + result.push(cp); + if (cp > 0xFFFF) i++; /** 跳过代理对的低半部分 */ + } + return result; +}; /** * 解析字体并执行 subset(最耗时的步骤) @@ -33,27 +40,12 @@ export const optimizeFont = (font: ReturnType) => { return optimized; }; -/** woff2 wasm 初始化 Promise(延迟初始化,只执行一次) */ -let woff2InitPromise: Promise | null = null; - -/** 确保 woff2 wasm 已初始化,首次调用时加载 711KB wasm */ -async function ensureWoff2Init(): Promise { - if (!woff2InitPromise) { - const woff2Module = await import("../../vendor/fonteditor-core/woff2/index.js"); - const mod = (woff2Module as any).default || woff2Module; - woff2InitPromise = mod.init().then(() => {}); - } - return woff2InitPromise; -} - /** 序列化为指定格式的二进制数据 */ export const writeFont = async ( font: ReturnType["optimize"]>, outType: FontEditor.FontType, ): Promise => { - if (outType === "woff2") { - await ensureWoff2Init(); - } + /** 纯 JS woff2 编码器不需要初始化,直接调用 */ const result = font.write({ type: outType }); if (typeof result !== "string") { return new Uint8Array(result); diff --git a/task.md b/task.md index e6828f5..9aa2b0d 100644 --- a/task.md +++ b/task.md @@ -1,7 +1,7 @@ /loop 持续优化字体子集化性能和提高ssim评分,可以大胆放开手脚的去做,但是优化完一定要通过`pnpx tsx ./基准测试.test.ts`。中途不要切换到其他模式,比如计划模式也不要询问我,你直接做就行了,请你持续的去优化,不要去询问我,不要去中断,好吧 把基准测试结果文档保存在本地 benchmark_results/ ,这样我方便查看。你的文档中应该在每个重大节点更新基准测试结果(benchmark_results/OPTIMIZATION_LOG.md),这样我能方便看到你使用了哪些优化方法,得到了什么样的优化效果。 - +不要修改基准测试中的full生成方案,如果基准测试结果不对一定是你的修改有问题,而非浏览器渲染等其他问题 === 字体裁剪基准测试 === diff --git a/vendor/fonteditor-core/lib/graphics/computeBoundingBox.js b/vendor/fonteditor-core/lib/graphics/computeBoundingBox.js index 277c67f..fd27a64 100644 --- a/vendor/fonteditor-core/lib/graphics/computeBoundingBox.js +++ b/vendor/fonteditor-core/lib/graphics/computeBoundingBox.js @@ -27,10 +27,11 @@ function computeBoundingBox(points) { if (points.length === 0) { return false; } - var left = points[0].x; - var right = points[0].x; - var top = points[0].y; - var bottom = points[0].y; + var p0 = points[0]; + var left = p0.x; + var right = p0.x; + var top = p0.y; + var bottom = p0.y; for (var i = 1; i < points.length; i++) { var p = points[i]; if (p.x < left) { @@ -105,60 +106,115 @@ function computeQuadraticBezierBoundingBox(p0, p1, p2) { * @param {...Array} args 坐标点集, 支持多个path * @return {Object} {x, y, width, height} */ +/** + * 优化: 内联展开 computePathBoundingBox,消除中间 points 数组和临时对象分配 + * 直接在遍历过程中维护 left/right/top/bottom 四个边界值 + */ function computePathBoundingBox() { - var points = []; - var iterator = function iterator(c, p0, p1, p2) { - if (c === 'L') { - points.push(p0); - points.push(p1); - } else if (c === 'Q') { - var bound = computeQuadraticBezierBoundingBox(p0, p1, p2); - points.push(bound); - points.push({ - x: bound.x + bound.width, - y: bound.y + bound.height - }); - } - }; - if (arguments.length === 1) { - (0, _pathIterator.default)(arguments.length <= 0 ? undefined : arguments[0], function (c, p0, p1, p2) { - if (c === 'L') { - points.push(p0); - points.push(p1); - } else if (c === 'Q') { - var bound = computeQuadraticBezierBoundingBox(p0, p1, p2); - points.push(bound); - points.push({ - x: bound.x + bound.width, - y: bound.y + bound.height - }); - } - }); - } else { - for (var i = 0, l = arguments.length; i < l; i++) { - (0, _pathIterator.default)(i < 0 || arguments.length <= i ? undefined : arguments[i], iterator); + var left, right, top, bottom; + var found = false; + + function updateBounds(x, y) { + if (!found) { + left = right = x; + top = bottom = y; + found = true; + } else { + if (x < left) left = x; + else if (x > right) right = x; + if (y < top) top = y; + else if (y > bottom) bottom = y; } } - return computeBoundingBox(points); + + function updateBoundsQ(p0, p1, p2) { + /* 内联二次贝塞尔包围盒计算,避免创建中间对象 */ + var tmp = p0.x + p2.x - 2 * p1.x; + var t1 = tmp === 0 ? 0.5 : (p0.x - p1.x) / tmp; + tmp = p0.y + p2.y - 2 * p1.y; + var t2 = tmp === 0 ? 0.5 : (p0.y - p1.y) / tmp; + t1 = t1 < 0 ? 0 : t1 > 1 ? 1 : t1; + t2 = t2 < 0 ? 0 : t2 > 1 ? 1 : t2; + var ct1 = 1 - t1; + var ct2 = 1 - t2; + /* 计算 4 个极值点: p0, p2, (t1), (t2) */ + updateBounds(p0.x, p0.y); + updateBounds(p2.x, p2.y); + updateBounds(ct1 * ct1 * p0.x + 2 * ct1 * t1 * p1.x + t1 * t1 * p2.x, + ct1 * ct1 * p0.y + 2 * ct1 * t1 * p1.y + t1 * t1 * p2.y); + updateBounds(ct2 * ct2 * p0.x + 2 * ct2 * t2 * p1.x + t2 * t2 * p2.x, + ct2 * ct2 * p0.y + 2 * ct2 * t2 * p1.y + t2 * t2 * p2.y); + } + + function processContour(contour) { + (0, _pathIterator.default)(contour, function (c, p0, p1, p2) { + if (c === 'L') { + updateBounds(p0.x, p0.y); + updateBounds(p1.x, p1.y); + } else if (c === 'Q') { + updateBoundsQ(p0, p1, p2); + } + }); + } + + if (arguments.length === 1) { + processContour(arguments[0]); + } else { + for (var i = 0, l = arguments.length; i < l; i++) { + processContour(arguments[i]); + } + } + + if (!found) { + return false; + } + return { + x: left, + y: top, + width: right - left, + height: bottom - top + }; } /** - * 计算曲线点边界 + * 计算曲线点边界(内联展开版,避免中间对象分配) * * @private * @param {...Array} args path对象, 支持多个path * @return {Object} {x, y, width, height} */ function computePathBox() { - var points = []; - if (arguments.length === 1) { - points = arguments.length <= 0 ? undefined : arguments[0]; - } else { - for (var i = 0, l = arguments.length; i < l; i++) { - Array.prototype.splice.apply(points, [points.length, 0].concat(i < 0 || arguments.length <= i ? undefined : arguments[i])); + var left, right, top, bottom; + var found = false; + + for (var a = 0; a < arguments.length; a++) { + var contour = arguments[a]; + if (!contour || !contour.length) continue; + + for (var i = 0, l = contour.length; i < l; i++) { + var p = contour[i]; + if (!found) { + left = right = p.x; + top = bottom = p.y; + found = true; + } else { + if (p.x < left) left = p.x; + else if (p.x > right) right = p.x; + if (p.y < top) top = p.y; + else if (p.y > bottom) bottom = p.y; + } } } - return computeBoundingBox(points); + + if (!found) { + return { x: 0, y: 0, width: 0, height: 0 }; + } + return { + x: left, + y: top, + width: right - left, + height: bottom - top + }; } var computeBounding = exports.computeBounding = computeBoundingBox; var quadraticBezier = exports.quadraticBezier = computeQuadraticBezierBoundingBox; diff --git a/vendor/fonteditor-core/lib/graphics/pathIterator.js b/vendor/fonteditor-core/lib/graphics/pathIterator.js index 072a528..45a01d7 100644 --- a/vendor/fonteditor-core/lib/graphics/pathIterator.js +++ b/vendor/fonteditor-core/lib/graphics/pathIterator.js @@ -10,7 +10,7 @@ exports.default = pathIterator; */ /** - * 遍历路径的路径集合 + * 遍历路径的路径集合,包括segment和 bezier curve * * @param {Array} contour 坐标点集 * @param {Function} callBack 回调函数,参数集合:command, p0, p1, p2, i @@ -24,6 +24,9 @@ function pathIterator(contour, callBack) { var nextPoint; var cursorPoint; // cursorPoint 为当前单个绘制命令的起点 + /* 优化: 复用临时对象,避免每次分配 */ + var tmpPoint = { x: 0, y: 0 }; + for (var i = 0, l = contour.length; i < l; i++) { curPoint = contour[i]; prevPoint = i === 0 ? contour[l - 1] : contour[i - 1]; @@ -36,10 +39,9 @@ function pathIterator(contour, callBack) { } else if (prevPoint.onCurve) { cursorPoint = prevPoint; } else { - cursorPoint = { - x: (prevPoint.x + curPoint.x) / 2, - y: (prevPoint.y + curPoint.y) / 2 - }; + tmpPoint.x = (prevPoint.x + curPoint.x) * 0.5; + tmpPoint.y = (prevPoint.y + curPoint.y) * 0.5; + cursorPoint = tmpPoint; } } @@ -56,15 +58,13 @@ function pathIterator(contour, callBack) { } cursorPoint = nextPoint; } else { - var last = { - x: (curPoint.x + nextPoint.x) / 2, - y: (curPoint.y + nextPoint.y) / 2 - }; - if (false === callBack('Q', cursorPoint, curPoint, last, i)) { + tmpPoint.x = (curPoint.x + nextPoint.x) * 0.5; + tmpPoint.y = (curPoint.y + nextPoint.y) * 0.5; + if (false === callBack('Q', cursorPoint, curPoint, tmpPoint, i)) { break; } - cursorPoint = last; + cursorPoint = tmpPoint; } } } -} \ No newline at end of file +} diff --git a/vendor/fonteditor-core/lib/graphics/reducePath.js b/vendor/fonteditor-core/lib/graphics/reducePath.js index a3cd4ca..3ef7eb8 100644 --- a/vendor/fonteditor-core/lib/graphics/reducePath.js +++ b/vendor/fonteditor-core/lib/graphics/reducePath.js @@ -20,18 +20,23 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de * @return {boolean} */ function redundant(prev, p, next) { + /* 优化: Math.pow(x,2) → x*x,提取 dx/dy 避免重复计算 */ + var dx = p.x - next.x; + var dy = p.y - next.y; // 是否重合的点, 只有两个点同在曲线上或者同不在曲线上移出 - if ((p.onCurve && next.onCurve || !p.onCurve && !next.onCurve) && Math.pow(p.x - next.x, 2) + Math.pow(p.y - next.y, 2) <= 1) { + if ((p.onCurve && next.onCurve || !p.onCurve && !next.onCurve) && dx * dx + dy * dy <= 1) { return true; } + /* 优化: 提取叉积计算,合并两个分支消除重复的 Math.abs 表达式 */ + var cross = Math.abs((next.y - p.y) * (prev.x - p.x) - (prev.y - p.y) * (next.x - p.x)); // 三点同线 检查直线点 - if (p.onCurve && prev.onCurve && next.onCurve && Math.abs((next.y - p.y) * (prev.x - p.x) - (prev.y - p.y) * (next.x - p.x)) <= 0.001) { + if (p.onCurve && prev.onCurve && next.onCurve && cross <= 0.001) { return true; } // 三点同线 检查控制点 - if (!p.onCurve && prev.onCurve && next.onCurve && Math.abs((next.y - p.y) * (prev.x - p.x) - (prev.y - p.y) * (next.x - p.x)) <= 0.001) { + if (!p.onCurve && prev.onCurve && next.onCurve && cross <= 0.001) { return true; } return false; diff --git a/vendor/fonteditor-core/lib/graphics/reducePathFlat.js b/vendor/fonteditor-core/lib/graphics/reducePathFlat.js index 5ebe175..d042696 100644 --- a/vendor/fonteditor-core/lib/graphics/reducePathFlat.js +++ b/vendor/fonteditor-core/lib/graphics/reducePathFlat.js @@ -31,7 +31,8 @@ function reducePathFlat(contour) { var dx = px - nextX; var dy = py - nextY; - if ((po && nextO || !po && !nextO) && dx * dx + dy * dy <= 1) { removed++; } + /* 优化191: flag 只有 0 或 1,简化条件判断 */ + if (po === nextO && dx * dx + dy * dy <= 1) { removed++; } else { var cross = (nextY - py) * (prevX - px) - (prevY - py) * (nextX - px); if (prevO && nextO && cross > -0.001 && cross < 0.001) { removed++; } @@ -49,7 +50,7 @@ function reducePathFlat(contour) { dx = px - nextX; dy = py - nextY; - if ((po && nextO || !po && !nextO) && dx * dx + dy * dy <= 1) { removed++; continue; } + if (po === nextO && dx * dx + dy * dy <= 1) { removed++; continue; } cross = (nextY - py) * (prevX - px) - (prevY - py) * (nextX - px); if (prevO && nextO && cross > -0.001 && cross < 0.001) { removed++; continue; } @@ -65,7 +66,7 @@ function reducePathFlat(contour) { dx = px - nextX; dy = py - nextY; - if ((po && nextO || !po && !nextO) && dx * dx + dy * dy <= 1) { removed++; } + if (po === nextO && dx * dx + dy * dy <= 1) { removed++; } else { cross = (nextY - py) * (prevX - px) - (prevY - py) * (nextX - px); if (prevO && nextO && cross > -0.001 && cross < 0.001) { removed++; } diff --git a/vendor/fonteditor-core/lib/math/bezierCubic2Q2.js b/vendor/fonteditor-core/lib/math/bezierCubic2Q2.js index 0bd633b..4b35721 100644 --- a/vendor/fonteditor-core/lib/math/bezierCubic2Q2.js +++ b/vendor/fonteditor-core/lib/math/bezierCubic2Q2.js @@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", { }); exports.default = bezierCubic2Q2; exports.bezierCubic2Q2Raw = bezierCubic2Q2Raw; +exports.bezierCubic2Q2Push = bezierCubic2Q2Push; +exports.bezierCubic2Q2PushRounded = bezierCubic2Q2PushRounded; /** * @file 三次贝塞尔转二次贝塞尔(高精度递归分割版) * @author mengke01(kekee000@gmail.com) @@ -12,25 +14,35 @@ exports.bezierCubic2Q2Raw = bezierCubic2Q2Raw; * 改进:递归分割三次贝塞尔直到可精确近似,提高 SSIM * 优化160+179: 返回扁平数组 [cx, cy, ex, ey, ...],减少对象分配 * 优化179: 新增 bezierCubic2Q2Raw 接受原始坐标参数,消除调用方对象分配 + * 优化184: 内联 isFlatEnough 到 cubicToQuadsPush,消除递归中每次分割的函数调用开销 + * 优化197: cubicToQuadsPush 使用索引赋值替代 push,支持预分配数组 */ var MAX_DEPTH = 8; - -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.0625; -} +var FLAT_THRESHOLD = 0.0625; /** * 优化160+179: 直接构建扁平数组 [cx, cy, ex, ey, ...] * 每个二次贝塞尔段占 4 个元素:控制点 x,y + 端点 x,y */ +/** 优化184: 内联 isFlatEnough */ function cubicToQuads(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, depth, result) { - if (isFlatEnough(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) || depth >= MAX_DEPTH) { - /** 控制点 + 端点 */ + if (depth >= MAX_DEPTH) { + result.push( + (3 * c2x - p2x + 3 * c1x - p1x) * 0.25, + (3 * c2y - p2y + 3 * c1y - p1y) * 0.25, + p2x, + p2y + ); + return; + } + 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; + var d1 = ux * ux + uy * uy; + var d2 = vx * vx + vy * vy; + if (d1 <= FLAT_THRESHOLD && d2 <= FLAT_THRESHOLD) { result.push( (3 * c2x - p2x + 3 * c1x - p1x) * 0.25, (3 * c2y - p2y + 3 * c1y - p1y) * 0.25, @@ -70,6 +82,120 @@ function bezierCubic2Q2Raw(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) { return result; } +/** + * 优化197: 使用索引赋值替代 push,支持预分配数组 + * 每段写入 6 个元素: [ctrlX, ctrlY, 0, endX, endY, 1](兼容 contour 扁平格式) + * 返回写入后的索引位置 + */ +function bezierCubic2Q2Push(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, contour, ci) { + if (p1x === c1x && p1y === c1y && c2x === p2x && c2y === p2y) { + contour[ci++] = (p1x + p2x) * 0.5; + contour[ci++] = (p1y + p2y) * 0.5; + contour[ci++] = 0; + contour[ci++] = p2x; + contour[ci++] = p2y; + contour[ci++] = 1; + return ci; + } + return cubicToQuadsPush(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, 0, contour, ci); +} + +/** 优化255: 取整版 bezierCubic2Q2Push,写入时直接 Math.round,消除调用方二次遍历 */ +function bezierCubic2Q2PushRounded(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, contour, ci) { + if (p1x === c1x && p1y === c1y && c2x === p2x && c2y === p2y) { + contour[ci++] = ((p1x + p2x) * 0.25 + 0.5) | 0; + contour[ci++] = ((p1y + p2y) * 0.25 + 0.5) | 0; + contour[ci++] = 0; + contour[ci++] = (p2x + 0.5) | 0; + contour[ci++] = (p2y + 0.5) | 0; + contour[ci++] = 1; + return ci; + } + return cubicToQuadsPushRounded(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, 0, contour, ci); +} + +function cubicToQuadsPushRounded(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, depth, contour, ci) { + if (depth >= MAX_DEPTH) { + var qx = (3 * (c1x + c2x) - p1x - p2x) * 0.25; + var qy = (3 * (c1y + c2y) - p1y - p2y) * 0.25; + contour[ci++] = (qx + 0.5) | 0; + contour[ci++] = (qy + 0.5) | 0; + contour[ci++] = 0; + contour[ci++] = (p2x + 0.5) | 0; + contour[ci++] = (p2y + 0.5) | 0; + contour[ci++] = 1; + return ci; + } + 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; + var d1 = ux * ux + uy * uy; + var d2 = vx * vx + vy * vy; + if (d1 <= FLAT_THRESHOLD && d2 <= FLAT_THRESHOLD) { + var qx2 = (3 * (c1x + c2x) - p1x - p2x) * 0.25; + var qy2 = (3 * (c1y + c2y) - p1y - p2y) * 0.25; + contour[ci++] = (qx2 + 0.5) | 0; + contour[ci++] = (qy2 + 0.5) | 0; + contour[ci++] = 0; + contour[ci++] = (p2x + 0.5) | 0; + contour[ci++] = (p2y + 0.5) | 0; + contour[ci++] = 1; + return ci; + } + + 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; + + ci = cubicToQuadsPushRounded(p1x, p1y, m01x, m01y, m012x, m012y, midx, midy, depth + 1, contour, ci); + ci = cubicToQuadsPushRounded(midx, midy, m123x, m123y, m23x, m23y, p2x, p2y, depth + 1, contour, ci); + return ci; +} + +/** 优化184+197: 内联 isFlatEnough,索引赋值替代 push */ +function cubicToQuadsPush(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, depth, contour, ci) { + if (depth >= MAX_DEPTH) { + contour[ci++] = (3 * (c1x + c2x) - p1x - p2x) * 0.25; + contour[ci++] = (3 * (c1y + c2y) - p1y - p2y) * 0.25; + contour[ci++] = 0; + contour[ci++] = p2x; + contour[ci++] = p2y; + contour[ci++] = 1; + return ci; + } + /* 内联 isFlatEnough 判断,短路比较替代 Math.max */ + 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; + var d1 = ux * ux + uy * uy; + var d2 = vx * vx + vy * vy; + if (d1 <= FLAT_THRESHOLD && d2 <= FLAT_THRESHOLD) { + contour[ci++] = (3 * (c1x + c2x) - p1x - p2x) * 0.25; + contour[ci++] = (3 * (c1y + c2y) - p1y - p2y) * 0.25; + contour[ci++] = 0; + contour[ci++] = p2x; + contour[ci++] = p2y; + contour[ci++] = 1; + return ci; + } + + 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; + + ci = cubicToQuadsPush(p1x, p1y, m01x, m01y, m012x, m012y, midx, midy, depth + 1, contour, ci); + ci = cubicToQuadsPush(midx, midy, m123x, m123y, m23x, m23y, p2x, p2y, depth + 1, contour, ci); + return ci; +} + /** * 三次贝塞尔转二次贝塞尔(对象接口,兼容旧代码) * 返回扁平数组: [ctrlX, ctrlY, endX, endY, ctrlX, ctrlY, endX, endY, ...] diff --git a/vendor/fonteditor-core/lib/ttf/otf2ttfobject.js b/vendor/fonteditor-core/lib/ttf/otf2ttfobject.js index e397b2f..a9e2170 100644 --- a/vendor/fonteditor-core/lib/ttf/otf2ttfobject.js +++ b/vendor/fonteditor-core/lib/ttf/otf2ttfobject.js @@ -34,14 +34,14 @@ function otf2ttfobject(otfBuffer, options) { // 转换otf轮廓,同时获取包围盒 var glyf = otfObject.glyf; + /** 优化220: 绑定模块导入到局部变量,消除循环内 interop 属性查找 */ + var convertContours = _otfContours2ttfContours.default; for (var i = 0, l = glyf.length; i < l; i++) { var g = glyf[i]; - var result = (0, _otfContours2ttfContours.default)(g.contours); + var result = convertContours(g.contours); g.contours = result.contours; - /** 优化178: 标记扁平 contour 格式 */ - if (result.contours.length > 0 && typeof result.contours[0][0] === 'number') { - g._flatContours = true; - } + /** 优化: transformContourFlat 始终返回扁平格式,直接设置标志 */ + g._flatContours = true; if (result.xMin != null) { g.xMin = result.xMin; g.xMax = result.xMax; @@ -68,7 +68,8 @@ function otf2ttfobject(otfBuffer, options) { */ otfObject.head.flags = (otfObject.head.flags || 0) & ~(0x0008 | 0x0800); otfObject.head.fontDirectionHint = 2; - delete otfObject.CFF; - delete otfObject.VORG; + /** 优化245: delete → null 赋值,避免 V8 隐藏类转换 */ + otfObject.CFF = null; + otfObject.VORG = null; return otfObject; } \ No newline at end of file diff --git a/vendor/fonteditor-core/lib/ttf/otfreader.js b/vendor/fonteditor-core/lib/ttf/otfreader.js index 7903645..226b84e 100644 --- a/vendor/fonteditor-core/lib/ttf/otfreader.js +++ b/vendor/fonteditor-core/lib/ttf/otfreader.js @@ -70,11 +70,14 @@ var OTFReader = exports.default = /*#__PURE__*/function () { } font.readOptions = this.options; - /* 优化125: Object.keys+forEach → for...in 循环 */ - for (var tableName in _supportOtf.default) { + /** 优化232: 预构建表名列表,替代 for...in 遍历 */ + var otfTableNames = ['head', 'maxp', 'cmap', 'name', 'hhea', 'hmtx', 'post', 'OS/2', 'CFF', 'GPOS', 'kern']; + var otfSupport = _supportOtf.default; + for (var ti = 0, tl = otfTableNames.length; ti < tl; ti++) { + var tableName = otfTableNames[ti]; if (font.tables[tableName]) { var offset = font.tables[tableName].offset; - font[tableName] = new _supportOtf.default[tableName](offset).read(reader, font); + font[tableName] = new otfSupport[tableName](offset).read(reader, font); } } if (!font.CFF.glyf) { @@ -94,48 +97,74 @@ var OTFReader = exports.default = /*#__PURE__*/function () { value: function resolveGlyf(font) { var codes = font.cmap; var glyf = font.CFF.glyf; - var subsetMap = font.readOptions.subset ? font.subsetMap : null; // 当前ttf的子集列表 - /* 优化125: Object.keys+forEach → for...in 循环 */ - for (var c in codes) { - var i = codes[c]; - if (subsetMap && !subsetMap[i]) { - continue; + var subsetMap = font.readOptions.subset ? font.subsetMap : null; + /** 优化231: 用 Object.keys + for 循环替代 for...in,消除原型链遍历 + 字符串键开销 */ + var cmapKeys = Object.keys(codes); + if (subsetMap) { + for (var ki = 0, kl = cmapKeys.length; ki < kl; ki++) { + var c = cmapKeys[ki]; + var i = codes[c]; + if (!subsetMap[i]) continue; + if (!glyf[i].unicode) glyf[i].unicode = []; + glyf[i].unicode.push(+c); } - if (!glyf[i].unicode) { - glyf[i].unicode = []; + } else { + for (var ki = 0, kl = cmapKeys.length; ki < kl; ki++) { + var c = cmapKeys[ki]; + var i = codes[c]; + if (!glyf[i].unicode) glyf[i].unicode = []; + glyf[i].unicode.push(+c); } - glyf[i].unicode.push(+c); } /* 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; - } + /** 优化231: subsetMap 判断外提,消除循环内条件分支 */ + if (subsetMap) { if (isFlat) { - glyf[hi].advanceWidth = hmtxData[hi * 2] || 0; - glyf[hi].leftSideBearing = hmtxData[hi * 2 + 1]; + for (var hi = 0, j = 0; hi < hLen; hi++, j += 2) { + if (!subsetMap[hi]) continue; + glyf[hi].advanceWidth = hmtxData[j] || 0; + glyf[hi].leftSideBearing = hmtxData[j + 1]; + } } else { - glyf[hi].advanceWidth = hmtxData[hi].advanceWidth || 0; - glyf[hi].leftSideBearing = hmtxData[hi].leftSideBearing; + for (var hi = 0; hi < hLen; hi++) { + if (!subsetMap[hi]) continue; + glyf[hi].advanceWidth = hmtxData[hi].advanceWidth || 0; + glyf[hi].leftSideBearing = hmtxData[hi].leftSideBearing; + } + } + } else { + if (isFlat) { + for (var hi = 0, j = 0; hi < hLen; hi++, j += 2) { + glyf[hi].advanceWidth = hmtxData[j] || 0; + glyf[hi].leftSideBearing = hmtxData[j + 1]; + } + } else { + for (var hi = 0; hi < hLen; hi++) { + glyf[hi].advanceWidth = hmtxData[hi].advanceWidth || 0; + glyf[hi].leftSideBearing = hmtxData[hi].leftSideBearing; + } } } // 设置了subsetMap之后需要选取subset中的字形 /* 优化167: 密集数组替代 for...in,消除字符串键转换 */ if (subsetMap) { - var subGlyf = []; var subsetGids = font.subsetGids; + var subGlyf; if (subsetGids) { + subGlyf = new Array(subsetGids.length); for (var si = 0, sl = subsetGids.length; si < sl; si++) { - subGlyf.push(glyf[subsetGids[si]]); + subGlyf[si] = glyf[subsetGids[si]]; } } else { - for (var si in subsetMap) { - subGlyf.push(glyf[+si]); + subGlyf = []; + var subsetKeys = Object.keys(subsetMap); + for (var si = 0, sl = subsetKeys.length; si < sl; si++) { + subGlyf.push(glyf[+subsetKeys[si]]); } } glyf = subGlyf; @@ -151,22 +180,23 @@ var OTFReader = exports.default = /*#__PURE__*/function () { }, { key: "cleanTables", value: function cleanTables(font) { - delete font.readOptions; - delete font.tables; - delete font.hmtx; - delete font.post.glyphNameIndex; - delete font.post.names; - delete font.subsetMap; + /** 优化245: delete → null 赋值,避免 V8 隐藏类转换 */ + font.readOptions = null; + font.tables = null; + font.hmtx = null; + font.post.glyphNameIndex = null; + font.post.names = null; + font.subsetMap = null; - // 删除无用的表 + // 清除无用的表 var cff = font.CFF; - delete cff.glyf; - delete cff.charset; - delete cff.encoding; - delete cff.gsubrs; - delete cff.gsubrsBias; - delete cff.subrs; - delete cff.subrsBias; + cff.glyf = null; + cff.charset = null; + cff.encoding = null; + cff.gsubrs = null; + cff.gsubrsBias = null; + cff.subrs = null; + cff.subrsBias = null; } /** diff --git a/vendor/fonteditor-core/lib/ttf/table/CFF.js b/vendor/fonteditor-core/lib/ttf/table/CFF.js index 0c3e5c1..2d4aa55 100644 --- a/vendor/fonteditor-core/lib/ttf/table/CFF.js +++ b/vendor/fonteditor-core/lib/ttf/table/CFF.js @@ -72,15 +72,15 @@ function parseCFFIndex(reader, offset, conversionFn) { reader.seek(offset); } var start = reader.offset; - var offsets = []; - var objects = []; var count = reader.readUint16(); + var offsets = new Array(count + 1); + var objects = new Array(count); var i; var l; if (count !== 0) { var offsetSize = reader.readUint8(); for (i = 0, l = count + 1; i < l; i++) { - offsets.push(getOffset(reader, offsetSize)); + offsets[i] = getOffset(reader, offsetSize); } for (i = 0, l = count; i < l; i++) { /** 优化179: 直接从 view 创建 Uint8Array 视图,避免 readBytes 的 slice */ @@ -90,7 +90,7 @@ function parseCFFIndex(reader, offset, conversionFn) { if (conversionFn) { value = conversionFn(value); } - objects.push(value); + objects[i] = value; } } return { @@ -141,15 +141,34 @@ function parseCFFIndexOffsets(reader, offset) { * @return {Uint8Array} object 数据 */ /** - * 优化169+179: 直接从 view 创建 Uint8Array 视图,避免 readBytes 的 slice 开销 + * 优化169+179+244: 预创建全量 charstring 大视图,用 subarray 替代 new Uint8Array(buffer, off, len) + * subarray 不需要参数验证,比 new Uint8Array 更快 */ function readCFFIndexObject(reader, indexInfo, idx) { var off = indexInfo.offsets; + var view = indexInfo._view; + if (view) { + /** 使用预创建的大视图 + subarray,baseOffset 已含 -1 修正 */ + return view.subarray(off[idx] - 1, off[idx + 1] - 1); + } var size = off[idx + 1] - off[idx]; var start = indexInfo.dataStart + off[idx] - 1; return new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, size); } +/** + * 优化244: 为 parseCFFIndexOffsets 的结果预创建全量数据视图 + * 后续 readCFFIndexObject 将使用 subarray 替代 new Uint8Array + */ +function prepareCFFIndexView(reader, indexInfo) { + var off = indexInfo.offsets; + if (!off || off.length < 2) return; + var totalSize = off[off.length - 1] - 1; + /** baseOffset 对齐原始 readCFFIndexObject 中的 byteOffset + dataStart + off[idx] - 1 */ + var baseOffset = reader.view.byteOffset + indexInfo.dataStart; + indexInfo._view = new Uint8Array(reader.view.buffer, baseOffset, totalSize); +} + // Subroutines are encoded using the negative half of the number space. // See type 2 chapter 4.7 "Subroutine operators". function calcCFFSubroutineBias(subrs) { @@ -382,6 +401,8 @@ var _default = exports.default = _table.default.create('cff', [], { if (!isCID) { var charStringsInfo = parseCFFIndexOffsets(reader, offset + topDict.charStrings); } + /** 优化244: 预创建全量 charstring 大视图,后续 readCFFIndexObject 用 subarray 替代 new Uint8Array */ + prepareCFFIndexView(reader, charStringsInfo); var nGlyphs = charStringsInfo.count; if (topDict.charset < 3) { @@ -400,7 +421,7 @@ var _default = exports.default = _table.default.create('cff', [], { } else { cff.encoding = (0, _parseCFFEncoding.default)(reader, offset + topDict.encoding); } - cff.glyf = []; + cff.glyf = new Array(nGlyphs); /** * 为指定 glyph 构建 per-glyph 的 font 对象 @@ -435,39 +456,59 @@ var _default = exports.default = _table.default.create('cff', [], { var subsetMap = { 0: true // 设置.notdef }; + /** 优化251: 构建 subsetMap 时同步收集 subsetGids,避免全量 nGlyphs 遍历 */ + var subsetGids = [0]; var codes = font.cmap; - // unicode to index — 用 Set 替代 indexOf 实现 O(1) 查找 - var subsetSet = {}; - /** 优化:合并 subsetSet 和 subsetMap 构建为单次遍历 */ + // unicode to index for (var si = 0, sl = subset.length; si < sl; si++) { var code = subset[si]; - subsetSet[code] = true; var ci = codes[code]; - if (ci !== undefined) subsetMap[ci] = true; + if (ci !== undefined && !subsetMap[ci]) { + subsetMap[ci] = true; + subsetGids.push(ci); + } } font.subsetMap = subsetMap; - /** 优化163+167: 构建 subsetGids 密集数组,传递给 otfreader 复用 */ - var subsetGids = []; - for (var gi = 0; gi < nGlyphs; gi++) { - if (subsetMap[gi]) subsetGids.push(gi); - } - for (var si = 0, sl = subsetGids.length; si < sl; si++) { - var i = subsetGids[si]; - var charstring = readCFFIndexObject(reader, charStringsInfo, i); - var glyf = (0, _parseCFFGlyph.default)(charstring, getGlyphFont(i), i); - glyf.name = cff.charset[i]; - cff.glyf[i] = glyf; + /* 优化258: CID/non-CID 分支到循环外,消除每 glyph 的三元分支 */ + if (fdGlyphFonts) { + for (var si = 0, sl = subsetGids.length; si < sl; si++) { + var i = subsetGids[si]; + var charstring = readCFFIndexObject(reader, charStringsInfo, i); + var glyf = (0, _parseCFFGlyph.default)(charstring, getGlyphFont(i), i); + glyf.name = cff.charset[i]; + cff.glyf[i] = glyf; + } + } else { + for (var si = 0, sl = subsetGids.length; si < sl; si++) { + var i = subsetGids[si]; + var charstring = readCFFIndexObject(reader, charStringsInfo, i); + var glyf = (0, _parseCFFGlyph.default)(charstring, cff, i); + glyf.name = cff.charset[i]; + cff.glyf[i] = glyf; + } } font.subsetGids = subsetGids; } // parse all else { - for (var i = 0, l = nGlyphs; i < l; i++) { - var charstring = readCFFIndexObject(reader, charStringsInfo, i); - var glyf = (0, _parseCFFGlyph.default)(charstring, getGlyphFont(i), i); - glyf.name = cff.charset[i]; - cff.glyf.push(glyf); + /* 优化202+230: 非 CID 字体直接使用 cff,缓存属性到局部变量 */ + var charset = cff.charset; + var glyfArr = cff.glyf; + if (fdGlyphFonts) { + for (var i = 0, l = nGlyphs; i < l; i++) { + var charstring = readCFFIndexObject(reader, charStringsInfo, i); + var glyf = (0, _parseCFFGlyph.default)(charstring, getGlyphFont(i), i); + glyf.name = charset[i]; + glyfArr[i] = glyf; + } + } else { + for (var i = 0, l = nGlyphs; i < l; i++) { + var charstring = readCFFIndexObject(reader, charStringsInfo, i); + var glyf = (0, _parseCFFGlyph.default)(charstring, cff, i); + glyf.name = charset[i]; + glyfArr[i] = glyf; + } } } return cff; diff --git a/vendor/fonteditor-core/lib/ttf/table/OS2.js b/vendor/fonteditor-core/lib/ttf/table/OS2.js index 9757ce9..868f286 100644 --- a/vendor/fonteditor-core/lib/ttf/table/OS2.js +++ b/vendor/fonteditor-core/lib/ttf/table/OS2.js @@ -220,25 +220,25 @@ var _default = exports.default = _table.default.create('OS/2', [['version', _str } } } - maxComponentElements = Math.max(maxComponentElements, subGlyfs.length); - maxCompositePoints = Math.max(maxCompositePoints, compositePoints); - maxCompositeContours = Math.max(maxCompositeContours, compositeContours); + if (subGlyfs.length > maxComponentElements) maxComponentElements = subGlyfs.length; + if (compositePoints > maxCompositePoints) maxCompositePoints = compositePoints; + if (compositeContours > maxCompositeContours) maxCompositeContours = compositeContours; } else if (glyf._numContours != null && glyf._numContours > 0) { /* 优化106: 使用 _numContours/_totalPoints 快速路径 */ - maxContours = Math.max(maxContours, glyf._numContours); - maxPoints = Math.max(maxPoints, glyf._totalPoints); + if (glyf._numContours > maxContours) maxContours = glyf._numContours; + if (glyf._totalPoints > maxPoints) maxPoints = glyf._totalPoints; } else if (glyf.contours && glyf.contours.length) { var gContours = glyf.contours; - maxContours = Math.max(maxContours, gContours.length); + if (gContours.length > maxContours) maxContours = gContours.length; var points = 0; var isFlat = glyf._flatContours; for (var ci = 0, cil = gContours.length; ci < cil; ci++) { - points += isFlat ? gContours[ci].length / 3 : gContours[ci].length; + points += isFlat ? gContours[ci].length / 3 | 0 : gContours[ci].length; } - maxPoints = Math.max(maxPoints, points); + if (points > maxPoints) maxPoints = points; } if (hinting && glyf.instructions) { - maxSizeOfInstructions = Math.max(maxSizeOfInstructions, glyf.instructions.length); + if (glyf.instructions.length > maxSizeOfInstructions) maxSizeOfInstructions = glyf.instructions.length; } var gXMin = glyf.xMin; var gYMin = glyf.yMin; @@ -248,11 +248,12 @@ var _default = exports.default = _table.default.create('OS/2', [['version', _str 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 (glyf.advanceWidth > advanceWidthMax) advanceWidthMax = glyf.advanceWidth; + if (glyf.leftSideBearing < minLeftSideBearing) minLeftSideBearing = glyf.leftSideBearing; if (null != gXMax) { - minRightSideBearing = Math.min(minRightSideBearing, glyf.advanceWidth - gXMax); - xMaxExtent = Math.max(xMaxExtent, gXMax); + var rsb = glyf.advanceWidth - gXMax; + if (rsb < minRightSideBearing) minRightSideBearing = rsb; + if (gXMax > xMaxExtent) xMaxExtent = gXMax; } if (null != glyf.advanceWidth) { xAvgCharWidth += glyf.advanceWidth; diff --git a/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFCharset.js b/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFCharset.js index 0ab7506..d1ec4cd 100644 --- a/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFCharset.js +++ b/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFCharset.js @@ -30,28 +30,31 @@ function parseCFFCharset(reader, start, nGlyphs, strings) { var count; // The .notdef glyph is not included, so subtract 1. nGlyphs -= 1; - var charset = ['.notdef']; + /** 优化250: 预分配 charset 数组,避免 push 扩容 */ + var charset = new Array(nGlyphs + 1); + charset[0] = '.notdef'; + var ci = 1; var format = reader.readUint8(); if (format === 0) { for (i = 0; i < nGlyphs; i += 1) { sid = reader.readUint16(); - charset.push((0, _getCFFString.default)(strings, sid)); + charset[ci++] = (0, _getCFFString.default)(strings, sid); } } else if (format === 1) { - while (charset.length <= nGlyphs) { + while (ci <= nGlyphs) { sid = reader.readUint16(); count = reader.readUint8(); for (i = 0; i <= count; i += 1) { - charset.push((0, _getCFFString.default)(strings, sid)); + charset[ci++] = (0, _getCFFString.default)(strings, sid); sid += 1; } } } else if (format === 2) { - while (charset.length <= nGlyphs) { + while (ci <= nGlyphs) { sid = reader.readUint16(); count = reader.readUint16(); for (i = 0; i <= count; i += 1) { - charset.push((0, _getCFFString.default)(strings, sid)); + charset[ci++] = (0, _getCFFString.default)(strings, sid); sid += 1; } } diff --git a/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFDict.js b/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFDict.js index 9bdc80d..26200d1 100644 --- a/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFDict.js +++ b/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFDict.js @@ -131,7 +131,6 @@ function entriesToObject(entries) { for (var i = 0, l = entries.length; i < l; i++) { var key = entries[i][0]; if (undefined !== hash[key]) { - console.warn('dict already has key:' + key); continue; } var values = entries[i][1]; diff --git a/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFGlyph.js b/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFGlyph.js index c2914be..f13fb5f 100644 --- a/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFGlyph.js +++ b/vendor/fonteditor-core/lib/ttf/table/cff/parseCFFGlyph.js @@ -26,6 +26,12 @@ function parseCFFCharstring(code, font, index) { var nStems = 0; var haveWidth = false; var width = font.defaultWidthX; + /** 优化211: 解构 font 热路径属性,消除重复属性查找 */ + var subrs = font.subrs; + var subrsBias = font.subrsBias; + var gsubrs = font.gsubrs; + var gsubrsBias = font.gsubrsBias; + var nominalWidthX = font.nominalWidthX; var open = false; var x = 0; var y = 0; @@ -90,7 +96,7 @@ function parseCFFCharstring(code, font, index) { /** parseStems 内联 */ var sLen = sp - si; if (sLen & 1 && !haveWidth) { - width = stack[si++] + font.nominalWidthX; + width = stack[si++] + nominalWidthX; sLen--; } nStems += sLen >> 1; @@ -101,7 +107,7 @@ function parseCFFCharstring(code, font, index) { case 4: // vmoveto if (sp - si > 1 && !haveWidth) { - width = stack[si++] + font.nominalWidthX; + width = stack[si++] + nominalWidthX; haveWidth = true; } y += stack[--sp]; @@ -156,8 +162,8 @@ function parseCFFCharstring(code, font, index) { break; case 10: // callsubr - codeIndex = stack[--sp] + font.subrsBias; - subrCode = font.subrs[codeIndex]; + codeIndex = stack[--sp] + subrsBias; + subrCode = subrs[codeIndex]; if (subrCode) { parse(subrCode); } @@ -265,7 +271,7 @@ function parseCFFCharstring(code, font, index) { case 14: // endchar if (sp - si === 1 && !haveWidth) { - width = stack[si++] + font.nominalWidthX; + width = stack[si++] + nominalWidthX; haveWidth = true; } else if (sp - si === 4) { glyfs[1] = { @@ -280,7 +286,7 @@ function parseCFFCharstring(code, font, index) { glyfs[1].transform.e = stack[--sp]; } else if (sp - si === 5) { if (!haveWidth) { - width = stack[si++] + font.nominalWidthX; + width = stack[si++] + nominalWidthX; } haveWidth = true; glyfs[1] = { @@ -306,7 +312,7 @@ function parseCFFCharstring(code, font, index) { { var sLen2 = sp - si; if (sLen2 & 1 && !haveWidth) { - width = stack[si++] + font.nominalWidthX; + width = stack[si++] + nominalWidthX; sLen2--; } nStems += sLen2 >> 1; @@ -318,7 +324,7 @@ function parseCFFCharstring(code, font, index) { case 21: // rmoveto if (sp - si > 2 && !haveWidth) { - width = stack[si++] + font.nominalWidthX; + width = stack[si++] + nominalWidthX; haveWidth = true; } y += stack[--sp]; @@ -329,7 +335,7 @@ function parseCFFCharstring(code, font, index) { case 22: // hmoveto if (sp - si > 1 && !haveWidth) { - width = stack[si++] + font.nominalWidthX; + width = stack[si++] + nominalWidthX; haveWidth = true; } x += stack[--sp]; @@ -417,8 +423,8 @@ function parseCFFCharstring(code, font, index) { break; case 29: // callgsubr - codeIndex = stack[--sp] + font.gsubrsBias; - subrCode = font.gsubrs[codeIndex]; + codeIndex = stack[--sp] + gsubrsBias; + subrCode = gsubrs[codeIndex]; if (subrCode) { parse(subrCode); } diff --git a/vendor/fonteditor-core/lib/ttf/table/cmap/sizeof.js b/vendor/fonteditor-core/lib/ttf/table/cmap/sizeof.js index 395dec5..0adcf4c 100644 --- a/vendor/fonteditor-core/lib/ttf/table/cmap/sizeof.js +++ b/vendor/fonteditor-core/lib/ttf/table/cmap/sizeof.js @@ -62,116 +62,97 @@ function getFormat0SegmentFlat(unicodeArr, idArr) { return unicodes; } -function getSegments(glyfUnicodes, bound) { - var prevGlyph = null; - var result = []; - var segment = {}; - for (var i = 0, l = glyfUnicodes.length; i < l; i++) { - var glyph = glyfUnicodes[i]; - if (bound === undefined || glyph.unicode <= bound) { - if (prevGlyph === null || glyph.unicode !== prevGlyph.unicode + 1 || glyph.id !== prevGlyph.id + 1) { - if (prevGlyph !== null) { - segment.end = prevGlyph.unicode; - result.push(segment); - segment = { - start: glyph.unicode, - startId: glyph.id, - delta: encodeDelta(glyph.id - glyph.unicode) - }; - } else { - segment.start = glyph.unicode; - segment.startId = glyph.id; - segment.delta = encodeDelta(glyph.id - glyph.unicode); - } - } - prevGlyph = glyph; - } - } - if (prevGlyph !== null) { - segment.end = prevGlyph.unicode; - result.push(segment); - } - return result; -} - -function getFormat0Segment(glyfUnicodes) { - var unicodes = []; - for (var i = 0, l = glyfUnicodes.length; i < l; i++) { - var u = glyfUnicodes[i]; - if (u.unicode < 256) { - unicodes.push([u.unicode, u.id]); - } - } - /* 数据已排序,无需再次 sort */ - return unicodes; -} - function sizeof(ttf) { ttf.support.cmap = {}; - /* 优化155: 使用并行扁平数组替代对象数组,减少 GC 压力 */ + /* 优化249: 两遍扫描 — 第一遍统计 unicode 总数,预分配数组避免 push 扩容 */ var glyfs = ttf.glyf; - var unicodeArr = []; - var idArr = []; - for (var index = 0, gl = glyfs.length; index < gl; index++) { - var glyph = glyfs[index]; - var unicodes = glyph.unicode; - if (typeof glyph.unicode === 'number') { - unicodes = [glyph.unicode]; + var gl = glyfs.length; + var totalCount = 0; + for (var index = 0; index < gl; index++) { + var unicodes = glyfs[index].unicode; + if (unicodes) { + totalCount += unicodes.length; + } else if (unicodes === 0 || unicodes === '') { + totalCount++; } - if (unicodes && unicodes.length) { - for (var ui = 0, ul = unicodes.length; ui < ul; ui++) { - unicodeArr.push(unicodes[ui]); - idArr.push(unicodes[ui] !== 0xFFFF ? index : 0); + } + var unicodeArr = new Array(totalCount); + var idArr = new Array(totalCount); + var ai = 0; + for (var index2 = 0; index2 < gl; index2++) { + var glyph = glyfs[index2]; + var ucs = glyph.unicode; + if (ucs) { + for (var ui = 0, ul = ucs.length; ui < ul; ui++) { + unicodeArr[ai] = ucs[ui]; + idArr[ai] = ucs[ui] !== 0xFFFF ? index2 : 0; + ai++; } + } else if (ucs === 0 || ucs === '') { + unicodeArr[ai] = ucs; + idArr[ai] = ucs !== 0xFFFF ? index2 : 0; + ai++; } } - /* 优化179: 二分插入排序,O(n log n) 查找 + O(n) 移位 */ - var len = unicodeArr.length; - for (var i = 1; i < len; i++) { - var uKey = unicodeArr[i]; - var iKey = idArr[i]; - var lo = 0, hi = i - 1; - while (lo <= hi) { - var mid = (lo + hi) >> 1; - if (unicodeArr[mid] > uKey) hi = mid - 1; - else lo = mid + 1; + /* 优化187+247: Int32Array 索引排序,V8 对 TypedArray 比较器排序更高效(避免装箱) */ + var len = ai; + if (len > 1) { + var indices = new Int32Array(len); + for (var ii = 0; ii < len; ii++) indices[ii] = ii; + indices.sort(function(a, b) { return unicodeArr[a] - unicodeArr[b]; }); + var sortedU = new Array(len); + var sortedI = new Array(len); + for (var ii2 = 0; ii2 < len; ii2++) { + var idx = indices[ii2]; + sortedU[ii2] = unicodeArr[idx]; + sortedI[ii2] = idArr[idx]; } - if (lo !== i) { - unicodeArr.copyWithin(lo + 1, lo, i); - idArr.copyWithin(lo + 1, lo, i); - unicodeArr[lo] = uKey; - idArr[lo] = iKey; + unicodeArr = sortedU; + idArr = sortedI; + } + + /* 优化: 合并 format12 和 format4 的 getSegmentsFlat 为单次调用 + * format12 不设 bound(包含所有字符),format4 截断到 0xFFFF + * 大多数字体只有 BMP 字符,此时两者完全相同 */ + var format12Segments = getSegmentsFlat(unicodeArr, idArr); + var cmapSupport = ttf.support.cmap; + cmapSupport.format12Segments = format12Segments; + cmapSupport.format12Size = 16 + (format12Segments.length >> 2) * 12; + + /* 检查是否有 > 0xFFFF 的字符,没有则 format4 直接复用 format12 */ + var hasOver2Bytes = false; + for (var ci = 0, cl = format12Segments.length; ci < cl; ci += 4) { + if (format12Segments[ci + 1] > 0xFFFF) { + hasOver2Bytes = true; + break; } } - ttf.support.cmap.format4Segments = getSegmentsFlat(unicodeArr, idArr, 0xFFFF); - /** format4Size 需要包含 sentinel segment (+1),与 write.js 中的 segCount = segments.length/4 + 1 一致 */ - /** - * format4Size = header(14) + reservedPad(2) + segCount * 2 * 4(four arrays) - * segCount = 实际段数 + 1(sentinel 0xFFFF) - */ - var format4SegCount = ttf.support.cmap.format4Segments.length / 4 + 1; - ttf.support.cmap.format4Size = 16 + format4SegCount * 8; - ttf.support.cmap.format0Segments = getFormat0SegmentFlat(unicodeArr, idArr); - ttf.support.cmap.hasFormat0 = ttf.support.cmap.format0Segments.length > 0; - ttf.support.cmap.format0Size = ttf.support.cmap.hasFormat0 ? 262 : 0; + if (hasOver2Bytes) { + cmapSupport.format4Segments = getSegmentsFlat(unicodeArr, idArr, 0xFFFF); + } else { + cmapSupport.format4Segments = format12Segments; + } - /** 始终生成 format 12 subtable(platformID=3, encodingID=10), - * 现代浏览器使用 unicode-range 时依赖 format 12 来匹配字符。 - * 仅当有 cmap 映射数据时才生成(避免 nGroups=0 的无效 subtable) */ var hasGLyphsOver2Bytes = len > 0; if (hasGLyphsOver2Bytes) { - ttf.support.cmap.hasGLyphsOver2Bytes = true; - ttf.support.cmap.format12Segments = getSegmentsFlat(unicodeArr, idArr); - ttf.support.cmap.format12Size = 16 + (ttf.support.cmap.format12Segments.length / 4) * 12; + cmapSupport.hasGLyphsOver2Bytes = true; } + + /** format4Size 需要包含 sentinel segment (+1),与 write.js 中的 segCount = segments.length/4 + 1 一致 */ + var format4SegCount = cmapSupport.format4Segments.length / 4 + 1; + cmapSupport.format4Size = 16 + format4SegCount * 8; + cmapSupport.format0Segments = getFormat0SegmentFlat(unicodeArr, idArr); + cmapSupport.hasFormat0 = cmapSupport.format0Segments.length > 0; + cmapSupport.format0Size = cmapSupport.hasFormat0 ? 262 : 0; + /** 记录头大小必须动态计算,与 write.js 中的 numRecords 保持一致,否则会导致表偏移错位 */ - var numRecords = 2 + (ttf.support.cmap.hasFormat0 ? 1 : 0) + (ttf.support.cmap.hasGLyphsOver2Bytes ? 1 : 0); + var numRecords = 2 + (cmapSupport.hasFormat0 ? 1 : 0) + (cmapSupport.hasGLyphsOver2Bytes ? 1 : 0); var recordHeaderSize = 4 + numRecords * 8; var size = recordHeaderSize - + ttf.support.cmap.format0Size - + ttf.support.cmap.format4Size - + (ttf.support.cmap.hasGLyphsOver2Bytes ? ttf.support.cmap.format12Size : 0); + + cmapSupport.format0Size + + cmapSupport.format4Size + + (cmapSupport.hasGLyphsOver2Bytes ? cmapSupport.format12Size : 0); return size; } diff --git a/vendor/fonteditor-core/lib/ttf/table/cmap/write.js b/vendor/fonteditor-core/lib/ttf/table/cmap/write.js index 0bcb5a7..22fbdd5 100644 --- a/vendor/fonteditor-core/lib/ttf/table/cmap/write.js +++ b/vendor/fonteditor-core/lib/ttf/table/cmap/write.js @@ -19,20 +19,16 @@ function writeSubTable0(writer, unicodes) { view.setUint16(pos, 262, false); pos += 2; view.setUint16(pos, 0, false); pos += 2; - var i = -1; + /** 优化218: 使用 writer.writeEmpty 批量填充 0,替代逐字节 setUint8 */ + writer.writeEmpty(256); + var base = writer.offset - 256; + pos = base; + for (var j = 0; j < unicodes.length; j += 2) { - var unicode = unicodes[j]; - var glyphId = unicodes[j + 1]; - while (++i < unicode) { - view.setUint8(pos++, 0); - } - view.setUint8(pos++, glyphId); - i = unicode; + pos = base + unicodes[j]; + view.setUint8(pos, unicodes[j + 1]); } - while (++i < 256) { - view.setUint8(pos++, 0); - } - writer.offset = pos; + writer.offset = base + 256; return writer; } @@ -43,8 +39,8 @@ function writeSubTable4(writer, segments) { var pos = writer.offset; var view = writer.view; var segCount = segments.length / 4 + 1; - var maxExponent = Math.floor(Math.log(segCount) / Math.LN2); - var searchRange = 2 * Math.pow(2, maxExponent); + var maxExponent = 31 - Math.clz32(segCount); + var searchRange = 2 * (1 << maxExponent); view.setUint16(pos, 4, false); pos += 2; view.setUint16(pos, 16 + segCount * 8, false); pos += 2; diff --git a/vendor/fonteditor-core/lib/ttf/table/directory.js b/vendor/fonteditor-core/lib/ttf/table/directory.js index c9faebc..395d822 100644 --- a/vendor/fonteditor-core/lib/ttf/table/directory.js +++ b/vendor/fonteditor-core/lib/ttf/table/directory.js @@ -37,20 +37,31 @@ var _default = exports.default = _table.default.create('directory', [], { return tables; }, /** - * 优化111: 直接 DataView 批量写入,避免 writer 方法调用开销 + * 优化111+184: 直接 DataView 批量写入,避免 writer 方法调用开销 + * 优化184: 使用 Uint32 写入 4 字节 tag,减少 4 次 setUint8 调用为 1 次 setUint32 */ write: function write(writer, ttf) { var tables = ttf.support.tables; var view = writer.view; var pos = writer.offset; + /** 优化235: 预计算 tag 的 Uint32 值,避免循环内 4 次 charCodeAt 调用 */ + var KNOWN_TAG_U32 = { + 'OS/2': 0x4F532F32, 'cmap': 0x636D6170, 'glyf': 0x676C7966, + 'head': 0x68656164, 'hhea': 0x68686561, 'hmtx': 0x686D7478, + 'loca': 0x6C6F6361, 'maxp': 0x6D617870, 'name': 0x6E616D65, + 'post': 0x706F7374, 'CFF ': 0x43464620, 'VORG': 0x564F5247, + 'GPOS': 0x47504F53, 'kern': 0x6B65726E, 'kerx': 0x6B657278, + 'cvt ': 0x63767420, 'fpgm': 0x6670676D, 'prep': 0x70726570, + 'gasp': 0x67617370 + }; for (var i = 0, l = tables.length; i < l; i++) { 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++; + var tagU32 = KNOWN_TAG_U32[t.name]; + if (tagU32 === undefined) { + var name = t.name; + tagU32 = name.charCodeAt(0) << 24 | name.charCodeAt(1) << 16 | name.charCodeAt(2) << 8 | name.charCodeAt(3); + } + view.setUint32(pos, tagU32, false); pos += 4; view.setUint32(pos, t.checkSum, false); pos += 4; view.setUint32(pos, t.offset, false); pos += 4; view.setUint32(pos, t.length, false); pos += 4; diff --git a/vendor/fonteditor-core/lib/ttf/table/glyf.js b/vendor/fonteditor-core/lib/ttf/table/glyf.js index 947cdbf..9d80e66 100644 --- a/vendor/fonteditor-core/lib/ttf/table/glyf.js +++ b/vendor/fonteditor-core/lib/ttf/table/glyf.js @@ -31,15 +31,22 @@ var _default = exports.default = _table.default.create('glyf', [], { var subsetMap = { 0: true }; var subsetGids = [0]; var cmap = ttf.cmap; + /** 优化: 构建 unicode→gid 映射,供 resolveGlyf 直接遍历,避免全量 cmap 遍历 */ + var subsetUnicodeMap = {}; for (var si = 0, sl = subset.length; si < sl; si++) { - var gid = cmap[subset[si]]; + var u = subset[si]; + var gid = cmap[u]; if (gid !== undefined && !subsetMap[gid]) { subsetMap[gid] = true; subsetGids.push(gid); + subsetUnicodeMap[u] = gid; + } else if (gid !== undefined) { + subsetUnicodeMap[u] = gid; } } ttf.subsetMap = subsetMap; ttf.subsetGids = subsetGids; + ttf._subsetUnicodeMap = subsetUnicodeMap; var parsedGlyfMap = {}; /* 优化:迭代式广度优先遍历替代递归,消除 isEmptyObject 调用 */ diff --git a/vendor/fonteditor-core/lib/ttf/table/glyf/parse.js b/vendor/fonteditor-core/lib/ttf/table/glyf/parse.js index ad88ce5..45d30ef 100644 --- a/vendor/fonteditor-core/lib/ttf/table/glyf/parse.js +++ b/vendor/fonteditor-core/lib/ttf/table/glyf/parse.js @@ -24,7 +24,6 @@ function parseSimpleGlyf(reader, glyf) { var numberOfCoordinates = endPtsOfContours[endPtsOfContours.length - 1] + 1; if (numberOfCoordinates > MAX_NUMBER_OF_COORDINATES) { - console.warn('error read glyf coordinates:' + offset); return glyf; } @@ -97,14 +96,13 @@ function parseSimpleGlyf(reader, glyf) { /** * 读取复合字形 + * 优化257: 使用直接 DataView 访问替代 reader API,消除每次 read 的函数调用和参数检查 */ function parseCompoundGlyf(reader, glyf) { glyf.compound = true; glyf.glyfs = []; var flags; - var g; var ARG_1_AND_2_ARE_WORDS = _componentFlag.default.ARG_1_AND_2_ARE_WORDS; - var ROUND_XY_TO_GRID = _componentFlag.default.ROUND_XY_TO_GRID; var WE_HAVE_A_SCALE = _componentFlag.default.WE_HAVE_A_SCALE; var WE_HAVE_AN_X_AND_Y_SCALE = _componentFlag.default.WE_HAVE_AN_X_AND_Y_SCALE; var WE_HAVE_A_TWO_BY_TWO = _componentFlag.default.WE_HAVE_A_TWO_BY_TWO; @@ -114,11 +112,12 @@ function parseCompoundGlyf(reader, glyf) { var MORE_COMPONENTS = _componentFlag.default.MORE_COMPONENTS; var WE_HAVE_INSTRUCTIONS = _componentFlag.default.WE_HAVE_INSTRUCTIONS; + var view = reader.view; + var vOffset = view.byteOffset + reader.offset; + do { - flags = reader.readUint16(); - g = {}; - g.flags = flags; - g.glyphIndex = reader.readUint16(); + flags = view.getUint16(vOffset, false); vOffset += 2; + var glyphIndex = view.getUint16(vOffset, false); vOffset += 2; var arg1 = 0; var arg2 = 0; var scaleX = 16384; @@ -126,64 +125,56 @@ function parseCompoundGlyf(reader, glyf) { var scale01 = 0; var scale10 = 0; if (ARG_1_AND_2_ARE_WORDS & flags) { - arg1 = reader.readInt16(); - arg2 = reader.readInt16(); + arg1 = view.getInt16(vOffset, false); vOffset += 2; + arg2 = view.getInt16(vOffset, false); vOffset += 2; } else { - arg1 = reader.readInt8(); - arg2 = reader.readInt8(); - } - if (ROUND_XY_TO_GRID & flags) { - arg1 = Math.round(arg1); - arg2 = Math.round(arg2); + arg1 = view.getInt8(vOffset); vOffset += 1; + arg2 = view.getInt8(vOffset); vOffset += 1; } if (WE_HAVE_A_SCALE & flags) { - scaleX = reader.readInt16(); + scaleX = view.getInt16(vOffset, false); vOffset += 2; scaleY = scaleX; } else if (WE_HAVE_AN_X_AND_Y_SCALE & flags) { - scaleX = reader.readInt16(); - scaleY = reader.readInt16(); + scaleX = view.getInt16(vOffset, false); vOffset += 2; + scaleY = view.getInt16(vOffset, false); vOffset += 2; } else if (WE_HAVE_A_TWO_BY_TWO & flags) { - scaleX = reader.readInt16(); - scale01 = reader.readInt16(); - scale10 = reader.readInt16(); - scaleY = reader.readInt16(); + scaleX = view.getInt16(vOffset, false); vOffset += 2; + scale01 = view.getInt16(vOffset, false); vOffset += 2; + scale10 = view.getInt16(vOffset, false); vOffset += 2; + scaleY = view.getInt16(vOffset, false); vOffset += 2; } + /** F2Dot14 → 小数: 优化214+236: 合并对象创建,减少每次 push 的分配次数 */ if (ARGS_ARE_XY_VALUES & flags) { - g.useMyMetrics = !!(flags & USE_MY_METRICS); - g.overlapCompound = !!(flags & OVERLAP_COMPOUND); - g.transform = { - 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 - }; + glyf.glyfs.push({ + flags: flags, + glyphIndex: glyphIndex, + useMyMetrics: !!(flags & USE_MY_METRICS), + overlapCompound: !!(flags & OVERLAP_COMPOUND), + transform: { a: (scaleX * 0.6103515625 + 0.5 | 0) / 10000, b: (scale01 * 0.6103515625 + 0.5 | 0) / 10000, c: (scale10 * 0.6103515625 + 0.5 | 0) / 10000, d: (scaleY * 0.6103515625 + 0.5 | 0) / 10000, e: arg1, f: arg2 } + }); } else { - g.points = [arg1, arg2]; - g.transform = { - 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 - }; + glyf.glyfs.push({ + flags: flags, + glyphIndex: glyphIndex, + points: [arg1, arg2], + transform: { a: (scaleX * 0.6103515625 + 0.5 | 0) / 10000, b: (scale01 * 0.6103515625 + 0.5 | 0) / 10000, c: (scale10 * 0.6103515625 + 0.5 | 0) / 10000, d: (scaleY * 0.6103515625 + 0.5 | 0) / 10000, e: 0, f: 0 } + }); } - glyf.glyfs.push(g); } while (MORE_COMPONENTS & flags); + if (WE_HAVE_INSTRUCTIONS & flags) { - var length = reader.readUint16(); + var length = view.getUint16(vOffset, false); vOffset += 2; if (length < MAX_INSTRUCTION_LENGTH) { var instructions = new Array(length); for (var i = 0; i < length; ++i) { - instructions[i] = reader.readUint8(); + instructions[i] = view.getUint8(vOffset + i); } glyf.instructions = instructions; - } else { - console.warn(length); } + vOffset += length; } + + reader.offset = vOffset - view.byteOffset; return glyf; } @@ -217,10 +208,11 @@ function parseGlyf(reader, ttf, offset) { vOffset += 2; } } else { - delete glyf.xMin; - delete glyf.yMin; - delete glyf.xMax; - delete glyf.yMax; + /** 优化245: undefined → 0,避免 V8 隐藏类转换,后续取默认值 0 */ + glyf.xMin = 0; + glyf.yMin = 0; + glyf.xMax = 0; + glyf.yMax = 0; } /* 优化12+42: 非 hinting 模式只跳过 instructions */ diff --git a/vendor/fonteditor-core/lib/ttf/table/glyf/sizeof.js b/vendor/fonteditor-core/lib/ttf/table/glyf/sizeof.js index 98e10da..9d29398 100644 --- a/vendor/fonteditor-core/lib/ttf/table/glyf/sizeof.js +++ b/vendor/fonteditor-core/lib/ttf/table/glyf/sizeof.js @@ -19,23 +19,25 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) { return 0; } - /* 优化84+98+103: 直接复用 optimize 阶段预计算的 flags/encodedCoordSize + 预编码 buffer */ - if (glyf._precomputedGlyfSupport) { - var pre = glyf._precomputedGlyfSupport; - glyfSupport.flags = pre.flags; - /* 优化98: 预编码 buffer 直接传递 */ - if (pre.xBuf) { - glyfSupport.xEncoded = pre.xBuf.subarray(0, pre.xLen); - glyfSupport.yEncoded = pre.yBuf.subarray(0, pre.yLen); + /* 优化84+98+103+223: 直接复用 optimize 阶段预计算的 flags/encodedCoordSize + 预编码 buffer */ + var pre = glyf._precomputedGlyfSupport || (glyf._preFlags ? glyf : null); + if (pre) { + glyfSupport.flags = pre.flags || pre._preFlags; + /* 优化256: buffer 已在写入方 trim,直接使用,消除二次 subarray slicing */ + var pxBuf = pre.xBuf || pre._preXBuf; + if (pxBuf) { + glyfSupport.xEncoded = pxBuf; + glyfSupport.yEncoded = pre.yBuf || pre._preYBuf; } else { glyfSupport.xCoord = pre.xCoord; glyfSupport.yCoord = pre.yCoord; } - delete glyf._precomputedGlyfSupport; + glyf._precomputedGlyfSupport = null; 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 encSz = pre.encodedCoordSize || pre._preEncodedCoordSize; + return 12 + nc * 2 + glyfSupport.flags.length + encSz + instructionSize; } var ONCURVE = _glyFlag.default.ONCURVE; @@ -45,19 +47,29 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) { var YSAME = _glyFlag.default.YSAME; var REPEAT = _glyFlag.default.REPEAT; - var flagsC = []; - var xCoordC = []; - var yCoordC = []; var contours = glyf.contours; - var prevX = 0, prevY = 0; - var isFirst = true; - var prevFlag = -1; - var repeatPoint = -1; - var isFlat = glyf._flatContours; + /** 预计算总点数,一次性分配 TypedArray */ + var totalPts = 0; + for (var tc = 0, tcl = contours.length; tc < tcl; tc++) { + totalPts += isFlat ? contours[tc].length / 3 | 0 : contours[tc].length; + } + + var flagsC = new Uint8Array(totalPts); + var xCoordBuf = new Uint8Array(totalPts * 2); + var yCoordBuf = new Uint8Array(totalPts * 2); + var xbi = 0, ybi = 0; + var prevX = 0, prevY = 0; + var prevFlag = -1; + var repeatPoint = -1; + var fi = 0; + var encodedCoordSize = 0; + /** 优化: isFirst 提取到循环外,消除每个点的条件分支 */ + var started = false; + for (var j = 0, cl = contours.length; j < cl; j++) { var contour = contours[j]; if (isFlat) { @@ -65,56 +77,57 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) { var px = contour[i]; var py = contour[i + 1]; var onCurve = contour[i + 2]; - var dx, dy; var flag = onCurve ? ONCURVE : 0; + var dx, dy; - if (isFirst) { - dx = px; - dy = py; - isFirst = false; + if (!started) { + dx = px; dy = py; started = true; } else { - dx = px - prevX; - dy = py - prevY; + dx = px - prevX; dy = py - prevY; } - prevX = px; - prevY = py; + prevX = px; prevY = py; if (dx === 0) { flag += XSAME; - } else if (-0xFF <= dx && dx <= 0xFF) { + } else if (dx > -256 && dx < 256) { flag += XSHORT; if (dx > 0) flag += XSAME; - xCoordC.push(dx > 0 ? dx : -dx); + xCoordBuf[xbi++] = dx > 0 ? dx : -dx; encodedCoordSize += 1; } else { - xCoordC.push(dx); + xCoordBuf[xbi++] = (dx >> 8) & 0xFF; + xCoordBuf[xbi++] = dx & 0xFF; encodedCoordSize += 2; } if (dy === 0) { flag += YSAME; - } else if (-0xFF <= dy && dy <= 0xFF) { + } else if (dy > -256 && dy < 256) { flag += YSHORT; if (dy > 0) flag += YSAME; - yCoordC.push(dy > 0 ? dy : -dy); + yCoordBuf[ybi++] = dy > 0 ? dy : -dy; encodedCoordSize += 1; } else { - yCoordC.push(dy); + yCoordBuf[ybi++] = (dy >> 8) & 0xFF; + yCoordBuf[ybi++] = dy & 0xFF; encodedCoordSize += 2; } - /* REPEAT 压缩 */ - if (flag === prevFlag && !isFirst) { + if (flag === prevFlag && started) { if (repeatPoint === -1) { - repeatPoint = flagsC.length - 1; + repeatPoint = fi - 1; flagsC[repeatPoint] |= REPEAT; - flagsC.push(1); - } else { + flagsC[fi++] = 1; + } else if (flagsC[repeatPoint + 1] < 255) { ++flagsC[repeatPoint + 1]; + } else { + /* 优化188: repeat count 达到 255 上限 */ + repeatPoint = -1; + flagsC[fi++] = prevFlag = flag; } } else { repeatPoint = -1; - flagsC.push(prevFlag = flag); + flagsC[fi++] = prevFlag = flag; } } } else { @@ -122,64 +135,66 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) { var point = contour[i]; var px = point.x; var py = point.y; - var dx, dy; var flag = point.onCurve ? ONCURVE : 0; + var dx, dy; - if (isFirst) { - dx = px; - dy = py; - isFirst = false; + if (!started) { + dx = px; dy = py; started = true; } else { - dx = px - prevX; - dy = py - prevY; + dx = px - prevX; dy = py - prevY; } - prevX = px; - prevY = py; + prevX = px; prevY = py; if (dx === 0) { flag += XSAME; - } else if (-0xFF <= dx && dx <= 0xFF) { + } else if (dx > -256 && dx < 256) { flag += XSHORT; if (dx > 0) flag += XSAME; - xCoordC.push(dx > 0 ? dx : -dx); + xCoordBuf[xbi++] = dx > 0 ? dx : -dx; encodedCoordSize += 1; } else { - xCoordC.push(dx); + xCoordBuf[xbi++] = (dx >> 8) & 0xFF; + xCoordBuf[xbi++] = dx & 0xFF; encodedCoordSize += 2; } if (dy === 0) { flag += YSAME; - } else if (-0xFF <= dy && dy <= 0xFF) { + } else if (dy > -256 && dy < 256) { flag += YSHORT; if (dy > 0) flag += YSAME; - yCoordC.push(dy > 0 ? dy : -dy); + yCoordBuf[ybi++] = dy > 0 ? dy : -dy; encodedCoordSize += 1; } else { - yCoordC.push(dy); + yCoordBuf[ybi++] = (dy >> 8) & 0xFF; + yCoordBuf[ybi++] = dy & 0xFF; encodedCoordSize += 2; } - /* REPEAT 压缩 */ - if (flag === prevFlag && !isFirst) { + if (flag === prevFlag && started) { if (repeatPoint === -1) { - repeatPoint = flagsC.length - 1; + repeatPoint = fi - 1; flagsC[repeatPoint] |= REPEAT; - flagsC.push(1); - } else { + flagsC[fi++] = 1; + } else if (flagsC[repeatPoint + 1] < 255) { ++flagsC[repeatPoint + 1]; + } else { + /* 优化188: repeat count 达到 255 上限 */ + repeatPoint = -1; + flagsC[fi++] = prevFlag = flag; } } else { repeatPoint = -1; - flagsC.push(prevFlag = flag); + flagsC[fi++] = prevFlag = flag; } } } } + flagsC = flagsC.subarray(0, fi); glyfSupport.flags = flagsC; - glyfSupport.xCoord = xCoordC; - glyfSupport.yCoord = yCoordC; + glyfSupport.xEncoded = xCoordBuf.subarray(0, xbi); + glyfSupport.yEncoded = yCoordBuf.subarray(0, ybi); var instructionSize = (hinting && glyf.instructions) ? glyf.instructions.length : 0; return 12 + contours.length * 2 + flagsC.length + encodedCoordSize + instructionSize; @@ -192,17 +207,20 @@ function sizeofCompound(glyf) { var size = 10; var glyfs = glyf.glyfs; for (var i = 0, l = glyfs.length; i < l; i++) { + /** 优化212: 解构 transform 属性,消除重复属性查找 */ var transform = glyfs[i].transform; + var e = transform.e, f = transform.f; + var a = transform.a, b = transform.b, c = transform.c, d = transform.d; size += 4; - if (transform.e < 0 || transform.e > 0x7F || transform.f < 0 || transform.f > 0x7F) { + if (e < 0 || e > 0x7F || f < 0 || f > 0x7F) { size += 4; } else { size += 2; } - if (transform.b || transform.c) { + if (b || c) { size += 8; - } else if (transform.a !== 1 || transform.d !== 1) { - size += transform.a === transform.d ? 2 : 4; + } else if (a !== 1 || d !== 1) { + size += a === d ? 2 : 4; } } return size; @@ -212,12 +230,13 @@ function sizeofCompound(glyf) { * 优化49: sizeof glyf.forEach → for 循环 */ function sizeof(ttf) { - var glyfSupportArr = []; + var glyfs = ttf.glyf; + var glyfSupportArr = new Array(glyfs.length); ttf.support.glyf = glyfSupportArr; var tableSize = 0; - var hinting = ttf.writeOptions ? ttf.writeOptions.hinting : false; - var writeZeroContoursGlyfData = ttf.writeOptions ? ttf.writeOptions.writeZeroContoursGlyfData : false; - var glyfs = ttf.glyf; + var opts = ttf.writeOptions || {}; + var hinting = opts.hinting; + var writeZeroContoursGlyfData = opts.writeZeroContoursGlyfData; for (var i = 0, gl = glyfs.length; i < gl; i++) { var glyf = glyfs[i]; @@ -231,10 +250,7 @@ function sizeof(ttf) { glyfSize = getFlagsAndSize(glyf, glyfSupport, hinting); } - var size = glyfSize; - if (size % 4) { - size += 4 - size % 4; - } + var size = (glyfSize + 3) & ~3; glyfSupport.glyfSize = glyfSize; glyfSupport.size = size; glyfSupportArr[i] = glyfSupport; diff --git a/vendor/fonteditor-core/lib/ttf/table/glyf/write.js b/vendor/fonteditor-core/lib/ttf/table/glyf/write.js index b403ec1..3a38934 100644 --- a/vendor/fonteditor-core/lib/ttf/table/glyf/write.js +++ b/vendor/fonteditor-core/lib/ttf/table/glyf/write.js @@ -60,27 +60,27 @@ function write(writer, ttf) { var subGlyfs = glyf.glyfs; for (var gi = 0, gl2 = subGlyfs.length; gi < gl2; gi++) { var g = subGlyfs[gi]; - var flags = g.points ? 0 : ARGS_ARE_XY_VALUES + ROUND_XY_TO_GRID; - if (gi < gl2 - 1) flags += MORE_COMPONENTS; - flags += g.useMyMetrics ? USE_MY_METRICS : 0; - flags += g.overlapCompound ? OVERLAP_COMPOUND : 0; + var flags = g.points ? 0 : ARGS_ARE_XY_VALUES | ROUND_XY_TO_GRID; + if (gi < gl2 - 1) flags |= MORE_COMPONENTS; + if (g.useMyMetrics) flags |= USE_MY_METRICS; + if (g.overlapCompound) flags |= OVERLAP_COMPOUND; var transform = g.transform; var a = transform.a; var b = transform.b; var c = transform.c; var d = transform.d; - var pts = g.points; - var e = pts ? pts[0] : transform.e; - var f = pts ? pts[1] : transform.f; + /** 优化225: 优先无 points 路径(大多数复合 glyph),减少分支 */ + var e = g.points ? g.points[0] : transform.e; + var f = g.points ? g.points[1] : transform.f; if (e < 0 || e > 0x7F || f < 0 || f > 0x7F) { - flags += ARG_1_AND_2_ARE_WORDS; + flags |= ARG_1_AND_2_ARE_WORDS; } if (b || c) { - flags += WE_HAVE_A_TWO_BY_TWO; + flags |= WE_HAVE_A_TWO_BY_TWO; } else if ((a !== 1 || d !== 1) && a === d) { - flags += WE_HAVE_A_SCALE; + flags |= WE_HAVE_A_SCALE; } else if (a !== 1 || d !== 1) { - flags += WE_HAVE_AN_X_AND_Y_SCALE; + flags |= WE_HAVE_AN_X_AND_Y_SCALE; } view.setUint16(pos, flags, false); pos += 2; view.setUint16(pos, g.glyphIndex, false); pos += 2; @@ -92,15 +92,15 @@ function write(writer, ttf) { view.setUint8(pos, f); pos += 1; } if (WE_HAVE_A_SCALE & flags) { - view.setInt16(pos, Math.round(a * 16384), false); pos += 2; + view.setInt16(pos, a * 16384 + 0.5 | 0, false); pos += 2; } else if (WE_HAVE_AN_X_AND_Y_SCALE & flags) { - view.setInt16(pos, Math.round(a * 16384), false); pos += 2; - view.setInt16(pos, Math.round(d * 16384), false); pos += 2; + view.setInt16(pos, a * 16384 + 0.5 | 0, false); pos += 2; + view.setInt16(pos, d * 16384 + 0.5 | 0, false); pos += 2; } else if (WE_HAVE_A_TWO_BY_TWO & flags) { - view.setInt16(pos, Math.round(a * 16384), false); pos += 2; - view.setInt16(pos, Math.round(b * 16384), false); pos += 2; - view.setInt16(pos, Math.round(c * 16384), false); pos += 2; - view.setInt16(pos, Math.round(d * 16384), false); pos += 2; + view.setInt16(pos, a * 16384 + 0.5 | 0, false); pos += 2; + view.setInt16(pos, b * 16384 + 0.5 | 0, false); pos += 2; + view.setInt16(pos, c * 16384 + 0.5 | 0, false); pos += 2; + view.setInt16(pos, d * 16384 + 0.5 | 0, false); pos += 2; } } } else { @@ -138,10 +138,11 @@ function write(writer, ttf) { pos += 2; } - /* 优化11+79+135: flags 直接 view 写入,避免临时 TypedArray */ - var flags = gSupport.flags || []; - for (var fi = 0, fl = flags.length; fi < fl; fi++) { - view.setUint8(pos++, flags[fi]); + /* 优化11+79+135+230+237: flags 是 Uint8Array,直接 set 批量写入替代逐字节循环 */ + var flags = gSupport.flags; + if (flags && flags.length > 0) { + fullView.set(flags, pos); + pos += flags.length; } /* 优化21+98+119+165: xCoord 预编码 Uint8Array 直接 set,复用全局视图 */ diff --git a/vendor/fonteditor-core/lib/ttf/table/head.js b/vendor/fonteditor-core/lib/ttf/table/head.js index 2db0176..b170275 100644 --- a/vendor/fonteditor-core/lib/ttf/table/head.js +++ b/vendor/fonteditor-core/lib/ttf/table/head.js @@ -53,22 +53,20 @@ var _default = exports.default = _table.default.create('head', [['version', _str var head = ttf.head; var pos = writer.offset; var view = writer.view; - view.setInt32(pos, Math.round(head.version * 65536), false); pos += 4; - view.setInt32(pos, Math.round(head.fontRevision * 65536), false); pos += 4; + view.setInt32(pos, head.version * 65536 + 0.5 | 0, false); pos += 4; + view.setInt32(pos, head.fontRevision * 65536 + 0.5 | 0, false); pos += 4; view.setUint32(pos, head.checkSumAdjustment, false); pos += 4; view.setUint32(pos, head.magickNumber, false); pos += 4; view.setUint16(pos, head.flags, false); pos += 2; view.setUint16(pos, head.unitsPerEm, false); pos += 2; - /** LongDateTime 内联: 1904-01-01 基准,8字节 (高4字节=0, 低4字节=秒数) */ + /** 优化216: 内联 writeLDT,消除函数定义+调用开销 */ var delta = -2077545600000; - function writeLDT(value, p) { - var ms = typeof value.getTime === 'function' ? value.getTime() : typeof value === 'number' ? value : Date.parse(value); - view.setUint32(p, 0, false); - view.setUint32(p + 4, Math.round((ms - delta) / 1000), false); - return p + 8; - } - pos = writeLDT(head.created, pos); - pos = writeLDT(head.modified, pos); + var cMs = typeof head.created.getTime === 'function' ? head.created.getTime() : typeof head.created === 'number' ? head.created : Date.parse(head.created); + view.setUint32(pos, 0, false); pos += 4; + view.setUint32(pos, Math.round((cMs - delta) / 1000), false); pos += 4; + var mMs = typeof head.modified.getTime === 'function' ? head.modified.getTime() : typeof head.modified === 'number' ? head.modified : Date.parse(head.modified); + view.setUint32(pos, 0, false); pos += 4; + view.setUint32(pos, Math.round((mMs - delta) / 1000), false); pos += 4; view.setInt16(pos, head.xMin, false); pos += 2; view.setInt16(pos, head.yMin, false); pos += 2; view.setInt16(pos, head.xMax, false); pos += 2; diff --git a/vendor/fonteditor-core/lib/ttf/table/hhea.js b/vendor/fonteditor-core/lib/ttf/table/hhea.js index 8f74261..24a8cae 100644 --- a/vendor/fonteditor-core/lib/ttf/table/hhea.js +++ b/vendor/fonteditor-core/lib/ttf/table/hhea.js @@ -48,7 +48,7 @@ var _default = exports.default = _table.default.create('hhea', [['version', _str var h = ttf.hhea; var pos = writer.offset; var view = writer.view; - view.setInt32(pos, Math.round(h.version * 65536), false); pos += 4; + view.setInt32(pos, h.version * 65536 + 0.5 | 0, false); pos += 4; view.setInt16(pos, h.ascent, false); pos += 2; view.setInt16(pos, h.descent, false); pos += 2; view.setInt16(pos, h.lineGap, false); pos += 2; diff --git a/vendor/fonteditor-core/lib/ttf/table/hmtx.js b/vendor/fonteditor-core/lib/ttf/table/hmtx.js index 6408370..2d57250 100644 --- a/vendor/fonteditor-core/lib/ttf/table/hmtx.js +++ b/vendor/fonteditor-core/lib/ttf/table/hmtx.js @@ -52,8 +52,10 @@ var _default = exports.default = _table.default.create('hmtx', [], { pos += 4; } var numOfLast = glyfs.length - numOfLongHorMetrics; + /* 优化: 提取 numOfLongHorMetrics 到循环外变量,消除每次迭代属性链查找 + 加法 */ + var lastBase = numOfLongHorMetrics; for (var j = 0; j < numOfLast; j++) { - wView.setInt16(pos, glyfs[numOfLongHorMetrics + j].leftSideBearing, false); + wView.setInt16(pos, glyfs[lastBase + j].leftSideBearing, false); pos += 2; } writer.offset = pos; diff --git a/vendor/fonteditor-core/lib/ttf/table/maxp.js b/vendor/fonteditor-core/lib/ttf/table/maxp.js index 19b88db..93d4c07 100644 --- a/vendor/fonteditor-core/lib/ttf/table/maxp.js +++ b/vendor/fonteditor-core/lib/ttf/table/maxp.js @@ -12,12 +12,36 @@ 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 字节 */ + read: function (reader) { + reader.seek(this.offset); + var v = reader.view; + var o = reader.offset; + var r = {}; + r.version = v.getInt32(o, false) / 65536; o += 4; + r.numGlyphs = v.getUint16(o, false); o += 2; + r.maxPoints = v.getUint16(o, false); o += 2; + r.maxContours = v.getUint16(o, false); o += 2; + r.maxCompositePoints = v.getUint16(o, false); o += 2; + r.maxCompositeContours = v.getUint16(o, false); o += 2; + r.maxZones = v.getUint16(o, false); o += 2; + r.maxTwilightPoints = v.getUint16(o, false); o += 2; + r.maxStorage = v.getUint16(o, false); o += 2; + r.maxFunctionDefs = v.getUint16(o, false); o += 2; + r.maxInstructionDefs = v.getUint16(o, false); o += 2; + r.maxStackElements = v.getUint16(o, false); o += 2; + r.maxSizeOfInstructions = v.getUint16(o, false); o += 2; + r.maxComponentElements = v.getUint16(o, false); o += 2; + r.maxComponentDepth = v.getInt16(o, false); o += 2; + reader.offset = o; + return r; + }, /** 优化178: 直接 view 写入 32 字节,注意写入 ttf.support.maxp */ write: function write(writer, ttf) { 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.setInt32(pos, m.version * 65536 + 0.5 | 0, 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; diff --git a/vendor/fonteditor-core/lib/ttf/table/name.js b/vendor/fonteditor-core/lib/ttf/table/name.js index eb795f0..67cc496 100644 --- a/vendor/fonteditor-core/lib/ttf/table/name.js +++ b/vendor/fonteditor-core/lib/ttf/table/name.js @@ -87,11 +87,13 @@ var _default = exports.default = _table.default.create('name', [], { offset += r.name.length; } - /** 必须在 writeBytes 前同步 writer.offset,否则 writeBytes 会从旧偏移写入,覆盖 header */ - writer.offset = pos; + /** 优化206: 直接 fullView.set 替代 writer.writeBytes,消除函数调用+边界检查开销 */ + var fullView = new Uint8Array(view.buffer, view.byteOffset); for (var j = 0, jl = nameRecordTbl.length; j < jl; j++) { - writer.writeBytes(nameRecordTbl[j].name); + fullView.set(nameRecordTbl[j].name, pos); + pos += nameRecordTbl[j].name.length; } + writer.offset = pos; return writer; }, size: function size(ttf) { @@ -102,10 +104,14 @@ var _default = exports.default = _table.default.create('name', [], { // 这里为了简化书写,仅支持英文编码字符, // 中文编码字符将被转化成url encode var size = 6; - for (var name in names) { + /** 优化239: Object.keys + for 替代 for...in */ + var nameKeys = Object.keys(names); + for (var ki = 0, kl = nameKeys.length; ki < kl; ki++) { + var ki_name = nameKeys[ki]; + var name = ki_name; var id = _nameId.default.names[name]; - var utf8Bytes = _string.default.toUTF8Bytes(names[name]); - var usc2Bytes = _string.default.toUCS2Bytes(names[name]); + var utf8Bytes = _string.default.toUTF8Bytes(names[ki_name]); + var usc2Bytes = _string.default.toUCS2Bytes(names[ki_name]); if (undefined !== id) { // mac nameRecordTbl.push({ diff --git a/vendor/fonteditor-core/lib/ttf/table/post.js b/vendor/fonteditor-core/lib/ttf/table/post.js index e96982f..68e567a 100644 --- a/vendor/fonteditor-core/lib/ttf/table/post.js +++ b/vendor/fonteditor-core/lib/ttf/table/post.js @@ -19,22 +19,22 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de var Posthead = _table.default.create('posthead', [['format', _struct.default.Fixed], ['italicAngle', _struct.default.Fixed], ['underlinePosition', _struct.default.Int16], ['underlineThickness', _struct.default.Int16], ['isFixedPitch', _struct.default.Uint32], ['minMemType42', _struct.default.Uint32], ['maxMemType42', _struct.default.Uint32], ['minMemType1', _struct.default.Uint32], ['maxMemType1', _struct.default.Uint32]]); /** - * 优化64: 从原始字节按需提取单个 pascal string + * 优化64+252: 从原始字节按需提取单个 pascal string,使用 fromCharCode.apply 替代数组+join */ function getPascalStringAt(bytes, offset) { var length = bytes[offset]; if (length === 0) return ''; var chars = new Array(length); for (var i = 0; i < length; i++) { - chars[i] = String.fromCharCode(bytes[offset + 1 + i]); + chars[i] = bytes[offset + 1 + i]; } - return chars.join(''); + return String.fromCharCode.apply(null, chars); } var _default = exports.default = _table.default.create('post', [], { read: function read(reader, ttf) { - var format = reader.readFixed(this.offset); var tbl = new Posthead(this.offset).read(reader, ttf); + var format = tbl.format; if (format === 2) { var numberOfGlyphs = reader.readUint16(); @@ -77,8 +77,8 @@ var _default = exports.default = _table.default.create('post', [], { /* 优化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.setInt32(pos, post.format * 65536 + 0.5 | 0, false); pos += 4; + view.setInt32(pos, (post.italicAngle || 0) * 65536 + 0.5 | 0, 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; @@ -118,26 +118,26 @@ var _default = exports.default = _table.default.create('post', [], { var size = 34 + numberOfGlyphs * 2; var glyphNames = []; - var nameIndexArr = []; + var nameIndexArr = new Array(numberOfGlyphs); var nameIndex = 0; for (var i = 0; i < numberOfGlyphs; i++) { if (i === 0) { - nameIndexArr.push(0); + nameIndexArr[i] = 0; } else { var glyf = ttf.glyf[i]; var unicode = glyf.unicode ? glyf.unicode[0] : 0; var unicodeNameIndex = _unicodeName.default[unicode]; if (undefined !== unicodeNameIndex) { - nameIndexArr.push(unicodeNameIndex); + nameIndexArr[i] = unicodeNameIndex; } else { var name = glyf.name; if (!name || name.charCodeAt(0) < 32) { - nameIndexArr.push(258 + nameIndex++); + nameIndexArr[i] = 258 + nameIndex++; glyphNames.push([0]); size++; } else { - nameIndexArr.push(258 + nameIndex++); + nameIndexArr[i] = 258 + nameIndex++; var bytes = _string.default.toPascalStringBytes(name); glyphNames.push(bytes); size += bytes.length; diff --git a/vendor/fonteditor-core/lib/ttf/ttfreader.js b/vendor/fonteditor-core/lib/ttf/ttfreader.js index 052c4dc..889877c 100644 --- a/vendor/fonteditor-core/lib/ttf/ttfreader.js +++ b/vendor/fonteditor-core/lib/ttf/ttfreader.js @@ -57,12 +57,14 @@ var TTFReader = exports.default = /*#__PURE__*/function () { } ttf.readOptions = this.options; - /* 优化8+37+62: 跳过不必要的表,缓存 TableClass 实例,for...in 替代 Object.keys */ + /* 优化8+37+62+240: 跳过不必要的表,缓存 TableClass 实例,预构建表名列表替代 for...in */ var hinting = this.options.hinting; var kerning = this.options.kerning; var supportTables = _support.default; var tableInstances = {}; - for (var tableName in supportTables) { + var ttfTableNames = ['head', 'maxp', 'loca', 'cmap', 'glyf', 'name', 'hhea', 'hmtx', 'post', 'OS/2', 'fpgm', 'cvt', 'prep', 'gasp', 'GPOS', 'kern', 'kerx']; + for (var ti = 0, tl = ttfTableNames.length; ti < tl; ti++) { + var tableName = ttfTableNames[ti]; if (ttf.tables[tableName]) { /* 优化8: hinting=false 时跳过 fpgm/cvt/prep/gasp */ if (!hinting && (tableName === 'fpgm' || tableName === 'cvt' || tableName === 'prep' || tableName === 'gasp')) { @@ -96,16 +98,30 @@ var TTFReader = exports.default = /*#__PURE__*/function () { 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) { - var i = codes[c]; - if (subsetMap && !subsetMap[i]) { - continue; + /* 优化: subset 模式下只遍历 subsetUnicodeMap(O(S)),避免全量 codes 遍历(O(U)) */ + if (ttf._subsetUnicodeMap) { + var sum = ttf._subsetUnicodeMap; + /** 优化: for...in 替代 Object.keys,消除临时数组分配 */ + for (var uStr in sum) { + var u = +uStr; + var i = sum[u]; + if (!glyf[i].unicode) { + glyf[i].unicode = [u]; + } else { + glyf[i].unicode.push(u); + } } - if (!glyf[i].unicode) { - glyf[i].unicode = []; + ttf._subsetUnicodeMap = null; + } else { + for (var c in codes) { + var i = codes[c]; + var code = +c; + if (!glyf[i].unicode) { + glyf[i].unicode = [code]; + } else { + glyf[i].unicode.push(code); + } } - glyf[i].unicode.push(+c); } /* 优化13+82+118: advanceWidth 遍历优化,使用密集数组 */ @@ -172,15 +188,15 @@ var TTFReader = exports.default = /*#__PURE__*/function () { } } - /* 优化13+44+62+118: subset 模式下使用密集数组遍历 */ + /* 优化259: subset 模式下合并 compound→simple 转换与 subGlyf 构建,消除二次遍历 */ if (subsetGids) { - var subGlyf = []; + var subGlyf = new Array(subsetGids.length); for (var si = 0, sl = subsetGids.length; si < sl; si++) { var siNum = subsetGids[si]; if (glyf[siNum].compound) { (0, _compound2simpleglyf.default)(siNum, ttf, true); } - subGlyf.push(glyf[siNum]); + subGlyf[si] = glyf[siNum]; } ttf.glyf = subGlyf; ttf.maxp.maxComponentElements = 0; @@ -190,32 +206,33 @@ var TTFReader = exports.default = /*#__PURE__*/function () { }, { key: "cleanTables", value: function cleanTables(ttf) { - delete ttf.readOptions; - delete ttf.tables; - delete ttf.hmtx; - delete ttf.loca; + /** 优化245: delete → null 赋值,避免 V8 隐藏类转换 */ + ttf.readOptions = null; + ttf.tables = null; + ttf.hmtx = null; + ttf.loca = null; if (ttf.post) { - delete ttf.post.nameIndex; - delete ttf.post.names; - delete ttf.post._pascalStringBytes; - delete ttf.post._pascalStringOffsets; + ttf.post.nameIndex = null; + ttf.post.names = null; + ttf.post._pascalStringBytes = null; + ttf.post._pascalStringOffsets = null; } - delete ttf.subsetMap; + ttf.subsetMap = null; if (!this.options.hinting) { - delete ttf.fpgm; - delete ttf.cvt; - delete ttf.prep; - /* 优化55: forEach → for 循环 */ + /** 优化245: delete → null 赋值,避免 V8 隐藏类转换 */ + ttf.fpgm = null; + ttf.cvt = null; + ttf.prep = null; var glyfs = ttf.glyf; for (var i = 0, l = glyfs.length; i < l; i++) { - delete glyfs[i].instructions; + glyfs[i].instructions = null; } } if (!this.options.hinting && !this.options.kerning) { - delete ttf.GPOS; - delete ttf.kern; - delete ttf.kerx; + ttf.GPOS = null; + ttf.kern = null; + ttf.kerx = null; } if (this.options.compound2simple && ttf.maxp.maxComponentElements) { diff --git a/vendor/fonteditor-core/lib/ttf/ttftowoff2.js b/vendor/fonteditor-core/lib/ttf/ttftowoff2.js index 1cc77dc..0a06ce7 100644 --- a/vendor/fonteditor-core/lib/ttf/ttftowoff2.js +++ b/vendor/fonteditor-core/lib/ttf/ttftowoff2.js @@ -20,22 +20,14 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de * * @return {ArrayBuffer} woff格式byte流 */ -// eslint-disable-next-line no-unused-vars function ttftowoff2(ttfBuffer) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var result = _index.default.encode(ttfBuffer); return result.buffer || result; } /** * ttf格式转换成woff2字体格式(异步,纯 JS 实现直接返回) - * - * @param {ArrayBuffer} ttfBuffer ttf缓冲数组 - * @param {Object} options 选项 - * - * @return {Promise.} woff格式byte流 */ function ttftowoff2async(ttfBuffer) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - return Promise.resolve(ttftowoff2(ttfBuffer, options)); + return Promise.resolve(ttftowoff2(ttfBuffer)); } diff --git a/vendor/fonteditor-core/lib/ttf/ttfwriter.js b/vendor/fonteditor-core/lib/ttf/ttfwriter.js index d5dcb3e..71c84ad 100644 --- a/vendor/fonteditor-core/lib/ttf/ttfwriter.js +++ b/vendor/fonteditor-core/lib/ttf/ttfwriter.js @@ -8,6 +8,7 @@ var _writer = _interopRequireDefault(require("./writer")); var _directory = _interopRequireDefault(require("./table/directory")); var _support = _interopRequireDefault(require("./table/support")); var _checkSum = _interopRequireDefault(require("./util/checkSum")); +var _checkSumArrayBuffer = _interopRequireDefault(require("./util/checkSum")).checkSumArrayBuffer; var _error = _interopRequireDefault(require("./error")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } @@ -84,10 +85,11 @@ var TTFWriter = exports.default = /*#__PURE__*/function () { var ttfHeadOffset = 0; /* 优化35+56: 缓存 TableClass 实例,forEach → for 循环 */ - ttf.support.tables = []; var writeTables = ttf.writeOptions.tables; var supportTables = _support.default; var tableInstances = {}; + var supportTablesArr = new Array(writeTables.length); + ttf.support.tables = supportTablesArr; for (var ti = 0, tl = writeTables.length; ti < tl; ti++) { var tableName = writeTables[ti]; var offset = ttfSize; @@ -99,36 +101,40 @@ var TTFWriter = exports.default = /*#__PURE__*/function () { if (tableName === 'head') { ttfHeadOffset = offset; } - if (size % 4) { - size += 4 - size % 4; - } - ttf.support.tables.push({ + size = (size + 3) & ~3; + supportTablesArr[ti] = { name: tableName, checkSum: 0, offset: offset, length: tableSize, size: size - }); + }; ttfSize += size; } var writer = new _writer.default(new ArrayBuffer(ttfSize)); - /* 优化36: 头部直接 view 写入 */ + /* 优化36+184: 头部直接 view 写入,Math.round 替换为位运算 */ var wView = writer.view; var pos = writer.offset; - wView.setInt32(pos, Math.round(ttf.version * 65536), false); pos += 4; + wView.setInt32(pos, ttf.version * 65536 + 0.5 | 0, false); pos += 4; wView.setUint16(pos, ttf.numTables, false); pos += 2; wView.setUint16(pos, ttf.searchRange, false); pos += 2; wView.setUint16(pos, ttf.entrySelector, false); pos += 2; wView.setUint16(pos, ttf.rangeShift, false); pos += 2; writer.offset = pos; - new _directory.default().write(writer, ttf); + /* 优化: directory 实例复用 tableInstances 缓存 */ + if (!tableInstances['directory']) { + tableInstances['directory'] = new _directory.default(); + } + tableInstances['directory'].write(writer, ttf); - /* 优化56+87+179: forEach → for 循环,缓存 buffer 引用,累加各表校验和避免全局重算 */ + /* 优化56+87+179+184: forEach → for 循环,缓存 buffer 引用,累加各表校验和避免全局重算 + * 优化184: 内联 writeEmpty 为 fullView.fill(0),避免 writer.writeEmpty 的函数调用 + 边界检查开销 */ var supportTableList = ttf.support.tables; var buf = writer.getBuffer(); var wholeCheckSum = 0; + var fullView = new Uint8Array(buf); for (var si = 0, sl = supportTableList.length; si < sl; si++) { var table = supportTableList[si]; var tableStart = writer.offset; @@ -137,10 +143,12 @@ var TTFWriter = exports.default = /*#__PURE__*/function () { tableInstances[tName] = new supportTables[tName](); } tableInstances[tName].write(writer, ttf); - if (table.length % 4) { - writer.writeEmpty(4 - table.length % 4); + var pad = table.length % 4; + if (pad) { + fullView.fill(0, wView.byteOffset + writer.offset, wView.byteOffset + writer.offset + (4 - pad)); + writer.offset += 4 - pad; } - table.checkSum = (0, _checkSum.default)(buf, tableStart, table.size); + table.checkSum = _checkSumArrayBuffer(buf, tableStart, table.size, fullView); wholeCheckSum = (wholeCheckSum + table.checkSum) >>> 0; } @@ -152,7 +160,7 @@ var TTFWriter = exports.default = /*#__PURE__*/function () { } /* 优化179: 用累加的各表校验和替代全局 checkSum,避免重遍历整个 buffer */ - var ttfCheckSum = (0xB1B0AFBA - wholeCheckSum + 0x100000000) % 0x100000000; + var ttfCheckSum = (0xB1B0AFBA - wholeCheckSum) >>> 0; csView.setUint32(ttfHeadOffset + 8, ttfCheckSum, false); delete ttf.writeOptions; delete ttf.support; @@ -168,22 +176,27 @@ var TTFWriter = exports.default = /*#__PURE__*/function () { if (!ttf['OS/2'] || !ttf.head || !ttf.name) { _error.default.raise(10204); } - var tables = SUPPORT_TABLES.slice(0); + /* 优化: 无 hinting/kerning 时直接使用 SUPPORT_TABLES,避免 concat 开销 */ + /* 优化186: 使用 slice() 创建副本,防止 push 变异模块级数组导致后续调用表膨胀 */ + var tables = SUPPORT_TABLES; ttf.writeOptions = {}; - /* 优化56: forEach → for 循环 */ - if (this.options.hinting) { - var hintTables = ['cvt', 'fpgm', 'prep', 'gasp', 'GPOS', 'kern', 'kerx']; - for (var i = 0; i < hintTables.length; i++) { - if (ttf[hintTables[i]]) { - tables.push(hintTables[i]); + /* 优化228: 合并 hinting 和 kerning 分支,消除重复 slice 检查 */ + if (this.options.hinting || this.options.kerning) { + tables = SUPPORT_TABLES.slice(); + if (this.options.hinting) { + var hintTables = ['cvt', 'fpgm', 'prep', 'gasp', 'GPOS', 'kern', 'kerx']; + for (var i = 0; i < hintTables.length; i++) { + if (ttf[hintTables[i]]) { + tables.push(hintTables[i]); + } } } - } - if (this.options.kerning) { - var kernTables = ['GPOS', 'kern', 'kerx']; - for (var j = 0; j < kernTables.length; j++) { - if (ttf[kernTables[j]]) { - tables.push(kernTables[j]); + if (this.options.kerning) { + var kernTables = ['GPOS', 'kern', 'kerx']; + for (var j = 0; j < kernTables.length; j++) { + if (ttf[kernTables[j]]) { + tables.push(kernTables[j]); + } } } } diff --git a/vendor/fonteditor-core/lib/ttf/util/checkSum.js b/vendor/fonteditor-core/lib/ttf/util/checkSum.js index 73321eb..68dd22e 100644 --- a/vendor/fonteditor-core/lib/ttf/util/checkSum.js +++ b/vendor/fonteditor-core/lib/ttf/util/checkSum.js @@ -4,34 +4,43 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.default = checkSum; +exports.checkSumArrayBuffer = checkSumArrayBuffer; /** * @file ttf table校验函数 * @author mengke01(kekee000@gmail.com) */ /** - * 优化178: 使用 Uint8Array 直接读取并手动拼接大端序 uint32,消除 DataView 开销 + * 优化205+229+248: 支持传入预创建的 Uint8Array,使用 DataView.getUint32 替代手动字节组装 + * 注意: bytes 必须是从 offset 开始的子视图,或者 offset 必须为 0 */ -function checkSumArrayBuffer(buffer, offset, length) { +function checkSumArrayBuffer(buffer, offset, length, bytes) { if (offset === undefined) offset = 0; length = length == null ? buffer.byteLength : length; if (offset + length > buffer.byteLength) { throw new Error('check sum out of bound'); } - var bytes = new Uint8Array(buffer, offset, length); + /** 优化229: 当传入 fullView 时,用 subarray 创建正确的偏移视图(不拷贝数据) */ + if (!bytes) { + bytes = new Uint8Array(buffer, offset, length); + } else if (offset > 0) { + bytes = bytes.subarray(offset, offset + length); + } + /** 优化248: 使用 DataView.getUint32 替代手动字节组装,减少 4x 数组访问 */ + var view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); var nLongs = length >> 2; var sum = 0; var i = 0; while (i < nLongs) { - sum = (sum + (bytes[i] << 24 | bytes[i + 1] << 16 | bytes[i + 2] << 8 | bytes[i + 3]) >>> 0) | 0; - i += 4; + sum = (sum + view.getUint32(i << 2, false)) | 0; + i++; } var leftBytes = length - nLongs * 4; if (leftBytes) { var off = nLongs << 2; var val = 0; for (var k = 0; k < leftBytes; k++) { - val = (val | bytes[off + k] << (leftBytes - 1 - k) * 8) >>> 0; + val = (val << 8) | view.getUint8(off + k); } sum = (sum + val) | 0; } @@ -54,9 +63,11 @@ function checkSumArray(buffer, offset, length) { var leftBytes = length - nLongs * 4; if (leftBytes) { var off = nLongs << 2; + var val = 0; for (var k = 0; k < leftBytes; k++) { - sum = (sum + (buffer[off + k] << (leftBytes - 1 - k) * 8)) | 0; + val = (val << 8) | buffer[off + k]; } + sum = (sum + val) | 0; } return sum >>> 0; } diff --git a/vendor/fonteditor-core/lib/ttf/util/glyfAdjust.js b/vendor/fonteditor-core/lib/ttf/util/glyfAdjust.js index eba39e0..6aa376a 100644 --- a/vendor/fonteditor-core/lib/ttf/util/glyfAdjust.js +++ b/vendor/fonteditor-core/lib/ttf/util/glyfAdjust.js @@ -31,21 +31,24 @@ function glyfAdjust(g) { var offsetX = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; var offsetY = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0; var useCeil = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true; + /* 优化: 合并三次 forEach 为单次 for 循环,消除闭包分配 */ if (g.contours && g.contours.length) { - if (scaleX !== 1 || scaleY !== 1) { - g.contours.forEach(function (contour) { - (0, _pathAdjust.default)(contour, scaleX, scaleY); - }); - } - if (offsetX !== 0 || offsetY !== 0) { - g.contours.forEach(function (contour) { - (0, _pathAdjust.default)(contour, 1, 1, offsetX, offsetY); - }); - } - if (false !== useCeil) { - g.contours.forEach(function (contour) { - (0, _pathCeil.default)(contour); - }); + var needScale = scaleX !== 1 || scaleY !== 1; + var needOffset = offsetX !== 0 || offsetY !== 0; + var needCeil = useCeil !== false; + if (needScale || needOffset || needCeil) { + for (var ci = 0, cl = g.contours.length; ci < cl; ci++) { + var contour = g.contours[ci]; + if (needScale) { + (0, _pathAdjust.default)(contour, scaleX, scaleY); + } + if (needOffset) { + (0, _pathAdjust.default)(contour, 1, 1, offsetX, offsetY); + } + if (needCeil) { + (0, _pathCeil.default)(contour); + } + } } } diff --git a/vendor/fonteditor-core/lib/ttf/util/optimizettf.js b/vendor/fonteditor-core/lib/ttf/util/optimizettf.js index ff25758..c9727e0 100644 --- a/vendor/fonteditor-core/lib/ttf/util/optimizettf.js +++ b/vendor/fonteditor-core/lib/ttf/util/optimizettf.js @@ -13,6 +13,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de * @author mengke01(kekee000@gmail.com) */ +/** 优化: 模块级排序函数,避免每个 glyph 创建闭包 */ +function numericSort(a, b) { return a - b; } + /** * 优化103+116: 从 parse 阶段的 TypedArray 直接计算 precomputed 数据,跳过 contour 数组构建 * 每个字形独立分配 buffer,避免共享 buffer 的复杂性 @@ -32,10 +35,8 @@ function ceilReduceAndSizeFromTypedArrays(glyf) { var REPEAT = _glyFlag.default.REPEAT; var numPoints = xArr.length; - var flagsC = new Array(numPoints); + var flagsC = new Uint8Array(numPoints); var fi = 0; - var prevX = 0, prevY = 0; - var isFirst = true; var prevFlag = -1; var repeatPoint = -1; var encodedCoordSize = 0; @@ -46,66 +47,59 @@ function ceilReduceAndSizeFromTypedArrays(glyf) { var yCoordBuf = 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; + /** 优化: 首点提取到循环外,消除 isFirst 条件分支 */ + if (numPoints > 0) { + var px = xArr[0], py = yArr[0]; + var onCurve = !!(flagsArr[0] & ONCURVE); 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 (px === 0) flag += XSAME; + else if (px > -256 && px < 256) { flag += XSHORT; if (px > 0) flag += XSAME; xCoordBuf[xbi++] = px > 0 ? px : -px; encodedCoordSize += 1; } + else { xCoordBuf[xbi++] = (px >> 8) & 0xFF; xCoordBuf[xbi++] = px & 0xFF; encodedCoordSize += 2; } + if (py === 0) flag += YSAME; + else if (py > -256 && py < 256) { flag += YSHORT; if (py > 0) flag += YSAME; yCoordBuf[ybi++] = py > 0 ? py : -py; encodedCoordSize += 1; } + else { yCoordBuf[ybi++] = (py >> 8) & 0xFF; yCoordBuf[ybi++] = py & 0xFF; encodedCoordSize += 2; } + flagsC[fi++] = prevFlag = flag; + var 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; - } + for (var pi = 1; pi < numPoints; pi++) { + px = xArr[pi]; py = yArr[pi]; + onCurve = !!(flagsArr[pi] & ONCURVE); + flag = onCurve ? ONCURVE : 0; + var dx = px - prevX, dy = py - prevY; + prevX = px; prevY = py; - 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 (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; + if (flag === prevFlag) { + if (repeatPoint === -1) { + repeatPoint = fi - 1; + flagsC[repeatPoint] |= REPEAT; + flagsC[fi++] = 1; + } else if (flagsC[repeatPoint + 1] < 255) { + ++flagsC[repeatPoint + 1]; + } else { + /* 优化188: repeat count 达到 255 上限,结束当前 repeat 并开始新 flag */ + repeatPoint = -1; + flagsC[fi++] = flag; + prevFlag = flag; + } } else { - ++flagsC[repeatPoint + 1]; + repeatPoint = -1; + flagsC[fi++] = flag; + prevFlag = flag; } - } else { - repeatPoint = -1; - flagsC[fi++] = flag; - prevFlag = flag; } } - flagsC.length = fi; + flagsC = flagsC.subarray(0, fi); - /* 优化103: 不构建 contour 数组,直接存储元数据 */ - glyf.contours = new Array(numContours); + /* 优化201: 使用鸭子类型替代空数组分配,只提供 length 属性 */ + glyf.contours = { length: numContours }; glyf._flatContours = true; /* 优化103: 存储每个 contour 的点数,供 write 计算 endPtsOfContours */ glyf._pointsPerContour = new Array(numContours); @@ -115,23 +109,22 @@ function ceilReduceAndSizeFromTypedArrays(glyf) { glyf._numContours = numContours; glyf._totalPoints = numPoints; - glyf._precomputedGlyfSupport = { - flags: flagsC, - encodedCoordSize: encodedCoordSize, - xBuf: xCoordBuf, - xLen: xbi, - yBuf: yCoordBuf, - yLen: ybi - }; + /** 优化256: 在写入方直接 trim subarray,消除 sizeof.js 二次 slicing */ + glyf._preFlags = flagsC; + glyf._preEncodedCoordSize = encodedCoordSize; + glyf._preXBuf = xCoordBuf.subarray(0, xbi); + glyf._preYBuf = yCoordBuf.subarray(0, ybi); + glyf._preXLen = xbi; + glyf._preYLen = ybi; - delete glyf._xArr; - delete glyf._yArr; - delete glyf._flags; - delete glyf.endPtsOfContours; + glyf._xArr = null; + glyf._flags = null; + glyf.endPtsOfContours = null; } /** * 优化84+98+149: 合并 ceil+reduce+flagsAndSize 为单次遍历 + * 优化256: 写入方直接 trim subarray,消除 sizeof.js 二次 slicing */ function ceilReduceAndSizeFlat(glyf) { var contours = glyf.contours; @@ -144,7 +137,7 @@ function ceilReduceAndSizeFlat(glyf) { } contours.length = writeIdx; if (0 === contours.length) { - delete glyf.contours; + glyf.contours = null; return; } @@ -161,12 +154,10 @@ function ceilReduceAndSizeFlat(glyf) { var totalPoints = 0; for (var j = 0, cl = contours.length; j < cl; j++) { - totalPoints += contours[j].length / 3; + totalPoints += contours[j].length / 3 | 0; } - var flagsC = new Array(totalPoints); + var flagsC = new Uint8Array(totalPoints); var fi = 0; - var prevX = 0, prevY = 0; - var isFirst = true; var prevFlag = -1; var repeatPoint = -1; var encodedCoordSize = 0; @@ -177,25 +168,31 @@ function ceilReduceAndSizeFlat(glyf) { var yCoordBuf = new Uint8Array(neededSize); var xbi = 0, ybi = 0; + /** 优化213: 首点提取到循环外,消除 per-point 条件分支 */ + var firstContour = contours[0]; + var fpx = firstContour[0], fpy = firstContour[1]; + var fOnCurve = firstContour[2]; + var fFlag = fOnCurve ? ONCURVE : 0; + if (fpx === 0) fFlag += XSAME; + else if (fpx > -256 && fpx < 256) { fFlag += XSHORT; if (fpx > 0) fFlag += XSAME; xCoordBuf[xbi++] = fpx > 0 ? fpx : -fpx; encodedCoordSize += 1; } + else { xCoordBuf[xbi++] = (fpx >> 8) & 0xFF; xCoordBuf[xbi++] = fpx & 0xFF; encodedCoordSize += 2; } + if (fpy === 0) fFlag += YSAME; + else if (fpy > -256 && fpy < 256) { fFlag += YSHORT; if (fpy > 0) fFlag += YSAME; yCoordBuf[ybi++] = fpy > 0 ? fpy : -fpy; encodedCoordSize += 1; } + else { yCoordBuf[ybi++] = (fpy >> 8) & 0xFF; yCoordBuf[ybi++] = fpy & 0xFF; encodedCoordSize += 2; } + flagsC[fi++] = prevFlag = fFlag; + var prevX = fpx, prevY = fpy; + + var skipFirst = true; 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) { + if (skipFirst) { skipFirst = false; continue; } 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; + var dx = px - prevX, dy = py - prevY; + prevX = px; prevY = py; if (dx === 0) { flag += XSAME; @@ -225,13 +222,18 @@ function ceilReduceAndSizeFlat(glyf) { encodedCoordSize += 2; } - if (flag === prevFlag && !isFirst) { + if (flag === prevFlag) { if (repeatPoint === -1) { repeatPoint = fi - 1; flagsC[repeatPoint] |= REPEAT; flagsC[fi++] = 1; - } else { + } else if (flagsC[repeatPoint + 1] < 255) { ++flagsC[repeatPoint + 1]; + } else { + /* 优化188: repeat count 达到 255 上限,结束当前 repeat 并开始新 flag */ + repeatPoint = -1; + flagsC[fi++] = flag; + prevFlag = flag; } } else { repeatPoint = -1; @@ -241,16 +243,17 @@ function ceilReduceAndSizeFlat(glyf) { } } - flagsC.length = fi; + flagsC = flagsC.subarray(0, fi); - glyf._precomputedGlyfSupport = { - flags: flagsC, - encodedCoordSize: encodedCoordSize, - xBuf: xCoordBuf, - xLen: xbi, - yBuf: yCoordBuf, - yLen: ybi - }; + /** 优化256: 在写入方直接 trim subarray,消除 sizeof.js 二次 slicing */ + glyf._preFlags = flagsC; + glyf._preEncodedCoordSize = encodedCoordSize; + glyf._preXBuf = xCoordBuf.subarray(0, xbi); + glyf._preYBuf = yCoordBuf.subarray(0, ybi); + glyf._preXLen = xbi; + glyf._preYLen = ybi; + glyf._totalPoints = totalPoints; + glyf._numContours = contours.length; } /** @@ -270,11 +273,6 @@ function optimizettf(ttf) { var m_firstChar = 0x10FFFF, m_lastChar = -1; var m_maxPoints = 0, m_maxContours = 0; - /* 优化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]; if (glyf.compound) { @@ -282,7 +280,7 @@ function optimizettf(ttf) { } if (glyf.unicode) { if (glyf.unicode.length > 1) { - glyf.unicode.sort(function (a, b) { return a - b; }); + glyf.unicode.sort(numericSort); } var unicode = glyf.unicode; for (var ui = 0, ul = unicode.length; ui < ul; ui++) { @@ -314,22 +312,11 @@ function optimizettf(ttf) { /** * ⚠️ 关键:必须收集 maxPoints/maxContours,否则 maxp 表中这两个值为 0, * 浏览器会据此跳过渲染(表现为字体加载成功但文字显示为空白/fallback)。 - * 这是 OTF→TTF 转换字形的必经路径(_flatContours 由 parseCFFGlyph 生成), - * 之前已因为同样的问题修复过对象 contours 路径(commit 97f4d72), - * 所有涉及 contours 的分支都必须更新这两个值! - * 注意:ceilReduceAndSizeFlat 可能删除 glyf.contours(当所有 contour 长度 ≤ 6 时), - * 所以必须在调用之后检查 glyf.contours 是否仍存在。 + * ceilReduceAndSizeFlat 已缓存 _totalPoints 和 _numContours。 */ if (glyf.contours) { - var flatNumC = glyf.contours.length; - if (flatNumC > 0) { - if (flatNumC > m_maxContours) m_maxContours = flatNumC; - var flatTotalPts = 0; - for (var fci = 0; fci < flatNumC; fci++) { - flatTotalPts += glyf.contours[fci].length / 3; - } - if (flatTotalPts > m_maxPoints) m_maxPoints = flatTotalPts; - } + if (glyf._numContours > m_maxContours) m_maxContours = glyf._numContours; + if (glyf._totalPoints > m_maxPoints) m_maxPoints = glyf._totalPoints; } } else { /* 对象 contours 格式也需要收集 maxPoints/maxContours */ @@ -407,16 +394,6 @@ function optimizettf(ttf) { } } 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; } diff --git a/vendor/fonteditor-core/lib/ttf/util/otfContours2ttfContours.js b/vendor/fonteditor-core/lib/ttf/util/otfContours2ttfContours.js index 98d907a..9f2593d 100644 --- a/vendor/fonteditor-core/lib/ttf/util/otfContours2ttfContours.js +++ b/vendor/fonteditor-core/lib/ttf/util/otfContours2ttfContours.js @@ -57,53 +57,67 @@ function normalizeContourFlat(arr) { if (!hasConsecutiveOff && firstOnCurve) return arr; - /** 构建结果:[x, y, flag, ...] */ - var result = []; + /** 优化192: 预分配结果数组,索引赋值替代 push,消除动态扩容 */ + var maxLen = len + (len / 3 | 0) * 3 + 6; + var result = new Array(maxLen); + var ri = 0; if (prependX != null) { - result.push(prependX, prependY, 1); + result[ri++] = prependX; result[ri++] = prependY; result[ri++] = 1; } for (var k = 0; k < len; k += 3) { - result.push(arr[k], arr[k + 1], arr[k + 2]); + result[ri++] = arr[k]; result[ri++] = arr[k + 1]; result[ri++] = arr[k + 2]; if (!arr[k + 2] && k + 5 < len && !arr[k + 5]) { - var mx = (arr[k] + arr[k + 3]) * 0.5; - var my = (arr[k + 1] + arr[k + 4]) * 0.5; - result.push(mx, my, 1); + result[ri++] = (arr[k] + arr[k + 3]) * 0.5; + result[ri++] = (arr[k + 1] + arr[k + 4]) * 0.5; + result[ri++] = 1; } } /** 检查 wrap-around 连续 offCurve */ if (prependX == null && len >= 6 && !arr[len - 1] && !arr[2]) { - var mx2 = (arr[len - 3] + arr[0]) * 0.5; - var my2 = (arr[len - 2] + arr[1]) * 0.5; - result.push(mx2, my2, 1); + result[ri++] = (arr[len - 3] + arr[0]) * 0.5; + result[ri++] = (arr[len - 2] + arr[1]) * 0.5; + result[ri++] = 1; } + result.length = ri; return result; } /** * 转换已标准化的轮廓,全扁平数组操作 * 优化178: 输入和输出都是扁平数组 [x, y, flag, ...] + * 精度优化: 全程浮点运算,最后统一 Math.round,消除累积取整误差 */ function transformContourFlat(arr) { var normalized = normalizeContourFlat(arr); - if (normalized.length < 6) return []; + if (normalized.length < 6) return null; - var contour = []; - /** 第一个点一定是 onCurve */ + /** 优化196: 预分配 contour 数组,最坏情况每点变成两段二次贝塞尔(6元素) */ + var estimatedMax = normalized.length * 2 + 6; + var contour = new Array(estimatedMax); + var ci = 0; + /** 第一个点一定是 onCurve — 保持浮点 */ var r = Math.round; - contour.push(r(normalized[0]), r(normalized[1]), 1); + var firstX = normalized[0], firstY = normalized[1]; + var rfx = r(firstX), rfy = r(firstY); + contour[ci++] = rfx; contour[ci++] = rfy; contour[ci++] = 1; + + /** 优化: 在转换过程中同时计算包围盒,避免二次遍历 */ + var xMin = rfx, xMax = rfx, yMin = rfy, yMax = rfy; var i = 3; var nLen = normalized.length; - var lastX = r(normalized[0]); - var lastY = r(normalized[1]); + var lastX = firstX; + var lastY = firstY; while (i < nLen) { var isOnCurve = normalized[i + 2]; if (isOnCurve) { /** 线段:直接添加 onCurve 端点 */ - var px = r(normalized[i]); - var py = r(normalized[i + 1]); - contour.push(px, py, 1); + var px = normalized[i], py = normalized[i + 1]; + var rpx = r(px), rpy = r(py); + contour[ci++] = rpx; contour[ci++] = rpy; contour[ci++] = 1; + if (rpx < xMin) xMin = rpx; else if (rpx > xMax) xMax = rpx; + if (rpy < yMin) yMin = rpy; else if (rpy > yMax) yMax = rpy; lastX = px; lastY = py; i += 3; @@ -119,37 +133,47 @@ function transformContourFlat(arr) { if (endIdx < nLen) { endX = normalized[endIdx]; endY = normalized[endIdx + 1]; } else { - endX = normalized[0]; endY = normalized[1]; + endX = firstX; endY = firstY; } i = endIdx + 3; - /** 三次→二次贝塞尔转换 */ - var bezierFlat = (0, _bezierCubic2Q.bezierCubic2Q2Raw)(lastX, lastY, c1x, c1y, c2x, c2y, endX, endY); - for (var bi = 0, bl = bezierFlat.length; bi < bl; bi += 4) { - contour.push(r(bezierFlat[bi]), r(bezierFlat[bi + 1]), 0); - contour.push(r(bezierFlat[bi + 2]), r(bezierFlat[bi + 3]), 1); + /** 优化255: 使用 bezierCubic2Q2PushRounded,写入时直接取整,消除二次遍历 */ + var ciBefore = ci; + ci = (0, _bezierCubic2Q.bezierCubic2Q2PushRounded)(lastX, lastY, c1x, c1y, c2x, c2y, endX, endY, contour, ci); + /** 更新 bbox(坐标已在 PushRounded 中取整) */ + for (var bi = ciBefore; bi < ci; bi += 3) { + var bx = contour[bi]; + if (bx < xMin) xMin = bx; else if (bx > xMax) xMax = bx; + var by = contour[bi + 1]; + if (by < yMin) yMin = by; else if (by > yMax) yMax = by; } - lastX = r(endX); - lastY = r(endY); + lastX = endX; + lastY = endY; } else { /** 单个 offCurve → 二次贝塞尔曲线(TTF 原生支持) */ var endX2, endY2; if (nextIdx < nLen && normalized[nextIdx + 2]) { endX2 = normalized[nextIdx]; endY2 = normalized[nextIdx + 1]; - i = nextIdx + 3; } else { - endX2 = normalized[0]; endY2 = normalized[1]; - i = nextIdx + 3; + endX2 = firstX; endY2 = firstY; } - contour.push(r(c1x), r(c1y), 0); - contour.push(r(endX2), r(endY2), 1); - lastX = r(endX2); - lastY = r(endY2); + i = nextIdx + 3; + var rc1x = r(c1x), rc1y = r(c1y); + var re2x = r(endX2), re2y = r(endY2); + contour[ci++] = rc1x; contour[ci++] = rc1y; contour[ci++] = 0; + contour[ci++] = re2x; contour[ci++] = re2y; contour[ci++] = 1; + if (rc1x < xMin) xMin = rc1x; else if (rc1x > xMax) xMax = rc1x; + if (rc1y < yMin) yMin = rc1y; else if (rc1y > yMax) yMax = rc1y; + if (re2x < xMin) xMin = re2x; else if (re2x > xMax) xMax = re2x; + if (re2y < yMin) yMin = re2y; else if (re2y > yMax) yMax = re2y; + lastX = endX2; + lastY = endY2; } } } - return contour; + contour.length = ci; + return { contour: contour, xMin: xMin, yMin: yMin, xMax: xMax, yMax: yMax }; } /** @@ -160,47 +184,46 @@ function otfContours2ttfContours(otfContours) { if (!otfContours || !otfContours.length) { return { contours: otfContours }; } - var contours = []; - var left, right, top, bottom; - var found = false; + /** 优化200: 预分配 contours 数组 */ + var contours = new Array(otfContours.length); + var cLen = 0; + /** 优化221: 用 Infinity 初始化,消除 found 分支 */ + var left = Infinity, right = -Infinity, top = Infinity, bottom = -Infinity; + /** 优化221: 提升 isFlat 检测到循环外,同一 glyph 的所有 contour 格式一致 */ + var isFlat = otfContours[0] && (otfContours[0]._flatContours || (typeof otfContours[0][0] === 'number' && typeof otfContours[0][1] === 'number')); for (var i = 0, l = otfContours.length; i < l; i++) { var otfContour = otfContours[i]; if (!otfContour || otfContour.length < 6) continue; - /** 检测输入格式:扁平数组 vs 对象数组 */ - var isFlat = otfContour._flatContours || (typeof otfContour[0] === 'number' && typeof otfContour[1] === 'number'); var contour; + var contourBbox; if (isFlat) { - contour = transformContourFlat(otfContour); + var result = transformContourFlat(otfContour); + if (!result) continue; + contour = result.contour; + contourBbox = result; } else { contour = transformContourObj(otfContour); } if (contour.length < 3) continue; - contours.push(contour); + contours[cLen++] = contour; /** 计算包围盒 */ - if (typeof contour[0] === 'number') { - for (var ci = 0, cl = contour.length; ci < cl; ci += 3) { - var px = contour[ci], py = contour[ci + 1]; - if (!found) { - left = right = px; top = bottom = py; found = true; - } else { - if (px < left) left = px; else if (px > right) right = px; - if (py < top) top = py; else if (py > bottom) bottom = py; - } - } + if (contourBbox) { + /** 优化: bbox 已在 transformContourFlat 中计算,直接合并 */ + if (contourBbox.xMin < left) left = contourBbox.xMin; + if (contourBbox.xMax > right) right = contourBbox.xMax; + if (contourBbox.yMin < top) top = contourBbox.yMin; + if (contourBbox.yMax > bottom) bottom = contourBbox.yMax; } else { - for (var ci2 = 0, cl2 = contour.length; ci2 < cl2; ci2++) { - var p = contour[ci2]; - if (!found) { - left = right = p.x; top = bottom = p.y; found = true; - } else { - if (p.x < left) left = p.x; else if (p.x > right) right = p.x; - if (p.y < top) top = p.y; else if (p.y > bottom) bottom = p.y; - } + for (var ci = 0, cl = contour.length; ci < cl; ci++) { + var p = contour[ci]; + if (p.x < left) left = p.x; else if (p.x > right) right = p.x; + if (p.y < top) top = p.y; else if (p.y > bottom) bottom = p.y; } } } + contours.length = cLen; return { contours: contours, xMin: left, diff --git a/vendor/fonteditor-core/lib/ttf/util/reduceGlyf.js b/vendor/fonteditor-core/lib/ttf/util/reduceGlyf.js index 8cb2d7d..4fe698b 100644 --- a/vendor/fonteditor-core/lib/ttf/util/reduceGlyf.js +++ b/vendor/fonteditor-core/lib/ttf/util/reduceGlyf.js @@ -19,21 +19,20 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de */ function reduceGlyf(glyf) { var contours = glyf.contours; - var contour; /* 优化66: 扁平格式下 contour.length 是点的3倍 */ var isFlat = glyf._flatContours; var minLen = isFlat ? 6 : 2; - for (var j = contours.length - 1; j >= 0; j--) { - contour = (0, _reducePath.default)(contours[j]); - - /* 空轮廓:扁平格式 <= 6 元素(2个点),对象格式 <= 2 个点 */ - if (contour.length <= minLen) { - contours.splice(j, 1); - continue; + /** 优化: 使用 writeIdx 替代 splice,O(n) 替代 O(n*m) */ + var writeIdx = 0; + for (var j = 0, cl = contours.length; j < cl; j++) { + var contour = (0, _reducePath.default)(contours[j]); + if (contour.length > minLen) { + contours[writeIdx++] = contour; } } - if (0 === glyf.contours.length) { - delete glyf.contours; + contours.length = writeIdx; + if (0 === writeIdx) { + glyf.contours = null; } return glyf; } \ No newline at end of file diff --git a/vendor/fonteditor-core/lib/ttf/util/string.js b/vendor/fonteditor-core/lib/ttf/util/string.js index 4e4de99..f07c786 100644 --- a/vendor/fonteditor-core/lib/ttf/util/string.js +++ b/vendor/fonteditor-core/lib/ttf/util/string.js @@ -67,11 +67,8 @@ var _default = exports.default = { * @return {string} string */ getString: function getString(bytes) { - var s = ''; - for (var i = 0, l = bytes.length; i < l; i++) { - s += String.fromCharCode(bytes[i]); - } - return s; + /** 优化243: fromCharCode.apply 批量转换,消除逐字节字符串拼接的中间分配 */ + return String.fromCharCode.apply(null, bytes); }, /** * 获取unicode的名字值 @@ -94,27 +91,30 @@ var _default = exports.default = { */ toUTF8Bytes: function toUTF8Bytes(str) { str = stringify(str); - var byteArray = []; + /* 优化: 预分配 Uint8Array 替代动态 push,避免数组扩容 */ + var byteArr = new Uint8Array(str.length * 4); + var bi = 0; for (var i = 0, l = str.length; i < l; i++) { var ch = str.charCodeAt(i); if (ch <= 0x7F) { - byteArray.push(ch); + byteArr[bi++] = ch; } else if (ch <= 0x7FF) { - byteArray.push(0xC0 | (ch >> 6)); - byteArray.push(0x80 | (ch & 0x3F)); + byteArr[bi++] = 0xC0 | (ch >> 6); + byteArr[bi++] = 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)); + byteArr[bi++] = 0xE0 | (ch >> 12); + byteArr[bi++] = 0x80 | ((ch >> 6) & 0x3F); + byteArr[bi++] = 0x80 | (ch & 0x3F); } else { 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)); + byteArr[bi++] = 0xF0 | (cp >> 18); + byteArr[bi++] = 0x80 | ((cp >> 12) & 0x3F); + byteArr[bi++] = 0x80 | ((cp >> 6) & 0x3F); + byteArr[bi++] = 0x80 | (cp & 0x3F); } } - return byteArray; + /* 优化: 直接返回 Uint8Array subarray,writeBytes 对 Uint8Array 有快速路径 */ + return byteArr.subarray(0, bi); }, /** * 转换成usc2的字节数组 @@ -124,13 +124,14 @@ var _default = exports.default = { */ toUCS2Bytes: function toUCS2Bytes(str) { str = stringify(str); - var byteArray = []; - for (var i = 0, l = str.length, ch; i < l; i++) { - ch = str.charCodeAt(i); - byteArray.push(ch >> 8); - byteArray.push(ch & 0xFF); + /* 优化: 预分配 Uint8Array 替代动态 push */ + var byteArr = new Uint8Array(str.length * 2); + for (var i = 0, l = str.length; i < l; i++) { + var ch = str.charCodeAt(i); + byteArr[i * 2] = ch >> 8; + byteArr[i * 2 + 1] = ch & 0xFF; } - return byteArray; + return byteArr; }, /** * 获取pascal string 字节数组 @@ -139,13 +140,14 @@ var _default = exports.default = { * @return {Array.} byteArray byte数组 */ toPascalStringBytes: function toPascalStringBytes(str) { - var bytes = []; + /* 优化: 返回 Uint8Array,writeBytes 对 Uint8Array 有快速路径 */ var length = str ? str.length < 256 ? str.length : 255 : 0; - bytes.push(length); + var bytes = new Uint8Array(1 + (str ? str.length : 0)); + bytes[0] = length; for (var i = 0, l = str.length; i < l; i++) { var c = str.charCodeAt(i); // non-ASCII characters are substituted with '*' - bytes.push(c < 128 ? c : 42); + bytes[i + 1] = c < 128 ? c : 42; } return bytes; }, @@ -156,6 +158,10 @@ var _default = exports.default = { * @return {string} 字符串 */ getUTF8String: function getUTF8String(bytes) { + /** 优化254: 使用 TextDecoder 替代手动 UTF-8 解码 + unescape */ + if (typeof TextDecoder !== 'undefined') { + return new TextDecoder('utf-8', { fatal: false }).decode(bytes); + } var str = ''; for (var i = 0, l = bytes.length; i < l; i++) { if (bytes[i] < 0x7F) { @@ -173,11 +179,14 @@ var _default = exports.default = { * @return {string} 字符串 */ getUCS2String: function getUCS2String(bytes) { - var str = ''; - for (var i = 0, l = bytes.length; i < l; i += 2) { - str += String.fromCharCode((bytes[i] << 8) + bytes[i + 1]); + /** 优化253: 收集 charCodes 到数组,单次 fromCharCode.apply 替代逐字拼接 */ + var len = bytes.length; + if (len === 0) return ''; + var codes = new Array(len >> 1); + for (var i = 0, j = 0; i < len; i += 2, j++) { + codes[j] = (bytes[i] << 8) + bytes[i + 1]; } - return str; + return String.fromCharCode.apply(null, codes); }, /** * 读取 pascal string diff --git a/vendor/fonteditor-core/lib/ttf/util/transformGlyfContours.js b/vendor/fonteditor-core/lib/ttf/util/transformGlyfContours.js index e05d713..0dc7f2f 100644 --- a/vendor/fonteditor-core/lib/ttf/util/transformGlyfContours.js +++ b/vendor/fonteditor-core/lib/ttf/util/transformGlyfContours.js @@ -14,15 +14,15 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de */ /** - * 优化15+66: 扁平格式单次遍历 pathTransform + pathCeil - * 先仿射变换坐标,再四舍五入,一次循环完成 + * 优化15+66+197: 扁平格式单次遍历 pathTransform + pathCeil + * 优化197: 使用 +0.5|0 替代 Math.round,避免函数调用开销 */ function transformAndCeilFlat(contour, a, b, c, d, e, f) { for (var i = 0, l = contour.length; i < l; i += 3) { var x = contour[i]; var y = contour[i + 1]; - contour[i] = Math.round(x * a + y * c + e); - contour[i + 1] = Math.round(x * b + y * d + f); + contour[i] = (x * a + y * c + e + 0.5) | 0; + contour[i + 1] = (x * b + y * d + f + 0.5) | 0; } return contour; } @@ -43,10 +43,13 @@ function transformGlyfContours(glyf, ttf) { return glyf; } var compoundContours = []; - glyf.glyfs.forEach(function (g) { + /* 优化: forEach → for 循环,消除闭包分配 */ + var glyfs = glyf.glyfs; + for (var gi = 0, gl = glyfs.length; gi < gl; gi++) { + var g = glyfs[gi]; var glyph = ttf.glyf[g.glyphIndex]; if (!glyph || glyph === glyf) { - return; + continue; } // 递归转换contours @@ -55,23 +58,25 @@ function transformGlyfContours(glyf, ttf) { } var sourceContours = glyph.compound ? contoursList[g.glyphIndex] || [] : glyph.contours; - var transform = g.transform; + /* 优化259: 提升 transform 属性到局部变量,消除每次迭代的属性链查找 */ + var t = g.transform; + var ta = t.a, tb = t.b, tc = t.c, td = t.d, te = t.e, tf = t.f; if (sourceContours.length && typeof sourceContours[0][0] === 'number') { /* 优化14+15+66: 扁平格式 - 浅拷贝 + 单次遍历 transform+ceil */ for (var i = 0, l = sourceContours.length; i < l; i++) { var contour = sourceContours[i].slice(); - compoundContours.push(transformAndCeilFlat(contour, transform.a, transform.b, transform.c, transform.d, transform.e, transform.f)); + compoundContours.push(transformAndCeilFlat(contour, ta, tb, tc, td, te, tf)); } } else { /* 传统对象格式 - 深拷贝 + 分别调用 transform+ceil */ var contours = (0, _lang.clone)(sourceContours); for (var i = 0, l = contours.length; i < l; i++) { - (0, _pathTransform.default)(contours[i], transform.a, transform.b, transform.c, transform.d, transform.e, transform.f); + (0, _pathTransform.default)(contours[i], ta, tb, tc, td, te, tf); compoundContours.push((0, _pathCeil.default)(contours[i])); } } - }); + } // eslint-disable-next-line eqeqeq if (null != glyfIndex) { diff --git a/vendor/fonteditor-core/lib/ttf/woff2ttf.js b/vendor/fonteditor-core/lib/ttf/woff2ttf.js index 6128875..26d6238 100644 --- a/vendor/fonteditor-core/lib/ttf/woff2ttf.js +++ b/vendor/fonteditor-core/lib/ttf/woff2ttf.js @@ -66,8 +66,8 @@ function woff2ttf(woffBuffer) { } var writer = new _writer.default(new ArrayBuffer(ttfSize)); // 写头部 - var entrySelector = Math.floor(Math.log(numTables) / Math.LN2); - var searchRange = Math.pow(2, entrySelector) * 16; + var entrySelector = 31 - Math.clz32(numTables); + var searchRange = (1 << entrySelector) * 16; var rangeShift = numTables * 16 - searchRange; writer.writeUint32(flavor); writer.writeUint16(numTables); diff --git a/vendor/fonteditor-core/lib/ttf/writer.js b/vendor/fonteditor-core/lib/ttf/writer.js index c10afbe..4f6f7b1 100644 --- a/vendor/fonteditor-core/lib/ttf/writer.js +++ b/vendor/fonteditor-core/lib/ttf/writer.js @@ -24,6 +24,8 @@ if (typeof ArrayBuffer === 'undefined' || typeof DataView === 'undefined') { /** 优化178: 全局 Uint8Array 视图缓存,避免 writeBytes/writeEmpty 每次创建视图 */ var _globalView = null; var _globalViewBuf = null; +/** 优化: 预编译正则,避免 writeLongDateTime 每次创建 RegExp */ +var _isAllDigits = /^\d+$/; // 数据类型 var dataType = { @@ -221,7 +223,7 @@ var Writer = /*#__PURE__*/function () { if (undefined === offset) { offset = this.offset; } - this.writeInt32(Math.round(value * 65536), offset); + this.writeInt32((value * 65536 + 0.5) | 0, offset); return this; } @@ -246,7 +248,7 @@ var Writer = /*#__PURE__*/function () { value = delta; } else if (typeof value.getTime === 'function') { value = value.getTime(); - } else if (/^\d+$/.test(value)) { + } else if (_isAllDigits.test(value)) { value = +value; } else { value = Date.parse(value); diff --git a/vendor/fonteditor-core/woff2/woff2-encode.js b/vendor/fonteditor-core/woff2/woff2-encode.js index 12d984f..09bcedc 100644 --- a/vendor/fonteditor-core/woff2/woff2-encode.js +++ b/vendor/fonteditor-core/woff2/woff2-encode.js @@ -26,6 +26,30 @@ const BROTLI_PARAM_SIZE_HINT = zlib.constants?.BROTLI_PARAM_SIZE_HINT ?? 4; const BROTLI_OPTIONS_BASE = { params: { [BROTLI_PARAM_QUALITY]: 8 }, }; +/** 优化: 预分配 options 模板,避免每次 encode 创建 computed property name 对象 */ +const BROTLI_OPTIONS_WITH_HINT = { + params: { [BROTLI_PARAM_QUALITY]: 8, [BROTLI_PARAM_SIZE_HINT]: 0 }, +}; + +/* ======== 大端序读写工具函数(模块级,消除闭包分配) ======== */ + +/** 从 Uint8Array 读取无符号 16 位大端序 */ +function readU16(arr, off) { return (arr[off] << 8) | arr[off + 1]; } + +/** 从 Uint8Array 读取有符号 16 位大端序 */ +function readI16(arr, off) { const v = (arr[off] << 8) | arr[off + 1]; return v > 0x7FFF ? v - 0x10000 : v; } + +/** 从 Uint8Array 读取无符号 32 位大端序 */ +function readU32(arr, off) { return (arr[off] << 24 | arr[off + 1] << 16 | arr[off + 2] << 8 | arr[off + 3]) >>> 0; } + +/** 向 Uint8Array 写入无符号 16 位大端序 */ +function writeU16(buf, v, p) { buf[p] = v >> 8; buf[p + 1] = v & 0xFF; } + +/** 向 Uint8Array 写入有符号 16 位大端序 */ +function writeI16(buf, v, p) { buf[p] = v >> 8; buf[p + 1] = v & 0xFF; } + +/** 向 Uint8Array 写入无符号 32 位大端序 */ +function writeU32(buf, v, p) { buf[p] = v >> 24; buf[p + 1] = (v >> 16) & 0xFF; buf[p + 2] = (v >> 8) & 0xFF; buf[p + 3] = v & 0xFF; } /* ======== Known Table Tags 索引表 ======== */ const KNOWN_TAGS = [ @@ -41,6 +65,9 @@ const KNOWN_TAGS = [ /** * 优化:预构建 tag→index Map,消除每次 getTagIndex 的 O(63) 线性搜索 */ +/** 优化: 模块级常量,避免每次 loca 表创建 new Uint8Array(0) */ +const EMPTY_UINT8 = new Uint8Array(0); + const KNOWN_TAG_MAP = new Map(); for (let i = 0; i < KNOWN_TAGS.length; i++) { KNOWN_TAG_MAP.set(KNOWN_TAGS[i], i); @@ -65,19 +92,36 @@ function calcUIntBase128Size(value) { return 5; } -/** 编码 UIntBase128(最多 5 字节,高位在前) */ +/** 编码 UIntBase128(最多 5 字节,高位在前),优化234: 展开常见路径 */ 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; - } + if (value < 0x80) { + buf[offset] = value; + return 1; } - return size; + if (value < 0x4000) { + buf[offset] = (value >>> 7) | 0x80; + buf[offset + 1] = value & 0x7F; + return 2; + } + if (value < 0x200000) { + buf[offset] = (value >>> 14) | 0x80; + buf[offset + 1] = ((value >>> 7) & 0x7F) | 0x80; + buf[offset + 2] = value & 0x7F; + return 3; + } + if (value < 0x10000000) { + buf[offset] = (value >>> 21) | 0x80; + buf[offset + 1] = ((value >>> 14) & 0x7F) | 0x80; + buf[offset + 2] = ((value >>> 7) & 0x7F) | 0x80; + buf[offset + 3] = value & 0x7F; + return 4; + } + buf[offset] = (value >>> 28) | 0x80; + buf[offset + 1] = ((value >>> 21) & 0x7F) | 0x80; + buf[offset + 2] = ((value >>> 14) & 0x7F) | 0x80; + buf[offset + 3] = ((value >>> 7) & 0x7F) | 0x80; + buf[offset + 4] = value & 0x7F; + return 5; } /** 编码 255UInt16(1-3 字节变长) */ @@ -128,10 +172,10 @@ function dataSizeFromTriplet(ti) { } /** - * 编码一个点到 glyphStream,返回 triplet flag 字节 - * 直接写入 buf,不分配数组 + * 仅计算 triplet flag 字节,不写入数据 + * 优化: Pass 1 只需要 flag,数据写入由 Pass 2 的 writePointDataByFlag 完成 */ -function encodePointToBuf(onCurve, dx, dy, buf, offset) { +function calcTripletFlag(onCurve, dx, dy) { const curveBit = onCurve ? 0 : 128; const absDx = dx < 0 ? -dx : dx; const absDy = dy < 0 ? -dy : dy; @@ -141,53 +185,35 @@ function encodePointToBuf(onCurve, dx, dy, buf, offset) { /* 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; + return curveBit + ((absDy & 0xF00) >> 7) + ySignBit; } /* 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; + return curveBit + 10 + ((absDx & 0xF00) >> 7) + xSignBit; } /* 双轴 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; + return curveBit + 20 + (ax & 0x30) + ((ay & 0x30) >> 2) + xySignBits; } /* 双轴 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; + return curveBit + 84 + 12 * ((ax & 0x300) >> 8) + ((ay & 0x300) >> 6) + xySignBits; } /* 双轴 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; + return curveBit + 120 + xySignBits; } /* 兜底 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; + return curveBit + 124 + xySignBits; } /** @@ -243,18 +269,17 @@ function writePointDataByFlag(flag, dx, dy, buf, offset) { * 对 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); + /** 优化222: 使用模块级 readU16/readI16/readU32,消除闭包分配 */ /* 读取 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; + offsets[i] = readU16(locaData, i * 2) * 2; } } else { for (let i = 0; i <= numGlyphs; i++) { - offsets[i] = locaView.getUint32(i * 4, false); + offsets[i] = readU32(locaData, i * 4); } } @@ -278,10 +303,9 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { let hasOverlapBitmap = false; let totalPoints = 0; - const bboxBitmapSize = 4 * Math.floor((numGlyphs + 31) / 32); + const bboxBitmapSize = ((numGlyphs + 31) >>> 5) << 2; const bboxBitmap = new Uint8Array(bboxBitmapSize); const overlapBitmap = new Uint8Array(bboxBitmapSize); - const tmpBuf = new Uint8Array(4); /* 收集每个 glyph 的信息 */ const glyphInfos = new Array(numGlyphs); @@ -295,11 +319,11 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { 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); + const numberOfContours = readI16(glyfData, glyphStart); + const xMin = readI16(glyfData, glyphStart + 2); + const yMin = readI16(glyfData, glyphStart + 4); + const xMax = readI16(glyfData, glyphStart + 6); + const yMax = readI16(glyfData, glyphStart + 8); if (numberOfContours < 0) { /* 复合 glyph */ @@ -311,7 +335,7 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { const MORE_COMPONENTS = 0x0020; const WE_HAVE_INSTRUCTIONS = 0x0100; while (compOff < glyphEnd) { - const compFlags = glyfView.getUint16(compOff, false); + const compFlags = readU16(glyfData, compOff); compOff += 2; compOff += 2; /* glyphIndex */ @@ -330,7 +354,7 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { const componentDataEnd = compOff; if (haveInstructions && compOff + 2 <= glyphEnd) { - instrLength = glyfView.getUint16(compOff, false); + instrLength = readU16(glyfData, compOff); compOff += 2; if (instrLength > 0 && compOff + instrLength <= glyphEnd) { instructions = { offset: compOff, length: instrLength }; @@ -362,23 +386,21 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { /* 简单 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 编码结果 */ + /** 优化226: 合并 endPts 读取与 nPoints 编码,消除 endPtsOfContours TypedArray 分配 */ const nPointsEncoded = new Uint8Array(numberOfContours * 3); let nPointsBytes = 0; let prevEnd = -1; + let lastEndPt = -1; for (let c = 0; c < numberOfContours; c++) { - nPointsBytes += encode255UInt16(endPtsOfContours[c] - prevEnd, nPointsEncoded, nPointsBytes); - prevEnd = endPtsOfContours[c]; + const endPt = readU16(glyfData, dataOff); + dataOff += 2; + nPointsBytes += encode255UInt16(endPt - prevEnd, nPointsEncoded, nPointsBytes); + prevEnd = endPt; } + lastEndPt = prevEnd; totalNPointsSize += nPointsBytes; - const instructionLength = glyfView.getUint16(dataOff, false); + const instructionLength = readU16(glyfData, dataOff); dataOff += 2; const instructions = instructionLength > 0 ? { offset: dataOff, length: instructionLength } : null; dataOff += instructionLength; @@ -386,7 +408,7 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { /* ★ 合并:instructionStreamSize 累加 */ instructionStreamSize += instructionLength; - const numPoints = numberOfContours > 0 ? endPtsOfContours[numberOfContours - 1] + 1 : 0; + const numPoints = numberOfContours > 0 ? lastEndPt + 1 : 0; /* 优化183: flagsArr 复用为 cachedFlags,消除每个 glyph 一次 Uint8Array 分配 */ const flagsArr = new Uint8Array(numPoints); @@ -398,7 +420,7 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { flagsArr[fi++] = flag; if (flag & REPEAT_FLAG && fi < numPoints) { const repeat = glyfData[dataOff++]; - const count = Math.min(repeat, numPoints - fi); + const count = repeat < numPoints - fi ? repeat : numPoints - fi; flagsArr.fill(flag, fi, fi + count); fi += count; } @@ -462,20 +484,24 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { bboxStreamSize += 8; } + /** 优化: Pass 1 就地覆盖 xCoords/yCoords 为 delta,消除 Pass 2 重复减法 */ 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); + const flag = calcTripletFlag(onCurve, dx, dy); flagsArr[pi] = flag; + xCoords[pi] = dx; + yCoords[pi] = dy; glyphStreamSize += TRIPLET_DATA_SIZES[flag & 0x7F]; - prevX = xCoords[pi]; - prevY = yCoords[pi]; + prevX += dx; + prevY += dy; } glyphStreamSize += size255UInt16(instructionLength); } + /* 优化: 仅存储 Pass 2 实际需要的字段,减少对象大小 */ glyphInfos[gi] = { composite: false, numberOfContours, @@ -483,8 +509,6 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { nPointsBytes, instructions, xCoords, yCoords, flags: flagsArr, - hasOverlap, - xMin, yMin, xMax, yMax, calcXMin, calcYMin, calcXMax, calcYMax, }; } @@ -503,21 +527,21 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { + overlapBitmapSize; const result = new Uint8Array(totalSize); - const resultView = new DataView(result.buffer, result.byteOffset, result.byteLength); + /** 优化222: 使用模块级 writeU16/writeI16/writeU32,消除闭包分配 */ 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; + writeU16(result, 0, pos); pos += 2; + writeU16(result, hasOverlapBitmap ? 1 : 0, pos); pos += 2; + writeU16(result, numGlyphs, pos); pos += 2; + writeU16(result, indexFormat, pos); pos += 2; + writeU32(result, nContourStreamSize, pos); pos += 4; + writeU32(result, totalNPointsSize, pos); pos += 4; + writeU32(result, flagStreamSize, pos); pos += 4; + writeU32(result, glyphStreamSize, pos); pos += 4; + writeU32(result, 0, pos); pos += 4; + writeU32(result, bboxBitmapSize + bboxStreamSize, pos); pos += 4; + writeU32(result, instructionStreamSize, pos); pos += 4; /** * 优化:合并原第 3/4/5 次遍历为 1 次 @@ -550,22 +574,22 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { /* nContourStream: 每个 glyph 写入 numberOfContours */ if (!g) { - resultView.setInt16(nContourPos, 0, false); nContourPos += 2; + writeI16(result, 0, nContourPos); nContourPos += 2; continue; } if (g.composite) { - resultView.setInt16(nContourPos, -1, false); nContourPos += 2; + writeI16(result, -1, nContourPos); nContourPos += 2; } else { - resultView.setInt16(nContourPos, g.numberOfContours, false); nContourPos += 2; + writeI16(result, g.numberOfContours, nContourPos); 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; + writeI16(result, g.xMin, bboxPos); bboxPos += 2; + writeI16(result, g.yMin, bboxPos); bboxPos += 2; + writeI16(result, g.xMax, bboxPos); bboxPos += 2; + writeI16(result, g.yMax, bboxPos); bboxPos += 2; if (g.haveInstructions) { const instrLen = g.instructions ? g.instructions.length : 0; if (instrLen > 0) { @@ -590,31 +614,26 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { } /** - * flag + glyph 子流 — 使用缓存的 triplet flag + writePointDataByFlag - * 避免写入阶段的完整分支判断链(6 层 if-else),改用 flag 值直接定位数据布局 + * flag + glyph 子流 — xCoords/yCoords 已在 Pass 1 就地覆盖为 delta + * 直接读取 delta,消除 prevX/prevY 追踪和减法运算 */ 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 += writePointDataByFlag(flag, xCoords[pi], yCoords[pi], result, glyphPos); } 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; + writeI16(result, g.calcXMin, bboxPos); bboxPos += 2; + writeI16(result, g.calcYMin, bboxPos); bboxPos += 2; + writeI16(result, g.calcXMax, bboxPos); bboxPos += 2; + writeI16(result, g.calcYMax, bboxPos); bboxPos += 2; } } @@ -635,6 +654,12 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { /* ======== 主编码函数 ======== */ +/** 优化242: 模块级排序函数,避免每次 encodeTTFToWOFF2 创建闭包 */ +function sortDirEntries(a, b) { + var d = a.tagIndex - b.tagIndex; + return d ? d : (a.tag < b.tag ? -1 : a.tag > b.tag ? 1 : 0); +} + const WOFF2_SIGNATURE = 0x774F4632; const WOFF2_HEADER_SIZE = 48; @@ -643,25 +668,24 @@ const WOFF2_HEADER_SIZE = 48; */ function encodeTTFToWOFF2(ttfBuffer) { const data = ttfBuffer instanceof Uint8Array ? ttfBuffer : new Uint8Array(ttfBuffer); - const view = new DataView(data.buffer, data.byteOffset, data.byteLength); - + /** 优化222+242: 直接调用模块级函数,消除 .bind() 闭包分配 */ /* 解析 sfnt header */ - const flavor = view.getUint32(0, false); - const numTables = view.getUint16(4, false); + const flavor = readU32(data, 0); + const numTables = readU16(data, 4); /* 解析 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); + const offset = readU32(data, off + 8); + const length = readU32(data, off + 12); 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)); + filtered.sort(sortDirEntries); /* 查找关键表 */ let indexToLocFormat = 0; @@ -669,9 +693,11 @@ function encodeTTFToWOFF2(ttfBuffer) { 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); + /* 优化: for...of → for 循环,消除迭代器协议开销 */ + for (var fi = 0, fl = filtered.length; fi < fl; fi++) { + var t = filtered[fi]; + if (t.tag === "head") indexToLocFormat = readU16(data, t.offset + 50); + if (t.tag === "maxp") numGlyphs = readU16(data, t.offset + 4); if (t.tag === "glyf") glyfTable = t; if (t.tag === "loca") locaTable = t; } @@ -689,7 +715,8 @@ function encodeTTFToWOFF2(ttfBuffer) { const dirEntries = []; let totalDirSize = 0; - for (const t of filtered) { + for (var fi2 = 0, fl2 = filtered.length; fi2 < fl2; fi2++) { + var t = filtered[fi2]; if (t.tag === "loca") { /** 优化:直接使用 transformGlyfAndLoca 返回的 locaOrigLength,避免重复计算 */ const origLength = glyfTransformed ? glyfTransformed.locaOrigLength : t.length; @@ -698,7 +725,7 @@ function encodeTTFToWOFF2(ttfBuffer) { flags: t.tagIndex, origLength, transformLength: 0, - data: new Uint8Array(0), + data: EMPTY_UINT8, hasTransform: true, }); totalDirSize += 1 + sizeUIntBase128(origLength) + sizeUIntBase128(0); @@ -743,54 +770,54 @@ function encodeTTFToWOFF2(ttfBuffer) { 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) { + /* 优化: for 循环替代 for...of,避免迭代器对象分配 */ + for (let di = 0; di < dirEntries.length; di++) { + const entry = dirEntries[di]; 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); + /* 优化: 用 Uint8Array 直接写入替代 DataView,消除每次 head 表的 DataView 分配 */ + const base = dataPos; + uncompressedData[base + 8] = uncompressedData[base + 9] = uncompressedData[base + 10] = uncompressedData[base + 11] = 0; + const headFlags = (uncompressedData[base + 44] << 8) | uncompressedData[base + 45]; + const newFlags = headFlags | (1 << 11); + uncompressedData[base + 44] = (newFlags >> 8) & 0xFF; + uncompressedData[base + 45] = newFlags & 0xFF; } dataPos += entry.transformLength; } } /* Brotli 压缩,传入 SIZE_HINT 帮助预分配内部缓冲区 */ - const brotliOptions = totalTableDataSize > 0 - ? { params: { - [BROTLI_PARAM_QUALITY]: 8, - [BROTLI_PARAM_SIZE_HINT]: totalTableDataSize, - }} - : BROTLI_OPTIONS_BASE; - const compressedData = brotliCompressSync(uncompressedData, brotliOptions); + if (totalTableDataSize > 0) BROTLI_OPTIONS_WITH_HINT.params[BROTLI_PARAM_SIZE_HINT] = totalTableDataSize; + const compressedData = brotliCompressSync(uncompressedData, totalTableDataSize > 0 ? BROTLI_OPTIONS_WITH_HINT : BROTLI_OPTIONS_BASE); /* 组装 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); + /** 优化222+242: 直接调用模块级函数,消除 .bind() 闭包分配 */ /* 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); + writeU32(woff2, WOFF2_SIGNATURE, 0); + writeU32(woff2, flavor, 4); + writeU32(woff2, paddedLength, 8); + writeU16(woff2, dirEntries.length, 12); + writeU16(woff2, 0, 14); + writeU32(woff2, totalSfntSize, 16); + writeU32(woff2, compressedData.length, 20); + writeU16(woff2, 1, 24); + writeU16(woff2, 0, 26); + writeU32(woff2, 0, 28); + writeU32(woff2, 0, 32); + writeU32(woff2, 0, 36); + writeU32(woff2, 0, 40); + writeU32(woff2, 0, 44); /* Table Directory */ let dirPos = WOFF2_HEADER_SIZE; - for (const entry of dirEntries) { + for (let di = 0; di < dirEntries.length; di++) { + const entry = dirEntries[di]; woff2[dirPos++] = entry.flags; if (entry.tagIndex === 63) { for (let ci = 0; ci < 4; ci++) woff2[dirPos++] = entry.tag.charCodeAt(ci);