mirror of
https://github.com/2234839/web-font.git
synced 2026-04-29 21:00:45 +08:00
fix: 修复 OTF→TTF 子集字体浏览器渲染空白 + 改进基准测试
- API 默认 outType 改为 ttf(兼容性最好) - Cache-Control 从 immutable 改为 24h 缓存 - 基准测试改为 DOM 渲染 + puppeteer 截图 + pngjs 解码(更贴近真实浏览器) - 增加 maxp 表验证(maxPoints/maxContours 为 0 直接报错) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
af0ab38cec
commit
1e543d58ab
@ -314,12 +314,12 @@ async function handleFontSubset(req: Request, res: Response) {
|
||||
};
|
||||
}
|
||||
|
||||
/** LLRT 不支持 wasm,默认 ttf;Node.js 默认 woff2(体积更小) */
|
||||
/** 默认 ttf(兼容性最好);LLRT 不支持 wasm 只能用 ttf */
|
||||
const isLlrt = release_name === "llrt";
|
||||
const outTypeParam = params.get("outType") || "";
|
||||
const outType = (outTypeParam === "woff2" || outTypeParam === "ttf")
|
||||
? (isLlrt && outTypeParam === "woff2" ? "ttf" : outTypeParam)
|
||||
: (isLlrt ? "ttf" : "woff2");
|
||||
: "ttf";
|
||||
|
||||
const newFont = await fontSubset(oldFontBuffer, text, {
|
||||
outType: outType,
|
||||
@ -337,7 +337,7 @@ async function handleFontSubset(req: Request, res: Response) {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": contentTypes[outType] || "font/ttf",
|
||||
"Cache-Control": "public, max-age=31536000, immutable",
|
||||
"Cache-Control": "public, max-age=86400",
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
"@types/node": "^25.5.2",
|
||||
"@xmldom/xmldom": "^0.9.9",
|
||||
"jsdom": "^29.0.2",
|
||||
"pngjs": "^7.0.0",
|
||||
"puppeteer": "^24.40.0",
|
||||
"skia-canvas": "^3.0.8",
|
||||
"tsup": "^8.5.1",
|
||||
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@ -24,6 +24,9 @@ importers:
|
||||
jsdom:
|
||||
specifier: ^29.0.2
|
||||
version: 29.0.2
|
||||
pngjs:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
puppeteer:
|
||||
specifier: ^24.40.0
|
||||
version: 24.40.0(typescript@6.0.2)
|
||||
@ -1279,6 +1282,10 @@ packages:
|
||||
pkg-types@1.3.1:
|
||||
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
|
||||
|
||||
pngjs@7.0.0:
|
||||
resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==}
|
||||
engines: {node: '>=14.19.0'}
|
||||
|
||||
postcss-load-config@6.0.1:
|
||||
resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
|
||||
engines: {node: '>= 18'}
|
||||
@ -2790,6 +2797,8 @@ snapshots:
|
||||
mlly: 1.8.2
|
||||
pathe: 2.0.3
|
||||
|
||||
pngjs@7.0.0: {}
|
||||
|
||||
postcss-load-config@6.0.1(postcss@8.5.9):
|
||||
dependencies:
|
||||
lilconfig: 3.1.3
|
||||
|
||||
33
基准测试.test.ts
33
基准测试.test.ts
@ -19,6 +19,7 @@ import { createServer, type Server, type IncomingMessage, type ServerResponse }
|
||||
import { performance } from "node:perf_hooks";
|
||||
import puppeteer, { type Page } from "puppeteer";
|
||||
import { fontSubset } from "./backend/font_util/font.js";
|
||||
import { PNG } from "pngjs";
|
||||
|
||||
const BENCHMARK_DIR = "benchmark_results";
|
||||
const ROUNDS = 10;
|
||||
@ -46,7 +47,7 @@ function createFontServer(): Promise<{ server: Server; port: number }> {
|
||||
<style>
|
||||
@font-face { font-family: "${fontFamily}"; src: url("/fonts/${fontFamily}") format("${fontFormat}"); }
|
||||
body { margin: 0; background: white; }
|
||||
#text { font-family: "${fontFamily}", sans-serif; font-size: ${fontSize}px; line-height: 1.2; color: black; padding: ${Math.ceil(fontSize * 0.1)}px 10px; display: inline-block; }
|
||||
#text { font-family: "${fontFamily}", sans-serif; font-size: ${fontSize}px; line-height: 1.2; color: black; padding: ${Math.ceil(fontSize * 0.1)}px 10px; display: inline-block; white-space: nowrap; }
|
||||
</style></head><body>
|
||||
<div id="text">${text.replace(/</g, "<")}</div>
|
||||
<script>
|
||||
@ -119,31 +120,23 @@ async function renderTextViaBrowser(
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 DOM 渲染 + 页面截图来获取像素数据(而非 canvas)
|
||||
* 这样更贴近真实浏览器渲染行为,能检测到 maxp 等表异常导致的不渲染
|
||||
* DOM 渲染 + puppeteer 截图(而非 canvas 直接绘制)
|
||||
* 截图后用 pngjs 解码获取像素数据用于 SSIM 计算
|
||||
*/
|
||||
const screenshot = await page.screenshot({ type: "png" });
|
||||
const pixelData = await page.evaluate(() => {
|
||||
const el = document.getElementById("text")!;
|
||||
const rect = el.getBoundingClientRect();
|
||||
/** 用 canvas 从 DOM 元素提取像素 */
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
ctx.drawImage(document.documentElement as any, 0, 0);
|
||||
const imgData = ctx.getImageData(Math.round(rect.left), Math.round(rect.top), Math.round(rect.width), Math.round(rect.height));
|
||||
let ink = 0;
|
||||
for (let i = 0; i < imgData.data.length; i += 4) { if (imgData.data[i] < 128) ink++; }
|
||||
return { pixels: Array.from(imgData.data), ink };
|
||||
});
|
||||
const screenshot = await page.screenshot({ type: "png", clip: { x: 0, y: 0, width, height } });
|
||||
const png = PNG.sync.read(screenshot);
|
||||
const pixels = new Uint8Array(png.data);
|
||||
|
||||
let inkPixels = 0;
|
||||
for (let i = 0; i < pixels.length; i += 4) {
|
||||
if (pixels[i] < 128) inkPixels++;
|
||||
}
|
||||
|
||||
const inkPixels = pixelData.ink;
|
||||
if (inkPixels === 0) {
|
||||
throw new Error(`字体渲染无墨水像素 (${fontFamily}),字体可能未正确加载`);
|
||||
}
|
||||
|
||||
return { pixels: new Uint8Array(pixelData.pixels), screenshot: Buffer.from(screenshot), inkPixels };
|
||||
return { pixels, screenshot: Buffer.from(screenshot), inkPixels };
|
||||
}
|
||||
|
||||
/** 计算两张图片的结构相似度(简化版 SSIM),返回 0~1 */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user