From b66427cd62eda984efd3511616f307566d068fcf Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 04:23:24 +0000 Subject: [PATCH] fix(export): render Monocraft font in minecraft mode PNG export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the manual `fontEmbedCSS` and the `getFontEmbedCSS()` helper so html-to-image's built-in `getWebFontCSS` runs instead. The previous code raw-dumped every `@font-face` rule from `document.styleSheets`, but those rules contain stylesheet-relative font URLs (e.g. `url("../media/Monocraft-...woff2")`) that can't resolve once embedded inside an SVG data URL. The browser silently fell back to Arial — which in minecraft mode replaced the pixel-perfect Monocraft with regular sans-serif Arial, exactly the bug in #282. `getWebFontCSS` walks the cloned subtree, resolves each font URL against its parent stylesheet's href, and inlines the woff2 as a data URL — so Monocraft renders correctly in the exported PNG. Fixes #282 Co-authored-by: functionstackx --- packages/app/src/hooks/useChartExport.ts | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/packages/app/src/hooks/useChartExport.ts b/packages/app/src/hooks/useChartExport.ts index d8e07c15..2b6f0ca9 100644 --- a/packages/app/src/hooks/useChartExport.ts +++ b/packages/app/src/hooks/useChartExport.ts @@ -95,21 +95,6 @@ function resolveCssVarsForExport(root: HTMLElement) { } } -/** Collect @font-face rules from all accessible stylesheets */ -function getFontEmbedCSS(): string { - const fontFaces: string[] = []; - for (const sheet of document.styleSheets) { - try { - for (const rule of sheet.cssRules || []) { - if (rule instanceof CSSFontFaceRule) fontFaces.push(rule.cssText); - } - } catch { - // skip CORS-restricted stylesheets - } - } - return fontFaces.join('\n'); -} - /** Wait for a React re-render to flush */ function waitForRender(): Promise { return new Promise((resolve) => { @@ -369,7 +354,9 @@ export function useChartExport({ }); } - // Capture chart image + // Don't pass `fontEmbedCSS`: raw `@font-face` rules keep relative URLs that + // can't resolve inside an SVG data URL. Letting html-to-image run its own + // `getWebFontCSS` inlines the woff2 as a data URL, so Monocraft renders. const { toPng } = await htmlToImagePromise; const chartDataUrl = await toPng(exportElement, { quality: 1, @@ -377,7 +364,6 @@ export function useChartExport({ backgroundColor: bgColor, cacheBust: true, skipFonts: false, - fontEmbedCSS: getFontEmbedCSS(), preferredFontFormat: 'woff2', filter: (node) => !node.classList?.contains('no-export'), style: {