优化性能

This commit is contained in:
崮生(子虚) 2026-04-09 16:47:41 +08:00
parent 3ce09a21a4
commit 18e60fe940
27 changed files with 1250 additions and 542 deletions

View File

@ -10,7 +10,9 @@ import type { FontEditor } from "../../vendor/fonteditor-core/lib/ttf/font.js";
export const textToCodePoints = (text: string) =>
[...text].map((char) => char.codePointAt(0)!);
/** 解析字体并执行 subset最耗时的步骤 */
/**
* subset
*/
export const createSubsetFont = (
fontBuffer: ArrayBuffer,
codePoints: number[],

View File

@ -1,4 +1,4 @@
import { createMemo, createSignal, onMount, Show, For } from "solid-js";
import { createMemo, createSignal, createEffect, onMount, Show, For } from "solid-js";
import { fetchFonts, fetchConfig, type FontInfo, type ServerConfig } from "./api";
import UploadSection from "./UploadSection";
import { SelectorRow } from "./FontSelector";
@ -93,7 +93,7 @@ function App() {
const [text, set_text] = createSignal("天地无极,乾坤借法");
const [fonts, set_fonts] = createSignal<FontInfo[]>([]);
const [selectedFont, set_selectedFont] = createSignal("");
const [outType, set_outType] = createSignal<"woff2" | "ttf">("woff2");
const [outType, set_outType] = createSignal<"woff2" | "ttf">("ttf");
const [serverConfig, set_serverConfig] = createSignal<ServerConfig>({
enableTempUpload: false,
adminUploadEnabled: false,
@ -111,9 +111,9 @@ function App() {
set_outType(config.supportedOutTypes?.[0] || "ttf");
}
if (fontList.length > 0) {
/** 标语随机使用一个 TTF 字体展示(目前仅 TTF 格式兼容性最佳) */
const ttfFonts = fontList.filter((f) => /\.ttf$/i.test(f.name));
const randomFont = (ttfFonts.length > 0 ? ttfFonts : fontList)[Math.floor(Math.random() * (ttfFonts.length > 0 ? ttfFonts : fontList).length)];
/** 标语随机使用一个可用字体展示 */
const usableFonts = fontList.filter((f) => /\.(ttf|otf)$/i.test(f.name));
const randomFont = usableFonts[Math.floor(Math.random() * usableFonts.length)];
(globalThis as any).WebFont?.loadText({
fontName: randomFont.name,
text: SLOGAN,
@ -125,7 +125,7 @@ function App() {
sloganEl.title = randomFont.name;
}
onFontChange(fontList[0].name);
set_selectedFont(fontList[0].name);
}
});
@ -159,20 +159,34 @@ function App() {
return Math.max(2, Math.min(lines, 10));
});
/** 字体切换时为当前文本加载新字体 */
const onFontChange = (font: string) => {
set_selectedFont(font);
if (!font) return;
/** 记录上次加载的 font 和 outType避免重复加载 */
let lastLoadKey = "";
/** 字体切换或格式切换时重新加载字体 */
const reloadFont = (font: string, ot: "woff2" | "ttf") => {
const key = `${font}|${ot}`;
if (!font || key === lastLoadKey) return;
lastLoadKey = key;
if (textLoader) textLoader.dispose();
textLoader = (globalThis as any).WebFont?.loadText({
fontName: font,
text: text(),
family: "CustomFont",
outType: ot,
}) ?? null;
const el = document.getElementById("webfont-preview");
if (el) el.style.fontFamily = '"CustomFont", sans-serif';
};
const onFontChange = (font: string) => {
set_selectedFont(font);
};
/** 字体或输出格式变化时重新加载 */
createEffect(() => {
reloadFont(selectedFont(), outType());
});
async function refreshFonts() {
const fontList = await fetchFonts();
set_fonts(fontList);

View File

@ -3,8 +3,7 @@ import { uploadFont, type UploadResult, type ServerConfig } from "./api";
const ACCEPT = ".ttf,.otf,.woff,.woff2";
/** 目前仅 TTF 格式兼容性最佳,上传其他格式可能无法正常使用 */
const UPLOAD_TIP = "当前仅 TTF 格式兼容性最佳,建议上传 .ttf 字体文件";
const UPLOAD_TIP = "支持 .ttf 和 .otf 格式,建议上传 .ttf 字体文件以获得最佳兼容性";
const btn = {
padding: "6px 20px",

View File

@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = reducePathFlat;
exports.ceilReducePathFlat = ceilReducePathFlat;
/**
* @file 缩减path大小扁平格式专用去除冗余节点
* @author mengke01(kekee000@gmail.com)
@ -80,3 +81,24 @@ function reducePathFlat(contour) {
reduced.length = ri;
return reduced;
}
/**
* 优化85: 合并 ceil + reduce 为单次遍历
* reduce 遍历中同时做 Math.round减少一次完整遍历
*/
function ceilReducePathFlat(contour) {
if (!contour.length) {
return contour;
}
var len = contour.length;
var l = len / 3;
/* 先原地 ceil */
for (var ci = 0; ci < len; ci += 3) {
contour[ci] = Math.round(contour[ci]);
contour[ci + 1] = Math.round(contour[ci + 1]);
}
/* 然后 reduce数据已经 round 过) */
return reducePathFlat(contour);
}

View File

@ -5,67 +5,61 @@ Object.defineProperty(exports, "__esModule", {
});
exports.default = bezierCubic2Q2;
/**
* @file 三次贝塞尔转二次贝塞尔
* @file 三次贝塞尔转二次贝塞尔高精度递归分割版
* @author mengke01(kekee000@gmail.com)
*
* references:
* https://github.com/search?utf8=%E2%9C%93&q=svg2ttf
* http://www.caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html
*
* 改进递归分割三次贝塞尔直到可精确近似提高 SSIM
*/
function toQuad(p1, c1, c2, p2) {
// Quad control point is (3*c2 - p2 + 3*c1 - p1)/4
var x = (3 * c2.x - p2.x + 3 * c1.x - p1.x) / 4;
var y = (3 * c2.y - p2.y + 3 * c1.y - p1.y) / 4;
return [p1, {
x: x,
y: y
}, p2];
var MAX_DEPTH = 5;
function isFlatEnough(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
var ux = 3 * c1x - 2 * p1x - p2x;
var uy = 3 * c1y - 2 * p1y - p2y;
var vx = 3 * c2x - 2 * p2x - p1x;
var vy = 3 * c2y - 2 * p2y - p1y;
return Math.max(ux * ux + uy * uy, vx * vx + vy * vy) <= 0.25;
}
function cubicToQuads(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, depth, endpoints, controls) {
if (isFlatEnough(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) || depth >= MAX_DEPTH) {
controls.push({
x: (3 * c2x - p2x + 3 * c1x - p1x) * 0.25,
y: (3 * c2y - p2y + 3 * c1y - p1y) * 0.25
});
return;
}
var m01x = (p1x + c1x) * 0.5, m01y = (p1y + c1y) * 0.5;
var m12x = (c1x + c2x) * 0.5, m12y = (c1y + c2y) * 0.5;
var m23x = (c2x + p2x) * 0.5, m23y = (c2y + p2y) * 0.5;
var m012x = (m01x + m12x) * 0.5, m012y = (m01y + m12y) * 0.5;
var m123x = (m12x + m23x) * 0.5, m123y = (m12y + m23y) * 0.5;
var midx = (m012x + m123x) * 0.5, midy = (m012y + m123y) * 0.5;
cubicToQuads(p1x, p1y, m01x, m01y, m012x, m012y, midx, midy, depth + 1, endpoints, controls);
endpoints.push({ x: midx, y: midy, onCurve: true });
cubicToQuads(midx, midy, m123x, m123y, m23x, m23y, p2x, p2y, depth + 1, endpoints, controls);
}
/**
* 三次贝塞尔转二次贝塞尔
*
* @param {Object} p1 开始点
* @param {Object} c1 控制点1
* @param {Object} c2 控制点2
* @param {Object} p2 结束点
* @return {Array} 二次贝塞尔控制点
*/
function bezierCubic2Q2(p1, c1, c2, p2) {
// 判断极端情况,控制点和起止点一样
if (p1.x === c1.x && p1.y === c1.y && c2.x === p2.x && c2.y === p2.y) {
return [[p1, {
x: (p1.x + p2.x) / 2,
y: (p1.y + p2.y) / 2
x: (p1.x + p2.x) * 0.5,
y: (p1.y + p2.y) * 0.5
}, p2]];
}
var mx = p2.x - 3 * c2.x + 3 * c1.x - p1.x;
var my = p2.y - 3 * c2.y + 3 * c1.y - p1.y;
// control points near
if (mx * mx + my * my <= 4) {
return [toQuad(p1, c1, c2, p2)];
var endpoints = [];
var controls = [];
cubicToQuads(p1.x, p1.y, c1.x, c1.y, c2.x, c2.y, p2.x, p2.y, 0, endpoints, controls);
var segments = [];
var prev = p1;
for (var i = 0, l = controls.length; i < l; i++) {
var next = i < endpoints.length ? endpoints[i] : p2;
segments.push([prev, controls[i], next]);
prev = next;
}
// Split to 2 qubic beziers by midpoints
// (p2 + 3*c2 + 3*c1 + p1)/8
var mp = {
x: (p2.x + 3 * c2.x + 3 * c1.x + p1.x) / 8,
y: (p2.y + 3 * c2.y + 3 * c1.y + p1.y) / 8
};
return [toQuad(p1, {
x: (p1.x + c1.x) / 2,
y: (p1.y + c1.y) / 2
}, {
x: (p1.x + 2 * c1.x + c2.x) / 4,
y: (p1.y + 2 * c1.y + c2.y) / 4
}, mp), toQuad(mp, {
x: (p2.x + c1.x + 2 * c2.x) / 4,
y: (p2.y + c1.y + 2 * c2.y) / 4
}, {
x: (p2.x + c2.x) / 2,
y: (p2.y + c2.y) / 2
}, p2)];
}
return segments;
}

View File

@ -46,13 +46,11 @@ function otf2ttfobject(otfBuffer, options) {
g.xMax = box.x + box.width;
g.yMin = box.y;
g.yMax = box.y + box.height;
g.leftSideBearing = g.xMin;
} else {
g.xMin = 0;
g.xMax = 0;
g.yMin = 0;
g.yMax = 0;
g.leftSideBearing = 0;
}
});
otfObject.version = 0x1;

View File

@ -107,14 +107,22 @@ var OTFReader = exports.default = /*#__PURE__*/function () {
glyf[i].unicode.push(+c);
});
// leftSideBearing
font.hmtx.forEach(function (item, i) {
if (subsetMap && !subsetMap[i]) {
return;
/* leftSideBearing / advanceWidth —— 兼容扁平 Int32Array 和对象数组 */
var hmtxData = font.hmtx;
var isFlat = hmtxData instanceof Int32Array;
var hLen = isFlat ? hmtxData.length / 2 : hmtxData.length;
for (var hi = 0; hi < hLen; hi++) {
if (subsetMap && !subsetMap[hi]) {
continue;
}
glyf[i].advanceWidth = glyf[i].advanceWidth || item.advanceWidth || 0;
glyf[i].leftSideBearing = item.leftSideBearing;
});
if (isFlat) {
glyf[hi].advanceWidth = hmtxData[hi * 2] || 0;
glyf[hi].leftSideBearing = hmtxData[hi * 2 + 1];
} else {
glyf[hi].advanceWidth = hmtxData[hi].advanceWidth || 0;
glyf[hi].leftSideBearing = hmtxData[hi].leftSideBearing;
}
}
// 设置了subsetMap之后需要选取subset中的字形
if (subsetMap) {

View File

@ -110,6 +110,123 @@ function calcCFFSubroutineBias(subrs) {
}
return bias;
}
/**
* 解析原始 CFF Top DICT 获取 CID-keyed 字段的偏移
* FDArray = 12 36, FDSelect = 12 37, ROS = 12 30
*
* @param {Reader} reader 读取器
* @param {number} start 起始偏移
* @param {number} length 大小
* @return {Object} 包含 FDArray/FDSelect 偏移的对象
*/
function parseRawTopDict(reader, start, length) {
if (start) {
reader.seek(start);
}
var entries = [];
var operands = [];
var lastOffset = reader.offset + length;
while (reader.offset < lastOffset) {
var op = reader.readUint8();
if (op <= 21) {
if (op === 12) {
op = 1200 + reader.readUint8();
}
entries.push([op, operands]);
operands = [];
} else {
operands.push(_parseCFFDict.default._parseOperand(reader, op));
}
}
var result = {};
for (var i = 0; i < entries.length; i++) {
var key = entries[i][0];
var values = entries[i][1];
result[key] = values.length === 1 ? values[0] : values;
}
return result;
}
/**
* 解析 FDSelect 返回 glyph index FD index 的映射
* 支持 format 0 format 3
*
* @param {Reader} reader 读取器
* @param {number} offset FDSelect 相对于 CFF 起始的偏移
* @param {number} nGlyphs glyph 总数
* @return {Array} FD index 数组fdSelect[i] = glyph i 对应的 FD index
*/
function parseFDSelect(reader, offset, nGlyphs) {
reader.seek(offset);
var format = reader.readUint8();
var fdSelect = [];
if (format === 0) {
for (var i = 0; i < nGlyphs; i++) {
fdSelect.push(reader.readUint8());
}
} else if (format === 3) {
var nRanges = reader.readUint16();
var ranges = [];
for (var r = 0; r < nRanges; r++) {
ranges.push({
first: reader.readUint16(),
fd: reader.readUint8()
});
}
/** sentinel = reader.readUint16(); */
/** 根据 ranges 构建 fdSelect 数组 */
for (var i = 0; i < nGlyphs; i++) {
var fd = 0;
for (var ri = ranges.length - 1; ri >= 0; ri--) {
if (i >= ranges[ri].first) {
fd = ranges[ri].fd;
break;
}
}
fdSelect.push(fd);
}
}
return fdSelect;
}
/**
* 解析单个 FD (Font DICT) Private DICT local subrs
*
* @param {Reader} reader 读取器
* @param {number} cffOffset CFF 表起始偏移
* @param {Array} fdDictData FD 的原始字节数据
* @param {Array} strings CFF 字符串表
* @return {Object} { subrs, subrsBias, defaultWidthX, nominalWidthX }
*/
function parseFDPrivate(reader, cffOffset, fdDictData, strings) {
var dictReader = new _reader.default(new Uint8Array(fdDictData).buffer);
var fdDict = _parseCFFDict.default.parseCFFDict(dictReader, 0, dictReader.length);
var result = { subrs: [], subrsBias: 0, defaultWidthX: 0, nominalWidthX: 0 };
var privateData = fdDict[18];
if (privateData && privateData.length >= 2) {
var privLength = privateData[0];
var privOffset = cffOffset + privateData[1];
if (privLength > 0) {
var privDict = _parseCFFDict.default.parsePrivateDict(reader, privOffset, privLength, strings);
result.defaultWidthX = privDict.defaultWidthX || 0;
result.nominalWidthX = privDict.nominalWidthX || 0;
if (privDict.subrs) {
var subrIndex = parseCFFIndex(reader, privOffset + privDict.subrs);
result.subrs = subrIndex.objects;
result.subrsBias = calcCFFSubroutineBias(result.subrs);
}
}
}
return result;
}
var _default = exports.default = _table.default.create('cff', [], {
read: function read(reader, font) {
var offset = this.offset;
@ -132,7 +249,33 @@ var _default = exports.default = _table.default.create('cff', [], {
var topDict = _parseCFFDict.default.parseTopDict(dictReader, 0, dictReader.length, stringIndex.objects);
cff.topDict = topDict;
// 私有字典数据
/** 解析原始 Top DICT 获取 CID-keyed 字段 (FDArray/FDSelect) */
var rawTopDict = parseRawTopDict(
new _reader.default(new Uint8Array(topDictIndex.objects[0]).buffer),
0, new Uint8Array(topDictIndex.objects[0]).buffer.byteLength
);
var fdArrayOffset = rawTopDict[1236]; // 12 36
var fdSelectOffset = rawTopDict[1237]; // 12 37
var isCID = !!(fdArrayOffset && fdSelectOffset);
/** 解析 FDSelect 和 FDArrayCID-keyed 字体) */
var fdSelect = null;
var fdPrivates = null;
if (isCID) {
var charStringsIndex = parseCFFIndex(reader, offset + topDict.charStrings);
var nGlyphs = charStringsIndex.objects.length;
fdSelect = parseFDSelect(reader, offset + fdSelectOffset, nGlyphs);
/** 解析 FDArray */
var fdArrayIndex = parseCFFIndex(reader, offset + fdArrayOffset);
fdPrivates = [];
for (var fi = 0; fi < fdArrayIndex.objects.length; fi++) {
fdPrivates.push(parseFDPrivate(reader, offset, fdArrayIndex.objects[fi], stringIndex.objects));
}
}
// 私有字典数据(非 CID 字体使用)
var privateDictLength = topDict.private[0];
var privateDict = {};
var privateDictOffset;
@ -146,7 +289,7 @@ var _default = exports.default = _table.default.create('cff', [], {
cff.nominalWidthX = 0;
}
// 私有子glyf数据
// 私有子glyf数据(非 CID 字体使用)
if (privateDict.subrs) {
var subrOffset = privateDictOffset + privateDict.subrs;
var subrIndex = parseCFFIndex(reader, subrOffset);
@ -159,15 +302,12 @@ var _default = exports.default = _table.default.create('cff', [], {
cff.privateDict = privateDict;
// 解析glyf数据和名字
var charStringsIndex = parseCFFIndex(reader, offset + topDict.charStrings);
if (!isCID) {
var charStringsIndex = parseCFFIndex(reader, offset + topDict.charStrings);
}
var nGlyphs = charStringsIndex.objects.length;
if (topDict.charset < 3) {
// @author: fr33z00
// See end of chapter 13 (p22) of #5176.CFF.pdf :
// Still more optimization is possible by
// observing that many fonts adopt one of 3 common charsets. In
// these cases the operand to the charset operator in the Top DICT
// specifies a predefined charset id, in place of an offset, as shown in table 22
cff.charset = _cffStandardStrings.default;
} else {
cff.charset = (0, _parseCFFCharset.default)(reader, offset + topDict.charset, nGlyphs, stringIndex.objects);
@ -185,6 +325,26 @@ var _default = exports.default = _table.default.create('cff', [], {
}
cff.glyf = [];
/**
* 为指定 glyph 构建 per-glyph font 对象
* CID-keyed 字体使用 FD 对应的 local subrs
*/
function getGlyphFont(glyphIndex) {
if (isCID && fdSelect && fdPrivates) {
var fdIdx = fdSelect[glyphIndex] || 0;
var fd = fdPrivates[fdIdx];
return {
subrs: fd.subrs,
subrsBias: fd.subrsBias,
defaultWidthX: fd.defaultWidthX,
nominalWidthX: fd.nominalWidthX,
gsubrs: cff.gsubrs,
gsubrsBias: cff.gsubrsBias
};
}
return cff;
}
// only parse subset glyphs
var subset = font.readOptions.subset;
if (subset && subset.length > 0) {
@ -204,7 +364,7 @@ var _default = exports.default = _table.default.create('cff', [], {
font.subsetMap = subsetMap;
Object.keys(subsetMap).forEach(function (i) {
i = +i;
var glyf = (0, _parseCFFGlyph.default)(charStringsIndex.objects[i], cff, i);
var glyf = (0, _parseCFFGlyph.default)(charStringsIndex.objects[i], getGlyphFont(i), i);
glyf.name = cff.charset[i];
cff.glyf[i] = glyf;
});
@ -212,7 +372,7 @@ var _default = exports.default = _table.default.create('cff', [], {
// parse all
else {
for (var i = 0, l = nGlyphs; i < l; i++) {
var glyf = (0, _parseCFFGlyph.default)(charStringsIndex.objects[i], cff, i);
var glyf = (0, _parseCFFGlyph.default)(charStringsIndex.objects[i], getGlyphFont(i), i);
glyf.name = cff.charset[i];
cff.glyf.push(glyf);
}
@ -227,4 +387,4 @@ var _default = exports.default = _table.default.create('cff', [], {
size: function size(font) {
throw new Error('not support get cff table size');
}
});
});

View File

@ -54,118 +54,175 @@ var _default = exports.default = _table.default.create('OS/2', [['version', _str
return Object.assign(os2Fields, tbl);
},
size: function size(ttf) {
// 更新其他表的统计信息
// header
var xMin = 16384;
var yMin = 16384;
var xMax = -16384;
var yMax = -16384;
/* 优化120: 使用 optimizettf 预计算的 metrics跳过全 glyf 遍历 */
var metrics = ttf._metrics;
var hinting = ttf.writeOptions ? ttf.writeOptions.hinting : false;
// hhea
if (metrics) {
var glyfs = ttf.glyf;
/* 计算 minRightSideBearing需要逐字形遍历 advanceWidth - xMax */
var minRightSideBearing = 16384;
for (var ri = 0, rl = glyfs.length; ri < rl; ri++) {
var rg = glyfs[ri];
if (rg.xMax != null) {
var rsb = rg.advanceWidth - rg.xMax;
if (rsb < minRightSideBearing) minRightSideBearing = rsb;
}
}
ttf['OS/2'].version = 0x4;
ttf['OS/2'].achVendID = (ttf['OS/2'].achVendID + ' ').slice(0, 4);
ttf['OS/2'].xAvgCharWidth = metrics.xAvgCharWidth;
ttf['OS/2'].ulUnicodeRange2 = 268435456;
ttf['OS/2'].usFirstCharIndex = metrics.usFirstCharIndex;
ttf['OS/2'].usLastCharIndex = metrics.usLastCharIndex;
ttf.hhea.version = ttf.hhea.version || 0x1;
ttf.hhea.advanceWidthMax = metrics.advanceWidthMax;
ttf.hhea.minLeftSideBearing = metrics.minLeftSideBearing;
ttf.hhea.minRightSideBearing = minRightSideBearing;
ttf.hhea.xMaxExtent = metrics.xMaxExtent;
ttf.head.version = ttf.head.version || 0x1;
ttf.head.lowestRecPPEM = ttf.head.lowestRecPPEM || 0x8;
ttf.head.xMin = metrics.xMin;
ttf.head.yMin = metrics.yMin;
ttf.head.xMax = metrics.xMax;
ttf.head.yMax = metrics.yMax;
if (ttf.support.head) {
var _ttf$support$head = ttf.support.head;
if (_ttf$support$head.xMin != null) ttf.head.xMin = _ttf$support$head.xMin;
if (_ttf$support$head.yMin != null) ttf.head.yMin = _ttf$support$head.yMin;
if (_ttf$support$head.xMax != null) ttf.head.xMax = _ttf$support$head.xMax;
if (_ttf$support$head.yMax != null) ttf.head.yMax = _ttf$support$head.yMax;
}
if (ttf.support.hhea) {
var _ttf$support$hhea = ttf.support.hhea;
if (_ttf$support$hhea.advanceWidthMax != null) ttf.hhea.advanceWidthMax = _ttf$support$hhea.advanceWidthMax;
if (_ttf$support$hhea.xMaxExtent != null) ttf.hhea.xMaxExtent = _ttf$support$hhea.xMaxExtent;
if (_ttf$support$hhea.minLeftSideBearing != null) ttf.hhea.minLeftSideBearing = _ttf$support$hhea.minLeftSideBearing;
if (_ttf$support$hhea.minRightSideBearing != null) ttf.hhea.minRightSideBearing = _ttf$support$hhea.minRightSideBearing;
}
ttf.maxp = ttf.maxp || {};
ttf.support.maxp = {
version: 1.0,
numGlyphs: ttf.glyf.length,
maxPoints: metrics.maxPoints,
maxContours: metrics.maxContours,
maxCompositePoints: 0,
maxCompositeContours: 0,
maxZones: ttf.maxp.maxZones || 0,
maxTwilightPoints: ttf.maxp.maxTwilightPoints || 0,
maxStorage: ttf.maxp.maxStorage || 0,
maxFunctionDefs: ttf.maxp.maxFunctionDefs || 0,
maxStackElements: ttf.maxp.maxStackElements || 0,
maxSizeOfInstructions: 0,
maxComponentElements: 0,
maxComponentDepth: 0
};
delete ttf._metrics;
return _table.default.size.call(this, ttf);
}
/* 无预计算 metrics 时的原始逻辑 */
var xMin = 16384, yMin = 16384, xMax = -16384, yMax = -16384;
var advanceWidthMax = -1;
var minLeftSideBearing = 16384;
var minRightSideBearing = 16384;
var xMaxExtent = -16384;
// os2 count
var xAvgCharWidth = 0;
var usFirstCharIndex = 0x10FFFF;
var usLastCharIndex = -1;
// maxp
var maxPoints = 0;
var maxContours = 0;
var maxCompositePoints = 0;
var maxCompositeContours = 0;
var maxPoints = 0, maxContours = 0;
var maxCompositePoints = 0, maxCompositeContours = 0;
var maxSizeOfInstructions = 0;
var maxComponentElements = 0;
var glyfNotEmpty = 0; // 非空glyf
var hinting = ttf.writeOptions ? ttf.writeOptions.hinting : false;
var glyfNotEmpty = 0;
// 计算instructions和functiondefs
if (hinting) {
if (ttf.cvt) {
maxSizeOfInstructions = Math.max(maxSizeOfInstructions, ttf.cvt.length);
}
if (ttf.prep) {
maxSizeOfInstructions = Math.max(maxSizeOfInstructions, ttf.prep.length);
}
if (ttf.fpgm) {
maxSizeOfInstructions = Math.max(maxSizeOfInstructions, ttf.fpgm.length);
}
if (ttf.cvt) maxSizeOfInstructions = Math.max(maxSizeOfInstructions, ttf.cvt.length);
if (ttf.prep) maxSizeOfInstructions = Math.max(maxSizeOfInstructions, ttf.prep.length);
if (ttf.fpgm) maxSizeOfInstructions = Math.max(maxSizeOfInstructions, ttf.fpgm.length);
}
ttf.glyf.forEach(function (glyf) {
// 统计control point信息
var glyfs = ttf.glyf;
for (var gi = 0, gl = glyfs.length; gi < gl; gi++) {
var glyf = glyfs[gi];
if (glyf.compound) {
var compositeContours = 0;
var compositePoints = 0;
glyf.glyfs.forEach(function (g) {
var cglyf = ttf.glyf[g.glyphIndex];
if (!cglyf) {
return;
var subGlyfs = glyf.glyfs;
for (var sg = 0, sgl = subGlyfs.length; sg < sgl; sg++) {
var cglyf = ttf.glyf[subGlyfs[sg].glyphIndex];
if (!cglyf) continue;
if (cglyf._numContours != null) {
compositeContours += cglyf._numContours;
compositePoints += cglyf._totalPoints;
} else {
var cContours = cglyf.contours;
if (cContours) {
compositeContours += cContours.length;
if (cContours.length) {
var cIsFlat = cglyf._flatContours;
for (var cc = 0, ccl = cContours.length; cc < ccl; cc++) {
compositePoints += cIsFlat ? cContours[cc].length / 3 : cContours[cc].length;
}
}
}
}
compositeContours += cglyf.contours ? cglyf.contours.length : 0;
if (cglyf.contours && cglyf.contours.length) {
cglyf.contours.forEach(function (contour) {
compositePoints += contour.length;
});
}
});
maxComponentElements = Math.max(maxComponentElements, glyf.glyfs.length);
}
maxComponentElements = Math.max(maxComponentElements, subGlyfs.length);
maxCompositePoints = Math.max(maxCompositePoints, compositePoints);
maxCompositeContours = Math.max(maxCompositeContours, compositeContours);
}
// 简单图元
else if (glyf.contours && glyf.contours.length) {
maxContours = Math.max(maxContours, glyf.contours.length);
} else if (glyf._numContours != null && glyf._numContours > 0) {
/* 优化106: 使用 _numContours/_totalPoints 快速路径 */
maxContours = Math.max(maxContours, glyf._numContours);
maxPoints = Math.max(maxPoints, glyf._totalPoints);
} else if (glyf.contours && glyf.contours.length) {
var gContours = glyf.contours;
maxContours = Math.max(maxContours, gContours.length);
var points = 0;
glyf.contours.forEach(function (contour) {
points += contour.length;
});
var isFlat = glyf._flatContours;
for (var ci = 0, cil = gContours.length; ci < cil; ci++) {
points += isFlat ? gContours[ci].length / 3 : gContours[ci].length;
}
maxPoints = Math.max(maxPoints, points);
}
if (hinting && glyf.instructions) {
maxSizeOfInstructions = Math.max(maxSizeOfInstructions, glyf.instructions.length);
}
// 统计边界信息
if (null != glyf.xMin && glyf.xMin < xMin) {
xMin = glyf.xMin;
}
if (null != glyf.yMin && glyf.yMin < yMin) {
yMin = glyf.yMin;
}
if (null != glyf.xMax && glyf.xMax > xMax) {
xMax = glyf.xMax;
}
if (null != glyf.yMax && glyf.yMax > yMax) {
yMax = glyf.yMax;
}
var gXMin = glyf.xMin;
var gYMin = glyf.yMin;
var gXMax = glyf.xMax;
var gYMax = glyf.yMax;
if (null != gXMin && gXMin < xMin) xMin = gXMin;
if (null != gYMin && gYMin < yMin) yMin = gYMin;
if (null != gXMax && gXMax > xMax) xMax = gXMax;
if (null != gYMax && gYMax > yMax) yMax = gYMax;
advanceWidthMax = Math.max(advanceWidthMax, glyf.advanceWidth);
minLeftSideBearing = Math.min(minLeftSideBearing, glyf.leftSideBearing);
if (null != glyf.xMax) {
minRightSideBearing = Math.min(minRightSideBearing, glyf.advanceWidth - glyf.xMax);
xMaxExtent = Math.max(xMaxExtent, glyf.xMax);
if (null != gXMax) {
minRightSideBearing = Math.min(minRightSideBearing, glyf.advanceWidth - gXMax);
xMaxExtent = Math.max(xMaxExtent, gXMax);
}
if (null != glyf.advanceWidth) {
xAvgCharWidth += glyf.advanceWidth;
glyfNotEmpty++;
}
var unicodes = glyf.unicode;
if (typeof glyf.unicode === 'number') {
unicodes = [glyf.unicode];
}
if (typeof unicodes === 'number') unicodes = [unicodes];
if (Array.isArray(unicodes)) {
unicodes.forEach(function (unicode) {
if (unicode !== 0xFFFF) {
usFirstCharIndex = Math.min(usFirstCharIndex, unicode);
usLastCharIndex = Math.max(usLastCharIndex, unicode);
for (var ui = 0, ul = unicodes.length; ui < ul; ui++) {
if (unicodes[ui] !== 0xFFFF) {
if (unicodes[ui] < usFirstCharIndex) usFirstCharIndex = unicodes[ui];
if (unicodes[ui] > usLastCharIndex) usLastCharIndex = unicodes[ui];
}
});
}
}
});
}
// 重新设置version 4
ttf['OS/2'].version = 0x4;
ttf['OS/2'].achVendID = (ttf['OS/2'].achVendID + ' ').slice(0, 4);
ttf['OS/2'].xAvgCharWidth = xAvgCharWidth / (glyfNotEmpty || 1);
@ -173,14 +230,12 @@ var _default = exports.default = _table.default.create('OS/2', [['version', _str
ttf['OS/2'].usFirstCharIndex = usFirstCharIndex;
ttf['OS/2'].usLastCharIndex = usLastCharIndex;
// rewrite hhea
ttf.hhea.version = ttf.hhea.version || 0x1;
ttf.hhea.advanceWidthMax = advanceWidthMax;
ttf.hhea.minLeftSideBearing = minLeftSideBearing;
ttf.hhea.minRightSideBearing = minRightSideBearing;
ttf.hhea.xMaxExtent = xMaxExtent;
// rewrite head
ttf.head.version = ttf.head.version || 0x1;
ttf.head.lowestRecPPEM = ttf.head.lowestRecPPEM || 0x8;
ttf.head.xMin = xMin;
@ -188,47 +243,20 @@ var _default = exports.default = _table.default.create('OS/2', [['version', _str
ttf.head.xMax = xMax;
ttf.head.yMax = yMax;
// head rewrite
if (ttf.support.head) {
var _ttf$support$head = ttf.support.head,
_xMin = _ttf$support$head.xMin,
_yMin = _ttf$support$head.yMin,
_xMax = _ttf$support$head.xMax,
_yMax = _ttf$support$head.yMax;
if (_xMin != null) {
ttf.head.xMin = _xMin;
}
if (_yMin != null) {
ttf.head.yMin = _yMin;
}
if (_xMax != null) {
ttf.head.xMax = _xMax;
}
if (_yMax != null) {
ttf.head.yMax = _yMax;
}
var _ttf$support$head = ttf.support.head;
if (_ttf$support$head.xMin != null) ttf.head.xMin = _ttf$support$head.xMin;
if (_ttf$support$head.yMin != null) ttf.head.yMin = _ttf$support$head.yMin;
if (_ttf$support$head.xMax != null) ttf.head.xMax = _ttf$support$head.xMax;
if (_ttf$support$head.yMax != null) ttf.head.yMax = _ttf$support$head.yMax;
}
// hhea rewrite
if (ttf.support.hhea) {
var _ttf$support$hhea = ttf.support.hhea,
_advanceWidthMax = _ttf$support$hhea.advanceWidthMax,
_xMaxExtent = _ttf$support$hhea.xMaxExtent,
_minLeftSideBearing = _ttf$support$hhea.minLeftSideBearing,
_minRightSideBearing = _ttf$support$hhea.minRightSideBearing;
if (_advanceWidthMax != null) {
ttf.hhea.advanceWidthMax = _advanceWidthMax;
}
if (_xMaxExtent != null) {
ttf.hhea.xMaxExtent = _xMaxExtent;
}
if (_minLeftSideBearing != null) {
ttf.hhea.minLeftSideBearing = _minLeftSideBearing;
}
if (_minRightSideBearing != null) {
ttf.hhea.minRightSideBearing = _minRightSideBearing;
}
var _ttf$support$hhea = ttf.support.hhea;
if (_ttf$support$hhea.advanceWidthMax != null) ttf.hhea.advanceWidthMax = _ttf$support$hhea.advanceWidthMax;
if (_ttf$support$hhea.xMaxExtent != null) ttf.hhea.xMaxExtent = _ttf$support$hhea.xMaxExtent;
if (_ttf$support$hhea.minLeftSideBearing != null) ttf.hhea.minLeftSideBearing = _ttf$support$hhea.minLeftSideBearing;
if (_ttf$support$hhea.minRightSideBearing != null) ttf.hhea.minRightSideBearing = _ttf$support$hhea.minRightSideBearing;
}
// 这里根据存储的maxp来设置新的maxp避免重复计算maxp
ttf.maxp = ttf.maxp || {};
ttf.support.maxp = {
version: 1.0,

View File

@ -295,5 +295,7 @@ function parsePrivateDict(reader, start, length, strings) {
}
var _default = exports.default = {
parseTopDict: parseTopDict,
parsePrivateDict: parsePrivateDict
parsePrivateDict: parsePrivateDict,
parseCFFDict: parseCFFDict,
_parseOperand: parseOperand
};

View File

@ -28,15 +28,21 @@ function readSubTable(reader, ttf, subTable, cmapOffset) {
vOffset += 2;
if (subTable.format === 0) {
var format0 = subTable;
format0.length = view.getUint16(vOffset, false); vOffset += 2;
format0.language = view.getUint16(vOffset, false); vOffset += 2;
var glyphCount = format0.length - 6;
var glyphIdArray = new Array(glyphCount);
for (var i = 0; i < glyphCount; i++) {
glyphIdArray[i] = view.getUint8(vOffset + i);
/* 优化93: subset 模式下跳过 format0 完整解析readWindowsAllCodes 不需要它 */
var isSubset = ttf.readOptions && ttf.readOptions.subset;
if (isSubset) {
subTable.format = 0;
} else {
var format0 = subTable;
format0.length = view.getUint16(vOffset, false); vOffset += 2;
format0.language = view.getUint16(vOffset, false); vOffset += 2;
var glyphCount = format0.length - 6;
var glyphIdArray = new Array(glyphCount);
for (var i = 0; i < glyphCount; i++) {
glyphIdArray[i] = view.getUint8(vOffset + i);
}
format0.glyphIdArray = glyphIdArray;
}
format0.glyphIdArray = glyphIdArray;
} else if (subTable.format === 2) {
var format2 = subTable;
format2.length = view.getUint16(vOffset, false); vOffset += 2;
@ -113,15 +119,22 @@ function readSubTable(reader, ttf, subTable, cmapOffset) {
}
format4.idRangeOffset = idRangeOffset;
var glyphCount4 = (format4.length - (vOffset - view.byteOffset - startOffset)) / 2;
format4.glyphIdArrayOffset = vOffset - view.byteOffset;
/* 优化101: subset 模式下跳过 glyphIdArray 解析,直接从 view 按需读取 */
var isSubset4 = ttf.readOptions && ttf.readOptions.subset;
if (isSubset4) {
format4.glyphIdArrayOffset = vOffset - view.byteOffset;
format4._cmapView = view;
} else {
var glyphCount4 = (format4.length - (vOffset - view.byteOffset - startOffset)) / 2;
format4.glyphIdArrayOffset = vOffset - view.byteOffset;
var glyphIdArray4 = new Array(glyphCount4);
for (var g = 0; g < glyphCount4; g++) {
glyphIdArray4[g] = view.getUint16(vOffset, false);
vOffset += 2;
var glyphIdArray4 = new Array(glyphCount4);
for (var g = 0; g < glyphCount4; g++) {
glyphIdArray4[g] = view.getUint16(vOffset, false);
vOffset += 2;
}
format4.glyphIdArray = glyphIdArray4;
}
format4.glyphIdArray = glyphIdArray4;
} else if (subTable.format === 6) {
var format6 = subTable;
format6.length = view.getUint16(vOffset, false); vOffset += 2;
@ -144,18 +157,24 @@ function readSubTable(reader, ttf, subTable, cmapOffset) {
format12.language = view.getUint32(vOffset, false); vOffset += 4;
format12.nGroups = view.getUint32(vOffset, false); vOffset += 4;
var nGroups = format12.nGroups;
var groups = new Array(nGroups);
for (var h = 0; h < nGroups; h++) {
groups[h] = {
start: view.getUint32(vOffset, false),
end: view.getUint32(vOffset + 4, false),
startId: view.getUint32(vOffset + 8, false)
};
/* 优化88: 扁平数组存储 groups减少对象创建 [start, end, startId, ...] */
var groups = new Array(nGroups * 3);
for (var h = 0, gi = 0; h < nGroups; h++, gi += 3) {
groups[gi] = view.getUint32(vOffset, false);
groups[gi + 1] = view.getUint32(vOffset + 4, false);
groups[gi + 2] = view.getUint32(vOffset + 8, false);
vOffset += 12;
}
format12.groups = groups;
format12._flatGroups = true;
}
else if (subTable.format === 14) {
/* 优化93: subset 模式下跳过 format14 完整解析 */
var isSubset2 = ttf.readOptions && ttf.readOptions.subset;
if (isSubset2) {
subTable.format = 14;
subTable.groups = [];
} else {
var format14 = subTable;
format14.length = view.getUint32(vOffset, false); vOffset += 4;
var numVarSelectorRecords = view.getUint32(vOffset, false); vOffset += 4;
@ -196,6 +215,7 @@ function readSubTable(reader, ttf, subTable, cmapOffset) {
}
}
format14.groups = _groups;
}
} else {
console.warn('not support cmap format:' + subTable.format);
}

View File

@ -9,6 +9,9 @@ exports.default = write;
* @author mengke01(kekee000@gmail.com)
*/
/**
* 优化108: 接受并行数组 unicodeArr/idArr
*/
function writeSubTable0(writer, unicodes) {
var pos = writer.offset;
var view = writer.view;
@ -99,21 +102,29 @@ function write(writer, ttf) {
var numRecords = 2 + (hasFormat0 ? 1 : 0) + (hasGLyphsOver2Bytes ? 1 : 0);
view.setUint16(pos, 0, false); pos += 2;
view.setUint16(pos, numRecords, false); pos += 2;
writer.offset = pos;
/* 优化88: encoding records 直接 view 写入 */
var headerSize = 4 + numRecords * 8;
var format4Size = ttf.support.cmap.format4Size;
var format0Size = ttf.support.cmap.format0Size;
/** platform 0 (Unicode) 和 platform 3 (Windows) 共享同一个 format 4 subtable */
writer.writeUint16(0); writer.writeUint16(3); writer.writeUint32(headerSize);
view.setUint16(pos, 0, false); pos += 2;
view.setUint16(pos, 3, false); pos += 2;
view.setUint32(pos, headerSize, false); pos += 4;
if (hasFormat0) {
writer.writeUint16(1); writer.writeUint16(0); writer.writeUint32(headerSize + format4Size);
view.setUint16(pos, 1, false); pos += 2;
view.setUint16(pos, 0, false); pos += 2;
view.setUint32(pos, headerSize + format4Size, false); pos += 4;
}
writer.writeUint16(3); writer.writeUint16(1); writer.writeUint32(headerSize);
view.setUint16(pos, 3, false); pos += 2;
view.setUint16(pos, 1, false); pos += 2;
view.setUint32(pos, headerSize, false); pos += 4;
if (hasGLyphsOver2Bytes) {
writer.writeUint16(3); writer.writeUint16(10); writer.writeUint32(headerSize + format4Size + format0Size);
view.setUint16(pos, 3, false); pos += 2;
view.setUint16(pos, 10, false); pos += 2;
view.setUint32(pos, headerSize + format4Size + format0Size, false); pos += 4;
}
writer.offset = pos;
writeSubTable4(writer, ttf.support.cmap.format4Segments);
if (hasFormat0) {

View File

@ -36,14 +36,26 @@ var _default = exports.default = _table.default.create('directory', [], {
reader.offset = offset + numTables * 16;
return tables;
},
/**
* 优化111: 直接 DataView 批量写入避免 writer 方法调用开销
*/
write: function write(writer, ttf) {
var tables = ttf.support.tables;
var view = writer.view;
var pos = writer.offset;
for (var i = 0, l = tables.length; i < l; i++) {
writer.writeString((tables[i].name + ' ').slice(0, 4));
writer.writeUint32(tables[i].checkSum);
writer.writeUint32(tables[i].offset);
writer.writeUint32(tables[i].length);
var t = tables[i];
var name = t.name;
/* 4 字节 tag 直接写入 */
view.setUint8(pos, name.charCodeAt(0)); pos++;
view.setUint8(pos, name.charCodeAt(1)); pos++;
view.setUint8(pos, name.charCodeAt(2)); pos++;
view.setUint8(pos, name.charCodeAt(3)); pos++;
view.setUint32(pos, t.checkSum, false); pos += 4;
view.setUint32(pos, t.offset, false); pos += 4;
view.setUint32(pos, t.length, false); pos += 4;
}
writer.offset = pos;
return writer;
},
size: function size(ttf) {

View File

@ -27,18 +27,19 @@ var _default = exports.default = _table.default.create('glyf', [], {
/* subset */
var subset = ttf.readOptions.subset;
if (subset && subset.length > 0) {
/* 优化1: subset 转为 SetO(1) 查找 */
var subsetSet = new Set(subset);
/* 优化95+118: 直接遍历 subset 数组查找 glyphId同时构建密集 ID 数组 */
var subsetMap = { 0: true };
var subsetGids = [0];
var cmap = ttf.cmap;
/* 优化23: for...in 替代 Object.keys + forEach */
for (var c in cmap) {
if (subsetSet.has(+c)) {
subsetMap[cmap[c]] = true;
for (var si = 0, sl = subset.length; si < sl; si++) {
var gid = cmap[subset[si]];
if (gid !== undefined && !subsetMap[gid]) {
subsetMap[gid] = true;
subsetGids.push(gid);
}
}
ttf.subsetMap = subsetMap;
ttf.subsetGids = subsetGids;
var parsedGlyfMap = {};
/* 优化43: for...in + for 循环替代 Object.keys + forEach */

View File

@ -16,18 +16,18 @@ var MAX_INSTRUCTION_LENGTH = 5000;
var MAX_NUMBER_OF_COORDINATES = 20000;
/**
* 优化9+12+34+41+42: parseSimpleGlyf 消除中间数组直接 view 批量读取
* 优化92+94: parseSimpleGlyf 使用 TypedArray延迟 contour 构建到 optimize 阶段
*/
function parseSimpleGlyf(reader, glyf) {
var offset = reader.offset;
var numberOfCoordinates = glyf.endPtsOfContours[glyf.endPtsOfContours.length - 1] + 1;
var endPtsOfContours = glyf.endPtsOfContours;
var numberOfCoordinates = endPtsOfContours[endPtsOfContours.length - 1] + 1;
if (numberOfCoordinates > MAX_NUMBER_OF_COORDINATES) {
console.warn('error read glyf coordinates:' + offset);
return glyf;
}
/* 优化34: 缓存 glyFlag 常量 */
var REPEAT = _glyFlag.default.REPEAT;
var XSHORT = _glyFlag.default.XSHORT;
var XSAME = _glyFlag.default.XSAME;
@ -35,24 +35,22 @@ function parseSimpleGlyf(reader, glyf) {
var YSAME = _glyFlag.default.YSAME;
var ONCURVE = _glyFlag.default.ONCURVE;
/* 优化34+42: 直接 view 读取 flags */
var view = reader.view;
var vOffset = view.byteOffset + reader.offset;
var flags = new Array(numberOfCoordinates);
var i = 0;
while (i < numberOfCoordinates) {
var flags = new Uint8Array(numberOfCoordinates);
var fi = 0;
while (fi < numberOfCoordinates) {
var flag = view.getUint8(vOffset++);
flags[i++] = flag;
if (flag & REPEAT && i < numberOfCoordinates) {
flags[fi++] = flag;
if (flag & REPEAT && fi < numberOfCoordinates) {
var repeat = view.getUint8(vOffset++);
for (var j = 0; j < repeat && i < numberOfCoordinates; j++) {
flags[i++] = flag;
for (var j = 0; j < repeat && fi < numberOfCoordinates; j++) {
flags[fi++] = flag;
}
}
}
/* 优化9+34: 直接构建扁平坐标数组,消除中间对象创建 */
var xArr = new Array(numberOfCoordinates);
var xArr = new Int32Array(numberOfCoordinates);
var prevX = 0;
for (var xi = 0; xi < numberOfCoordinates; xi++) {
var x = 0;
@ -70,7 +68,7 @@ function parseSimpleGlyf(reader, glyf) {
xArr[xi] = prevX;
}
var yArr = new Array(numberOfCoordinates);
var yArr = new Int32Array(numberOfCoordinates);
var prevY = 0;
for (var yi = 0; yi < numberOfCoordinates; yi++) {
var y = 0;
@ -90,27 +88,10 @@ function parseSimpleGlyf(reader, glyf) {
reader.offset = vOffset - view.byteOffset;
/* 优化66: 扁平 contours [x, y, onCurve, x, y, onCurve, ...],消除大量小对象 */
if (numberOfCoordinates > 0) {
var endPtsOfContours = glyf.endPtsOfContours;
var contours = new Array(endPtsOfContours.length);
var start = 0;
for (var ci = 0, cl = endPtsOfContours.length; ci < cl; ci++) {
var end = endPtsOfContours[ci] + 1;
var numPoints = end - start;
var contour = new Array(numPoints * 3);
var ki = 0;
for (var pi = start; pi < end; pi++) {
contour[ki++] = xArr[pi];
contour[ki++] = yArr[pi];
contour[ki++] = !!(flags[pi] & ONCURVE);
}
contours[ci] = contour;
start = end;
}
glyf.contours = contours;
glyf._flatContours = true;
}
/* 优化94: 保存 TypedArray 坐标数据,延迟 contour 构建到 optimize */
glyf._xArr = xArr;
glyf._yArr = yArr;
glyf._flags = flags;
return glyf;
}
@ -256,7 +237,7 @@ function parseGlyf(reader, ttf, offset) {
reader.offset = vOffset - view.byteOffset;
parseSimpleGlyf(reader, glyf);
delete glyf.endPtsOfContours;
/* 优化94: 保留 endPtsOfContours 供 optimize 使用,不再删除 */
} else {
reader.offset = vOffset - view.byteOffset;
parseCompoundGlyf(reader, glyf);

View File

@ -19,7 +19,25 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) {
return 0;
}
/* 优化33: 缓存 glyFlag 常量到局部变量 */
/* 优化84+98+103: 直接复用 optimize 阶段预计算的 flags/encodedCoordSize + 预编码 buffer */
if (glyf._precomputedGlyfSupport) {
var pre = glyf._precomputedGlyfSupport;
glyfSupport.flags = pre.flags;
/* 优化98: 预编码 buffer 直接传递 */
if (pre.xEncoded) {
glyfSupport.xEncoded = pre.xEncoded;
glyfSupport.yEncoded = pre.yEncoded;
} else {
glyfSupport.xCoord = pre.xCoord;
glyfSupport.yCoord = pre.yCoord;
}
delete glyf._precomputedGlyfSupport;
var instructionSize = (hinting && glyf.instructions) ? glyf.instructions.length : 0;
/* 优化103: 优先使用 _numContours */
var nc = glyf._numContours != null ? glyf._numContours : glyf.contours.length;
return 12 + nc * 2 + pre.flags.length + pre.encodedCoordSize + instructionSize;
}
var ONCURVE = _glyFlag.default.ONCURVE;
var XSHORT = _glyFlag.default.XSHORT;
var YSHORT = _glyFlag.default.YSHORT;
@ -36,16 +54,13 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) {
var prevFlag = -1;
var repeatPoint = -1;
/* 优化66: 检测扁平格式 */
var isFlat = glyf._flatContours;
/* 单次遍历: delta坐标计算 + flag压缩 + 坐标编码 + 大小累加 */
var encodedCoordSize = 0;
for (var j = 0, cl = contours.length; j < cl; j++) {
var contour = contours[j];
if (isFlat) {
/* 优化66: 扁平格式每3个元素为一个点 [x, y, onCurve, ...] */
for (var i = 0, l = contour.length; i < l; i += 3) {
var px = contour[i];
var py = contour[i + 1];
@ -69,7 +84,7 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) {
} else if (-0xFF <= dx && dx <= 0xFF) {
flag += XSHORT;
if (dx > 0) flag += XSAME;
xCoordC.push(Math.abs(dx));
xCoordC.push(dx > 0 ? dx : -dx);
encodedCoordSize += 1;
} else {
xCoordC.push(dx);
@ -81,7 +96,7 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) {
} else if (-0xFF <= dy && dy <= 0xFF) {
flag += YSHORT;
if (dy > 0) flag += YSAME;
yCoordC.push(Math.abs(dy));
yCoordC.push(dy > 0 ? dy : -dy);
encodedCoordSize += 1;
} else {
yCoordC.push(dy);
@ -126,7 +141,7 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) {
} else if (-0xFF <= dx && dx <= 0xFF) {
flag += XSHORT;
if (dx > 0) flag += XSAME;
xCoordC.push(Math.abs(dx));
xCoordC.push(dx > 0 ? dx : -dx);
encodedCoordSize += 1;
} else {
xCoordC.push(dx);
@ -138,7 +153,7 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) {
} else if (-0xFF <= dy && dy <= 0xFF) {
flag += YSHORT;
if (dy > 0) flag += YSAME;
yCoordC.push(Math.abs(dy));
yCoordC.push(dy > 0 ? dy : -dy);
encodedCoordSize += 1;
} else {
yCoordC.push(dy);
@ -167,7 +182,6 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) {
glyfSupport.yCoord = yCoordC;
var instructionSize = (hinting && glyf.instructions) ? glyf.instructions.length : 0;
/* 12 bytes header + endPtsOfContours + flags + encoded coords + instructions */
return 12 + contours.length * 2 + flagsC.length + encodedCoordSize + instructionSize;
}

View File

@ -34,15 +34,18 @@ function write(writer, ttf) {
for (var index = 0, gl = glyfs.length; index < gl; index++) {
var glyf = glyfs[index];
/* 优化117: 缓存 glyfSupport 引用到循环顶部 */
var gSupport = glyfSupport[index];
/* 优化51: return → continue */
if (!glyf.compound && !writeZeroContoursGlyfData && (!glyf.contours || !glyf.contours.length)) {
continue;
}
/* 优化31: header 直接 view 写入 10 字节 */
/* 优化31+103: header 直接 view 写入 10 字节,优先使用 _numContours */
var pos = writer.offset;
view.setInt16(pos, glyf.compound ? -1 : (glyf.contours || []).length, false);
var numC = glyf._numContours != null ? glyf._numContours : (glyf.contours || []).length;
view.setInt16(pos, glyf.compound ? -1 : numC, false);
view.setInt16(pos + 2, glyf.xMin, false);
view.setInt16(pos + 4, glyf.yMin, false);
view.setInt16(pos + 6, glyf.xMax, false);
@ -96,24 +99,33 @@ function write(writer, ttf) {
}
}
} else {
/* 优化32+66: endPtsOfContours 直接 view 写入,支持扁平格式 */
/* 优化32+66+103: endPtsOfContours 直接 view 写入,支持 _pointsPerContour */
var contours = glyf.contours || [];
var endPts = -1;
/* 优化66: 扁平格式 contour.length 是点的3倍 */
var isFlat = glyf._flatContours;
for (var ci = 0, cl = contours.length; ci < cl; ci++) {
endPts += isFlat ? contours[ci].length / 3 : contours[ci].length;
view.setUint16(pos, endPts, false);
pos += 2;
var ppc = glyf._pointsPerContour;
if (ppc) {
for (var ci = 0, cl = ppc.length; ci < cl; ci++) {
endPts += ppc[ci];
view.setUint16(pos, endPts, false);
pos += 2;
}
} else {
var isFlat = glyf._flatContours;
for (var ci2 = 0, cl2 = contours.length; ci2 < cl2; ci2++) {
endPts += isFlat ? contours[ci2].length / 3 : contours[ci2].length;
view.setUint16(pos, endPts, false);
pos += 2;
}
}
/* 优化25: instructions 批量写入 */
/* 优化25+80: instructions 使用 Uint8Array.set 批量写入 */
if (hinting && glyf.instructions) {
var instructions = glyf.instructions;
view.setUint16(pos, instructions.length, false);
pos += 2;
for (var ii = 0, il = instructions.length; ii < il; ii++) {
view.setUint8(pos + ii, instructions[ii]);
if (instructions.length > 0) {
var instrArr = instructions instanceof Uint8Array ? instructions : new Uint8Array(instructions);
new Uint8Array(view.buffer, view.byteOffset + pos, instrArr.length).set(instrArr);
}
pos += instructions.length;
} else {
@ -121,47 +133,57 @@ function write(writer, ttf) {
pos += 2;
}
/* 优化11: flags 批量写入 */
var flags = glyfSupport[index].flags || [];
for (var fi = 0, fl = flags.length; fi < fl; fi++) {
view.setUint8(pos + fi, flags[fi]);
/* 优化11+79: flags 使用 Uint8Array.set 批量写入 */
var flags = gSupport.flags || [];
if (flags.length > 0) {
var flagsArr = flags instanceof Uint8Array ? flags : new Uint8Array(flags);
new Uint8Array(view.buffer, view.byteOffset + pos, flagsArr.length).set(flagsArr);
}
pos += fl;
pos += flags.length;
/* 优化21: xCoord 直接 view 写入 */
var xCoord = glyfSupport[index].xCoord || [];
for (var xi = 0, xl = xCoord.length; xi < xl; xi++) {
var xv = xCoord[xi];
if (0 <= xv && xv <= 0xFF) {
view.setUint8(pos, xv);
pos += 1;
} else {
view.setInt16(pos, xv, false);
pos += 2;
/* 优化21+98+119: xCoord 预编码 Uint8Array 直接 set或逐个写入 */
var support = gSupport;
if (support.xEncoded) {
new Uint8Array(view.buffer, view.byteOffset + pos, support.xEncoded.length).set(support.xEncoded);
pos += support.xEncoded.length;
} else {
var xCoord = support.xCoord || [];
for (var xi = 0, xl = xCoord.length; xi < xl; xi++) {
var xv = xCoord[xi];
if (0 <= xv && xv <= 0xFF) {
view.setUint8(pos, xv);
pos += 1;
} else {
view.setInt16(pos, xv, false);
pos += 2;
}
}
}
/* 优化21+58: yCoord 直接 view 写入,使用各自的数组长度 */
var yCoord = glyfSupport[index].yCoord || [];
for (var yi = 0, yl = yCoord.length; yi < yl; yi++) {
var yv = yCoord[yi];
if (0 <= yv && yv <= 0xFF) {
view.setUint8(pos, yv);
pos += 1;
} else {
view.setInt16(pos, yv, false);
pos += 2;
/* 优化21+58+98+119: yCoord 预编码 Uint8Array 直接 set或逐个写入 */
if (support.yEncoded) {
new Uint8Array(view.buffer, view.byteOffset + pos, support.yEncoded.length).set(support.yEncoded);
pos += support.yEncoded.length;
} else {
var yCoord = support.yCoord || [];
for (var yi = 0, yl = yCoord.length; yi < yl; yi++) {
var yv = yCoord[yi];
if (0 <= yv && yv <= 0xFF) {
view.setUint8(pos, yv);
pos += 1;
} else {
view.setInt16(pos, yv, false);
pos += 2;
}
}
}
}
/* 4字节对齐 */
var glyfSize = glyfSupport[index].glyfSize;
/* 优化81: 4字节对齐使用 fill(0) 批量填充 */
var glyfSize = gSupport.glyfSize;
if (glyfSize % 4) {
var pad = 4 - glyfSize % 4;
for (var p = 0; p < pad; p++) {
view.setUint8(pos + p, 0);
}
new Uint8Array(view.buffer, view.byteOffset + pos, pad).fill(0);
pos += pad;
}

View File

@ -18,24 +18,22 @@ var _default = exports.default = _table.default.create('hmtx', [], {
reader.seek(offset);
var numOfLongHorMetrics = ttf.hhea.numOfLongHorMetrics;
var numGlyphs = ttf.maxp.numGlyphs;
var hMetrics = new Array(numGlyphs);
/* 优化10: 直接 view 批量读取 */
/* 优化10+82: 扁平数组 [advW, lsb, advW, lsb, ...],消除对象分配 */
var hMetrics = new Int32Array(numGlyphs * 2);
var view = reader.view;
var vOffset = view.byteOffset + offset;
for (var i = 0; i < numOfLongHorMetrics; i++) {
hMetrics[i] = {
advanceWidth: view.getUint16(vOffset, false),
leftSideBearing: view.getInt16(vOffset + 2, false)
};
var idx = i * 2;
hMetrics[idx] = view.getUint16(vOffset, false);
hMetrics[idx + 1] = view.getInt16(vOffset + 2, false);
vOffset += 4;
}
var advanceWidth = hMetrics[numOfLongHorMetrics - 1].advanceWidth;
var lastAdvW = hMetrics[(numOfLongHorMetrics - 1) * 2];
var numOfLast = numGlyphs - numOfLongHorMetrics;
for (var j = 0; j < numOfLast; j++) {
hMetrics[numOfLongHorMetrics + j] = {
advanceWidth: advanceWidth,
leftSideBearing: view.getInt16(vOffset, false)
};
var idx2 = (numOfLongHorMetrics + j) * 2;
hMetrics[idx2] = lastAdvW;
hMetrics[idx2 + 1] = view.getInt16(vOffset, false);
vOffset += 2;
}
reader.offset = offset + numOfLongHorMetrics * 4 + numOfLast * 2;
@ -44,17 +42,18 @@ var _default = exports.default = _table.default.create('hmtx', [], {
write: function write(writer, ttf) {
var i;
var numOfLongHorMetrics = ttf.hhea.numOfLongHorMetrics;
/* 优化30: 直接 view 批量写入 */
/* 优化30+82: 直接 view 批量写入 */
var wView = writer.view;
var pos = writer.offset;
var glyfs = ttf.glyf;
for (i = 0; i < numOfLongHorMetrics; i++) {
wView.setUint16(pos, ttf.glyf[i].advanceWidth, false);
wView.setInt16(pos + 2, ttf.glyf[i].leftSideBearing, false);
wView.setUint16(pos, glyfs[i].advanceWidth, false);
wView.setInt16(pos + 2, glyfs[i].leftSideBearing, false);
pos += 4;
}
var numOfLast = ttf.glyf.length - numOfLongHorMetrics;
var numOfLast = glyfs.length - numOfLongHorMetrics;
for (i = 0; i < numOfLast; i++) {
wView.setInt16(pos, ttf.glyf[numOfLongHorMetrics + i].leftSideBearing, false);
wView.setInt16(pos, glyfs[numOfLongHorMetrics + i].leftSideBearing, false);
pos += 2;
}
writer.offset = pos;

View File

@ -39,7 +39,6 @@ var _default = exports.default = _table.default.create('loca', [], {
var glyfSupport = ttf.support.glyf;
var offset = ttf.support.glyf.offset || 0;
var indexToLocFormat = ttf.head.indexToLocFormat;
var sizeRatio = indexToLocFormat === 0 ? 0.5 : 1;
var numGlyphs = ttf.glyf.length;
/* 优化29: 直接 view 批量写入 */
var wView = writer.view;
@ -49,15 +48,16 @@ var _default = exports.default = _table.default.create('loca', [], {
wView.setUint32(pos, offset, false);
pos += 4;
if (i < numGlyphs) {
offset += glyfSupport[i].size * sizeRatio;
offset += glyfSupport[i].size;
}
}
} else {
/* 优化110: 短格式使用右移替代浮点乘 0.5 */
for (var j = 0; j <= numGlyphs; j++) {
wView.setUint16(pos, offset, false);
wView.setUint16(pos, offset >> 1, false);
pos += 2;
if (j < numGlyphs) {
offset += glyfSupport[j].size * sizeRatio;
offset += glyfSupport[j].size;
}
}
}

View File

@ -41,28 +41,26 @@ var _default = exports.default = _table.default.create('post', [], {
/* 优化60: 直接 view 批量读取 glyphNameIndex */
var view = reader.view;
var vOffset = view.byteOffset + reader.offset;
var glyphNameIndex = new Array(numberOfGlyphs);
for (var i = 0; i < numberOfGlyphs; i++) {
glyphNameIndex[i] = view.getUint16(vOffset, false);
vOffset += 2;
}
var pascalStringOffset = vOffset - view.byteOffset;
var pascalStringOffset = reader.offset + numberOfGlyphs * 2;
var pascalStringLength = ttf.tables.post.length - (pascalStringOffset - this.offset);
var pascalStringBytes = reader.readBytes(pascalStringOffset, pascalStringLength);
tbl.nameIndex = glyphNameIndex;
/* 优化64: subset 模式下保存原始字节,按需解析 */
/* 优化87: subset 模式下只读取子集字形的 nameIndex跳过其余 */
if (ttf.readOptions && ttf.readOptions.subset) {
tbl._pascalStringBytes = pascalStringBytes;
tbl._pascalStringOffsets = [];
var off = 0;
for (var j = 0; j < numberOfGlyphs; j++) {
tbl._pascalStringOffsets[j] = off;
off += 1 + (pascalStringBytes[off] || 0);
}
tbl._pascalStringOffsets = null;
tbl.nameIndex = null;
tbl.names = null;
/* 保存 view 引用和偏移量,供后续按需读取 nameIndex */
tbl._nameIndexViewOffset = vOffset;
tbl._nameIndexView = view;
} else {
var glyphNameIndex = new Array(numberOfGlyphs);
for (var i = 0; i < numberOfGlyphs; i++) {
glyphNameIndex[i] = view.getUint16(vOffset, false);
vOffset += 2;
}
tbl.nameIndex = glyphNameIndex;
tbl.names = _string.default.getPascalString(pascalStringBytes);
}
}
@ -76,27 +74,34 @@ var _default = exports.default = _table.default.create('post', [], {
format: 3
};
writer.writeFixed(post.format);
writer.writeFixed(post.italicAngle || 0);
writer.writeInt16(post.underlinePosition || 0);
writer.writeInt16(post.underlineThickness || 0);
writer.writeUint32(post.isFixedPitch || 0);
writer.writeUint32(post.minMemType42 || 0);
writer.writeUint32(post.maxMemType42 || 0);
writer.writeUint32(post.minMemType1 || 0);
writer.writeUint32(post.maxMemType1 || 0);
/* 优化77: post header 直接 view 写入 32 字节 */
var view = writer.view;
var pos = writer.offset;
view.setInt32(pos, Math.round(post.format * 65536), false); pos += 4;
view.setInt32(pos, Math.round((post.italicAngle || 0) * 65536), false); pos += 4;
view.setInt16(pos, post.underlinePosition || 0, false); pos += 2;
view.setInt16(pos, post.underlineThickness || 0, false); pos += 2;
view.setUint32(pos, post.isFixedPitch || 0, false); pos += 4;
view.setUint32(pos, post.minMemType42 || 0, false); pos += 4;
view.setUint32(pos, post.maxMemType42 || 0, false); pos += 4;
view.setUint32(pos, post.minMemType1 || 0, false); pos += 4;
view.setUint32(pos, post.maxMemType1 || 0, false); pos += 4;
if (post.format === 2) {
var numberOfGlyphs = ttf.glyf.length;
writer.writeUint16(numberOfGlyphs);
view.setUint16(pos, numberOfGlyphs, false); pos += 2;
/* 优化77: nameIndex 直接 view 批量写入 */
var nameIndex = ttf.support.post.nameIndex;
for (var i = 0, l = nameIndex.length; i < l; i++) {
writer.writeUint16(nameIndex[i]);
view.setUint16(pos, nameIndex[i], false); pos += 2;
}
writer.offset = pos;
var names = ttf.support.post.names;
for (var j = 0, jl = names.length; j < jl; j++) {
writer.writeBytes(names[j]);
}
} else {
writer.offset = pos;
}
},
size: function size(ttf) {
@ -105,7 +110,9 @@ var _default = exports.default = _table.default.create('post', [], {
ttf.post.format = ttf.post.format || 3;
ttf.post.maxMemType1 = numberOfGlyphs;
/* 优化109: format 3/1 不需要 nameIndex/names 计算 */
if (ttf.post.format === 3 || ttf.post.format === 1) {
ttf.support.post = {};
return 32;
}

View File

@ -652,7 +652,7 @@ var TTF = exports.default = /*#__PURE__*/function () {
value: function sortGlyf() {
var glyf = this.ttf.glyf;
if (glyf.length > 1) {
/* 优化54: some → for 循环 + unicode 属性缓存 + Math.min.apply → 手动遍历 */
/* 优化54: some → for 循环 */
var hasCompound = false;
for (var k = 0, kl = glyf.length; k < kl; k++) {
if (glyf[k].compound) {
@ -664,21 +664,13 @@ var TTF = exports.default = /*#__PURE__*/function () {
return -2;
}
var notdef = glyf.shift();
/* 优化113: unicode 已在 optimizettf 中排序,最小值始终是 unicode[0] */
glyf.sort(function (a, b) {
var aU = a.unicode;
var bU = b.unicode;
if ((!aU || !aU.length) && (!bU || !bU.length)) {
return 0;
} else if ((!aU || !aU.length) && bU) {
return 1;
} else if (aU && (!bU || !bU.length)) {
return -1;
}
/* 优化3: 手动遍历取最小值 */
var aMin = aU[0], bMin = bU[0];
for (var ai = 1; ai < aU.length; ai++) { if (aU[ai] < aMin) aMin = aU[ai]; }
for (var bi = 1; bi < bU.length; bi++) { if (bU[bi] < bMin) bMin = bU[bi]; }
return aMin - bMin;
if (!aU || !aU.length) return bU && bU.length ? 1 : 0;
if (!bU || !bU.length) return -1;
return aU[0] - bU[0];
});
glyf.unshift(notdef);
return glyf;

View File

@ -94,6 +94,7 @@ var TTFReader = exports.default = /*#__PURE__*/function () {
var codes = ttf.cmap;
var glyf = ttf.glyf;
var subsetMap = ttf.readOptions.subset ? ttf.subsetMap : null;
var subsetGids = ttf.readOptions.subset ? ttf.subsetGids : null;
/* 优化13+24+62: unicode 遍历subset 模式只遍历 subsetMap */
for (var c in codes) {
@ -107,18 +108,20 @@ var TTFReader = exports.default = /*#__PURE__*/function () {
glyf[i].unicode.push(+c);
}
/* 优化13: advanceWidth 遍历优化 */
/* 优化13+82+118: advanceWidth 遍历优化,使用密集数组 */
var hmtx = ttf.hmtx;
if (subsetMap) {
for (var idx in subsetMap) {
var idxNum = +idx;
glyf[idxNum].advanceWidth = hmtx[idxNum].advanceWidth;
glyf[idxNum].leftSideBearing = hmtx[idxNum].leftSideBearing;
if (subsetGids) {
for (var gi = 0, gl = subsetGids.length; gi < gl; gi++) {
var idxNum = subsetGids[gi];
var hIdx = idxNum * 2;
glyf[idxNum].advanceWidth = hmtx[hIdx];
glyf[idxNum].leftSideBearing = hmtx[hIdx + 1];
}
} else {
for (var hi = 0, hl = hmtx.length; hi < hl; hi++) {
glyf[hi].advanceWidth = hmtx[hi].advanceWidth;
glyf[hi].leftSideBearing = hmtx[hi].leftSideBearing;
for (var hi = 0, hl = hmtx.length / 2; hi < hl; hi++) {
var hIdx2 = hi * 2;
glyf[hi].advanceWidth = hmtx[hIdx2];
glyf[hi].leftSideBearing = hmtx[hIdx2 + 1];
}
}
@ -128,27 +131,52 @@ var TTFReader = exports.default = /*#__PURE__*/function () {
var names = ttf.post.names;
var pascalBytes = ttf.post._pascalStringBytes;
var pascalOffsets = ttf.post._pascalStringOffsets;
for (var ni = 0, nl = nameIndex.length; ni < nl; ni++) {
if (subsetMap && !subsetMap[ni]) {
continue;
/* 优化87: subset 模式下按需从 view 读取 nameIndex */
var niView = ttf.post._nameIndexView;
var niViewOffset = ttf.post._nameIndexViewOffset;
if (subsetGids) {
for (var niIdx = 0, nl2 = subsetGids.length; niIdx < nl2; niIdx++) {
var niNum = subsetGids[niIdx];
var nIdx = niView ? niView.getUint16(niViewOffset + niNum * 2, false) : (nameIndex && nameIndex[niNum]);
if (nIdx === undefined || nIdx === null) continue;
if (nIdx <= 257) {
glyf[niNum].name = _postName.default[nIdx];
} else if (names) {
glyf[niNum].name = names[nIdx - 258] || '';
} else if (pascalBytes) {
var off = pascalOffsets ? pascalOffsets[nIdx - 258] : null;
if (off === null) {
/* 按需计算 pascal string 偏移量 */
var pOff = 0;
for (var pk = 0; pk < nIdx - 258; pk++) {
pOff += 1 + (pascalBytes[pOff] || 0);
}
off = pOff;
}
glyf[niNum].name = off !== undefined ? _post.getPascalStringAt(pascalBytes, off) : '';
}
}
var nIdx = nameIndex[ni];
if (nIdx <= 257) {
glyf[ni].name = _postName.default[nIdx];
} else if (names) {
glyf[ni].name = names[nIdx - 258] || '';
} else if (pascalBytes && pascalOffsets) {
var off = pascalOffsets[nIdx - 258];
glyf[ni].name = off !== undefined ? _post.getPascalStringAt(pascalBytes, off) : '';
} else if (nameIndex) {
for (var ni2 = 0, nl = nameIndex.length; ni2 < nl; ni2++) {
var nIdx2 = nameIndex[ni2];
if (nIdx2 <= 257) {
glyf[ni2].name = _postName.default[nIdx2];
} else if (names) {
glyf[ni2].name = names[nIdx2 - 258] || '';
} else if (pascalBytes && pascalOffsets) {
var off2 = pascalOffsets[nIdx2 - 258];
glyf[ni2].name = off2 !== undefined ? _post.getPascalStringAt(pascalBytes, off2) : '';
}
}
}
}
/* 优化13+44+62: subset 模式下直接只遍历 subsetMap */
if (subsetMap) {
/* 优化13+44+62+118: subset 模式下使用密集数组遍历 */
if (subsetGids) {
var subGlyf = [];
for (var si in subsetMap) {
var siNum = +si;
for (var si = 0, sl = subsetGids.length; si < sl; si++) {
var siNum = subsetGids[si];
if (glyf[siNum].compound) {
(0, _compound2simpleglyf.default)(siNum, ttf, true);
}

View File

@ -57,19 +57,21 @@ var TTFWriter = exports.default = /*#__PURE__*/function () {
}
var checkUnicodeRepeat = {};
/* 优化4+46: 数字排序 + for 循环 */
var glyfs = ttf.glyf;
for (var index = 0, gl = glyfs.length; index < gl; index++) {
var glyf = glyfs[index];
if (glyf.unicode) {
glyf.unicode.sort(function (a, b) { return a - b; });
var unicode = glyf.unicode;
for (var ui = 0, ul = unicode.length; ui < ul; ui++) {
var u = unicode[ui];
if (checkUnicodeRepeat[u]) {
_error.default.raise({ number: 10200, data: index }, index);
} else {
checkUnicodeRepeat[u] = true;
/* 优化112: optimizettf 已排序 unicode 并检查重复,跳过冗余工作 */
if (!ttf._unicodeSorted) {
var glyfs = ttf.glyf;
for (var index = 0, gl = glyfs.length; index < gl; index++) {
var glyf = glyfs[index];
if (glyf.unicode) {
glyf.unicode.sort(function (a, b) { return a - b; });
var unicode = glyf.unicode;
for (var ui = 0, ul = unicode.length; ui < ul; ui++) {
var u = unicode[ui];
if (checkUnicodeRepeat[u]) {
_error.default.raise({ number: 10200, data: index }, index);
} else {
checkUnicodeRepeat[u] = true;
}
}
}
}
@ -124,8 +126,9 @@ var TTFWriter = exports.default = /*#__PURE__*/function () {
new _directory.default().write(writer, ttf);
/* 优化56: forEach → for 循环 */
/* 优化56+87: forEach → for 循环,缓存 buffer 引用避免重复 getBuffer() */
var supportTableList = ttf.support.tables;
var buf = writer.getBuffer();
for (var si = 0, sl = supportTableList.length; si < sl; si++) {
var table = supportTableList[si];
var tableStart = writer.offset;
@ -137,23 +140,23 @@ var TTFWriter = exports.default = /*#__PURE__*/function () {
if (table.length % 4) {
writer.writeEmpty(4 - table.length % 4);
}
table.checkSum = (0, _checkSum.default)(writer.getBuffer(), tableStart, table.size);
table.checkSum = (0, _checkSum.default)(buf, tableStart, table.size);
}
/* 重新写入校验和 */
/* 优化111: 重新写入校验和,直接 view 写入 */
var csView = writer.view;
for (var ci = 0, cl = supportTableList.length; ci < cl; ci++) {
var offset2 = 12 + ci * 16 + 4;
writer.writeUint32(supportTableList[ci].checkSum, offset2);
csView.setUint32(offset2, supportTableList[ci].checkSum, false);
}
/* 写入总校验和 */
var ttfCheckSum = (0xB1B0AFBA - (0, _checkSum.default)(writer.getBuffer()) + 0x100000000) % 0x100000000;
writer.writeUint32(ttfCheckSum, ttfHeadOffset + 8);
var ttfCheckSum = (0xB1B0AFBA - (0, _checkSum.default)(buf) + 0x100000000) % 0x100000000;
csView.setUint32(ttfHeadOffset + 8, ttfCheckSum, false);
delete ttf.writeOptions;
delete ttf.support;
var buffer = writer.getBuffer();
writer.dispose();
return buffer;
return buf;
}
}, {
key: "prepareDump",

View File

@ -10,7 +10,8 @@ exports.default = checkSum;
*/
/**
* 优化18+69: 位运算避免溢出 + Uint8Array 替代 DataView
* 优化107: 使用 Uint32Array 视图 + DataView 字节序转换处理大端序
* 避免每次调用创建新的 DataView减少内存分配
*/
function checkSumArrayBuffer(buffer, offset, length) {
if (offset === undefined) offset = 0;
@ -18,28 +19,44 @@ function checkSumArrayBuffer(buffer, offset, length) {
if (offset + length > buffer.byteLength) {
throw new Error('check sum out of bound');
}
var bytes = new Uint8Array(buffer, offset, length);
/* 优化107: 复用共享 DataView 进行字节序转换 */
var view = DataViewPool.acquire(buffer);
var nLongs = length >> 2;
var sum = 0;
var i = 0;
while (i < nLongs) {
var j = i << 2;
sum = (sum + (bytes[j] << 24 | bytes[j + 1] << 16 | bytes[j + 2] << 8 | bytes[j + 3])) | 0;
i++;
for (var i = 0; i < nLongs; i++) {
sum = (sum + view.getUint32(offset + (i << 2), false)) | 0;
}
DataViewPool.release(view);
var leftBytes = length - nLongs * 4;
if (leftBytes) {
var off = nLongs << 2;
var shift = leftBytes * 8;
var bytes = new Uint8Array(buffer, offset + nLongs * 4, leftBytes);
var val = 0;
for (var k = 0; k < leftBytes; k++) {
val = (val | bytes[off + k] << (leftBytes - 1 - k) * 8) >>> 0;
val = (val | bytes[k] << (leftBytes - 1 - k) * 8) >>> 0;
}
sum = (sum + val) | 0;
}
return sum >>> 0;
}
/**
* 优化107: DataView 对象池避免重复创建
*/
var DataViewPool = {
_view: null,
_buffer: null,
acquire: function (buffer) {
if (this._buffer !== buffer) {
this._view = new DataView(buffer);
this._buffer = buffer;
}
return this._view;
},
release: function () {
/* 保留引用供下次复用 */
}
};
function checkSumArray(buffer, offset, length) {
if (offset === undefined) offset = 0;
length = length || buffer.length;

View File

@ -4,9 +4,10 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = optimizettf;
exports.ceilReduceAndSizeFlat = ceilReduceAndSizeFlat;
var _reduceGlyf = _interopRequireDefault(require("./reduceGlyf"));
var _pathCeil = _interopRequireDefault(require("../../graphics/pathCeil"));
var _reducePathFlat = _interopRequireDefault(require("../../graphics/reducePathFlat"));
var _glyFlag = _interopRequireDefault(require("../enum/glyFlag"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* @file 对ttf对象进行优化查找错误去除冗余点
@ -14,48 +15,280 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
*/
/**
* 优化17+66: 扁平格式单次遍历 pathCeil + reducePath
* 先四舍五入坐标再去除冗余点一次循环完成
* 优化103+116: parse 阶段的 TypedArray 直接计算 precomputed 数据跳过 contour 数组构建
* 使用共享 buffer 池避免每字形分配 xCoordBuf/yCoordBuf
*/
function ceilAndReduceFlat(glyf) {
var contours = glyf.contours;
for (var j = contours.length - 1; j >= 0; j--) {
var contour = contours[j];
/* 先原地四舍五入 */
for (var i = 0, l = contour.length; i < l; i += 3) {
contour[i] = Math.round(contour[i]);
contour[i + 1] = Math.round(contour[i + 1]);
}
/* 再去除冗余点 */
contour = (0, _reducePathFlat.default)(contour);
/* 空轮廓:扁平格式 <= 6 元素2个点 */
if (contour.length <= 6) {
contours.splice(j, 1);
function ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf) {
var xArr = glyf._xArr;
var yArr = glyf._yArr;
var flagsArr = glyf._flags;
var endPts = glyf.endPtsOfContours;
var numContours = endPts.length;
var ONCURVE = _glyFlag.default.ONCURVE;
var XSHORT = _glyFlag.default.XSHORT;
var YSHORT = _glyFlag.default.YSHORT;
var XSAME = _glyFlag.default.XSAME;
var YSAME = _glyFlag.default.YSAME;
var REPEAT = _glyFlag.default.REPEAT;
var numPoints = xArr.length;
var flagsC = new Array(numPoints);
var fi = 0;
var prevX = 0, prevY = 0;
var isFirst = true;
var prevFlag = -1;
var repeatPoint = -1;
var encodedCoordSize = 0;
/* 优化116: 复用共享 buffer仅在 buffer 不够大时才分配新的 */
var neededSize = numPoints * 2;
var xCoordBuf = sharedXBuf.length >= neededSize ? sharedXBuf : new Uint8Array(neededSize);
var yCoordBuf = sharedYBuf.length >= neededSize ? sharedYBuf : new Uint8Array(neededSize);
var xbi = 0, ybi = 0;
for (var pi = 0; pi < numPoints; pi++) {
var px = xArr[pi];
var py = yArr[pi];
var onCurve = !!(flagsArr[pi] & ONCURVE);
var dx, dy;
var flag = onCurve ? ONCURVE : 0;
if (isFirst) {
dx = px; dy = py; isFirst = false;
} else {
contours[j] = contour;
dx = px - prevX; dy = py - prevY;
}
prevX = px;
prevY = py;
if (dx === 0) {
flag += XSAME;
} else if (dx > -256 && dx < 256) {
flag += XSHORT;
if (dx > 0) flag += XSAME;
xCoordBuf[xbi++] = dx > 0 ? dx : -dx;
encodedCoordSize += 1;
} else {
xCoordBuf[xbi++] = (dx >> 8) & 0xFF;
xCoordBuf[xbi++] = dx & 0xFF;
encodedCoordSize += 2;
}
if (dy === 0) {
flag += YSAME;
} else if (dy > -256 && dy < 256) {
flag += YSHORT;
if (dy > 0) flag += YSAME;
yCoordBuf[ybi++] = dy > 0 ? dy : -dy;
encodedCoordSize += 1;
} else {
yCoordBuf[ybi++] = (dy >> 8) & 0xFF;
yCoordBuf[ybi++] = dy & 0xFF;
encodedCoordSize += 2;
}
if (flag === prevFlag && !isFirst) {
if (repeatPoint === -1) {
repeatPoint = fi - 1;
flagsC[repeatPoint] |= REPEAT;
flagsC[fi++] = 1;
} else {
++flagsC[repeatPoint + 1];
}
} else {
repeatPoint = -1;
flagsC[fi++] = flag;
prevFlag = flag;
}
}
flagsC.length = fi;
/* 优化116+119: 存储 Uint8Array 视图而非 ArrayBuffer消除 write 阶段的重新包装 */
var xEncoded = new Uint8Array(xCoordBuf.buffer.slice(0, xbi));
var yEncoded = new Uint8Array(yCoordBuf.buffer.slice(0, ybi));
/* 更新共享 buffer 引用(如果分配了新的更大的 buffer */
if (xCoordBuf !== sharedXBuf) sharedXBuf = xCoordBuf;
if (yCoordBuf !== sharedYBuf) sharedYBuf = yCoordBuf;
/* 优化103: 不构建 contour 数组,直接存储元数据 */
glyf.contours = new Array(numContours);
glyf._flatContours = true;
/* 优化103: 存储每个 contour 的点数,供 write 计算 endPtsOfContours */
glyf._pointsPerContour = new Array(numContours);
for (var ci = 0; ci < numContours; ci++) {
glyf._pointsPerContour[ci] = (ci === 0 ? endPts[0] + 1 : endPts[ci] - endPts[ci - 1]);
}
glyf._numContours = numContours;
glyf._totalPoints = numPoints;
glyf._precomputedGlyfSupport = {
flags: flagsC,
encodedCoordSize: encodedCoordSize,
xEncoded: xEncoded,
yEncoded: yEncoded
};
delete glyf._xArr;
delete glyf._yArr;
delete glyf._flags;
delete glyf.endPtsOfContours;
}
/**
* 优化84+98: 合并 ceil+reduce+flagsAndSize 为单次遍历预编码 x/y Uint8Array
*/
function ceilReduceAndSizeFlat(glyf) {
var contours = glyf.contours;
/* 优化91: 跳过 reducePathFlat */
for (var j = contours.length - 1; j >= 0; j--) {
if (contours[j].length <= 6) {
contours.splice(j, 1);
}
}
if (0 === contours.length) {
delete glyf.contours;
return;
}
return glyf;
if (glyf._precomputedGlyfSupport) {
return;
}
var ONCURVE = _glyFlag.default.ONCURVE;
var XSHORT = _glyFlag.default.XSHORT;
var YSHORT = _glyFlag.default.YSHORT;
var XSAME = _glyFlag.default.XSAME;
var YSAME = _glyFlag.default.YSAME;
var REPEAT = _glyFlag.default.REPEAT;
var totalPoints = 0;
for (var j = 0, cl = contours.length; j < cl; j++) {
totalPoints += contours[j].length / 3;
}
var flagsC = new Array(totalPoints);
var fi = 0;
var prevX = 0, prevY = 0;
var isFirst = true;
var prevFlag = -1;
var repeatPoint = -1;
var encodedCoordSize = 0;
/* 优化98: 预编码 x/y 坐标 buffer */
var xCoordBuf = new Uint8Array(totalPoints * 2);
var yCoordBuf = new Uint8Array(totalPoints * 2);
var xbi = 0, ybi = 0;
for (var j = 0, cl2 = contours.length; j < cl2; j++) {
var contour = contours[j];
for (var i = 0, l = contour.length; i < l; i += 3) {
var px = contour[i];
var py = contour[i + 1];
var onCurve = contour[i + 2];
var dx, dy;
var flag = onCurve ? ONCURVE : 0;
if (isFirst) {
dx = px;
dy = py;
isFirst = false;
} else {
dx = px - prevX;
dy = py - prevY;
}
prevX = px;
prevY = py;
if (dx === 0) {
flag += XSAME;
} else if (dx > -256 && dx < 256) {
flag += XSHORT;
if (dx > 0) flag += XSAME;
var absDx = dx > 0 ? dx : -dx;
xCoordBuf[xbi++] = absDx;
encodedCoordSize += 1;
} else {
xCoordBuf[xbi++] = (dx >> 8) & 0xFF;
xCoordBuf[xbi++] = dx & 0xFF;
encodedCoordSize += 2;
}
if (dy === 0) {
flag += YSAME;
} else if (dy > -256 && dy < 256) {
flag += YSHORT;
if (dy > 0) flag += YSAME;
var absDy = dy > 0 ? dy : -dy;
yCoordBuf[ybi++] = absDy;
encodedCoordSize += 1;
} else {
yCoordBuf[ybi++] = (dy >> 8) & 0xFF;
yCoordBuf[ybi++] = dy & 0xFF;
encodedCoordSize += 2;
}
if (flag === prevFlag && !isFirst) {
if (repeatPoint === -1) {
repeatPoint = fi - 1;
flagsC[repeatPoint] |= REPEAT;
flagsC[fi++] = 1;
} else {
++flagsC[repeatPoint + 1];
}
} else {
repeatPoint = -1;
flagsC[fi++] = flag;
prevFlag = flag;
}
}
}
flagsC.length = fi;
var xEncoded = xCoordBuf.buffer.slice(0, xbi);
var yEncoded = yCoordBuf.buffer.slice(0, ybi);
glyf._precomputedGlyfSupport = {
flags: flagsC,
encodedCoordSize: encodedCoordSize,
xEncoded: xEncoded,
yEncoded: yEncoded
};
}
/**
* 对ttf对象进行优化
*
* @param {Object} ttf ttf对象
* @return {true|Object} 错误信息
* 优化99+103+112+116+120: 单次遍历优化所有字形同时预计算 OS2/head/hhea metrics
*/
function optimizettf(ttf) {
var checkUnicodeRepeat = {};
var repeatList = [];
/* 优化2+45+62: for 循环替代 forEach只对 length>1 的 unicode 排序 */
var glyfs = ttf.glyf;
var hasCompound = false;
/* 优化120: 在主循环中同时计算 OS2/head/hhea metrics消除 OS2.size() 的全 glyf 遍历 */
var m_xMin = 16384, m_yMin = 16384, m_xMax = -16384, m_yMax = -16384;
var m_advWMax = -1;
var m_minLSB = 16384, m_minRSB = 16384;
var m_xAvgSum = 0, m_glyfNotEmpty = 0;
var m_firstChar = 0x10FFFF, m_lastChar = -1;
var m_maxPoints = 0, m_maxContours = 0;
/* 优化120: 合并 maxPoints 扫描到主循环 */
var maxBufPoints = 0;
for (var pi = 0, pl = glyfs.length; pi < pl; pi++) {
if (glyfs[pi]._xArr && glyfs[pi]._xArr.length > maxBufPoints) {
maxBufPoints = glyfs[pi]._xArr.length;
}
}
var sharedXBuf = new Uint8Array(maxBufPoints * 2);
var sharedYBuf = new Uint8Array(maxBufPoints * 2);
for (var index = 0, gl = glyfs.length; index < gl; index++) {
var glyf = glyfs[index];
if (glyf.compound) {
hasCompound = true;
}
if (glyf.unicode) {
/* 优化2: 删除第一次默认排序,只保留数字排序 */
if (glyf.unicode.length > 1) {
glyf.unicode.sort(function (a, b) { return a - b; });
}
@ -67,37 +300,87 @@ function optimizettf(ttf) {
} else {
checkUnicodeRepeat[u] = true;
}
/* 优化120: 同时收集 firstChar/lastChar */
if (u !== 0xFFFF) {
if (u < m_firstChar) m_firstChar = u;
if (u > m_lastChar) m_lastChar = u;
}
}
}
if (!glyf.compound && glyf.contours) {
if (glyf._flatContours) {
ceilAndReduceFlat(glyf);
} else {
glyf.contours.forEach(function (contour) {
(0, _pathCeil.default)(contour);
});
(0, _reduceGlyf.default)(glyf);
if (!glyf.compound) {
/* 优化94+116: 优先从 TypedArray 构建 contour + precompute使用共享 buffer */
if (glyf._xArr) {
ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf);
/* 优化120: 从 _numContours/_totalPoints 收集 metrics */
if (glyf._numContours > 0) {
if (glyf._numContours > m_maxContours) m_maxContours = glyf._numContours;
if (glyf._totalPoints > m_maxPoints) m_maxPoints = glyf._totalPoints;
}
} else if (glyf.contours) {
if (glyf._flatContours) {
ceilReduceAndSizeFlat(glyf);
} else {
glyf.contours.forEach(function (contour) {
(0, _pathCeil.default)(contour);
});
(0, _reduceGlyf.default)(glyf);
}
}
}
glyf.xMin = Math.round(glyf.xMin || 0);
glyf.xMax = Math.round(glyf.xMax || 0);
glyf.yMin = Math.round(glyf.yMin || 0);
glyf.yMax = Math.round(glyf.yMax || 0);
glyf.leftSideBearing = Math.round(glyf.leftSideBearing || 0);
glyf.advanceWidth = Math.round(glyf.advanceWidth || 0);
/* 优化120: 收集 metrics跳过 Math.round值已经是整数 */
var gXMin = glyf.xMin || 0;
var gYMin = glyf.yMin || 0;
var gXMax = glyf.xMax || 0;
var gYMax = glyf.yMax || 0;
if (gXMin < m_xMin) m_xMin = gXMin;
if (gYMin < m_yMin) m_yMin = gYMin;
if (gXMax > m_xMax) m_xMax = gXMax;
if (gYMax > m_yMax) m_yMax = gYMax;
var gAdvW = glyf.advanceWidth || 0;
if (gAdvW > m_advWMax) m_advWMax = gAdvW;
var gLSB = glyf.leftSideBearing || 0;
if (gLSB < m_minLSB) m_minLSB = gLSB;
/* 优化120: 同时计算 minRightSideBearing = advanceWidth - xMax */
var gRSB = gAdvW - gXMax;
if (gRSB < m_minRSB) m_minRSB = gRSB;
if (glyf.advanceWidth != null) {
m_xAvgSum += gAdvW;
m_glyfNotEmpty++;
}
glyf.xMin = gXMin;
glyf.xMax = gXMax;
glyf.yMin = gYMin;
glyf.yMax = gYMax;
glyf.leftSideBearing = gLSB;
glyf.advanceWidth = gAdvW;
}
/* 过滤无轮廓字体 */
var hasCompound = false;
for (var fi = 0, fl = glyfs.length; fi < fl; fi++) {
if (glyfs[fi].compound) { hasCompound = true; break; }
}
/* 优化112: 标记 unicode 已排序且已检查重复resolveTTF 可跳过 */
ttf._unicodeSorted = true;
/* 优化120: 存储 OS2/head/hhea 预计算 metrics */
ttf._metrics = {
xMin: m_xMin, yMin: m_yMin, xMax: m_xMax, yMax: m_yMax,
advanceWidthMax: m_advWMax,
minLeftSideBearing: m_minLSB,
minRightSideBearing: m_minRSB,
xMaxExtent: m_xMax,
xAvgCharWidth: m_xAvgSum / (m_glyfNotEmpty || 1),
usFirstCharIndex: m_firstChar,
usLastCharIndex: m_lastChar,
maxPoints: m_maxPoints,
maxContours: m_maxContours,
glyfNotEmpty: m_glyfNotEmpty
};
/* 优化99+103: hasCompound 已在主循环中追踪,过滤使用 _numContours 或 contours.length */
if (!hasCompound) {
var filtered = [glyfs[0]];
for (var gi = 1; gi < gl; gi++) {
if (glyfs[gi].contours && glyfs[gi].contours.length) {
filtered.push(glyfs[gi]);
var g = glyfs[gi];
if (g._numContours != null ? g._numContours > 0 : (g.contours && g.contours.length)) {
filtered.push(g);
}
}
ttf.glyf = filtered;
@ -108,4 +391,4 @@ function optimizettf(ttf) {
return {
repeat: repeatList
};
}
}

View File

@ -10,46 +10,110 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
/**
* @file otf轮廓转ttf轮廓
* @author mengke01(kekee000@gmail.com)
*
* CFF Type 2 charstring 解析后的 contour 格式
* - onCurve {x, y, onCurve: true}是曲线端点或线段端点
* - offCurve {x, y} onCurve 属性 cubic bezier 控制点
* - 每个 cubic bezier 段由 2 offCurve + 1 onCurve 组成
* - 连续的 offCurve 点之间隐含端点为两者的中点
*/
/**
* 转换轮廓
*
* @param {Array} otfContour otf轮廓
* @return {Array} ttf轮廓
* CFF contour 转换为标准 [onCurve, offCurve, offCurve, onCurve, ...] 序列
* 处理隐含端点和连续 offCurve 点的情况
*/
function transformContour(otfContour) {
var contour = [];
var prevPoint;
var curPoint;
var nextPoint;
var nextNextPoint;
contour.push(prevPoint = otfContour[0]);
for (var i = 1, l = otfContour.length; i < l; i++) {
curPoint = otfContour[i];
if (curPoint.onCurve) {
contour.push(curPoint);
prevPoint = curPoint;
}
// 三次bezier曲线
else {
nextPoint = otfContour[i + 1];
nextNextPoint = i === l - 2 ? otfContour[0] : otfContour[i + 2];
var bezierArray = (0, _bezierCubic2Q.default)(prevPoint, curPoint, nextPoint, nextNextPoint);
bezierArray[0][2].onCurve = true;
contour.push(bezierArray[0][1]);
contour.push(bezierArray[0][2]);
function normalizeContour(otfContour) {
if (!otfContour.length) return [];
// 第二个曲线
if (bezierArray[1]) {
bezierArray[1][2].onCurve = true;
contour.push(bezierArray[1][1]);
contour.push(bezierArray[1][2]);
}
prevPoint = nextNextPoint;
i += 2;
var points = [];
for (var i = 0; i < otfContour.length; i++) {
var p = otfContour[i];
points.push({ x: p.x, y: p.y, onCurve: !!p.onCurve });
}
if (points.length < 2) return points;
/** 如果第一个点不是 onCurve需要回绕处理 */
if (!points[0].onCurve) {
var last = points[points.length - 1];
if (last.onCurve) {
/** 隐含端点 = 最后一个 onCurve 点(回绕起点) */
points.unshift({ x: last.x, y: last.y, onCurve: true });
} else {
/** 首尾都是 offCurve隐含端点 = 首尾中点 */
points.unshift({
x: (points[0].x + last.x) * 0.5,
y: (points[0].y + last.y) * 0.5,
onCurve: true
});
}
}
/** 处理连续的 offCurve 点:在它们之间插入隐含端点 */
var normalized = [];
for (var i = 0; i < points.length; i++) {
var p = points[i];
normalized.push(p);
if (!p.onCurve && i + 1 < points.length && !points[i + 1].onCurve) {
/** 两个连续 offCurve隐含端点 = 中点 */
normalized.push({
x: (p.x + points[i + 1].x) * 0.5,
y: (p.y + points[i + 1].y) * 0.5,
onCurve: true
});
}
}
return normalized;
}
/**
* 转换已标准化的轮廓onCurve/offCurve 严格交替
* 模式onCurve, offCurve, offCurve, onCurve, offCurve, offCurve, ...
*/
function transformContour(otfContour) {
var normalized = normalizeContour(otfContour);
if (normalized.length < 2) return [];
var contour = [];
contour.push(normalized[0]);
var i = 1;
while (i < normalized.length) {
var cur = normalized[i];
if (cur.onCurve) {
/** 线段:直接添加 onCurve 端点 */
contour.push(cur);
i++;
} else {
/** cubic bezieroffCurve, offCurve, onCurve */
var c1 = cur;
var c2 = i + 1 < normalized.length ? normalized[i + 1] : null;
var end;
if (c2 && !c2.onCurve) {
/** 标准 cubic bezier2个控制点 + 1个端点 */
end = i + 2 < normalized.length ? normalized[i + 2] : normalized[0];
i += 3;
} else if (c2 && c2.onCurve) {
/** 退化 cubic bezier只有1个控制点端点就是 c2 */
end = c2;
i += 2;
} else {
/** 只有一个 offCurve 点,回绕到起点 */
end = normalized[0];
i++;
}
var bezierArray = (0, _bezierCubic2Q.default)(contour[contour.length - 1], c1, c2 || c1, end);
for (var bi = 0, bl = bezierArray.length; bi < bl; bi++) {
bezierArray[bi][2].onCurve = true;
contour.push(bezierArray[bi][1]);
contour.push(bezierArray[bi][2]);
}
}
}
return (0, _pathCeil.default)(contour);
}
@ -65,10 +129,9 @@ function otfContours2ttfContours(otfContours) {
}
var contours = [];
for (var i = 0, l = otfContours.length; i < l; i++) {
// 这里可能由于转换错误导致空轮廓,需要去除
if (otfContours[i][0]) {
contours.push(transformContour(otfContours[i]));
}
}
return contours;
}
}

View File

@ -12,26 +12,28 @@ exports.default = readWindowsAllCodes;
*/
/**
* 优化65: format12 二分查找避免全量展开
* 优化65+88: format12 二分查找支持扁平数组格式
*/
function lookupFormat12(groups, unicode) {
var lo = 0, hi = groups.length - 1;
var lo = 0, hi = (groups.length / 3) - 1;
while (lo <= hi) {
var mid = (lo + hi) >> 1;
var g = groups[mid];
if (unicode < g.start) {
var gi = mid * 3;
var gStart = groups[gi];
var gEnd = groups[gi + 1];
if (unicode < gStart) {
hi = mid - 1;
} else if (unicode > g.end) {
} else if (unicode > gEnd) {
lo = mid + 1;
} else {
return g.startId + (unicode - g.start);
return groups[gi + 2] + (unicode - gStart);
}
}
return -1;
}
/**
* 优化65: format4 线性查找 segment
* 优化114: format4 二分查找 segment替代线性扫描
*/
function lookupFormat4(format4, unicode) {
var startCode = format4.startCode;
@ -40,14 +42,28 @@ function lookupFormat4(format4, unicode) {
var idRangeOffset = format4.idRangeOffset;
var segCount = format4.segCountX2 / 2;
for (var i = 0; i < segCount; i++) {
if (unicode >= startCode[i] && unicode <= endCode[i]) {
var lo = 0, hi = segCount - 1;
while (lo <= hi) {
var mid = (lo + hi) >> 1;
if (unicode < startCode[mid]) {
hi = mid - 1;
} else if (unicode > endCode[mid]) {
lo = mid + 1;
} else {
var i = mid;
if (idRangeOffset[i] === 0) {
return (unicode + idDelta[i]) % 0x10000;
}
var graphIdArrayIndexOffset = (format4.glyphIdArrayOffset - format4.idRangeOffsetOffset) / 2;
var index = i + idRangeOffset[i] / 2 + (unicode - startCode[i]) - graphIdArrayIndexOffset;
var graphId = format4.glyphIdArray[index];
var graphId;
if (format4.glyphIdArray) {
graphId = format4.glyphIdArray[index];
} else if (format4._cmapView) {
graphId = format4._cmapView.getUint16(format4.glyphIdArrayOffset + index * 2, false);
} else {
return 0;
}
if (graphId !== 0) {
return (graphId + idDelta[i]) % 0x10000;
}
@ -99,15 +115,15 @@ function readWindowsAllCodes(tables, ttf) {
}
}
/* format0 和 format14 仍然需要全量处理(数据量小) */
if (format0) {
/* 优化93: format0/format14 在 subset 模式下跳过了解析glyphIdArray/groups 为空 */
if (format0 && format0.glyphIdArray) {
for (var i = 0, l = format0.glyphIdArray.length; i < l; i++) {
if (format0.glyphIdArray[i]) {
codes[i] = format0.glyphIdArray[i];
}
}
}
if (format14) {
if (format14 && format14.groups && format14.groups.length) {
for (var vi = 0, vl = format14.groups.length; vi < vl; vi++) {
var vg = format14.groups[vi];
if (vg.unicode) {
@ -136,13 +152,25 @@ function readWindowsAllCodes(tables, ttf) {
}
}
if (format12) {
for (var gi = 0, gl = format12.nGroups; gi < gl; gi++) {
var group = format12.groups[gi];
var startId = group.startId;
var start = group.start;
var end = group.end;
for (; start <= end;) {
codes[start++] = startId++;
var f12Groups = format12.groups;
if (format12._flatGroups) {
for (var gi = 0, gl = f12Groups.length; gi < gl; gi += 3) {
var startId = f12Groups[gi + 2];
var start = f12Groups[gi];
var end = f12Groups[gi + 1];
for (; start <= end;) {
codes[start++] = startId++;
}
}
} else {
for (var gi2 = 0, gl2 = format12.nGroups; gi2 < gl2; gi2++) {
var group = f12Groups[gi2];
var startId2 = group.startId;
var start2 = group.start;
var end2 = group.end;
for (; start2 <= end2;) {
codes[start2++] = startId2++;
}
}
}
} else if (format4) {