mirror of
https://github.com/2234839/web-font.git
synced 2026-04-29 21:00:45 +08:00
优化基准测试
This commit is contained in:
parent
c74278cf2f
commit
a8fdb24de4
6
.gitignore
vendored
6
.gitignore
vendored
@ -30,4 +30,8 @@ app
|
||||
*.tar
|
||||
|
||||
dist_backend
|
||||
.pilot
|
||||
.pilot
|
||||
|
||||
verify_font_baseline
|
||||
benchmark_results
|
||||
.claude
|
||||
3
task.md
Normal file
3
task.md
Normal file
@ -0,0 +1,3 @@
|
||||
/loop 持续优化字体子集化性能,可以大胆放开手脚的去做,但是优化完一定要通过基准测试。中途不要切换到其他模式,比如计划模式也不要询问我,你直接做就行了,请你持续的去优化,不要去询问我,不要去中断,好吧
|
||||
|
||||
啊,你每次优化能不能把基准测试保存在本地目录下,这样我方便查看。你的文档中应该在每个重大节点更新基准测试结果,这样我能方便看到你使用了哪些优化方法,得到了什么样的优化效果。
|
||||
@ -18,6 +18,7 @@ const isBaseline = process.argv[2] === "baseline";
|
||||
const FONT_PATH = "font/令东齐伋复刻体.ttf";
|
||||
const FONT_NAME = "令东齐伋复刻体";
|
||||
const SIMILARITY_THRESHOLD = 0.98;
|
||||
const BASELINE_DIR = "benchmark_results";
|
||||
|
||||
const raw = await readFile(FONT_PATH);
|
||||
const fontBuffer = new Uint8Array(raw).buffer;
|
||||
@ -135,15 +136,16 @@ function extractFontData(font: any) {
|
||||
|
||||
/** 注册子集字体并返回 fontFamily 名 */
|
||||
async function registerSubsetFont(ttfBuffer: ArrayBuffer, counter: number): Promise<string> {
|
||||
const fontPath = `verify_font_baseline/_verify_${counter}.ttf`;
|
||||
const fontPath = `${BASELINE_DIR}/_verify_${counter}.ttf`;
|
||||
await writeFile(fontPath, Buffer.from(ttfBuffer));
|
||||
const familyName = `SubsetFont_${counter}`;
|
||||
FontLibrary.use(familyName, [fontPath]);
|
||||
return familyName;
|
||||
}
|
||||
|
||||
await mkdir(BASELINE_DIR, { recursive: true });
|
||||
|
||||
if (isBaseline) {
|
||||
await mkdir("verify_font_baseline", { recursive: true });
|
||||
const baseline: Record<string, any> = {};
|
||||
|
||||
for (const { text, label } of testCases) {
|
||||
@ -151,7 +153,7 @@ if (isBaseline) {
|
||||
const font = Font.create(fontBuffer, { type: "ttf", subset });
|
||||
const data = extractFontData(font);
|
||||
|
||||
await writeFile(`verify_font_baseline/${label}.ttf`, Buffer.from(data.buffer));
|
||||
await writeFile(`${BASELINE_DIR}/${label}.ttf`, Buffer.from(data.buffer));
|
||||
|
||||
/** 用完整字体和子集字体分别渲染,保存像素数据 */
|
||||
const fullPixels = renderText(FONT_NAME, text, 48);
|
||||
@ -172,10 +174,10 @@ if (isBaseline) {
|
||||
console.log(` ${label}: glyf=${data.glyfCount}, output=${data.outputSize} bytes, ssim=${ssim.toFixed(4)}`);
|
||||
}
|
||||
|
||||
await writeFile("verify_font_baseline.json", JSON.stringify(baseline, null, 2));
|
||||
await writeFile(`${BASELINE_DIR}/verify_baseline.json`, JSON.stringify(baseline, null, 2));
|
||||
console.log("\n基准已生成(含完整字体+子集字体渲染像素数据及相似度)");
|
||||
} else {
|
||||
const baselineRaw = await readFile("verify_font_baseline.json", "utf-8");
|
||||
const baselineRaw = await readFile(`${BASELINE_DIR}/verify_baseline.json`, "utf-8");
|
||||
const baseline = JSON.parse(baselineRaw);
|
||||
|
||||
let allPassed = true;
|
||||
|
||||
46
基准测试.test.ts
46
基准测试.test.ts
@ -5,6 +5,7 @@
|
||||
* 测量:
|
||||
* 1. 子集化总耗时(Font.create → optimize → sort → write)
|
||||
* 2. 渲染相似度(子集字体 vs 完整字体,SSIM 指标)
|
||||
* 3. 输出渲染对比图片到 benchmark_results/ 目录
|
||||
*/
|
||||
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
||||
import { performance } from "node:perf_hooks";
|
||||
@ -13,6 +14,7 @@ import { Canvas, FontLibrary } from "skia-canvas";
|
||||
|
||||
const FONT_PATH = "font/令东齐伋复刻体.ttf";
|
||||
const FONT_NAME = "令东齐伋复刻体";
|
||||
const BENCHMARK_DIR = "benchmark_results";
|
||||
|
||||
const raw = await readFile(FONT_PATH);
|
||||
const fontBuffer = new Uint8Array(raw).buffer;
|
||||
@ -48,6 +50,22 @@ function renderText(fontFamily: string, text: string, fontSize: number): Uint8Ar
|
||||
return new Uint8Array(imgData.data.buffer);
|
||||
}
|
||||
|
||||
/** 渲染文字并保存为 PNG */
|
||||
async function renderTextToPng(fontFamily: string, text: string, fontSize: number, filePath: string) {
|
||||
const charWidth = Math.ceil(fontSize * 1.5);
|
||||
const width = text.length * charWidth + 20;
|
||||
const height = Math.ceil(fontSize * 1.5);
|
||||
const canvas = new Canvas(width, height);
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.fillStyle = "white";
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
ctx.font = `${fontSize}px ${fontFamily}`;
|
||||
ctx.fillStyle = "black";
|
||||
ctx.fillText(text, 10, Math.ceil(fontSize * 1.2));
|
||||
const buffer = await canvas.toBuffer("png");
|
||||
return writeFile(filePath, buffer);
|
||||
}
|
||||
|
||||
/** 计算两张图片的结构相似度(简化版 SSIM),返回 0~1 */
|
||||
function calculateSSIM(a: Uint8Array, b: Uint8Array): number {
|
||||
if (a.length !== b.length) return 0;
|
||||
@ -84,16 +102,27 @@ function calculateSSIM(a: Uint8Array, b: Uint8Array): number {
|
||||
|
||||
/** 注册子集字体用于渲染 */
|
||||
async function registerSubsetFont(ttfBuffer: ArrayBuffer, counter: number): Promise<string> {
|
||||
await mkdir("verify_font_baseline", { recursive: true });
|
||||
const fontPath = `verify_font_baseline/_bench_${counter}.ttf`;
|
||||
await mkdir(BENCHMARK_DIR, { recursive: true });
|
||||
const fontPath = `${BENCHMARK_DIR}/_bench_${counter}.ttf`;
|
||||
await writeFile(fontPath, Buffer.from(ttfBuffer));
|
||||
const familyName = `BenchSubset_${counter}`;
|
||||
FontLibrary.use(familyName, [fontPath]);
|
||||
return familyName;
|
||||
}
|
||||
|
||||
await mkdir(BENCHMARK_DIR, { recursive: true });
|
||||
|
||||
console.log("\n=== 字体裁剪基准测试 ===\n");
|
||||
|
||||
const results: Array<{
|
||||
label: string;
|
||||
avg: number;
|
||||
min: number;
|
||||
max: number;
|
||||
outputSize: number;
|
||||
ssim: number;
|
||||
}> = [];
|
||||
|
||||
for (const { label, text } of testCases) {
|
||||
const subset = [...text].map((c) => c.codePointAt(0)!);
|
||||
const times: number[] = [];
|
||||
@ -122,12 +151,25 @@ for (const { label, text } of testCases) {
|
||||
if (lastTtfBuffer) {
|
||||
subsetFontCounter++;
|
||||
const familyName = await registerSubsetFont(lastTtfBuffer, subsetFontCounter);
|
||||
|
||||
/** 保存渲染对比图片 */
|
||||
const safeLabel = label.replace(/[^a-zA-Z0-9\u4e00-\u9fff]/g, "_");
|
||||
await renderTextToPng(FONT_NAME, text, 48, `${BENCHMARK_DIR}/${safeLabel}_full.png`);
|
||||
await renderTextToPng(familyName, text, 48, `${BENCHMARK_DIR}/${safeLabel}_subset.png`);
|
||||
|
||||
const fullPixels = renderText(FONT_NAME, text, 48);
|
||||
const subsetPixels = renderText(familyName, text, 48);
|
||||
ssim = calculateSSIM(fullPixels, subsetPixels);
|
||||
}
|
||||
|
||||
results.push({ label, avg, min, max, outputSize: lastOutputSize, ssim });
|
||||
console.log(` ${label}: avg=${avg.toFixed(1)}ms min=${min.toFixed(1)}ms max=${max.toFixed(1)}ms 输出=${lastOutputSize.toLocaleString()} bytes ssim=${ssim.toFixed(4)}`);
|
||||
}
|
||||
|
||||
/** 保存结果到 JSON */
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||
const resultFile = `${BENCHMARK_DIR}/benchmark_${timestamp}.json`;
|
||||
await writeFile(resultFile, JSON.stringify({ timestamp: new Date().toISOString(), rounds: ROUNDS, results }, null, 2));
|
||||
console.log(`\n结果已保存到 ${resultFile}`);
|
||||
console.log(`渲染对比图片已保存到 ${BENCHMARK_DIR}/ 目录`);
|
||||
console.log("");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user