perf(hmtx): subset 模式紧凑存储,消除 numGlyphs*2 大对象分配

subset 模式下 hmtx 改为按 subsetGids 顺序紧凑存储 Int32Array(S*2),
下标用 gi*2(subsetGids 内位置)替代 gid*2。原实现分配 Int32Array(numGlyphs*2),
CJK 字体 numGlyphs 达 3.5 万→284KB 大对象,V8 大对象分配+zero-fill 单次 ~54μs。

resolveGlyf subset 分支同步改用 gi*2 索引。OTF 路径不变(表顺序 hmtx 在 CFF 前,
subsetGids 未就绪,仍走全量数组)。

字节逐字节一致(令东/思源/初夏/得意黑/otf 全 cmp 通过),SSIM 全程不变。
初夏 hmtx 0.056→0.002ms(-96%),create -15%,fontSubset 总 -7%。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
崮生(子虚) 2026-07-24 10:52:30 +08:00
parent aa2818b13e
commit b7dbe3fe1d
2 changed files with 16 additions and 10 deletions

View File

@ -18,22 +18,24 @@ var _default = exports.default = _table.default.create('hmtx', [], {
reader.seek(offset);
var numOfLongHorMetrics = ttf.hhea.numOfLongHorMetrics;
var numGlyphs = ttf.maxp.numGlyphs;
/* 优化10+82: 扁平数组 [advW, lsb, advW, lsb, ...],消除对象分配 */
var hMetrics = new Int32Array(numGlyphs * 2);
var view = reader.view;
var vOffset = view.byteOffset + offset;
/* subset subset ttf.subsetGids glyf.read hmtx
* resolveGlyf 也只访问 subsetGids 对应的 hmtx[gid*2]/[gid*2+1]
* 原实现全量解析 numOfLongHorMetrics 思源 30888 Font.create ~10% 8 字子集是纯浪费
* 改为只读 subsetGids 需要的项其余位置保持 0不会被访问 */
/* subset subset ttf.subsetGids glyf.read hmtx
* subsetGids 顺序紧凑存储为 Int32Array(S*2)下标用 gi*2subsetGids 内的位置而非 gid*2
* resolveGlyf/otfreader subset 分支同样用 gi*2 索引
* 原实现分配 Int32Array(numGlyphs*2)CJK 字体 numGlyphs 3.5 284KB 大对象
* V8 大对象分配+zero-fill 单次 ~54μs占初夏 hmtx 全部 0.056ms紧凑存储仅 S*2 初夏 13104B
* 分配降到 ~0.2μs只读 subsetGids 需要的项全量解析 numOfLongHorMetrics 对小子集是纯浪费 */
var subsetGids = ttf.readOptions && ttf.readOptions.subset && ttf.readOptions.subset.length > 0 ? ttf.subsetGids : null;
if (subsetGids) {
var sl = subsetGids.length;
var hMetrics = new Int32Array(sl * 2);
/* Last 段gid >= numOfLongHorMetrics的起始字节偏移共享 advW = 最后一个 LongHorMetric 的 advW */
var lastSegOff = vOffset + numOfLongHorMetrics * 4;
var lastAdvWOff = vOffset + (numOfLongHorMetrics - 1) * 4;
for (var si = 0, sl = subsetGids.length; si < sl; si++) {
for (var si = 0; si < sl; si++) {
var gid = subsetGids[si];
var idx = gid * 2;
var idx = si * 2;
if (gid < numOfLongHorMetrics) {
/* LongHorMetric 段advW + lsb 各 2 字节 */
var gOff = vOffset + gid * 4;
@ -48,6 +50,8 @@ var _default = exports.default = _table.default.create('hmtx', [], {
reader.offset = offset + numOfLongHorMetrics * 4 + (numGlyphs - numOfLongHorMetrics) * 2;
return hMetrics;
}
/* 全量模式:扁平数组 [advW, lsb, advW, lsb, ...],下标 gid*2与 resolveGlyf 全量分支一致) */
var hMetrics = new Int32Array(numGlyphs * 2);
/* TypedArray +hmtx DataView.getUint16/getInt16
* 有边界检查开销LongHorMetric 每项 u16 advW + i16 lsb Uint16Array view 共享 buffer
* 读取后内联翻转实测思源黑体30888 2×19898μsoffset 2 字节对齐

View File

@ -127,12 +127,14 @@ var TTFReader = exports.default = /*#__PURE__*/function () {
}
}
/* 优化13+82+118: advanceWidth 遍历优化,使用密集数组 */
/* 13+82+118: advanceWidth 使
* 优化hmtx 紧凑存储subset 模式下 hmtx subsetGids 顺序存储Int32Array(S*2)
* 下标用 gi*2subsetGids 内位置而非原 gid*2初夏 hmtx 0.056~0.002ms */
var hmtx = ttf.hmtx;
if (subsetGids) {
for (var gi = 0, gl = subsetGids.length; gi < gl; gi++) {
var idxNum = subsetGids[gi];
var hIdx = idxNum * 2;
var hIdx = gi * 2;
glyf[idxNum].advanceWidth = hmtx[hIdx];
glyf[idxNum].leftSideBearing = hmtx[hIdx + 1];
}