web-font/scripts/test-leafer-node.mts
崮生(子虚) a0c2b4aafc feat: webfont-sdk Node 模式 + leafer 版基准测试(双方案并存)
- webfont-sdk: 新增 Node 模式(node-registry),GlobalFonts 注册 + 每 chunk 唯一 family + 逗号链回退
- leafer-x-webfont: fontFamily 链写回(Node 端),scan 链归一化防套娃、rewriteFamily 跳过链形态防抹回
- 修复 pnpm 双实例 bug: @napi-rs/canvas 版本对齐 1.0.6(根与 SDK 同版本共享 .pnpm 条目)
- 基准测试重构: 用例表抽取为 基准测试用例.ts 共享,SSIM 计算收敛到 scripts/ssim.ts
- 新增 基准测试_leafer.test.ts: @leafer-ui/node + Skia 渲染验证(无浏览器),ttf 输出用例 SSIM=1.0000;woff2 用例仅计时(Skia 不吃 woff2,容器验证由 puppeteer 版守护)
- Node 端集成测试 scripts/test-leafer-node.mts(插件链路 SSIM=1.0000)
2026-08-15 15:51:42 +08:00

86 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Node 端 leafer-x-webfont 集成测试
*
* 验证链路:@leafer-ui/node → WebFontPlugin 扫描 → webfont-sdk Node 模式
* HTTP 子集 API → GlobalFonts 唯一 family 注册 → fontFamily 链回退写回)
* → Leafer 渲染导出 PNG → 与全量基准 SSIM 对比。
*
* 运行npx tsx scripts/test-leafer-node.mts
*/
import { Leafer, Text, useCanvas } from '@leafer-ui/node'
import { Canvas as NapiCanvas, GlobalFonts, loadImage } from '@napi-rs/canvas'
import { readFileSync, writeFileSync } from 'node:fs'
/** 必须在创建任何 Leafer 实例之前初始化 napi canvas 平台 */
useCanvas('napi', { Canvas: NapiCanvas, loadImage })
const { WebFontPlugin } = await import('../packages/leafer-x-webfont/src/index.ts')
const { fontSubset } = await import('../backend/font_util/font.ts')
/** 本地后端服务8087 已运行) */
const BASE_URL = 'http://localhost:8087'
const FONT_FILE = 'font/令东齐伋复刻体.ttf'
const TEXT = '静心茶舍'
const OUT_DIR = 'benchmark_results/debug'
/** leafer.export('png') 返回 { data: 'data:image/png;base64,...', width, height } */
async function renderAndExport(family: string, outFile: string): Promise<number> {
const leafer = new Leafer({ width: 400, height: 120, fill: '#ffffff' })
leafer.add(new Text({ text: TEXT, fontFamily: family, fontSize: 64, fill: '#000000', x: 20, y: 20 }))
await new Promise((r) => setTimeout(r, 200))
const out = (await leafer.export('png', { pixelRatio: 1 })) as { data: string }
const buf = Buffer.from(out.data.split(',')[1]!, 'base64')
writeFileSync(outFile, buf)
leafer.destroy()
return buf.length
}
async function main(): Promise<void> {
/* ---------- 1. 全量基准图:直接 GlobalFonts 注册(不经插件) ---------- */
const full = readFileSync(FONT_FILE)
const fullSubset = await fontSubset(full.buffer.slice(0), TEXT, { sourceType: 'ttf', outType: 'ttf' })
GlobalFonts.register(new Uint8Array(fullSubset), 'BaselineFont')
const baselineSize = await renderAndExport('BaselineFont', `${OUT_DIR}/node_leafer_baseline.png`)
console.log('[1] 基准图:', baselineSize, 'bytes')
/* ---------- 2. 插件链路HTTP API + 增量 + 链回退 ---------- */
const leafer = new Leafer({ width: 400, height: 120, fill: '#ffffff' })
const webfont = new WebFontPlugin(leafer as never, { baseUrl: BASE_URL, debug: true })
const text = new Text({ text: TEXT, fontFamily: '令东齐伋复刻体.ttf', fontSize: 64, fill: '#000000', x: 20, y: 20 })
leafer.add(text)
webfont.refresh()
await webfont.ready()
await new Promise((r) => setTimeout(r, 200))
console.log('[2] 节点 fontFamily =', JSON.stringify(text.fontFamily))
const chainOk = /__\d/.test(String(text.fontFamily))
console.log('[2] fontFamily 链写回:', chainOk ? '✓' : '✗ 链未生效')
await new Promise((r) => setTimeout(r, 200))
const out = (await leafer.export('png', { pixelRatio: 1 })) as { data: string }
const buf = Buffer.from(out.data.split(',')[1]!, 'base64')
writeFileSync(`${OUT_DIR}/node_leafer_test.png`, buf)
console.log('[2] 插件链路图:', buf.length, 'bytes')
/* ---------- 3. SSIM 对比 ---------- */
const { calculateSSIM } = await import('./ssim.ts')
const { PNG } = await import('pngjs')
const a = PNG.sync.read(readFileSync(`${OUT_DIR}/node_leafer_baseline.png`))
const b = PNG.sync.read(readFileSync(`${OUT_DIR}/node_leafer_test.png`))
if (a.width !== b.width || a.height !== b.height) {
console.log(`[3] 尺寸不一致: ${a.width}x${a.height} vs ${b.width}x${b.height}`)
return
}
const ssim = calculateSSIM(a.data, b.data, a.width, a.height)
console.log(`[3] SSIM = ${ssim.toFixed(4)}`, ssim > 0.95 ? '✓ 通过' : '✗ 未达 0.95')
webfont.destroy()
leafer.destroy()
}
main().catch((err) => {
console.error('测试失败:', err)
process.exit(1)
})