diff --git a/src/utils/svgDownload.ts b/src/utils/svgDownload.ts index fe12f34..b8c68bc 100644 --- a/src/utils/svgDownload.ts +++ b/src/utils/svgDownload.ts @@ -3,6 +3,55 @@ const SVG_NS = "http://www.w3.org/2000/svg" const TABLER_DOWNLOAD_ICON = `` +const stripQuotes = (s: string): string => s.trim().replace(/^["']|["']$/g, "") + +const collectFontFamilies = (svg: SVGSVGElement): Set => { + const families = new Set() + const collect = (raw: string | null) => { + if (!raw) return + raw.split(",").forEach((part) => { + const cleaned = stripQuotes(part) + if (cleaned) families.add(cleaned) + }) + } + collect(svg.getAttribute("font-family")) + svg.querySelectorAll("[font-family]").forEach((el) => + collect(el.getAttribute("font-family")) + ) + return families +} + +const isFontFaceRule = (rule: CSSRule): boolean => { + if (typeof CSSFontFaceRule !== "undefined" && rule instanceof CSSFontFaceRule) + return true + return rule.cssText.trimStart().startsWith("@font-face") +} + +const collectFontFaceCss = (families: Set): string => { + if (families.size === 0) return "" + const parts: string[] = [] + for (const sheet of Array.from(document.styleSheets)) { + let rules: CSSRuleList | null = null + try { + rules = sheet.cssRules + } catch { + continue + } + if (!rules) continue + for (const rule of Array.from(rules)) { + if (!isFontFaceRule(rule)) continue + const styleRule = rule as CSSFontFaceRule + const family = stripQuotes( + styleRule.style?.getPropertyValue("font-family") ?? "" + ) + if (family && families.has(family)) { + parts.push(rule.cssText) + } + } + } + return parts.join("\n") +} + const getSvgPixelSize = (svg: SVGSVGElement): { width: number; height: number } => { const viewBox = svg.viewBox?.baseVal if (viewBox && viewBox.width > 0 && viewBox.height > 0) { @@ -36,6 +85,14 @@ const buildExportableSvgString = (svg: SVGSVGElement): string => { bg.setAttribute("fill", "#ffffff") clone.insertBefore(bg, clone.firstChild) + const fontFaceCss = collectFontFaceCss(collectFontFamilies(svg)) + if (fontFaceCss) { + const style = document.createElementNS(SVG_NS, "style") + style.setAttribute("type", "text/css") + style.textContent = fontFaceCss + clone.insertBefore(style, clone.firstChild) + } + return new XMLSerializer().serializeToString(clone) } @@ -67,6 +124,9 @@ const downloadAsPng = async ( const svgUrl = URL.createObjectURL(svgBlob) try { + if (document.fonts?.ready) { + await document.fonts.ready + } const img = new Image() img.crossOrigin = "anonymous" await new Promise((resolve, reject) => {