优化性能

This commit is contained in:
崮生(子虚) 2026-04-11 10:25:54 +08:00
parent 2f9eb3fd1a
commit 6ba093ff70
40 changed files with 1322 additions and 949 deletions

View File

@ -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<typeof Font.create>) => {
return optimized;
};
/** woff2 wasm 初始化 Promise延迟初始化只执行一次 */
let woff2InitPromise: Promise<void> | null = null;
/** 确保 woff2 wasm 已初始化,首次调用时加载 711KB wasm */
async function ensureWoff2Init(): Promise<void> {
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<ReturnType<typeof Font.create>["optimize"]>,
outType: FontEditor.FontType,
): Promise<Uint8Array> => {
if (outType === "woff2") {
await ensureWoff2Init();
}
/** 纯 JS woff2 编码器不需要初始化,直接调用 */
const result = font.write({ type: outType });
if (typeof result !== "string") {
return new Uint8Array(result);

View File

@ -1,7 +1,7 @@
/loop 持续优化字体子集化性能和提高ssim评分可以大胆放开手脚的去做但是优化完一定要通过`pnpx tsx ./基准测试.test.ts`。中途不要切换到其他模式,比如计划模式也不要询问我,你直接做就行了,请你持续的去优化,不要去询问我,不要去中断,好吧
把基准测试结果文档保存在本地 benchmark_results/ 这样我方便查看。你的文档中应该在每个重大节点更新基准测试结果benchmark_results/OPTIMIZATION_LOG.md这样我能方便看到你使用了哪些优化方法得到了什么样的优化效果。
不要修改基准测试中的full生成方案如果基准测试结果不对一定是你的修改有问题而非浏览器渲染等其他问题
=== 字体裁剪基准测试 ===

View File

@ -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;

View File

@ -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;
}
}
}
}
}

View File

@ -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;

View File

@ -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++; }

View File

@ -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, ...]

View File

@ -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;
}

View File

@ -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;
}
/**

View File

@ -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) {
/** 使用预创建的大视图 + subarraybaseOffset 已含 -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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];

View File

@ -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);
}

View File

@ -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 subtableplatformID=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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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 调用 */

View File

@ -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 */

View File

@ -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;

View File

@ -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复用全局视图 */

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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({

View File

@ -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;

View File

@ -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 模式下只遍历 subsetUnicodeMapO(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) {

View File

@ -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.<ArrayBuffer>} 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));
}

View File

@ -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]);
}
}
}
}

View File

@ -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;
}

View File

@ -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);
}
}
}
}

View File

@ -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
* 这是 OTFTTF 转换字形的必经路径_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;
}

View File

@ -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,

View File

@ -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 替代 spliceO(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;
}

View File

@ -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 subarraywriteBytes 对 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.<byte>} byteArray byte数组
*/
toPascalStringBytes: function toPascalStringBytes(str) {
var bytes = [];
/* 优化: 返回 Uint8ArraywriteBytes 对 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

View File

@ -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) {

View File

@ -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);

View File

@ -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);

View File

@ -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 = [
/**
* 优化预构建 tagindex 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;
}
/** 编码 255UInt161-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 11headFlags避免额外拷贝 */
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);