From 59e26064e5db37af3ce0b2bd2d50230e9efe3cd2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 31 Mar 2026 18:25:15 +0800 Subject: [PATCH] fix: add cjk fallback fonts for psd export --- lib/agents/psd-template-renderer.ts | 44 ++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/lib/agents/psd-template-renderer.ts b/lib/agents/psd-template-renderer.ts index eb229a892..4f856a643 100644 --- a/lib/agents/psd-template-renderer.ts +++ b/lib/agents/psd-template-renderer.ts @@ -136,6 +136,47 @@ function getFontMimeType(font: AgentPsdFont | undefined, fontFamily: string) { return "font/ttf"; } +const GENERIC_FONT_FAMILIES = new Set(["sans-serif", "serif", "monospace", "cursive", "fantasy", "system-ui"]); +const CJK_FALLBACK_FONT_FAMILIES = [ + "Noto Sans CJK SC", + "Noto Sans SC", + "Source Han Sans SC", + "Microsoft YaHei", + "PingFang SC", + "Hiragino Sans GB", + "WenQuanYi Zen Hei", + "SimHei", + "Arial Unicode MS", + "sans-serif", +] as const; + +function textContainsCjkGlyphs(value: string) { + return /[\u3400-\u9fff\u3040-\u30ff\uac00-\ud7af]/.test(String(value || "")); +} + +function buildSvgFontFamilyStack(fontFamily: string, text: string) { + const familyTokens = new Set(); + const primaryFamily = String(fontFamily || "").trim(); + + if (primaryFamily) { + familyTokens.add(primaryFamily); + } + + if (textContainsCjkGlyphs(text)) { + for (const fallbackFamily of CJK_FALLBACK_FONT_FAMILIES) { + familyTokens.add(fallbackFamily); + } + } else { + familyTokens.add("sans-serif"); + } + + return Array.from(familyTokens) + .map((family) => + GENERIC_FONT_FAMILIES.has(family) ? family : `"${escapeXml(family)}"` + ) + .join(", "); +} + async function createTextOverlayBuffer(params: { width: number; height: number; @@ -192,6 +233,7 @@ async function createTextOverlayBuffer(params: { } const escapedFamily = escapeXml(fontFamily || "sans-serif"); + const fontFamilyStack = buildSvgFontFamilyStack(fontFamily, normalizedText); const fontFaceCss = fontBuffer ? `@font-face { font-family: "${escapedFamily}"; src: url("data:${getFontMimeType(font, fontFamily)};base64,${fontBuffer.toString("base64")}"); }` : ""; @@ -217,7 +259,7 @@ async function createTextOverlayBuffer(params: {