diff --git a/_cmp.entry.ts b/_cmp.entry.ts new file mode 100644 index 0000000..2cf497f --- /dev/null +++ b/_cmp.entry.ts @@ -0,0 +1,8 @@ +import { readFileSync, writeFileSync } from 'fs'; +import { fontSubset } from './backend/font_util/font.js'; +const buf = readFileSync('font/令东齐伋复刻体.ttf'); +const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); +const text = '天地玄黄宇宙洪荒日月盈昃辰宿列张寒来暑往秋收冬藏闰余成岁律吕调阳云腾致雨露结为霜金生丽水玉出昆冈剑号巨阙珠称夜光果珍李柰菜重芥姜海咸河淡鳞潜羽翔'; +const out = fontSubset(ab, text, {sourceType:'ttf',outType:'woff2'}); +writeFileSync('/tmp/woff2_opt312.bin', out); +console.log('len', out.length); diff --git a/_cmp2.entry.ts b/_cmp2.entry.ts new file mode 100644 index 0000000..00f50df --- /dev/null +++ b/_cmp2.entry.ts @@ -0,0 +1,8 @@ +import { readFileSync, writeFileSync } from 'fs'; +import { fontSubset } from './backend/font_util/font.js'; +const buf = readFileSync('font/令东齐伋复刻体.ttf'); +const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); +const text = '天地玄黄宇宙洪荒日月盈昃辰宿列张寒来暑往秋收冬藏闰余成岁律吕调阳云腾致雨露结为霜金生丽水玉出昆冈剑号巨阙珠称夜光果珍李柰菜重芥姜海咸河淡鳞潜羽翔'; +const out = fontSubset(ab, text, {sourceType:'ttf',outType:'woff2'}); +writeFileSync('/tmp/woff2_clean.bin', out); +console.log('len', out.length); diff --git a/_probe_hmtx.entry.ts b/_probe_hmtx.entry.ts new file mode 100644 index 0000000..c741864 --- /dev/null +++ b/_probe_hmtx.entry.ts @@ -0,0 +1,18 @@ +import { readFileSync } from 'fs'; +import { fontSubset } from './backend/font_util/font.js'; +import supportMod from './vendor/fonteditor-core/lib/ttf/table/support.js'; +const support = (supportMod as any).default || supportMod; +const hmtxT = support.hmtx; +const origRead = hmtxT.prototype.read; +hmtxT.prototype.read = function(reader: any, ttf: any) { + const r = origRead.call(this, reader, ttf); + if (ttf.subsetGids) { + let mx = 0; + for (const g of ttf.subsetGids) if (g>mx) mx=g; + console.log(`numGlyphs=${ttf.maxp.numGlyphs} subsetGids.len=${ttf.subsetGids.length} maxGid=${mx} alloc=${ttf.maxp.numGlyphs*2*4}B needed=${(mx+1)*2*4}B`); + } + return r; +}; +const buf = readFileSync('font/令东齐伋复刻体.ttf'); +const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); +fontSubset(ab, '天地玄黄宇宙洪荒', {sourceType:'ttf',outType:'ttf'}); diff --git a/_prof312.entry.ts b/_prof312.entry.ts new file mode 100644 index 0000000..6c171bc --- /dev/null +++ b/_prof312.entry.ts @@ -0,0 +1,44 @@ +import { performance } from 'perf_hooks'; +import { readFileSync } from 'fs'; +import { fontSubset } from './backend/font_util/font.js'; +import supportMod from './vendor/fonteditor-core/lib/ttf/table/support.js'; +const support = (supportMod as any).default || supportMod; + +const buf = readFileSync('font/令东齐伋复刻体.ttf'); +const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); + +const times: Record = {}; +function wrap(proto: any, name: string, label: string) { + const orig = proto[name]; + if (typeof orig !== 'function') return; + proto[name] = function(...args: any[]) { + const t = performance.now(); + const r = orig.apply(this, args); + times[label] = (times[label]||0) + (performance.now()-t); + return r; + }; +} + +for (const tname of Object.keys(support)) { + const T = support[tname]; + if (!T || !T.prototype) continue; + wrap(T.prototype, 'read', 'read:'+tname); + wrap(T.prototype, 'write', 'write:'+tname); + wrap(T.prototype, 'size', 'size:'+tname); +} + +const text = '天地玄黄宇宙洪荒日月盈昃辰宿列张寒来暑往秋收冬藏闰余成岁律吕调阳云腾致雨露结为霜金生丽水玉出昆冈剑号巨阙珠称夜光果珍李柰菜重芥姜海咸河淡鳞潜羽翔'; +for (const outType of ['ttf','woff2'] as const) { + for (const k of Object.keys(times)) delete times[k]; + const opt = {sourceType:'ttf' as const, outType}; + fontSubset(ab, text, opt); + const N = 200; + const t0 = performance.now(); + for (let i=0;ib[1]-a[1]); + for (const [k,v] of sorted.slice(0,12)) { + console.log(` ${k.padEnd(18)} ${(v*1000/N|0)/1000}µs ${(v/total*100).toFixed(1)}%`); + } +} diff --git a/_prof_w2.entry.ts b/_prof_w2.entry.ts new file mode 100644 index 0000000..2a8df94 --- /dev/null +++ b/_prof_w2.entry.ts @@ -0,0 +1,18 @@ +import { fontSubset } from './backend/font_util/font.js'; +import { readFileSync } from 'fs'; +const buf = readFileSync('font/令东齐伋复刻体.ttf'); +const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); +const text = '天地玄黄宇宙洪荒日月盈昃辰宿列张寒来暑往秋收冬藏闰余成岁律吕调阳云腾致雨露结为霜金生丽水玉出昆冈剑号巨阙珠称夜光果珍李柰菜重芥姜海咸河淡鳞潜羽翔'; +const g:any = globalThis; +const N=200; +fontSubset(ab, text, {sourceType:'ttf',outType:'woff2'}); // warmup +g.__t.transform=0; g.__t.brotli=0; +import('perf_hooks').then(({performance})=>{ + const t0=performance.now(); + for(let i=0;i _reuseXCoords.length) { const cap = _reuseXCoords.length; const newCap = cap * 2 > numPoints ? cap * 2 : numPoints; _reuseXCoords = new Int32Array(newCap); - _reuseYCoords = new Int32Array(newCap); } const xCoords = _reuseXCoords; - const yCoords = _reuseYCoords; let px = 0; let calcXMin, calcXMax; /** @@ -446,7 +447,6 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { * 中文字体 87% 的点为 short 模式,其中正负各半(50/50), * 原三元 `(f & XSAME) ? b : -b` 是 50/50 不可预测分支,被 V8 编译成条件跳转导致流水线冲刷。 * 改用 sign 位乘法 `(b * 2 - 1)` 消除分支:XSAME=16(bit4),sign=(f>>4)&1。 - * 微基准:0.49ms → 0.14ms(3.6x),57168 点场景显著加速 transformGlyfAndLoca。 */ for (let xi = 0; xi < numPoints; xi++) { const f = flagAccum[flagWriteBase + xi]; @@ -466,30 +466,11 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { } let py = 0; - let calcYMin, calcYMax; - for (let yi = 0; yi < numPoints; yi++) { - const f = flagAccum[flagWriteBase + yi]; - if (f & YSHORT_FLAG) { - const b = glyfData[dataOff++]; - py += b * (((f >> 5) & 1) * 2 - 1); - } 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; - if (yi === 0) { calcYMin = py; calcYMax = py; } - else if (py < calcYMin) calcYMin = py; - else if (py > calcYMax) calcYMax = py; - } + let calcYMin = 0, calcYMax = 0; if (numberOfContours > 0) { - const bboxMatches = calcXMin === xMin && calcYMin === yMin && calcXMax === xMax && calcYMax === yMax; - if (!bboxMatches) { - bboxBitmap[gi >> 3] |= (0x80 >> (gi & 7)); - bboxStreamSize += 8; - } + /** 优化312: bbox bitmap 判定拆分——x 在此先判,y 在合并循环算完后补判 */ + let bboxSet = !(calcXMin === xMin && calcXMax === xMax); /** * 优化294: triplet 数据直接追加写入 glyphAccum(连续累积),不再分配 per-glyph glyphStreamBuf @@ -505,26 +486,41 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { const gsBase = glyphAccumLen; let gsbi = 0; let prevX = 0, prevY = 0; - /** - * 优化303: calcTripletAndWrite 手动 inline 到主循环(消除 54350 次/call 函数调用开销)。 - * 千字文 54350 点场景,calcTripletAndWrite 占 transformGlyfAndLoca 31.8% CPU(prof 实测), - * 函数调用与无法 inline 的参数装箱是主因。inline 后 V8 可在循环内做寄存器分配 + - * 公共子表达式消除(absDx/absDy/curveBit 跨分支复用)。语义与原 calcTripletAndWrite 完全一致。 - * 分支顺序保持原样(特例 yOnly/xOnly 优先),千字文 78% 命中 1B 双轴分支。 - */ const _gs = glyphAccum; - for (let pi = 0; pi < numPoints; pi++) { - const cx = xCoords[pi]; - const cy = yCoords[pi]; - /** 优化302: curveBit 无分支——onCurve(flag&1=1)→0, 控制点(flag&1=0)→128 */ - const curveBit = ((flagAccum[flagWriteBase + pi] & 1) ^ 1) << 7; + const _fa = flagAccum; + const _fwb = flagWriteBase; + const _gd = glyfData; + let dyBboxUnset = true; + /** + * 优化312: y 解码 + triplet 编码合并为单循环。 + * py 从 TTF yCoord 字节流解码(累积绝对坐标),cx 从 xCoords 取(x 已在前一循环解好), + * triplet delta = cx - prevX / py - prevY,当场编码写入 glyphAccum。 + * bbox_y 的 min/max 也在本循环同步计算(原在独立 y 循环)。 + */ + for (let yi = 0; yi < numPoints; yi++) { + const f = _fa[_fwb + yi]; + if (f & YSHORT_FLAG) { + const b = _gd[dataOff++]; + py += b * (((f >> 5) & 1) * 2 - 1); + } else if (!(f & YSAME_FLAG)) { + let dy0 = (_gd[dataOff] << 8) | _gd[dataOff + 1]; + if (dy0 > 0x7FFF) dy0 -= 0x10000; + py += dy0; + dataOff += 2; + } + if (dyBboxUnset) { calcYMin = py; calcYMax = py; dyBboxUnset = false; } + else if (py < calcYMin) calcYMin = py; + else if (py > calcYMax) calcYMax = py; + + /** triplet 编码(与原 calcTripletAndWrite inline 语义一致) */ + const cx = xCoords[yi]; + const cy = py; + const curveBit = ((f & 1) ^ 1) << 7; const dx = cx - prevX; const dy = cy - prevY; const absDx = dx < 0 ? -dx : dx; const absDy = dy < 0 ? -dy : dy; const wpos = gsBase + gsbi; - /** triplet flag(写入 flagAccum + 索引 TRIPLET_DATA_SIZES)。 - * 默认值仅占位,每个分支都会覆盖。 */ let flag; if (dx === 0 && absDy < 1280) { _gs[wpos] = absDy & 0xFF; @@ -564,11 +560,17 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { flag = curveBit + 124 + xSignBit + 2 * ySignBit; } /** triplet flag 回写到 flagAccum(flagStream 存 triplet flag 而非原始 flag) */ - flagAccum[flagWriteBase + pi] = flag; + _fa[_fwb + yi] = flag; gsbi += TRIPLET_DATA_SIZES[flag & 0x7F]; prevX = cx; prevY = cy; } + /** 优化312: y 的 bbox 匹配补判 */ + if (calcYMin !== yMin || calcYMax !== yMax) bboxSet = true; + if (bboxSet) { + bboxBitmap[gi >> 3] |= (0x80 >> (gi & 7)); + bboxStreamSize += 8; + } glyphAccumLen += gsbi; glyphStreamSize += gsbi; @@ -582,6 +584,16 @@ function transformGlyfAndLoca(glyfData, locaData, indexFormat, numGlyphs) { } for (let e = 0; e < n; e++) glyphAccum[glyphAccumLen++] = _reuseEnc255[e]; glyphStreamSize += n; + } else { + /** numberOfContours === 0 的空字形:仍需消费 yCoord 字节以推进 dataOff(保持原语义) */ + for (let yi0 = 0; yi0 < numPoints; yi0++) { + const f = flagAccum[flagWriteBase + yi0]; + if (f & YSHORT_FLAG) { + dataOff++; + } else if (!(f & YSAME_FLAG)) { + dataOff += 2; + } + } } glyphInfos[gi] = numberOfContours > 0