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: codePoints,
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 优化字体(去冗余表、清理无用字形) */
|
/**
|
||||||
|
* 优化字体(去冗余表、清理无用字形)
|
||||||
|
* subset 模式下 TTFReader.resolveGlyf 已完成 compound2simple,跳过
|
||||||
|
* optimizettf 已设置 _unicodeSorted=true,sortGlyf 会直接返回
|
||||||
|
*/
|
||||||
export const optimizeFont = (font: ReturnType<typeof Font.create>) => {
|
export const optimizeFont = (font: ReturnType<typeof Font.create>) => {
|
||||||
let optimized = font.optimize();
|
const optimized = font.optimize();
|
||||||
optimized = optimized.compound2simple();
|
|
||||||
optimized = optimized.sort();
|
|
||||||
return optimized;
|
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) 查找
|
// unicode to index — 用 Set 替代 indexOf 实现 O(1) 查找
|
||||||
var subsetSet = {};
|
var subsetSet = {};
|
||||||
|
/** 优化:合并 subsetSet 和 subsetMap 构建为单次遍历 */
|
||||||
for (var si = 0, sl = subset.length; si < sl; si++) {
|
for (var si = 0, sl = subset.length; si < sl; si++) {
|
||||||
subsetSet[subset[si]] = true;
|
var code = subset[si];
|
||||||
}
|
subsetSet[code] = true;
|
||||||
/** 优化168: 用 subset 直接遍历替代 codes for...in,减少遍历量 */
|
var ci = codes[code];
|
||||||
for (var si = 0, sl = subset.length; si < sl; si++) {
|
|
||||||
var ci = codes[subset[si]];
|
|
||||||
if (ci !== undefined) subsetMap[ci] = true;
|
if (ci !== undefined) subsetMap[ci] = true;
|
||||||
}
|
}
|
||||||
font.subsetMap = subsetMap;
|
font.subsetMap = subsetMap;
|
||||||
|
|||||||
@ -140,11 +140,13 @@ function entriesToObject(entries) {
|
|||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 优化:lookup 表提升到模块级,避免每次调用重新分配 */
|
||||||
|
var FLOAT_LOOKUP = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-'];
|
||||||
|
|
||||||
/* eslint-disable no-constant-condition */
|
/* eslint-disable no-constant-condition */
|
||||||
function parseFloatOperand(reader) {
|
function parseFloatOperand(reader) {
|
||||||
var s = '';
|
var s = '';
|
||||||
var eof = 15;
|
var eof = 15;
|
||||||
var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-'];
|
|
||||||
while (true) {
|
while (true) {
|
||||||
var b = reader.readUint8();
|
var b = reader.readUint8();
|
||||||
var n1 = b >> 4;
|
var n1 = b >> 4;
|
||||||
@ -152,11 +154,11 @@ function parseFloatOperand(reader) {
|
|||||||
if (n1 === eof) {
|
if (n1 === eof) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
s += lookup[n1];
|
s += FLOAT_LOOKUP[n1];
|
||||||
if (n2 === eof) {
|
if (n2 === eof) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
s += lookup[n2];
|
s += FLOAT_LOOKUP[n2];
|
||||||
}
|
}
|
||||||
return parseFloat(s);
|
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;
|
ttf.subsetGids = subsetGids;
|
||||||
var parsedGlyfMap = {};
|
var parsedGlyfMap = {};
|
||||||
|
|
||||||
/* 优化43: for...in + for 循环替代 Object.keys + forEach */
|
/* 优化:迭代式广度优先遍历替代递归,消除 isEmptyObject 调用 */
|
||||||
var travelsParse = function travels(sMap) {
|
var queue = subsetGids;
|
||||||
var newSubsetMap = {};
|
while (queue.length > 0) {
|
||||||
for (var idx in sMap) {
|
var nextQueue = [];
|
||||||
var index = +idx;
|
for (var qi = 0, ql = queue.length; qi < ql; qi++) {
|
||||||
|
var index = queue[qi];
|
||||||
parsedGlyfMap[index] = true;
|
parsedGlyfMap[index] = true;
|
||||||
if (loca[index] === loca[index + 1]) {
|
if (loca[index] === loca[index + 1]) {
|
||||||
glyphs[index] = { contours: [] };
|
glyphs[index] = { contours: [] };
|
||||||
@ -57,16 +58,13 @@ var _default = exports.default = _table.default.create('glyf', [], {
|
|||||||
var glyfs = glyphs[index].glyfs;
|
var glyfs = glyphs[index].glyfs;
|
||||||
for (var gi = 0, gl = glyfs.length; gi < gl; gi++) {
|
for (var gi = 0, gl = glyfs.length; gi < gl; gi++) {
|
||||||
if (!parsedGlyfMap[glyfs[gi].glyphIndex]) {
|
if (!parsedGlyfMap[glyfs[gi].glyphIndex]) {
|
||||||
newSubsetMap[glyfs[gi].glyphIndex] = true;
|
nextQueue.push(glyfs[gi].glyphIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!(0, _lang.isEmptyObject)(newSubsetMap)) {
|
queue = nextQueue;
|
||||||
travels(newSubsetMap);
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
travelsParse(subsetMap);
|
|
||||||
return glyphs;
|
return glyphs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -44,9 +44,9 @@ function parseSimpleGlyf(reader, glyf) {
|
|||||||
flags[fi++] = flag;
|
flags[fi++] = flag;
|
||||||
if (flag & REPEAT && fi < numberOfCoordinates) {
|
if (flag & REPEAT && fi < numberOfCoordinates) {
|
||||||
var repeat = view.getUint8(vOffset++);
|
var repeat = view.getUint8(vOffset++);
|
||||||
for (var j = 0; j < repeat && fi < numberOfCoordinates; j++) {
|
var fillCount = repeat < numberOfCoordinates - fi ? repeat : numberOfCoordinates - fi;
|
||||||
flags[fi++] = flag;
|
flags.fill(flag, fi, fi + fillCount);
|
||||||
}
|
fi += fillCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,20 +152,20 @@ function parseCompoundGlyf(reader, glyf) {
|
|||||||
g.useMyMetrics = !!(flags & USE_MY_METRICS);
|
g.useMyMetrics = !!(flags & USE_MY_METRICS);
|
||||||
g.overlapCompound = !!(flags & OVERLAP_COMPOUND);
|
g.overlapCompound = !!(flags & OVERLAP_COMPOUND);
|
||||||
g.transform = {
|
g.transform = {
|
||||||
a: Math.round(10000 * scaleX / 16384) / 10000,
|
a: Math.round(scaleX * 0.6103515625) / 10000,
|
||||||
b: Math.round(10000 * scale01 / 16384) / 10000,
|
b: Math.round(scale01 * 0.6103515625) / 10000,
|
||||||
c: Math.round(10000 * scale10 / 16384) / 10000,
|
c: Math.round(scale10 * 0.6103515625) / 10000,
|
||||||
d: Math.round(10000 * scaleY / 16384) / 10000,
|
d: Math.round(scaleY * 0.6103515625) / 10000,
|
||||||
e: arg1,
|
e: arg1,
|
||||||
f: arg2
|
f: arg2
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
g.points = [arg1, arg2];
|
g.points = [arg1, arg2];
|
||||||
g.transform = {
|
g.transform = {
|
||||||
a: Math.round(10000 * scaleX / 16384) / 10000,
|
a: Math.round(scaleX * 0.6103515625) / 10000,
|
||||||
b: Math.round(10000 * scale01 / 16384) / 10000,
|
b: Math.round(scale01 * 0.6103515625) / 10000,
|
||||||
c: Math.round(10000 * scale10 / 16384) / 10000,
|
c: Math.round(scale10 * 0.6103515625) / 10000,
|
||||||
d: Math.round(10000 * scaleY / 16384) / 10000,
|
d: Math.round(scaleY * 0.6103515625) / 10000,
|
||||||
e: 0,
|
e: 0,
|
||||||
f: 0
|
f: 0
|
||||||
};
|
};
|
||||||
|
|||||||
@ -24,9 +24,9 @@ function getFlagsAndSize(glyf, glyfSupport, hinting) {
|
|||||||
var pre = glyf._precomputedGlyfSupport;
|
var pre = glyf._precomputedGlyfSupport;
|
||||||
glyfSupport.flags = pre.flags;
|
glyfSupport.flags = pre.flags;
|
||||||
/* 优化98: 预编码 buffer 直接传递 */
|
/* 优化98: 预编码 buffer 直接传递 */
|
||||||
if (pre.xEncoded) {
|
if (pre.xBuf) {
|
||||||
glyfSupport.xEncoded = pre.xEncoded;
|
glyfSupport.xEncoded = pre.xBuf.subarray(0, pre.xLen);
|
||||||
glyfSupport.yEncoded = pre.yEncoded;
|
glyfSupport.yEncoded = pre.yBuf.subarray(0, pre.yLen);
|
||||||
} else {
|
} else {
|
||||||
glyfSupport.xCoord = pre.xCoord;
|
glyfSupport.xCoord = pre.xCoord;
|
||||||
glyfSupport.yCoord = pre.yCoord;
|
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]], {
|
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; },
|
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 */
|
/** 优化178: 全部内联 view 写入 54 字节,包括 LongDateTime */
|
||||||
write: function (writer, ttf) {
|
write: function (writer, ttf) {
|
||||||
var head = ttf.head;
|
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]], {
|
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; },
|
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) {
|
write: function (writer, ttf) {
|
||||||
var h = ttf.hhea;
|
var h = ttf.hhea;
|
||||||
var pos = writer.offset;
|
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)
|
* @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]], {
|
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) {
|
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;
|
return writer;
|
||||||
},
|
},
|
||||||
size: function size() {
|
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 {ArrayBuffer} ttfBuffer ttf缓冲数组
|
||||||
* @param {Object} options 选项
|
* @param {Object} options 选项
|
||||||
*
|
*
|
||||||
* @return {Promise.<ArrayBuffer>} woff格式byte流
|
* @return {ArrayBuffer} woff格式byte流
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
function ttftowoff2(ttfBuffer) {
|
function ttftowoff2(ttfBuffer) {
|
||||||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
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);
|
var result = _index.default.encode(ttfBuffer);
|
||||||
return result.buffer;
|
return result.buffer || result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ttf格式转换成woff2字体格式
|
* ttf格式转换成woff2字体格式(异步,纯 JS 实现直接返回)
|
||||||
*
|
*
|
||||||
* @param {ArrayBuffer} ttfBuffer ttf缓冲数组
|
* @param {ArrayBuffer} ttfBuffer ttf缓冲数组
|
||||||
* @param {Object} options 选项
|
* @param {Object} options 选项
|
||||||
@ -40,8 +37,5 @@ function ttftowoff2(ttfBuffer) {
|
|||||||
*/
|
*/
|
||||||
function ttftowoff2async(ttfBuffer) {
|
function ttftowoff2async(ttfBuffer) {
|
||||||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||||
return _index.default.init(options.wasmUrl).then(function () {
|
return Promise.resolve(ttftowoff2(ttfBuffer, options));
|
||||||
var result = _index.default.encode(ttfBuffer);
|
}
|
||||||
return result.buffer;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
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) {
|
value: function resolveTTF(ttf) {
|
||||||
ttf.version = ttf.version || 0x1;
|
ttf.version = ttf.version || 0x1;
|
||||||
ttf.numTables = ttf.writeOptions.tables.length;
|
ttf.numTables = ttf.writeOptions.tables.length;
|
||||||
ttf.entrySelector = Math.floor(Math.log(ttf.numTables) / Math.LN2);
|
ttf.entrySelector = 31 - Math.clz32(ttf.numTables);
|
||||||
ttf.searchRange = Math.pow(2, ttf.entrySelector) * 16;
|
ttf.searchRange = 2 << ttf.entrySelector;
|
||||||
ttf.rangeShift = ttf.numTables * 16 - ttf.searchRange;
|
ttf.rangeShift = ttf.numTables * 16 - ttf.searchRange;
|
||||||
|
|
||||||
ttf.head.checkSumAdjustment = 0;
|
ttf.head.checkSumAdjustment = 0;
|
||||||
|
|||||||
@ -15,9 +15,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 优化103+116: 从 parse 阶段的 TypedArray 直接计算 precomputed 数据,跳过 contour 数组构建
|
* 优化103+116: 从 parse 阶段的 TypedArray 直接计算 precomputed 数据,跳过 contour 数组构建
|
||||||
* 使用共享 buffer 池避免每字形分配 xCoordBuf/yCoordBuf
|
* 每个字形独立分配 buffer,避免共享 buffer 的复杂性
|
||||||
*/
|
*/
|
||||||
function ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf) {
|
function ceilReduceAndSizeFromTypedArrays(glyf) {
|
||||||
var xArr = glyf._xArr;
|
var xArr = glyf._xArr;
|
||||||
var yArr = glyf._yArr;
|
var yArr = glyf._yArr;
|
||||||
var flagsArr = glyf._flags;
|
var flagsArr = glyf._flags;
|
||||||
@ -40,10 +40,10 @@ function ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf) {
|
|||||||
var repeatPoint = -1;
|
var repeatPoint = -1;
|
||||||
var encodedCoordSize = 0;
|
var encodedCoordSize = 0;
|
||||||
|
|
||||||
/* 优化116: 复用共享 buffer,仅在 buffer 不够大时才分配新的 */
|
/* 每个字形独立分配 buffer */
|
||||||
var neededSize = numPoints * 2;
|
var neededSize = numPoints * 2;
|
||||||
var xCoordBuf = sharedXBuf.length >= neededSize ? sharedXBuf : new Uint8Array(neededSize);
|
var xCoordBuf = new Uint8Array(neededSize);
|
||||||
var yCoordBuf = sharedYBuf.length >= neededSize ? sharedYBuf : new Uint8Array(neededSize);
|
var yCoordBuf = new Uint8Array(neededSize);
|
||||||
var xbi = 0, ybi = 0;
|
var xbi = 0, ybi = 0;
|
||||||
|
|
||||||
for (var pi = 0; pi < numPoints; pi++) {
|
for (var pi = 0; pi < numPoints; pi++) {
|
||||||
@ -103,13 +103,6 @@ function ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
flagsC.length = fi;
|
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 数组,直接存储元数据 */
|
/* 优化103: 不构建 contour 数组,直接存储元数据 */
|
||||||
glyf.contours = new Array(numContours);
|
glyf.contours = new Array(numContours);
|
||||||
@ -125,22 +118,22 @@ function ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf) {
|
|||||||
glyf._precomputedGlyfSupport = {
|
glyf._precomputedGlyfSupport = {
|
||||||
flags: flagsC,
|
flags: flagsC,
|
||||||
encodedCoordSize: encodedCoordSize,
|
encodedCoordSize: encodedCoordSize,
|
||||||
xEncoded: xEncoded,
|
xBuf: xCoordBuf,
|
||||||
yEncoded: yEncoded
|
xLen: xbi,
|
||||||
|
yBuf: yCoordBuf,
|
||||||
|
yLen: ybi
|
||||||
};
|
};
|
||||||
|
|
||||||
delete glyf._xArr;
|
delete glyf._xArr;
|
||||||
delete glyf._yArr;
|
delete glyf._yArr;
|
||||||
delete glyf._flags;
|
delete glyf._flags;
|
||||||
delete glyf.endPtsOfContours;
|
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;
|
var contours = glyf.contours;
|
||||||
/* 优化91+164: 跳过 reducePathFlat,用 write-index 替代 splice */
|
/* 优化91+164: 跳过 reducePathFlat,用 write-index 替代 splice */
|
||||||
var writeIdx = 0;
|
var writeIdx = 0;
|
||||||
@ -152,11 +145,11 @@ function ceilReduceAndSizeFlat(glyf, sharedXBuf, sharedYBuf) {
|
|||||||
contours.length = writeIdx;
|
contours.length = writeIdx;
|
||||||
if (0 === contours.length) {
|
if (0 === contours.length) {
|
||||||
delete glyf.contours;
|
delete glyf.contours;
|
||||||
return { sharedXBuf: sharedXBuf, sharedYBuf: sharedYBuf };
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (glyf._precomputedGlyfSupport) {
|
if (glyf._precomputedGlyfSupport) {
|
||||||
return { sharedXBuf: sharedXBuf, sharedYBuf: sharedYBuf };
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ONCURVE = _glyFlag.default.ONCURVE;
|
var ONCURVE = _glyFlag.default.ONCURVE;
|
||||||
@ -178,10 +171,10 @@ function ceilReduceAndSizeFlat(glyf, sharedXBuf, sharedYBuf) {
|
|||||||
var repeatPoint = -1;
|
var repeatPoint = -1;
|
||||||
var encodedCoordSize = 0;
|
var encodedCoordSize = 0;
|
||||||
|
|
||||||
/* 优化149: 复用共享 buffer,仅在 buffer 不够大时才分配新的 */
|
/* 每个字形独立分配 buffer */
|
||||||
var neededSize = totalPoints * 2;
|
var neededSize = totalPoints * 2;
|
||||||
var xCoordBuf = sharedXBuf.length >= neededSize ? sharedXBuf : new Uint8Array(neededSize);
|
var xCoordBuf = new Uint8Array(neededSize);
|
||||||
var yCoordBuf = sharedYBuf.length >= neededSize ? sharedYBuf : new Uint8Array(neededSize);
|
var yCoordBuf = new Uint8Array(neededSize);
|
||||||
var xbi = 0, ybi = 0;
|
var xbi = 0, ybi = 0;
|
||||||
|
|
||||||
for (var j = 0, cl2 = contours.length; j < cl2; j++) {
|
for (var j = 0, cl2 = contours.length; j < cl2; j++) {
|
||||||
@ -249,22 +242,15 @@ function ceilReduceAndSizeFlat(glyf, sharedXBuf, sharedYBuf) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
flagsC.length = fi;
|
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 = {
|
glyf._precomputedGlyfSupport = {
|
||||||
flags: flagsC,
|
flags: flagsC,
|
||||||
encodedCoordSize: encodedCoordSize,
|
encodedCoordSize: encodedCoordSize,
|
||||||
xEncoded: xEncoded,
|
xBuf: xCoordBuf,
|
||||||
yEncoded: yEncoded
|
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_firstChar = 0x10FFFF, m_lastChar = -1;
|
||||||
var m_maxPoints = 0, m_maxContours = 0;
|
var m_maxPoints = 0, m_maxContours = 0;
|
||||||
|
|
||||||
/* 优化120+150: 预扫描 _xArr 最大点数以一次性分配共享 buffer */
|
/* 优化120+cmap: 在主循环中同时构建预排序的 cmap unicode/id 数组 */
|
||||||
var maxBufPoints = 0;
|
var cmapUnicodeArr = [];
|
||||||
for (var pi = 0, pl = glyfs.length; pi < pl; pi++) {
|
var cmapIdArr = [];
|
||||||
if (glyfs[pi]._xArr && glyfs[pi]._xArr.length > maxBufPoints) {
|
var cmapCount = 0;
|
||||||
maxBufPoints = glyfs[pi]._xArr.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var sharedXBuf = new Uint8Array(maxBufPoints * 2 || 256);
|
|
||||||
var sharedYBuf = new Uint8Array(maxBufPoints * 2 || 256);
|
|
||||||
|
|
||||||
for (var index = 0, gl = glyfs.length; index < gl; index++) {
|
for (var index = 0, gl = glyfs.length; index < gl; index++) {
|
||||||
var glyf = glyfs[index];
|
var glyf = glyfs[index];
|
||||||
@ -321,8 +302,7 @@ function optimizettf(ttf) {
|
|||||||
if (!glyf.compound) {
|
if (!glyf.compound) {
|
||||||
/* 优化94+116+149+150: 优先从 TypedArray 构建 contour + precompute,使用共享 buffer 池 */
|
/* 优化94+116+149+150: 优先从 TypedArray 构建 contour + precompute,使用共享 buffer 池 */
|
||||||
if (glyf._xArr) {
|
if (glyf._xArr) {
|
||||||
var bufResult = ceilReduceAndSizeFromTypedArrays(glyf, sharedXBuf, sharedYBuf);
|
ceilReduceAndSizeFromTypedArrays(glyf);
|
||||||
if (bufResult) { sharedXBuf = bufResult.sharedXBuf; sharedYBuf = bufResult.sharedYBuf; }
|
|
||||||
/* 优化120: 从 _numContours/_totalPoints 收集 metrics */
|
/* 优化120: 从 _numContours/_totalPoints 收集 metrics */
|
||||||
if (glyf._numContours > 0) {
|
if (glyf._numContours > 0) {
|
||||||
if (glyf._numContours > m_maxContours) m_maxContours = glyf._numContours;
|
if (glyf._numContours > m_maxContours) m_maxContours = glyf._numContours;
|
||||||
@ -330,8 +310,7 @@ function optimizettf(ttf) {
|
|||||||
}
|
}
|
||||||
} else if (glyf.contours) {
|
} else if (glyf.contours) {
|
||||||
if (glyf._flatContours) {
|
if (glyf._flatContours) {
|
||||||
var flatResult = ceilReduceAndSizeFlat(glyf, sharedXBuf, sharedYBuf);
|
ceilReduceAndSizeFlat(glyf);
|
||||||
if (flatResult) { sharedXBuf = flatResult.sharedXBuf; sharedYBuf = flatResult.sharedYBuf; }
|
|
||||||
/**
|
/**
|
||||||
* ⚠️ 关键:必须收集 maxPoints/maxContours,否则 maxp 表中这两个值为 0,
|
* ⚠️ 关键:必须收集 maxPoints/maxContours,否则 maxp 表中这两个值为 0,
|
||||||
* 浏览器会据此跳过渲染(表现为字体加载成功但文字显示为空白/fallback)。
|
* 浏览器会据此跳过渲染(表现为字体加载成功但文字显示为空白/fallback)。
|
||||||
@ -417,14 +396,30 @@ function optimizettf(ttf) {
|
|||||||
|
|
||||||
/* 优化99+103: hasCompound 已在主循环中追踪,过滤使用 _numContours 或 contours.length */
|
/* 优化99+103: hasCompound 已在主循环中追踪,过滤使用 _numContours 或 contours.length */
|
||||||
if (!hasCompound) {
|
if (!hasCompound) {
|
||||||
|
/* 优化:glyf 过滤时同步重映射 cmap 索引,防止 format12 startId 超出 numGlyphs */
|
||||||
var filtered = [glyfs[0]];
|
var filtered = [glyfs[0]];
|
||||||
|
var indexMap = [0];
|
||||||
for (var gi = 1; gi < gl; gi++) {
|
for (var gi = 1; gi < gl; gi++) {
|
||||||
var g = glyfs[gi];
|
var g = glyfs[gi];
|
||||||
if (g._numContours != null ? g._numContours > 0 : (g.contours && g.contours.length)) {
|
if (g._numContours != null ? g._numContours > 0 : (g.contours && g.contours.length)) {
|
||||||
|
indexMap[gi] = filtered.length;
|
||||||
filtered.push(g);
|
filtered.push(g);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ttf.glyf = filtered;
|
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) {
|
if (!repeatList.length) {
|
||||||
return true;
|
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);
|
str = stringify(str);
|
||||||
var byteArray = [];
|
var byteArray = [];
|
||||||
for (var i = 0, l = str.length; i < l; i++) {
|
for (var i = 0, l = str.length; i < l; i++) {
|
||||||
if (str.charCodeAt(i) <= 0x7F) {
|
var ch = str.charCodeAt(i);
|
||||||
byteArray.push(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 {
|
} else {
|
||||||
var codePoint = str.codePointAt(i);
|
var cp = ((ch - 0xD800) << 10) + (str.charCodeAt(++i) - 0xDC00);
|
||||||
if (codePoint > 0xffff) {
|
byteArray.push(0xF0 | (cp >> 18));
|
||||||
i++;
|
byteArray.push(0x80 | ((cp >> 12) & 0x3F));
|
||||||
}
|
byteArray.push(0x80 | ((cp >> 6) & 0x3F));
|
||||||
var h = encodeURIComponent(String.fromCodePoint(codePoint)).slice(1).split('%');
|
byteArray.push(0x80 | (cp & 0x3F));
|
||||||
for (var j = 0; j < h.length; j++) {
|
|
||||||
byteArray.push(parseInt(h[j], 16));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return byteArray;
|
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 选项
|
* @param {Object} options 选项
|
||||||
*
|
*
|
||||||
* @return {ArrayBuffer} woff格式byte流
|
* @return {ArrayBuffer} ttf格式byte流
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
function woff2tottf(woff2Buffer) {
|
function woff2tottf(woff2Buffer) {
|
||||||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
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);
|
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 选项
|
* @param {Object} options 选项
|
||||||
*
|
*
|
||||||
* @return {Promise.<ArrayBuffer>} woff格式byte流
|
* @return {Promise.<ArrayBuffer>} ttf格式byte流
|
||||||
*/
|
*/
|
||||||
function woff2tottfasync(woff2Buffer) {
|
function woff2tottfasync(woff2Buffer) {
|
||||||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||||
return _index.default.init(options.wasmUrl).then(function () {
|
return Promise.resolve(woff2tottf(woff2Buffer, options));
|
||||||
var result = _index.default.decode(woff2Buffer);
|
}
|
||||||
return result.buffer;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
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
|
* @file woff2 纯 JavaScript 编码器/解码器
|
||||||
* thanks to woff2-asm
|
* 替代原 wasm 实现
|
||||||
* https://github.com/alimilhim/woff2-wasm
|
|
||||||
* @author mengke01(kekee000@gmail.com)
|
* @author mengke01(kekee000@gmail.com)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Require the woff2 module
|
const { encodeTTFToWOFF2 } = require('./woff2-encode');
|
||||||
const woff2ModuleLoader = require('./woff2');
|
|
||||||
|
|
||||||
function convertFromVecToUint8Array(vector) {
|
/** @type {typeof import("zlib")} */
|
||||||
const arr = [];
|
let zlib;
|
||||||
for (let i = 0, l = vector.size(); i < l; i++) {
|
try {
|
||||||
arr.push(vector.get(i));
|
zlib = require("node:zlib");
|
||||||
}
|
} catch (_) {
|
||||||
return new Uint8Array(arr);
|
zlib = require("zlib");
|
||||||
}
|
}
|
||||||
|
const brotliDecompressSync = zlib.brotliDecompressSync;
|
||||||
|
|
||||||
// Define as a named object that can be exported with CommonJS
|
|
||||||
const woff2Module = {
|
const woff2Module = {
|
||||||
woff2Module: null,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否已经加载完毕
|
* 是否已经加载完毕(纯 JS 实现不需要初始化)
|
||||||
*
|
*
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
isInited() {
|
isInited() {
|
||||||
return (
|
return true;
|
||||||
this.woff2Module && this.woff2Module.woff2Enc && this.woff2Module.woff2Dec
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化 woff 模块
|
* 初始化(纯 JS 实现不需要初始化)
|
||||||
*
|
*
|
||||||
* @param {string|ArrayBuffer} wasmUrl woff2.wasm file url
|
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
init(wasmUrl) {
|
init() {
|
||||||
return new Promise((resolve) => {
|
return Promise.resolve(this);
|
||||||
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);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -83,9 +42,7 @@ const woff2Module = {
|
|||||||
* @return {Uint8Array} uint8 array
|
* @return {Uint8Array} uint8 array
|
||||||
*/
|
*/
|
||||||
encode(ttfBuffer) {
|
encode(ttfBuffer) {
|
||||||
const buffer = new Uint8Array(ttfBuffer);
|
return new Uint8Array(encodeTTFToWOFF2(ttfBuffer));
|
||||||
const woffbuff = this.woff2Module.woff2Enc(buffer, buffer.byteLength);
|
|
||||||
return convertFromVecToUint8Array(woffbuff);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -95,9 +52,19 @@ const woff2Module = {
|
|||||||
* @return {Uint8Array} uint8 array
|
* @return {Uint8Array} uint8 array
|
||||||
*/
|
*/
|
||||||
decode(woff2Buffer) {
|
decode(woff2Buffer) {
|
||||||
const buffer = new Uint8Array(woff2Buffer);
|
/* 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 ttfbuff = this.woff2Module.woff2Dec(buffer, buffer.byteLength);
|
const data = new Uint8Array(woff2Buffer);
|
||||||
return convertFromVecToUint8Array(ttfbuff);
|
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