fix: 更新字体输出格式为 ttf,优化加载性能

This commit is contained in:
崮生(子虚) 2026-08-27 06:06:42 +08:00
parent 8e2c68e6f9
commit cc792d27b0
12 changed files with 34 additions and 34 deletions

View File

@ -13,7 +13,7 @@ This project subsets fonts on the server side — pass in the text you need, get
<style>
@font-face {
font-family: "MyFont";
src: url("https://webfont.shenzilong.cn/api?font=令东齐伋复刻体&text=静心茶舍&outType=woff2") format("woff2");
src: url("https://webfont.shenzilong.cn/api?font=令东齐伋复刻体&text=静心茶舍&outType=ttf") format("truetype");
}
.title { font-family: "MyFont", serif; }
</style>
@ -97,7 +97,7 @@ No SDK needed. One CSS block to use any Chinese font. Server returns only the ch
```css
@font-face {
font-family: "MyFont";
src: url("https://webfont.shenzilong.cn/api?font=令东齐伋复刻体&text=你的文字&outType=woff2") format("woff2");
src: url("https://webfont.shenzilong.cn/api?font=令东齐伋复刻体&text=你的文字&outType=ttf") format("truetype");
}
```

View File

@ -13,7 +13,7 @@
<style>
@font-face {
font-family: "MyFont";
src: url("https://webfont.shenzilong.cn/api?font=令东齐伋复刻体&text=静心茶舍&outType=woff2") format("woff2");
src: url("https://webfont.shenzilong.cn/api?font=令东齐伋复刻体&text=静心茶舍&outType=ttf") format("truetype");
}
.title { font-family: "MyFont", serif; }
</style>
@ -98,7 +98,7 @@ Prompt: "生成一个东方禅意风格的茶品牌官网"
```css
@font-face {
font-family: "MyFont";
src: url("https://webfont.shenzilong.cn/api?font=令东齐伋复刻体&text=你的文字&outType=woff2") format("woff2");
src: url("https://webfont.shenzilong.cn/api?font=令东齐伋复刻体&text=你的文字&outType=ttf") format("truetype");
}
```

View File

@ -8,7 +8,7 @@ export async function handleGetConfig(req: Request, _res: Response) {
res: jsonResponse({
enableTempUpload,
adminUploadEnabled: !!adminApiKey,
supportedOutTypes: ["woff2", "ttf"],
supportedOutTypes: ["ttf", "woff2"],
/** 临时字体保留时限(秒) */
tempRetentionSeconds,
/** 字体子集化最大并发数 */

View File

@ -48,7 +48,7 @@ const blob = await leafer.export('png', { pixelRatio: 2 })
```ts
new WebFontPlugin(leafer, {
baseUrl: 'https://webfont.shenzilong.cn', // 自部署时改这里
outType: 'woff2',
outType: 'ttf', // 默认 ttf子集场景无 brotli 编码/解码开销,端到端更快;流量敏感可改 'woff2'
debounceMs: 120,
watch: true, // 持续监听画布变化;静态海报导出可关掉
debug: false,

View File

@ -16,7 +16,7 @@ export interface IWebFontOptions {
baseUrl?: string
/** CSS font-family 名,默认去掉扩展名的字体名 */
family?: string
/** 输出格式,默认 woff2 */
/** 输出格式,默认 ttf子集场景无 brotli 编码/解码开销,端到端更快(几 KB 的子集体积差异可忽略) */
outType?: 'woff2' | 'ttf'
}
@ -145,7 +145,7 @@ export class WebFontCSSMode {
ensurePreconnect(baseUrl)
this.engine.ensureState(key, options.fontName, {
baseUrl,
outType: options.outType ?? 'woff2',
outType: options.outType ?? 'ttf',
onLoadChunk: this.makeOnLoadChunk(key, family),
})
@ -175,7 +175,7 @@ export class WebFontCSSMode {
ensurePreconnect(baseUrl)
this.engine.ensureState(key, options.fontName, {
baseUrl,
outType: options.outType ?? 'woff2',
outType: options.outType ?? 'ttf',
onLoadChunk: this.makeOnLoadChunk(key, family),
})
@ -248,7 +248,7 @@ export class WebFontCSSMode {
ensurePreconnect(baseUrl)
this.engine.ensureState(key, options.fontName, {
baseUrl,
outType: options.outType ?? 'woff2',
outType: options.outType ?? 'ttf',
onLoadChunk: this.makeOnLoadChunk(key, family),
})
this.engine.submitText(key, options.text)

View File

@ -15,7 +15,7 @@ export interface IFontFaceOptions {
baseUrl?: string
/** 注册用的 family 名,默认去掉扩展名 */
family?: string
/** 输出格式,默认 woff2Node 端自动降级 ttf见 loadFontFace */
/** 输出格式,默认 ttf子集场景无 brotli 编码/解码开销端到端更快Node 端也强制 ttf见 loadFontFace */
outType?: 'woff2' | 'ttf'
}
@ -80,8 +80,8 @@ export class WebFontFontFaceMode {
const family = options.family ?? fontName.replace(/\.(ttf|otf|woff2?|ttc)$/i, '').trim()
const key = IncrementalEngine.fontKey(fontName, family)
const baseUrl = options.baseUrl ?? this.defaultBaseUrl
/** Node 模式统一用 ttfwoff2 注册返回 null 不可靠(探针实证) */
const outType = this.nodeEnv ? 'ttf' : options.outType ?? 'woff2'
/** Node 模式统一用 ttfwoff2 注册返回 null 不可靠(探针实证);浏览器默认也是 ttf见 IFontFaceOptions.outType 注释) */
const outType = this.nodeEnv ? 'ttf' : options.outType ?? 'ttf'
/** 浏览器分支FontFace + unicodeRange多 chunk 同 family 按字符精确生效) */
const handleChunkBrowser = async (chunk: LoadedChunk): Promise<void> => {

View File

@ -250,7 +250,7 @@
ensurePreconnect(baseUrl);
this.engine.ensureState(key, options.fontName, {
baseUrl,
outType: options.outType ?? "woff2",
outType: options.outType ?? "ttf",
onLoadChunk: this.makeOnLoadChunk(key, family)
});
if (this.pollTasks.has(options.selector)) clearInterval(this.pollTasks.get(options.selector).timer);
@ -273,7 +273,7 @@
ensurePreconnect(baseUrl);
this.engine.ensureState(key, options.fontName, {
baseUrl,
outType: options.outType ?? "woff2",
outType: options.outType ?? "ttf",
onLoadChunk: this.makeOnLoadChunk(key, family)
});
if (this.observeTasks.has(options.selector)) this.observeTasks.get(options.selector).dispose();
@ -323,7 +323,7 @@
ensurePreconnect(baseUrl);
this.engine.ensureState(key, options.fontName, {
baseUrl,
outType: options.outType ?? "woff2",
outType: options.outType ?? "ttf",
onLoadChunk: this.makeOnLoadChunk(key, family)
});
this.engine.submitText(key, options.text);
@ -519,8 +519,8 @@
const family = options.family ?? fontName.replace(/\.(ttf|otf|woff2?|ttc)$/i, "").trim();
const key = IncrementalEngine.fontKey(fontName, family);
const baseUrl = options.baseUrl ?? this.defaultBaseUrl;
/** Node 模式统一用 ttfwoff2 注册返回 null 不可靠(探针实证) */
const outType = this.nodeEnv ? "ttf" : options.outType ?? "woff2";
/** Node 模式统一用 ttfwoff2 注册返回 null 不可靠(探针实证);浏览器默认也是 ttf见 IFontFaceOptions.outType 注释) */
const outType = this.nodeEnv ? "ttf" : options.outType ?? "ttf";
/** 浏览器分支FontFace + unicodeRange多 chunk 同 family 按字符精确生效) */
const handleChunkBrowser = async (chunk) => {
const unicodeRanges = chunk.chars.map((c) => "U+" + c.codePointAt(0).toString(16).padStart(4, "0")).join(", ");

View File

@ -15,7 +15,7 @@ description: 中文字体按需裁剪与加载能力,可让 AI 在生成中文
<style>
@font-face {
font-family: "MyFont";
src: url("https://webfont.shenzilong.cn/api?font=令东齐伋复刻体&text=静心茶舍&outType=woff2") format("woff2");
src: url("https://webfont.shenzilong.cn/api?font=令东齐伋复刻体&text=静心茶舍&outType=ttf") format("truetype");
}
</style>
```
@ -33,13 +33,13 @@ GET https://webfont.shenzilong.cn/api/fonts
### 其他接口
```
GET https://webfont.shenzilong.cn/api?font={name}&text={chars}&outType=woff2 — 裁剪字体
GET https://webfont.shenzilong.cn/api?font={name}&text={chars}&outType=ttf — 裁剪字体
GET https://webfont.shenzilong.cn/api/fonts — 列出可用字体
GET https://webfont.shenzilong.cn/api/config — 服务配置
POST https://webfont.shenzilong.cn/api/upload?mode=temp — 临时上传字体
```
字体名支持模糊匹配(精确 > 前缀 > 包含)。`outType` 支持 `woff2` 和 `ttf`
字体名支持模糊匹配(精确 > 前缀 > 包含)。`outType` 支持 `ttf`(默认,服务端裁剪快 2~6 倍,子集场景端到端更快)和 `woff2`(体积小 ~50%,流量敏感时用)
### SDK 增量加载

View File

@ -26,7 +26,7 @@ onMounted(() => {
fontName: "令东齐伋复刻体.ttf",
selector: ".demo-after .zh-font",
family: "ZenSerif",
outType: "woff2",
outType: "ttf",
})
})
</script>

View File

@ -84,8 +84,8 @@ const messages = {
outputFormat: "输出格式",
woff2Label: "WOFF2 体积更小",
ttfLabel: "TTF 速度更快",
woff2Desc: "约压缩 50%,适合生产",
ttfDesc: "无编码开销,适合开发",
woff2Desc: "体积小约 50%,流量敏感时用",
ttfDesc: "无编码/解码开销,端到端更快",
// UploadSection.vue
uploadTip: "支持 .ttf 和 .otf 格式,建议上传 .ttf 字体文件以获得最佳兼容性",
@ -194,8 +194,8 @@ const messages = {
outputFormat: "Format",
woff2Label: "WOFF2 Smaller",
ttfLabel: "TTF Faster",
woff2Desc: "~50% smaller, for production",
ttfDesc: "No encoding overhead, for dev",
woff2Desc: "~50% smaller, for traffic-sensitive cases",
ttfDesc: "No encode/decode overhead, faster end-to-end",
// UploadSection.vue
uploadTip: "Supports .ttf and .otf. .ttf recommended for best compatibility",

View File

@ -116,16 +116,16 @@ const allPreviewText = computed(() => {
/** 子集字体 URL合并所有预览文字 */
const subsetFontUrl = computed(() => {
if (!allPreviewText.value) return "";
return `${origin.value}/api?font=${encodeURIComponent(fontName.value)}&text=${encodeURIComponent(allPreviewText.value)}&outType=woff2`;
return `${origin.value}/api?font=${encodeURIComponent(fontName.value)}&text=${encodeURIComponent(allPreviewText.value)}&outType=ttf`;
});
/** 动态注入 @font-face —— API 返回二进制 woff2,需用 @font-face 引入而非 <link rel=stylesheet> */
/** 动态注入 @font-face —— API 返回二进制 ttf,需用 @font-face 引入而非 <link rel=stylesheet> */
let injectedStyle: HTMLStyleElement | null = null;
watchEffect(() => {
/** SSG/SSR 阶段没有 document */
if (typeof document === "undefined") return;
const css = subsetFontUrl.value
? `@font-face { font-family: "${fontName.value}"; src: url("${subsetFontUrl.value}") format("woff2"); }`
? `@font-face { font-family: "${fontName.value}"; src: url("${subsetFontUrl.value}") format("truetype"); }`
: "";
if (!css) return;
if (!injectedStyle) {
@ -155,7 +155,7 @@ watchEffect(() => {
fontName: font,
text,
family: font,
outType: "woff2",
outType: "ttf",
}) ?? null;
} else {
charsetLoader.update(text);
@ -167,7 +167,7 @@ const usageCode = computed(() =>
`<style>
@font-face {
font-family: "${fontName.value}";
src: url("${origin.value}/api?font=${fontName.value}&text=你的文字&outType=woff2") format("woff2");
src: url("${origin.value}/api?font=${fontName.value}&text=你的文字&outType=ttf") format("truetype");
}
.my-title { font-family: "${fontName.value}"; }
</style>`

View File

@ -44,7 +44,7 @@ const outType = ref<"woff2" | "ttf">("ttf");
const serverConfig = ref<ServerConfig>({
enableTempUpload: false,
adminUploadEnabled: false,
supportedOutTypes: ["woff2", "ttf"],
supportedOutTypes: ["ttf", "woff2"],
});
onMounted(async () => {
@ -60,7 +60,7 @@ onMounted(async () => {
const [fontList, config] = await Promise.all([
fetchFonts().catch(() => [] as FontInfo[]),
fetchConfig().catch((): ServerConfig => ({ enableTempUpload: false, adminUploadEnabled: false, supportedOutTypes: ["woff2", "ttf"] })),
fetchConfig().catch((): ServerConfig => ({ enableTempUpload: false, adminUploadEnabled: false, supportedOutTypes: ["ttf", "woff2"] })),
]);
fonts.value = fontList;
serverConfig.value = config;
@ -220,7 +220,7 @@ async function refreshFonts() {
:fonts="fonts"
:selectedFont="selectedFont"
:onFontChange="onFontChange"
:supportedOutTypes="serverConfig.supportedOutTypes || ['woff2', 'ttf']"
:supportedOutTypes="serverConfig.supportedOutTypes || ['ttf', 'woff2']"
:outType="outType"
:onOutTypeChange="(v: 'woff2' | 'ttf') => outType = v"
/>