mirror of
https://github.com/2234839/web-font.git
synced 2026-09-07 00:02:40 +08:00
chore: 删除误提交的临时 profile 脚本
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b17d5a3700
commit
f0a5ac175f
16
_p_check.ts
16
_p_check.ts
@ -1,16 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
import { Font } from './vendor/fonteditor-core/lib/ttf/font.js';
|
||||
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(',。!?、;:“”‘’');
|
||||
const font = Font.create(ab, { type: 'ttf', subset: cps as any, kerning: true });
|
||||
const opt = (font as any).optimize();
|
||||
const ttf = opt.get();
|
||||
console.log('GPOS present:', !!ttf.GPOS, 'len:', ttf.GPOS && ttf.GPOS.length);
|
||||
console.log('GSUB present:', !!ttf.GSUB);
|
||||
console.log('numGlyphs:', ttf.glyf.length);
|
||||
const ttfBuf = opt.write({ type: 'ttf', hinting: false } as any);
|
||||
console.log('ttfBuf type:', ttfBuf.constructor.name, 'len:', ttfBuf.length);
|
||||
// 完整 woff2 via font.write
|
||||
const woff2 = opt.write({ type: 'woff2', hinting: false } as any);
|
||||
console.log('woff2 (font.write):', woff2.length, 'type:', woff2.constructor.name);
|
||||
61
_p_chxia.ts
61
_p_chxia.ts
@ -1,61 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
import { Font } from './vendor/fonteditor-core/lib/ttf/font.js';
|
||||
import { probeGsubAndCmap, collectReachableGsubTargets } from './backend/font_util/gsub-probe.js';
|
||||
|
||||
const fontBuf = fs.readFileSync('/mnt/d/字体资源/初夏明朝/初夏明朝-Regular.ttf');
|
||||
const ab = fontBuf.buffer.slice(fontBuf.byteOffset, fontBuf.byteOffset + fontBuf.byteLength) as ArrayBuffer;
|
||||
const text = ',。!?、;:“”‘’';
|
||||
const codePoints = Array.from(text);
|
||||
const option = { sourceType: 'ttf' as const, outType: 'woff2' as const };
|
||||
|
||||
function timeOnce<T>(label: string, fn: () => T, runs = 1): T {
|
||||
// warmup
|
||||
for (let i = 0; i < 3; i++) fn();
|
||||
const t0 = performance.now();
|
||||
let res: T;
|
||||
for (let i = 0; i < runs; i++) res = fn();
|
||||
const t1 = performance.now();
|
||||
console.log(`${label}: ${((t1 - t0) / runs).toFixed(2)}ms`);
|
||||
return res!;
|
||||
}
|
||||
|
||||
// 阶段拆解(best of 5)
|
||||
function median(arr: number[]) { return arr.slice().sort((a,b)=>a-b)[Math.floor(arr.length/2)]; }
|
||||
|
||||
function bench(label: string, fn: () => void, iters = 7) {
|
||||
for (let i = 0; i < 3; i++) fn(); // warmup
|
||||
const ts: number[] = [];
|
||||
for (let i = 0; i < iters; i++) {
|
||||
const t0 = performance.now();
|
||||
fn();
|
||||
ts.push(performance.now() - t0);
|
||||
}
|
||||
console.log(`${label}: ${median(ts).toFixed(3)}ms (min ${Math.min(...ts).toFixed(3)})`);
|
||||
}
|
||||
|
||||
// 1. probe
|
||||
bench('1.probe', () => probeGsubAndCmap(ab, codePoints, option.sourceType));
|
||||
|
||||
// 2. Font.create
|
||||
bench('2.Font.create', () => Font.create(ab, { type: option.sourceType, subset: codePoints as any, kerning: true }));
|
||||
|
||||
// 完整 Font.create 拿到对象
|
||||
const font = Font.create(ab, { type: option.sourceType, subset: codePoints as any, kerning: true });
|
||||
|
||||
// 3. optimize
|
||||
bench('3.optimize', () => (font as any).optimize());
|
||||
|
||||
// 4. write (woff2)
|
||||
const optimized = (font as any).optimize();
|
||||
bench('5.write(woff2)', () => {
|
||||
const f = Font.create(ab, { type: option.sourceType, subset: codePoints as any, kerning: true });
|
||||
const o = (f as any).optimize();
|
||||
o.write({ type: 'woff2', hinting: false } as any);
|
||||
});
|
||||
|
||||
// 端到端对比
|
||||
bench('E2E', () => {
|
||||
const f = Font.create(ab, { type: option.sourceType, subset: codePoints as any, kerning: true });
|
||||
const o = (f as any).optimize();
|
||||
o.write({ type: 'woff2', hinting: false } as any);
|
||||
}, 5);
|
||||
@ -1,9 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const ttf2woff2 = require('./vendor/fonteditor-core/lib/ttf2woff2.js');
|
||||
|
||||
const fontBuf = fs.readFileSync('./backend/font_util/标点测试字体/初夏明朝.ttf');
|
||||
const text = ',。!?、:;';
|
||||
|
||||
// 复刻 fontSubset 的关键阶段
|
||||
const { fontSubset } = require('./backend-ts-compiled.cjs');
|
||||
24
_p_e2e.ts
24
_p_e2e.ts
@ -1,24 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
import { fontSubset } from './backend/font_util/font.js';
|
||||
|
||||
function median(a: number[]) { return a.slice().sort((x,y)=>x-y)[Math.floor(a.length/2)]; }
|
||||
function bench(label: string, fn: () => void, iters = 15) {
|
||||
for (let i = 0; i < 5; i++) fn();
|
||||
const ts: number[] = [];
|
||||
for (let i = 0; i < iters; i++) { const t0 = performance.now(); fn(); ts.push(performance.now() - t0); }
|
||||
console.log(`${label}: ${median(ts).toFixed(3)}ms (min ${Math.min(...ts).toFixed(3)})`);
|
||||
}
|
||||
|
||||
for (const [name, p, text] of [
|
||||
['初夏纯标点', '/mnt/d/字体资源/初夏明朝/初夏明朝-Regular.ttf', ',。!?、;:“”‘’'],
|
||||
['初夏汉字标点', '/mnt/d/字体资源/初夏明朝/初夏明朝-Regular.ttf', '你好,世界!今天天气不错。'],
|
||||
['思源8字', './font/思源黑体.ttf', '天地玄黄宇宙洪荒'],
|
||||
['令东千字', './font/令东齐伋复刻体.ttf', '天地玄黄宇宙洪荒日月盈昃辰宿列张寒来暑往秋收冬藏闰余成岁律吕调阳云腾致雨露结为霜金生丽水玉出昆冈'],
|
||||
] as const) {
|
||||
const buf = fs.readFileSync(p);
|
||||
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset+buf.byteLength) as ArrayBuffer;
|
||||
console.log(`\n=== ${name} ===`);
|
||||
let outLen = 0;
|
||||
bench(' fontSubset(woff2)', () => { const o = fontSubset(ab, text, { sourceType: 'ttf' as const, outType: 'woff2' as const }); outLen = o.length; });
|
||||
console.log(` woff2 out: ${outLen}B`);
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
const buf = fs.readFileSync('/mnt/d/字体资源/初夏明朝/初夏明朝-Regular.ttf');
|
||||
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset+buf.byteLength) as ArrayBuffer;
|
||||
const view = new DataView(ab);
|
||||
const numTables = view.getUint16(4, false);
|
||||
let gposOff=0,gposLen=0;
|
||||
for (let i=0;i<numTables;i++){const r=12+i*16;const tag=String.fromCharCode(view.getUint8(r),view.getUint8(r+1),view.getUint8(r+2),view.getUint8(r+3));if(tag==='GPOS'){gposOff=view.getUint32(r+8,false);gposLen=view.getUint32(r+12,false);break;}}
|
||||
console.log('GPOS',gposOff,gposLen);
|
||||
|
||||
function median(a:number[]){return a.slice().sort((x,y)=>x-y)[Math.floor(a.length/2)];}
|
||||
|
||||
// 方式A: readBytes 的 slice 方式
|
||||
const tA:number[]=[];
|
||||
for(let i=0;i<30;i++){const t0=performance.now();const bytes=new Uint8Array(ab,gposOff,gposLen).slice();tA.push(performance.now()-t0);}
|
||||
console.log(`slice(${gposLen}): ${median(tA).toFixed(4)}ms`);
|
||||
|
||||
// 方式B: new Uint8Array + set
|
||||
const tB:number[]=[];
|
||||
for(let i=0;i<30;i++){const t0=performance.now();const b=new Uint8Array(gposLen);b.set(new Uint8Array(ab,gposOff,gposLen));tB.push(performance.now()-t0);}
|
||||
console.log(`new+set: ${median(tB).toFixed(4)}ms`);
|
||||
|
||||
// 方式C: subarray (零拷贝,但要确认后续不改原 buffer)
|
||||
const tC:number[]=[];
|
||||
for(let i=0;i<30;i++){const t0=performance.now();const b=new Uint8Array(ab,gposOff,gposLen);tC.push(performance.now()-t0);}
|
||||
console.log(`subarray(view): ${median(tC).toFixed(4)}ms`);
|
||||
|
||||
// 方式D: copyBytesTo (Node 较新)
|
||||
const tD:number[]=[];
|
||||
const src=new Uint8Array(ab);
|
||||
for(let i=0;i<30;i++){const b=new Uint8Array(gposLen);const t0=performance.now();src.copyBytesTo?src.copyBytesTo(b,0,gposOff,gposOff+gposLen):null;tD.push(performance.now()-t0);}
|
||||
console.log(`copyBytesTo: ${median(tD).toFixed(4)}ms`);
|
||||
@ -1,29 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
import { Font } from './vendor/fonteditor-core/lib/ttf/font.js';
|
||||
import { subsetGSUB } from './backend/font_util/gsub-subset.js';
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
const cases: [string, string, string][] = [
|
||||
['初夏纯标点', '/mnt/d/字体资源/初夏明朝/初夏明朝-Regular.ttf', ',。!?、;:“”‘’'],
|
||||
['初夏汉字', '/mnt/d/字体资源/初夏明朝/初夏明朝-Regular.ttf', '你好,世界!今天天气不错。'],
|
||||
['思源8字', './font/思源黑体.ttf', '天地玄黄宇宙洪荒'],
|
||||
['令东千字', './font/令东齐伋复刻体.ttf', '天地玄黄宇宙洪荒日月盈昃辰宿列张寒来暑往秋收冬藏闰余成岁律吕调阳云腾致雨露结为霜金生丽水玉出昆冈'],
|
||||
['FiraCode', '/mnt/d/字体资源/FiraCode/FiraCode-Medium.ttf', '=> !== >= <= ==='],
|
||||
];
|
||||
for (const [name, p, text] of cases) {
|
||||
if (!fs.existsSync(p)) { console.log(`${name}: 跳过(文件不存在)`); continue; }
|
||||
const buf = fs.readFileSync(p);
|
||||
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset+buf.byteLength) as ArrayBuffer;
|
||||
const cps = Array.from(text).map(c => c.codePointAt(0)!);
|
||||
const font = Font.create(ab, { type: 'ttf', subset: cps as any, kerning: true });
|
||||
const ttf = font.get();
|
||||
const subsetGids: number[] = ttf.subsetGids ?? [];
|
||||
const origToNew = new Map<number, number>();
|
||||
for (let i = 0; i < subsetGids.length; i++) origToNew.set(subsetGids[i], i);
|
||||
const gsub = ttf.GSUB;
|
||||
if (!gsub) { console.log(`${name}: 无 GSUB`); continue; }
|
||||
const b = gsub instanceof Uint8Array ? gsub : new Uint8Array(gsub);
|
||||
const out = subsetGSUB(b, origToNew);
|
||||
const hash = crypto.createHash('sha256').update(out).digest('hex').slice(0,16);
|
||||
console.log(`${name}: gsubOut=${out.length}B hash=${hash}`);
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
const buf = fs.readFileSync('/mnt/d/字体资源/初夏明朝/初夏明朝-Regular.ttf');
|
||||
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset+buf.byteLength) as ArrayBuffer;
|
||||
const view = new DataView(ab);
|
||||
// 找 GSUB
|
||||
const numTables = view.getUint16(4, false);
|
||||
let off=0;
|
||||
for (let i=0;i<numTables;i++){const r=12+i*16;const tag=String.fromCharCode(view.getUint8(r),view.getUint8(r+1),view.getUint8(r+2),view.getUint8(r+3));if(tag==='GSUB'){off=view.getUint32(r+8,false);break;}}
|
||||
// GSUB: version(u32) scriptListOff(u16) featureListOff(u16) lookupListOff(u16)
|
||||
const v = view.getUint32(off, false);
|
||||
const scriptOff = off + view.getUint16(off+4, false);
|
||||
const featOff = off + view.getUint16(off+6, false);
|
||||
const lookupOff = off + view.getUint16(off+8, false);
|
||||
const lookupCount = view.getUint16(lookupOff, false);
|
||||
console.log('GSUB version:', (v>>>16), '.', (v&0xffff));
|
||||
console.log('lookupCount:', lookupCount);
|
||||
for (let i=0;i<lookupCount;i++){
|
||||
const lOff = lookupOff + view.getUint16(lookupOff+2+i*2, false);
|
||||
const type = view.getUint16(lOff, false);
|
||||
const flag = view.getUint16(lOff+2, false);
|
||||
const subCount = view.getUint16(lOff+4, false);
|
||||
console.log(` lookup[${i}]: type=${type} flag=${flag} subCount=${subCount}`);
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
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 supportNS: any = await import('./vendor/fonteditor-core/lib/ttf/table/support.js');
|
||||
const support: any = supportNS.default.default ?? supportNS.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)!);
|
||||
|
||||
// monkey-patch 每个表类的 read,累加耗时
|
||||
const stats: Record<string, number[]> = {};
|
||||
for (const tn of Object.keys(support)) {
|
||||
const TableClass: any = support[tn];
|
||||
if (!TableClass || !TableClass.prototype || !TableClass.prototype.read) continue;
|
||||
const orig = TableClass.prototype.read;
|
||||
TableClass.prototype.read = function(reader: any, ttf: any) {
|
||||
const t0 = performance.now();
|
||||
const r = orig.call(this, reader, ttf);
|
||||
const dt = performance.now() - t0;
|
||||
(stats[tn] ??= []).push(dt);
|
||||
return r;
|
||||
};
|
||||
}
|
||||
|
||||
function median(a: number[]) { return a.slice().sort((x,y)=>x-y)[Math.floor(a.length/2)]; }
|
||||
|
||||
// 多次跑 readBuffer 累加统计
|
||||
for (let i = 0; i < 25; i++) {
|
||||
const r = new (TTFReader as any)({ subset: cps, kerning: true });
|
||||
r.readBuffer(ab);
|
||||
}
|
||||
|
||||
console.log('=== 初夏标点 各表 read 耗时(25次中位数)===');
|
||||
const rows = Object.entries(stats).map(([n, a]) => ({ n, med: median(a), min: Math.min(...a) }));
|
||||
rows.sort((a,b)=>b.med-a.med);
|
||||
for (const r of rows) console.log(` ${r.n}: ${r.med.toFixed(3)}ms (min ${r.min.toFixed(3)}, ${stats[r.n].length}次)`);
|
||||
30
_p_read.ts
30
_p_read.ts
@ -1,30 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
import * as TTFReaderNS from './vendor/fonteditor-core/lib/ttf/ttfreader.js';
|
||||
const TTFReader: any = (TTFReaderNS as any).default.default ?? (TTFReaderNS as any).default ?? TTFReaderNS;
|
||||
|
||||
function median(a: number[]) { return a.slice().sort((x,y)=>x-y)[Math.floor(a.length/2)]; }
|
||||
function bench(label: string, mk: () => () => void, iters = 9) {
|
||||
const fn = mk();
|
||||
for (let i = 0; i < 3; i++) fn();
|
||||
const ts: number[] = [];
|
||||
for (let i = 0; i < iters; i++) { const f = mk(); const t0 = performance.now(); f(); ts.push(performance.now() - t0); }
|
||||
console.log(`${label}: ${median(ts).toFixed(3)}ms (min ${Math.min(...ts).toFixed(3)})`);
|
||||
}
|
||||
|
||||
for (const [name, p, text] of [
|
||||
['初夏标点', '/mnt/d/字体资源/初夏明朝/初夏明朝-Regular.ttf', ',。!?、;:“”‘’'],
|
||||
['思源8字', './font/思源黑体.ttf', '天地玄黄宇宙洪荒'],
|
||||
] as const) {
|
||||
const buf = fs.readFileSync(p);
|
||||
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset+buf.byteLength) as ArrayBuffer;
|
||||
const cps = Array.from(text).map(c => c.codePointAt(0)!);
|
||||
console.log(`\n=== ${name} ===`);
|
||||
|
||||
bench(' readBuffer', () => () => { const r = new (TTFReader as any)({ subset: cps, kerning: true }); r.readBuffer(ab); });
|
||||
bench(' resolveGlyf', () => () => {
|
||||
const r = new (TTFReader as any)({ subset: cps, kerning: true });
|
||||
const ttf = r.readBuffer(ab);
|
||||
r.resolveGlyf(ttf);
|
||||
});
|
||||
bench(' full read', () => () => { const r = new (TTFReader as any)({ subset: cps, kerning: true }); r.read(ab); });
|
||||
}
|
||||
32
_p_sgpos.ts
32
_p_sgpos.ts
@ -1,32 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
import { Font } from './vendor/fonteditor-core/lib/ttf/font.js';
|
||||
import { subsetGPOS } from './backend/font_util/gpos-subset.js';
|
||||
import { subsetGSUB } from './backend/font_util/gsub-subset.js';
|
||||
|
||||
function median(a: number[]) { return a.slice().sort((x,y)=>x-y)[Math.floor(a.length/2)]; }
|
||||
|
||||
const p = '/mnt/d/字体资源/初夏明朝/初夏明朝-Regular.ttf';
|
||||
const text = ',。!?、;:“”‘’';
|
||||
const buf = fs.readFileSync(p);
|
||||
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset+buf.byteLength) as ArrayBuffer;
|
||||
const codePoints = Array.from(text).map(c => c.codePointAt(0)!);
|
||||
|
||||
// 一次性拿到 subsetGids + GPOS 字节 + origToNew
|
||||
const font = Font.create(ab, { type: 'ttf', subset: codePoints as any, kerning: true });
|
||||
const preOpt = font.get();
|
||||
const subsetGids: number[] = preOpt.subsetGids ?? [];
|
||||
const origToNew = new Map<number, number>();
|
||||
for (let i = 0; i < subsetGids.length; i++) origToNew.set(subsetGids[i], i);
|
||||
const gposBytes = preOpt.GPOS instanceof Uint8Array ? preOpt.GPOS : new Uint8Array(preOpt.GPOS);
|
||||
const gsubBytes = preOpt.GSUB instanceof Uint8Array ? preOpt.GSUB : new Uint8Array(preOpt.GSUB);
|
||||
console.log('subsetGids:', subsetGids.length, 'GPOS:', gposBytes.length, 'GSUB:', gsubBytes.length);
|
||||
|
||||
function bench(label: string, fn: () => void, iters = 25) {
|
||||
for (let i = 0; i < 8; i++) fn();
|
||||
const ts: number[] = [];
|
||||
for (let i = 0; i < iters; i++) { const t0 = performance.now(); fn(); ts.push(performance.now() - t0); }
|
||||
console.log(` ${label}: ${median(ts).toFixed(3)}ms (min ${Math.min(...ts).toFixed(3)})`);
|
||||
}
|
||||
|
||||
bench('subsetGPOS', () => subsetGPOS(gposBytes, origToNew));
|
||||
bench('subsetGSUB', () => subsetGSUB(gsubBytes, origToNew));
|
||||
85
_p_stages.ts
85
_p_stages.ts
@ -1,85 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
import { Font } from './vendor/fonteditor-core/lib/ttf/font.js';
|
||||
import { probeGsubAndCmap } from './backend/font_util/gsub-probe.js';
|
||||
import { collectReachableGsubTargets } from './backend/font_util/gsub-reachable.js';
|
||||
import { subsetGPOS } from './backend/font_util/gpos-subset.js';
|
||||
import { subsetGSUB } from './backend/font_util/gsub-subset.js';
|
||||
|
||||
function rewriteLayoutTablesForSubset(opt: any, subsetGids: number[]) {
|
||||
const ttf = opt.get();
|
||||
const origToNew = new Map<number, number>();
|
||||
for (let i = 0; i < subsetGids.length; i++) origToNew.set(subsetGids[i], i);
|
||||
const og = ttf.GPOS;
|
||||
if (og) { const b = og instanceof Uint8Array ? og : new Uint8Array(og); if (b.byteLength > 0) { const r = subsetGPOS(b, origToNew); if (r) ttf.GPOS = r; } }
|
||||
const os = ttf.GSUB;
|
||||
if (os) { const b = os instanceof Uint8Array ? os : new Uint8Array(os); if (b.byteLength > 0) { ttf.GSUB = subsetGSUB(b, origToNew); } }
|
||||
}
|
||||
|
||||
function median(a: number[]) { return a.slice().sort((x,y)=>x-y)[Math.floor(a.length/2)]; }
|
||||
|
||||
const p = '/mnt/d/字体资源/初夏明朝/初夏明朝-Regular.ttf';
|
||||
const text = ',。!?、;:“”‘’';
|
||||
const buf = fs.readFileSync(p);
|
||||
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset+buf.byteLength) as ArrayBuffer;
|
||||
const codePoints = Array.from(text).map(c => c.codePointAt(0)!);
|
||||
|
||||
// 准备共享对象
|
||||
function build() {
|
||||
const probe = probeGsubAndCmap(ab, codePoints, 'ttf');
|
||||
let extraSubsetGids: number[] | undefined;
|
||||
if (probe.ok) {
|
||||
const seed = new Set<number>([0]);
|
||||
for (const cp of codePoints) { const g = probe.lookup.get(cp); if (g !== undefined) seed.add(g); }
|
||||
const r = collectReachableGsubTargets(probe.gsubBytes!, seed);
|
||||
if (r.size > 0) extraSubsetGids = [...r];
|
||||
}
|
||||
return { extraSubsetGids };
|
||||
}
|
||||
|
||||
const { extraSubsetGids } = build();
|
||||
|
||||
function timePhase(label: string, fn: () => void, iters = 13) {
|
||||
for (let i = 0; i < 4; i++) fn();
|
||||
const ts: number[] = [];
|
||||
for (let i = 0; i < iters; i++) { const t0 = performance.now(); fn(); ts.push(performance.now() - t0); }
|
||||
console.log(` ${label}: ${median(ts).toFixed(3)}ms`);
|
||||
}
|
||||
|
||||
console.log('=== 初夏纯标点 各阶段(独立 best-of)===');
|
||||
timePhase('1.probe', () => probeGsubAndCmap(ab, codePoints, 'ttf'));
|
||||
|
||||
let fontRef: any;
|
||||
timePhase('2.Font.create', () => { fontRef = Font.create(ab, { type: 'ttf', subset: codePoints as any, kerning: true, extraSubsetGids }); });
|
||||
|
||||
const font = Font.create(ab, { type: 'ttf', subset: codePoints as any, kerning: true, extraSubsetGids });
|
||||
const preOpt = font.get();
|
||||
const subsetGids: number[] = preOpt.subsetGids ?? [];
|
||||
console.log(` subsetGids count: ${subsetGids.length}`);
|
||||
|
||||
timePhase('3.optimize', () => { const f = Font.create(ab, { type: 'ttf', subset: codePoints as any, kerning: true, extraSubsetGids }); f.optimize(); });
|
||||
|
||||
const opt = font.optimize();
|
||||
// rewrite 内部拆解
|
||||
timePhase('4a.subsetGPOS', () => {
|
||||
const f = Font.create(ab, { type: 'ttf', subset: codePoints as any, kerning: true, extraSubsetGids });
|
||||
const o = f.optimize();
|
||||
// 复刻 rewriteLayoutTablesForSubset 的 GPOS 部分
|
||||
const ttf = o.get();
|
||||
const origToNew = new Map<number, number>();
|
||||
for (let i = 0; i < subsetGids.length; i++) origToNew.set(subsetGids[i], i);
|
||||
const g = ttf.GPOS;
|
||||
if (g) { subsetGPOS(g instanceof Uint8Array ? g : new Uint8Array(g), origToNew); }
|
||||
});
|
||||
|
||||
timePhase('4.rewriteLayoutTables', () => {
|
||||
const f = Font.create(ab, { type: 'ttf', subset: codePoints as any, kerning: true, extraSubsetGids });
|
||||
const o = f.optimize();
|
||||
rewriteLayoutTablesForSubset(o, subsetGids);
|
||||
});
|
||||
|
||||
timePhase('5.write(woff2)', () => {
|
||||
const f = Font.create(ab, { type: 'ttf', subset: codePoints as any, kerning: true, extraSubsetGids });
|
||||
const o = f.optimize();
|
||||
rewriteLayoutTablesForSubset(o, subsetGids);
|
||||
o.write({ type: 'woff2', hinting: false } as any);
|
||||
});
|
||||
29
_p_tables.ts
29
_p_tables.ts
@ -1,29 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
import * as TTFReaderNS from './vendor/fonteditor-core/lib/ttf/ttfreader.js';
|
||||
const TTFReader: any = TTFReaderNS.default.default ?? TTFReaderNS.default;
|
||||
|
||||
function median(a: number[]) { return a.slice().sort((x,y)=>x-y)[Math.floor(a.length/2)]; }
|
||||
|
||||
for (const [name, p, text] of [
|
||||
['初夏标点', '/mnt/d/字体资源/初夏明朝/初夏明朝-Regular.ttf', ',。!?、;:“”‘’'],
|
||||
] as const) {
|
||||
const buf = fs.readFileSync(p);
|
||||
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset+buf.byteLength) as ArrayBuffer;
|
||||
const cps = Array.from(text).map(c => c.codePointAt(0)!);
|
||||
console.log(`\n=== ${name} (字体 ${ (buf.length/1024/1024).toFixed(1)}MB) ===`);
|
||||
|
||||
// 读 directory 拿各表大小
|
||||
const view = new DataView(ab);
|
||||
const numTables = view.getUint16(4, false);
|
||||
const tables: {name:string; offset:number; length:number}[] = [];
|
||||
for (let i = 0; i < numTables; i++) {
|
||||
const rec = 12 + i*16;
|
||||
const tag = String.fromCharCode(view.getUint8(rec), view.getUint8(rec+1), view.getUint8(rec+2), view.getUint8(rec+3));
|
||||
const off = view.getUint32(rec+8, false);
|
||||
const len = view.getUint32(rec+12, false);
|
||||
tables.push({name: tag, offset: off, length: len});
|
||||
}
|
||||
tables.sort((a,b)=>b.length-a.length);
|
||||
console.log('表大小 TOP:');
|
||||
for (const t of tables.slice(0,12)) console.log(` ${t.name}: ${(t.length/1024).toFixed(0)}KB`);
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
import { Font } from './vendor/fonteditor-core/lib/ttf/font.js';
|
||||
const woff2enc: any = await import('./vendor/fonteditor-core/woff2/woff2-encode.js');
|
||||
|
||||
function median(a: number[]) { return a.slice().sort((x,y)=>x-y)[Math.floor(a.length/2)]; }
|
||||
function bench(label: string, fn: () => void, iters = 15) {
|
||||
for (let i = 0; i < 5; i++) fn();
|
||||
const ts: number[] = [];
|
||||
for (let i = 0; i < iters; i++) { const t0 = performance.now(); fn(); ts.push(performance.now() - t0); }
|
||||
console.log(`${label}: ${median(ts).toFixed(3)}ms (min ${Math.min(...ts).toFixed(3)})`);
|
||||
}
|
||||
|
||||
for (const [name, p, text] of [
|
||||
['初夏标点', '/mnt/d/字体资源/初夏明朝/初夏明朝-Regular.ttf', ',。!?、;:“”‘’'],
|
||||
['思源8字', './font/思源黑体.ttf', '天地玄黄宇宙洪荒'],
|
||||
['令东千字', './font/令东齐伋复刻体.ttf', '天地玄黄宇宙洪荒日月盈昃辰宿列张寒来暑往秋收冬藏闰余成岁律吕调阳云腾致雨露结为霜金生丽水玉出昆冈'],
|
||||
] as const) {
|
||||
const buf = fs.readFileSync(p);
|
||||
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset+buf.byteLength) as ArrayBuffer;
|
||||
const cps = Array.from(text);
|
||||
const font = Font.create(ab, { type: 'ttf', subset: cps as any, kerning: true });
|
||||
const opt = (font as any).optimize();
|
||||
const ttfBuf = opt.write({ type: 'ttf', hinting: false } as any);
|
||||
console.log(`\n=== ${name} (ttf ${(ttfBuf.length/1024).toFixed(1)}KB) ===`);
|
||||
bench(' TTFWriter.write(ttf)', () => opt.write({ type: 'ttf', hinting: false } as any));
|
||||
bench(' encodeTTFToWOFF2(全部)', () => woff2enc.encodeTTFToWOFF2(ttfBuf));
|
||||
// 单独 transformGlyfAndLoca?需拆解,先量总 encode
|
||||
const w2 = woff2enc.encodeTTFToWOFF2(ttfBuf);
|
||||
console.log(` woff2 out: ${(w2.length)}B`);
|
||||
}
|
||||
28
_p_write.ts
28
_p_write.ts
@ -1,28 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
import { Font } from './vendor/fonteditor-core/lib/ttf/font.js';
|
||||
|
||||
function median(a: number[]) { return a.slice().sort((x,y)=>x-y)[Math.floor(a.length/2)]; }
|
||||
function bench(label: string, fn: () => void, iters = 9) {
|
||||
for (let i = 0; i < 3; i++) fn();
|
||||
const ts: number[] = [];
|
||||
for (let i = 0; i < iters; i++) { const t0 = performance.now(); fn(); ts.push(performance.now() - t0); }
|
||||
console.log(`${label}: ${median(ts).toFixed(3)}ms (min ${Math.min(...ts).toFixed(3)})`);
|
||||
}
|
||||
|
||||
for (const [name, p, text] of [
|
||||
['初夏标点', '/mnt/d/字体资源/初夏明朝/初夏明朝-Regular.ttf', ',。!?、;:“”‘’'],
|
||||
['思源8字', './font/思源黑体.ttf', '天地玄黄宇宙洪荒'],
|
||||
['令东千字', './font/令东齐伋复刻体.ttf', '天地玄黄宇宙洪荒日月盈昃辰宿列张寒来暑往秋收冬藏闰余成岁律吕调阳云腾致雨露结为霜金生丽水玉出昆冈'],
|
||||
] as const) {
|
||||
const buf = fs.readFileSync(p);
|
||||
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset+buf.byteLength) as ArrayBuffer;
|
||||
const cps = Array.from(text);
|
||||
console.log(`\n=== ${name} (${text.length}字) ===`);
|
||||
|
||||
bench(' create', () => Font.create(ab, { type: 'ttf', subset: cps as any, kerning: true }));
|
||||
const font = Font.create(ab, { type: 'ttf', subset: cps as any, kerning: true });
|
||||
const opt = (font as any).optimize();
|
||||
bench(' write-woff2', () => opt.write({ type: 'woff2', hinting: false } as any));
|
||||
// write-ttf 对比
|
||||
bench(' write-ttf', () => opt.write({ type: 'ttf', hinting: false } as any));
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user