mirror of
https://github.com/2234839/web-font.git
synced 2026-04-30 05:08:14 +08:00
优化性能
This commit is contained in:
parent
2f9eb3fd1a
commit
6ba093ff70
@ -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);
|
||||
|
||||
2
task.md
2
task.md
@ -1,7 +1,7 @@
|
||||
/loop 持续优化字体子集化性能和提高ssim评分,可以大胆放开手脚的去做,但是优化完一定要通过`pnpx tsx ./基准测试.test.ts`。中途不要切换到其他模式,比如计划模式也不要询问我,你直接做就行了,请你持续的去优化,不要去询问我,不要去中断,好吧
|
||||
|
||||
把基准测试结果文档保存在本地 benchmark_results/ ,这样我方便查看。你的文档中应该在每个重大节点更新基准测试结果(benchmark_results/OPTIMIZATION_LOG.md),这样我能方便看到你使用了哪些优化方法,得到了什么样的优化效果。
|
||||
|
||||
不要修改基准测试中的full生成方案,如果基准测试结果不对一定是你的修改有问题,而非浏览器渲染等其他问题
|
||||
|
||||
=== 字体裁剪基准测试 ===
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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++; }
|
||||
|
||||
146
vendor/fonteditor-core/lib/math/bezierCubic2Q2.js
vendored
146
vendor/fonteditor-core/lib/math/bezierCubic2Q2.js
vendored
@ -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, ...]
|
||||
|
||||
15
vendor/fonteditor-core/lib/ttf/otf2ttfobject.js
vendored
15
vendor/fonteditor-core/lib/ttf/otf2ttfobject.js
vendored
@ -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;
|
||||
}
|
||||
106
vendor/fonteditor-core/lib/ttf/otfreader.js
vendored
106
vendor/fonteditor-core/lib/ttf/otfreader.js
vendored
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
95
vendor/fonteditor-core/lib/ttf/table/CFF.js
vendored
95
vendor/fonteditor-core/lib/ttf/table/CFF.js
vendored
@ -72,15 +72,15 @@ function parseCFFIndex(reader, offset, conversionFn) {
|
||||
reader.seek(offset);
|
||||
}
|
||||
var start = reader.offset;
|
||||
var offsets = [];
|
||||
var objects = [];
|
||||
var count = reader.readUint16();
|
||||
var offsets = new Array(count + 1);
|
||||
var objects = new Array(count);
|
||||
var i;
|
||||
var l;
|
||||
if (count !== 0) {
|
||||
var offsetSize = reader.readUint8();
|
||||
for (i = 0, l = count + 1; i < l; i++) {
|
||||
offsets.push(getOffset(reader, offsetSize));
|
||||
offsets[i] = getOffset(reader, offsetSize);
|
||||
}
|
||||
for (i = 0, l = count; i < l; i++) {
|
||||
/** 优化179: 直接从 view 创建 Uint8Array 视图,避免 readBytes 的 slice */
|
||||
@ -90,7 +90,7 @@ function parseCFFIndex(reader, offset, conversionFn) {
|
||||
if (conversionFn) {
|
||||
value = conversionFn(value);
|
||||
}
|
||||
objects.push(value);
|
||||
objects[i] = value;
|
||||
}
|
||||
}
|
||||
return {
|
||||
@ -141,15 +141,34 @@ function parseCFFIndexOffsets(reader, offset) {
|
||||
* @return {Uint8Array} object 数据
|
||||
*/
|
||||
/**
|
||||
* 优化169+179: 直接从 view 创建 Uint8Array 视图,避免 readBytes 的 slice 开销
|
||||
* 优化169+179+244: 预创建全量 charstring 大视图,用 subarray 替代 new Uint8Array(buffer, off, len)
|
||||
* subarray 不需要参数验证,比 new Uint8Array 更快
|
||||
*/
|
||||
function readCFFIndexObject(reader, indexInfo, idx) {
|
||||
var off = indexInfo.offsets;
|
||||
var view = indexInfo._view;
|
||||
if (view) {
|
||||
/** 使用预创建的大视图 + subarray,baseOffset 已含 -1 修正 */
|
||||
return view.subarray(off[idx] - 1, off[idx + 1] - 1);
|
||||
}
|
||||
var size = off[idx + 1] - off[idx];
|
||||
var start = indexInfo.dataStart + off[idx] - 1;
|
||||
return new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化244: 为 parseCFFIndexOffsets 的结果预创建全量数据视图
|
||||
* 后续 readCFFIndexObject 将使用 subarray 替代 new Uint8Array
|
||||
*/
|
||||
function prepareCFFIndexView(reader, indexInfo) {
|
||||
var off = indexInfo.offsets;
|
||||
if (!off || off.length < 2) return;
|
||||
var totalSize = off[off.length - 1] - 1;
|
||||
/** baseOffset 对齐原始 readCFFIndexObject 中的 byteOffset + dataStart + off[idx] - 1 */
|
||||
var baseOffset = reader.view.byteOffset + indexInfo.dataStart;
|
||||
indexInfo._view = new Uint8Array(reader.view.buffer, baseOffset, totalSize);
|
||||
}
|
||||
|
||||
// Subroutines are encoded using the negative half of the number space.
|
||||
// See type 2 chapter 4.7 "Subroutine operators".
|
||||
function calcCFFSubroutineBias(subrs) {
|
||||
@ -382,6 +401,8 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
if (!isCID) {
|
||||
var charStringsInfo = parseCFFIndexOffsets(reader, offset + topDict.charStrings);
|
||||
}
|
||||
/** 优化244: 预创建全量 charstring 大视图,后续 readCFFIndexObject 用 subarray 替代 new Uint8Array */
|
||||
prepareCFFIndexView(reader, charStringsInfo);
|
||||
var nGlyphs = charStringsInfo.count;
|
||||
|
||||
if (topDict.charset < 3) {
|
||||
@ -400,7 +421,7 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
} else {
|
||||
cff.encoding = (0, _parseCFFEncoding.default)(reader, offset + topDict.encoding);
|
||||
}
|
||||
cff.glyf = [];
|
||||
cff.glyf = new Array(nGlyphs);
|
||||
|
||||
/**
|
||||
* 为指定 glyph 构建 per-glyph 的 font 对象
|
||||
@ -435,39 +456,59 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
var subsetMap = {
|
||||
0: true // 设置.notdef
|
||||
};
|
||||
/** 优化251: 构建 subsetMap 时同步收集 subsetGids,避免全量 nGlyphs 遍历 */
|
||||
var subsetGids = [0];
|
||||
var codes = font.cmap;
|
||||
|
||||
// unicode to index — 用 Set 替代 indexOf 实现 O(1) 查找
|
||||
var subsetSet = {};
|
||||
/** 优化:合并 subsetSet 和 subsetMap 构建为单次遍历 */
|
||||
// unicode to index
|
||||
for (var si = 0, sl = subset.length; si < sl; si++) {
|
||||
var code = subset[si];
|
||||
subsetSet[code] = true;
|
||||
var ci = codes[code];
|
||||
if (ci !== undefined) subsetMap[ci] = true;
|
||||
if (ci !== undefined && !subsetMap[ci]) {
|
||||
subsetMap[ci] = true;
|
||||
subsetGids.push(ci);
|
||||
}
|
||||
}
|
||||
font.subsetMap = subsetMap;
|
||||
/** 优化163+167: 构建 subsetGids 密集数组,传递给 otfreader 复用 */
|
||||
var subsetGids = [];
|
||||
for (var gi = 0; gi < nGlyphs; gi++) {
|
||||
if (subsetMap[gi]) subsetGids.push(gi);
|
||||
}
|
||||
for (var si = 0, sl = subsetGids.length; si < sl; si++) {
|
||||
var i = subsetGids[si];
|
||||
var charstring = readCFFIndexObject(reader, charStringsInfo, i);
|
||||
var glyf = (0, _parseCFFGlyph.default)(charstring, getGlyphFont(i), i);
|
||||
glyf.name = cff.charset[i];
|
||||
cff.glyf[i] = glyf;
|
||||
/* 优化258: CID/non-CID 分支到循环外,消除每 glyph 的三元分支 */
|
||||
if (fdGlyphFonts) {
|
||||
for (var si = 0, sl = subsetGids.length; si < sl; si++) {
|
||||
var i = subsetGids[si];
|
||||
var charstring = readCFFIndexObject(reader, charStringsInfo, i);
|
||||
var glyf = (0, _parseCFFGlyph.default)(charstring, getGlyphFont(i), i);
|
||||
glyf.name = cff.charset[i];
|
||||
cff.glyf[i] = glyf;
|
||||
}
|
||||
} else {
|
||||
for (var si = 0, sl = subsetGids.length; si < sl; si++) {
|
||||
var i = subsetGids[si];
|
||||
var charstring = readCFFIndexObject(reader, charStringsInfo, i);
|
||||
var glyf = (0, _parseCFFGlyph.default)(charstring, cff, i);
|
||||
glyf.name = cff.charset[i];
|
||||
cff.glyf[i] = glyf;
|
||||
}
|
||||
}
|
||||
font.subsetGids = subsetGids;
|
||||
}
|
||||
// parse all
|
||||
else {
|
||||
for (var i = 0, l = nGlyphs; i < l; i++) {
|
||||
var charstring = readCFFIndexObject(reader, charStringsInfo, i);
|
||||
var glyf = (0, _parseCFFGlyph.default)(charstring, getGlyphFont(i), i);
|
||||
glyf.name = cff.charset[i];
|
||||
cff.glyf.push(glyf);
|
||||
/* 优化202+230: 非 CID 字体直接使用 cff,缓存属性到局部变量 */
|
||||
var charset = cff.charset;
|
||||
var glyfArr = cff.glyf;
|
||||
if (fdGlyphFonts) {
|
||||
for (var i = 0, l = nGlyphs; i < l; i++) {
|
||||
var charstring = readCFFIndexObject(reader, charStringsInfo, i);
|
||||
var glyf = (0, _parseCFFGlyph.default)(charstring, getGlyphFont(i), i);
|
||||
glyf.name = charset[i];
|
||||
glyfArr[i] = glyf;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0, l = nGlyphs; i < l; i++) {
|
||||
var charstring = readCFFIndexObject(reader, charStringsInfo, i);
|
||||
var glyf = (0, _parseCFFGlyph.default)(charstring, cff, i);
|
||||
glyf.name = charset[i];
|
||||
glyfArr[i] = glyf;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cff;
|
||||
|
||||
27
vendor/fonteditor-core/lib/ttf/table/OS2.js
vendored
27
vendor/fonteditor-core/lib/ttf/table/OS2.js
vendored
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
165
vendor/fonteditor-core/lib/ttf/table/cmap/sizeof.js
vendored
165
vendor/fonteditor-core/lib/ttf/table/cmap/sizeof.js
vendored
@ -62,116 +62,97 @@ function getFormat0SegmentFlat(unicodeArr, idArr) {
|
||||
return unicodes;
|
||||
}
|
||||
|
||||
function getSegments(glyfUnicodes, bound) {
|
||||
var prevGlyph = null;
|
||||
var result = [];
|
||||
var segment = {};
|
||||
for (var i = 0, l = glyfUnicodes.length; i < l; i++) {
|
||||
var glyph = glyfUnicodes[i];
|
||||
if (bound === undefined || glyph.unicode <= bound) {
|
||||
if (prevGlyph === null || glyph.unicode !== prevGlyph.unicode + 1 || glyph.id !== prevGlyph.id + 1) {
|
||||
if (prevGlyph !== null) {
|
||||
segment.end = prevGlyph.unicode;
|
||||
result.push(segment);
|
||||
segment = {
|
||||
start: glyph.unicode,
|
||||
startId: glyph.id,
|
||||
delta: encodeDelta(glyph.id - glyph.unicode)
|
||||
};
|
||||
} else {
|
||||
segment.start = glyph.unicode;
|
||||
segment.startId = glyph.id;
|
||||
segment.delta = encodeDelta(glyph.id - glyph.unicode);
|
||||
}
|
||||
}
|
||||
prevGlyph = glyph;
|
||||
}
|
||||
}
|
||||
if (prevGlyph !== null) {
|
||||
segment.end = prevGlyph.unicode;
|
||||
result.push(segment);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getFormat0Segment(glyfUnicodes) {
|
||||
var unicodes = [];
|
||||
for (var i = 0, l = glyfUnicodes.length; i < l; i++) {
|
||||
var u = glyfUnicodes[i];
|
||||
if (u.unicode < 256) {
|
||||
unicodes.push([u.unicode, u.id]);
|
||||
}
|
||||
}
|
||||
/* 数据已排序,无需再次 sort */
|
||||
return unicodes;
|
||||
}
|
||||
|
||||
function sizeof(ttf) {
|
||||
ttf.support.cmap = {};
|
||||
/* 优化155: 使用并行扁平数组替代对象数组,减少 GC 压力 */
|
||||
/* 优化249: 两遍扫描 — 第一遍统计 unicode 总数,预分配数组避免 push 扩容 */
|
||||
var glyfs = ttf.glyf;
|
||||
var unicodeArr = [];
|
||||
var idArr = [];
|
||||
for (var index = 0, gl = glyfs.length; index < gl; index++) {
|
||||
var glyph = glyfs[index];
|
||||
var unicodes = glyph.unicode;
|
||||
if (typeof glyph.unicode === 'number') {
|
||||
unicodes = [glyph.unicode];
|
||||
var gl = glyfs.length;
|
||||
var totalCount = 0;
|
||||
for (var index = 0; index < gl; index++) {
|
||||
var unicodes = glyfs[index].unicode;
|
||||
if (unicodes) {
|
||||
totalCount += unicodes.length;
|
||||
} else if (unicodes === 0 || unicodes === '') {
|
||||
totalCount++;
|
||||
}
|
||||
if (unicodes && unicodes.length) {
|
||||
for (var ui = 0, ul = unicodes.length; ui < ul; ui++) {
|
||||
unicodeArr.push(unicodes[ui]);
|
||||
idArr.push(unicodes[ui] !== 0xFFFF ? index : 0);
|
||||
}
|
||||
var unicodeArr = new Array(totalCount);
|
||||
var idArr = new Array(totalCount);
|
||||
var ai = 0;
|
||||
for (var index2 = 0; index2 < gl; index2++) {
|
||||
var glyph = glyfs[index2];
|
||||
var ucs = glyph.unicode;
|
||||
if (ucs) {
|
||||
for (var ui = 0, ul = ucs.length; ui < ul; ui++) {
|
||||
unicodeArr[ai] = ucs[ui];
|
||||
idArr[ai] = ucs[ui] !== 0xFFFF ? index2 : 0;
|
||||
ai++;
|
||||
}
|
||||
} else if (ucs === 0 || ucs === '') {
|
||||
unicodeArr[ai] = ucs;
|
||||
idArr[ai] = ucs !== 0xFFFF ? index2 : 0;
|
||||
ai++;
|
||||
}
|
||||
}
|
||||
/* 优化179: 二分插入排序,O(n log n) 查找 + O(n) 移位 */
|
||||
var len = unicodeArr.length;
|
||||
for (var i = 1; i < len; i++) {
|
||||
var uKey = unicodeArr[i];
|
||||
var iKey = idArr[i];
|
||||
var lo = 0, hi = i - 1;
|
||||
while (lo <= hi) {
|
||||
var mid = (lo + hi) >> 1;
|
||||
if (unicodeArr[mid] > uKey) hi = mid - 1;
|
||||
else lo = mid + 1;
|
||||
/* 优化187+247: Int32Array 索引排序,V8 对 TypedArray 比较器排序更高效(避免装箱) */
|
||||
var len = ai;
|
||||
if (len > 1) {
|
||||
var indices = new Int32Array(len);
|
||||
for (var ii = 0; ii < len; ii++) indices[ii] = ii;
|
||||
indices.sort(function(a, b) { return unicodeArr[a] - unicodeArr[b]; });
|
||||
var sortedU = new Array(len);
|
||||
var sortedI = new Array(len);
|
||||
for (var ii2 = 0; ii2 < len; ii2++) {
|
||||
var idx = indices[ii2];
|
||||
sortedU[ii2] = unicodeArr[idx];
|
||||
sortedI[ii2] = idArr[idx];
|
||||
}
|
||||
if (lo !== i) {
|
||||
unicodeArr.copyWithin(lo + 1, lo, i);
|
||||
idArr.copyWithin(lo + 1, lo, i);
|
||||
unicodeArr[lo] = uKey;
|
||||
idArr[lo] = iKey;
|
||||
unicodeArr = sortedU;
|
||||
idArr = sortedI;
|
||||
}
|
||||
|
||||
/* 优化: 合并 format12 和 format4 的 getSegmentsFlat 为单次调用
|
||||
* format12 不设 bound(包含所有字符),format4 截断到 0xFFFF
|
||||
* 大多数字体只有 BMP 字符,此时两者完全相同 */
|
||||
var format12Segments = getSegmentsFlat(unicodeArr, idArr);
|
||||
var cmapSupport = ttf.support.cmap;
|
||||
cmapSupport.format12Segments = format12Segments;
|
||||
cmapSupport.format12Size = 16 + (format12Segments.length >> 2) * 12;
|
||||
|
||||
/* 检查是否有 > 0xFFFF 的字符,没有则 format4 直接复用 format12 */
|
||||
var hasOver2Bytes = false;
|
||||
for (var ci = 0, cl = format12Segments.length; ci < cl; ci += 4) {
|
||||
if (format12Segments[ci + 1] > 0xFFFF) {
|
||||
hasOver2Bytes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ttf.support.cmap.format4Segments = getSegmentsFlat(unicodeArr, idArr, 0xFFFF);
|
||||
/** format4Size 需要包含 sentinel segment (+1),与 write.js 中的 segCount = segments.length/4 + 1 一致 */
|
||||
/**
|
||||
* format4Size = header(14) + reservedPad(2) + segCount * 2 * 4(four arrays)
|
||||
* segCount = 实际段数 + 1(sentinel 0xFFFF)
|
||||
*/
|
||||
var format4SegCount = ttf.support.cmap.format4Segments.length / 4 + 1;
|
||||
ttf.support.cmap.format4Size = 16 + format4SegCount * 8;
|
||||
ttf.support.cmap.format0Segments = getFormat0SegmentFlat(unicodeArr, idArr);
|
||||
ttf.support.cmap.hasFormat0 = ttf.support.cmap.format0Segments.length > 0;
|
||||
ttf.support.cmap.format0Size = ttf.support.cmap.hasFormat0 ? 262 : 0;
|
||||
if (hasOver2Bytes) {
|
||||
cmapSupport.format4Segments = getSegmentsFlat(unicodeArr, idArr, 0xFFFF);
|
||||
} else {
|
||||
cmapSupport.format4Segments = format12Segments;
|
||||
}
|
||||
|
||||
/** 始终生成 format 12 subtable(platformID=3, encodingID=10),
|
||||
* 现代浏览器使用 unicode-range 时依赖 format 12 来匹配字符。
|
||||
* 仅当有 cmap 映射数据时才生成(避免 nGroups=0 的无效 subtable) */
|
||||
var hasGLyphsOver2Bytes = len > 0;
|
||||
if (hasGLyphsOver2Bytes) {
|
||||
ttf.support.cmap.hasGLyphsOver2Bytes = true;
|
||||
ttf.support.cmap.format12Segments = getSegmentsFlat(unicodeArr, idArr);
|
||||
ttf.support.cmap.format12Size = 16 + (ttf.support.cmap.format12Segments.length / 4) * 12;
|
||||
cmapSupport.hasGLyphsOver2Bytes = true;
|
||||
}
|
||||
|
||||
/** format4Size 需要包含 sentinel segment (+1),与 write.js 中的 segCount = segments.length/4 + 1 一致 */
|
||||
var format4SegCount = cmapSupport.format4Segments.length / 4 + 1;
|
||||
cmapSupport.format4Size = 16 + format4SegCount * 8;
|
||||
cmapSupport.format0Segments = getFormat0SegmentFlat(unicodeArr, idArr);
|
||||
cmapSupport.hasFormat0 = cmapSupport.format0Segments.length > 0;
|
||||
cmapSupport.format0Size = cmapSupport.hasFormat0 ? 262 : 0;
|
||||
|
||||
/** 记录头大小必须动态计算,与 write.js 中的 numRecords 保持一致,否则会导致表偏移错位 */
|
||||
var numRecords = 2 + (ttf.support.cmap.hasFormat0 ? 1 : 0) + (ttf.support.cmap.hasGLyphsOver2Bytes ? 1 : 0);
|
||||
var numRecords = 2 + (cmapSupport.hasFormat0 ? 1 : 0) + (cmapSupport.hasGLyphsOver2Bytes ? 1 : 0);
|
||||
var recordHeaderSize = 4 + numRecords * 8;
|
||||
var size = recordHeaderSize
|
||||
+ ttf.support.cmap.format0Size
|
||||
+ ttf.support.cmap.format4Size
|
||||
+ (ttf.support.cmap.hasGLyphsOver2Bytes ? ttf.support.cmap.format12Size : 0);
|
||||
+ cmapSupport.format0Size
|
||||
+ cmapSupport.format4Size
|
||||
+ (cmapSupport.hasGLyphsOver2Bytes ? cmapSupport.format12Size : 0);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
9
vendor/fonteditor-core/lib/ttf/table/glyf.js
vendored
9
vendor/fonteditor-core/lib/ttf/table/glyf.js
vendored
@ -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 调用 */
|
||||
|
||||
@ -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 */
|
||||
|
||||
164
vendor/fonteditor-core/lib/ttf/table/glyf/sizeof.js
vendored
164
vendor/fonteditor-core/lib/ttf/table/glyf/sizeof.js
vendored
@ -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;
|
||||
|
||||
@ -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,复用全局视图 */
|
||||
|
||||
20
vendor/fonteditor-core/lib/ttf/table/head.js
vendored
20
vendor/fonteditor-core/lib/ttf/table/head.js
vendored
@ -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;
|
||||
|
||||
2
vendor/fonteditor-core/lib/ttf/table/hhea.js
vendored
2
vendor/fonteditor-core/lib/ttf/table/hhea.js
vendored
@ -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;
|
||||
|
||||
4
vendor/fonteditor-core/lib/ttf/table/hmtx.js
vendored
4
vendor/fonteditor-core/lib/ttf/table/hmtx.js
vendored
@ -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;
|
||||
|
||||
26
vendor/fonteditor-core/lib/ttf/table/maxp.js
vendored
26
vendor/fonteditor-core/lib/ttf/table/maxp.js
vendored
@ -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;
|
||||
|
||||
18
vendor/fonteditor-core/lib/ttf/table/name.js
vendored
18
vendor/fonteditor-core/lib/ttf/table/name.js
vendored
@ -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({
|
||||
|
||||
22
vendor/fonteditor-core/lib/ttf/table/post.js
vendored
22
vendor/fonteditor-core/lib/ttf/table/post.js
vendored
@ -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;
|
||||
|
||||
77
vendor/fonteditor-core/lib/ttf/ttfreader.js
vendored
77
vendor/fonteditor-core/lib/ttf/ttfreader.js
vendored
@ -57,12 +57,14 @@ var TTFReader = exports.default = /*#__PURE__*/function () {
|
||||
}
|
||||
ttf.readOptions = this.options;
|
||||
|
||||
/* 优化8+37+62: 跳过不必要的表,缓存 TableClass 实例,for...in 替代 Object.keys */
|
||||
/* 优化8+37+62+240: 跳过不必要的表,缓存 TableClass 实例,预构建表名列表替代 for...in */
|
||||
var hinting = this.options.hinting;
|
||||
var kerning = this.options.kerning;
|
||||
var supportTables = _support.default;
|
||||
var tableInstances = {};
|
||||
for (var tableName in supportTables) {
|
||||
var ttfTableNames = ['head', 'maxp', 'loca', 'cmap', 'glyf', 'name', 'hhea', 'hmtx', 'post', 'OS/2', 'fpgm', 'cvt', 'prep', 'gasp', 'GPOS', 'kern', 'kerx'];
|
||||
for (var ti = 0, tl = ttfTableNames.length; ti < tl; ti++) {
|
||||
var tableName = ttfTableNames[ti];
|
||||
if (ttf.tables[tableName]) {
|
||||
/* 优化8: hinting=false 时跳过 fpgm/cvt/prep/gasp */
|
||||
if (!hinting && (tableName === 'fpgm' || tableName === 'cvt' || tableName === 'prep' || tableName === 'gasp')) {
|
||||
@ -96,16 +98,30 @@ var TTFReader = exports.default = /*#__PURE__*/function () {
|
||||
var subsetMap = ttf.readOptions.subset ? ttf.subsetMap : null;
|
||||
var subsetGids = ttf.readOptions.subset ? ttf.subsetGids : null;
|
||||
|
||||
/* 优化13+24+62: unicode 遍历,subset 模式只遍历 subsetMap */
|
||||
for (var c in codes) {
|
||||
var i = codes[c];
|
||||
if (subsetMap && !subsetMap[i]) {
|
||||
continue;
|
||||
/* 优化: subset 模式下只遍历 subsetUnicodeMap(O(S)),避免全量 codes 遍历(O(U)) */
|
||||
if (ttf._subsetUnicodeMap) {
|
||||
var sum = ttf._subsetUnicodeMap;
|
||||
/** 优化: for...in 替代 Object.keys,消除临时数组分配 */
|
||||
for (var uStr in sum) {
|
||||
var u = +uStr;
|
||||
var i = sum[u];
|
||||
if (!glyf[i].unicode) {
|
||||
glyf[i].unicode = [u];
|
||||
} else {
|
||||
glyf[i].unicode.push(u);
|
||||
}
|
||||
}
|
||||
if (!glyf[i].unicode) {
|
||||
glyf[i].unicode = [];
|
||||
ttf._subsetUnicodeMap = null;
|
||||
} else {
|
||||
for (var c in codes) {
|
||||
var i = codes[c];
|
||||
var code = +c;
|
||||
if (!glyf[i].unicode) {
|
||||
glyf[i].unicode = [code];
|
||||
} else {
|
||||
glyf[i].unicode.push(code);
|
||||
}
|
||||
}
|
||||
glyf[i].unicode.push(+c);
|
||||
}
|
||||
|
||||
/* 优化13+82+118: advanceWidth 遍历优化,使用密集数组 */
|
||||
@ -172,15 +188,15 @@ var TTFReader = exports.default = /*#__PURE__*/function () {
|
||||
}
|
||||
}
|
||||
|
||||
/* 优化13+44+62+118: subset 模式下使用密集数组遍历 */
|
||||
/* 优化259: subset 模式下合并 compound→simple 转换与 subGlyf 构建,消除二次遍历 */
|
||||
if (subsetGids) {
|
||||
var subGlyf = [];
|
||||
var subGlyf = new Array(subsetGids.length);
|
||||
for (var si = 0, sl = subsetGids.length; si < sl; si++) {
|
||||
var siNum = subsetGids[si];
|
||||
if (glyf[siNum].compound) {
|
||||
(0, _compound2simpleglyf.default)(siNum, ttf, true);
|
||||
}
|
||||
subGlyf.push(glyf[siNum]);
|
||||
subGlyf[si] = glyf[siNum];
|
||||
}
|
||||
ttf.glyf = subGlyf;
|
||||
ttf.maxp.maxComponentElements = 0;
|
||||
@ -190,32 +206,33 @@ var TTFReader = exports.default = /*#__PURE__*/function () {
|
||||
}, {
|
||||
key: "cleanTables",
|
||||
value: function cleanTables(ttf) {
|
||||
delete ttf.readOptions;
|
||||
delete ttf.tables;
|
||||
delete ttf.hmtx;
|
||||
delete ttf.loca;
|
||||
/** 优化245: delete → null 赋值,避免 V8 隐藏类转换 */
|
||||
ttf.readOptions = null;
|
||||
ttf.tables = null;
|
||||
ttf.hmtx = null;
|
||||
ttf.loca = null;
|
||||
if (ttf.post) {
|
||||
delete ttf.post.nameIndex;
|
||||
delete ttf.post.names;
|
||||
delete ttf.post._pascalStringBytes;
|
||||
delete ttf.post._pascalStringOffsets;
|
||||
ttf.post.nameIndex = null;
|
||||
ttf.post.names = null;
|
||||
ttf.post._pascalStringBytes = null;
|
||||
ttf.post._pascalStringOffsets = null;
|
||||
}
|
||||
delete ttf.subsetMap;
|
||||
ttf.subsetMap = null;
|
||||
|
||||
if (!this.options.hinting) {
|
||||
delete ttf.fpgm;
|
||||
delete ttf.cvt;
|
||||
delete ttf.prep;
|
||||
/* 优化55: forEach → for 循环 */
|
||||
/** 优化245: delete → null 赋值,避免 V8 隐藏类转换 */
|
||||
ttf.fpgm = null;
|
||||
ttf.cvt = null;
|
||||
ttf.prep = null;
|
||||
var glyfs = ttf.glyf;
|
||||
for (var i = 0, l = glyfs.length; i < l; i++) {
|
||||
delete glyfs[i].instructions;
|
||||
glyfs[i].instructions = null;
|
||||
}
|
||||
}
|
||||
if (!this.options.hinting && !this.options.kerning) {
|
||||
delete ttf.GPOS;
|
||||
delete ttf.kern;
|
||||
delete ttf.kerx;
|
||||
ttf.GPOS = null;
|
||||
ttf.kern = null;
|
||||
ttf.kerx = null;
|
||||
}
|
||||
|
||||
if (this.options.compound2simple && ttf.maxp.maxComponentElements) {
|
||||
|
||||
10
vendor/fonteditor-core/lib/ttf/ttftowoff2.js
vendored
10
vendor/fonteditor-core/lib/ttf/ttftowoff2.js
vendored
@ -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));
|
||||
}
|
||||
|
||||
67
vendor/fonteditor-core/lib/ttf/ttfwriter.js
vendored
67
vendor/fonteditor-core/lib/ttf/ttfwriter.js
vendored
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
25
vendor/fonteditor-core/lib/ttf/util/checkSum.js
vendored
25
vendor/fonteditor-core/lib/ttf/util/checkSum.js
vendored
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
223
vendor/fonteditor-core/lib/ttf/util/optimizettf.js
vendored
223
vendor/fonteditor-core/lib/ttf/util/optimizettf.js
vendored
@ -13,6 +13,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
||||
* @author mengke01(kekee000@gmail.com)
|
||||
*/
|
||||
|
||||
/** 优化: 模块级排序函数,避免每个 glyph 创建闭包 */
|
||||
function numericSort(a, b) { return a - b; }
|
||||
|
||||
/**
|
||||
* 优化103+116: 从 parse 阶段的 TypedArray 直接计算 precomputed 数据,跳过 contour 数组构建
|
||||
* 每个字形独立分配 buffer,避免共享 buffer 的复杂性
|
||||
@ -32,10 +35,8 @@ function ceilReduceAndSizeFromTypedArrays(glyf) {
|
||||
var REPEAT = _glyFlag.default.REPEAT;
|
||||
|
||||
var numPoints = xArr.length;
|
||||
var flagsC = new Array(numPoints);
|
||||
var flagsC = new Uint8Array(numPoints);
|
||||
var fi = 0;
|
||||
var prevX = 0, prevY = 0;
|
||||
var isFirst = true;
|
||||
var prevFlag = -1;
|
||||
var repeatPoint = -1;
|
||||
var encodedCoordSize = 0;
|
||||
@ -46,66 +47,59 @@ function ceilReduceAndSizeFromTypedArrays(glyf) {
|
||||
var yCoordBuf = new Uint8Array(neededSize);
|
||||
var xbi = 0, ybi = 0;
|
||||
|
||||
for (var pi = 0; pi < numPoints; pi++) {
|
||||
var px = xArr[pi];
|
||||
var py = yArr[pi];
|
||||
var onCurve = !!(flagsArr[pi] & ONCURVE);
|
||||
|
||||
var dx, dy;
|
||||
/** 优化: 首点提取到循环外,消除 isFirst 条件分支 */
|
||||
if (numPoints > 0) {
|
||||
var px = xArr[0], py = yArr[0];
|
||||
var onCurve = !!(flagsArr[0] & ONCURVE);
|
||||
var flag = onCurve ? ONCURVE : 0;
|
||||
if (isFirst) {
|
||||
dx = px; dy = py; isFirst = false;
|
||||
} else {
|
||||
dx = px - prevX; dy = py - prevY;
|
||||
}
|
||||
prevX = px;
|
||||
prevY = py;
|
||||
if (px === 0) flag += XSAME;
|
||||
else if (px > -256 && px < 256) { flag += XSHORT; if (px > 0) flag += XSAME; xCoordBuf[xbi++] = px > 0 ? px : -px; encodedCoordSize += 1; }
|
||||
else { xCoordBuf[xbi++] = (px >> 8) & 0xFF; xCoordBuf[xbi++] = px & 0xFF; encodedCoordSize += 2; }
|
||||
if (py === 0) flag += YSAME;
|
||||
else if (py > -256 && py < 256) { flag += YSHORT; if (py > 0) flag += YSAME; yCoordBuf[ybi++] = py > 0 ? py : -py; encodedCoordSize += 1; }
|
||||
else { yCoordBuf[ybi++] = (py >> 8) & 0xFF; yCoordBuf[ybi++] = py & 0xFF; encodedCoordSize += 2; }
|
||||
flagsC[fi++] = prevFlag = flag;
|
||||
var prevX = px, prevY = py;
|
||||
|
||||
if (dx === 0) {
|
||||
flag += XSAME;
|
||||
} else if (dx > -256 && dx < 256) {
|
||||
flag += XSHORT;
|
||||
if (dx > 0) flag += XSAME;
|
||||
xCoordBuf[xbi++] = dx > 0 ? dx : -dx;
|
||||
encodedCoordSize += 1;
|
||||
} else {
|
||||
xCoordBuf[xbi++] = (dx >> 8) & 0xFF;
|
||||
xCoordBuf[xbi++] = dx & 0xFF;
|
||||
encodedCoordSize += 2;
|
||||
}
|
||||
for (var pi = 1; pi < numPoints; pi++) {
|
||||
px = xArr[pi]; py = yArr[pi];
|
||||
onCurve = !!(flagsArr[pi] & ONCURVE);
|
||||
flag = onCurve ? ONCURVE : 0;
|
||||
var dx = px - prevX, dy = py - prevY;
|
||||
prevX = px; prevY = py;
|
||||
|
||||
if (dy === 0) {
|
||||
flag += YSAME;
|
||||
} else if (dy > -256 && dy < 256) {
|
||||
flag += YSHORT;
|
||||
if (dy > 0) flag += YSAME;
|
||||
yCoordBuf[ybi++] = dy > 0 ? dy : -dy;
|
||||
encodedCoordSize += 1;
|
||||
} else {
|
||||
yCoordBuf[ybi++] = (dy >> 8) & 0xFF;
|
||||
yCoordBuf[ybi++] = dy & 0xFF;
|
||||
encodedCoordSize += 2;
|
||||
}
|
||||
if (dx === 0) { flag += XSAME; }
|
||||
else if (dx > -256 && dx < 256) { flag += XSHORT; if (dx > 0) flag += XSAME; xCoordBuf[xbi++] = dx > 0 ? dx : -dx; encodedCoordSize += 1; }
|
||||
else { xCoordBuf[xbi++] = (dx >> 8) & 0xFF; xCoordBuf[xbi++] = dx & 0xFF; encodedCoordSize += 2; }
|
||||
if (dy === 0) { flag += YSAME; }
|
||||
else if (dy > -256 && dy < 256) { flag += YSHORT; if (dy > 0) flag += YSAME; yCoordBuf[ybi++] = dy > 0 ? dy : -dy; encodedCoordSize += 1; }
|
||||
else { yCoordBuf[ybi++] = (dy >> 8) & 0xFF; yCoordBuf[ybi++] = dy & 0xFF; encodedCoordSize += 2; }
|
||||
|
||||
if (flag === prevFlag && !isFirst) {
|
||||
if (repeatPoint === -1) {
|
||||
repeatPoint = fi - 1;
|
||||
flagsC[repeatPoint] |= REPEAT;
|
||||
flagsC[fi++] = 1;
|
||||
if (flag === prevFlag) {
|
||||
if (repeatPoint === -1) {
|
||||
repeatPoint = fi - 1;
|
||||
flagsC[repeatPoint] |= REPEAT;
|
||||
flagsC[fi++] = 1;
|
||||
} else if (flagsC[repeatPoint + 1] < 255) {
|
||||
++flagsC[repeatPoint + 1];
|
||||
} else {
|
||||
/* 优化188: repeat count 达到 255 上限,结束当前 repeat 并开始新 flag */
|
||||
repeatPoint = -1;
|
||||
flagsC[fi++] = flag;
|
||||
prevFlag = flag;
|
||||
}
|
||||
} else {
|
||||
++flagsC[repeatPoint + 1];
|
||||
repeatPoint = -1;
|
||||
flagsC[fi++] = flag;
|
||||
prevFlag = flag;
|
||||
}
|
||||
} else {
|
||||
repeatPoint = -1;
|
||||
flagsC[fi++] = flag;
|
||||
prevFlag = flag;
|
||||
}
|
||||
}
|
||||
|
||||
flagsC.length = fi;
|
||||
flagsC = flagsC.subarray(0, fi);
|
||||
|
||||
/* 优化103: 不构建 contour 数组,直接存储元数据 */
|
||||
glyf.contours = new Array(numContours);
|
||||
/* 优化201: 使用鸭子类型替代空数组分配,只提供 length 属性 */
|
||||
glyf.contours = { length: numContours };
|
||||
glyf._flatContours = true;
|
||||
/* 优化103: 存储每个 contour 的点数,供 write 计算 endPtsOfContours */
|
||||
glyf._pointsPerContour = new Array(numContours);
|
||||
@ -115,23 +109,22 @@ function ceilReduceAndSizeFromTypedArrays(glyf) {
|
||||
glyf._numContours = numContours;
|
||||
glyf._totalPoints = numPoints;
|
||||
|
||||
glyf._precomputedGlyfSupport = {
|
||||
flags: flagsC,
|
||||
encodedCoordSize: encodedCoordSize,
|
||||
xBuf: xCoordBuf,
|
||||
xLen: xbi,
|
||||
yBuf: yCoordBuf,
|
||||
yLen: ybi
|
||||
};
|
||||
/** 优化256: 在写入方直接 trim subarray,消除 sizeof.js 二次 slicing */
|
||||
glyf._preFlags = flagsC;
|
||||
glyf._preEncodedCoordSize = encodedCoordSize;
|
||||
glyf._preXBuf = xCoordBuf.subarray(0, xbi);
|
||||
glyf._preYBuf = yCoordBuf.subarray(0, ybi);
|
||||
glyf._preXLen = xbi;
|
||||
glyf._preYLen = ybi;
|
||||
|
||||
delete glyf._xArr;
|
||||
delete glyf._yArr;
|
||||
delete glyf._flags;
|
||||
delete glyf.endPtsOfContours;
|
||||
glyf._xArr = null;
|
||||
glyf._flags = null;
|
||||
glyf.endPtsOfContours = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化84+98+149: 合并 ceil+reduce+flagsAndSize 为单次遍历
|
||||
* 优化256: 写入方直接 trim subarray,消除 sizeof.js 二次 slicing
|
||||
*/
|
||||
function ceilReduceAndSizeFlat(glyf) {
|
||||
var contours = glyf.contours;
|
||||
@ -144,7 +137,7 @@ function ceilReduceAndSizeFlat(glyf) {
|
||||
}
|
||||
contours.length = writeIdx;
|
||||
if (0 === contours.length) {
|
||||
delete glyf.contours;
|
||||
glyf.contours = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -161,12 +154,10 @@ function ceilReduceAndSizeFlat(glyf) {
|
||||
|
||||
var totalPoints = 0;
|
||||
for (var j = 0, cl = contours.length; j < cl; j++) {
|
||||
totalPoints += contours[j].length / 3;
|
||||
totalPoints += contours[j].length / 3 | 0;
|
||||
}
|
||||
var flagsC = new Array(totalPoints);
|
||||
var flagsC = new Uint8Array(totalPoints);
|
||||
var fi = 0;
|
||||
var prevX = 0, prevY = 0;
|
||||
var isFirst = true;
|
||||
var prevFlag = -1;
|
||||
var repeatPoint = -1;
|
||||
var encodedCoordSize = 0;
|
||||
@ -177,25 +168,31 @@ function ceilReduceAndSizeFlat(glyf) {
|
||||
var yCoordBuf = new Uint8Array(neededSize);
|
||||
var xbi = 0, ybi = 0;
|
||||
|
||||
/** 优化213: 首点提取到循环外,消除 per-point 条件分支 */
|
||||
var firstContour = contours[0];
|
||||
var fpx = firstContour[0], fpy = firstContour[1];
|
||||
var fOnCurve = firstContour[2];
|
||||
var fFlag = fOnCurve ? ONCURVE : 0;
|
||||
if (fpx === 0) fFlag += XSAME;
|
||||
else if (fpx > -256 && fpx < 256) { fFlag += XSHORT; if (fpx > 0) fFlag += XSAME; xCoordBuf[xbi++] = fpx > 0 ? fpx : -fpx; encodedCoordSize += 1; }
|
||||
else { xCoordBuf[xbi++] = (fpx >> 8) & 0xFF; xCoordBuf[xbi++] = fpx & 0xFF; encodedCoordSize += 2; }
|
||||
if (fpy === 0) fFlag += YSAME;
|
||||
else if (fpy > -256 && fpy < 256) { fFlag += YSHORT; if (fpy > 0) fFlag += YSAME; yCoordBuf[ybi++] = fpy > 0 ? fpy : -fpy; encodedCoordSize += 1; }
|
||||
else { yCoordBuf[ybi++] = (fpy >> 8) & 0xFF; yCoordBuf[ybi++] = fpy & 0xFF; encodedCoordSize += 2; }
|
||||
flagsC[fi++] = prevFlag = fFlag;
|
||||
var prevX = fpx, prevY = fpy;
|
||||
|
||||
var skipFirst = true;
|
||||
for (var j = 0, cl2 = contours.length; j < cl2; j++) {
|
||||
var contour = contours[j];
|
||||
for (var i = 0, l = contour.length; i < l; i += 3) {
|
||||
if (skipFirst) { skipFirst = false; continue; }
|
||||
var px = contour[i];
|
||||
var py = contour[i + 1];
|
||||
var onCurve = contour[i + 2];
|
||||
var dx, dy;
|
||||
var flag = onCurve ? ONCURVE : 0;
|
||||
|
||||
if (isFirst) {
|
||||
dx = px;
|
||||
dy = py;
|
||||
isFirst = false;
|
||||
} else {
|
||||
dx = px - prevX;
|
||||
dy = py - prevY;
|
||||
}
|
||||
prevX = px;
|
||||
prevY = py;
|
||||
var dx = px - prevX, dy = py - prevY;
|
||||
prevX = px; prevY = py;
|
||||
|
||||
if (dx === 0) {
|
||||
flag += XSAME;
|
||||
@ -225,13 +222,18 @@ function ceilReduceAndSizeFlat(glyf) {
|
||||
encodedCoordSize += 2;
|
||||
}
|
||||
|
||||
if (flag === prevFlag && !isFirst) {
|
||||
if (flag === prevFlag) {
|
||||
if (repeatPoint === -1) {
|
||||
repeatPoint = fi - 1;
|
||||
flagsC[repeatPoint] |= REPEAT;
|
||||
flagsC[fi++] = 1;
|
||||
} else {
|
||||
} else if (flagsC[repeatPoint + 1] < 255) {
|
||||
++flagsC[repeatPoint + 1];
|
||||
} else {
|
||||
/* 优化188: repeat count 达到 255 上限,结束当前 repeat 并开始新 flag */
|
||||
repeatPoint = -1;
|
||||
flagsC[fi++] = flag;
|
||||
prevFlag = flag;
|
||||
}
|
||||
} else {
|
||||
repeatPoint = -1;
|
||||
@ -241,16 +243,17 @@ function ceilReduceAndSizeFlat(glyf) {
|
||||
}
|
||||
}
|
||||
|
||||
flagsC.length = fi;
|
||||
flagsC = flagsC.subarray(0, fi);
|
||||
|
||||
glyf._precomputedGlyfSupport = {
|
||||
flags: flagsC,
|
||||
encodedCoordSize: encodedCoordSize,
|
||||
xBuf: xCoordBuf,
|
||||
xLen: xbi,
|
||||
yBuf: yCoordBuf,
|
||||
yLen: ybi
|
||||
};
|
||||
/** 优化256: 在写入方直接 trim subarray,消除 sizeof.js 二次 slicing */
|
||||
glyf._preFlags = flagsC;
|
||||
glyf._preEncodedCoordSize = encodedCoordSize;
|
||||
glyf._preXBuf = xCoordBuf.subarray(0, xbi);
|
||||
glyf._preYBuf = yCoordBuf.subarray(0, ybi);
|
||||
glyf._preXLen = xbi;
|
||||
glyf._preYLen = ybi;
|
||||
glyf._totalPoints = totalPoints;
|
||||
glyf._numContours = contours.length;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -270,11 +273,6 @@ function optimizettf(ttf) {
|
||||
var m_firstChar = 0x10FFFF, m_lastChar = -1;
|
||||
var m_maxPoints = 0, m_maxContours = 0;
|
||||
|
||||
/* 优化120+cmap: 在主循环中同时构建预排序的 cmap unicode/id 数组 */
|
||||
var cmapUnicodeArr = [];
|
||||
var cmapIdArr = [];
|
||||
var cmapCount = 0;
|
||||
|
||||
for (var index = 0, gl = glyfs.length; index < gl; index++) {
|
||||
var glyf = glyfs[index];
|
||||
if (glyf.compound) {
|
||||
@ -282,7 +280,7 @@ function optimizettf(ttf) {
|
||||
}
|
||||
if (glyf.unicode) {
|
||||
if (glyf.unicode.length > 1) {
|
||||
glyf.unicode.sort(function (a, b) { return a - b; });
|
||||
glyf.unicode.sort(numericSort);
|
||||
}
|
||||
var unicode = glyf.unicode;
|
||||
for (var ui = 0, ul = unicode.length; ui < ul; ui++) {
|
||||
@ -314,22 +312,11 @@ function optimizettf(ttf) {
|
||||
/**
|
||||
* ⚠️ 关键:必须收集 maxPoints/maxContours,否则 maxp 表中这两个值为 0,
|
||||
* 浏览器会据此跳过渲染(表现为字体加载成功但文字显示为空白/fallback)。
|
||||
* 这是 OTF→TTF 转换字形的必经路径(_flatContours 由 parseCFFGlyph 生成),
|
||||
* 之前已因为同样的问题修复过对象 contours 路径(commit 97f4d72),
|
||||
* 所有涉及 contours 的分支都必须更新这两个值!
|
||||
* 注意:ceilReduceAndSizeFlat 可能删除 glyf.contours(当所有 contour 长度 ≤ 6 时),
|
||||
* 所以必须在调用之后检查 glyf.contours 是否仍存在。
|
||||
* ceilReduceAndSizeFlat 已缓存 _totalPoints 和 _numContours。
|
||||
*/
|
||||
if (glyf.contours) {
|
||||
var flatNumC = glyf.contours.length;
|
||||
if (flatNumC > 0) {
|
||||
if (flatNumC > m_maxContours) m_maxContours = flatNumC;
|
||||
var flatTotalPts = 0;
|
||||
for (var fci = 0; fci < flatNumC; fci++) {
|
||||
flatTotalPts += glyf.contours[fci].length / 3;
|
||||
}
|
||||
if (flatTotalPts > m_maxPoints) m_maxPoints = flatTotalPts;
|
||||
}
|
||||
if (glyf._numContours > m_maxContours) m_maxContours = glyf._numContours;
|
||||
if (glyf._totalPoints > m_maxPoints) m_maxPoints = glyf._totalPoints;
|
||||
}
|
||||
} else {
|
||||
/* 对象 contours 格式也需要收集 maxPoints/maxContours */
|
||||
@ -407,16 +394,6 @@ function optimizettf(ttf) {
|
||||
}
|
||||
}
|
||||
ttf.glyf = filtered;
|
||||
if (ttf._cmapSortedIdArr) {
|
||||
var cmapIdArr = ttf._cmapSortedIdArr;
|
||||
for (var ci = 0, cl = cmapIdArr.length; ci < cl; ci++) {
|
||||
var oldIdx = cmapIdArr[ci];
|
||||
if (oldIdx > 0) {
|
||||
var newIdx = indexMap[oldIdx];
|
||||
if (newIdx !== undefined) { cmapIdArr[ci] = newIdx; }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ttf.support && ttf.support.maxp) {
|
||||
ttf.support.maxp.numGlyphs = filtered.length;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -19,21 +19,20 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
||||
*/
|
||||
function reduceGlyf(glyf) {
|
||||
var contours = glyf.contours;
|
||||
var contour;
|
||||
/* 优化66: 扁平格式下 contour.length 是点的3倍 */
|
||||
var isFlat = glyf._flatContours;
|
||||
var minLen = isFlat ? 6 : 2;
|
||||
for (var j = contours.length - 1; j >= 0; j--) {
|
||||
contour = (0, _reducePath.default)(contours[j]);
|
||||
|
||||
/* 空轮廓:扁平格式 <= 6 元素(2个点),对象格式 <= 2 个点 */
|
||||
if (contour.length <= minLen) {
|
||||
contours.splice(j, 1);
|
||||
continue;
|
||||
/** 优化: 使用 writeIdx 替代 splice,O(n) 替代 O(n*m) */
|
||||
var writeIdx = 0;
|
||||
for (var j = 0, cl = contours.length; j < cl; j++) {
|
||||
var contour = (0, _reducePath.default)(contours[j]);
|
||||
if (contour.length > minLen) {
|
||||
contours[writeIdx++] = contour;
|
||||
}
|
||||
}
|
||||
if (0 === glyf.contours.length) {
|
||||
delete glyf.contours;
|
||||
contours.length = writeIdx;
|
||||
if (0 === writeIdx) {
|
||||
glyf.contours = null;
|
||||
}
|
||||
return glyf;
|
||||
}
|
||||
69
vendor/fonteditor-core/lib/ttf/util/string.js
vendored
69
vendor/fonteditor-core/lib/ttf/util/string.js
vendored
@ -67,11 +67,8 @@ var _default = exports.default = {
|
||||
* @return {string} string
|
||||
*/
|
||||
getString: function getString(bytes) {
|
||||
var s = '';
|
||||
for (var i = 0, l = bytes.length; i < l; i++) {
|
||||
s += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return s;
|
||||
/** 优化243: fromCharCode.apply 批量转换,消除逐字节字符串拼接的中间分配 */
|
||||
return String.fromCharCode.apply(null, bytes);
|
||||
},
|
||||
/**
|
||||
* 获取unicode的名字值
|
||||
@ -94,27 +91,30 @@ var _default = exports.default = {
|
||||
*/
|
||||
toUTF8Bytes: function toUTF8Bytes(str) {
|
||||
str = stringify(str);
|
||||
var byteArray = [];
|
||||
/* 优化: 预分配 Uint8Array 替代动态 push,避免数组扩容 */
|
||||
var byteArr = new Uint8Array(str.length * 4);
|
||||
var bi = 0;
|
||||
for (var i = 0, l = str.length; i < l; i++) {
|
||||
var ch = str.charCodeAt(i);
|
||||
if (ch <= 0x7F) {
|
||||
byteArray.push(ch);
|
||||
byteArr[bi++] = ch;
|
||||
} else if (ch <= 0x7FF) {
|
||||
byteArray.push(0xC0 | (ch >> 6));
|
||||
byteArray.push(0x80 | (ch & 0x3F));
|
||||
byteArr[bi++] = 0xC0 | (ch >> 6);
|
||||
byteArr[bi++] = 0x80 | (ch & 0x3F);
|
||||
} else if (ch < 0xD800 || ch >= 0xE000) {
|
||||
byteArray.push(0xE0 | (ch >> 12));
|
||||
byteArray.push(0x80 | ((ch >> 6) & 0x3F));
|
||||
byteArray.push(0x80 | (ch & 0x3F));
|
||||
byteArr[bi++] = 0xE0 | (ch >> 12);
|
||||
byteArr[bi++] = 0x80 | ((ch >> 6) & 0x3F);
|
||||
byteArr[bi++] = 0x80 | (ch & 0x3F);
|
||||
} else {
|
||||
var cp = ((ch - 0xD800) << 10) + (str.charCodeAt(++i) - 0xDC00);
|
||||
byteArray.push(0xF0 | (cp >> 18));
|
||||
byteArray.push(0x80 | ((cp >> 12) & 0x3F));
|
||||
byteArray.push(0x80 | ((cp >> 6) & 0x3F));
|
||||
byteArray.push(0x80 | (cp & 0x3F));
|
||||
byteArr[bi++] = 0xF0 | (cp >> 18);
|
||||
byteArr[bi++] = 0x80 | ((cp >> 12) & 0x3F);
|
||||
byteArr[bi++] = 0x80 | ((cp >> 6) & 0x3F);
|
||||
byteArr[bi++] = 0x80 | (cp & 0x3F);
|
||||
}
|
||||
}
|
||||
return byteArray;
|
||||
/* 优化: 直接返回 Uint8Array subarray,writeBytes 对 Uint8Array 有快速路径 */
|
||||
return byteArr.subarray(0, bi);
|
||||
},
|
||||
/**
|
||||
* 转换成usc2的字节数组
|
||||
@ -124,13 +124,14 @@ var _default = exports.default = {
|
||||
*/
|
||||
toUCS2Bytes: function toUCS2Bytes(str) {
|
||||
str = stringify(str);
|
||||
var byteArray = [];
|
||||
for (var i = 0, l = str.length, ch; i < l; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
byteArray.push(ch >> 8);
|
||||
byteArray.push(ch & 0xFF);
|
||||
/* 优化: 预分配 Uint8Array 替代动态 push */
|
||||
var byteArr = new Uint8Array(str.length * 2);
|
||||
for (var i = 0, l = str.length; i < l; i++) {
|
||||
var ch = str.charCodeAt(i);
|
||||
byteArr[i * 2] = ch >> 8;
|
||||
byteArr[i * 2 + 1] = ch & 0xFF;
|
||||
}
|
||||
return byteArray;
|
||||
return byteArr;
|
||||
},
|
||||
/**
|
||||
* 获取pascal string 字节数组
|
||||
@ -139,13 +140,14 @@ var _default = exports.default = {
|
||||
* @return {Array.<byte>} byteArray byte数组
|
||||
*/
|
||||
toPascalStringBytes: function toPascalStringBytes(str) {
|
||||
var bytes = [];
|
||||
/* 优化: 返回 Uint8Array,writeBytes 对 Uint8Array 有快速路径 */
|
||||
var length = str ? str.length < 256 ? str.length : 255 : 0;
|
||||
bytes.push(length);
|
||||
var bytes = new Uint8Array(1 + (str ? str.length : 0));
|
||||
bytes[0] = length;
|
||||
for (var i = 0, l = str.length; i < l; i++) {
|
||||
var c = str.charCodeAt(i);
|
||||
// non-ASCII characters are substituted with '*'
|
||||
bytes.push(c < 128 ? c : 42);
|
||||
bytes[i + 1] = c < 128 ? c : 42;
|
||||
}
|
||||
return bytes;
|
||||
},
|
||||
@ -156,6 +158,10 @@ var _default = exports.default = {
|
||||
* @return {string} 字符串
|
||||
*/
|
||||
getUTF8String: function getUTF8String(bytes) {
|
||||
/** 优化254: 使用 TextDecoder 替代手动 UTF-8 解码 + unescape */
|
||||
if (typeof TextDecoder !== 'undefined') {
|
||||
return new TextDecoder('utf-8', { fatal: false }).decode(bytes);
|
||||
}
|
||||
var str = '';
|
||||
for (var i = 0, l = bytes.length; i < l; i++) {
|
||||
if (bytes[i] < 0x7F) {
|
||||
@ -173,11 +179,14 @@ var _default = exports.default = {
|
||||
* @return {string} 字符串
|
||||
*/
|
||||
getUCS2String: function getUCS2String(bytes) {
|
||||
var str = '';
|
||||
for (var i = 0, l = bytes.length; i < l; i += 2) {
|
||||
str += String.fromCharCode((bytes[i] << 8) + bytes[i + 1]);
|
||||
/** 优化253: 收集 charCodes 到数组,单次 fromCharCode.apply 替代逐字拼接 */
|
||||
var len = bytes.length;
|
||||
if (len === 0) return '';
|
||||
var codes = new Array(len >> 1);
|
||||
for (var i = 0, j = 0; i < len; i += 2, j++) {
|
||||
codes[j] = (bytes[i] << 8) + bytes[i + 1];
|
||||
}
|
||||
return str;
|
||||
return String.fromCharCode.apply(null, codes);
|
||||
},
|
||||
/**
|
||||
* 读取 pascal string
|
||||
|
||||
@ -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) {
|
||||
|
||||
4
vendor/fonteditor-core/lib/ttf/woff2ttf.js
vendored
4
vendor/fonteditor-core/lib/ttf/woff2ttf.js
vendored
@ -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);
|
||||
|
||||
6
vendor/fonteditor-core/lib/ttf/writer.js
vendored
6
vendor/fonteditor-core/lib/ttf/writer.js
vendored
@ -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);
|
||||
|
||||
307
vendor/fonteditor-core/woff2/woff2-encode.js
vendored
307
vendor/fonteditor-core/woff2/woff2-encode.js
vendored
@ -26,6 +26,30 @@ const BROTLI_PARAM_SIZE_HINT = zlib.constants?.BROTLI_PARAM_SIZE_HINT ?? 4;
|
||||
const BROTLI_OPTIONS_BASE = {
|
||||
params: { [BROTLI_PARAM_QUALITY]: 8 },
|
||||
};
|
||||
/** 优化: 预分配 options 模板,避免每次 encode 创建 computed property name 对象 */
|
||||
const BROTLI_OPTIONS_WITH_HINT = {
|
||||
params: { [BROTLI_PARAM_QUALITY]: 8, [BROTLI_PARAM_SIZE_HINT]: 0 },
|
||||
};
|
||||
|
||||
/* ======== 大端序读写工具函数(模块级,消除闭包分配) ======== */
|
||||
|
||||
/** 从 Uint8Array 读取无符号 16 位大端序 */
|
||||
function readU16(arr, off) { return (arr[off] << 8) | arr[off + 1]; }
|
||||
|
||||
/** 从 Uint8Array 读取有符号 16 位大端序 */
|
||||
function readI16(arr, off) { const v = (arr[off] << 8) | arr[off + 1]; return v > 0x7FFF ? v - 0x10000 : v; }
|
||||
|
||||
/** 从 Uint8Array 读取无符号 32 位大端序 */
|
||||
function readU32(arr, off) { return (arr[off] << 24 | arr[off + 1] << 16 | arr[off + 2] << 8 | arr[off + 3]) >>> 0; }
|
||||
|
||||
/** 向 Uint8Array 写入无符号 16 位大端序 */
|
||||
function writeU16(buf, v, p) { buf[p] = v >> 8; buf[p + 1] = v & 0xFF; }
|
||||
|
||||
/** 向 Uint8Array 写入有符号 16 位大端序 */
|
||||
function writeI16(buf, v, p) { buf[p] = v >> 8; buf[p + 1] = v & 0xFF; }
|
||||
|
||||
/** 向 Uint8Array 写入无符号 32 位大端序 */
|
||||
function writeU32(buf, v, p) { buf[p] = v >> 24; buf[p + 1] = (v >> 16) & 0xFF; buf[p + 2] = (v >> 8) & 0xFF; buf[p + 3] = v & 0xFF; }
|
||||
|
||||
/* ======== Known Table Tags 索引表 ======== */
|
||||
const KNOWN_TAGS = [
|
||||
@ -41,6 +65,9 @@ const KNOWN_TAGS = [
|
||||
/**
|
||||
* 优化:预构建 tag→index Map,消除每次 getTagIndex 的 O(63) 线性搜索
|
||||
*/
|
||||
/** 优化: 模块级常量,避免每次 loca 表创建 new Uint8Array(0) */
|
||||
const EMPTY_UINT8 = new Uint8Array(0);
|
||||
|
||||
const KNOWN_TAG_MAP = new Map();
|
||||
for (let i = 0; i < KNOWN_TAGS.length; i++) {
|
||||
KNOWN_TAG_MAP.set(KNOWN_TAGS[i], i);
|
||||
@ -65,19 +92,36 @@ function calcUIntBase128Size(value) {
|
||||
return 5;
|
||||
}
|
||||
|
||||
/** 编码 UIntBase128(最多 5 字节,高位在前) */
|
||||
/** 编码 UIntBase128(最多 5 字节,高位在前),优化234: 展开常见路径 */
|
||||
function encodeUIntBase128(value, buf, offset) {
|
||||
const size = calcUIntBase128Size(value);
|
||||
let pos = offset;
|
||||
for (let i = size - 1; i >= 0; i--) {
|
||||
const byte = (value >>> (7 * i)) & 0x7F;
|
||||
if (i > 0) {
|
||||
buf[pos++] = byte | 0x80;
|
||||
} else {
|
||||
buf[pos++] = byte;
|
||||
}
|
||||
if (value < 0x80) {
|
||||
buf[offset] = value;
|
||||
return 1;
|
||||
}
|
||||
return size;
|
||||
if (value < 0x4000) {
|
||||
buf[offset] = (value >>> 7) | 0x80;
|
||||
buf[offset + 1] = value & 0x7F;
|
||||
return 2;
|
||||
}
|
||||
if (value < 0x200000) {
|
||||
buf[offset] = (value >>> 14) | 0x80;
|
||||
buf[offset + 1] = ((value >>> 7) & 0x7F) | 0x80;
|
||||
buf[offset + 2] = value & 0x7F;
|
||||
return 3;
|
||||
}
|
||||
if (value < 0x10000000) {
|
||||
buf[offset] = (value >>> 21) | 0x80;
|
||||
buf[offset + 1] = ((value >>> 14) & 0x7F) | 0x80;
|
||||
buf[offset + 2] = ((value >>> 7) & 0x7F) | 0x80;
|
||||
buf[offset + 3] = value & 0x7F;
|
||||
return 4;
|
||||
}
|
||||
buf[offset] = (value >>> 28) | 0x80;
|
||||
buf[offset + 1] = ((value >>> 21) & 0x7F) | 0x80;
|
||||
buf[offset + 2] = ((value >>> 14) & 0x7F) | 0x80;
|
||||
buf[offset + 3] = ((value >>> 7) & 0x7F) | 0x80;
|
||||
buf[offset + 4] = value & 0x7F;
|
||||
return 5;
|
||||
}
|
||||
|
||||
/** 编码 255UInt16(1-3 字节变长) */
|
||||
@ -128,10 +172,10 @@ function dataSizeFromTriplet(ti) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码一个点到 glyphStream,返回 triplet flag 字节
|
||||
* 直接写入 buf,不分配数组
|
||||
* 仅计算 triplet flag 字节,不写入数据
|
||||
* 优化: Pass 1 只需要 flag,数据写入由 Pass 2 的 writePointDataByFlag 完成
|
||||
*/
|
||||
function encodePointToBuf(onCurve, dx, dy, buf, offset) {
|
||||
function calcTripletFlag(onCurve, dx, dy) {
|
||||
const curveBit = onCurve ? 0 : 128;
|
||||
const absDx = dx < 0 ? -dx : dx;
|
||||
const absDy = dy < 0 ? -dy : dy;
|
||||
@ -141,53 +185,35 @@ function encodePointToBuf(onCurve, dx, dy, buf, offset) {
|
||||
|
||||
/* dx=0, Y 单轴 1 数据字节 (flag 0-9) */
|
||||
if (dx === 0 && absDy < 1280) {
|
||||
const flag = curveBit + ((absDy & 0xF00) >> 7) + ySignBit;
|
||||
buf[offset] = absDy & 0xFF;
|
||||
return flag;
|
||||
return curveBit + ((absDy & 0xF00) >> 7) + ySignBit;
|
||||
}
|
||||
|
||||
/* dy=0, dx≠0, X 单轴 1 数据字节 (flag 10-19) */
|
||||
if (dy === 0 && dx !== 0 && absDx < 1280) {
|
||||
const flag = curveBit + 10 + ((absDx & 0xF00) >> 7) + xSignBit;
|
||||
buf[offset] = absDx & 0xFF;
|
||||
return flag;
|
||||
return curveBit + 10 + ((absDx & 0xF00) >> 7) + xSignBit;
|
||||
}
|
||||
|
||||
/* 双轴 1 数据字节 (flag 20-83): 1 ≤ |dx| ≤ 64, 1 ≤ |dy| ≤ 64 */
|
||||
if (dx !== 0 && dy !== 0 && absDx < 65 && absDy < 65) {
|
||||
const ax = absDx - 1;
|
||||
const ay = absDy - 1;
|
||||
const flag = curveBit + 20 + (ax & 0x30) + ((ay & 0x30) >> 2) + xySignBits;
|
||||
buf[offset] = ((ax & 0xF) << 4) | (ay & 0xF);
|
||||
return flag;
|
||||
return curveBit + 20 + (ax & 0x30) + ((ay & 0x30) >> 2) + xySignBits;
|
||||
}
|
||||
|
||||
/* 双轴 2 数据字节 (flag 84-119): 1 ≤ |dx| ≤ 768, 1 ≤ |dy| ≤ 768 */
|
||||
if (dx !== 0 && dy !== 0 && absDx < 769 && absDy < 769) {
|
||||
const ax = absDx - 1;
|
||||
const ay = absDy - 1;
|
||||
const flag = curveBit + 84 + 12 * ((ax & 0x300) >> 8) + ((ay & 0x300) >> 6) + xySignBits;
|
||||
buf[offset] = ax & 0xFF;
|
||||
buf[offset + 1] = ay & 0xFF;
|
||||
return flag;
|
||||
return curveBit + 84 + 12 * ((ax & 0x300) >> 8) + ((ay & 0x300) >> 6) + xySignBits;
|
||||
}
|
||||
|
||||
/* 双轴 3 数据字节 (flag 120-123) */
|
||||
if (absDx < 4096 && absDy < 4096) {
|
||||
const flag = curveBit + 120 + xySignBits;
|
||||
buf[offset] = absDx >> 4;
|
||||
buf[offset + 1] = ((absDx & 0xF) << 4) | (absDy >> 8);
|
||||
buf[offset + 2] = absDy & 0xFF;
|
||||
return flag;
|
||||
return curveBit + 120 + xySignBits;
|
||||
}
|
||||
|
||||
/* 兜底 4 数据字节 (flag 124-127) */
|
||||
const flag = curveBit + 124 + xySignBits;
|
||||
buf[offset] = (absDx >> 8) & 0xFF;
|
||||
buf[offset + 1] = absDx & 0xFF;
|
||||
buf[offset + 2] = (absDy >> 8) & 0xFF;
|
||||
buf[offset + 3] = absDy & 0xFF;
|
||||
return flag;
|
||||
return curveBit + 124 + xySignBits;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -243,18 +269,17 @@ function writePointDataByFlag(flag, dx, dy, buf, offset) {
|
||||
* 对 glyf + loca 表执行 WOFF2 变换
|
||||
*/
|
||||
function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
const glyfView = new DataView(glyfData.buffer, glyfData.byteOffset, glyfData.byteLength);
|
||||
const locaView = new DataView(locaData.buffer, locaData.byteOffset, locaData.byteLength);
|
||||
/** 优化222: 使用模块级 readU16/readI16/readU32,消除闭包分配 */
|
||||
|
||||
/* 读取 loca 表获取每个 glyph 的偏移 */
|
||||
const offsets = new Int32Array(numGlyphs + 1);
|
||||
if (indexFormat === 0) {
|
||||
for (let i = 0; i <= numGlyphs; i++) {
|
||||
offsets[i] = locaView.getUint16(i * 2, false) * 2;
|
||||
offsets[i] = readU16(locaData, i * 2) * 2;
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i <= numGlyphs; i++) {
|
||||
offsets[i] = locaView.getUint32(i * 4, false);
|
||||
offsets[i] = readU32(locaData, i * 4);
|
||||
}
|
||||
}
|
||||
|
||||
@ -278,10 +303,9 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
let hasOverlapBitmap = false;
|
||||
let totalPoints = 0;
|
||||
|
||||
const bboxBitmapSize = 4 * Math.floor((numGlyphs + 31) / 32);
|
||||
const bboxBitmapSize = ((numGlyphs + 31) >>> 5) << 2;
|
||||
const bboxBitmap = new Uint8Array(bboxBitmapSize);
|
||||
const overlapBitmap = new Uint8Array(bboxBitmapSize);
|
||||
const tmpBuf = new Uint8Array(4);
|
||||
|
||||
/* 收集每个 glyph 的信息 */
|
||||
const glyphInfos = new Array(numGlyphs);
|
||||
@ -295,11 +319,11 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const numberOfContours = glyfView.getInt16(glyphStart, false);
|
||||
const xMin = glyfView.getInt16(glyphStart + 2, false);
|
||||
const yMin = glyfView.getInt16(glyphStart + 4, false);
|
||||
const xMax = glyfView.getInt16(glyphStart + 6, false);
|
||||
const yMax = glyfView.getInt16(glyphStart + 8, false);
|
||||
const numberOfContours = readI16(glyfData, glyphStart);
|
||||
const xMin = readI16(glyfData, glyphStart + 2);
|
||||
const yMin = readI16(glyfData, glyphStart + 4);
|
||||
const xMax = readI16(glyfData, glyphStart + 6);
|
||||
const yMax = readI16(glyfData, glyphStart + 8);
|
||||
|
||||
if (numberOfContours < 0) {
|
||||
/* 复合 glyph */
|
||||
@ -311,7 +335,7 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
const MORE_COMPONENTS = 0x0020;
|
||||
const WE_HAVE_INSTRUCTIONS = 0x0100;
|
||||
while (compOff < glyphEnd) {
|
||||
const compFlags = glyfView.getUint16(compOff, false);
|
||||
const compFlags = readU16(glyfData, compOff);
|
||||
compOff += 2;
|
||||
compOff += 2; /* glyphIndex */
|
||||
|
||||
@ -330,7 +354,7 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
const componentDataEnd = compOff;
|
||||
|
||||
if (haveInstructions && compOff + 2 <= glyphEnd) {
|
||||
instrLength = glyfView.getUint16(compOff, false);
|
||||
instrLength = readU16(glyfData, compOff);
|
||||
compOff += 2;
|
||||
if (instrLength > 0 && compOff + instrLength <= glyphEnd) {
|
||||
instructions = { offset: compOff, length: instrLength };
|
||||
@ -362,23 +386,21 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
/* 简单 glyph */
|
||||
let dataOff = glyphStart + 10;
|
||||
|
||||
const endPtsOfContours = new Uint16Array(numberOfContours);
|
||||
for (let c = 0; c < numberOfContours; c++) {
|
||||
endPtsOfContours[c] = glyfView.getUint16(dataOff, false);
|
||||
dataOff += 2;
|
||||
}
|
||||
|
||||
/* ★ 合并:totalNPointsSize 计算 + 缓存 255UInt16 编码结果 */
|
||||
/** 优化226: 合并 endPts 读取与 nPoints 编码,消除 endPtsOfContours TypedArray 分配 */
|
||||
const nPointsEncoded = new Uint8Array(numberOfContours * 3);
|
||||
let nPointsBytes = 0;
|
||||
let prevEnd = -1;
|
||||
let lastEndPt = -1;
|
||||
for (let c = 0; c < numberOfContours; c++) {
|
||||
nPointsBytes += encode255UInt16(endPtsOfContours[c] - prevEnd, nPointsEncoded, nPointsBytes);
|
||||
prevEnd = endPtsOfContours[c];
|
||||
const endPt = readU16(glyfData, dataOff);
|
||||
dataOff += 2;
|
||||
nPointsBytes += encode255UInt16(endPt - prevEnd, nPointsEncoded, nPointsBytes);
|
||||
prevEnd = endPt;
|
||||
}
|
||||
lastEndPt = prevEnd;
|
||||
totalNPointsSize += nPointsBytes;
|
||||
|
||||
const instructionLength = glyfView.getUint16(dataOff, false);
|
||||
const instructionLength = readU16(glyfData, dataOff);
|
||||
dataOff += 2;
|
||||
const instructions = instructionLength > 0 ? { offset: dataOff, length: instructionLength } : null;
|
||||
dataOff += instructionLength;
|
||||
@ -386,7 +408,7 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
/* ★ 合并:instructionStreamSize 累加 */
|
||||
instructionStreamSize += instructionLength;
|
||||
|
||||
const numPoints = numberOfContours > 0 ? endPtsOfContours[numberOfContours - 1] + 1 : 0;
|
||||
const numPoints = numberOfContours > 0 ? lastEndPt + 1 : 0;
|
||||
|
||||
/* 优化183: flagsArr 复用为 cachedFlags,消除每个 glyph 一次 Uint8Array 分配 */
|
||||
const flagsArr = new Uint8Array(numPoints);
|
||||
@ -398,7 +420,7 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
flagsArr[fi++] = flag;
|
||||
if (flag & REPEAT_FLAG && fi < numPoints) {
|
||||
const repeat = glyfData[dataOff++];
|
||||
const count = Math.min(repeat, numPoints - fi);
|
||||
const count = repeat < numPoints - fi ? repeat : numPoints - fi;
|
||||
flagsArr.fill(flag, fi, fi + count);
|
||||
fi += count;
|
||||
}
|
||||
@ -462,20 +484,24 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
bboxStreamSize += 8;
|
||||
}
|
||||
|
||||
/** 优化: Pass 1 就地覆盖 xCoords/yCoords 为 delta,消除 Pass 2 重复减法 */
|
||||
let prevX = 0, prevY = 0;
|
||||
for (let pi = 0; pi < numPoints; pi++) {
|
||||
const onCurve = !!(flagsArr[pi] & ONCURVE_FLAG);
|
||||
const dx = xCoords[pi] - prevX;
|
||||
const dy = yCoords[pi] - prevY;
|
||||
const flag = encodePointToBuf(onCurve, dx, dy, tmpBuf, 0);
|
||||
const flag = calcTripletFlag(onCurve, dx, dy);
|
||||
flagsArr[pi] = flag;
|
||||
xCoords[pi] = dx;
|
||||
yCoords[pi] = dy;
|
||||
glyphStreamSize += TRIPLET_DATA_SIZES[flag & 0x7F];
|
||||
prevX = xCoords[pi];
|
||||
prevY = yCoords[pi];
|
||||
prevX += dx;
|
||||
prevY += dy;
|
||||
}
|
||||
glyphStreamSize += size255UInt16(instructionLength);
|
||||
}
|
||||
|
||||
/* 优化: 仅存储 Pass 2 实际需要的字段,减少对象大小 */
|
||||
glyphInfos[gi] = {
|
||||
composite: false,
|
||||
numberOfContours,
|
||||
@ -483,8 +509,6 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
nPointsBytes,
|
||||
instructions,
|
||||
xCoords, yCoords, flags: flagsArr,
|
||||
hasOverlap,
|
||||
xMin, yMin, xMax, yMax,
|
||||
calcXMin, calcYMin, calcXMax, calcYMax,
|
||||
};
|
||||
}
|
||||
@ -503,21 +527,21 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
+ overlapBitmapSize;
|
||||
|
||||
const result = new Uint8Array(totalSize);
|
||||
const resultView = new DataView(result.buffer, result.byteOffset, result.byteLength);
|
||||
/** 优化222: 使用模块级 writeU16/writeI16/writeU32,消除闭包分配 */
|
||||
let pos = 0;
|
||||
|
||||
/* Header */
|
||||
resultView.setUint16(pos, 0, false); pos += 2;
|
||||
resultView.setUint16(pos, hasOverlapBitmap ? 1 : 0, false); pos += 2;
|
||||
resultView.setUint16(pos, numGlyphs, false); pos += 2;
|
||||
resultView.setUint16(pos, indexFormat, false); pos += 2;
|
||||
resultView.setUint32(pos, nContourStreamSize, false); pos += 4;
|
||||
resultView.setUint32(pos, totalNPointsSize, false); pos += 4;
|
||||
resultView.setUint32(pos, flagStreamSize, false); pos += 4;
|
||||
resultView.setUint32(pos, glyphStreamSize, false); pos += 4;
|
||||
resultView.setUint32(pos, 0, false); pos += 4;
|
||||
resultView.setUint32(pos, bboxBitmapSize + bboxStreamSize, false); pos += 4;
|
||||
resultView.setUint32(pos, instructionStreamSize, false); pos += 4;
|
||||
writeU16(result, 0, pos); pos += 2;
|
||||
writeU16(result, hasOverlapBitmap ? 1 : 0, pos); pos += 2;
|
||||
writeU16(result, numGlyphs, pos); pos += 2;
|
||||
writeU16(result, indexFormat, pos); pos += 2;
|
||||
writeU32(result, nContourStreamSize, pos); pos += 4;
|
||||
writeU32(result, totalNPointsSize, pos); pos += 4;
|
||||
writeU32(result, flagStreamSize, pos); pos += 4;
|
||||
writeU32(result, glyphStreamSize, pos); pos += 4;
|
||||
writeU32(result, 0, pos); pos += 4;
|
||||
writeU32(result, bboxBitmapSize + bboxStreamSize, pos); pos += 4;
|
||||
writeU32(result, instructionStreamSize, pos); pos += 4;
|
||||
|
||||
/**
|
||||
* 优化:合并原第 3/4/5 次遍历为 1 次
|
||||
@ -550,22 +574,22 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
|
||||
/* nContourStream: 每个 glyph 写入 numberOfContours */
|
||||
if (!g) {
|
||||
resultView.setInt16(nContourPos, 0, false); nContourPos += 2;
|
||||
writeI16(result, 0, nContourPos); nContourPos += 2;
|
||||
continue;
|
||||
}
|
||||
if (g.composite) {
|
||||
resultView.setInt16(nContourPos, -1, false); nContourPos += 2;
|
||||
writeI16(result, -1, nContourPos); nContourPos += 2;
|
||||
} else {
|
||||
resultView.setInt16(nContourPos, g.numberOfContours, false); nContourPos += 2;
|
||||
writeI16(result, g.numberOfContours, nContourPos); nContourPos += 2;
|
||||
}
|
||||
|
||||
if (g.composite) {
|
||||
result.set(glyfData.subarray(g.rawOffset, g.rawOffset + g.rawLength), glyphPos);
|
||||
glyphPos += g.rawLength;
|
||||
resultView.setInt16(bboxPos, g.xMin, false); bboxPos += 2;
|
||||
resultView.setInt16(bboxPos, g.yMin, false); bboxPos += 2;
|
||||
resultView.setInt16(bboxPos, g.xMax, false); bboxPos += 2;
|
||||
resultView.setInt16(bboxPos, g.yMax, false); bboxPos += 2;
|
||||
writeI16(result, g.xMin, bboxPos); bboxPos += 2;
|
||||
writeI16(result, g.yMin, bboxPos); bboxPos += 2;
|
||||
writeI16(result, g.xMax, bboxPos); bboxPos += 2;
|
||||
writeI16(result, g.yMax, bboxPos); bboxPos += 2;
|
||||
if (g.haveInstructions) {
|
||||
const instrLen = g.instructions ? g.instructions.length : 0;
|
||||
if (instrLen > 0) {
|
||||
@ -590,31 +614,26 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
}
|
||||
|
||||
/**
|
||||
* flag + glyph 子流 — 使用缓存的 triplet flag + writePointDataByFlag
|
||||
* 避免写入阶段的完整分支判断链(6 层 if-else),改用 flag 值直接定位数据布局
|
||||
* flag + glyph 子流 — xCoords/yCoords 已在 Pass 1 就地覆盖为 delta
|
||||
* 直接读取 delta,消除 prevX/prevY 追踪和减法运算
|
||||
*/
|
||||
const numPts = g.xCoords.length;
|
||||
const xCoords = g.xCoords;
|
||||
const yCoords = g.yCoords;
|
||||
const tripletFlags = g.flags;
|
||||
let prevX = 0, prevY = 0;
|
||||
for (let pi = 0; pi < numPts; pi++) {
|
||||
const dx = xCoords[pi] - prevX;
|
||||
const dy = yCoords[pi] - prevY;
|
||||
const flag = tripletFlags[pi];
|
||||
result[flagPos++] = flag;
|
||||
glyphPos += writePointDataByFlag(flag, dx, dy, result, glyphPos);
|
||||
prevX = xCoords[pi];
|
||||
prevY = yCoords[pi];
|
||||
glyphPos += writePointDataByFlag(flag, xCoords[pi], yCoords[pi], result, glyphPos);
|
||||
}
|
||||
|
||||
glyphPos += encode255UInt16(instrLen, result, glyphPos);
|
||||
|
||||
if (bboxBitmap[gi >> 3] & (0x80 >> (gi & 7))) {
|
||||
resultView.setInt16(bboxPos, g.calcXMin, false); bboxPos += 2;
|
||||
resultView.setInt16(bboxPos, g.calcYMin, false); bboxPos += 2;
|
||||
resultView.setInt16(bboxPos, g.calcXMax, false); bboxPos += 2;
|
||||
resultView.setInt16(bboxPos, g.calcYMax, false); bboxPos += 2;
|
||||
writeI16(result, g.calcXMin, bboxPos); bboxPos += 2;
|
||||
writeI16(result, g.calcYMin, bboxPos); bboxPos += 2;
|
||||
writeI16(result, g.calcXMax, bboxPos); bboxPos += 2;
|
||||
writeI16(result, g.calcYMax, bboxPos); bboxPos += 2;
|
||||
}
|
||||
}
|
||||
|
||||
@ -635,6 +654,12 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
|
||||
/* ======== 主编码函数 ======== */
|
||||
|
||||
/** 优化242: 模块级排序函数,避免每次 encodeTTFToWOFF2 创建闭包 */
|
||||
function sortDirEntries(a, b) {
|
||||
var d = a.tagIndex - b.tagIndex;
|
||||
return d ? d : (a.tag < b.tag ? -1 : a.tag > b.tag ? 1 : 0);
|
||||
}
|
||||
|
||||
const WOFF2_SIGNATURE = 0x774F4632;
|
||||
const WOFF2_HEADER_SIZE = 48;
|
||||
|
||||
@ -643,25 +668,24 @@ const WOFF2_HEADER_SIZE = 48;
|
||||
*/
|
||||
function encodeTTFToWOFF2(ttfBuffer) {
|
||||
const data = ttfBuffer instanceof Uint8Array ? ttfBuffer : new Uint8Array(ttfBuffer);
|
||||
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
||||
|
||||
/** 优化222+242: 直接调用模块级函数,消除 .bind() 闭包分配 */
|
||||
/* 解析 sfnt header */
|
||||
const flavor = view.getUint32(0, false);
|
||||
const numTables = view.getUint16(4, false);
|
||||
const flavor = readU32(data, 0);
|
||||
const numTables = readU16(data, 4);
|
||||
|
||||
/* 解析 Table Directory */
|
||||
const tables = [];
|
||||
for (let i = 0; i < numTables; i++) {
|
||||
const off = 12 + i * 16;
|
||||
const tag = String.fromCharCode(data[off], data[off + 1], data[off + 2], data[off + 3]);
|
||||
const offset = view.getUint32(off + 8, false);
|
||||
const length = view.getUint32(off + 12, false);
|
||||
const offset = readU32(data, off + 8);
|
||||
const length = readU32(data, off + 12);
|
||||
tables.push({ tag, offset, length, tagIndex: getTagIndex(tag) });
|
||||
}
|
||||
|
||||
/* 移除 DSIG,按 tag 升序排列 */
|
||||
const filtered = tables.filter(t => t.tag !== "DSIG");
|
||||
filtered.sort((a, b) => a.tagIndex - b.tagIndex || (a.tag < b.tag ? -1 : a.tag > b.tag ? 1 : 0));
|
||||
filtered.sort(sortDirEntries);
|
||||
|
||||
/* 查找关键表 */
|
||||
let indexToLocFormat = 0;
|
||||
@ -669,9 +693,11 @@ function encodeTTFToWOFF2(ttfBuffer) {
|
||||
let glyfTable = null;
|
||||
let locaTable = null;
|
||||
|
||||
for (const t of filtered) {
|
||||
if (t.tag === "head") indexToLocFormat = view.getUint16(t.offset + 50, false);
|
||||
if (t.tag === "maxp") numGlyphs = view.getUint16(t.offset + 4, false);
|
||||
/* 优化: for...of → for 循环,消除迭代器协议开销 */
|
||||
for (var fi = 0, fl = filtered.length; fi < fl; fi++) {
|
||||
var t = filtered[fi];
|
||||
if (t.tag === "head") indexToLocFormat = readU16(data, t.offset + 50);
|
||||
if (t.tag === "maxp") numGlyphs = readU16(data, t.offset + 4);
|
||||
if (t.tag === "glyf") glyfTable = t;
|
||||
if (t.tag === "loca") locaTable = t;
|
||||
}
|
||||
@ -689,7 +715,8 @@ function encodeTTFToWOFF2(ttfBuffer) {
|
||||
const dirEntries = [];
|
||||
let totalDirSize = 0;
|
||||
|
||||
for (const t of filtered) {
|
||||
for (var fi2 = 0, fl2 = filtered.length; fi2 < fl2; fi2++) {
|
||||
var t = filtered[fi2];
|
||||
if (t.tag === "loca") {
|
||||
/** 优化:直接使用 transformGlyfAndLoca 返回的 locaOrigLength,避免重复计算 */
|
||||
const origLength = glyfTransformed ? glyfTransformed.locaOrigLength : t.length;
|
||||
@ -698,7 +725,7 @@ function encodeTTFToWOFF2(ttfBuffer) {
|
||||
flags: t.tagIndex,
|
||||
origLength,
|
||||
transformLength: 0,
|
||||
data: new Uint8Array(0),
|
||||
data: EMPTY_UINT8,
|
||||
hasTransform: true,
|
||||
});
|
||||
totalDirSize += 1 + sizeUIntBase128(origLength) + sizeUIntBase128(0);
|
||||
@ -743,54 +770,54 @@ function encodeTTFToWOFF2(ttfBuffer) {
|
||||
for (let i = 0; i < dirEntries.length; i++) totalTableDataSize += dirEntries[i].transformLength;
|
||||
const uncompressedData = new Uint8Array(totalTableDataSize);
|
||||
let dataPos = 0;
|
||||
for (const entry of dirEntries) {
|
||||
/* 优化: for 循环替代 for...of,避免迭代器对象分配 */
|
||||
for (let di = 0; di < dirEntries.length; di++) {
|
||||
const entry = dirEntries[di];
|
||||
if (entry.transformLength > 0) {
|
||||
uncompressedData.set(entry.data, dataPos);
|
||||
if (entry.isHead) {
|
||||
/* head 表原地修改:清零 checkSumAdjustment,设置 bit 11(headFlags),避免额外拷贝 */
|
||||
const uView = new DataView(uncompressedData.buffer, uncompressedData.byteOffset + dataPos, entry.transformLength);
|
||||
uView.setUint32(8, 0, false);
|
||||
const headFlags = uView.getUint16(44, false);
|
||||
uView.setUint16(44, headFlags | (1 << 11), false);
|
||||
/* 优化: 用 Uint8Array 直接写入替代 DataView,消除每次 head 表的 DataView 分配 */
|
||||
const base = dataPos;
|
||||
uncompressedData[base + 8] = uncompressedData[base + 9] = uncompressedData[base + 10] = uncompressedData[base + 11] = 0;
|
||||
const headFlags = (uncompressedData[base + 44] << 8) | uncompressedData[base + 45];
|
||||
const newFlags = headFlags | (1 << 11);
|
||||
uncompressedData[base + 44] = (newFlags >> 8) & 0xFF;
|
||||
uncompressedData[base + 45] = newFlags & 0xFF;
|
||||
}
|
||||
dataPos += entry.transformLength;
|
||||
}
|
||||
}
|
||||
|
||||
/* Brotli 压缩,传入 SIZE_HINT 帮助预分配内部缓冲区 */
|
||||
const brotliOptions = totalTableDataSize > 0
|
||||
? { params: {
|
||||
[BROTLI_PARAM_QUALITY]: 8,
|
||||
[BROTLI_PARAM_SIZE_HINT]: totalTableDataSize,
|
||||
}}
|
||||
: BROTLI_OPTIONS_BASE;
|
||||
const compressedData = brotliCompressSync(uncompressedData, brotliOptions);
|
||||
if (totalTableDataSize > 0) BROTLI_OPTIONS_WITH_HINT.params[BROTLI_PARAM_SIZE_HINT] = totalTableDataSize;
|
||||
const compressedData = brotliCompressSync(uncompressedData, totalTableDataSize > 0 ? BROTLI_OPTIONS_WITH_HINT : BROTLI_OPTIONS_BASE);
|
||||
|
||||
/* 组装 WOFF2(预计算 padding,避免额外拷贝) */
|
||||
const rawLength = WOFF2_HEADER_SIZE + totalDirSize + compressedData.length;
|
||||
const paddedLength = (rawLength + 3) & ~3;
|
||||
const woff2 = new Uint8Array(paddedLength);
|
||||
const woff2View = new DataView(woff2.buffer, woff2.byteOffset, woff2.byteLength);
|
||||
/** 优化222+242: 直接调用模块级函数,消除 .bind() 闭包分配 */
|
||||
|
||||
/* Header */
|
||||
woff2View.setUint32(0, WOFF2_SIGNATURE, false);
|
||||
woff2View.setUint32(4, flavor, false);
|
||||
woff2View.setUint32(8, paddedLength, false);
|
||||
woff2View.setUint16(12, dirEntries.length, false);
|
||||
woff2View.setUint16(14, 0, false);
|
||||
woff2View.setUint32(16, totalSfntSize, false);
|
||||
woff2View.setUint32(20, compressedData.length, false);
|
||||
woff2View.setUint16(24, 1, false);
|
||||
woff2View.setUint16(26, 0, false);
|
||||
woff2View.setUint32(28, 0, false);
|
||||
woff2View.setUint32(32, 0, false);
|
||||
woff2View.setUint32(36, 0, false);
|
||||
woff2View.setUint32(40, 0, false);
|
||||
woff2View.setUint32(44, 0, false);
|
||||
writeU32(woff2, WOFF2_SIGNATURE, 0);
|
||||
writeU32(woff2, flavor, 4);
|
||||
writeU32(woff2, paddedLength, 8);
|
||||
writeU16(woff2, dirEntries.length, 12);
|
||||
writeU16(woff2, 0, 14);
|
||||
writeU32(woff2, totalSfntSize, 16);
|
||||
writeU32(woff2, compressedData.length, 20);
|
||||
writeU16(woff2, 1, 24);
|
||||
writeU16(woff2, 0, 26);
|
||||
writeU32(woff2, 0, 28);
|
||||
writeU32(woff2, 0, 32);
|
||||
writeU32(woff2, 0, 36);
|
||||
writeU32(woff2, 0, 40);
|
||||
writeU32(woff2, 0, 44);
|
||||
|
||||
/* Table Directory */
|
||||
let dirPos = WOFF2_HEADER_SIZE;
|
||||
for (const entry of dirEntries) {
|
||||
for (let di = 0; di < dirEntries.length; di++) {
|
||||
const entry = dirEntries[di];
|
||||
woff2[dirPos++] = entry.flags;
|
||||
if (entry.tagIndex === 63) {
|
||||
for (let ci = 0; ci < 4; ci++) woff2[dirPos++] = entry.tag.charCodeAt(ci);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user