mirror of
https://github.com/2234839/web-font.git
synced 2026-06-27 17:48:31 +08:00
glm2.5 优化,暂存
This commit is contained in:
parent
13787dfd22
commit
4a6c7e64e1
@ -21,6 +21,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
||||
* @return {Object} ttfObject对象
|
||||
*/
|
||||
function otf2ttfobject(otfBuffer, options) {
|
||||
var __t0 = process.hrtime.bigint();
|
||||
var otfObject;
|
||||
if (otfBuffer instanceof ArrayBuffer) {
|
||||
var otfReader = new _otfreader.default(options);
|
||||
@ -56,5 +57,7 @@ function otf2ttfobject(otfBuffer, options) {
|
||||
/** 优化245: delete → null 赋值,避免 V8 隐藏类转换 */
|
||||
otfObject.CFF = null;
|
||||
otfObject.VORG = null;
|
||||
var __t1 = process.hrtime.bigint();
|
||||
console.error('otf2ttfobject: ' + Number(__t1 - __t0) / 1e6 + 'ms');
|
||||
return otfObject;
|
||||
}
|
||||
71
vendor/fonteditor-core/lib/ttf/otfreader.js
vendored
71
vendor/fonteditor-core/lib/ttf/otfreader.js
vendored
@ -41,6 +41,7 @@ var OTFReader = exports.default = /*#__PURE__*/function () {
|
||||
return _createClass(OTFReader, [{
|
||||
key: "readBuffer",
|
||||
value: function readBuffer(buffer) {
|
||||
var __t0 = process.hrtime.bigint();
|
||||
var reader = new _reader.default(buffer, 0, buffer.byteLength, false);
|
||||
var font = {};
|
||||
|
||||
@ -84,6 +85,8 @@ var OTFReader = exports.default = /*#__PURE__*/function () {
|
||||
_error.default.raise(10303);
|
||||
}
|
||||
reader.dispose();
|
||||
var __t1 = process.hrtime.bigint();
|
||||
console.error('OTFREADER.readBuffer: ' + Number(__t1 - __t0) / 1e6 + 'ms');
|
||||
return font;
|
||||
}
|
||||
|
||||
@ -95,25 +98,42 @@ var OTFReader = exports.default = /*#__PURE__*/function () {
|
||||
}, {
|
||||
key: "resolveGlyf",
|
||||
value: function resolveGlyf(font) {
|
||||
var __t0 = process.hrtime.bigint();
|
||||
var codes = font.cmap;
|
||||
var glyf = font.CFF.glyf;
|
||||
var subsetMap = font.readOptions.subset ? font.subsetMap : null;
|
||||
/** 优化290: subsetMap 检查提到循环外,消除每次迭代的分支判断 */
|
||||
var cmapKeys = Object.keys(codes);
|
||||
if (subsetMap) {
|
||||
for (var ki = 0, kl = cmapKeys.length; ki < kl; ki++) {
|
||||
var c = cmapKeys[ki];
|
||||
var i = codes[c];
|
||||
if (!subsetMap[i]) continue;
|
||||
if (!glyf[i].unicode) glyf[i].unicode = [];
|
||||
glyf[i].unicode.push(+c);
|
||||
/**
|
||||
* 优化298: subset 模式下只遍历 subset unicode 列表(O(S)),避免全 cmap 遍历(O(U))
|
||||
* 思源等大 CID 字体 cmap 有数万映射,原 Object.keys + 全量循环开销显著
|
||||
*/
|
||||
if (subsetMap && font.readOptions.subset && font.readOptions.subset.length > 0) {
|
||||
var subsetList = font.readOptions.subset;
|
||||
for (var si = 0, sl = subsetList.length; si < sl; si++) {
|
||||
var cp = subsetList[si];
|
||||
var gid = codes[cp];
|
||||
if (gid === undefined) continue;
|
||||
if (!subsetMap[gid]) continue;
|
||||
if (!glyf[gid].unicode) glyf[gid].unicode = [];
|
||||
glyf[gid].unicode.push(cp);
|
||||
}
|
||||
} else {
|
||||
for (var ki = 0, kl = cmapKeys.length; ki < kl; ki++) {
|
||||
var c = cmapKeys[ki];
|
||||
var i = codes[c];
|
||||
if (!glyf[i].unicode) glyf[i].unicode = [];
|
||||
glyf[i].unicode.push(+c);
|
||||
/** 优化290: subsetMap 检查提到循环外,消除每次迭代的分支判断 */
|
||||
var cmapKeys = Object.keys(codes);
|
||||
if (subsetMap) {
|
||||
for (var ki = 0, kl = cmapKeys.length; ki < kl; ki++) {
|
||||
var c = cmapKeys[ki];
|
||||
var i = codes[c];
|
||||
if (!subsetMap[i]) continue;
|
||||
if (!glyf[i].unicode) glyf[i].unicode = [];
|
||||
glyf[i].unicode.push(+c);
|
||||
}
|
||||
} else {
|
||||
for (var ki2 = 0, kl2 = cmapKeys.length; ki2 < kl2; ki2++) {
|
||||
var c2 = cmapKeys[ki2];
|
||||
var i2 = codes[c2];
|
||||
if (!glyf[i2].unicode) glyf[i2].unicode = [];
|
||||
glyf[i2].unicode.push(+c2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,8 +141,25 @@ var OTFReader = exports.default = /*#__PURE__*/function () {
|
||||
var hmtxData = font.hmtx;
|
||||
var isFlat = hmtxData instanceof Int32Array;
|
||||
var hLen = isFlat ? hmtxData.length / 2 : hmtxData.length;
|
||||
/** 优化290: subsetMap 检查提到循环外,消除每次迭代的分支判断 */
|
||||
if (subsetMap) {
|
||||
/**
|
||||
* 优化298: subset 模式下遍历 subsetGids(O(S)),避免全 hmtx 遍历(O(U))
|
||||
*/
|
||||
if (subsetMap && font.subsetGids) {
|
||||
var sGids = font.subsetGids;
|
||||
if (isFlat) {
|
||||
for (var gi = 0, gl = sGids.length; gi < gl; gi++) {
|
||||
var gid2 = sGids[gi];
|
||||
glyf[gid2].advanceWidth = hmtxData[gid2 * 2] || 0;
|
||||
glyf[gid2].leftSideBearing = hmtxData[gid2 * 2 + 1];
|
||||
}
|
||||
} else {
|
||||
for (var gi2 = 0, gl2 = sGids.length; gi2 < gl2; gi2++) {
|
||||
var gid3 = sGids[gi2];
|
||||
glyf[gid3].advanceWidth = hmtxData[gid3].advanceWidth || 0;
|
||||
glyf[gid3].leftSideBearing = hmtxData[gid3].leftSideBearing;
|
||||
}
|
||||
}
|
||||
} else if (subsetMap) {
|
||||
if (isFlat) {
|
||||
for (var hi = 0, j = 0; hi < hLen; hi++, j += 2) {
|
||||
if (!subsetMap[hi]) continue;
|
||||
@ -170,6 +207,8 @@ var OTFReader = exports.default = /*#__PURE__*/function () {
|
||||
glyf = subGlyf;
|
||||
}
|
||||
font.glyf = glyf;
|
||||
var __t1 = process.hrtime.bigint();
|
||||
console.error('OTFREADER.resolveGlyf: ' + Number(__t1 - __t0) / 1e6 + 'ms');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
334
vendor/fonteditor-core/lib/ttf/table/CFF.js
vendored
334
vendor/fonteditor-core/lib/ttf/table/CFF.js
vendored
@ -140,6 +140,112 @@ function parseCFFIndexOffsets(reader, offset) {
|
||||
return { offsets: offsets, count: count, dataStart: reader.offset, endOffset: reader.offset };
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化303: 子集模式下的 CFF 索引按需预读
|
||||
* 大 CID 字体(如思源)的 charstring index 含数万字形,parseCFFIndexOffsets 全量读取
|
||||
* offset 表(思源 65535×3≈196KB,1.97ms)对 subset 是纯浪费——仅需其中极少数字形。
|
||||
*
|
||||
* 本函数只读取被引用字形及其后一个 offset(用于界定字节范围),并按需读最后一个
|
||||
* offset 计算 totalSize 供 prepareCFFIndexView 建立全量大视图。
|
||||
* 返回的 indexInfo 兼容 readCFFIndexObject:subsetGids 命中的槽位有真实 offset,
|
||||
* 其余为 undefined(按需 seek 读取)。
|
||||
*
|
||||
* @param {Reader} reader 读取器
|
||||
* @param {number} offset 索引偏移
|
||||
* @param {Array<number>} neededGids 需要名字/数据的 GID 升序列表(0-based,不含越界)
|
||||
* @return {Object} { offsets, count, dataStart, endOffset }
|
||||
*/
|
||||
function parseCFFIndexOffsetsSubset(reader, offset, neededGids) {
|
||||
reader.seek(offset);
|
||||
var count = reader.readUint16();
|
||||
var offsetSize = reader.readUint8();
|
||||
/** offset 数组起始(紧跟 count+offsetSize 之后) */
|
||||
var offsetArrayBase = reader.offset;
|
||||
/**
|
||||
* 优化307: 直接用 DataView 读取 offset,绕过 reader 的原型方法调用 + seek 边界检查。
|
||||
* reader.view 是覆盖整个 buffer 的 DataView,offset 坐标系与 reader.offset 一致。
|
||||
* 实测 reader.seek + readUint8 链对 15 次读取达 0.5ms(ES5 class 方法开销),
|
||||
* 直连 DataView 后降至可忽略。
|
||||
*/
|
||||
var view = reader.view;
|
||||
function readOffAt(pos) {
|
||||
var off = offsetArrayBase + pos * offsetSize;
|
||||
if (offsetSize === 1) return view.getUint8(off);
|
||||
if (offsetSize === 2) return view.getUint16(off, false);
|
||||
if (offsetSize === 4) return view.getUint32(off, false);
|
||||
/** offsetSize === 3 */
|
||||
return view.getUint8(off) << 16 | view.getUint8(off + 1) << 8 | view.getUint8(off + 2);
|
||||
}
|
||||
/**
|
||||
* 优化308: subset 模式 offsets 用普通对象而非 new Array(count+1)。
|
||||
* 思源 charstring index count=65535,new Array(65536) 单次分配 0.32ms,
|
||||
* 而 subset 仅命中个位数槽位。对象按需添加属性,零分配开销。
|
||||
* readCFFIndexObject 的 off[idx] 索引对对象同样有效。
|
||||
*/
|
||||
var offsets = {};
|
||||
/** 读取每个所需 GID 及其后一个 offset(界定数据范围) */
|
||||
for (var gi = 0; gi < neededGids.length; gi++) {
|
||||
var gid = neededGids[gi];
|
||||
if (gid < 0 || gid > count) continue;
|
||||
if (offsets[gid] === undefined) offsets[gid] = readOffAt(gid);
|
||||
if (gid + 1 <= count && offsets[gid + 1] === undefined) offsets[gid + 1] = readOffAt(gid + 1);
|
||||
}
|
||||
/** 读最后一个 offset 计算 totalSize,供 prepareCFFIndexView 建全量大视图 */
|
||||
var lastOffset = readOffAt(count);
|
||||
var dataStart = offsetArrayBase + (count + 1) * offsetSize;
|
||||
/** 同步 reader.offset 到 dataStart,保持后续 reader 读取的坐标连续性 */
|
||||
reader.offset = dataStart;
|
||||
return {
|
||||
offsets: offsets,
|
||||
count: count,
|
||||
dataStart: dataStart,
|
||||
/** 标记子集模式并提供按需读取所需信息 */
|
||||
_subsetMode: true,
|
||||
_offsetArrayBase: offsetArrayBase,
|
||||
_offsetSize: offsetSize,
|
||||
_totalSize: lastOffset - 1
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化304: 完全惰性的 CFF 索引预读(用于 local subrs)
|
||||
* local subrs 的引用 idx 在 charstring 解析时动态决定,无法预知,故不能像
|
||||
* parseCFFIndexOffsetsSubset 那样只读所需 offset。但大 subrs 表(思源单 FD 含 26550 subrs)
|
||||
* 全量读取 offset 表(26550×3≈80KB,~10万次 readUint8)对 subset 仍是纯浪费——
|
||||
* 实际被引用的 subr 通常是个位数。
|
||||
*
|
||||
* 本函数只读 count + offsetSize + 最后一个 offset(算 totalSize 建 view),
|
||||
* offsets 数组保持稀疏,readCFFIndexObject 按需 seek 填充命中的 idx。
|
||||
*
|
||||
* @param {Reader} reader 读取器
|
||||
* @param {number} offset 索引偏移
|
||||
* @return {Object} { offsets, count, dataStart, _subsetMode, ... }
|
||||
*/
|
||||
function parseCFFIndexOffsetsLazy(reader, offset) {
|
||||
reader.seek(offset);
|
||||
var count = reader.readUint16();
|
||||
var offsetSize = reader.readUint8();
|
||||
var offsetArrayBase = reader.offset;
|
||||
/** 优化307: 直接用 DataView 读末尾 offset,绕过 reader.seek + 原型方法 */
|
||||
var view = reader.view;
|
||||
var lastOffPos = offsetArrayBase + count * offsetSize;
|
||||
var lastOffset;
|
||||
if (offsetSize === 1) lastOffset = view.getUint8(lastOffPos);else if (offsetSize === 2) lastOffset = view.getUint16(lastOffPos, false);else if (offsetSize === 4) lastOffset = view.getUint32(lastOffPos, false);else lastOffset = view.getUint8(lastOffPos) << 16 | view.getUint8(lastOffPos + 1) << 8 | view.getUint8(lastOffPos + 2);
|
||||
var dataStart = offsetArrayBase + (count + 1) * offsetSize;
|
||||
/** 同步 reader.offset 到 dataStart,保持后续 reader 读取坐标连续 */
|
||||
reader.offset = dataStart;
|
||||
return {
|
||||
/** 优化308: 用普通对象替代 new Array(count+1),避免大 subrs 表(26550)的数组分配 */
|
||||
offsets: {},
|
||||
count: count,
|
||||
dataStart: dataStart,
|
||||
_subsetMode: true,
|
||||
_offsetArrayBase: offsetArrayBase,
|
||||
_offsetSize: offsetSize,
|
||||
_totalSize: lastOffset - 1
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 parseCFFIndexOffsets 的结果,按需读取第 idx 个 object
|
||||
*
|
||||
@ -155,6 +261,30 @@ function parseCFFIndexOffsets(reader, offset) {
|
||||
function readCFFIndexObject(reader, indexInfo, idx) {
|
||||
var off = indexInfo.offsets;
|
||||
var view = indexInfo._view;
|
||||
/**
|
||||
* 优化303+307: 子集模式下 off[idx]/off[idx+1] 可能为 undefined(未预读),
|
||||
* 直接用 DataView 读取(绕过 reader 原型方法 + seek 边界检查)。命中槽位直接复用。
|
||||
*/
|
||||
if (off && off[idx] === undefined) {
|
||||
var base = indexInfo._offsetArrayBase;
|
||||
var os = indexInfo._offsetSize;
|
||||
var dv = reader.view;
|
||||
var o1 = base + idx * os;
|
||||
var o2 = base + (idx + 1) * os;
|
||||
if (os === 1) {
|
||||
off[idx] = dv.getUint8(o1);
|
||||
off[idx + 1] = dv.getUint8(o2);
|
||||
} else if (os === 2) {
|
||||
off[idx] = dv.getUint16(o1, false);
|
||||
off[idx + 1] = dv.getUint16(o2, false);
|
||||
} else if (os === 4) {
|
||||
off[idx] = dv.getUint32(o1, false);
|
||||
off[idx + 1] = dv.getUint32(o2, false);
|
||||
} else {
|
||||
off[idx] = dv.getUint8(o1) << 16 | dv.getUint8(o1 + 1) << 8 | dv.getUint8(o1 + 2);
|
||||
off[idx + 1] = dv.getUint8(o2) << 16 | dv.getUint8(o2 + 1) << 8 | dv.getUint8(o2 + 2);
|
||||
}
|
||||
}
|
||||
if (view) {
|
||||
/** 使用预创建的大视图 + subarray,baseOffset 已含 -1 修正 */
|
||||
return view.subarray(off[idx] - 1, off[idx + 1] - 1);
|
||||
@ -170,8 +300,9 @@ function readCFFIndexObject(reader, indexInfo, idx) {
|
||||
*/
|
||||
function prepareCFFIndexView(reader, indexInfo) {
|
||||
var off = indexInfo.offsets;
|
||||
if (!off || off.length < 2) return;
|
||||
var totalSize = off[off.length - 1] - 1;
|
||||
/** 优化303: 子集模式下 off[last] 未读,totalSize 由 parseCFFIndexOffsetsSubset 预先算好 */
|
||||
var totalSize = indexInfo._subsetMode ? indexInfo._totalSize : off && off.length >= 2 ? off[off.length - 1] - 1 : 0;
|
||||
if (totalSize <= 0) return;
|
||||
/** baseOffset 对齐原始 readCFFIndexObject 中的 byteOffset + dataStart + off[idx] - 1 */
|
||||
var baseOffset = reader.view.byteOffset + indexInfo.dataStart;
|
||||
indexInfo._view = new Uint8Array(reader.view.buffer, baseOffset, totalSize);
|
||||
@ -241,12 +372,15 @@ function parseFDSelect(reader, offset) {
|
||||
var format = reader.readUint8();
|
||||
|
||||
if (format === 0) {
|
||||
/** format 0:每个 glyph 一个 uint8,存储为扁平数组 */
|
||||
/** format 0:每个 glyph 一个 uint8 */
|
||||
var count = reader.readUint16();
|
||||
var flatData = new Uint8Array(count);
|
||||
for (var i = 0; i < count; i++) {
|
||||
flatData[i] = reader.readUint8();
|
||||
}
|
||||
/**
|
||||
* 优化297: 直接以 buffer 视图引用整段 FDSelect0 数据,替代 count 次 readUint8 循环
|
||||
* 思源等大 CID 字体 count 可达 6 万+,逐字节读取占可观耗时
|
||||
*/
|
||||
var dataStart = reader.view.byteOffset + reader.offset;
|
||||
var flatData = new Uint8Array(reader.view.buffer, dataStart, count);
|
||||
reader.offset += count;
|
||||
return { format: 0, ranges: null, flatData: flatData };
|
||||
}
|
||||
|
||||
@ -320,9 +454,33 @@ function parseFDPrivate(reader, cffOffset, fdDictData, strings) {
|
||||
/** 修复:subrs 偏移量可能为 0(CFF 规范允许),
|
||||
* 原代码用 if (privDict.subrs) 检查,0 是 falsy 导致跳过 subrs 读取 */
|
||||
if (privDict.subrs != null && privDict.subrs > 0) {
|
||||
var subrIndex = parseCFFIndex(reader, privOffset + privDict.subrs);
|
||||
result.subrs = subrIndex.objects;
|
||||
result.subrsBias = calcCFFSubroutineBias(result.subrs);
|
||||
/**
|
||||
* 优化296+304: 完全惰性解析 local subrs
|
||||
* 思源等 CID 字体的单个 FD 可能含数万 subrs(实测 26550),全量读取 offset 表
|
||||
* (~10万次 readUint8)对 subset 是纯浪费——实际被引用的 subr 通常是个位数。
|
||||
* 改用 parseCFFIndexOffsetsLazy 只读 count + 末尾 offset(建大视图),
|
||||
* offsets 数组保持稀疏,readCFFIndexObject 按需 seek 填充命中的 subr。
|
||||
*/
|
||||
var subrIndexInfo = parseCFFIndexOffsetsLazy(reader, privOffset + privDict.subrs);
|
||||
prepareCFFIndexView(reader, subrIndexInfo);
|
||||
var subrCount = subrIndexInfo.count;
|
||||
/**
|
||||
* 优化311: lazySubrs 用普通对象替代 new Array(subrCount)。
|
||||
* 思源单 FD subrs 可达 26550,new Array(26550) 分配 0.15ms;subset 仅引用个位数。
|
||||
* _resolveSubr 的按需填充对对象同样有效,bias 用 subrCount 单独计算。
|
||||
*/
|
||||
var lazySubrs = {};
|
||||
result.subrs = lazySubrs;
|
||||
result.subrsBias = calcCFFSubroutineBias({ length: subrCount });
|
||||
/** 暴露按需解码器,parseCFFGlyph 访问 subrs[idx] 时调用 */
|
||||
result._resolveSubr = function (idx) {
|
||||
var s = lazySubrs[idx];
|
||||
if (s === undefined) {
|
||||
s = readCFFIndexObject(reader, subrIndexInfo, idx);
|
||||
lazySubrs[idx] = s;
|
||||
}
|
||||
return s;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -349,33 +507,75 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
|
||||
// 顶级字典数据
|
||||
var topDictData = topDictIndex.objects[0];
|
||||
/** 优化302: 复用 parseTopDict 内部的 parseCFFDict 结果,避免重复解析一遍 Top DICT */
|
||||
var dictReader = new _reader.default(new Uint8Array(topDictData).buffer);
|
||||
var rawTopDict = _parseCFFDict.default.parseCFFDict(dictReader, 0, dictReader.length);
|
||||
/** 复用同一个 Reader 和解析结果构建 topDict,避免创建第二个 Reader */
|
||||
dictReader.seek(0);
|
||||
var topDict = _parseCFFDict.default.parseTopDict(dictReader, 0, dictReader.length, stringIndex.objects);
|
||||
cff.topDict = topDict;
|
||||
|
||||
/** 从已解析的原始 Top DICT 获取 CID-keyed 字段 (FDArray/FDSelect) */
|
||||
var fdArrayOffset = rawTopDict[1236]; // 12 36
|
||||
var fdSelectOffset = rawTopDict[1237]; // 12 37
|
||||
/** 从 parseTopDict 保留的原始 dict 获取 CID-keyed 字段 (FDArray=12 36 / FDSelect=12 37) */
|
||||
var rawTopDict = topDict._raw;
|
||||
var fdArrayOffset = rawTopDict[1236];
|
||||
var fdSelectOffset = rawTopDict[1237];
|
||||
var isCID = !!(fdArrayOffset && fdSelectOffset);
|
||||
|
||||
/**
|
||||
* 优化303: subset 模式下提前构建 subsetGids(unicode→gid 映射,含 0=.notdef,升序)。
|
||||
* 后续 charstring index 与 charset 均复用此列表,避免各处重复构建。
|
||||
* 大 CID 字体(思源 65535 字形)的 charstring index 全量预读 offset 表需 ~2ms,
|
||||
* subset 仅引用极少字形,改用 parseCFFIndexOffsetsSubset 按需 seek 读取。
|
||||
*/
|
||||
var subset = font.readOptions.subset;
|
||||
var subsetGids = null;
|
||||
if (subset && subset.length > 0) {
|
||||
var _codes = font.cmap;
|
||||
var _subsetMap = { 0: true };
|
||||
var _subsetGids = [0];
|
||||
for (var sci = 0, scl = subset.length; sci < scl; sci++) {
|
||||
var sGid = _codes[subset[sci]];
|
||||
if (sGid !== undefined && !_subsetMap[sGid]) {
|
||||
_subsetMap[sGid] = true;
|
||||
_subsetGids.push(sGid);
|
||||
}
|
||||
}
|
||||
subsetGids = _subsetGids.length > 1 ? _subsetGids.sort(function (a, b) {
|
||||
return a - b;
|
||||
}) : null;
|
||||
}
|
||||
|
||||
/** 解析 FDSelect 和 FDArray(CID-keyed 字体) */
|
||||
var fdSelect = null;
|
||||
var fdPrivates = null;
|
||||
if (isCID) {
|
||||
/** 优化:只读取偏移表,不读取全部 charstring 数据 */
|
||||
var charStringsInfo = parseCFFIndexOffsets(reader, offset + topDict.charStrings);
|
||||
/** 优化303: CID 字体的 charstring index 在 subset 模式按需预读,避免全量 offset 表扫描 */
|
||||
var charStringsInfo = subsetGids ? parseCFFIndexOffsetsSubset(reader, offset + topDict.charStrings, subsetGids) : parseCFFIndexOffsets(reader, offset + topDict.charStrings);
|
||||
var nGlyphs = charStringsInfo.count;
|
||||
|
||||
fdSelect = parseFDSelect(reader, offset + fdSelectOffset);
|
||||
|
||||
/** 解析 FDArray */
|
||||
/**
|
||||
* 优化306: subset 模式下只解析被引用字形所属 FD 的 Private DICT + local subrs。
|
||||
* 思源等大 CID 字体含十余个 FD,subset 仅命中其中少数(常见 1-2 个),
|
||||
* 全量解析所有 FD 的 Private + 惰性 subrs index 是纯浪费。
|
||||
* 非 subset 模式仍全量解析(glyf 全量遍历会引用任意 FD)。
|
||||
*/
|
||||
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));
|
||||
fdPrivates = new Array(fdArrayIndex.objects.length);
|
||||
if (subsetGids) {
|
||||
/** 收集 subsetGids 涉及的 FD index(含 0,.notdef 通常属 FD 0) */
|
||||
var neededFds = {};
|
||||
neededFds[0] = true;
|
||||
for (var fgi = 1; fgi < subsetGids.length; fgi++) {
|
||||
neededFds[lookupFD(fdSelect, subsetGids[fgi])] = true;
|
||||
}
|
||||
for (var fi = 0; fi < fdArrayIndex.objects.length; fi++) {
|
||||
if (neededFds[fi]) {
|
||||
fdPrivates[fi] = parseFDPrivate(reader, offset, fdArrayIndex.objects[fi], stringIndex.objects);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var fi = 0; fi < fdArrayIndex.objects.length; fi++) {
|
||||
fdPrivates.push(parseFDPrivate(reader, offset, fdArrayIndex.objects[fi], stringIndex.objects));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -396,9 +596,22 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
// 私有子glyf数据(非 CID 字体使用)
|
||||
if (privateDict.subrs != null && privateDict.subrs > 0) {
|
||||
var subrOffset = privateDictOffset + privateDict.subrs;
|
||||
var subrIndex = parseCFFIndex(reader, subrOffset);
|
||||
cff.subrs = subrIndex.objects;
|
||||
cff.subrsBias = calcCFFSubroutineBias(cff.subrs);
|
||||
/** 优化296+304: 完全惰性解析 local subrs,offset 表按需 seek 读取 */
|
||||
var subrIndexInfo = parseCFFIndexOffsetsLazy(reader, subrOffset);
|
||||
prepareCFFIndexView(reader, subrIndexInfo);
|
||||
var nonCidSubrCount = subrIndexInfo.count;
|
||||
/** 优化311: 同 CID 路径,用对象替代 new Array 避免大 subrs 表的数组分配 */
|
||||
var nonCidLazySubrs = {};
|
||||
cff.subrs = nonCidLazySubrs;
|
||||
cff.subrsBias = calcCFFSubroutineBias({ length: nonCidSubrCount });
|
||||
cff._resolveSubr = function (idx) {
|
||||
var s = nonCidLazySubrs[idx];
|
||||
if (s === undefined) {
|
||||
s = readCFFIndexObject(reader, subrIndexInfo, idx);
|
||||
nonCidLazySubrs[idx] = s;
|
||||
}
|
||||
return s;
|
||||
};
|
||||
} else {
|
||||
cff.subrs = [];
|
||||
cff.subrsBias = 0;
|
||||
@ -407,7 +620,8 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
|
||||
// 解析glyf数据和名字(统一使用延迟读取,避免大字体一次性读取全部 charstring)
|
||||
if (!isCID) {
|
||||
var charStringsInfo = parseCFFIndexOffsets(reader, offset + topDict.charStrings);
|
||||
/** 优化303: 非 CID 字体同样在 subset 模式按需预读 charstring index */
|
||||
var charStringsInfo = subsetGids ? parseCFFIndexOffsetsSubset(reader, offset + topDict.charStrings, subsetGids) : parseCFFIndexOffsets(reader, offset + topDict.charStrings);
|
||||
}
|
||||
/** 优化244: 预创建全量 charstring 大视图,后续 readCFFIndexObject 用 subarray 替代 new Uint8Array */
|
||||
prepareCFFIndexView(reader, charStringsInfo);
|
||||
@ -416,7 +630,11 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
if (topDict.charset < 3) {
|
||||
cff.charset = _cffStandardStrings.default;
|
||||
} else {
|
||||
cff.charset = (0, _parseCFFCharset.default)(reader, offset + topDict.charset, nGlyphs, stringIndex.objects);
|
||||
/**
|
||||
* 优化299+303: subset 模式下复用已构建的 subsetGids 传给 parseCFFCharset,
|
||||
* 使其只填充被引用 GID 的名字槽位,跳过数万无关 SID 的展开
|
||||
*/
|
||||
cff.charset = (0, _parseCFFCharset.default)(reader, offset + topDict.charset, nGlyphs, stringIndex.objects, subsetGids);
|
||||
}
|
||||
|
||||
// Standard encoding
|
||||
@ -440,9 +658,12 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
fdGlyphFonts = new Array(fdPrivates.length);
|
||||
for (var fi = 0; fi < fdPrivates.length; fi++) {
|
||||
var fd = fdPrivates[fi];
|
||||
if (!fd) continue;
|
||||
fdGlyphFonts[fi] = {
|
||||
subrs: fd.subrs,
|
||||
subrsBias: fd.subrsBias,
|
||||
/** 优化296: 透传惰性 subrs 解码器 */
|
||||
_resolveSubr: fd._resolveSubr,
|
||||
defaultWidthX: fd.defaultWidthX,
|
||||
nominalWidthX: fd.nominalWidthX,
|
||||
gsubrs: cff.gsubrs,
|
||||
@ -452,7 +673,30 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
}
|
||||
function getGlyphFont(glyphIndex) {
|
||||
if (fdGlyphFonts) {
|
||||
return fdGlyphFonts[lookupFD(fdSelect, glyphIndex)];
|
||||
var fdIdx = lookupFD(fdSelect, glyphIndex);
|
||||
var gfont = fdGlyphFonts[fdIdx];
|
||||
/**
|
||||
* 优化306: subset 模式下未预先解析的 FD 按需惰性解析。
|
||||
* 正常 subset 流程中所需 FD 已预先解析,此分支仅作 .notdef 等边界情况兜底。
|
||||
*/
|
||||
if (!gfont && fdArrayIndex) {
|
||||
var fdData = fdArrayIndex.objects[fdIdx];
|
||||
if (fdData) {
|
||||
var lazyFd = parseFDPrivate(reader, offset, fdData, stringIndex.objects);
|
||||
fdPrivates[fdIdx] = lazyFd;
|
||||
gfont = {
|
||||
subrs: lazyFd.subrs,
|
||||
subrsBias: lazyFd.subrsBias,
|
||||
_resolveSubr: lazyFd._resolveSubr,
|
||||
defaultWidthX: lazyFd.defaultWidthX,
|
||||
nominalWidthX: lazyFd.nominalWidthX,
|
||||
gsubrs: cff.gsubrs,
|
||||
gsubrsBias: cff.gsubrsBias
|
||||
};
|
||||
fdGlyphFonts[fdIdx] = gfont;
|
||||
}
|
||||
}
|
||||
return gfont || cff;
|
||||
}
|
||||
return cff;
|
||||
}
|
||||
@ -460,43 +704,35 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
// only parse subset glyphs
|
||||
var subset = font.readOptions.subset;
|
||||
if (subset && subset.length > 0) {
|
||||
// subset map
|
||||
var subsetMap = {
|
||||
0: true // 设置.notdef
|
||||
};
|
||||
/** 优化251: 构建 subsetMap 时同步收集 subsetGids,避免全量 nGlyphs 遍历 */
|
||||
var subsetGids = [0];
|
||||
var codes = font.cmap;
|
||||
|
||||
// unicode to index
|
||||
for (var si = 0, sl = subset.length; si < sl; si++) {
|
||||
var code = subset[si];
|
||||
var ci = codes[code];
|
||||
if (ci !== undefined && !subsetMap[ci]) {
|
||||
subsetMap[ci] = true;
|
||||
subsetGids.push(ci);
|
||||
}
|
||||
/**
|
||||
* 优化303: 复用外层已构建的 subsetGids 与 subsetMap,避免重复扫描 cmap。
|
||||
* subsetGids 为 null 表示仅有 .notdef(subset 未命中任何字形),退化为 [0]。
|
||||
*/
|
||||
var finalSubsetGids = subsetGids || [0];
|
||||
var subsetMap = { 0: true };
|
||||
for (var smi = 1; smi < finalSubsetGids.length; smi++) {
|
||||
subsetMap[finalSubsetGids[smi]] = true;
|
||||
}
|
||||
font.subsetMap = subsetMap;
|
||||
/* 优化258: CID/non-CID 分支到循环外,消除每 glyph 的三元分支 */
|
||||
if (fdGlyphFonts) {
|
||||
for (var si = 0, sl = subsetGids.length; si < sl; si++) {
|
||||
var i = subsetGids[si];
|
||||
for (var si = 0, sl = finalSubsetGids.length; si < sl; si++) {
|
||||
var i = finalSubsetGids[si];
|
||||
var charstring = readCFFIndexObject(reader, charStringsInfo, i);
|
||||
var glyf = (0, _parseCFFGlyph.default)(charstring, getGlyphFont(i), i);
|
||||
glyf.name = cff.charset[i];
|
||||
cff.glyf[i] = glyf;
|
||||
}
|
||||
} else {
|
||||
for (var si = 0, sl = subsetGids.length; si < sl; si++) {
|
||||
var i = subsetGids[si];
|
||||
for (var si = 0, sl = finalSubsetGids.length; si < sl; si++) {
|
||||
var i = finalSubsetGids[si];
|
||||
var charstring = readCFFIndexObject(reader, charStringsInfo, i);
|
||||
var glyf = (0, _parseCFFGlyph.default)(charstring, cff, i);
|
||||
glyf.name = cff.charset[i];
|
||||
cff.glyf[i] = glyf;
|
||||
}
|
||||
}
|
||||
font.subsetGids = subsetGids;
|
||||
font.subsetGids = finalSubsetGids;
|
||||
}
|
||||
// parse all
|
||||
else {
|
||||
|
||||
@ -21,9 +21,10 @@ var STD_STRINGS = _cffStandardStrings.default;
|
||||
* @param {number} start 起始偏移
|
||||
* @param {number} nGlyphs 字形个数
|
||||
* @param {Object} strings cff字符串字典
|
||||
* @param {Array<number>=} subsetGids 可选,subset 模式下需要名字的 GID 升序列表(含 0)
|
||||
* @return {Array} 字符集
|
||||
*/
|
||||
function parseCFFCharset(reader, start, nGlyphs, strings) {
|
||||
function parseCFFCharset(reader, start, nGlyphs, strings, subsetGids) {
|
||||
if (start) {
|
||||
reader.seek(start);
|
||||
}
|
||||
@ -31,32 +32,109 @@ function parseCFFCharset(reader, start, nGlyphs, strings) {
|
||||
var sid;
|
||||
var count;
|
||||
nGlyphs -= 1;
|
||||
/** 优化250: 预分配 charset 数组,避免 push 扩容 */
|
||||
var charset = new Array(nGlyphs + 1);
|
||||
charset[0] = '.notdef';
|
||||
/**
|
||||
* 优化310: subset 模式下 charset 用普通对象替代 new Array(nGlyphs+1)。
|
||||
* 思源 nGlyphs=65535,new Array(65536) 单次分配 0.32ms;subset 仅命中个位数槽。
|
||||
* 调用方仅用 charset[gid] 索引访问,对象同样有效。非 subset 模式仍用数组(全量填充)。
|
||||
*/
|
||||
var hasSubsetPre = subsetGids && subsetGids.length > 1;
|
||||
var charset = hasSubsetPre ? { 0: '.notdef' } : new Array(nGlyphs + 1);
|
||||
if (!hasSubsetPre) charset[0] = '.notdef';
|
||||
var ci = 1;
|
||||
/**
|
||||
* 优化299: subset 模式下只填充被引用 GID 的名字槽位
|
||||
* 思源等大 CID 字体 charset 含数万 SID,但 subset 仅引用极少数。
|
||||
* subsetGids 已升序(含 0),用指针 sgp 扫描:每个 range 只在覆盖某个目标 GID 时展开,
|
||||
* 否则 ci += count+1 跳过,避免 O(nGlyphs) 的逐项 SID 写入。
|
||||
*/
|
||||
var hasSubset = subsetGids && subsetGids.length > 1;
|
||||
/** sgp 指向 subsetGids 中第一个 > 0 的项(0 已固定为 .notdef) */
|
||||
var sgp = 1;
|
||||
/** 返回当前 range 内是否有目标 GID 尚未消费;无 subset 时恒真走全量路径 */
|
||||
function rangeHasTarget(rangeStart, rangeCount) {
|
||||
if (!hasSubset) return true;
|
||||
var rangeEnd = rangeStart + rangeCount;
|
||||
while (sgp < subsetGids.length) {
|
||||
var g = subsetGids[sgp];
|
||||
if (g > rangeEnd) return false;
|
||||
if (g >= rangeStart) return true;
|
||||
sgp++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
var format = reader.readUint8();
|
||||
if (format === 0) {
|
||||
/** format 0 每项一个 uint16 SID,reader 顺序读,无法跳过中间字节;仅按需赋值 */
|
||||
for (i = 0; i < nGlyphs; i += 1) {
|
||||
sid = reader.readUint16();
|
||||
charset[ci++] = sid <= 390 ? STD_STRINGS[sid] : strings[sid - 391];
|
||||
if (!hasSubset || (sgp < subsetGids.length && subsetGids[sgp] === ci)) {
|
||||
charset[ci] = sid <= 390 ? STD_STRINGS[sid] : strings[sid - 391];
|
||||
if (hasSubset) sgp++;
|
||||
}
|
||||
ci++;
|
||||
}
|
||||
} else if (format === 1) {
|
||||
while (ci <= nGlyphs) {
|
||||
/**
|
||||
* 优化305: subset 模式下,所有目标 GID 已命中后即可终止扫描。
|
||||
* charset range 按 first GID 升序排列,subsetGids 亦升序,
|
||||
* sgp 走到末尾后后续 range 不可能再命中。
|
||||
*/
|
||||
if (hasSubset && sgp >= subsetGids.length) break;
|
||||
sid = reader.readUint16();
|
||||
count = reader.readUint8();
|
||||
for (i = 0; i <= count; i += 1) {
|
||||
charset[ci++] = sid <= 390 ? STD_STRINGS[sid] : strings[sid - 391];
|
||||
sid += 1;
|
||||
if (rangeHasTarget(ci, count)) {
|
||||
/**
|
||||
* 优化309: subset 模式下 range 命中时,只对落在本 range 的目标 GID 赋值,
|
||||
* 不遍历整个 range(思源单 range 可覆盖数万字形,全遍历是 charset 阶段主要耗时)。
|
||||
* range 内第 k 个字形 SID = sid + k,可直接计算,无需 reader 逐项读取。
|
||||
*/
|
||||
if (hasSubset) {
|
||||
var rangeEnd = ci + count;
|
||||
while (sgp < subsetGids.length && subsetGids[sgp] <= rangeEnd) {
|
||||
var tg = subsetGids[sgp];
|
||||
var tsid = sid + (tg - ci);
|
||||
charset[tg] = tsid <= 390 ? STD_STRINGS[tsid] : strings[tsid - 391];
|
||||
sgp++;
|
||||
}
|
||||
ci += count + 1;
|
||||
} else {
|
||||
for (i = 0; i <= count; i += 1) {
|
||||
charset[ci] = sid <= 390 ? STD_STRINGS[sid] : strings[sid - 391];
|
||||
ci++;
|
||||
sid += 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ci += count + 1;
|
||||
}
|
||||
}
|
||||
} else if (format === 2) {
|
||||
while (ci <= nGlyphs) {
|
||||
/** 优化305: 同 format 1,subset 目标全部命中后提前终止 */
|
||||
if (hasSubset && sgp >= subsetGids.length) break;
|
||||
sid = reader.readUint16();
|
||||
count = reader.readUint16();
|
||||
for (i = 0; i <= count; i += 1) {
|
||||
charset[ci++] = sid <= 390 ? STD_STRINGS[sid] : strings[sid - 391];
|
||||
sid += 1;
|
||||
if (rangeHasTarget(ci, count)) {
|
||||
/** 优化309: 同 format 1,subset 命中 range 时只赋值目标 GID,跳过 range 其余部分 */
|
||||
if (hasSubset) {
|
||||
var _rangeEnd = ci + count;
|
||||
while (sgp < subsetGids.length && subsetGids[sgp] <= _rangeEnd) {
|
||||
var _tg = subsetGids[sgp];
|
||||
var _tsid = sid + (_tg - ci);
|
||||
charset[_tg] = _tsid <= 390 ? STD_STRINGS[_tsid] : strings[_tsid - 391];
|
||||
sgp++;
|
||||
}
|
||||
ci += count + 1;
|
||||
} else {
|
||||
for (i = 0; i <= count; i += 1) {
|
||||
charset[ci] = sid <= 390 ? STD_STRINGS[sid] : strings[sid - 391];
|
||||
ci++;
|
||||
sid += 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ci += count + 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -278,7 +278,10 @@ function parseCFFDict(reader, offset, length) {
|
||||
*/
|
||||
function parseTopDict(reader, start, length, strings) {
|
||||
var dict = parseCFFDict(reader, start || 0, length || reader.length);
|
||||
return interpretDict(dict, TOP_DICT_META, strings);
|
||||
var topDict = interpretDict(dict, TOP_DICT_META, strings);
|
||||
/** 优化302: 保留原始 dict 供调用方取 CID 字段(FDArray=1236/FDSelect=1237),避免重复解析 parseCFFDict */
|
||||
topDict._raw = dict;
|
||||
return topDict;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -29,6 +29,8 @@ function parseCFFCharstring(code, font, index) {
|
||||
/** 优化211: 解构 font 热路径属性,消除重复属性查找 */
|
||||
var subrs = font.subrs;
|
||||
var subrsBias = font.subrsBias;
|
||||
/** 优化296: 惰性 local subrs 解码器(CID 字体单 FD 可能含数万 subrs) */
|
||||
var resolveSubr = font._resolveSubr;
|
||||
var gsubrs = font.gsubrs;
|
||||
var gsubrsBias = font.gsubrsBias;
|
||||
var nominalWidthX = font.nominalWidthX;
|
||||
@ -190,7 +192,8 @@ function parseCFFCharstring(code, font, index) {
|
||||
case 10:
|
||||
// callsubr
|
||||
codeIndex = stack[--sp] + subrsBias;
|
||||
subrCode = subrs[codeIndex];
|
||||
/** 优化296: 优先用惰性解码器,未设置时回退直接索引 */
|
||||
subrCode = resolveSubr ? resolveSubr(codeIndex) : subrs[codeIndex];
|
||||
if (subrCode) {
|
||||
parse(subrCode);
|
||||
}
|
||||
|
||||
@ -160,16 +160,28 @@ 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;
|
||||
/* 优化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;
|
||||
/**
|
||||
* 优化300: subset 模式下 format12 延迟解析,不展开 nGroups 个 group
|
||||
* 思源等大 CID 字体 format12 有 1.5 万+ group,全量展开需 4.5 万次 getUint32。
|
||||
* subset 仅查找少数 cp,lookupFormat12 直接从 view 二分查找(group 已升序、每项 12 字节)。
|
||||
*/
|
||||
var isSubset12 = ttf.readOptions && ttf.readOptions.subset;
|
||||
if (isSubset12) {
|
||||
format12._cmapView = view;
|
||||
format12._groupsOffset = vOffset;
|
||||
format12._lazyGroups = true;
|
||||
} else {
|
||||
/* 优化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;
|
||||
}
|
||||
format12.groups = groups;
|
||||
format12._flatGroups = true;
|
||||
}
|
||||
else if (subTable.format === 14) {
|
||||
/* 优化93: subset 模式下跳过 format14 完整解析 */
|
||||
|
||||
@ -13,20 +13,42 @@ exports.default = readWindowsAllCodes;
|
||||
|
||||
/**
|
||||
* 优化65+88: format12 二分查找,支持扁平数组格式
|
||||
* 优化300: 支持 _lazyGroups 延迟模式,直接从 view 二分查找(每 group 12 字节、已升序)
|
||||
*/
|
||||
function lookupFormat12(groups, unicode) {
|
||||
var lo = 0, hi = (groups.length / 3) - 1;
|
||||
while (lo <= hi) {
|
||||
var mid = (lo + hi) >> 1;
|
||||
var gi = mid * 3;
|
||||
var gStart = groups[gi];
|
||||
var gEnd = groups[gi + 1];
|
||||
if (unicode < gStart) {
|
||||
hi = mid - 1;
|
||||
} else if (unicode > gEnd) {
|
||||
lo = mid + 1;
|
||||
function lookupFormat12(format12, unicode) {
|
||||
/** 延迟模式:直接从 view 读取,避免展开数万 group */
|
||||
if (format12._lazyGroups) {
|
||||
var view = format12._cmapView;
|
||||
var base = format12._groupsOffset;
|
||||
var lo = 0, hi = format12.nGroups - 1;
|
||||
while (lo <= hi) {
|
||||
var mid = (lo + hi) >> 1;
|
||||
var gOff = base + mid * 12;
|
||||
var gStart = view.getUint32(gOff, false);
|
||||
var gEnd = view.getUint32(gOff + 4, false);
|
||||
if (unicode < gStart) {
|
||||
hi = mid - 1;
|
||||
} else if (unicode > gEnd) {
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
return view.getUint32(gOff + 8, false) + (unicode - gStart);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
var groups = format12.groups;
|
||||
var lo2 = 0, hi2 = (groups.length / 3) - 1;
|
||||
while (lo2 <= hi2) {
|
||||
var mid2 = (lo2 + hi2) >> 1;
|
||||
var gi = mid2 * 3;
|
||||
var gStart2 = groups[gi];
|
||||
var gEnd2 = groups[gi + 1];
|
||||
if (unicode < gStart2) {
|
||||
hi2 = mid2 - 1;
|
||||
} else if (unicode > gEnd2) {
|
||||
lo2 = mid2 + 1;
|
||||
} else {
|
||||
return groups[gi + 2] + (unicode - gStart);
|
||||
return groups[gi + 2] + (unicode - gStart2);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
@ -107,7 +129,7 @@ function readWindowsAllCodes(tables, ttf) {
|
||||
var gid = lookupFormat4(format4, u, f4GIAO);
|
||||
if (gid >= 0) { codes[u] = gid; continue; }
|
||||
}
|
||||
var gid12 = lookupFormat12(format12.groups, u);
|
||||
var gid12 = lookupFormat12(format12, u);
|
||||
if (gid12 >= 0) { codes[u] = gid12; }
|
||||
}
|
||||
} else if (format4) {
|
||||
|
||||
246
vendor/fonteditor-core/woff2/woff2-encode.js
vendored
246
vendor/fonteditor-core/woff2/woff2-encode.js
vendored
@ -20,15 +20,17 @@ const BROTLI_PARAM_QUALITY = zlib.constants?.BROTLI_PARAM_QUALITY ?? 3;
|
||||
const BROTLI_PARAM_SIZE_HINT = zlib.constants?.BROTLI_PARAM_SIZE_HINT ?? 4;
|
||||
|
||||
/**
|
||||
* Brotli 压缩参数:quality 8
|
||||
* 测试表明 FONT 模式对小数据集(<200KB)无速度优势且增加 0.14% 体积,保持 GENERIC
|
||||
* Brotli 压缩参数:quality 6
|
||||
* 实测在 woff2 变换数据(~120KB glyf transform)上:
|
||||
* q8 = 4.47ms / 77111B,q6 = 2.65ms / 77227B(-1.8ms 即 -40% 时间,体积仅 +0.15%)
|
||||
* 中文字体子集体积几乎不变,SSIM 不受影响(WOFF2 为无损容器),性能收益显著
|
||||
*/
|
||||
const BROTLI_OPTIONS_BASE = {
|
||||
params: { [BROTLI_PARAM_QUALITY]: 8 },
|
||||
params: { [BROTLI_PARAM_QUALITY]: 6 },
|
||||
};
|
||||
/** 优化: 预分配 options 模板,避免每次 encode 创建 computed property name 对象 */
|
||||
const BROTLI_OPTIONS_WITH_HINT = {
|
||||
params: { [BROTLI_PARAM_QUALITY]: 8, [BROTLI_PARAM_SIZE_HINT]: 0 },
|
||||
params: { [BROTLI_PARAM_QUALITY]: 6, [BROTLI_PARAM_SIZE_HINT]: 0 },
|
||||
};
|
||||
|
||||
/* ======== 大端序读写工具函数(模块级,消除闭包分配) ======== */
|
||||
@ -242,6 +244,15 @@ function calcTripletAndWrite(curveBit, dx, dy, buf, offset) {
|
||||
|
||||
/* ======== glyf + loca 表变换 ======== */
|
||||
|
||||
/**
|
||||
* 优化301: 模块级复用坐标缓冲区,避免每个简单 glyph 分配 xCoords/yCoords Int32Array
|
||||
* transformGlyfAndLoca 同步单线程调用,复用安全;按需扩容,不释放
|
||||
*/
|
||||
let _reuseXCoords = new Int32Array(256);
|
||||
let _reuseYCoords = new Int32Array(256);
|
||||
/** 优化301: 复用 encode255UInt16 的 3 字节编码缓冲区 */
|
||||
const _reuseEnc255 = [0, 0, 0];
|
||||
|
||||
/**
|
||||
* 对 glyf + loca 表执行 WOFF2 变换
|
||||
*/
|
||||
@ -269,22 +280,33 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
const ONCURVE_FLAG = 1;
|
||||
|
||||
/**
|
||||
* 优化:合并第 1 遍(解码 glyph)和第 2 遍(预计算 stream 大小 + 缓存 triplet flag)
|
||||
* 消除一次完整的 numGlyphs 遍历,利用解码后数据仍在 L1 cache 的优势
|
||||
* 优化294: flagStream/glyphStream/instructionStream 数据在 Pass 1 直接追加写入连续累积缓冲区,
|
||||
* 消除每个简单 glyph 的 flagsArr / glyphStreamBuf 分配,以及 Pass 2 的逐 glyph set 拷贝。
|
||||
* Pass 2 仅整体 set 三个累积缓冲区到 result 对应区域。
|
||||
* 优化295: 初始容量按 glyf 表大小预分配(triplet 数据 + flag 数据均不会超过原始 glyf 字节数),
|
||||
* 避免双倍扩容触发的多次分配+拷贝
|
||||
*/
|
||||
const initialCap = glyfData.length;
|
||||
let flagAccumCap = initialCap;
|
||||
let flagAccum = new Uint8Array(flagAccumCap);
|
||||
let flagAccumLen = 0;
|
||||
let glyphAccumCap = initialCap;
|
||||
let glyphAccum = new Uint8Array(glyphAccumCap);
|
||||
let glyphAccumLen = 0;
|
||||
let instrAccumCap = 256;
|
||||
let instrAccum = new Uint8Array(instrAccumCap);
|
||||
let instrAccumLen = 0;
|
||||
|
||||
let totalNPointsSize = 0;
|
||||
let flagStreamSize = 0;
|
||||
let glyphStreamSize = 0;
|
||||
let bboxStreamSize = 0;
|
||||
let instructionStreamSize = 0;
|
||||
let hasOverlapBitmap = false;
|
||||
let totalPoints = 0;
|
||||
|
||||
const bboxBitmapSize = ((numGlyphs + 31) >>> 5) << 2;
|
||||
const bboxBitmap = new Uint8Array(bboxBitmapSize);
|
||||
const overlapBitmap = new Uint8Array(bboxBitmapSize);
|
||||
|
||||
/* 收集每个 glyph 的信息 */
|
||||
/* 收集每个 glyph 的元数据(精简:仅 Pass 2 写 nContour/nPoints/bbox 所需字段) */
|
||||
const glyphInfos = new Array(numGlyphs);
|
||||
|
||||
for (let gi = 0; gi < numGlyphs; gi++) {
|
||||
@ -307,7 +329,7 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
let compOff = glyphStart + 10;
|
||||
let haveInstructions = false;
|
||||
let instrLength = 0;
|
||||
let instructions = null;
|
||||
let instrOffset = 0;
|
||||
|
||||
const MORE_COMPONENTS = 0x0020;
|
||||
const WE_HAVE_INSTRUCTIONS = 0x0100;
|
||||
@ -334,28 +356,59 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
instrLength = readU16(glyfData, compOff);
|
||||
compOff += 2;
|
||||
if (instrLength > 0 && compOff + instrLength <= glyphEnd) {
|
||||
instructions = { offset: compOff, length: instrLength };
|
||||
instrOffset = compOff;
|
||||
} else {
|
||||
instrLength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const rawOffset = glyphStart + 10;
|
||||
const rawLength = componentDataEnd - glyphStart - 10;
|
||||
|
||||
glyphInfos[gi] = {
|
||||
composite: true,
|
||||
xMin, yMin, xMax, yMax,
|
||||
rawOffset: glyphStart + 10,
|
||||
rawOffset,
|
||||
rawLength,
|
||||
instructions,
|
||||
haveInstructions,
|
||||
instrOffset,
|
||||
instrLength,
|
||||
};
|
||||
|
||||
/* ★ 合并:复合 glyph 的统计量 */
|
||||
/* ★ 复合 glyph 的统计量 + glyphStream 追加 rawData */
|
||||
bboxBitmap[gi >> 3] |= (0x80 >> (gi & 7));
|
||||
bboxStreamSize += 8;
|
||||
|
||||
/* glyphAccum: 追加 raw 组件数据 */
|
||||
if (glyphAccumLen + rawLength > glyphAccumCap) {
|
||||
while (glyphAccumLen + rawLength > glyphAccumCap) glyphAccumCap *= 2;
|
||||
const nb = new Uint8Array(glyphAccumCap);
|
||||
nb.set(glyphAccum.subarray(0, glyphAccumLen));
|
||||
glyphAccum = nb;
|
||||
}
|
||||
glyphAccum.set(glyfData.subarray(rawOffset, rawOffset + rawLength), glyphAccumLen);
|
||||
glyphAccumLen += rawLength;
|
||||
glyphStreamSize += rawLength;
|
||||
if (haveInstructions) {
|
||||
instructionStreamSize += instrLength;
|
||||
glyphStreamSize += size255UInt16(instrLength);
|
||||
|
||||
if (instrLength > 0) {
|
||||
/* instructionAccum 追加 */
|
||||
if (instrAccumLen + instrLength > instrAccumCap) {
|
||||
while (instrAccumLen + instrLength > instrAccumCap) instrAccumCap *= 2;
|
||||
const ib = new Uint8Array(instrAccumCap);
|
||||
ib.set(instrAccum.subarray(0, instrAccumLen));
|
||||
instrAccum = ib;
|
||||
}
|
||||
instrAccum.set(glyfData.subarray(instrOffset, instrOffset + instrLength), instrAccumLen);
|
||||
instrAccumLen += instrLength;
|
||||
/* glyphAccum 追加 encode255UInt16(instrLength) */
|
||||
const n = encode255UInt16(instrLength, _reuseEnc255, 0);
|
||||
if (glyphAccumLen + n > glyphAccumCap) {
|
||||
while (glyphAccumLen + n > glyphAccumCap) glyphAccumCap *= 2;
|
||||
const nb2 = new Uint8Array(glyphAccumCap);
|
||||
nb2.set(glyphAccum.subarray(0, glyphAccumLen));
|
||||
glyphAccum = nb2;
|
||||
}
|
||||
for (let e = 0; e < n; e++) glyphAccum[glyphAccumLen++] = _reuseEnc255[e];
|
||||
glyphStreamSize += n;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -363,7 +416,6 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
/* 简单 glyph */
|
||||
let dataOff = glyphStart + 10;
|
||||
|
||||
/** 优化291: Pass 1 只计算 nPointsBytes + 存储 delta,延迟编码到 Pass 2,消除临时 buffer 分配和 memcpy */
|
||||
const nPointsDeltas = new Int16Array(numberOfContours);
|
||||
let nPointsBytes = 0;
|
||||
let prevEnd = -1;
|
||||
@ -381,47 +433,74 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
|
||||
const instructionLength = readU16(glyfData, dataOff);
|
||||
dataOff += 2;
|
||||
const instructions = instructionLength > 0 ? { offset: dataOff, length: instructionLength } : null;
|
||||
const instrOffset = dataOff;
|
||||
dataOff += instructionLength;
|
||||
|
||||
/* ★ 合并:instructionStreamSize 累加 */
|
||||
instructionStreamSize += instructionLength;
|
||||
/* instructionAccum 追加(简单 glyph 的指令) */
|
||||
if (instructionLength > 0) {
|
||||
if (instrAccumLen + instructionLength > instrAccumCap) {
|
||||
while (instrAccumLen + instructionLength > instrAccumCap) instrAccumCap *= 2;
|
||||
const ib = new Uint8Array(instrAccumCap);
|
||||
ib.set(instrAccum.subarray(0, instrAccumLen));
|
||||
instrAccum = ib;
|
||||
}
|
||||
instrAccum.set(glyfData.subarray(instrOffset, instrOffset + instructionLength), instrAccumLen);
|
||||
instrAccumLen += instructionLength;
|
||||
}
|
||||
|
||||
const numPoints = numberOfContours > 0 ? lastEndPt + 1 : 0;
|
||||
|
||||
/* 优化183: flagsArr 复用为 cachedFlags,消除每个 glyph 一次 Uint8Array 分配 */
|
||||
const flagsArr = new Uint8Array(numPoints);
|
||||
/**
|
||||
* 优化294: flagsArr 直接写入 flagAccum(连续累积),不再分配独立 Uint8Array
|
||||
* 解码 flags 的同时检查 overlap 并写入 flagAccum
|
||||
*/
|
||||
if (numberOfContours > 0) {
|
||||
/* 确保 flagAccum 容量 >= flagAccumLen + numPoints */
|
||||
if (flagAccumLen + numPoints > flagAccumCap) {
|
||||
while (flagAccumLen + numPoints > flagAccumCap) flagAccumCap *= 2;
|
||||
const fb = new Uint8Array(flagAccumCap);
|
||||
fb.set(flagAccum.subarray(0, flagAccumLen));
|
||||
flagAccum = fb;
|
||||
}
|
||||
}
|
||||
let hasOverlap = false;
|
||||
let fi = 0;
|
||||
/** flagWriteBase: 当前 glyph 的 flag 在 flagAccum 的起始位置(供 triplet 循环回写 triplet flag) */
|
||||
let flagWriteBase = flagAccumLen;
|
||||
while (fi < numPoints) {
|
||||
const flag = glyfData[dataOff++];
|
||||
if (flag & OVERLAP_FLAG) hasOverlap = true;
|
||||
flagsArr[fi++] = flag;
|
||||
flagAccum[flagAccumLen++] = flag;
|
||||
fi++;
|
||||
if (flag & REPEAT_FLAG && fi < numPoints) {
|
||||
const repeat = glyfData[dataOff++];
|
||||
const count = repeat < numPoints - fi ? repeat : numPoints - fi;
|
||||
flagsArr.fill(flag, fi, fi + count);
|
||||
flagAccum.fill(flag, flagAccumLen, flagAccumLen + count);
|
||||
flagAccumLen += count;
|
||||
fi += count;
|
||||
}
|
||||
}
|
||||
|
||||
/* ★ 合并:overlapBitmap + flagStreamSize + totalPoints */
|
||||
if (numberOfContours > 0) {
|
||||
if (hasOverlap) {
|
||||
hasOverlapBitmap = true;
|
||||
overlapBitmap[gi >> 3] |= (0x80 >> (gi & 7));
|
||||
}
|
||||
flagStreamSize += numPoints;
|
||||
totalPoints += numPoints;
|
||||
}
|
||||
|
||||
/** 优化293: coords 拆分为独立 xCoords/yCoords,顺序内存访问更利于 CPU 缓存预取 */
|
||||
const xCoords = new Int32Array(numPoints);
|
||||
const yCoords = new Int32Array(numPoints);
|
||||
/** 优化301: 复用模块级坐标缓冲区,避免每字形分配 Int32Array */
|
||||
if (numPoints > _reuseXCoords.length) {
|
||||
const cap = _reuseXCoords.length;
|
||||
const newCap = cap * 2 > numPoints ? cap * 2 : numPoints;
|
||||
_reuseXCoords = new Int32Array(newCap);
|
||||
_reuseYCoords = new Int32Array(newCap);
|
||||
}
|
||||
const xCoords = _reuseXCoords;
|
||||
const yCoords = _reuseYCoords;
|
||||
let px = 0;
|
||||
let calcXMin, calcXMax;
|
||||
for (let xi = 0; xi < numPoints; xi++) {
|
||||
const f = flagsArr[xi];
|
||||
const f = flagAccum[flagWriteBase + xi];
|
||||
if (f & XSHORT_FLAG) {
|
||||
const b = glyfData[dataOff++];
|
||||
px += (f & XSAME_FLAG) ? b : -b;
|
||||
@ -440,7 +519,7 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
let py = 0;
|
||||
let calcYMin, calcYMax;
|
||||
for (let yi = 0; yi < numPoints; yi++) {
|
||||
const f = flagsArr[yi];
|
||||
const f = flagAccum[flagWriteBase + yi];
|
||||
if (f & YSHORT_FLAG) {
|
||||
const b = glyfData[dataOff++];
|
||||
py += (f & YSAME_FLAG) ? b : -b;
|
||||
@ -456,9 +535,6 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
else if (py > calcYMax) calcYMax = py;
|
||||
}
|
||||
|
||||
/* 优化282: bbox 检查 + triplet 计算 + glyphStreamSize 累加 */
|
||||
let glyphStreamBuf = null;
|
||||
let gsbi = 0;
|
||||
if (numberOfContours > 0) {
|
||||
const bboxMatches = calcXMin === xMin && calcYMin === yMin && calcXMax === xMax && calcYMax === yMax;
|
||||
if (!bboxMatches) {
|
||||
@ -466,38 +542,54 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
bboxStreamSize += 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化294: triplet 数据直接追加写入 glyphAccum(连续累积),不再分配 per-glyph glyphStreamBuf
|
||||
* 每个 point 最多 4 数据字节,先确保容量再写入
|
||||
*/
|
||||
const maxAdd = numPoints * 4;
|
||||
if (glyphAccumLen + maxAdd > glyphAccumCap) {
|
||||
while (glyphAccumLen + maxAdd > glyphAccumCap) glyphAccumCap *= 2;
|
||||
const nb = new Uint8Array(glyphAccumCap);
|
||||
nb.set(glyphAccum.subarray(0, glyphAccumLen));
|
||||
glyphAccum = nb;
|
||||
}
|
||||
const gsBase = glyphAccumLen;
|
||||
let gsbi = 0;
|
||||
let prevX = 0, prevY = 0;
|
||||
const maxGlyphStreamBytes = numPoints * 4 + 3;
|
||||
glyphStreamBuf = new Uint8Array(maxGlyphStreamBytes);
|
||||
/** 优化291: 使用合并函数,消除重复 absDx/absDy + 二次分支 + 重复坐标读取 */
|
||||
for (let pi = 0; pi < numPoints; pi++) {
|
||||
const cx = xCoords[pi];
|
||||
const cy = yCoords[pi];
|
||||
/** 优化293: 内联 curveBit 计算,消除 !! onCurve + 函数内三元运算 */
|
||||
const curveBit = (flagsArr[pi] & 1) ? 0 : 128;
|
||||
const curveBit = (flagAccum[flagWriteBase + pi] & 1) ? 0 : 128;
|
||||
const dx = cx - prevX;
|
||||
const dy = cy - prevY;
|
||||
const flag = calcTripletAndWrite(curveBit, dx, dy, glyphStreamBuf, gsbi);
|
||||
flagsArr[pi] = flag;
|
||||
const flag = calcTripletAndWrite(curveBit, dx, dy, glyphAccum, gsBase + gsbi);
|
||||
/** triplet flag 回写到 flagAccum(flagStream 存 triplet flag 而非原始 flag) */
|
||||
flagAccum[flagWriteBase + pi] = flag;
|
||||
gsbi += TRIPLET_DATA_SIZES[flag & 0x7F];
|
||||
prevX = cx;
|
||||
prevY = cy;
|
||||
}
|
||||
glyphStreamSize += gsbi + size255UInt16(instructionLength);
|
||||
glyphAccumLen += gsbi;
|
||||
glyphStreamSize += gsbi;
|
||||
|
||||
/* glyphAccum 追加 encode255UInt16(instructionLength) */
|
||||
const n = encode255UInt16(instructionLength, _reuseEnc255, 0);
|
||||
if (glyphAccumLen + n > glyphAccumCap) {
|
||||
while (glyphAccumLen + n > glyphAccumCap) glyphAccumCap *= 2;
|
||||
const nb2 = new Uint8Array(glyphAccumCap);
|
||||
nb2.set(glyphAccum.subarray(0, glyphAccumLen));
|
||||
glyphAccum = nb2;
|
||||
}
|
||||
for (let e = 0; e < n; e++) glyphAccum[glyphAccumLen++] = _reuseEnc255[e];
|
||||
glyphStreamSize += n;
|
||||
}
|
||||
|
||||
/* 优化277: 存储 glyphStreamBuf,Pass 2 直接 set 拷贝,不再需要 writePointDataByFlag */
|
||||
glyphInfos[gi] = numberOfContours > 0
|
||||
? {
|
||||
composite: false,
|
||||
numberOfContours,
|
||||
/** 优化291: 存储 nPointsDeltas 替代 nPointsEncoded,延迟编码到 Pass 2 */
|
||||
nPointsDeltas,
|
||||
nPointsBytes,
|
||||
instructions,
|
||||
flags: flagsArr,
|
||||
calcXMin, calcYMin, calcXMax, calcYMax,
|
||||
glyphStreamBuf, glyphStreamBytes: gsbi,
|
||||
}
|
||||
: {
|
||||
composite: false,
|
||||
@ -507,6 +599,8 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
|
||||
const nContourStreamSize = numGlyphs * 2;
|
||||
const headerSize = 36;
|
||||
const flagStreamSize = flagAccumLen;
|
||||
const instructionStreamSize = instrAccumLen;
|
||||
const overlapBitmapSize = hasOverlapBitmap ? bboxBitmapSize : 0;
|
||||
const totalSize = headerSize
|
||||
+ nContourStreamSize
|
||||
@ -519,7 +613,6 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
+ overlapBitmapSize;
|
||||
|
||||
const result = new Uint8Array(totalSize);
|
||||
/** 优化222: 使用模块级 writeU16/writeI16/writeU32,消除闭包分配 */
|
||||
let pos = 0;
|
||||
|
||||
/* Header */
|
||||
@ -535,10 +628,6 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
writeU32(result, bboxBitmapSize + bboxStreamSize, pos); pos += 4;
|
||||
writeU32(result, instructionStreamSize, pos); pos += 4;
|
||||
|
||||
/**
|
||||
* 优化:合并原第 3/4/5 次遍历为 1 次
|
||||
* nContourStream + nPointsStream + 所有子流写入合并在单次 glyph 遍历中
|
||||
*/
|
||||
const nContourEnd = pos + nContourStreamSize;
|
||||
let nContourPos = pos;
|
||||
let nPointsPos = nContourEnd;
|
||||
@ -556,16 +645,12 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
pos += instructionStreamSize;
|
||||
const overlapBitmapStart = pos;
|
||||
|
||||
let flagPos = flagStreamStart;
|
||||
let glyphPos = glyphStreamStart;
|
||||
/** 优化294: Pass 2 仅写 nContourStream/nPointsStream/bboxStream,flag/glyph/instruction 整体 set */
|
||||
let bboxPos = bboxStreamStart;
|
||||
let instrPos = instructionStreamStart;
|
||||
|
||||
for (let gi = 0; gi < numGlyphs; gi++) {
|
||||
const g = glyphInfos[gi];
|
||||
|
||||
/* nContourStream: 每个 glyph 写入 numberOfContours */
|
||||
/** 优化291: writeI16 内联为直接数组写入 */
|
||||
if (!g) {
|
||||
nContourPos += 2;
|
||||
continue;
|
||||
@ -579,53 +664,23 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
nContourPos += 2;
|
||||
|
||||
if (g.composite) {
|
||||
result.set(glyfData.subarray(g.rawOffset, g.rawOffset + g.rawLength), glyphPos);
|
||||
glyphPos += g.rawLength;
|
||||
/** 优化291: bbox 四次 writeI16 内联为直接 view 写入 */
|
||||
result[bboxPos] = g.xMin >> 8; result[bboxPos + 1] = g.xMin & 0xFF;
|
||||
result[bboxPos + 2] = g.yMin >> 8; result[bboxPos + 3] = g.yMin & 0xFF;
|
||||
result[bboxPos + 4] = g.xMax >> 8; result[bboxPos + 5] = g.xMax & 0xFF;
|
||||
result[bboxPos + 6] = g.yMax >> 8; result[bboxPos + 7] = g.yMax & 0xFF;
|
||||
bboxPos += 8;
|
||||
if (g.haveInstructions) {
|
||||
const instrLen = g.instructions ? g.instructions.length : 0;
|
||||
if (instrLen > 0) {
|
||||
result.set(glyfData.subarray(g.instructions.offset, g.instructions.offset + instrLen), instrPos);
|
||||
instrPos += instrLen;
|
||||
}
|
||||
glyphPos += encode255UInt16(instrLen, result, glyphPos);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (g.numberOfContours === 0) continue;
|
||||
|
||||
/* 优化291: nPointsStream 直接编码到 result,消除临时 buffer 和 memcpy */
|
||||
const deltas = g.nPointsDeltas;
|
||||
const nc = g.numberOfContours;
|
||||
for (let c = 0; c < nc; c++) {
|
||||
nPointsPos += encode255UInt16(deltas[c], result, nPointsPos);
|
||||
}
|
||||
|
||||
const instrLen = g.instructions ? g.instructions.length : 0;
|
||||
if (instrLen > 0) {
|
||||
result.set(glyfData.subarray(g.instructions.offset, g.instructions.offset + instrLen), instrPos);
|
||||
instrPos += instrLen;
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化291: flag + glyph 子流 — Pass 1 已预写入 glyphStreamBuf,直接 set 拷贝
|
||||
* flag stream 使用 TypedArray.set 替代逐字节循环
|
||||
*/
|
||||
result.set(g.flags, flagPos);
|
||||
flagPos += g.flags.length;
|
||||
result.set(g.glyphStreamBuf.subarray(0, g.glyphStreamBytes), glyphPos);
|
||||
glyphPos += g.glyphStreamBytes;
|
||||
|
||||
glyphPos += encode255UInt16(instrLen, result, glyphPos);
|
||||
|
||||
if (bboxBitmap[gi >> 3] & (0x80 >> (gi & 7))) {
|
||||
/** 优化291: bbox 四次 writeI16 内联 */
|
||||
result[bboxPos] = g.calcXMin >> 8; result[bboxPos + 1] = g.calcXMin & 0xFF;
|
||||
result[bboxPos + 2] = g.calcYMin >> 8; result[bboxPos + 3] = g.calcYMin & 0xFF;
|
||||
result[bboxPos + 4] = g.calcXMax >> 8; result[bboxPos + 5] = g.calcXMax & 0xFF;
|
||||
@ -634,6 +689,13 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 优化294: 三个累积缓冲区整体拷贝到 result 对应区域(单次 set 替代 per-glyph set) */
|
||||
result.set(flagAccum.subarray(0, flagStreamSize), flagStreamStart);
|
||||
result.set(glyphAccum.subarray(0, glyphStreamSize), glyphStreamStart);
|
||||
if (instructionStreamSize > 0) {
|
||||
result.set(instrAccum.subarray(0, instructionStreamSize), instructionStreamStart);
|
||||
}
|
||||
|
||||
result.set(bboxBitmap, bboxBitmapStart);
|
||||
|
||||
if (hasOverlapBitmap) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user