web-font/_p_perTable.ts
崮生(子虚) 8a7efea9d0 perf(gpos/gsub): read 零拷贝 subarray 替代 readBytes slice
GPOS/GSUB 表类 read 原用 reader.readBytes (= new Uint8Array(view.buffer, off, len).slice()),
.slice() 拷贝整个表字节(初夏 GPOS 68KB、GSUB 31KB)。V8 中 68KB slice 耗 ~40μs,
而 subarray 零拷贝仅 ~0.3μs(130× 差距)。

GPOS/GSUB 是原始字节透传:subsetGPOS/subsetGSUB 只读 OTReader + 写全新 OTWriter,
write 只 writeBytes 只读,optimize 不碰,整个 read→optimize→subset→write 生命周期
从不原地修改底层字节,故可安全返回共享 ArrayBuffer 的 subarray 视图。

初夏 full read 0.124→0.063ms (-49%),readBuffer 0.165→0.098ms。
全基准 SSIM 与输出字节完全不变。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 15:07:18 +08:00

51 lines
2.4 KiB
TypeScript

import fs from 'node:fs';
import * as TTFReaderNS from './vendor/fonteditor-core/lib/ttf/ttfreader.js';
const TTFReader: any = TTFReaderNS.default.default ?? TTFReaderNS.default;
const support: any = (await import('./vendor/fonteditor-core/lib/ttf/table/support.js')).default.default ?? (await import('./vendor/fonteditor-core/lib/ttf/table/support.js')).default;
const buf = fs.readFileSync('/mnt/d/字体资源/初夏明朝/初夏明朝-Regular.ttf');
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset+buf.byteLength) as ArrayBuffer;
const cps = Array.from(',。!?、;:“”‘’').map(c => c.codePointAt(0)!);
// 先拿到 reader + tables 目录
const r0 = new (TTFReader as any)({ subset: cps, kerning: true });
// 手动复刻 readBuffer 前半,拿 reader + tables
const Reader = (await import('./vendor/fonteditor-core/lib/ttf/reader.js')).default.default ?? (await import('./vendor/fonteditor-core/lib/ttf/reader.js')).default;
const Directory = (await import('./vendor/fonteditor-core/lib/ttf/directory.js')).default.default ?? (await import('./vendor/fonteditor-core/lib/ttf/directory.js')).default;
function makeReader() {
const reader = new Reader(ab, 0, ab.byteLength, false);
reader.offset = 12;
return reader;
}
// 各表独立计时
function timeTable(tableName: string, iters = 15) {
const TableClass = support[tableName];
if (!TableClass) { console.log(` ${tableName}: 无 support 类`); return; }
// warmup + 计时
const ts: number[] = [];
for (let i = 0; i < 5; i++) {
const reader = makeReader();
const dir = new Directory(12).read(reader, {});
if (!dir[tableName]) return;
const tc = new TableClass(dir[tableName].offset);
tc.read(reader, { tables: dir, readOptions: { subset: cps, kerning: true } } as any);
}
for (let i = 0; i < iters; i++) {
const reader = makeReader();
const dir = new Directory(12).read(reader, {});
const tc = new TableClass(dir[tableName].offset);
const t0 = performance.now();
tc.read(reader, { tables: dir, readOptions: { subset: cps, kerning: true } } as any);
ts.push(performance.now() - t0);
}
ts.sort((a,b)=>a-b);
console.log(` ${tableName}: ${ts[Math.floor(ts.length/2)].toFixed(3)}ms (min ${ts[0].toFixed(3)})`);
}
console.log('=== 初夏标点 各表 read 耗时 ===');
for (const t of ['head','hhea','maxp','OS/2','name','cmap','hmtx','vmtx','GPOS','GSUB','post']) {
timeTable(t);
}