实现 js 版 woff2 处理

This commit is contained in:
崮生(子虚) 2026-04-10 20:42:30 +08:00
parent e977cc9575
commit 161bafc02a
17 changed files with 1034 additions and 185 deletions

View File

@ -23,11 +23,13 @@ export const createSubsetFont = (
subset: codePoints,
});
/** 优化字体(去冗余表、清理无用字形) */
/**
*
* subset TTFReader.resolveGlyf compound2simple
* optimizettf _unicodeSorted=truesortGlyf
*/
export const optimizeFont = (font: ReturnType<typeof Font.create>) => {
let optimized = font.optimize();
optimized = optimized.compound2simple();
optimized = optimized.sort();
const optimized = font.optimize();
return optimized;
};

View File

@ -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;

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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() {

View File

@ -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));
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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));
}

View File

@ -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);
},
};

View 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",
];
/**
* 优化预构建 tagindex 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;
}
/** 编码 255UInt161-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 11headFlags避免额外拷贝 */
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 };

Binary file not shown.