mirror of
https://github.com/2234839/web-font.git
synced 2026-04-29 21:00:45 +08:00
实现 js 版 woff2 处理
This commit is contained in:
parent
e977cc9575
commit
161bafc02a
@ -23,11 +23,13 @@ export const createSubsetFont = (
|
||||
subset: codePoints,
|
||||
});
|
||||
|
||||
/** 优化字体(去冗余表、清理无用字形) */
|
||||
/**
|
||||
* 优化字体(去冗余表、清理无用字形)
|
||||
* subset 模式下 TTFReader.resolveGlyf 已完成 compound2simple,跳过
|
||||
* optimizettf 已设置 _unicodeSorted=true,sortGlyf 会直接返回
|
||||
*/
|
||||
export const optimizeFont = (font: ReturnType<typeof Font.create>) => {
|
||||
let optimized = font.optimize();
|
||||
optimized = optimized.compound2simple();
|
||||
optimized = optimized.sort();
|
||||
const optimized = font.optimize();
|
||||
return optimized;
|
||||
};
|
||||
|
||||
|
||||
9
vendor/fonteditor-core/lib/ttf/table/CFF.js
vendored
9
vendor/fonteditor-core/lib/ttf/table/CFF.js
vendored
@ -439,12 +439,11 @@ var _default = exports.default = _table.default.create('cff', [], {
|
||||
|
||||
// unicode to index — 用 Set 替代 indexOf 实现 O(1) 查找
|
||||
var subsetSet = {};
|
||||
/** 优化:合并 subsetSet 和 subsetMap 构建为单次遍历 */
|
||||
for (var si = 0, sl = subset.length; si < sl; si++) {
|
||||
subsetSet[subset[si]] = true;
|
||||
}
|
||||
/** 优化168: 用 subset 直接遍历替代 codes for...in,减少遍历量 */
|
||||
for (var si = 0, sl = subset.length; si < sl; si++) {
|
||||
var ci = codes[subset[si]];
|
||||
var code = subset[si];
|
||||
subsetSet[code] = true;
|
||||
var ci = codes[code];
|
||||
if (ci !== undefined) subsetMap[ci] = true;
|
||||
}
|
||||
font.subsetMap = subsetMap;
|
||||
|
||||
@ -140,11 +140,13 @@ function entriesToObject(entries) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
/** 优化:lookup 表提升到模块级,避免每次调用重新分配 */
|
||||
var FLOAT_LOOKUP = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-'];
|
||||
|
||||
/* eslint-disable no-constant-condition */
|
||||
function parseFloatOperand(reader) {
|
||||
var s = '';
|
||||
var eof = 15;
|
||||
var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-'];
|
||||
while (true) {
|
||||
var b = reader.readUint8();
|
||||
var n1 = b >> 4;
|
||||
@ -152,11 +154,11 @@ function parseFloatOperand(reader) {
|
||||
if (n1 === eof) {
|
||||
break;
|
||||
}
|
||||
s += lookup[n1];
|
||||
s += FLOAT_LOOKUP[n1];
|
||||
if (n2 === eof) {
|
||||
break;
|
||||
}
|
||||
s += lookup[n2];
|
||||
s += FLOAT_LOOKUP[n2];
|
||||
}
|
||||
return parseFloat(s);
|
||||
}
|
||||
|
||||
20
vendor/fonteditor-core/lib/ttf/table/glyf.js
vendored
20
vendor/fonteditor-core/lib/ttf/table/glyf.js
vendored
@ -42,11 +42,12 @@ var _default = exports.default = _table.default.create('glyf', [], {
|
||||
ttf.subsetGids = subsetGids;
|
||||
var parsedGlyfMap = {};
|
||||
|
||||
/* 优化43: for...in + for 循环替代 Object.keys + forEach */
|
||||
var travelsParse = function travels(sMap) {
|
||||
var newSubsetMap = {};
|
||||
for (var idx in sMap) {
|
||||
var index = +idx;
|
||||
/* 优化:迭代式广度优先遍历替代递归,消除 isEmptyObject 调用 */
|
||||
var queue = subsetGids;
|
||||
while (queue.length > 0) {
|
||||
var nextQueue = [];
|
||||
for (var qi = 0, ql = queue.length; qi < ql; qi++) {
|
||||
var index = queue[qi];
|
||||
parsedGlyfMap[index] = true;
|
||||
if (loca[index] === loca[index + 1]) {
|
||||
glyphs[index] = { contours: [] };
|
||||
@ -57,16 +58,13 @@ var _default = exports.default = _table.default.create('glyf', [], {
|
||||
var glyfs = glyphs[index].glyfs;
|
||||
for (var gi = 0, gl = glyfs.length; gi < gl; gi++) {
|
||||
if (!parsedGlyfMap[glyfs[gi].glyphIndex]) {
|
||||
newSubsetMap[glyfs[gi].glyphIndex] = true;
|
||||
nextQueue.push(glyfs[gi].glyphIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!(0, _lang.isEmptyObject)(newSubsetMap)) {
|
||||
travels(newSubsetMap);
|
||||
}
|
||||
};
|
||||
travelsParse(subsetMap);
|
||||
queue = nextQueue;
|
||||
}
|
||||
return glyphs;
|
||||
}
|
||||
|
||||
|
||||
@ -44,9 +44,9 @@ function parseSimpleGlyf(reader, glyf) {
|
||||
flags[fi++] = flag;
|
||||
if (flag & REPEAT && fi < numberOfCoordinates) {
|
||||
var repeat = view.getUint8(vOffset++);
|
||||
for (var j = 0; j < repeat && fi < numberOfCoordinates; j++) {
|
||||
flags[fi++] = flag;
|
||||
}
|
||||
var fillCount = repeat < numberOfCoordinates - fi ? repeat : numberOfCoordinates - fi;
|
||||
flags.fill(flag, fi, fi + fillCount);
|
||||
fi += fillCount;
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,20 +152,20 @@ function parseCompoundGlyf(reader, glyf) {
|
||||
g.useMyMetrics = !!(flags & USE_MY_METRICS);
|
||||
g.overlapCompound = !!(flags & OVERLAP_COMPOUND);
|
||||
g.transform = {
|
||||
a: Math.round(10000 * scaleX / 16384) / 10000,
|
||||
b: Math.round(10000 * scale01 / 16384) / 10000,
|
||||
c: Math.round(10000 * scale10 / 16384) / 10000,
|
||||
d: Math.round(10000 * scaleY / 16384) / 10000,
|
||||
a: Math.round(scaleX * 0.6103515625) / 10000,
|
||||
b: Math.round(scale01 * 0.6103515625) / 10000,
|
||||
c: Math.round(scale10 * 0.6103515625) / 10000,
|
||||
d: Math.round(scaleY * 0.6103515625) / 10000,
|
||||
e: arg1,
|
||||
f: arg2
|
||||
};
|
||||
} else {
|
||||
g.points = [arg1, arg2];
|
||||
g.transform = {
|
||||
a: Math.round(10000 * scaleX / 16384) / 10000,
|
||||
b: Math.round(10000 * scale01 / 16384) / 10000,
|
||||
c: Math.round(10000 * scale10 / 16384) / 10000,
|
||||
d: Math.round(10000 * scaleY / 16384) / 10000,
|
||||
a: Math.round(scaleX * 0.6103515625) / 10000,
|
||||
b: Math.round(scale01 * 0.6103515625) / 10000,
|
||||
c: Math.round(scale10 * 0.6103515625) / 10000,
|
||||
d: Math.round(scaleY * 0.6103515625) / 10000,
|
||||
e: 0,
|
||||
f: 0
|
||||
};
|
||||
|
||||
@ -24,9 +24,9 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) {
|
||||
var pre = glyf._precomputedGlyfSupport;
|
||||
glyfSupport.flags = pre.flags;
|
||||
/* 优化98: 预编码 buffer 直接传递 */
|
||||
if (pre.xEncoded) {
|
||||
glyfSupport.xEncoded = pre.xEncoded;
|
||||
glyfSupport.yEncoded = pre.yEncoded;
|
||||
if (pre.xBuf) {
|
||||
glyfSupport.xEncoded = pre.xBuf.subarray(0, pre.xLen);
|
||||
glyfSupport.yEncoded = pre.yBuf.subarray(0, pre.yLen);
|
||||
} else {
|
||||
glyfSupport.xCoord = pre.xCoord;
|
||||
glyfSupport.yCoord = pre.yCoord;
|
||||
|
||||
35
vendor/fonteditor-core/lib/ttf/table/head.js
vendored
35
vendor/fonteditor-core/lib/ttf/table/head.js
vendored
@ -13,6 +13,41 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
||||
*/
|
||||
var _default = exports.default = _table.default.create('head', [['version', _struct.default.Fixed], ['fontRevision', _struct.default.Fixed], ['checkSumAdjustment', _struct.default.Uint32], ['magickNumber', _struct.default.Uint32], ['flags', _struct.default.Uint16], ['unitsPerEm', _struct.default.Uint16], ['created', _struct.default.LongDateTime], ['modified', _struct.default.LongDateTime], ['xMin', _struct.default.Int16], ['yMin', _struct.default.Int16], ['xMax', _struct.default.Int16], ['yMax', _struct.default.Int16], ['macStyle', _struct.default.Uint16], ['lowestRecPPEM', _struct.default.Uint16], ['fontDirectionHint', _struct.default.Int16], ['indexToLocFormat', _struct.default.Int16], ['glyphDataFormat', _struct.default.Int16]], {
|
||||
size: function () { return 54; },
|
||||
/** 优化178: 全部内联 view 读取 54 字节,LongDateTime 存储为毫秒时间戳 */
|
||||
read: function (reader) {
|
||||
reader.seek(this.offset);
|
||||
var v = reader.view;
|
||||
var o = reader.offset;
|
||||
var base = -2082844800000;
|
||||
this.version = v.getInt32(o, false) / 65536; o += 4;
|
||||
this.fontRevision = v.getInt32(o, false) / 65536; o += 4;
|
||||
this.checkSumAdjustment = v.getUint32(o, false); o += 4;
|
||||
this.magickNumber = v.getUint32(o, false); o += 4;
|
||||
this.flags = v.getUint16(o, false); o += 2;
|
||||
this.unitsPerEm = v.getUint16(o, false); o += 2;
|
||||
this.created = base + v.getUint32(o + 4, false) * 1000; o += 8;
|
||||
this.modified = base + v.getUint32(o + 4, false) * 1000; o += 8;
|
||||
this.xMin = v.getInt16(o, false); o += 2;
|
||||
this.yMin = v.getInt16(o, false); o += 2;
|
||||
this.xMax = v.getInt16(o, false); o += 2;
|
||||
this.yMax = v.getInt16(o, false); o += 2;
|
||||
this.macStyle = v.getUint16(o, false); o += 2;
|
||||
this.lowestRecPPEM = v.getUint16(o, false); o += 2;
|
||||
this.fontDirectionHint = v.getInt16(o, false); o += 2;
|
||||
this.indexToLocFormat = v.getInt16(o, false); o += 2;
|
||||
this.glyphDataFormat = v.getInt16(o, false); o += 2;
|
||||
reader.offset = o;
|
||||
return {
|
||||
version: this.version, fontRevision: this.fontRevision,
|
||||
checkSumAdjustment: this.checkSumAdjustment, magickNumber: this.magickNumber,
|
||||
flags: this.flags, unitsPerEm: this.unitsPerEm,
|
||||
created: this.created, modified: this.modified,
|
||||
xMin: this.xMin, yMin: this.yMin, xMax: this.xMax, yMax: this.yMax,
|
||||
macStyle: this.macStyle, lowestRecPPEM: this.lowestRecPPEM,
|
||||
fontDirectionHint: this.fontDirectionHint, indexToLocFormat: this.indexToLocFormat,
|
||||
glyphDataFormat: this.glyphDataFormat
|
||||
};
|
||||
},
|
||||
/** 优化178: 全部内联 view 写入 54 字节,包括 LongDateTime */
|
||||
write: function (writer, ttf) {
|
||||
var head = ttf.head;
|
||||
|
||||
29
vendor/fonteditor-core/lib/ttf/table/hhea.js
vendored
29
vendor/fonteditor-core/lib/ttf/table/hhea.js
vendored
@ -15,6 +15,35 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
||||
*/
|
||||
var _default = exports.default = _table.default.create('hhea', [['version', _struct.default.Fixed], ['ascent', _struct.default.Int16], ['descent', _struct.default.Int16], ['lineGap', _struct.default.Int16], ['advanceWidthMax', _struct.default.Uint16], ['minLeftSideBearing', _struct.default.Int16], ['minRightSideBearing', _struct.default.Int16], ['xMaxExtent', _struct.default.Int16], ['caretSlopeRise', _struct.default.Int16], ['caretSlopeRun', _struct.default.Int16], ['caretOffset', _struct.default.Int16], ['reserved0', _struct.default.Int16], ['reserved1', _struct.default.Int16], ['reserved2', _struct.default.Int16], ['reserved3', _struct.default.Int16], ['metricDataFormat', _struct.default.Int16], ['numOfLongHorMetrics', _struct.default.Uint16]], {
|
||||
size: function () { return 36; },
|
||||
/** 优化178: 全部内联 view 读取 36 字节 */
|
||||
read: function (reader) {
|
||||
reader.seek(this.offset);
|
||||
var v = reader.view;
|
||||
var o = reader.offset;
|
||||
this.version = v.getInt32(o, false) / 65536; o += 4;
|
||||
this.ascent = v.getInt16(o, false); o += 2;
|
||||
this.descent = v.getInt16(o, false); o += 2;
|
||||
this.lineGap = v.getInt16(o, false); o += 2;
|
||||
this.advanceWidthMax = v.getUint16(o, false); o += 2;
|
||||
this.minLeftSideBearing = v.getInt16(o, false); o += 2;
|
||||
this.minRightSideBearing = v.getInt16(o, false); o += 2;
|
||||
this.xMaxExtent = v.getInt16(o, false); o += 2;
|
||||
this.caretSlopeRise = v.getInt16(o, false); o += 2;
|
||||
this.caretSlopeRun = v.getInt16(o, false); o += 2;
|
||||
this.caretOffset = v.getInt16(o, false); o += 2;
|
||||
o += 8; /* reserved0-3 */
|
||||
this.metricDataFormat = v.getInt16(o, false); o += 2;
|
||||
this.numOfLongHorMetrics = v.getUint16(o, false); o += 2;
|
||||
reader.offset = o;
|
||||
return {
|
||||
version: this.version, ascent: this.ascent, descent: this.descent,
|
||||
lineGap: this.lineGap, advanceWidthMax: this.advanceWidthMax,
|
||||
minLeftSideBearing: this.minLeftSideBearing, minRightSideBearing: this.minRightSideBearing,
|
||||
xMaxExtent: this.xMaxExtent, caretSlopeRise: this.caretSlopeRise,
|
||||
caretSlopeRun: this.caretSlopeRun, caretOffset: this.caretOffset,
|
||||
metricDataFormat: this.metricDataFormat, numOfLongHorMetrics: this.numOfLongHorMetrics
|
||||
};
|
||||
},
|
||||
write: function (writer, ttf) {
|
||||
var h = ttf.hhea;
|
||||
var pos = writer.offset;
|
||||
|
||||
21
vendor/fonteditor-core/lib/ttf/table/maxp.js
vendored
21
vendor/fonteditor-core/lib/ttf/table/maxp.js
vendored
@ -12,8 +12,27 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
||||
* @author mengke01(kekee000@gmail.com)
|
||||
*/
|
||||
var _default = exports.default = _table.default.create('maxp', [['version', _struct.default.Fixed], ['numGlyphs', _struct.default.Uint16], ['maxPoints', _struct.default.Uint16], ['maxContours', _struct.default.Uint16], ['maxCompositePoints', _struct.default.Uint16], ['maxCompositeContours', _struct.default.Uint16], ['maxZones', _struct.default.Uint16], ['maxTwilightPoints', _struct.default.Uint16], ['maxStorage', _struct.default.Uint16], ['maxFunctionDefs', _struct.default.Uint16], ['maxInstructionDefs', _struct.default.Uint16], ['maxStackElements', _struct.default.Uint16], ['maxSizeOfInstructions', _struct.default.Uint16], ['maxComponentElements', _struct.default.Uint16], ['maxComponentDepth', _struct.default.Int16]], {
|
||||
/** 优化178: 直接 view 写入 32 字节,注意写入 ttf.support.maxp */
|
||||
write: function write(writer, ttf) {
|
||||
_table.default.write.call(this, writer, ttf.support);
|
||||
var m = ttf.support.maxp;
|
||||
var pos = writer.offset;
|
||||
var view = writer.view;
|
||||
view.setInt32(pos, Math.round(m.version * 65536), false); pos += 4;
|
||||
view.setUint16(pos, m.numGlyphs, false); pos += 2;
|
||||
view.setUint16(pos, m.maxPoints, false); pos += 2;
|
||||
view.setUint16(pos, m.maxContours, false); pos += 2;
|
||||
view.setUint16(pos, m.maxCompositePoints, false); pos += 2;
|
||||
view.setUint16(pos, m.maxCompositeContours, false); pos += 2;
|
||||
view.setUint16(pos, m.maxZones, false); pos += 2;
|
||||
view.setUint16(pos, m.maxTwilightPoints, false); pos += 2;
|
||||
view.setUint16(pos, m.maxStorage, false); pos += 2;
|
||||
view.setUint16(pos, m.maxFunctionDefs, false); pos += 2;
|
||||
view.setUint16(pos, m.maxInstructionDefs, false); pos += 2;
|
||||
view.setUint16(pos, m.maxStackElements, false); pos += 2;
|
||||
view.setUint16(pos, m.maxSizeOfInstructions, false); pos += 2;
|
||||
view.setUint16(pos, m.maxComponentElements, false); pos += 2;
|
||||
view.setInt16(pos, m.maxComponentDepth, false); pos += 2;
|
||||
writer.offset = pos;
|
||||
return writer;
|
||||
},
|
||||
size: function size() {
|
||||
|
||||
16
vendor/fonteditor-core/lib/ttf/ttftowoff2.js
vendored
16
vendor/fonteditor-core/lib/ttf/ttftowoff2.js
vendored
@ -18,20 +18,17 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
||||
* @param {ArrayBuffer} ttfBuffer ttf缓冲数组
|
||||
* @param {Object} options 选项
|
||||
*
|
||||
* @return {Promise.<ArrayBuffer>} woff格式byte流
|
||||
* @return {ArrayBuffer} woff格式byte流
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function ttftowoff2(ttfBuffer) {
|
||||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||
if (!_index.default.isInited()) {
|
||||
throw new Error('use woff2.init() to init woff2 module!');
|
||||
}
|
||||
var result = _index.default.encode(ttfBuffer);
|
||||
return result.buffer;
|
||||
return result.buffer || result;
|
||||
}
|
||||
|
||||
/**
|
||||
* ttf格式转换成woff2字体格式
|
||||
* ttf格式转换成woff2字体格式(异步,纯 JS 实现直接返回)
|
||||
*
|
||||
* @param {ArrayBuffer} ttfBuffer ttf缓冲数组
|
||||
* @param {Object} options 选项
|
||||
@ -40,8 +37,5 @@ function ttftowoff2(ttfBuffer) {
|
||||
*/
|
||||
function ttftowoff2async(ttfBuffer) {
|
||||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||
return _index.default.init(options.wasmUrl).then(function () {
|
||||
var result = _index.default.encode(ttfBuffer);
|
||||
return result.buffer;
|
||||
});
|
||||
}
|
||||
return Promise.resolve(ttftowoff2(ttfBuffer, options));
|
||||
}
|
||||
|
||||
4
vendor/fonteditor-core/lib/ttf/ttfwriter.js
vendored
4
vendor/fonteditor-core/lib/ttf/ttfwriter.js
vendored
@ -37,8 +37,8 @@ var TTFWriter = exports.default = /*#__PURE__*/function () {
|
||||
value: function resolveTTF(ttf) {
|
||||
ttf.version = ttf.version || 0x1;
|
||||
ttf.numTables = ttf.writeOptions.tables.length;
|
||||
ttf.entrySelector = Math.floor(Math.log(ttf.numTables) / Math.LN2);
|
||||
ttf.searchRange = Math.pow(2, ttf.entrySelector) * 16;
|
||||
ttf.entrySelector = 31 - Math.clz32(ttf.numTables);
|
||||
ttf.searchRange = 2 << ttf.entrySelector;
|
||||
ttf.rangeShift = ttf.numTables * 16 - ttf.searchRange;
|
||||
|
||||
ttf.head.checkSumAdjustment = 0;
|
||||
|
||||
@ -15,9 +15,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
||||
|
||||
/**
|
||||
* 优化103+116: 从 parse 阶段的 TypedArray 直接计算 precomputed 数据,跳过 contour 数组构建
|
||||
* 使用共享 buffer 池避免每字形分配 xCoordBuf/yCoordBuf
|
||||
* 每个字形独立分配 buffer,避免共享 buffer 的复杂性
|
||||
*/
|
||||
function ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf) {
|
||||
function ceilReduceAndSizeFromTypedArrays(glyf) {
|
||||
var xArr = glyf._xArr;
|
||||
var yArr = glyf._yArr;
|
||||
var flagsArr = glyf._flags;
|
||||
@ -40,10 +40,10 @@ function ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf) {
|
||||
var repeatPoint = -1;
|
||||
var encodedCoordSize = 0;
|
||||
|
||||
/* 优化116: 复用共享 buffer,仅在 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 xCoordBuf = new Uint8Array(neededSize);
|
||||
var yCoordBuf = new Uint8Array(neededSize);
|
||||
var xbi = 0, ybi = 0;
|
||||
|
||||
for (var pi = 0; pi < numPoints; pi++) {
|
||||
@ -103,13 +103,6 @@ function ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf) {
|
||||
}
|
||||
|
||||
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);
|
||||
@ -125,22 +118,22 @@ function ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf) {
|
||||
glyf._precomputedGlyfSupport = {
|
||||
flags: flagsC,
|
||||
encodedCoordSize: encodedCoordSize,
|
||||
xEncoded: xEncoded,
|
||||
yEncoded: yEncoded
|
||||
xBuf: xCoordBuf,
|
||||
xLen: xbi,
|
||||
yBuf: yCoordBuf,
|
||||
yLen: ybi
|
||||
};
|
||||
|
||||
delete glyf._xArr;
|
||||
delete glyf._yArr;
|
||||
delete glyf._flags;
|
||||
delete glyf.endPtsOfContours;
|
||||
|
||||
return { sharedXBuf: sharedXBuf, sharedYBuf: sharedYBuf };
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化84+98+149: 合并 ceil+reduce+flagsAndSize 为单次遍历,使用共享 buffer 池
|
||||
* 优化84+98+149: 合并 ceil+reduce+flagsAndSize 为单次遍历
|
||||
*/
|
||||
function ceilReduceAndSizeFlat(glyf, sharedXBuf, sharedYBuf) {
|
||||
function ceilReduceAndSizeFlat(glyf) {
|
||||
var contours = glyf.contours;
|
||||
/* 优化91+164: 跳过 reducePathFlat,用 write-index 替代 splice */
|
||||
var writeIdx = 0;
|
||||
@ -152,11 +145,11 @@ function ceilReduceAndSizeFlat(glyf, sharedXBuf, sharedYBuf) {
|
||||
contours.length = writeIdx;
|
||||
if (0 === contours.length) {
|
||||
delete glyf.contours;
|
||||
return { sharedXBuf: sharedXBuf, sharedYBuf: sharedYBuf };
|
||||
return;
|
||||
}
|
||||
|
||||
if (glyf._precomputedGlyfSupport) {
|
||||
return { sharedXBuf: sharedXBuf, sharedYBuf: sharedYBuf };
|
||||
return;
|
||||
}
|
||||
|
||||
var ONCURVE = _glyFlag.default.ONCURVE;
|
||||
@ -178,10 +171,10 @@ function ceilReduceAndSizeFlat(glyf, sharedXBuf, sharedYBuf) {
|
||||
var repeatPoint = -1;
|
||||
var encodedCoordSize = 0;
|
||||
|
||||
/* 优化149: 复用共享 buffer,仅在 buffer 不够大时才分配新的 */
|
||||
/* 每个字形独立分配 buffer */
|
||||
var neededSize = totalPoints * 2;
|
||||
var xCoordBuf = sharedXBuf.length >= neededSize ? sharedXBuf : new Uint8Array(neededSize);
|
||||
var yCoordBuf = sharedYBuf.length >= neededSize ? sharedYBuf : new Uint8Array(neededSize);
|
||||
var xCoordBuf = new Uint8Array(neededSize);
|
||||
var yCoordBuf = new Uint8Array(neededSize);
|
||||
var xbi = 0, ybi = 0;
|
||||
|
||||
for (var j = 0, cl2 = contours.length; j < cl2; j++) {
|
||||
@ -249,22 +242,15 @@ function ceilReduceAndSizeFlat(glyf, sharedXBuf, sharedYBuf) {
|
||||
}
|
||||
|
||||
flagsC.length = fi;
|
||||
/* 优化149: 存储 Uint8Array 视图,与 ceilReduceAndSizeFromTypedArrays 一致 */
|
||||
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;
|
||||
|
||||
glyf._precomputedGlyfSupport = {
|
||||
flags: flagsC,
|
||||
encodedCoordSize: encodedCoordSize,
|
||||
xEncoded: xEncoded,
|
||||
yEncoded: yEncoded
|
||||
xBuf: xCoordBuf,
|
||||
xLen: xbi,
|
||||
yBuf: yCoordBuf,
|
||||
yLen: ybi
|
||||
};
|
||||
|
||||
return { sharedXBuf: sharedXBuf, sharedYBuf: sharedYBuf };
|
||||
}
|
||||
|
||||
/**
|
||||
@ -284,15 +270,10 @@ function optimizettf(ttf) {
|
||||
var m_firstChar = 0x10FFFF, m_lastChar = -1;
|
||||
var m_maxPoints = 0, m_maxContours = 0;
|
||||
|
||||
/* 优化120+150: 预扫描 _xArr 最大点数以一次性分配共享 buffer */
|
||||
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 || 256);
|
||||
var sharedYBuf = new Uint8Array(maxBufPoints * 2 || 256);
|
||||
/* 优化120+cmap: 在主循环中同时构建预排序的 cmap unicode/id 数组 */
|
||||
var cmapUnicodeArr = [];
|
||||
var cmapIdArr = [];
|
||||
var cmapCount = 0;
|
||||
|
||||
for (var index = 0, gl = glyfs.length; index < gl; index++) {
|
||||
var glyf = glyfs[index];
|
||||
@ -321,8 +302,7 @@ function optimizettf(ttf) {
|
||||
if (!glyf.compound) {
|
||||
/* 优化94+116+149+150: 优先从 TypedArray 构建 contour + precompute,使用共享 buffer 池 */
|
||||
if (glyf._xArr) {
|
||||
var bufResult = ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf);
|
||||
if (bufResult) { sharedXBuf = bufResult.sharedXBuf; sharedYBuf = bufResult.sharedYBuf; }
|
||||
ceilReduceAndSizeFromTypedArrays(glyf);
|
||||
/* 优化120: 从 _numContours/_totalPoints 收集 metrics */
|
||||
if (glyf._numContours > 0) {
|
||||
if (glyf._numContours > m_maxContours) m_maxContours = glyf._numContours;
|
||||
@ -330,8 +310,7 @@ function optimizettf(ttf) {
|
||||
}
|
||||
} else if (glyf.contours) {
|
||||
if (glyf._flatContours) {
|
||||
var flatResult = ceilReduceAndSizeFlat(glyf, sharedXBuf, sharedYBuf);
|
||||
if (flatResult) { sharedXBuf = flatResult.sharedXBuf; sharedYBuf = flatResult.sharedYBuf; }
|
||||
ceilReduceAndSizeFlat(glyf);
|
||||
/**
|
||||
* ⚠️ 关键:必须收集 maxPoints/maxContours,否则 maxp 表中这两个值为 0,
|
||||
* 浏览器会据此跳过渲染(表现为字体加载成功但文字显示为空白/fallback)。
|
||||
@ -417,14 +396,30 @@ function optimizettf(ttf) {
|
||||
|
||||
/* 优化99+103: hasCompound 已在主循环中追踪,过滤使用 _numContours 或 contours.length */
|
||||
if (!hasCompound) {
|
||||
/* 优化:glyf 过滤时同步重映射 cmap 索引,防止 format12 startId 超出 numGlyphs */
|
||||
var filtered = [glyfs[0]];
|
||||
var indexMap = [0];
|
||||
for (var gi = 1; gi < gl; gi++) {
|
||||
var g = glyfs[gi];
|
||||
if (g._numContours != null ? g._numContours > 0 : (g.contours && g.contours.length)) {
|
||||
indexMap[gi] = filtered.length;
|
||||
filtered.push(g);
|
||||
}
|
||||
}
|
||||
ttf.glyf = filtered;
|
||||
if (ttf._cmapSortedIdArr) {
|
||||
var cmapIdArr = ttf._cmapSortedIdArr;
|
||||
for (var ci = 0, cl = cmapIdArr.length; ci < cl; ci++) {
|
||||
var oldIdx = cmapIdArr[ci];
|
||||
if (oldIdx > 0) {
|
||||
var newIdx = indexMap[oldIdx];
|
||||
if (newIdx !== undefined) { cmapIdArr[ci] = newIdx; }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ttf.support && ttf.support.maxp) {
|
||||
ttf.support.maxp.numGlyphs = filtered.length;
|
||||
}
|
||||
}
|
||||
if (!repeatList.length) {
|
||||
return true;
|
||||
|
||||
25
vendor/fonteditor-core/lib/ttf/util/string.js
vendored
25
vendor/fonteditor-core/lib/ttf/util/string.js
vendored
@ -96,17 +96,22 @@ var _default = exports.default = {
|
||||
str = stringify(str);
|
||||
var byteArray = [];
|
||||
for (var i = 0, l = str.length; i < l; i++) {
|
||||
if (str.charCodeAt(i) <= 0x7F) {
|
||||
byteArray.push(str.charCodeAt(i));
|
||||
var ch = str.charCodeAt(i);
|
||||
if (ch <= 0x7F) {
|
||||
byteArray.push(ch);
|
||||
} else if (ch <= 0x7FF) {
|
||||
byteArray.push(0xC0 | (ch >> 6));
|
||||
byteArray.push(0x80 | (ch & 0x3F));
|
||||
} else if (ch < 0xD800 || ch >= 0xE000) {
|
||||
byteArray.push(0xE0 | (ch >> 12));
|
||||
byteArray.push(0x80 | ((ch >> 6) & 0x3F));
|
||||
byteArray.push(0x80 | (ch & 0x3F));
|
||||
} else {
|
||||
var codePoint = str.codePointAt(i);
|
||||
if (codePoint > 0xffff) {
|
||||
i++;
|
||||
}
|
||||
var h = encodeURIComponent(String.fromCodePoint(codePoint)).slice(1).split('%');
|
||||
for (var j = 0; j < h.length; j++) {
|
||||
byteArray.push(parseInt(h[j], 16));
|
||||
}
|
||||
var cp = ((ch - 0xD800) << 10) + (str.charCodeAt(++i) - 0xDC00);
|
||||
byteArray.push(0xF0 | (cp >> 18));
|
||||
byteArray.push(0x80 | ((cp >> 12) & 0x3F));
|
||||
byteArray.push(0x80 | ((cp >> 6) & 0x3F));
|
||||
byteArray.push(0x80 | (cp & 0x3F));
|
||||
}
|
||||
}
|
||||
return byteArray;
|
||||
|
||||
24
vendor/fonteditor-core/lib/ttf/woff2tottf.js
vendored
24
vendor/fonteditor-core/lib/ttf/woff2tottf.js
vendored
@ -13,35 +13,29 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
||||
*/
|
||||
|
||||
/**
|
||||
* ttf格式转换成woff2字体格式
|
||||
* woff2格式转换成ttf字体格式
|
||||
*
|
||||
* @param {ArrayBuffer} woff2Buffer ttf缓冲数组
|
||||
* @param {ArrayBuffer} woff2Buffer woff2缓冲数组
|
||||
* @param {Object} options 选项
|
||||
*
|
||||
* @return {ArrayBuffer} woff格式byte流
|
||||
* @return {ArrayBuffer} ttf格式byte流
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
function woff2tottf(woff2Buffer) {
|
||||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||
if (!_index.default.isInited()) {
|
||||
throw new Error('use woff2.init() to init woff2 module!');
|
||||
}
|
||||
var result = _index.default.decode(woff2Buffer);
|
||||
return result.buffer;
|
||||
return result.buffer || result;
|
||||
}
|
||||
|
||||
/**
|
||||
* ttf格式转换成woff2字体格式
|
||||
* woff2格式转换成ttf字体格式(异步,纯 JS 实现直接返回)
|
||||
*
|
||||
* @param {ArrayBuffer} woff2Buffer ttf缓冲数组
|
||||
* @param {ArrayBuffer} woff2Buffer woff2缓冲数组
|
||||
* @param {Object} options 选项
|
||||
*
|
||||
* @return {Promise.<ArrayBuffer>} woff格式byte流
|
||||
* @return {Promise.<ArrayBuffer>} ttf格式byte流
|
||||
*/
|
||||
function woff2tottfasync(woff2Buffer) {
|
||||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||
return _index.default.init(options.wasmUrl).then(function () {
|
||||
var result = _index.default.decode(woff2Buffer);
|
||||
return result.buffer;
|
||||
});
|
||||
}
|
||||
return Promise.resolve(woff2tottf(woff2Buffer, options));
|
||||
}
|
||||
|
||||
91
vendor/fonteditor-core/woff2/index.js
vendored
91
vendor/fonteditor-core/woff2/index.js
vendored
@ -1,79 +1,38 @@
|
||||
/**
|
||||
* @file woff2 wasm build of google woff2
|
||||
* thanks to woff2-asm
|
||||
* https://github.com/alimilhim/woff2-wasm
|
||||
* @file woff2 纯 JavaScript 编码器/解码器
|
||||
* 替代原 wasm 实现
|
||||
* @author mengke01(kekee000@gmail.com)
|
||||
*/
|
||||
|
||||
// Require the woff2 module
|
||||
const woff2ModuleLoader = require('./woff2');
|
||||
const { encodeTTFToWOFF2 } = require('./woff2-encode');
|
||||
|
||||
function convertFromVecToUint8Array(vector) {
|
||||
const arr = [];
|
||||
for (let i = 0, l = vector.size(); i < l; i++) {
|
||||
arr.push(vector.get(i));
|
||||
}
|
||||
return new Uint8Array(arr);
|
||||
/** @type {typeof import("zlib")} */
|
||||
let zlib;
|
||||
try {
|
||||
zlib = require("node:zlib");
|
||||
} catch (_) {
|
||||
zlib = require("zlib");
|
||||
}
|
||||
const brotliDecompressSync = zlib.brotliDecompressSync;
|
||||
|
||||
// Define as a named object that can be exported with CommonJS
|
||||
const woff2Module = {
|
||||
woff2Module: null,
|
||||
|
||||
/**
|
||||
* 是否已经加载完毕
|
||||
* 是否已经加载完毕(纯 JS 实现不需要初始化)
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
isInited() {
|
||||
return (
|
||||
this.woff2Module && this.woff2Module.woff2Enc && this.woff2Module.woff2Dec
|
||||
);
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化 woff 模块
|
||||
* 初始化(纯 JS 实现不需要初始化)
|
||||
*
|
||||
* @param {string|ArrayBuffer} wasmUrl woff2.wasm file url
|
||||
* @return {Promise}
|
||||
*/
|
||||
init(wasmUrl) {
|
||||
return new Promise((resolve) => {
|
||||
if (this.woff2Module) {
|
||||
resolve(this);
|
||||
return;
|
||||
}
|
||||
|
||||
let moduleLoaderConfig = null;
|
||||
if (typeof window !== 'undefined') {
|
||||
moduleLoaderConfig = {
|
||||
locateFile(path) {
|
||||
if (path.endsWith('.wasm')) {
|
||||
return wasmUrl;
|
||||
}
|
||||
return path;
|
||||
},
|
||||
};
|
||||
}
|
||||
// for nodejs
|
||||
else {
|
||||
// Use path resolution that works in both ESM and CommonJS
|
||||
let wasmPath = './woff2.wasm';
|
||||
// If running in Node.js with __dirname available (CommonJS)
|
||||
if (typeof __dirname !== 'undefined') {
|
||||
wasmPath = __dirname + '/woff2.wasm';
|
||||
}
|
||||
|
||||
moduleLoaderConfig = {
|
||||
wasmBinaryFile: wasmPath,
|
||||
};
|
||||
}
|
||||
const woffModule = woff2ModuleLoader(moduleLoaderConfig);
|
||||
woffModule.onRuntimeInitialized = () => {
|
||||
this.woff2Module = woffModule;
|
||||
resolve(this);
|
||||
};
|
||||
});
|
||||
init() {
|
||||
return Promise.resolve(this);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -83,9 +42,7 @@ const woff2Module = {
|
||||
* @return {Uint8Array} uint8 array
|
||||
*/
|
||||
encode(ttfBuffer) {
|
||||
const buffer = new Uint8Array(ttfBuffer);
|
||||
const woffbuff = this.woff2Module.woff2Enc(buffer, buffer.byteLength);
|
||||
return convertFromVecToUint8Array(woffbuff);
|
||||
return new Uint8Array(encodeTTFToWOFF2(ttfBuffer));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -95,9 +52,19 @@ const woff2Module = {
|
||||
* @return {Uint8Array} uint8 array
|
||||
*/
|
||||
decode(woff2Buffer) {
|
||||
const buffer = new Uint8Array(woff2Buffer);
|
||||
const ttfbuff = this.woff2Module.woff2Dec(buffer, buffer.byteLength);
|
||||
return convertFromVecToUint8Array(ttfbuff);
|
||||
/* WOFF2 文件头: signature(4) + flavor(4) + length(4) + numTables(2) + reserved(2) + totalSfntSize(4) + totalCompressedSize(4) + majorVersion(2) + minorVersion(2) + metaOffset(4) + metaLength(4) + metaOrigLength(4) + privOffset(4) + privLength(4) = 48 bytes */
|
||||
const data = new Uint8Array(woff2Buffer);
|
||||
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
||||
|
||||
/* 跳过 WOFF2 header (48 bytes) + table directory (numTables * 20 bytes) */
|
||||
const numTables = view.getUint16(12);
|
||||
const totalCompressedSize = view.getUint32(20);
|
||||
const dirEnd = 48 + numTables * 20;
|
||||
|
||||
/* 压缩的表数据紧跟在 directory 之后 */
|
||||
const compressedData = data.subarray(dirEnd, dirEnd + totalCompressedSize);
|
||||
const decompressed = brotliDecompressSync(compressedData);
|
||||
return new Uint8Array(decompressed.buffer, decompressed.byteOffset, decompressed.byteLength);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
810
vendor/fonteditor-core/woff2/woff2-encode.js
vendored
Normal file
810
vendor/fonteditor-core/woff2/woff2-encode.js
vendored
Normal file
@ -0,0 +1,810 @@
|
||||
/**
|
||||
* @file 纯 JavaScript 实现的 WOFF2 编码器
|
||||
* 替代原 wasm 实现,使用 Brotli 压缩
|
||||
*
|
||||
* WOFF2 格式参考: https://www.w3.org/TR/WOFF2/
|
||||
* 兼容 Node.js (require("node:zlib"))、LLRT (require("zlib")) 及浏览器环境
|
||||
*/
|
||||
|
||||
/** @type {typeof import("zlib")} */
|
||||
let zlib;
|
||||
try {
|
||||
zlib = require("node:zlib");
|
||||
} catch (_) {
|
||||
zlib = require("zlib");
|
||||
}
|
||||
const brotliCompressSync = zlib.brotliCompressSync;
|
||||
const constants = zlib.constants;
|
||||
|
||||
/**
|
||||
* Brotli 压缩参数:quality 8
|
||||
* 测试表明 FONT 模式对小数据集(<200KB)无速度优势且增加 0.14% 体积,保持 GENERIC
|
||||
*/
|
||||
const BROTLI_OPTIONS_BASE = {
|
||||
params: { [constants.BROTLI_PARAM_QUALITY]: 8 },
|
||||
};
|
||||
|
||||
/* ======== Known Table Tags 索引表 ======== */
|
||||
const KNOWN_TAGS = [
|
||||
"cmap", "head", "hhea", "hmtx", "maxp", "name", "OS/2", "post",
|
||||
"cvt ", "fpgm", "glyf", "loca", "prep ", "CFF ", "VORG ", "EBDT",
|
||||
"EBLC", "EBSC", "CBDT", "CBLC", "COLR", "CPAL", "SVG ", "sbix",
|
||||
"acnt", "avar", "bdat", "bloc", "bsln", "cvar", "fdsc", "feat",
|
||||
"fmtx", "fvar", "gvar", "gdef", "hsty", "jstf", "lcar", "mort",
|
||||
"morx", "opbd", "prop", "trak", "Zapf", "Silf", "Glat", "Gloc",
|
||||
"Feat", "Sill",
|
||||
];
|
||||
|
||||
/**
|
||||
* 优化:预构建 tag→index Map,消除每次 getTagIndex 的 O(63) 线性搜索
|
||||
*/
|
||||
const KNOWN_TAG_MAP = new Map();
|
||||
for (let i = 0; i < KNOWN_TAGS.length; i++) {
|
||||
KNOWN_TAG_MAP.set(KNOWN_TAGS[i], i);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 4 字节 tag 获取 Known Table Tag 索引
|
||||
*/
|
||||
function getTagIndex(tag) {
|
||||
const idx = KNOWN_TAG_MAP.get(tag);
|
||||
return idx !== undefined ? idx : 63;
|
||||
}
|
||||
|
||||
/* ======== 变长整数编码 ======== */
|
||||
|
||||
/** 计算 UIntBase128 编码后的字节数 */
|
||||
function calcUIntBase128Size(value) {
|
||||
if (value < 0x80) return 1;
|
||||
if (value < 0x4000) return 2;
|
||||
if (value < 0x200000) return 3;
|
||||
if (value < 0x10000000) return 4;
|
||||
return 5;
|
||||
}
|
||||
|
||||
/** 编码 UIntBase128(最多 5 字节,高位在前) */
|
||||
function encodeUIntBase128(value, buf, offset) {
|
||||
const size = calcUIntBase128Size(value);
|
||||
let pos = offset;
|
||||
for (let i = size - 1; i >= 0; i--) {
|
||||
const byte = (value >>> (7 * i)) & 0x7F;
|
||||
if (i > 0) {
|
||||
buf[pos++] = byte | 0x80;
|
||||
} else {
|
||||
buf[pos++] = byte;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
/** 编码 255UInt16(1-3 字节变长) */
|
||||
function encode255UInt16(value, buf, offset) {
|
||||
if (value < 253) {
|
||||
buf[offset] = value;
|
||||
return 1;
|
||||
}
|
||||
if (value < 506) {
|
||||
buf[offset] = 255;
|
||||
buf[offset + 1] = value - 253;
|
||||
return 2;
|
||||
}
|
||||
if (value < 762) {
|
||||
buf[offset] = 254;
|
||||
buf[offset + 1] = value - 506;
|
||||
return 2;
|
||||
}
|
||||
buf[offset] = 253;
|
||||
buf[offset + 1] = (value >> 8) & 0xFF;
|
||||
buf[offset + 2] = value & 0xFF;
|
||||
return 3;
|
||||
}
|
||||
|
||||
/** 计算 255UInt16 编码后的字节数 */
|
||||
function size255UInt16(value) {
|
||||
if (value <= 252) return 1;
|
||||
if (value <= 762) return 2;
|
||||
return 3;
|
||||
}
|
||||
|
||||
const sizeUIntBase128 = calcUIntBase128Size;
|
||||
|
||||
/* ======== Triplet 编码 ======== */
|
||||
|
||||
/**
|
||||
* 从 triplet flag 推断数据字节数(避免重复计算)
|
||||
* tripletIndex = flag & 0x7F
|
||||
* 优化:预计算查找表,消除函数调用和条件分支
|
||||
*/
|
||||
const TRIPLET_DATA_SIZES = new Uint8Array(128);
|
||||
for (let i = 0; i < 84; i++) TRIPLET_DATA_SIZES[i] = 1;
|
||||
for (let i = 84; i < 120; i++) TRIPLET_DATA_SIZES[i] = 2;
|
||||
for (let i = 120; i < 124; i++) TRIPLET_DATA_SIZES[i] = 3;
|
||||
for (let i = 124; i < 128; i++) TRIPLET_DATA_SIZES[i] = 4;
|
||||
function dataSizeFromTriplet(ti) {
|
||||
return TRIPLET_DATA_SIZES[ti];
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码一个点到 glyphStream,返回 triplet flag 字节
|
||||
* 直接写入 buf,不分配数组
|
||||
*/
|
||||
function encodePointToBuf(onCurve, dx, dy, buf, offset) {
|
||||
const curveBit = onCurve ? 0 : 128;
|
||||
const absDx = dx < 0 ? -dx : dx;
|
||||
const absDy = dy < 0 ? -dy : dy;
|
||||
const xSignBit = dx >= 0 ? 1 : 0;
|
||||
const ySignBit = dy >= 0 ? 1 : 0;
|
||||
const xySignBits = xSignBit + 2 * ySignBit;
|
||||
|
||||
/* dx=0, Y 单轴 1 数据字节 (flag 0-9) */
|
||||
if (dx === 0 && absDy < 1280) {
|
||||
const flag = curveBit + ((absDy & 0xF00) >> 7) + ySignBit;
|
||||
buf[offset] = absDy & 0xFF;
|
||||
return flag;
|
||||
}
|
||||
|
||||
/* dy=0, dx≠0, X 单轴 1 数据字节 (flag 10-19) */
|
||||
if (dy === 0 && dx !== 0 && absDx < 1280) {
|
||||
const flag = curveBit + 10 + ((absDx & 0xF00) >> 7) + xSignBit;
|
||||
buf[offset] = absDx & 0xFF;
|
||||
return flag;
|
||||
}
|
||||
|
||||
/* 双轴 1 数据字节 (flag 20-83): 1 ≤ |dx| ≤ 64, 1 ≤ |dy| ≤ 64 */
|
||||
if (dx !== 0 && dy !== 0 && absDx < 65 && absDy < 65) {
|
||||
const ax = absDx - 1;
|
||||
const ay = absDy - 1;
|
||||
const flag = curveBit + 20 + (ax & 0x30) + ((ay & 0x30) >> 2) + xySignBits;
|
||||
buf[offset] = ((ax & 0xF) << 4) | (ay & 0xF);
|
||||
return flag;
|
||||
}
|
||||
|
||||
/* 双轴 2 数据字节 (flag 84-119): 1 ≤ |dx| ≤ 768, 1 ≤ |dy| ≤ 768 */
|
||||
if (dx !== 0 && dy !== 0 && absDx < 769 && absDy < 769) {
|
||||
const ax = absDx - 1;
|
||||
const ay = absDy - 1;
|
||||
const flag = curveBit + 84 + 12 * ((ax & 0x300) >> 8) + ((ay & 0x300) >> 6) + xySignBits;
|
||||
buf[offset] = ax & 0xFF;
|
||||
buf[offset + 1] = ay & 0xFF;
|
||||
return flag;
|
||||
}
|
||||
|
||||
/* 双轴 3 数据字节 (flag 120-123) */
|
||||
if (absDx < 4096 && absDy < 4096) {
|
||||
const flag = curveBit + 120 + xySignBits;
|
||||
buf[offset] = absDx >> 4;
|
||||
buf[offset + 1] = ((absDx & 0xF) << 4) | (absDy >> 8);
|
||||
buf[offset + 2] = absDy & 0xFF;
|
||||
return flag;
|
||||
}
|
||||
|
||||
/* 兜底 4 数据字节 (flag 124-127) */
|
||||
const flag = curveBit + 124 + xySignBits;
|
||||
buf[offset] = (absDx >> 8) & 0xFF;
|
||||
buf[offset + 1] = absDx & 0xFF;
|
||||
buf[offset + 2] = (absDy >> 8) & 0xFF;
|
||||
buf[offset + 3] = absDy & 0xFF;
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅写入数据字节(不含 flag),利用缓存的 flag 值避免重复分支判断
|
||||
* flag 的低 7 位 (tripletIndex) 决定数据字节布局
|
||||
* 优化182: 返回写入的字节数,消除调用方 TRIPLET_DATA_SIZES 重复查找
|
||||
*/
|
||||
function writePointDataByFlag(flag, dx, dy, buf, offset) {
|
||||
const ti = flag & 0x7F;
|
||||
const absDx = dx < 0 ? -dx : dx;
|
||||
const absDy = dy < 0 ? -dy : dy;
|
||||
|
||||
if (ti < 10) {
|
||||
/* Y 单轴 1 字节 */
|
||||
buf[offset] = absDy & 0xFF;
|
||||
return 1;
|
||||
} else if (ti < 20) {
|
||||
/* X 单轴 1 字节 */
|
||||
buf[offset] = absDx & 0xFF;
|
||||
return 1;
|
||||
} else if (ti < 84) {
|
||||
/* 双轴 1 字节 */
|
||||
const ax = absDx - 1;
|
||||
const ay = absDy - 1;
|
||||
buf[offset] = ((ax & 0xF) << 4) | (ay & 0xF);
|
||||
return 1;
|
||||
} else if (ti < 120) {
|
||||
/* 双轴 2 字节 */
|
||||
const ax = absDx - 1;
|
||||
const ay = absDy - 1;
|
||||
buf[offset] = ax & 0xFF;
|
||||
buf[offset + 1] = ay & 0xFF;
|
||||
return 2;
|
||||
} else if (ti < 124) {
|
||||
/* 双轴 3 字节 */
|
||||
buf[offset] = absDx >> 4;
|
||||
buf[offset + 1] = ((absDx & 0xF) << 4) | (absDy >> 8);
|
||||
buf[offset + 2] = absDy & 0xFF;
|
||||
return 3;
|
||||
} else {
|
||||
/* 兜底 4 字节 */
|
||||
buf[offset] = (absDx >> 8) & 0xFF;
|
||||
buf[offset + 1] = absDx & 0xFF;
|
||||
buf[offset + 2] = (absDy >> 8) & 0xFF;
|
||||
buf[offset + 3] = absDy & 0xFF;
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
/* ======== glyf + loca 表变换 ======== */
|
||||
|
||||
/**
|
||||
* 对 glyf + loca 表执行 WOFF2 变换
|
||||
*/
|
||||
function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) {
|
||||
const glyfView = new DataView(glyfData.buffer, glyfData.byteOffset, glyfData.byteLength);
|
||||
const locaView = new DataView(locaData.buffer, locaData.byteOffset, locaData.byteLength);
|
||||
|
||||
/* 读取 loca 表获取每个 glyph 的偏移 */
|
||||
const offsets = new Int32Array(numGlyphs + 1);
|
||||
if (indexFormat === 0) {
|
||||
for (let i = 0; i <= numGlyphs; i++) {
|
||||
offsets[i] = locaView.getUint16(i * 2, false) * 2;
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i <= numGlyphs; i++) {
|
||||
offsets[i] = locaView.getUint32(i * 4, false);
|
||||
}
|
||||
}
|
||||
|
||||
const XSHORT_FLAG = 2;
|
||||
const XSAME_FLAG = 16;
|
||||
const YSHORT_FLAG = 4;
|
||||
const YSAME_FLAG = 32;
|
||||
const REPEAT_FLAG = 8;
|
||||
const OVERLAP_FLAG = 64;
|
||||
const ONCURVE_FLAG = 1;
|
||||
|
||||
/**
|
||||
* 优化:合并第 1 遍(解码 glyph)和第 2 遍(预计算 stream 大小 + 缓存 triplet flag)
|
||||
* 消除一次完整的 numGlyphs 遍历,利用解码后数据仍在 L1 cache 的优势
|
||||
*/
|
||||
let totalNPointsSize = 0;
|
||||
let flagStreamSize = 0;
|
||||
let glyphStreamSize = 0;
|
||||
let bboxStreamSize = 0;
|
||||
let instructionStreamSize = 0;
|
||||
let hasOverlapBitmap = false;
|
||||
let totalPoints = 0;
|
||||
|
||||
const bboxBitmapSize = 4 * Math.floor((numGlyphs + 31) / 32);
|
||||
const bboxBitmap = new Uint8Array(bboxBitmapSize);
|
||||
const overlapBitmap = new Uint8Array(bboxBitmapSize);
|
||||
const tmpBuf = new Uint8Array(4);
|
||||
|
||||
/* 收集每个 glyph 的信息 */
|
||||
const glyphInfos = new Array(numGlyphs);
|
||||
|
||||
for (let gi = 0; gi < numGlyphs; gi++) {
|
||||
const glyphStart = offsets[gi];
|
||||
const glyphEnd = offsets[gi + 1];
|
||||
|
||||
if (glyphStart === glyphEnd) {
|
||||
glyphInfos[gi] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
const numberOfContours = glyfView.getInt16(glyphStart, false);
|
||||
const xMin = glyfView.getInt16(glyphStart + 2, false);
|
||||
const yMin = glyfView.getInt16(glyphStart + 4, false);
|
||||
const xMax = glyfView.getInt16(glyphStart + 6, false);
|
||||
const yMax = glyfView.getInt16(glyphStart + 8, false);
|
||||
|
||||
if (numberOfContours < 0) {
|
||||
/* 复合 glyph */
|
||||
let compOff = glyphStart + 10;
|
||||
let haveInstructions = false;
|
||||
let instrLength = 0;
|
||||
let instructions = null;
|
||||
|
||||
const MORE_COMPONENTS = 0x0020;
|
||||
const WE_HAVE_INSTRUCTIONS = 0x0100;
|
||||
while (compOff < glyphEnd) {
|
||||
const compFlags = glyfView.getUint16(compOff, false);
|
||||
compOff += 2;
|
||||
compOff += 2; /* glyphIndex */
|
||||
|
||||
if (compFlags & 0x0001) compOff += 4;
|
||||
else compOff += 2;
|
||||
if (compFlags & 0x0008) compOff += 2;
|
||||
else if (compFlags & 0x0040) compOff += 4;
|
||||
else if (compFlags & 0x0080) compOff += 8;
|
||||
|
||||
if (!(compFlags & MORE_COMPONENTS)) {
|
||||
haveInstructions = !!(compFlags & WE_HAVE_INSTRUCTIONS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const componentDataEnd = compOff;
|
||||
|
||||
if (haveInstructions && compOff + 2 <= glyphEnd) {
|
||||
instrLength = glyfView.getUint16(compOff, false);
|
||||
compOff += 2;
|
||||
if (instrLength > 0 && compOff + instrLength <= glyphEnd) {
|
||||
instructions = { offset: compOff, length: instrLength };
|
||||
}
|
||||
}
|
||||
|
||||
const rawLength = componentDataEnd - glyphStart - 10;
|
||||
|
||||
glyphInfos[gi] = {
|
||||
composite: true,
|
||||
xMin, yMin, xMax, yMax,
|
||||
rawOffset: glyphStart + 10,
|
||||
rawLength,
|
||||
instructions,
|
||||
haveInstructions,
|
||||
};
|
||||
|
||||
/* ★ 合并:复合 glyph 的统计量 */
|
||||
bboxBitmap[gi >> 3] |= (0x80 >> (gi & 7));
|
||||
bboxStreamSize += 8;
|
||||
glyphStreamSize += rawLength;
|
||||
if (haveInstructions) {
|
||||
instructionStreamSize += instrLength;
|
||||
glyphStreamSize += size255UInt16(instrLength);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 简单 glyph */
|
||||
let dataOff = glyphStart + 10;
|
||||
|
||||
const endPtsOfContours = new Uint16Array(numberOfContours);
|
||||
for (let c = 0; c < numberOfContours; c++) {
|
||||
endPtsOfContours[c] = glyfView.getUint16(dataOff, false);
|
||||
dataOff += 2;
|
||||
}
|
||||
|
||||
/* ★ 合并:totalNPointsSize 计算 + 缓存 255UInt16 编码结果 */
|
||||
const nPointsEncoded = new Uint8Array(numberOfContours * 3);
|
||||
let nPointsBytes = 0;
|
||||
let prevEnd = -1;
|
||||
for (let c = 0; c < numberOfContours; c++) {
|
||||
nPointsBytes += encode255UInt16(endPtsOfContours[c] - prevEnd, nPointsEncoded, nPointsBytes);
|
||||
prevEnd = endPtsOfContours[c];
|
||||
}
|
||||
totalNPointsSize += nPointsBytes;
|
||||
|
||||
const instructionLength = glyfView.getUint16(dataOff, false);
|
||||
dataOff += 2;
|
||||
const instructions = instructionLength > 0 ? { offset: dataOff, length: instructionLength } : null;
|
||||
dataOff += instructionLength;
|
||||
|
||||
/* ★ 合并:instructionStreamSize 累加 */
|
||||
instructionStreamSize += instructionLength;
|
||||
|
||||
const numPoints = numberOfContours > 0 ? endPtsOfContours[numberOfContours - 1] + 1 : 0;
|
||||
|
||||
/* 优化183: flagsArr 复用为 cachedFlags,消除每个 glyph 一次 Uint8Array 分配 */
|
||||
const flagsArr = new Uint8Array(numPoints);
|
||||
let hasOverlap = false;
|
||||
let fi = 0;
|
||||
while (fi < numPoints) {
|
||||
const flag = glyfData[dataOff++];
|
||||
if (flag & OVERLAP_FLAG) hasOverlap = true;
|
||||
flagsArr[fi++] = flag;
|
||||
if (flag & REPEAT_FLAG && fi < numPoints) {
|
||||
const repeat = glyfData[dataOff++];
|
||||
const count = Math.min(repeat, numPoints - fi);
|
||||
flagsArr.fill(flag, fi, fi + count);
|
||||
fi += count;
|
||||
}
|
||||
}
|
||||
|
||||
/* ★ 合并:overlapBitmap + flagStreamSize + totalPoints */
|
||||
if (numberOfContours > 0) {
|
||||
if (hasOverlap) {
|
||||
hasOverlapBitmap = true;
|
||||
overlapBitmap[gi >> 3] |= (0x80 >> (gi & 7));
|
||||
}
|
||||
flagStreamSize += numPoints;
|
||||
totalPoints += numPoints;
|
||||
}
|
||||
|
||||
/* 解码 X 坐标 */
|
||||
const xCoords = new Int32Array(numPoints);
|
||||
let px = 0;
|
||||
for (let xi = 0; xi < numPoints; xi++) {
|
||||
const f = flagsArr[xi];
|
||||
if (f & XSHORT_FLAG) {
|
||||
const b = glyfData[dataOff++];
|
||||
px += (f & XSAME_FLAG) ? b : -b;
|
||||
} else if (!(f & XSAME_FLAG)) {
|
||||
let dx = (glyfData[dataOff] << 8) | glyfData[dataOff + 1];
|
||||
if (dx > 0x7FFF) dx -= 0x10000;
|
||||
px += dx;
|
||||
dataOff += 2;
|
||||
}
|
||||
xCoords[xi] = px;
|
||||
}
|
||||
|
||||
/* 解码 Y 坐标 + 同时计算 bbox */
|
||||
const yCoords = new Int32Array(numPoints);
|
||||
let py = 0;
|
||||
let calcXMin = xCoords[0], calcYMin = py, calcXMax = xCoords[0], calcYMax = py;
|
||||
for (let yi = 0; yi < numPoints; yi++) {
|
||||
const f = flagsArr[yi];
|
||||
if (f & YSHORT_FLAG) {
|
||||
const b = glyfData[dataOff++];
|
||||
py += (f & YSAME_FLAG) ? b : -b;
|
||||
} else if (!(f & YSAME_FLAG)) {
|
||||
let dy = (glyfData[dataOff] << 8) | glyfData[dataOff + 1];
|
||||
if (dy > 0x7FFF) dy -= 0x10000;
|
||||
py += dy;
|
||||
dataOff += 2;
|
||||
}
|
||||
yCoords[yi] = py;
|
||||
const px2 = xCoords[yi];
|
||||
if (px2 < calcXMin) calcXMin = px2;
|
||||
else if (px2 > calcXMax) calcXMax = px2;
|
||||
if (py < calcYMin) calcYMin = py;
|
||||
else if (py > calcYMax) calcYMax = py;
|
||||
}
|
||||
|
||||
/* 优化183: 就地覆盖 flagsArr 为 triplet flags,省掉单独的 cachedFlags 分配 */
|
||||
if (numberOfContours > 0) {
|
||||
const bboxMatches = calcXMin === xMin && calcYMin === yMin && calcXMax === xMax && calcYMax === yMax;
|
||||
if (!bboxMatches) {
|
||||
bboxBitmap[gi >> 3] |= (0x80 >> (gi & 7));
|
||||
bboxStreamSize += 8;
|
||||
}
|
||||
|
||||
let prevX = 0, prevY = 0;
|
||||
for (let pi = 0; pi < numPoints; pi++) {
|
||||
const onCurve = !!(flagsArr[pi] & ONCURVE_FLAG);
|
||||
const dx = xCoords[pi] - prevX;
|
||||
const dy = yCoords[pi] - prevY;
|
||||
const flag = encodePointToBuf(onCurve, dx, dy, tmpBuf, 0);
|
||||
flagsArr[pi] = flag;
|
||||
glyphStreamSize += TRIPLET_DATA_SIZES[flag & 0x7F];
|
||||
prevX = xCoords[pi];
|
||||
prevY = yCoords[pi];
|
||||
}
|
||||
glyphStreamSize += size255UInt16(instructionLength);
|
||||
}
|
||||
|
||||
glyphInfos[gi] = {
|
||||
composite: false,
|
||||
numberOfContours,
|
||||
nPointsEncoded,
|
||||
nPointsBytes,
|
||||
instructions,
|
||||
xCoords, yCoords, flags: flagsArr,
|
||||
hasOverlap,
|
||||
xMin, yMin, xMax, yMax,
|
||||
calcXMin, calcYMin, calcXMax, calcYMax,
|
||||
};
|
||||
}
|
||||
|
||||
const nContourStreamSize = numGlyphs * 2;
|
||||
const headerSize = 36;
|
||||
const overlapBitmapSize = hasOverlapBitmap ? bboxBitmapSize : 0;
|
||||
const totalSize = headerSize
|
||||
+ nContourStreamSize
|
||||
+ totalNPointsSize
|
||||
+ flagStreamSize
|
||||
+ glyphStreamSize
|
||||
+ bboxBitmapSize
|
||||
+ bboxStreamSize
|
||||
+ instructionStreamSize
|
||||
+ overlapBitmapSize;
|
||||
|
||||
const result = new Uint8Array(totalSize);
|
||||
const resultView = new DataView(result.buffer, result.byteOffset, result.byteLength);
|
||||
let pos = 0;
|
||||
|
||||
/* Header */
|
||||
resultView.setUint16(pos, 0, false); pos += 2;
|
||||
resultView.setUint16(pos, hasOverlapBitmap ? 1 : 0, false); pos += 2;
|
||||
resultView.setUint16(pos, numGlyphs, false); pos += 2;
|
||||
resultView.setUint16(pos, indexFormat, false); pos += 2;
|
||||
resultView.setUint32(pos, nContourStreamSize, false); pos += 4;
|
||||
resultView.setUint32(pos, totalNPointsSize, false); pos += 4;
|
||||
resultView.setUint32(pos, flagStreamSize, false); pos += 4;
|
||||
resultView.setUint32(pos, glyphStreamSize, false); pos += 4;
|
||||
resultView.setUint32(pos, 0, false); pos += 4;
|
||||
resultView.setUint32(pos, bboxBitmapSize + bboxStreamSize, false); pos += 4;
|
||||
resultView.setUint32(pos, instructionStreamSize, false); pos += 4;
|
||||
|
||||
/**
|
||||
* 优化:合并原第 3/4/5 次遍历为 1 次
|
||||
* nContourStream + nPointsStream + 所有子流写入合并在单次 glyph 遍历中
|
||||
*/
|
||||
const nContourEnd = pos + nContourStreamSize;
|
||||
let nContourPos = pos;
|
||||
let nPointsPos = nContourEnd;
|
||||
pos = nContourEnd + totalNPointsSize;
|
||||
|
||||
const flagStreamStart = pos;
|
||||
pos += flagStreamSize;
|
||||
const glyphStreamStart = pos;
|
||||
pos += glyphStreamSize;
|
||||
const bboxBitmapStart = pos;
|
||||
pos += bboxBitmapSize;
|
||||
const bboxStreamStart = pos;
|
||||
pos += bboxStreamSize;
|
||||
const instructionStreamStart = pos;
|
||||
pos += instructionStreamSize;
|
||||
const overlapBitmapStart = pos;
|
||||
|
||||
let flagPos = flagStreamStart;
|
||||
let glyphPos = glyphStreamStart;
|
||||
let bboxPos = bboxStreamStart;
|
||||
let instrPos = instructionStreamStart;
|
||||
|
||||
for (let gi = 0; gi < numGlyphs; gi++) {
|
||||
const g = glyphInfos[gi];
|
||||
|
||||
/* nContourStream: 每个 glyph 写入 numberOfContours */
|
||||
if (!g) {
|
||||
resultView.setInt16(nContourPos, 0, false); nContourPos += 2;
|
||||
continue;
|
||||
}
|
||||
if (g.composite) {
|
||||
resultView.setInt16(nContourPos, -1, false); nContourPos += 2;
|
||||
} else {
|
||||
resultView.setInt16(nContourPos, g.numberOfContours, false); nContourPos += 2;
|
||||
}
|
||||
|
||||
if (g.composite) {
|
||||
result.set(glyfData.subarray(g.rawOffset, g.rawOffset + g.rawLength), glyphPos);
|
||||
glyphPos += g.rawLength;
|
||||
resultView.setInt16(bboxPos, g.xMin, false); bboxPos += 2;
|
||||
resultView.setInt16(bboxPos, g.yMin, false); bboxPos += 2;
|
||||
resultView.setInt16(bboxPos, g.xMax, false); bboxPos += 2;
|
||||
resultView.setInt16(bboxPos, g.yMax, false); bboxPos += 2;
|
||||
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;
|
||||
|
||||
/* nPointsStream: 直接使用预编码的 delta 数据 */
|
||||
result.set(g.nPointsEncoded.subarray(0, g.nPointsBytes), nPointsPos);
|
||||
nPointsPos += g.nPointsBytes;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* flag + glyph 子流 — 使用缓存的 triplet flag + writePointDataByFlag
|
||||
* 避免写入阶段的完整分支判断链(6 层 if-else),改用 flag 值直接定位数据布局
|
||||
*/
|
||||
const numPts = g.xCoords.length;
|
||||
const xCoords = g.xCoords;
|
||||
const yCoords = g.yCoords;
|
||||
const tripletFlags = g.flags;
|
||||
let prevX = 0, prevY = 0;
|
||||
for (let pi = 0; pi < numPts; pi++) {
|
||||
const dx = xCoords[pi] - prevX;
|
||||
const dy = yCoords[pi] - prevY;
|
||||
const flag = tripletFlags[pi];
|
||||
result[flagPos++] = flag;
|
||||
glyphPos += writePointDataByFlag(flag, dx, dy, result, glyphPos);
|
||||
prevX = xCoords[pi];
|
||||
prevY = yCoords[pi];
|
||||
}
|
||||
|
||||
glyphPos += encode255UInt16(instrLen, result, glyphPos);
|
||||
|
||||
if (bboxBitmap[gi >> 3] & (0x80 >> (gi & 7))) {
|
||||
resultView.setInt16(bboxPos, g.calcXMin, false); bboxPos += 2;
|
||||
resultView.setInt16(bboxPos, g.calcYMin, false); bboxPos += 2;
|
||||
resultView.setInt16(bboxPos, g.calcXMax, false); bboxPos += 2;
|
||||
resultView.setInt16(bboxPos, g.calcYMax, false); bboxPos += 2;
|
||||
}
|
||||
}
|
||||
|
||||
result.set(bboxBitmap, bboxBitmapStart);
|
||||
|
||||
if (hasOverlapBitmap) {
|
||||
result.set(overlapBitmap, overlapBitmapStart);
|
||||
}
|
||||
|
||||
const locaOrigLength = indexFormat === 0 ? (numGlyphs + 1) * 2 : (numGlyphs + 1) * 4;
|
||||
|
||||
return {
|
||||
transformedGlyf: result,
|
||||
locaOrigLength,
|
||||
locaTransformLength: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/* ======== 主编码函数 ======== */
|
||||
|
||||
const WOFF2_SIGNATURE = 0x774F4632;
|
||||
const WOFF2_HEADER_SIZE = 48;
|
||||
|
||||
/**
|
||||
* 将 TTF buffer 编码为 WOFF2 buffer
|
||||
*/
|
||||
function encodeTTFToWOFF2(ttfBuffer) {
|
||||
const data = ttfBuffer instanceof Uint8Array ? ttfBuffer : new Uint8Array(ttfBuffer);
|
||||
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
||||
|
||||
/* 解析 sfnt header */
|
||||
const flavor = view.getUint32(0, false);
|
||||
const numTables = view.getUint16(4, false);
|
||||
|
||||
/* 解析 Table Directory */
|
||||
const tables = [];
|
||||
for (let i = 0; i < numTables; i++) {
|
||||
const off = 12 + i * 16;
|
||||
const tag = String.fromCharCode(data[off], data[off + 1], data[off + 2], data[off + 3]);
|
||||
const offset = view.getUint32(off + 8, false);
|
||||
const length = view.getUint32(off + 12, false);
|
||||
tables.push({ tag, offset, length, tagIndex: getTagIndex(tag) });
|
||||
}
|
||||
|
||||
/* 移除 DSIG,按 tag 升序排列 */
|
||||
const filtered = tables.filter(t => t.tag !== "DSIG");
|
||||
filtered.sort((a, b) => a.tagIndex - b.tagIndex || (a.tag < b.tag ? -1 : a.tag > b.tag ? 1 : 0));
|
||||
|
||||
/* 查找关键表 */
|
||||
let indexToLocFormat = 0;
|
||||
let numGlyphs = 0;
|
||||
let glyfTable = null;
|
||||
let locaTable = null;
|
||||
|
||||
for (const t of filtered) {
|
||||
if (t.tag === "head") indexToLocFormat = view.getUint16(t.offset + 50, false);
|
||||
if (t.tag === "maxp") numGlyphs = view.getUint16(t.offset + 4, false);
|
||||
if (t.tag === "glyf") glyfTable = t;
|
||||
if (t.tag === "loca") locaTable = t;
|
||||
}
|
||||
|
||||
/* glyf + loca 变换 */
|
||||
let glyfTransformed = null;
|
||||
if (glyfTable && locaTable) {
|
||||
const glyfData = data.subarray(glyfTable.offset, glyfTable.offset + glyfTable.length);
|
||||
const locaData = data.subarray(locaTable.offset, locaTable.offset + locaTable.length);
|
||||
const result = transformGlyfAndLoca(glyfData, locaData, indexToLocFormat, numGlyphs);
|
||||
glyfTransformed = result;
|
||||
}
|
||||
|
||||
/* 构建 Table Directory entries */
|
||||
const dirEntries = [];
|
||||
let totalDirSize = 0;
|
||||
|
||||
for (const t of filtered) {
|
||||
if (t.tag === "loca") {
|
||||
/** 优化:直接使用 transformGlyfAndLoca 返回的 locaOrigLength,避免重复计算 */
|
||||
const origLength = glyfTransformed ? glyfTransformed.locaOrigLength : t.length;
|
||||
dirEntries.push({
|
||||
tag: t.tag, tagIndex: t.tagIndex,
|
||||
flags: t.tagIndex,
|
||||
origLength,
|
||||
transformLength: 0,
|
||||
data: new Uint8Array(0),
|
||||
hasTransform: true,
|
||||
});
|
||||
totalDirSize += 1 + sizeUIntBase128(origLength) + sizeUIntBase128(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (t.tag === "glyf" && glyfTransformed) {
|
||||
dirEntries.push({
|
||||
tag: t.tag, tagIndex: t.tagIndex,
|
||||
flags: t.tagIndex,
|
||||
origLength: t.length,
|
||||
transformLength: glyfTransformed.transformedGlyf.length,
|
||||
data: glyfTransformed.transformedGlyf,
|
||||
hasTransform: true,
|
||||
});
|
||||
totalDirSize += 1 + sizeUIntBase128(t.length) + sizeUIntBase128(glyfTransformed.transformedGlyf.length);
|
||||
continue;
|
||||
}
|
||||
|
||||
let tableData = data.subarray(t.offset, t.offset + t.length);
|
||||
|
||||
dirEntries.push({
|
||||
tag: t.tag, tagIndex: t.tagIndex,
|
||||
flags: t.tagIndex,
|
||||
origLength: t.length,
|
||||
transformLength: t.length,
|
||||
data: tableData,
|
||||
isHead: t.tag === "head",
|
||||
});
|
||||
totalDirSize += 1 + sizeUIntBase128(t.length);
|
||||
}
|
||||
|
||||
/* 计算 totalSfntSize */
|
||||
let totalSfntSize = 12 + filtered.length * 16;
|
||||
for (let i = 0; i < filtered.length; i++) {
|
||||
const len = filtered[i].length;
|
||||
totalSfntSize += len + (len & 3 ? 4 - (len & 3) : 0);
|
||||
}
|
||||
|
||||
/* 拼接表数据 */
|
||||
let totalTableDataSize = 0;
|
||||
for (let i = 0; i < dirEntries.length; i++) totalTableDataSize += dirEntries[i].transformLength;
|
||||
const uncompressedData = new Uint8Array(totalTableDataSize);
|
||||
let dataPos = 0;
|
||||
for (const entry of dirEntries) {
|
||||
if (entry.transformLength > 0) {
|
||||
uncompressedData.set(entry.data, dataPos);
|
||||
if (entry.isHead) {
|
||||
/* head 表原地修改:清零 checkSumAdjustment,设置 bit 11(headFlags),避免额外拷贝 */
|
||||
const uView = new DataView(uncompressedData.buffer, uncompressedData.byteOffset + dataPos, entry.transformLength);
|
||||
uView.setUint32(8, 0, false);
|
||||
const headFlags = uView.getUint16(44, false);
|
||||
uView.setUint16(44, headFlags | (1 << 11), false);
|
||||
}
|
||||
dataPos += entry.transformLength;
|
||||
}
|
||||
}
|
||||
|
||||
/* Brotli 压缩,传入 SIZE_HINT 帮助预分配内部缓冲区 */
|
||||
const brotliOptions = totalTableDataSize > 0
|
||||
? { params: {
|
||||
[constants.BROTLI_PARAM_QUALITY]: 8,
|
||||
[constants.BROTLI_PARAM_SIZE_HINT]: totalTableDataSize,
|
||||
}}
|
||||
: BROTLI_OPTIONS_BASE;
|
||||
const compressedData = brotliCompressSync(uncompressedData, brotliOptions);
|
||||
|
||||
/* 组装 WOFF2(预计算 padding,避免额外拷贝) */
|
||||
const rawLength = WOFF2_HEADER_SIZE + totalDirSize + compressedData.length;
|
||||
const paddedLength = (rawLength + 3) & ~3;
|
||||
const woff2 = new Uint8Array(paddedLength);
|
||||
const woff2View = new DataView(woff2.buffer, woff2.byteOffset, woff2.byteLength);
|
||||
|
||||
/* Header */
|
||||
woff2View.setUint32(0, WOFF2_SIGNATURE, false);
|
||||
woff2View.setUint32(4, flavor, false);
|
||||
woff2View.setUint32(8, paddedLength, false);
|
||||
woff2View.setUint16(12, dirEntries.length, false);
|
||||
woff2View.setUint16(14, 0, false);
|
||||
woff2View.setUint32(16, totalSfntSize, false);
|
||||
woff2View.setUint32(20, compressedData.length, false);
|
||||
woff2View.setUint16(24, 1, false);
|
||||
woff2View.setUint16(26, 0, false);
|
||||
woff2View.setUint32(28, 0, false);
|
||||
woff2View.setUint32(32, 0, false);
|
||||
woff2View.setUint32(36, 0, false);
|
||||
woff2View.setUint32(40, 0, false);
|
||||
woff2View.setUint32(44, 0, false);
|
||||
|
||||
/* Table Directory */
|
||||
let dirPos = WOFF2_HEADER_SIZE;
|
||||
for (const entry of dirEntries) {
|
||||
woff2[dirPos++] = entry.flags;
|
||||
if (entry.tagIndex === 63) {
|
||||
for (let ci = 0; ci < 4; ci++) woff2[dirPos++] = entry.tag.charCodeAt(ci);
|
||||
}
|
||||
dirPos += encodeUIntBase128(entry.origLength, woff2, dirPos);
|
||||
if (entry.hasTransform) {
|
||||
dirPos += encodeUIntBase128(entry.transformLength, woff2, dirPos);
|
||||
}
|
||||
}
|
||||
|
||||
/* 压缩数据 */
|
||||
woff2.set(compressedData, dirPos);
|
||||
|
||||
/* WOFF2 规范要求文件大小 4 字节对齐(Round4)
|
||||
已在分配时预留 padding 空间,无需额外拷贝 */
|
||||
|
||||
return woff2.buffer;
|
||||
}
|
||||
|
||||
module.exports = { encodeTTFToWOFF2 };
|
||||
BIN
vendor/fonteditor-core/woff2/woff2.wasm
vendored
BIN
vendor/fonteditor-core/woff2/woff2.wasm
vendored
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user