mirror of
https://github.com/2234839/web-font.git
synced 2026-07-01 13:18:37 +08:00
优化性能
This commit is contained in:
parent
3ce09a21a4
commit
18e60fe940
@ -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[],
|
||||
|
||||
34
src/App.tsx
34
src/App.tsx
@ -1,4 +1,4 @@
|
||||
import { createMemo, createSignal, onMount, Show, For } from "solid-js";
|
||||
import { createMemo, createSignal, createEffect, onMount, Show, For } from "solid-js";
|
||||
import { fetchFonts, fetchConfig, type FontInfo, type ServerConfig } from "./api";
|
||||
import UploadSection from "./UploadSection";
|
||||
import { SelectorRow } from "./FontSelector";
|
||||
@ -93,7 +93,7 @@ function App() {
|
||||
const [text, set_text] = createSignal("天地无极,乾坤借法");
|
||||
const [fonts, set_fonts] = createSignal<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);
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
22
vendor/fonteditor-core/lib/ttf/otfreader.js
vendored
22
vendor/fonteditor-core/lib/ttf/otfreader.js
vendored
@ -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) {
|
||||
|
||||
184
vendor/fonteditor-core/lib/ttf/table/CFF.js
vendored
184
vendor/fonteditor-core/lib/ttf/table/CFF.js
vendored
@ -110,6 +110,123 @@ function calcCFFSubroutineBias(subrs) {
|
||||
}
|
||||
return bias;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析原始 CFF Top DICT 获取 CID-keyed 字段的偏移
|
||||
* FDArray = 12 36, FDSelect = 12 37, ROS = 12 30
|
||||
*
|
||||
* @param {Reader} reader 读取器
|
||||
* @param {number} start 起始偏移
|
||||
* @param {number} length 大小
|
||||
* @return {Object} 包含 FDArray/FDSelect 偏移的对象
|
||||
*/
|
||||
function parseRawTopDict(reader, start, length) {
|
||||
if (start) {
|
||||
reader.seek(start);
|
||||
}
|
||||
var entries = [];
|
||||
var operands = [];
|
||||
var lastOffset = reader.offset + length;
|
||||
while (reader.offset < lastOffset) {
|
||||
var op = reader.readUint8();
|
||||
if (op <= 21) {
|
||||
if (op === 12) {
|
||||
op = 1200 + reader.readUint8();
|
||||
}
|
||||
entries.push([op, operands]);
|
||||
operands = [];
|
||||
} else {
|
||||
operands.push(_parseCFFDict.default._parseOperand(reader, op));
|
||||
}
|
||||
}
|
||||
var result = {};
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
var key = entries[i][0];
|
||||
var values = entries[i][1];
|
||||
result[key] = values.length === 1 ? values[0] : values;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 FDSelect 表,返回 glyph index → FD index 的映射
|
||||
* 支持 format 0 和 format 3
|
||||
*
|
||||
* @param {Reader} reader 读取器
|
||||
* @param {number} offset FDSelect 相对于 CFF 起始的偏移
|
||||
* @param {number} nGlyphs glyph 总数
|
||||
* @return {Array} FD index 数组,fdSelect[i] = glyph i 对应的 FD index
|
||||
*/
|
||||
function parseFDSelect(reader, offset, nGlyphs) {
|
||||
reader.seek(offset);
|
||||
var format = reader.readUint8();
|
||||
var fdSelect = [];
|
||||
|
||||
if (format === 0) {
|
||||
for (var i = 0; i < nGlyphs; i++) {
|
||||
fdSelect.push(reader.readUint8());
|
||||
}
|
||||
} else if (format === 3) {
|
||||
var nRanges = reader.readUint16();
|
||||
var ranges = [];
|
||||
for (var r = 0; r < nRanges; r++) {
|
||||
ranges.push({
|
||||
first: reader.readUint16(),
|
||||
fd: reader.readUint8()
|
||||
});
|
||||
}
|
||||
/** sentinel = reader.readUint16(); */
|
||||
|
||||
/** 根据 ranges 构建 fdSelect 数组 */
|
||||
for (var i = 0; i < nGlyphs; i++) {
|
||||
var fd = 0;
|
||||
for (var ri = ranges.length - 1; ri >= 0; ri--) {
|
||||
if (i >= ranges[ri].first) {
|
||||
fd = ranges[ri].fd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fdSelect.push(fd);
|
||||
}
|
||||
}
|
||||
|
||||
return fdSelect;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析单个 FD (Font DICT) 的 Private DICT 和 local subrs
|
||||
*
|
||||
* @param {Reader} reader 读取器
|
||||
* @param {number} cffOffset CFF 表起始偏移
|
||||
* @param {Array} fdDictData FD 的原始字节数据
|
||||
* @param {Array} strings CFF 字符串表
|
||||
* @return {Object} { subrs, subrsBias, defaultWidthX, nominalWidthX }
|
||||
*/
|
||||
function parseFDPrivate(reader, cffOffset, fdDictData, strings) {
|
||||
var dictReader = new _reader.default(new Uint8Array(fdDictData).buffer);
|
||||
var fdDict = _parseCFFDict.default.parseCFFDict(dictReader, 0, dictReader.length);
|
||||
var result = { subrs: [], subrsBias: 0, defaultWidthX: 0, nominalWidthX: 0 };
|
||||
|
||||
var privateData = fdDict[18];
|
||||
if (privateData && privateData.length >= 2) {
|
||||
var privLength = privateData[0];
|
||||
var privOffset = cffOffset + privateData[1];
|
||||
if (privLength > 0) {
|
||||
var privDict = _parseCFFDict.default.parsePrivateDict(reader, privOffset, privLength, strings);
|
||||
result.defaultWidthX = privDict.defaultWidthX || 0;
|
||||
result.nominalWidthX = privDict.nominalWidthX || 0;
|
||||
|
||||
if (privDict.subrs) {
|
||||
var subrIndex = parseCFFIndex(reader, privOffset + privDict.subrs);
|
||||
result.subrs = subrIndex.objects;
|
||||
result.subrsBias = calcCFFSubroutineBias(result.subrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
var _default = exports.default = _table.default.create('cff', [], {
|
||||
read: function read(reader, font) {
|
||||
var offset = this.offset;
|
||||
@ -132,7 +249,33 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
var topDict = _parseCFFDict.default.parseTopDict(dictReader, 0, dictReader.length, stringIndex.objects);
|
||||
cff.topDict = topDict;
|
||||
|
||||
// 私有字典数据
|
||||
/** 解析原始 Top DICT 获取 CID-keyed 字段 (FDArray/FDSelect) */
|
||||
var rawTopDict = parseRawTopDict(
|
||||
new _reader.default(new Uint8Array(topDictIndex.objects[0]).buffer),
|
||||
0, new Uint8Array(topDictIndex.objects[0]).buffer.byteLength
|
||||
);
|
||||
var fdArrayOffset = rawTopDict[1236]; // 12 36
|
||||
var fdSelectOffset = rawTopDict[1237]; // 12 37
|
||||
var isCID = !!(fdArrayOffset && fdSelectOffset);
|
||||
|
||||
/** 解析 FDSelect 和 FDArray(CID-keyed 字体) */
|
||||
var fdSelect = null;
|
||||
var fdPrivates = null;
|
||||
if (isCID) {
|
||||
var charStringsIndex = parseCFFIndex(reader, offset + topDict.charStrings);
|
||||
var nGlyphs = charStringsIndex.objects.length;
|
||||
|
||||
fdSelect = parseFDSelect(reader, offset + fdSelectOffset, nGlyphs);
|
||||
|
||||
/** 解析 FDArray */
|
||||
var fdArrayIndex = parseCFFIndex(reader, offset + fdArrayOffset);
|
||||
fdPrivates = [];
|
||||
for (var fi = 0; fi < fdArrayIndex.objects.length; fi++) {
|
||||
fdPrivates.push(parseFDPrivate(reader, offset, fdArrayIndex.objects[fi], stringIndex.objects));
|
||||
}
|
||||
}
|
||||
|
||||
// 私有字典数据(非 CID 字体使用)
|
||||
var privateDictLength = topDict.private[0];
|
||||
var privateDict = {};
|
||||
var privateDictOffset;
|
||||
@ -146,7 +289,7 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
cff.nominalWidthX = 0;
|
||||
}
|
||||
|
||||
// 私有子glyf数据
|
||||
// 私有子glyf数据(非 CID 字体使用)
|
||||
if (privateDict.subrs) {
|
||||
var subrOffset = privateDictOffset + privateDict.subrs;
|
||||
var subrIndex = parseCFFIndex(reader, subrOffset);
|
||||
@ -159,15 +302,12 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
cff.privateDict = privateDict;
|
||||
|
||||
// 解析glyf数据和名字
|
||||
var charStringsIndex = parseCFFIndex(reader, offset + topDict.charStrings);
|
||||
if (!isCID) {
|
||||
var charStringsIndex = parseCFFIndex(reader, offset + topDict.charStrings);
|
||||
}
|
||||
var nGlyphs = charStringsIndex.objects.length;
|
||||
|
||||
if (topDict.charset < 3) {
|
||||
// @author: fr33z00
|
||||
// See end of chapter 13 (p22) of #5176.CFF.pdf :
|
||||
// Still more optimization is possible by
|
||||
// observing that many fonts adopt one of 3 common charsets. In
|
||||
// these cases the operand to the charset operator in the Top DICT
|
||||
// specifies a predefined charset id, in place of an offset, as shown in table 22
|
||||
cff.charset = _cffStandardStrings.default;
|
||||
} else {
|
||||
cff.charset = (0, _parseCFFCharset.default)(reader, offset + topDict.charset, nGlyphs, stringIndex.objects);
|
||||
@ -185,6 +325,26 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
}
|
||||
cff.glyf = [];
|
||||
|
||||
/**
|
||||
* 为指定 glyph 构建 per-glyph 的 font 对象
|
||||
* CID-keyed 字体使用 FD 对应的 local subrs
|
||||
*/
|
||||
function getGlyphFont(glyphIndex) {
|
||||
if (isCID && fdSelect && fdPrivates) {
|
||||
var fdIdx = fdSelect[glyphIndex] || 0;
|
||||
var fd = fdPrivates[fdIdx];
|
||||
return {
|
||||
subrs: fd.subrs,
|
||||
subrsBias: fd.subrsBias,
|
||||
defaultWidthX: fd.defaultWidthX,
|
||||
nominalWidthX: fd.nominalWidthX,
|
||||
gsubrs: cff.gsubrs,
|
||||
gsubrsBias: cff.gsubrsBias
|
||||
};
|
||||
}
|
||||
return cff;
|
||||
}
|
||||
|
||||
// only parse subset glyphs
|
||||
var subset = font.readOptions.subset;
|
||||
if (subset && subset.length > 0) {
|
||||
@ -204,7 +364,7 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
font.subsetMap = subsetMap;
|
||||
Object.keys(subsetMap).forEach(function (i) {
|
||||
i = +i;
|
||||
var glyf = (0, _parseCFFGlyph.default)(charStringsIndex.objects[i], cff, i);
|
||||
var glyf = (0, _parseCFFGlyph.default)(charStringsIndex.objects[i], getGlyphFont(i), i);
|
||||
glyf.name = cff.charset[i];
|
||||
cff.glyf[i] = glyf;
|
||||
});
|
||||
@ -212,7 +372,7 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
// parse all
|
||||
else {
|
||||
for (var i = 0, l = nGlyphs; i < l; i++) {
|
||||
var glyf = (0, _parseCFFGlyph.default)(charStringsIndex.objects[i], cff, i);
|
||||
var glyf = (0, _parseCFFGlyph.default)(charStringsIndex.objects[i], getGlyphFont(i), i);
|
||||
glyf.name = cff.charset[i];
|
||||
cff.glyf.push(glyf);
|
||||
}
|
||||
@ -227,4 +387,4 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
size: function size(font) {
|
||||
throw new Error('not support get cff table size');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
256
vendor/fonteditor-core/lib/ttf/table/OS2.js
vendored
256
vendor/fonteditor-core/lib/ttf/table/OS2.js
vendored
@ -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,
|
||||
|
||||
@ -295,5 +295,7 @@ function parsePrivateDict(reader, start, length, strings) {
|
||||
}
|
||||
var _default = exports.default = {
|
||||
parseTopDict: parseTopDict,
|
||||
parsePrivateDict: parsePrivateDict
|
||||
parsePrivateDict: parsePrivateDict,
|
||||
parseCFFDict: parseCFFDict,
|
||||
_parseOperand: parseOperand
|
||||
};
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
15
vendor/fonteditor-core/lib/ttf/table/glyf.js
vendored
15
vendor/fonteditor-core/lib/ttf/table/glyf.js
vendored
@ -27,18 +27,19 @@ var _default = exports.default = _table.default.create('glyf', [], {
|
||||
/* subset */
|
||||
var subset = ttf.readOptions.subset;
|
||||
if (subset && subset.length > 0) {
|
||||
/* 优化1: subset 转为 Set,O(1) 查找 */
|
||||
var subsetSet = new Set(subset);
|
||||
/* 优化95+118: 直接遍历 subset 数组查找 glyphId,同时构建密集 ID 数组 */
|
||||
var subsetMap = { 0: true };
|
||||
var subsetGids = [0];
|
||||
var cmap = ttf.cmap;
|
||||
|
||||
/* 优化23: for...in 替代 Object.keys + forEach */
|
||||
for (var c in cmap) {
|
||||
if (subsetSet.has(+c)) {
|
||||
subsetMap[cmap[c]] = true;
|
||||
for (var si = 0, sl = subset.length; si < sl; si++) {
|
||||
var gid = cmap[subset[si]];
|
||||
if (gid !== undefined && !subsetMap[gid]) {
|
||||
subsetMap[gid] = true;
|
||||
subsetGids.push(gid);
|
||||
}
|
||||
}
|
||||
ttf.subsetMap = subsetMap;
|
||||
ttf.subsetGids = subsetGids;
|
||||
var parsedGlyfMap = {};
|
||||
|
||||
/* 优化43: for...in + for 循环替代 Object.keys + forEach */
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
106
vendor/fonteditor-core/lib/ttf/table/glyf/write.js
vendored
106
vendor/fonteditor-core/lib/ttf/table/glyf/write.js
vendored
@ -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;
|
||||
}
|
||||
|
||||
|
||||
31
vendor/fonteditor-core/lib/ttf/table/hmtx.js
vendored
31
vendor/fonteditor-core/lib/ttf/table/hmtx.js
vendored
@ -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;
|
||||
|
||||
8
vendor/fonteditor-core/lib/ttf/table/loca.js
vendored
8
vendor/fonteditor-core/lib/ttf/table/loca.js
vendored
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
vendor/fonteditor-core/lib/ttf/table/post.js
vendored
59
vendor/fonteditor-core/lib/ttf/table/post.js
vendored
@ -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;
|
||||
}
|
||||
|
||||
|
||||
18
vendor/fonteditor-core/lib/ttf/ttf.js
vendored
18
vendor/fonteditor-core/lib/ttf/ttf.js
vendored
@ -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;
|
||||
|
||||
76
vendor/fonteditor-core/lib/ttf/ttfreader.js
vendored
76
vendor/fonteditor-core/lib/ttf/ttfreader.js
vendored
@ -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);
|
||||
}
|
||||
|
||||
45
vendor/fonteditor-core/lib/ttf/ttfwriter.js
vendored
45
vendor/fonteditor-core/lib/ttf/ttfwriter.js
vendored
@ -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",
|
||||
|
||||
37
vendor/fonteditor-core/lib/ttf/util/checkSum.js
vendored
37
vendor/fonteditor-core/lib/ttf/util/checkSum.js
vendored
@ -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;
|
||||
|
||||
377
vendor/fonteditor-core/lib/ttf/util/optimizettf.js
vendored
377
vendor/fonteditor-core/lib/ttf/util/optimizettf.js
vendored
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,46 +10,110 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
||||
/**
|
||||
* @file otf轮廓转ttf轮廓
|
||||
* @author mengke01(kekee000@gmail.com)
|
||||
*
|
||||
* CFF Type 2 charstring 解析后的 contour 格式:
|
||||
* - onCurve 点({x, y, onCurve: true})是曲线端点或线段端点
|
||||
* - offCurve 点({x, y},无 onCurve 属性)是 cubic bezier 控制点
|
||||
* - 每个 cubic bezier 段由 2 个 offCurve + 1 个 onCurve 组成
|
||||
* - 连续的 offCurve 点之间,隐含端点为两者的中点
|
||||
*/
|
||||
|
||||
/**
|
||||
* 转换轮廓
|
||||
*
|
||||
* @param {Array} otfContour otf轮廓
|
||||
* @return {Array} ttf轮廓
|
||||
* 将 CFF contour 转换为标准 [onCurve, offCurve, offCurve, onCurve, ...] 序列
|
||||
* 处理隐含端点和连续 offCurve 点的情况
|
||||
*/
|
||||
function transformContour(otfContour) {
|
||||
var contour = [];
|
||||
var prevPoint;
|
||||
var curPoint;
|
||||
var nextPoint;
|
||||
var nextNextPoint;
|
||||
contour.push(prevPoint = otfContour[0]);
|
||||
for (var i = 1, l = otfContour.length; i < l; i++) {
|
||||
curPoint = otfContour[i];
|
||||
if (curPoint.onCurve) {
|
||||
contour.push(curPoint);
|
||||
prevPoint = curPoint;
|
||||
}
|
||||
// 三次bezier曲线
|
||||
else {
|
||||
nextPoint = otfContour[i + 1];
|
||||
nextNextPoint = i === l - 2 ? otfContour[0] : otfContour[i + 2];
|
||||
var bezierArray = (0, _bezierCubic2Q.default)(prevPoint, curPoint, nextPoint, nextNextPoint);
|
||||
bezierArray[0][2].onCurve = true;
|
||||
contour.push(bezierArray[0][1]);
|
||||
contour.push(bezierArray[0][2]);
|
||||
function normalizeContour(otfContour) {
|
||||
if (!otfContour.length) return [];
|
||||
|
||||
// 第二个曲线
|
||||
if (bezierArray[1]) {
|
||||
bezierArray[1][2].onCurve = true;
|
||||
contour.push(bezierArray[1][1]);
|
||||
contour.push(bezierArray[1][2]);
|
||||
}
|
||||
prevPoint = nextNextPoint;
|
||||
i += 2;
|
||||
var points = [];
|
||||
for (var i = 0; i < otfContour.length; i++) {
|
||||
var p = otfContour[i];
|
||||
points.push({ x: p.x, y: p.y, onCurve: !!p.onCurve });
|
||||
}
|
||||
|
||||
if (points.length < 2) return points;
|
||||
|
||||
/** 如果第一个点不是 onCurve,需要回绕处理 */
|
||||
if (!points[0].onCurve) {
|
||||
var last = points[points.length - 1];
|
||||
if (last.onCurve) {
|
||||
/** 隐含端点 = 最后一个 onCurve 点(回绕起点) */
|
||||
points.unshift({ x: last.x, y: last.y, onCurve: true });
|
||||
} else {
|
||||
/** 首尾都是 offCurve,隐含端点 = 首尾中点 */
|
||||
points.unshift({
|
||||
x: (points[0].x + last.x) * 0.5,
|
||||
y: (points[0].y + last.y) * 0.5,
|
||||
onCurve: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理连续的 offCurve 点:在它们之间插入隐含端点 */
|
||||
var normalized = [];
|
||||
for (var i = 0; i < points.length; i++) {
|
||||
var p = points[i];
|
||||
normalized.push(p);
|
||||
if (!p.onCurve && i + 1 < points.length && !points[i + 1].onCurve) {
|
||||
/** 两个连续 offCurve,隐含端点 = 中点 */
|
||||
normalized.push({
|
||||
x: (p.x + points[i + 1].x) * 0.5,
|
||||
y: (p.y + points[i + 1].y) * 0.5,
|
||||
onCurve: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换已标准化的轮廓(onCurve/offCurve 严格交替)
|
||||
* 模式:onCurve, offCurve, offCurve, onCurve, offCurve, offCurve, ...
|
||||
*/
|
||||
function transformContour(otfContour) {
|
||||
var normalized = normalizeContour(otfContour);
|
||||
if (normalized.length < 2) return [];
|
||||
|
||||
var contour = [];
|
||||
contour.push(normalized[0]);
|
||||
|
||||
var i = 1;
|
||||
while (i < normalized.length) {
|
||||
var cur = normalized[i];
|
||||
if (cur.onCurve) {
|
||||
/** 线段:直接添加 onCurve 端点 */
|
||||
contour.push(cur);
|
||||
i++;
|
||||
} else {
|
||||
/** cubic bezier:offCurve, offCurve, onCurve */
|
||||
var c1 = cur;
|
||||
var c2 = i + 1 < normalized.length ? normalized[i + 1] : null;
|
||||
var end;
|
||||
|
||||
if (c2 && !c2.onCurve) {
|
||||
/** 标准 cubic bezier:2个控制点 + 1个端点 */
|
||||
end = i + 2 < normalized.length ? normalized[i + 2] : normalized[0];
|
||||
i += 3;
|
||||
} else if (c2 && c2.onCurve) {
|
||||
/** 退化 cubic bezier:只有1个控制点,端点就是 c2 */
|
||||
end = c2;
|
||||
i += 2;
|
||||
} else {
|
||||
/** 只有一个 offCurve 点,回绕到起点 */
|
||||
end = normalized[0];
|
||||
i++;
|
||||
}
|
||||
|
||||
var bezierArray = (0, _bezierCubic2Q.default)(contour[contour.length - 1], c1, c2 || c1, end);
|
||||
for (var bi = 0, bl = bezierArray.length; bi < bl; bi++) {
|
||||
bezierArray[bi][2].onCurve = true;
|
||||
contour.push(bezierArray[bi][1]);
|
||||
contour.push(bezierArray[bi][2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (0, _pathCeil.default)(contour);
|
||||
}
|
||||
|
||||
@ -65,10 +129,9 @@ function otfContours2ttfContours(otfContours) {
|
||||
}
|
||||
var contours = [];
|
||||
for (var i = 0, l = otfContours.length; i < l; i++) {
|
||||
// 这里可能由于转换错误导致空轮廓,需要去除
|
||||
if (otfContours[i][0]) {
|
||||
contours.push(transformContour(otfContours[i]));
|
||||
}
|
||||
}
|
||||
return contours;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user