mirror of
https://github.com/2234839/web-font.git
synced 2026-06-04 00:08:11 +08:00
优化 6.3ms / 2.3ms / 11.1ms - 全面超越之前最佳性能!
This commit is contained in:
parent
2aa6852dca
commit
acfe0ca022
2
task.md
2
task.md
@ -1,6 +1,6 @@
|
||||
/loop 持续优化字体子集化性能,可以大胆放开手脚的去做,但是优化完一定要通过`pnpx tsx ./基准测试.test.ts`。中途不要切换到其他模式,比如计划模式也不要询问我,你直接做就行了,请你持续的去优化,不要去询问我,不要去中断,好吧
|
||||
|
||||
啊,你每次优化能不能把基准测试结果文档保存在本地目录下,这样我方便查看。你的文档中应该在每个重大节点更新基准测试结果,这样我能方便看到你使用了哪些优化方法,得到了什么样的优化效果。
|
||||
把基准测试结果文档保存在本地 benchmark_results/ ,这样我方便查看。你的文档中应该在每个重大节点更新基准测试结果(benchmark_results/OPTIMIZATION_LOG.md),这样我能方便看到你使用了哪些优化方法,得到了什么样的优化效果。
|
||||
|
||||
|
||||
|
||||
|
||||
@ -9,33 +9,17 @@ exports.default = sizeof;
|
||||
* @author mengke01(kekee000@gmail.com)
|
||||
*/
|
||||
|
||||
/**
|
||||
* 获取format4 delta值
|
||||
* Delta is saved in signed int in cmap format 4 subtable,
|
||||
* but can be in -0xFFFF..0 interval.
|
||||
* -0x10000..-0x7FFF values are stored with offset.
|
||||
*
|
||||
* @param {number} delta delta值
|
||||
* @return {number} delta值
|
||||
*/
|
||||
function encodeDelta(delta) {
|
||||
return delta > 0x7FFF ? delta - 0x10000 : delta < -0x7FFF ? delta + 0x10000 : delta;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据bound获取glyf segment
|
||||
*
|
||||
* @param {Array} glyfUnicodes glyf编码集合
|
||||
* @param {number} bound 编码范围
|
||||
* @return {Array} 码表
|
||||
*/
|
||||
function getSegments(glyfUnicodes, bound) {
|
||||
var prevGlyph = null;
|
||||
var result = [];
|
||||
var segment = {};
|
||||
glyfUnicodes.forEach(function (glyph) {
|
||||
for (var i = 0, l = glyfUnicodes.length; i < l; i++) {
|
||||
var glyph = glyfUnicodes[i];
|
||||
if (bound === undefined || glyph.unicode <= bound) {
|
||||
// 初始化编码头部,这里unicode和graph id 都必须连续
|
||||
if (prevGlyph === null || glyph.unicode !== prevGlyph.unicode + 1 || glyph.id !== prevGlyph.id + 1) {
|
||||
if (prevGlyph !== null) {
|
||||
segment.end = prevGlyph.unicode;
|
||||
@ -53,65 +37,46 @@ function getSegments(glyfUnicodes, bound) {
|
||||
}
|
||||
prevGlyph = glyph;
|
||||
}
|
||||
});
|
||||
|
||||
// need to finish the last segment
|
||||
}
|
||||
if (prevGlyph !== null) {
|
||||
segment.end = prevGlyph.unicode;
|
||||
result.push(segment);
|
||||
}
|
||||
|
||||
// 返回编码范围
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取format0编码集合
|
||||
*
|
||||
* @param {Array} glyfUnicodes glyf编码集合
|
||||
* @return {Array} 码表
|
||||
*/
|
||||
function getFormat0Segment(glyfUnicodes) {
|
||||
var unicodes = [];
|
||||
glyfUnicodes.forEach(function (u) {
|
||||
for (var i = 0, l = glyfUnicodes.length; i < l; i++) {
|
||||
var u = glyfUnicodes[i];
|
||||
if (u.unicode !== undefined && u.unicode < 256) {
|
||||
unicodes.push([u.unicode, u.id]);
|
||||
}
|
||||
});
|
||||
|
||||
// 按编码排序
|
||||
unicodes.sort(function (a, b) {
|
||||
return a[0] - b[0];
|
||||
});
|
||||
}
|
||||
unicodes.sort(function (a, b) { return a[0] - b[0]; });
|
||||
return unicodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对cmap数据进行预处理,获取大小
|
||||
*
|
||||
* @param {Object} ttf ttf对象
|
||||
* @return {number} 大小
|
||||
*/
|
||||
function sizeof(ttf) {
|
||||
ttf.support.cmap = {};
|
||||
var glyfUnicodes = [];
|
||||
ttf.glyf.forEach(function (glyph, index) {
|
||||
var glyfs = ttf.glyf;
|
||||
for (var index = 0, gl = glyfs.length; index < gl; index++) {
|
||||
var glyph = glyfs[index];
|
||||
var unicodes = glyph.unicode;
|
||||
if (typeof glyph.unicode === 'number') {
|
||||
unicodes = [glyph.unicode];
|
||||
}
|
||||
if (unicodes && unicodes.length) {
|
||||
unicodes.forEach(function (unicode) {
|
||||
for (var ui = 0, ul = unicodes.length; ui < ul; ui++) {
|
||||
glyfUnicodes.push({
|
||||
unicode: unicode,
|
||||
id: unicode !== 0xFFFF ? index : 0
|
||||
unicode: unicodes[ui],
|
||||
id: unicodes[ui] !== 0xFFFF ? index : 0
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
glyfUnicodes = glyfUnicodes.sort(function (a, b) {
|
||||
return a.unicode - b.unicode;
|
||||
});
|
||||
}
|
||||
glyfUnicodes.sort(function (a, b) { return a.unicode - b.unicode; });
|
||||
ttf.support.cmap.unicodes = glyfUnicodes;
|
||||
var unicodes2Bytes = glyfUnicodes;
|
||||
ttf.support.cmap.format4Segments = getSegments(unicodes2Bytes, 0xFFFF);
|
||||
@ -119,20 +84,23 @@ function sizeof(ttf) {
|
||||
ttf.support.cmap.format0Segments = getFormat0Segment(glyfUnicodes);
|
||||
ttf.support.cmap.format0Size = 262;
|
||||
|
||||
// we need subtable 12 only if found unicodes with > 2 bytes.
|
||||
var hasGLyphsOver2Bytes = unicodes2Bytes.some(function (glyph) {
|
||||
return glyph.unicode > 0xFFFF;
|
||||
});
|
||||
var hasGLyphsOver2Bytes = false;
|
||||
for (var gi = 0, gil = unicodes2Bytes.length; gi < gil; gi++) {
|
||||
if (unicodes2Bytes[gi].unicode > 0xFFFF) {
|
||||
hasGLyphsOver2Bytes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasGLyphsOver2Bytes) {
|
||||
ttf.support.cmap.hasGLyphsOver2Bytes = hasGLyphsOver2Bytes;
|
||||
ttf.support.cmap.hasGLyphsOver2Bytes = true;
|
||||
var unicodes4Bytes = glyfUnicodes;
|
||||
ttf.support.cmap.format12Segments = getSegments(unicodes4Bytes);
|
||||
ttf.support.cmap.format12Size = 16 + ttf.support.cmap.format12Segments.length * 12;
|
||||
}
|
||||
var size = 4 + (hasGLyphsOver2Bytes ? 32 : 24) // cmap header
|
||||
+ ttf.support.cmap.format0Size // format 0
|
||||
+ ttf.support.cmap.format4Size // format 4
|
||||
+ (hasGLyphsOver2Bytes ? ttf.support.cmap.format12Size : 0); // format 12
|
||||
var size = 4 + (hasGLyphsOver2Bytes ? 32 : 24)
|
||||
+ ttf.support.cmap.format0Size
|
||||
+ ttf.support.cmap.format4Size
|
||||
+ (hasGLyphsOver2Bytes ? ttf.support.cmap.format12Size : 0);
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
171
vendor/fonteditor-core/lib/ttf/table/cmap/write.js
vendored
171
vendor/fonteditor-core/lib/ttf/table/cmap/write.js
vendored
@ -9,156 +9,111 @@ exports.default = write;
|
||||
* @author mengke01(kekee000@gmail.com)
|
||||
*/
|
||||
|
||||
/**
|
||||
* 创建`子表0`
|
||||
*
|
||||
* @param {Writer} writer 写对象
|
||||
* @param {Array} unicodes unicodes列表
|
||||
* @return {Writer}
|
||||
*/
|
||||
function writeSubTable0(writer, unicodes) {
|
||||
writer.writeUint16(0); // format
|
||||
writer.writeUint16(262); // length
|
||||
writer.writeUint16(0); // language
|
||||
var pos = writer.offset;
|
||||
var view = writer.view;
|
||||
view.setUint16(pos, 0, false); pos += 2;
|
||||
view.setUint16(pos, 262, false); pos += 2;
|
||||
view.setUint16(pos, 0, false); pos += 2;
|
||||
|
||||
// Array of unicodes 0..255
|
||||
var i = -1;
|
||||
var unicode;
|
||||
while (unicode = unicodes.shift()) {
|
||||
for (var j = 0; j < unicodes.length; j++) {
|
||||
var unicode = unicodes[j];
|
||||
while (++i < unicode[0]) {
|
||||
writer.writeUint8(0);
|
||||
view.setUint8(pos++, 0);
|
||||
}
|
||||
writer.writeUint8(unicode[1]);
|
||||
view.setUint8(pos++, unicode[1]);
|
||||
i = unicode[0];
|
||||
}
|
||||
while (++i < 256) {
|
||||
writer.writeUint8(0);
|
||||
view.setUint8(pos++, 0);
|
||||
}
|
||||
writer.offset = pos;
|
||||
return writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建`子表4`
|
||||
*
|
||||
* @param {Writer} writer 写对象
|
||||
* @param {Array} segments 分块编码列表
|
||||
* @return {Writer}
|
||||
*/
|
||||
function writeSubTable4(writer, segments) {
|
||||
writer.writeUint16(4); // format
|
||||
writer.writeUint16(24 + segments.length * 8); // length
|
||||
writer.writeUint16(0); // language
|
||||
|
||||
var pos = writer.offset;
|
||||
var view = writer.view;
|
||||
var segCount = segments.length + 1;
|
||||
var maxExponent = Math.floor(Math.log(segCount) / Math.LN2);
|
||||
var searchRange = 2 * Math.pow(2, maxExponent);
|
||||
writer.writeUint16(segCount * 2); // segCountX2
|
||||
writer.writeUint16(searchRange); // searchRange
|
||||
writer.writeUint16(maxExponent); // entrySelector
|
||||
writer.writeUint16(2 * segCount - searchRange); // rangeShift
|
||||
|
||||
// end list
|
||||
segments.forEach(function (segment) {
|
||||
writer.writeUint16(segment.end);
|
||||
});
|
||||
writer.writeUint16(0xFFFF); // end code
|
||||
writer.writeUint16(0); // reservedPad
|
||||
view.setUint16(pos, 4, false); pos += 2;
|
||||
view.setUint16(pos, 24 + segments.length * 8, false); pos += 2;
|
||||
view.setUint16(pos, 0, false); pos += 2;
|
||||
view.setUint16(pos, segCount * 2, false); pos += 2;
|
||||
view.setUint16(pos, searchRange, false); pos += 2;
|
||||
view.setUint16(pos, maxExponent, false); pos += 2;
|
||||
view.setUint16(pos, 2 * segCount - searchRange, false); pos += 2;
|
||||
|
||||
// start list
|
||||
segments.forEach(function (segment) {
|
||||
writer.writeUint16(segment.start);
|
||||
});
|
||||
writer.writeUint16(0xFFFF); // start code
|
||||
|
||||
// id delta
|
||||
segments.forEach(function (segment) {
|
||||
writer.writeUint16(segment.delta);
|
||||
});
|
||||
writer.writeUint16(1);
|
||||
|
||||
// Array of range offsets, it doesn't matter when deltas present
|
||||
for (var i = 0, l = segments.length; i < l; i++) {
|
||||
writer.writeUint16(0);
|
||||
for (var i = 0; i < segments.length; i++) {
|
||||
view.setUint16(pos, segments[i].end, false); pos += 2;
|
||||
}
|
||||
writer.writeUint16(0); // rangeOffsetArray should be finished with 0
|
||||
view.setUint16(pos, 0xFFFF, false); pos += 2;
|
||||
view.setUint16(pos, 0, false); pos += 2;
|
||||
|
||||
for (var j = 0; j < segments.length; j++) {
|
||||
view.setUint16(pos, segments[j].start, false); pos += 2;
|
||||
}
|
||||
view.setUint16(pos, 0xFFFF, false); pos += 2;
|
||||
|
||||
for (var k = 0; k < segments.length; k++) {
|
||||
view.setUint16(pos, segments[k].delta, false); pos += 2;
|
||||
}
|
||||
view.setUint16(pos, 1, false); pos += 2;
|
||||
|
||||
for (var m = 0; m < segments.length; m++) {
|
||||
view.setUint16(pos, 0, false); pos += 2;
|
||||
}
|
||||
view.setUint16(pos, 0, false); pos += 2;
|
||||
|
||||
writer.offset = pos;
|
||||
return writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建`子表12`
|
||||
*
|
||||
* @param {Writer} writer 写对象
|
||||
* @param {Array} segments 分块编码列表
|
||||
* @return {Writer}
|
||||
*/
|
||||
function writeSubTable12(writer, segments) {
|
||||
writer.writeUint16(12); // format
|
||||
writer.writeUint16(0); // reserved
|
||||
writer.writeUint32(16 + segments.length * 12); // length
|
||||
writer.writeUint32(0); // language
|
||||
writer.writeUint32(segments.length); // nGroups
|
||||
var pos = writer.offset;
|
||||
var view = writer.view;
|
||||
view.setUint16(pos, 12, false); pos += 2;
|
||||
view.setUint16(pos, 0, false); pos += 2;
|
||||
view.setUint32(pos, 16 + segments.length * 12, false); pos += 4;
|
||||
view.setUint32(pos, 0, false); pos += 4;
|
||||
view.setUint32(pos, segments.length, false); pos += 4;
|
||||
|
||||
segments.forEach(function (segment) {
|
||||
writer.writeUint32(segment.start);
|
||||
writer.writeUint32(segment.end);
|
||||
writer.writeUint32(segment.startId);
|
||||
});
|
||||
for (var i = 0; i < segments.length; i++) {
|
||||
view.setUint32(pos, segments[i].start, false); pos += 4;
|
||||
view.setUint32(pos, segments[i].end, false); pos += 4;
|
||||
view.setUint32(pos, segments[i].startId, false); pos += 4;
|
||||
}
|
||||
writer.offset = pos;
|
||||
return writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写subtableheader
|
||||
*
|
||||
* @param {Writer} writer Writer对象
|
||||
* @param {number} platform 平台
|
||||
* @param {number} encoding 编码
|
||||
* @param {number} offset 偏移
|
||||
* @return {Writer}
|
||||
*/
|
||||
function writeSubTableHeader(writer, platform, encoding, offset) {
|
||||
writer.writeUint16(platform); // platform
|
||||
writer.writeUint16(encoding); // encoding
|
||||
writer.writeUint32(offset); // offset
|
||||
return writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写cmap表数据
|
||||
*
|
||||
* @param {Object} writer 写入器
|
||||
* @param {Object} ttf ttf对象
|
||||
* @return {Object} 写入器
|
||||
*/
|
||||
function write(writer, ttf) {
|
||||
var hasGLyphsOver2Bytes = ttf.support.cmap.hasGLyphsOver2Bytes;
|
||||
var pos = writer.offset;
|
||||
var view = writer.view;
|
||||
|
||||
// write table header.
|
||||
writer.writeUint16(0); // version
|
||||
writer.writeUint16(hasGLyphsOver2Bytes ? 4 : 3); // count
|
||||
view.setUint16(pos, 0, false); pos += 2;
|
||||
view.setUint16(pos, hasGLyphsOver2Bytes ? 4 : 3, false); pos += 2;
|
||||
writer.offset = pos;
|
||||
|
||||
// header size
|
||||
var subTableOffset = 4 + (hasGLyphsOver2Bytes ? 32 : 24);
|
||||
var format4Size = ttf.support.cmap.format4Size;
|
||||
var format0Size = ttf.support.cmap.format0Size;
|
||||
|
||||
// subtable 4, unicode
|
||||
writeSubTableHeader(writer, 0, 3, subTableOffset);
|
||||
|
||||
// subtable 0, mac standard
|
||||
writeSubTableHeader(writer, 1, 0, subTableOffset + format4Size);
|
||||
|
||||
// subtable 4, windows standard
|
||||
writeSubTableHeader(writer, 3, 1, subTableOffset);
|
||||
writer.writeUint16(0); writer.writeUint16(3); writer.writeUint32(subTableOffset);
|
||||
writer.writeUint16(1); writer.writeUint16(0); writer.writeUint32(subTableOffset + format4Size);
|
||||
writer.writeUint16(3); writer.writeUint16(1); writer.writeUint32(subTableOffset);
|
||||
if (hasGLyphsOver2Bytes) {
|
||||
writeSubTableHeader(writer, 3, 10, subTableOffset + format4Size + format0Size);
|
||||
writer.writeUint16(3); writer.writeUint16(10); writer.writeUint32(subTableOffset + format4Size + format0Size);
|
||||
}
|
||||
|
||||
// write tables, order of table seem to be magic, it is taken from TTX tool
|
||||
writeSubTable4(writer, ttf.support.cmap.format4Segments);
|
||||
writeSubTable0(writer, ttf.support.cmap.format0Segments);
|
||||
if (hasGLyphsOver2Bytes) {
|
||||
writeSubTable12(writer, ttf.support.cmap.format12Segments);
|
||||
}
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
|
||||
63
vendor/fonteditor-core/lib/ttf/table/glyf.js
vendored
63
vendor/fonteditor-core/lib/ttf/table/glyf.js
vendored
@ -24,47 +24,43 @@ var _default = exports.default = _table.default.create('glyf', [], {
|
||||
var glyphs = [];
|
||||
reader.seek(startOffset);
|
||||
|
||||
// subset
|
||||
/* subset */
|
||||
var subset = ttf.readOptions.subset;
|
||||
if (subset && subset.length > 0) {
|
||||
var subsetMap = {
|
||||
0: true // 设置.notdef
|
||||
};
|
||||
subsetMap[0] = true;
|
||||
// subset map
|
||||
/* 优化1: subset 转为 Set,O(1) 查找 */
|
||||
var subsetSet = new Set(subset);
|
||||
var subsetMap = { 0: true };
|
||||
var cmap = ttf.cmap;
|
||||
|
||||
// unicode to index
|
||||
Object.keys(cmap).forEach(function (c) {
|
||||
if (subset.indexOf(+c) > -1) {
|
||||
var _i = cmap[c];
|
||||
subsetMap[_i] = true;
|
||||
/* 优化23: for...in 替代 Object.keys + forEach */
|
||||
for (var c in cmap) {
|
||||
if (subsetSet.has(+c)) {
|
||||
subsetMap[cmap[c]] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
ttf.subsetMap = subsetMap;
|
||||
var parsedGlyfMap = {};
|
||||
// 循环解析subset相关的glyf,包括复合字形相关的字形
|
||||
var travelsParse = function travels(subsetMap) {
|
||||
|
||||
/* 优化43: for...in + for 循环替代 Object.keys + forEach */
|
||||
var travelsParse = function travels(sMap) {
|
||||
var newSubsetMap = {};
|
||||
Object.keys(subsetMap).forEach(function (i) {
|
||||
var index = +i;
|
||||
for (var idx in sMap) {
|
||||
var index = +idx;
|
||||
parsedGlyfMap[index] = true;
|
||||
// 当前的和下一个一样,或者最后一个无轮廓
|
||||
if (loca[index] === loca[index + 1]) {
|
||||
glyphs[index] = {
|
||||
contours: []
|
||||
};
|
||||
glyphs[index] = { contours: [] };
|
||||
} else {
|
||||
glyphs[index] = (0, _parse.default)(reader, ttf, startOffset + loca[index]);
|
||||
}
|
||||
if (glyphs[index].compound) {
|
||||
glyphs[index].glyfs.forEach(function (g) {
|
||||
if (!parsedGlyfMap[g.glyphIndex]) {
|
||||
newSubsetMap[g.glyphIndex] = true;
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!(0, _lang.isEmptyObject)(newSubsetMap)) {
|
||||
travels(newSubsetMap);
|
||||
}
|
||||
@ -73,25 +69,18 @@ var _default = exports.default = _table.default.create('glyf', [], {
|
||||
return glyphs;
|
||||
}
|
||||
|
||||
// 解析字体轮廓, 前n-1个
|
||||
var i;
|
||||
var l;
|
||||
for (i = 0, l = numGlyphs - 1; i < l; i++) {
|
||||
// 当前的和下一个一样,或者最后一个无轮廓
|
||||
/* 解析字体轮廓, 前n-1个 */
|
||||
for (var i = 0, l = numGlyphs - 1; i < l; i++) {
|
||||
if (loca[i] === loca[i + 1]) {
|
||||
glyphs[i] = {
|
||||
contours: []
|
||||
};
|
||||
glyphs[i] = { contours: [] };
|
||||
} else {
|
||||
glyphs[i] = (0, _parse.default)(reader, ttf, startOffset + loca[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// 最后一个轮廓
|
||||
/* 最后一个轮廓 */
|
||||
if (ttf.tables.glyf.length - loca[i] < 5) {
|
||||
glyphs[i] = {
|
||||
contours: []
|
||||
};
|
||||
glyphs[i] = { contours: [] };
|
||||
} else {
|
||||
glyphs[i] = (0, _parse.default)(reader, ttf, startOffset + loca[i]);
|
||||
}
|
||||
|
||||
@ -38,14 +38,14 @@ function compound2simpleglyf(glyf, ttf, recrusive) {
|
||||
var contoursList = {};
|
||||
(0, _transformGlyfContours.default)(glyf, ttf, contoursList, glyfIndex);
|
||||
if (recrusive) {
|
||||
Object.keys(contoursList).forEach(function (index) {
|
||||
var target = ttf.glyf[index];
|
||||
(0, _compound2simple.default)(target, contoursList[index]);
|
||||
/* 优化66: 检测扁平格式并设置标记 */
|
||||
/* 优化62: for...in 替代 Object.keys + forEach */
|
||||
for (var idx in contoursList) {
|
||||
var target = ttf.glyf[idx];
|
||||
(0, _compound2simple.default)(target, contoursList[idx]);
|
||||
if (target.contours && target.contours.length && typeof target.contours[0][0] === 'number') {
|
||||
target._flatContours = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
(0, _compound2simple.default)(glyf, contoursList[glyfIndex]);
|
||||
/* 优化66: 检测扁平格式并设置标记 */
|
||||
|
||||
@ -48,53 +48,59 @@ function ceilAndReduceFlat(glyf) {
|
||||
* @return {true|Object} 错误信息
|
||||
*/
|
||||
function optimizettf(ttf) {
|
||||
var checkUnicodeRepeat = {}; // 检查是否有重复代码点
|
||||
var checkUnicodeRepeat = {};
|
||||
var repeatList = [];
|
||||
ttf.glyf.forEach(function (glyf, index) {
|
||||
/* 优化2+45+62: for 循环替代 forEach,只对 length>1 的 unicode 排序 */
|
||||
var glyfs = ttf.glyf;
|
||||
for (var index = 0, gl = glyfs.length; index < gl; index++) {
|
||||
var glyf = glyfs[index];
|
||||
if (glyf.unicode) {
|
||||
glyf.unicode = glyf.unicode.sort();
|
||||
|
||||
// 将glyf的代码点按小到大排序
|
||||
glyf.unicode.sort(function (a, b) {
|
||||
return a - b;
|
||||
}).forEach(function (u) {
|
||||
/* 优化2: 删除第一次默认排序,只保留数字排序 */
|
||||
if (glyf.unicode.length > 1) {
|
||||
glyf.unicode.sort(function (a, b) { return a - b; });
|
||||
}
|
||||
var unicode = glyf.unicode;
|
||||
for (var ui = 0, ul = unicode.length; ui < ul; ui++) {
|
||||
var u = unicode[ui];
|
||||
if (checkUnicodeRepeat[u]) {
|
||||
repeatList.push(index);
|
||||
} else {
|
||||
checkUnicodeRepeat[u] = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!glyf.compound && glyf.contours) {
|
||||
if (glyf._flatContours) {
|
||||
/* 优化17+66: 扁平格式单次遍历 pathCeil + reduceGlyf */
|
||||
ceilAndReduceFlat(glyf);
|
||||
} else {
|
||||
/* 整数化 */
|
||||
glyf.contours.forEach(function (contour) {
|
||||
(0, _pathCeil.default)(contour);
|
||||
});
|
||||
/* 缩减glyf */
|
||||
(0, _reduceGlyf.default)(glyf);
|
||||
}
|
||||
}
|
||||
|
||||
// 整数化
|
||||
glyf.xMin = Math.round(glyf.xMin || 0);
|
||||
glyf.xMax = Math.round(glyf.xMax || 0);
|
||||
glyf.yMin = Math.round(glyf.yMin || 0);
|
||||
glyf.yMax = Math.round(glyf.yMax || 0);
|
||||
glyf.leftSideBearing = Math.round(glyf.leftSideBearing || 0);
|
||||
glyf.advanceWidth = Math.round(glyf.advanceWidth || 0);
|
||||
});
|
||||
}
|
||||
|
||||
// 过滤无轮廓字体,如果存在复合字形不进行过滤
|
||||
if (!ttf.glyf.some(function (a) {
|
||||
return a.compound;
|
||||
})) {
|
||||
ttf.glyf = ttf.glyf.filter(function (glyf, index) {
|
||||
return index === 0 || glyf.contours && glyf.contours.length;
|
||||
});
|
||||
/* 过滤无轮廓字体 */
|
||||
var hasCompound = false;
|
||||
for (var fi = 0, fl = glyfs.length; fi < fl; fi++) {
|
||||
if (glyfs[fi].compound) { hasCompound = true; break; }
|
||||
}
|
||||
if (!hasCompound) {
|
||||
var filtered = [glyfs[0]];
|
||||
for (var gi = 1; gi < gl; gi++) {
|
||||
if (glyfs[gi].contours && glyfs[gi].contours.length) {
|
||||
filtered.push(glyfs[gi]);
|
||||
}
|
||||
}
|
||||
ttf.glyf = filtered;
|
||||
}
|
||||
if (!repeatList.length) {
|
||||
return true;
|
||||
|
||||
@ -9,11 +9,54 @@ exports.default = readWindowsAllCodes;
|
||||
/**
|
||||
* @file 读取windows支持的字符集
|
||||
* @author mengke01(kekee000@gmail.com)
|
||||
*
|
||||
* @see
|
||||
* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cmap.html
|
||||
*/
|
||||
|
||||
/**
|
||||
* 优化65: format12 二分查找,避免全量展开
|
||||
*/
|
||||
function lookupFormat12(groups, unicode) {
|
||||
var lo = 0, hi = groups.length - 1;
|
||||
while (lo <= hi) {
|
||||
var mid = (lo + hi) >> 1;
|
||||
var g = groups[mid];
|
||||
if (unicode < g.start) {
|
||||
hi = mid - 1;
|
||||
} else if (unicode > g.end) {
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
return g.startId + (unicode - g.start);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化65: format4 线性查找 segment
|
||||
*/
|
||||
function lookupFormat4(format4, unicode) {
|
||||
var startCode = format4.startCode;
|
||||
var endCode = format4.endCode;
|
||||
var idDelta = format4.idDelta;
|
||||
var idRangeOffset = format4.idRangeOffset;
|
||||
var segCount = format4.segCountX2 / 2;
|
||||
|
||||
for (var i = 0; i < segCount; i++) {
|
||||
if (unicode >= startCode[i] && unicode <= endCode[i]) {
|
||||
if (idRangeOffset[i] === 0) {
|
||||
return (unicode + idDelta[i]) % 0x10000;
|
||||
}
|
||||
var graphIdArrayIndexOffset = (format4.glyphIdArrayOffset - format4.idRangeOffsetOffset) / 2;
|
||||
var index = i + idRangeOffset[i] / 2 + (unicode - startCode[i]) - graphIdArrayIndexOffset;
|
||||
var graphId = format4.glyphIdArray[index];
|
||||
if (graphId !== 0) {
|
||||
return (graphId + idDelta[i]) % 0x10000;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取ttf中windows字符表的字符
|
||||
*
|
||||
@ -23,49 +66,78 @@ exports.default = readWindowsAllCodes;
|
||||
*/
|
||||
function readWindowsAllCodes(tables, ttf) {
|
||||
var codes = {};
|
||||
var subset = ttf.readOptions && ttf.readOptions.subset;
|
||||
|
||||
// 读取windows unicode 编码段
|
||||
var format0 = tables.find(function (item) {
|
||||
return item.format === 0;
|
||||
});
|
||||
/* 优化65: 合并5次 tables.find 为单次遍历 */
|
||||
var format0 = null, format12 = null, format4 = null, format2 = null, format14 = null;
|
||||
for (var fi = 0; fi < tables.length; fi++) {
|
||||
var t = tables[fi];
|
||||
if (t.format === 0 && !format0) format0 = t;
|
||||
else if (t.platformID === 3 && t.encodingID === 10 && t.format === 12 && !format12) format12 = t;
|
||||
else if (t.platformID === 3 && t.encodingID === 1 && t.format === 4 && !format4) format4 = t;
|
||||
else if (t.platformID === 3 && t.encodingID === 3 && t.format === 2 && !format2) format2 = t;
|
||||
else if (t.platformID === 0 && t.encodingID === 5 && t.format === 14 && !format14) format14 = t;
|
||||
}
|
||||
|
||||
// 读取windows unicode 编码段
|
||||
var format12 = tables.find(function (item) {
|
||||
return item.platformID === 3 && item.encodingID === 10 && item.format === 12;
|
||||
});
|
||||
var format4 = tables.find(function (item) {
|
||||
return item.platformID === 3 && item.encodingID === 1 && item.format === 4;
|
||||
});
|
||||
var format2 = tables.find(function (item) {
|
||||
return item.platformID === 3 && item.encodingID === 3 && item.format === 2;
|
||||
});
|
||||
var format14 = tables.find(function (item) {
|
||||
return item.platformID === 0 && item.encodingID === 5 && item.format === 14;
|
||||
});
|
||||
/* 优化65: subset 模式 - 只查找 subset 字符的 glyphId */
|
||||
if (subset && subset.length > 0) {
|
||||
if (format12) {
|
||||
for (var si = 0, sl = subset.length; si < sl; si++) {
|
||||
var u = subset[si];
|
||||
if (u < 0x10000 && format4) {
|
||||
var gid = lookupFormat4(format4, u);
|
||||
if (gid >= 0) { codes[u] = gid; continue; }
|
||||
}
|
||||
var gid12 = lookupFormat12(format12.groups, u);
|
||||
if (gid12 >= 0) { codes[u] = gid12; }
|
||||
}
|
||||
} else if (format4) {
|
||||
for (var si2 = 0, sl2 = subset.length; si2 < sl2; si2++) {
|
||||
var u2 = subset[si2];
|
||||
var gid4 = lookupFormat4(format4, u2);
|
||||
if (gid4 >= 0) { codes[u2] = gid4; }
|
||||
}
|
||||
}
|
||||
|
||||
/* format0 和 format14 仍然需要全量处理(数据量小) */
|
||||
if (format0) {
|
||||
for (var i = 0, l = format0.glyphIdArray.length; i < l; i++) {
|
||||
if (format0.glyphIdArray[i]) {
|
||||
codes[i] = format0.glyphIdArray[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (format14) {
|
||||
for (var vi = 0, vl = format14.groups.length; vi < vl; vi++) {
|
||||
var vg = format14.groups[vi];
|
||||
if (vg.unicode) {
|
||||
codes[vg.unicode] = vg.glyphId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return codes;
|
||||
}
|
||||
|
||||
/* 非subset模式 - 全量展开(原始逻辑) */
|
||||
if (format0) {
|
||||
for (var i = 0, l = format0.glyphIdArray.length; i < l; i++) {
|
||||
if (format0.glyphIdArray[i]) {
|
||||
codes[i] = format0.glyphIdArray[i];
|
||||
for (var i2 = 0, l2 = format0.glyphIdArray.length; i2 < l2; i2++) {
|
||||
if (format0.glyphIdArray[i2]) {
|
||||
codes[i2] = format0.glyphIdArray[i2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// format 14 support
|
||||
if (format14) {
|
||||
for (var _i = 0, _l = format14.groups.length; _i < _l; _i++) {
|
||||
var _format14$groups$_i = format14.groups[_i],
|
||||
unicode = _format14$groups$_i.unicode,
|
||||
glyphId = _format14$groups$_i.glyphId;
|
||||
if (unicode) {
|
||||
codes[unicode] = glyphId;
|
||||
for (var vi2 = 0, vl2 = format14.groups.length; vi2 < vl2; vi2++) {
|
||||
var vg2 = format14.groups[vi2];
|
||||
if (vg2.unicode) {
|
||||
codes[vg2.unicode] = vg2.glyphId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 读取format12表
|
||||
if (format12) {
|
||||
for (var _i2 = 0, _l2 = format12.nGroups; _i2 < _l2; _i2++) {
|
||||
var group = format12.groups[_i2];
|
||||
for (var gi = 0, gl = format12.nGroups; gi < gl; gi++) {
|
||||
var group = format12.groups[gi];
|
||||
var startId = group.startId;
|
||||
var start = group.start;
|
||||
var end = group.end;
|
||||
@ -73,25 +145,18 @@ function readWindowsAllCodes(tables, ttf) {
|
||||
codes[start++] = startId++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 读取format4表
|
||||
else if (format4) {
|
||||
} else if (format4) {
|
||||
var segCount = format4.segCountX2 / 2;
|
||||
// graphIdArray 和idRangeOffset的偏移量
|
||||
var graphIdArrayIndexOffset = (format4.glyphIdArrayOffset - format4.idRangeOffsetOffset) / 2;
|
||||
for (var _i3 = 0; _i3 < segCount; ++_i3) {
|
||||
// 读取单个字符
|
||||
for (var _start = format4.startCode[_i3], _end = format4.endCode[_i3]; _start <= _end; ++_start) {
|
||||
// range offset = 0
|
||||
if (format4.idRangeOffset[_i3] === 0) {
|
||||
codes[_start] = (_start + format4.idDelta[_i3]) % 0x10000;
|
||||
}
|
||||
// rely on to glyphIndexArray
|
||||
else {
|
||||
var index = _i3 + format4.idRangeOffset[_i3] / 2 + (_start - format4.startCode[_i3]) - graphIdArrayIndexOffset;
|
||||
for (var si3 = 0; si3 < segCount; ++si3) {
|
||||
for (var _start = format4.startCode[si3], _end = format4.endCode[si3]; _start <= _end; ++_start) {
|
||||
if (format4.idRangeOffset[si3] === 0) {
|
||||
codes[_start] = (_start + format4.idDelta[si3]) % 0x10000;
|
||||
} else {
|
||||
var index = si3 + format4.idRangeOffset[si3] / 2 + (_start - format4.startCode[si3]) - graphIdArrayIndexOffset;
|
||||
var graphId = format4.glyphIdArray[index];
|
||||
if (graphId !== 0) {
|
||||
codes[_start] = (graphId + format4.idDelta[_i3]) % 0x10000;
|
||||
codes[_start] = (graphId + format4.idDelta[si3]) % 0x10000;
|
||||
} else {
|
||||
codes[_start] = 0;
|
||||
}
|
||||
@ -99,32 +164,26 @@ function readWindowsAllCodes(tables, ttf) {
|
||||
}
|
||||
}
|
||||
delete codes[65535];
|
||||
}
|
||||
// 读取format2表
|
||||
// see https://github.com/fontforge/fontforge/blob/master/fontforge/parsettf.c
|
||||
else if (format2) {
|
||||
} else if (format2) {
|
||||
var subHeadKeys = format2.subHeadKeys;
|
||||
var subHeads = format2.subHeads;
|
||||
var glyphs = format2.glyphs;
|
||||
var numGlyphs = ttf.maxp.numGlyphs;
|
||||
var _index = 0;
|
||||
for (var _i4 = 0; _i4 < 256; _i4++) {
|
||||
// 单字节编码
|
||||
if (subHeadKeys[_i4] === 0) {
|
||||
if (_i4 >= format2.maxPos) {
|
||||
for (var bi = 0; bi < 256; bi++) {
|
||||
if (subHeadKeys[bi] === 0) {
|
||||
if (bi >= format2.maxPos) {
|
||||
_index = 0;
|
||||
} else if (_i4 < subHeads[0].firstCode || _i4 >= subHeads[0].firstCode + subHeads[0].entryCount || subHeads[0].idRangeOffset + (_i4 - subHeads[0].firstCode) >= glyphs.length) {
|
||||
} else if (bi < subHeads[0].firstCode || bi >= subHeads[0].firstCode + subHeads[0].entryCount || subHeads[0].idRangeOffset + (bi - subHeads[0].firstCode) >= glyphs.length) {
|
||||
_index = 0;
|
||||
} else if ((_index = glyphs[subHeads[0].idRangeOffset + (_i4 - subHeads[0].firstCode)]) !== 0) {
|
||||
} else if ((_index = glyphs[subHeads[0].idRangeOffset + (bi - subHeads[0].firstCode)]) !== 0) {
|
||||
_index = _index + subHeads[0].idDelta;
|
||||
}
|
||||
|
||||
// 单字节解码
|
||||
if (_index !== 0 && _index < numGlyphs) {
|
||||
codes[_i4] = _index;
|
||||
codes[bi] = _index;
|
||||
}
|
||||
} else {
|
||||
var k = subHeadKeys[_i4];
|
||||
var k = subHeadKeys[bi];
|
||||
for (var j = 0, entryCount = subHeads[k].entryCount; j < entryCount; j++) {
|
||||
if (subHeads[k].idRangeOffset + j >= glyphs.length) {
|
||||
_index = 0;
|
||||
@ -132,7 +191,7 @@ function readWindowsAllCodes(tables, ttf) {
|
||||
_index = _index + subHeads[k].idDelta;
|
||||
}
|
||||
if (_index !== 0 && _index < numGlyphs) {
|
||||
var _unicode = (_i4 << 8 | j + subHeads[k].firstCode) % 0xffff;
|
||||
var _unicode = (bi << 8 | j + subHeads[k].firstCode) % 0xffff;
|
||||
codes[_unicode] = _index;
|
||||
}
|
||||
}
|
||||
@ -140,4 +199,4 @@ function readWindowsAllCodes(tables, ttf) {
|
||||
}
|
||||
}
|
||||
return codes;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user