@pixldocs/canvas-renderer 0.5.174 → 0.5.176
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{index-DLAkGTqW.js → index-C03-L2j3.js} +38 -42
- package/dist/index-C03-L2j3.js.map +1 -0
- package/dist/{index-BH1kJLpb.cjs → index-DSe1eOVD.cjs} +38 -42
- package/dist/index-DSe1eOVD.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/{svgTextToPath-ra4EhtBL.js → svgTextToPath-BoT6H7Lz.js} +12 -2
- package/dist/svgTextToPath-BoT6H7Lz.js.map +1 -0
- package/dist/{svgTextToPath-BLk_mcqi.cjs → svgTextToPath-hYM9qTeC.cjs} +12 -2
- package/dist/svgTextToPath-hYM9qTeC.cjs.map +1 -0
- package/dist/{vectorPdfExport-B4B0CPjW.js → vectorPdfExport-CA6a-apV.js} +8 -16
- package/dist/{vectorPdfExport-B4B0CPjW.js.map → vectorPdfExport-CA6a-apV.js.map} +1 -1
- package/dist/{vectorPdfExport-wyGn4ybk.cjs → vectorPdfExport-CZWsOCqF.cjs} +8 -16
- package/dist/{vectorPdfExport-wyGn4ybk.cjs.map → vectorPdfExport-CZWsOCqF.cjs.map} +1 -1
- package/package.json +1 -1
- package/dist/index-BH1kJLpb.cjs.map +0 -1
- package/dist/index-DLAkGTqW.js.map +0 -1
- package/dist/svgTextToPath-BLk_mcqi.cjs.map +0 -1
- package/dist/svgTextToPath-ra4EhtBL.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const index = require("./index-
|
|
3
|
+
const index = require("./index-DSe1eOVD.cjs");
|
|
4
4
|
exports.DEPLOYMENT_VERSION_MARKER = index.DEPLOYMENT_VERSION_MARKER;
|
|
5
5
|
exports.FONT_FALLBACK_DEVANAGARI = index.FONT_FALLBACK_DEVANAGARI;
|
|
6
6
|
exports.FONT_FALLBACK_MATH = index.FONT_FALLBACK_MATH;
|
package/dist/index.d.ts
CHANGED
|
@@ -324,9 +324,9 @@ export declare interface PdfAssemblyOptions {
|
|
|
324
324
|
outlineText?: boolean;
|
|
325
325
|
/**
|
|
326
326
|
* Per-template text rendering mode.
|
|
327
|
-
* - 'auto' : outline
|
|
328
|
-
* - 'selectable' :
|
|
329
|
-
* - 'pixel-perfect' : outline every <text> for 100%
|
|
327
|
+
* - 'auto' : outline gradient-filled text only; other text remains selectable.
|
|
328
|
+
* - 'selectable' : keep text live; gradient text falls back to first gradient stop.
|
|
329
|
+
* - 'pixel-perfect' : outline every <text> for 100% visual parity.
|
|
330
330
|
* When omitted, falls back to `outlineText` (legacy boolean) for backwards compatibility.
|
|
331
331
|
*/
|
|
332
332
|
textMode?: 'auto' | 'selectable' | 'pixel-perfect';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D, F, o, q, s, P, t, u, v, w, x, y, z, B, C, E, G, H, I, J, K, L, b, M, N, O, Q, R, S, U, V, W, X, Y, Z, _, $, a0, a1 } from "./index-
|
|
1
|
+
import { D, F, o, q, s, P, t, u, v, w, x, y, z, B, C, E, G, H, I, J, K, L, b, M, N, O, Q, R, S, U, V, W, X, Y, Z, _, $, a0, a1 } from "./index-C03-L2j3.js";
|
|
2
2
|
export {
|
|
3
3
|
D as DEPLOYMENT_VERSION_MARKER,
|
|
4
4
|
F as FONT_FALLBACK_DEVANAGARI,
|
|
@@ -442,7 +442,7 @@ function textHasGradientFill(textEl) {
|
|
|
442
442
|
return false;
|
|
443
443
|
}
|
|
444
444
|
function replaceGradientTextFillsWithFirstStop(svgStr) {
|
|
445
|
-
if (!svgStr ||
|
|
445
|
+
if (!svgStr || !/url\s*\(\s*['"]?#/i.test(svgStr)) return svgStr;
|
|
446
446
|
let doc;
|
|
447
447
|
try {
|
|
448
448
|
doc = new DOMParser().parseFromString(svgStr, "image/svg+xml");
|
|
@@ -499,6 +499,15 @@ function replaceGradientTextFillsWithFirstStop(svgStr) {
|
|
|
499
499
|
doc.querySelectorAll("text, tspan").forEach(resolveFillAttr);
|
|
500
500
|
return new XMLSerializer().serializeToString(doc.documentElement);
|
|
501
501
|
}
|
|
502
|
+
async function prepareSvgTextForPdfMode(svgStr, textMode = "selectable", fontBaseUrl) {
|
|
503
|
+
if (textMode === "pixel-perfect") {
|
|
504
|
+
return convertDevanagariTextToPath(svgStr, fontBaseUrl, { mode: "all" });
|
|
505
|
+
}
|
|
506
|
+
if (textMode === "auto") {
|
|
507
|
+
return convertDevanagariTextToPath(svgStr, fontBaseUrl, { mode: "gradient-only" });
|
|
508
|
+
}
|
|
509
|
+
return replaceGradientTextFillsWithFirstStop(svgStr);
|
|
510
|
+
}
|
|
502
511
|
async function convertDevanagariTextToPath(svgStr, fontBaseUrl, options = {}) {
|
|
503
512
|
var _a, _b, _c;
|
|
504
513
|
const baseUrl = fontBaseUrl ?? (typeof window !== "undefined" ? window.location.origin + "/fonts/" : "/fonts/");
|
|
@@ -783,6 +792,7 @@ export {
|
|
|
783
792
|
convertAllTextToPath,
|
|
784
793
|
convertDevanagariTextToPath,
|
|
785
794
|
preloadDevanagariFont,
|
|
795
|
+
prepareSvgTextForPdfMode,
|
|
786
796
|
replaceGradientTextFillsWithFirstStop
|
|
787
797
|
};
|
|
788
|
-
//# sourceMappingURL=svgTextToPath-
|
|
798
|
+
//# sourceMappingURL=svgTextToPath-BoT6H7Lz.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svgTextToPath-BoT6H7Lz.js","sources":["../../../src/lib/harfbuzzShaper.ts","../../../src/lib/svgTextToPath.ts"],"sourcesContent":["/**\n * HarfBuzz Shaper — Browser-side complex-script text shaping for vector PDF export.\n *\n * `opentype.js`'s `getPath()` only does naive codepoint→glyph mapping with very\n * limited GSUB support. That breaks complex scripts like Devanagari (Hindi /\n * Marathi / Sanskrit) where glyphs require:\n * - Conjuncts (क्ष, ज्ञ, त्र — multi-consonant ligatures)\n * - Reph (र above following consonant)\n * - Half-forms before virama\n * - Matra reordering (कि — इ matra rendered before क but typed after)\n * - Indic-specific GSUB lookups (nukt, akhn, rphf, blwf, half, pstf, vatu,\n * cjct, pres, abvs, blws, psts, haln, rkrf)\n *\n * The browser canvas preview works because the browser's text renderer uses\n * HarfBuzz internally. Our EC2 PDF works because Puppeteer also uses HarfBuzz.\n * The client-side vector PDF was the only path missing a real shaper.\n *\n * This module wraps `harfbuzzjs` (HarfBuzz compiled to WASM, ~400KB) and\n * returns a single shaped SVG path string per text run, ready to drop into\n * the SVG that we hand to svg2pdf.js.\n *\n * Lazy-loaded — the WASM is only fetched the first time a Devanagari (or other\n * complex-script) string is encountered during PDF export.\n */\n\n// hb.wasm is served from /public/wasm/hb.wasm\nconst HB_WASM_URL = '/wasm/hb.wasm';\n\ntype HBFont = {\n ptr: number;\n setScale: (x: number, y: number) => void;\n glyphToPath: (glyphId: number) => string;\n destroy: () => void;\n};\n\ntype HBBuffer = {\n addText: (text: string) => void;\n guessSegmentProperties: () => void;\n setDirection: (dir: 'ltr' | 'rtl' | 'ttb' | 'btt') => void;\n setScript: (script: string) => void;\n setLanguage: (lang: string) => void;\n json: () => Array<{\n g: number;\n cl: number;\n ax: number;\n ay: number;\n dx: number;\n dy: number;\n flags?: number;\n }>;\n destroy: () => void;\n};\n\ntype HBInstance = {\n createBlob: (data: Uint8Array | ArrayBuffer) => { destroy: () => void };\n createFace: (blob: any, index: number) => { upem?: number; destroy: () => void; getUpem?: () => number };\n createFont: (face: any) => HBFont;\n createBuffer: () => HBBuffer;\n shape: (font: HBFont, buffer: HBBuffer, features?: string) => void;\n};\n\nlet hbInstancePromise: Promise<HBInstance> | null = null;\n\n/** Lazy-load and initialize HarfBuzz WASM. Idempotent. */\nasync function getHB(): Promise<HBInstance> {\n if (hbInstancePromise) return hbInstancePromise;\n\n hbInstancePromise = (async () => {\n // Dynamically import to keep HarfBuzz out of the main bundle.\n // hb.js is an Emscripten module that exports a `createHarfBuzz` factory.\n // It looks up the .wasm file via Module.locateFile.\n const [{ default: createHarfBuzz }, { default: hbjs }] = await Promise.all([\n import('harfbuzzjs/hb.js'),\n import('harfbuzzjs/hbjs.js'),\n ]);\n\n const moduleInstance = await createHarfBuzz({\n locateFile: (path: string) => {\n if (path.endsWith('.wasm')) return HB_WASM_URL;\n return path;\n },\n });\n\n return hbjs(moduleInstance) as HBInstance;\n })();\n\n return hbInstancePromise;\n}\n\n/** Per-font cache: parsed face/font ready for repeated shaping calls. */\nconst hbFontCache = new Map<string, { face: any; font: HBFont; upem: number }>();\n\n/**\n * Register (or fetch from cache) a font with HarfBuzz.\n * `cacheKey` should uniquely identify the font binary (e.g. `family|weight`).\n * `fontDataLoader` is only invoked on cache miss.\n */\nasync function getHBFont(\n cacheKey: string,\n fontDataLoader: () => Promise<Uint8Array>,\n): Promise<{ font: HBFont; upem: number }> {\n const cached = hbFontCache.get(cacheKey);\n if (cached) return { font: cached.font, upem: cached.upem };\n\n const hb = await getHB();\n const fontData = await fontDataLoader();\n const blob = hb.createBlob(fontData);\n const face = hb.createFace(blob, 0);\n const font = hb.createFont(face);\n\n // Use the font's native upem so glyphToPath coordinates are in font units.\n // `setScale(upem, upem)` keeps the natural EM-square coordinates.\n const upem = (face.getUpem ? face.getUpem() : (face.upem ?? 1000)) || 1000;\n font.setScale(upem, upem);\n\n // Blob can be destroyed; face/font hold their own references.\n blob.destroy();\n\n hbFontCache.set(cacheKey, { face, font, upem });\n return { font, upem };\n}\n\nexport interface ShapedRun {\n /** SVG path data ready to drop into a <path d=\"...\"> element. */\n pathData: string;\n /** Total advance width of the run, in user-space units (matching `fontSize`). */\n width: number;\n}\n\n/**\n * Shape `text` with `fontData` at `fontSize` and return a single SVG path\n * positioned starting at (`x`, `y`) on the SVG baseline.\n *\n * `fontSize` is the rendered point size (matches CSS `font-size`).\n * `cacheKey` must uniquely identify `fontData` so subsequent calls hit cache.\n */\nexport async function shapeRunToSvgPath(\n fontData: Uint8Array | (() => Promise<Uint8Array>),\n cacheKey: string,\n text: string,\n x: number,\n y: number,\n fontSize: number,\n opts?: { script?: string; language?: string; direction?: 'ltr' | 'rtl' },\n): Promise<ShapedRun> {\n const hb = await getHB();\n const loader = typeof fontData === 'function'\n ? (fontData as () => Promise<Uint8Array>)\n : async () => fontData as Uint8Array;\n\n const { font, upem } = await getHBFont(cacheKey, loader);\n\n const buffer = hb.createBuffer();\n buffer.addText(text);\n // Setting script + direction explicitly is more reliable than guess for\n // Indic mixed-content runs.\n if (opts?.direction) buffer.setDirection(opts.direction);\n if (opts?.script) buffer.setScript(opts.script);\n if (opts?.language) buffer.setLanguage(opts.language);\n buffer.guessSegmentProperties();\n\n hb.shape(font, buffer);\n const glyphs = buffer.json();\n\n // HarfBuzz coordinate system: Y is UP, units are font units (we set scale\n // to upem so 1 unit == 1 font-design-unit). Convert to SVG user-space:\n // svgUnits = fontUnits * (fontSize / upem)\n // and flip Y.\n const scale = fontSize / upem;\n\n // Pen position, in font units.\n let penX = 0;\n let penY = 0;\n const pieces: string[] = [];\n\n for (const g of glyphs) {\n const rawPath = font.glyphToPath(g.g);\n if (rawPath) {\n // Transform glyph path:\n // 1. Translate to (penX + dx, penY + dy) in font units\n // 2. Flip Y (since HB Y-up, SVG Y-down)\n // 3. Scale by `scale`\n // 4. Translate by (x, y) in user units\n const ox = (penX + g.dx) * scale + x;\n const oy = (penY + g.dy) * -scale + y;\n pieces.push(transformPathData(rawPath, scale, -scale, ox, oy));\n }\n penX += g.ax;\n penY += g.ay;\n }\n\n buffer.destroy();\n\n return {\n pathData: pieces.join(''),\n width: penX * scale,\n };\n}\n\n/**\n * Apply an affine transform (sx, sy, tx, ty) to an SVG path's absolute\n * coordinates. The path emitted by HarfBuzz uses only absolute commands\n * (M, L, C, Q, Z), so we only need to transform numeric coordinate pairs.\n */\nfunction transformPathData(d: string, sx: number, sy: number, tx: number, ty: number): string {\n // Tokenize into commands and number pairs.\n // HB output format: \"M x,y\", \"L x,y\", \"C cx1,cy1 cx2,cy2 x,y\", \"Q cx,cy x,y\", \"Z\"\n // Numbers may be negative or fractional.\n let out = '';\n let i = 0;\n const n = d.length;\n while (i < n) {\n const c = d[i];\n if (c === 'M' || c === 'L' || c === 'C' || c === 'Q') {\n out += c;\n i++;\n // Read remaining coordinate pairs until next command letter or end.\n // Pairs are separated by space or comma.\n let pairsBuf = '';\n while (i < n && d[i] !== 'M' && d[i] !== 'L' && d[i] !== 'C' && d[i] !== 'Q' && d[i] !== 'Z') {\n pairsBuf += d[i];\n i++;\n }\n // pairsBuf now holds something like \"12.5,30.2 -4,5 0,0\"\n const nums = pairsBuf.trim().split(/[ ,]+/).filter(Boolean).map(Number);\n for (let k = 0; k < nums.length; k += 2) {\n const px = nums[k] * sx + tx;\n const py = nums[k + 1] * sy + ty;\n if (k > 0) out += ' ';\n out += `${round(px)},${round(py)}`;\n }\n } else if (c === 'Z') {\n out += 'Z';\n i++;\n } else {\n // Skip unexpected character (e.g. stray whitespace)\n i++;\n }\n }\n return out;\n}\n\nfunction round(n: number): string {\n // 2 decimal places — same precision as opentype.js fallback.\n return (Math.round(n * 100) / 100).toString();\n}\n\n/**\n * Pre-warm HarfBuzz WASM so the first PDF export doesn't pay the load cost.\n * Safe to call multiple times.\n */\nexport function preloadHarfBuzz(): Promise<HBInstance> {\n return getHB();\n}\n","/**\n * SVG Text-to-Path Conversion (universal outlining)\n *\n * Converts EVERY <text> element to <path> elements using the actual font the\n * browser is rendering. This guarantees 100% visual parity between the canvas\n * preview and the downloaded vector PDF — what you see is what you get, for\n * any font (Google Fonts, local TTFs) and any script (Latin, Devanagari, etc.).\n *\n * Tradeoffs vs. leaving text as <text>:\n * + Pixel-perfect parity for any font, including unknown weights, custom kerns,\n * ligatures, and complex Indic shaping.\n * - Text is no longer selectable/searchable in the PDF.\n * - PDF file size grows ~30-50% (paths are bigger than text + embedded TTF).\n *\n * Mechanics:\n * - Latin runs → opentype.js getPath() (uses font's own metrics)\n * - Devanagari → HarfBuzz shaping for proper conjuncts/matra reordering\n * - Mixed runs → split by script, advance cursor between runs\n * - text-anchor → run width measured via the font; offset applied\n * - Failure → keep original <text> so the user still sees something\n */\n\nimport * as opentype from 'opentype.js';\nimport {\n FONT_FILES,\n FONT_FALLBACK_SYMBOLS,\n FONT_FALLBACK_DEVANAGARI,\n FONT_FALLBACK_MATH,\n resolveFontWeight,\n resolveDevanagariSibling,\n getGoogleFontBytes,\n getFontshareFontBytes,\n} from '@/lib/pdfFonts';\nimport type { FontWeightFiles } from '@/lib/pdfFonts';\nimport { shapeRunToSvgPath } from '@/lib/harfbuzzShaper';\n\n// Cache parsed fonts to avoid re-fetching\nconst fontCache = new Map<string, opentype.Font>();\n/** Raw font bytes cache — shared between opentype.js and HarfBuzz */\nconst fontBytesCache = new Map<string, Uint8Array>();\n\n/** True if the character is in the Devanagari Unicode block */\nfunction isDevanagari(char: string): boolean {\n const c = char.codePointAt(0) ?? 0;\n return (c >= 0x0900 && c <= 0x097F) || (c >= 0xA8E0 && c <= 0xA8FF) || (c >= 0x1CD0 && c <= 0x1CFF);\n}\n\n/** Check if a string contains any Devanagari characters */\nfunction containsDevanagari(text: string): boolean {\n if (!text) return false;\n for (const char of text) {\n if (isDevanagari(char)) return true;\n }\n return false;\n}\n\n/** True if the character is in Basic Latin, Latin-1, or Latin Extended. */\nfunction isBasicLatinOrLatinExtended(char: string): boolean {\n const c = char.codePointAt(0) ?? 0;\n return c <= 0x024F;\n}\n\ntype FontRunType = 'main' | 'devanagari' | 'math' | 'symbol';\n\n/** Math operators / arrows (≠ ≤ ≥ ≈ ∞ → ← ∑ √ ∈ …) — typically missing from\n * Latin fonts AND from the Noto Sans symbol fallback subset, so they need\n * the dedicated Noto Sans Math fallback to render correctly. */\nfunction isMathOperatorChar(char: string): boolean {\n const c = char.codePointAt(0) ?? 0;\n if (c >= 0x2190 && c <= 0x21FF) return true; // Arrows\n if (c >= 0x2200 && c <= 0x22FF) return true; // Mathematical Operators\n if (c >= 0x2300 && c <= 0x23FF) return true; // Misc Technical\n if (c >= 0x27C0 && c <= 0x27EF) return true; // Misc Math Symbols-A\n if (c >= 0x2980 && c <= 0x29FF) return true; // Misc Math Symbols-B\n if (c >= 0x2A00 && c <= 0x2AFF) return true; // Supplemental Math Operators\n if (c >= 0x2B00 && c <= 0x2B59) return true;\n return false;\n}\n\nfunction classifyCharForFontRun(char: string): FontRunType {\n if (isBasicLatinOrLatinExtended(char)) return 'main';\n if (isDevanagari(char)) return 'devanagari';\n if (isMathOperatorChar(char)) return 'math';\n return 'symbol';\n}\n\n/** Neutral marks/punctuation that don't need their own glyph coverage decision. */\nfunction isIgnorableForCoverage(char: string): boolean {\n return /\\s/.test(char) || /[\\u0964\\u0965\\u200c\\u200d]/u.test(char);\n}\n\n/** True when the parsed font can actually draw every relevant glyph in this run. */\nfunction fontSupportsRun(font: opentype.Font | null, text: string, runType: FontRunType): boolean {\n if (!font) return false;\n for (const char of text) {\n if (isIgnorableForCoverage(char)) continue;\n if (runType === 'devanagari' && !isDevanagari(char)) continue;\n if (runType === 'symbol' && classifyCharForFontRun(char) !== 'symbol') continue;\n if (runType === 'math' && classifyCharForFontRun(char) !== 'math') continue;\n const glyph = font.charToGlyph(char);\n if (!glyph || glyph.index === 0) return false;\n }\n return true;\n}\n\nconst browserMeasureCanvas: HTMLCanvasElement | null = typeof document !== 'undefined'\n ? document.createElement('canvas')\n : null;\n\n/** Measure with the browser's real canvas fallback stack — this is our preview truth source. */\nfunction measureBrowserWidth(fontFamily: string, weight: number, fontSize: number, text: string): number | null {\n const ctx = browserMeasureCanvas?.getContext('2d');\n if (!ctx) return null;\n ctx.font = `normal normal ${weight} ${fontSize}px \"${fontFamily}\"`;\n return ctx.measureText(text).width;\n}\n\nfunction uniqueFamilies(families: Array<string | null | undefined>): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const family of families) {\n const clean = family?.trim();\n if (!clean || seen.has(clean)) continue;\n seen.add(clean);\n out.push(clean);\n }\n return out;\n}\n\nfunction fontLooksItalic(font: opentype.Font | null | undefined): boolean {\n if (!font) return false;\n const names = font.names as unknown as Record<string, Record<string, string> | undefined>;\n const candidates = [\n names.fontSubfamily?.en,\n names.typographicSubfamily?.en,\n names.fullName?.en,\n names.postScriptName?.en,\n ].filter(Boolean).join(' ');\n return /italic|oblique/i.test(candidates);\n}\n\nfunction syntheticItalicTransform(anchorX: number, baselineY: number): string {\n return `translate(${anchorX} ${baselineY}) skewX(-12) translate(${-anchorX} ${-baselineY})`;\n}\n\n/** Get the font file path for a given weight */\nfunction getFontPath(\n fontFiles: FontWeightFiles,\n weight: number,\n isItalic = false,\n): string | null {\n const resolved = resolveFontWeight(weight);\n const uprightMap: Record<number, (keyof FontWeightFiles)[]> = {\n 300: ['light', 'regular'],\n 400: ['regular'],\n 500: ['medium', 'regular'],\n 600: ['semibold', 'bold'],\n 700: ['bold', 'semibold', 'regular'],\n };\n // Italic/oblique: prefer the matching italic variant, then fall back through\n // other italic variants, then upright as a last resort. Without this italic\n // tspans (e.g. *italic* markdown) silently rendered as upright in the PDF.\n const italicMap: Record<number, (keyof FontWeightFiles)[]> = {\n 300: ['lightItalic', 'italic', 'light', 'regular'],\n 400: ['italic', 'regular'],\n 500: ['mediumItalic', 'italic', 'medium', 'regular'],\n 600: ['semiboldItalic', 'boldItalic', 'italic', 'semibold', 'bold', 'regular'],\n 700: ['boldItalic', 'semiboldItalic', 'italic', 'bold', 'semibold', 'regular'],\n };\n const map = isItalic ? italicMap : uprightMap;\n for (const key of map[resolved] || ['regular']) {\n if (fontFiles[key]) return fontFiles[key]!;\n }\n return fontFiles.regular || null;\n}\n\n/** Build a fetchable font URL without accidentally producing /fonts/fonts/*. */\nfunction resolveFontUrl(fileName: string, fontBaseUrl: string): string {\n if (/^https?:\\/\\//i.test(fileName) || fileName.startsWith('data:')) return fileName;\n if (fileName.startsWith('/')) {\n return typeof window !== 'undefined' ? new URL(fileName, window.location.origin).toString() : fileName;\n }\n const baseUrl = fontBaseUrl.endsWith('/') ? fontBaseUrl : fontBaseUrl + '/';\n return new URL(fileName, baseUrl).toString();\n}\n\n/** Load and cache a font via opentype.js */\nasync function loadFont(fontFamily: string, weight: number, fontBaseUrl: string, isItalic = false): Promise<opentype.Font | null> {\n const cacheKey = `${fontFamily}__${weight}__${isItalic ? 'i' : 'n'}`;\n if (fontCache.has(cacheKey)) return fontCache.get(cacheKey)!;\n\n const fontFiles = FONT_FILES[fontFamily];\n\n // ── Local TTF path ──\n // Use local files for both upright and italic variants when available.\n // (Previously italic always fell through to Google/Fontshare and silently\n // dropped to the regular weight when those lookups failed, so *italic*\n // markdown text rendered upright in the vector PDF.)\n if (fontFiles) {\n const fileName = getFontPath(fontFiles, weight, isItalic);\n if (!fileName) return null;\n const url = resolveFontUrl(fileName, fontBaseUrl);\n try {\n const response = await fetch(url);\n if (!response.ok) {\n console.warn(`[text-to-path] Failed to fetch font ${url}: ${response.status}`);\n return null;\n }\n const buffer = await response.arrayBuffer();\n const bytes = new Uint8Array(buffer);\n fontBytesCache.set(cacheKey, bytes);\n const font = opentype.parse(buffer);\n fontCache.set(cacheKey, font);\n return font;\n } catch (err) {\n console.warn(`[text-to-path] Failed to load local font ${fontFamily}:`, err);\n return null;\n }\n }\n\n // ── Google Fonts fallback (e.g. Mukta, Tiro Devanagari Hindi, Kalam) ──\n try {\n const resolvedWeight = resolveFontWeight(weight);\n // Try Google first, then Fontshare. Both populate the same byte cache shape\n // (`gf:${family}:${weight}:n|i`) so downstream HarfBuzz lookups still hit.\n let bytes = await getGoogleFontBytes(fontFamily, resolvedWeight, isItalic);\n if (!bytes) {\n bytes = await getFontshareFontBytes(fontFamily, resolvedWeight, isItalic);\n }\n // For italic, fall back to upright if italic variant is unavailable so\n // the bold/italic combo still renders the right family.\n if (!bytes && isItalic) {\n bytes = await getGoogleFontBytes(fontFamily, resolvedWeight, false);\n if (!bytes) bytes = await getFontshareFontBytes(fontFamily, resolvedWeight, false);\n }\n if (!bytes) {\n console.warn(`[text-to-path] No TTF available for ${fontFamily} (${weight}) on Google Fonts or Fontshare`);\n return null;\n }\n fontBytesCache.set(cacheKey, bytes);\n // opentype.parse needs an ArrayBuffer (not a typed-array view).\n const ab = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n const font = opentype.parse(ab);\n fontCache.set(cacheKey, font);\n return font;\n } catch (err) {\n console.warn(`[text-to-path] Failed to load Google font ${fontFamily}:`, err);\n return null;\n }\n}\n\n/** Get raw font bytes (loads via opentype path if not yet cached). */\nasync function getFontBytes(\n fontFamily: string,\n weight: number,\n fontBaseUrl: string,\n isItalic = false,\n): Promise<{ bytes: Uint8Array; cacheKey: string } | null> {\n const cacheKey = `${fontFamily}__${weight}__${isItalic ? 'i' : 'n'}`;\n if (!fontBytesCache.has(cacheKey)) {\n // Triggers fetch + populates fontBytesCache as a side-effect.\n await loadFont(fontFamily, weight, fontBaseUrl, isItalic);\n }\n const bytes = fontBytesCache.get(cacheKey);\n return bytes ? { bytes, cacheKey } : null;\n}\n\n/** Measure run advance width using opentype's own metrics (kerning enabled). */\nfunction measureRunWidth(font: opentype.Font, text: string, fontSize: number): number {\n try {\n return font.getAdvanceWidth(text, fontSize, { kerning: true });\n } catch {\n try { return font.getAdvanceWidth(text, fontSize); } catch { return text.length * fontSize * 0.5; }\n }\n}\n\n/**\n * Split a string into consecutive runs by script/fallback font,\n * preserving order. Whitespace stays with its preceding run.\n */\nfunction splitByFontRun(text: string): Array<{ text: string; runType: FontRunType }> {\n if (!text) return [];\n const runs: Array<{ text: string; runType: FontRunType }> = [];\n let buf = '';\n let bufType = classifyCharForFontRun(text[0]);\n for (const ch of text) {\n const chType = classifyCharForFontRun(ch);\n // Whitespace and punctuation stick to the current run to avoid breaking shaping.\n const isNeutral = /\\s/.test(ch);\n if (chType === bufType || isNeutral) {\n buf += ch;\n } else {\n if (buf) runs.push({ text: buf, runType: bufType });\n buf = ch;\n bufType = chType;\n }\n }\n if (buf) runs.push({ text: buf, runType: bufType });\n return runs;\n}\n\n/**\n * Generate an SVG path `d` string for a text run using the appropriate engine.\n * Returns { pathData, advance } or null on failure.\n */\nasync function shapeRunToPath(\n run: string,\n isDeva: boolean,\n primaryFont: opentype.Font,\n primaryBytes: { bytes: Uint8Array; cacheKey: string } | null,\n devaFont: opentype.Font | null,\n devaBytes: { bytes: Uint8Array; cacheKey: string } | null,\n x: number,\n y: number,\n fontSize: number,\n): Promise<{ pathData: string; advance: number } | null> {\n // Devanagari → HarfBuzz with the resolved Devanagari font (sibling of the chosen Latin font)\n if (isDeva && devaBytes && devaFont) {\n try {\n const shaped = await shapeRunToSvgPath(\n devaBytes.bytes,\n devaBytes.cacheKey,\n run,\n x,\n y,\n fontSize,\n { script: 'deva', language: 'hin', direction: 'ltr' },\n );\n if (shaped.pathData) {\n const advance = measureRunWidth(devaFont, run, fontSize);\n return { pathData: shaped.pathData, advance };\n }\n } catch (err) {\n console.warn(`[text-to-path] HarfBuzz failed for \"${run}\":`, err);\n }\n // Fall through to opentype.js fallback below using devaFont.\n try {\n const path = devaFont.getPath(run, x, y, fontSize);\n const d = path.toPathData(2);\n if (d && d !== 'M0 0') return { pathData: d, advance: measureRunWidth(devaFont, run, fontSize) };\n } catch { /* noop */ }\n return null;\n }\n\n // Latin / non-Indic → opentype.js getPath with the actual chosen font\n try {\n const path = primaryFont.getPath(run, x, y, fontSize, { kerning: true });\n const d = path.toPathData(2);\n if (d && d !== 'M0 0') return { pathData: d, advance: measureRunWidth(primaryFont, run, fontSize) };\n return { pathData: '', advance: measureRunWidth(primaryFont, run, fontSize) };\n } catch (err) {\n console.warn(`[text-to-path] opentype getPath failed for \"${run}\":`, err);\n return null;\n }\n}\n\n/** Parse a CSS-style transform attribute to extract translate values */\nfunction parseTranslate(transform: string | null): { tx: number; ty: number } {\n if (!transform) return { tx: 0, ty: 0 };\n const m = transform.match(/translate\\(\\s*([-\\d.]+)[ \\s,]+([-\\d.]+)\\s*\\)/);\n if (m) return { tx: parseFloat(m[1]), ty: parseFloat(m[2]) };\n // matrix(a,b,c,d,e,f) — e=tx, f=ty\n const mm = transform.match(/matrix\\(\\s*([-\\d.]+)[ \\s,]+([-\\d.]+)[ \\s,]+([-\\d.]+)[ \\s,]+([-\\d.]+)[ \\s,]+([-\\d.]+)[ \\s,]+([-\\d.]+)\\s*\\)/);\n if (mm) return { tx: parseFloat(mm[5]), ty: parseFloat(mm[6]) };\n return { tx: 0, ty: 0 };\n}\n\n/** Heuristic: does this text need shaping (complex script) we can't trust the PDF font embedder for? */\nfunction needsComplexShaping(text: string): boolean {\n if (!text) return false;\n for (const ch of text) {\n const c = ch.codePointAt(0) ?? 0;\n // Devanagari + extensions\n if ((c >= 0x0900 && c <= 0x097F) || (c >= 0xA8E0 && c <= 0xA8FF) || (c >= 0x1CD0 && c <= 0x1CFF)) return true;\n // Bengali, Gurmukhi, Gujarati, Oriya, Tamil, Telugu, Kannada, Malayalam, Sinhala\n if (c >= 0x0980 && c <= 0x0DFF) return true;\n // Thai, Lao, Tibetan, Myanmar, Khmer\n if (c >= 0x0E00 && c <= 0x0FFF) return true;\n if (c >= 0x1000 && c <= 0x109F) return true;\n if (c >= 0x1780 && c <= 0x17FF) return true;\n // Arabic, Hebrew (RTL + shaping)\n if (c >= 0x0590 && c <= 0x06FF) return true;\n if (c >= 0x0700 && c <= 0x074F) return true;\n // CJK\n if (c >= 0x3000 && c <= 0x9FFF) return true;\n if (c >= 0xAC00 && c <= 0xD7AF) return true;\n // Emoji / supplementary\n if (c >= 0x1F000) return true;\n // Math operators / arrows / misc technical — Latin fonts almost never\n // contain these glyphs, so jsPDF renders them as `.notdef` (backtick).\n // Treat as complex-shaping so they get outlined via the math fallback.\n if (c >= 0x2190 && c <= 0x21FF) return true; // Arrows\n if (c >= 0x2200 && c <= 0x22FF) return true; // Mathematical Operators\n if (c >= 0x2300 && c <= 0x23FF) return true; // Misc Technical\n if (c >= 0x27C0 && c <= 0x27EF) return true; // Misc Math Symbols-A\n if (c >= 0x2980 && c <= 0x29FF) return true; // Misc Math Symbols-B\n if (c >= 0x2A00 && c <= 0x2AFF) return true; // Supplemental Math Operators\n if (c >= 0x2B00 && c <= 0x2B59) return true;\n }\n return false;\n}\n\n/**\n * Decorative / script Latin fonts whose jsPDF text embedding (svg2pdf path)\n * is unreliable in production: either the local TTF isn't shipped under\n * `/fonts/` and the Google Fonts fallback is silently rejected, or the font\n * is single-weight with custom kerning that jsPDF does not honor. Symptom:\n * the PDF shows a generic Times/Helvetica fallback instead of the actual\n * decorative face the user sees on the canvas preview.\n *\n * In `auto` mode we outline these via opentype.js (which fetches the same\n * TTF the browser uses, with Google Fonts fallback) so the downloaded PDF\n * matches the canvas pixel-for-pixel. Body fonts (Inter, Roboto, Montserrat,\n * Open Sans, etc.) stay selectable.\n *\n * Match is case- and space-insensitive against the resolved CSS family.\n */\nconst DECORATIVE_OUTLINE_FAMILIES = new Set<string>([\n 'greatvibes', 'great vibes',\n 'cinzel', 'cinzeldecorative', 'cinzel decorative',\n 'pacifico',\n 'allura',\n 'alexbrush', 'alex brush',\n 'abrilfatface', 'abril fatface',\n 'cormorant', 'cormorantgaramond', 'cormorant garamond',\n 'dancingscript', 'dancing script',\n 'sacramento',\n 'lobster',\n 'satisfy',\n 'kaushanscript', 'kaushan script',\n 'parisienne',\n 'tangerine',\n 'yellowtail',\n 'shadowsintolight', 'shadows into light',\n 'amaticsc', 'amatic sc',\n 'caveat',\n 'playball',\n 'marckscript', 'marck script',\n 'courgette',\n]);\n\nfunction isDecorativeFamily(family: string | null | undefined): boolean {\n if (!family) return false;\n const k = family.replace(/['\"]/g, '').replace(/\\s+/g, ' ').trim().toLowerCase();\n if (DECORATIVE_OUTLINE_FAMILIES.has(k)) return true;\n // Also match the no-space variant (e.g. \"GreatVibes\" CSS keyword).\n return DECORATIVE_OUTLINE_FAMILIES.has(k.replace(/\\s+/g, ''));\n}\n\n/**\n * Read the resolved font-family of a `<text>` element, walking up the\n * inheritance chain across both attribute and style declarations.\n */\nfunction readResolvedFontFamily(textEl: Element): string | null {\n const readStyleToken = (style: string, prop: string): string | null => {\n const m = style.match(new RegExp(`${prop}\\\\s*:\\\\s*([^;]+)`, 'i'));\n return m?.[1]?.trim() || null;\n };\n let current: Element | null = textEl;\n while (current) {\n const a = current.getAttribute('font-family')?.trim();\n if (a) return a.split(',')[0]?.replace(/['\"]/g, '').trim() || null;\n const s = readStyleToken(current.getAttribute('style') || '', 'font-family');\n if (s) return s.split(',')[0]?.replace(/['\"]/g, '').trim() || null;\n current = current.parentElement;\n }\n return null;\n}\n\nexport interface OutlineTextOptions {\n /**\n * - 'all' (default): outline every <text> for pixel-perfect parity.\n * - 'complex-only': only outline text that contains complex scripts (Indic, Arabic, CJK, emoji);\n * leave Latin text as <text> so it stays selectable in the PDF.\n * - 'mixed-style-only': only outline <text> elements whose tspans override\n * font-weight / font-style / fill (i.e. inline markdown formatting like\n * **bold**, *italic*, [c=primary]color[/c]). Plain text stays selectable\n * while formatted runs render as paths so bold/italic/colored spans don't\n * silently lose their styling because jsPDF only embedded one font weight.\n */\n /**\n * Additional mode (v0.5.130 PARITY):\n * - 'shadow-bound' → outline only <text> nodes whose nearest Fabric\n * textbox <g> ancestor carries `data-pd-shadow-blur` (set by\n * textBackgroundRenderer when a non-zero-blur shadow is emitted) OR\n * <text> nodes that live inside a `g.__pdShadowRaster` marker. Visible\n * text and shadow silhouette text then share IDENTICAL glyph outlines,\n * so the rasterized shadow PNG aligns pixel-perfect with the\n * svg2pdf-drawn vector text — fixing the small horizontal drift that\n * appeared on centered text with shadow > 0 in both client and server\n * PDF exports.\n */\n mode?: 'all' | 'complex-only' | 'mixed-style-only' | 'shadow-bound' | 'gradient-only';\n}\n\n/**\n * True if a <text> element (or any of its <tspan> descendants) carries a\n * gradient fill (`fill=\"url(#…)\"` or `style=\"fill:url(#…)\"`). svg2pdf.js\n * cannot honor gradient fills on live <text> — it falls back to a single\n * solid color via jsPDF's text() API — so callers use this to detect text\n * that must either be outlined to <path> (where gradients render correctly)\n * or have its fill resolved to a solid color before PDF generation.\n */\nfunction textHasGradientFill(textEl: Element): boolean {\n const isGradRef = (val: string | null | undefined): boolean =>\n !!val && /url\\s*\\(\\s*['\"]?#/i.test(val);\n const readFill = (el: Element): string => {\n const attr = el.getAttribute('fill') || '';\n if (attr) return attr;\n const style = el.getAttribute('style') || '';\n const m = style.match(/(?:^|;)\\s*fill\\s*:\\s*([^;]+)/i);\n return m ? m[1].trim() : '';\n };\n if (isGradRef(readFill(textEl))) return true;\n const tspans = textEl.querySelectorAll('tspan');\n for (const ts of tspans) {\n if (isGradRef(readFill(ts))) return true;\n }\n return false;\n}\n\n/**\n * For PDF pipelines that keep text live/selectable (no outlining), gradient\n * fills on <text> would otherwise render as solid black via svg2pdf's text\n * path. This helper rewrites every gradient-ref fill on <text>/<tspan> to\n * the first colour stop of the referenced gradient, so the PDF at least\n * shows the dominant gradient colour instead of black.\n *\n * Selectability is preserved. Visual parity with the canvas gradient is\n * lost — that's the documented trade-off of \"Selectable\" mode.\n */\nexport function replaceGradientTextFillsWithFirstStop(svgStr: string): string {\n if (!svgStr || !/url\\s*\\(\\s*['\"]?#/i.test(svgStr)) return svgStr;\n let doc: Document;\n try {\n doc = new DOMParser().parseFromString(svgStr, 'image/svg+xml');\n } catch {\n return svgStr;\n }\n // Build id → first-stop colour map from gradient defs.\n const stopColorFor = (gradEl: Element): string | null => {\n const stop = gradEl.querySelector('stop');\n if (!stop) return null;\n const attr = stop.getAttribute('stop-color') || '';\n if (attr) return attr;\n const style = stop.getAttribute('style') || '';\n const m = style.match(/(?:^|;)\\s*stop-color\\s*:\\s*([^;]+)/i);\n return m ? m[1].trim() : null;\n };\n const gradMap = new Map<string, string>();\n doc.querySelectorAll('linearGradient, radialGradient').forEach((g) => {\n const id = g.getAttribute('id');\n if (!id) return;\n // Follow xlink:href chains to a gradient that actually has stops.\n let cur: Element | null = g;\n let hops = 0;\n while (cur && cur.querySelectorAll('stop').length === 0 && hops < 4) {\n const href = cur.getAttribute('xlink:href') || cur.getAttribute('href');\n if (!href || !href.startsWith('#')) break;\n const next = doc.getElementById(href.slice(1));\n if (!next) break;\n cur = next;\n hops++;\n }\n const color = cur ? stopColorFor(cur) : null;\n if (color) gradMap.set(id, color);\n });\n if (gradMap.size === 0) return svgStr;\n\n const refRe = /url\\s*\\(\\s*['\"]?#([^)'\"]+)['\"]?\\s*\\)/i;\n const resolveFillAttr = (el: Element) => {\n const attr = el.getAttribute('fill');\n if (attr) {\n const m = attr.match(refRe);\n if (m) {\n const color = gradMap.get(m[1]);\n if (color) el.setAttribute('fill', color);\n }\n }\n const style = el.getAttribute('style');\n if (style && refRe.test(style)) {\n const newStyle = style.replace(/(^|;)\\s*fill\\s*:\\s*url\\s*\\([^)]+\\)/i, (whole, sep) => {\n const m = whole.match(refRe);\n const color = m ? gradMap.get(m[1]) : null;\n return color ? `${sep}fill:${color}` : whole;\n });\n if (newStyle !== style) el.setAttribute('style', newStyle);\n }\n };\n doc.querySelectorAll('text, tspan').forEach(resolveFillAttr);\n return new XMLSerializer().serializeToString(doc.documentElement);\n}\n\nexport type PdfTextMode = 'auto' | 'selectable' | 'pixel-perfect';\n\n/**\n * Single shared PDF text-prep contract for host exports and the published\n * canvas-renderer package:\n * - pixel-perfect: outline every text node, gradient or not\n * - auto: outline only gradient-filled text so gradients render as vectors\n * - selectable: keep live text and replace gradient fills with first stop\n */\nexport async function prepareSvgTextForPdfMode(\n svgStr: string,\n textMode: PdfTextMode = 'selectable',\n fontBaseUrl?: string,\n): Promise<string> {\n if (textMode === 'pixel-perfect') {\n return convertDevanagariTextToPath(svgStr, fontBaseUrl, { mode: 'all' });\n }\n\n if (textMode === 'auto') {\n return convertDevanagariTextToPath(svgStr, fontBaseUrl, { mode: 'gradient-only' });\n }\n\n return replaceGradientTextFillsWithFirstStop(svgStr);\n}\n\n/**\n * Convert EVERY <text> element in the SVG to <path> elements using the actual\n * font (Latin → opentype, Devanagari → HarfBuzz). Guarantees 100% font parity\n * between the canvas preview and the downloaded PDF.\n *\n * Name kept as `convertDevanagariTextToPath` for backwards compatibility with\n * existing callers (UsePackage.tsx). The behaviour is now universal — name is\n * a misnomer.\n *\n * @param svgStr - The SVG string from Fabric's toSVG()\n * @param fontBaseUrl - Base URL where TTF font files are served (e.g., '/fonts/')\n * @param options - Outline mode controls (default: outline all)\n * @returns Modified SVG string with text converted to paths\n */\nexport async function convertDevanagariTextToPath(\n svgStr: string,\n fontBaseUrl?: string,\n options: OutlineTextOptions = {},\n): Promise<string> {\n const baseUrl = fontBaseUrl ?? (typeof window !== 'undefined' ? window.location.origin + '/fonts/' : '/fonts/');\n const mode = options.mode ?? 'all';\n\n const parser = new DOMParser();\n const doc = parser.parseFromString(svgStr, 'image/svg+xml');\n\n // Find all top-level <text> elements (not nested tspans)\n const textEls = doc.querySelectorAll('text');\n let convertedCount = 0;\n let skippedCount = 0;\n\n for (const textEl of textEls) {\n // Universal mode: process every <text>, not just Devanagari ones.\n const fullText = textEl.textContent || '';\n if (!fullText.trim()) continue;\n\n // 'shadow-bound' mode: outline only text bound to a PDF-sensitive decoration.\n // We accept text that lives directly inside a __pdShadowRaster marker\n // (the silhouette layer) AND text whose nearest Fabric textbox <g> ancestor\n // was tagged for blurred shadow or text background (the visible layer).\n if (mode === 'shadow-bound') {\n const inShadowMarker =\n !!textEl.closest && !!(textEl as any).closest('g.__pdShadowRaster');\n let inTaggedTextbox = false;\n let cur: Element | null = textEl.parentElement;\n while (cur) {\n if (cur.tagName.toLowerCase() === 'g' && (cur.hasAttribute('data-pd-shadow-blur') || cur.hasAttribute('data-pd-text-bg'))) {\n inTaggedTextbox = true;\n break;\n }\n cur = cur.parentElement;\n }\n if (!inShadowMarker && !inTaggedTextbox) {\n skippedCount++;\n continue;\n }\n }\n\n // Helper: does any tspan override formatting (markdown bold/italic/color/decoration)?\n const hasMixedStyleTspans = (): boolean => {\n const tspansAll = textEl.querySelectorAll('tspan');\n const readProp = (el: Element, prop: string): string => {\n const attr = (el.getAttribute(prop) || '').trim();\n if (attr) return attr;\n const style = el.getAttribute('style') || '';\n const m = style.match(new RegExp(`(?:^|;)\\\\s*${prop}\\\\s*:\\\\s*([^;]+)`, 'i'));\n return m ? m[1].trim() : '';\n };\n const baseWeight = readProp(textEl, 'font-weight');\n const baseStyle = readProp(textEl, 'font-style');\n const baseFill = readProp(textEl, 'fill');\n const baseDeco = readProp(textEl, 'text-decoration');\n for (const ts of tspansAll) {\n const tw = readProp(ts, 'font-weight');\n const tst = readProp(ts, 'font-style');\n const tFill = readProp(ts, 'fill');\n const tDeco = readProp(ts, 'text-decoration');\n const tBg = (ts.getAttribute('style') || '').match(/background|text-background/i);\n if (\n (tw && tw !== baseWeight) ||\n (tst && tst !== baseStyle) ||\n (tFill && tFill !== baseFill) ||\n (tDeco && tDeco !== baseDeco) ||\n tBg\n ) {\n return true;\n }\n }\n return false;\n };\n\n // 'gradient-only' mode: leave every <text> selectable EXCEPT those with a\n // gradient fill (`url(#…)`). svg2pdf cannot render gradient fills on live\n // text — outlining the run to <path> preserves the gradient. Used by the\n // \"Auto\" PDF text mode so only gradient text loses selectability.\n if (mode === 'gradient-only') {\n if (!textHasGradientFill(textEl)) {\n skippedCount++;\n continue;\n }\n }\n\n // 'complex-only' mode: leave plain Latin text as real selectable text,\n // BUT still outline (a) anything with markdown formatting (mixed-style\n // tspans) since svg2pdf only embeds one font weight per element, and\n // (b) text rendered in a known decorative / script Latin family whose\n // jsPDF embedding is unreliable (GreatVibes, Cinzel, Pacifico, etc.).\n // Without (b) the downloaded PDF silently falls back to Times/Helvetica\n // for those fonts even though the canvas preview shows the real face.\n if (mode === 'complex-only') {\n const resolvedFamily = readResolvedFontFamily(textEl);\n const decorative = isDecorativeFamily(resolvedFamily);\n // (c) text whose fill is a gradient reference (`url(#…)`). svg2pdf.js\n // only feeds a single solid RGB to jsPDF's text() API, so a\n // gradient fill on live <text> silently degrades to the first\n // stop / black. Outlining the run into <path> keeps the gradient\n // because svg2pdf renders gradient fills correctly on paths.\n const hasGradientFill = textHasGradientFill(textEl);\n if (\n !needsComplexShaping(fullText) &&\n !hasMixedStyleTspans() &&\n !decorative &&\n !hasGradientFill\n ) {\n skippedCount++;\n continue;\n }\n }\n\n // 'mixed-style-only' mode: only outline <text> elements that have any\n // tspan with formatting overrides relative to the parent <text> (different\n // font-weight, font-style, fill, or text-decoration). These come from\n // inline markdown — bold/italic/highlight/color runs that svg2pdf can't\n // render correctly because jsPDF only has the element's base font embedded.\n if (mode === 'mixed-style-only' && !hasMixedStyleTspans()) {\n skippedCount++;\n continue;\n }\n\n // Resolve font properties\n const readStyleToken = (style: string, prop: string): string | null => {\n const m = style.match(new RegExp(`${prop}\\\\s*:\\\\s*([^;]+)`, 'i'));\n return m?.[1]?.trim() || null;\n };\n\n const getAttrOrStyle = (el: Element, attr: string): string | null => {\n let current: Element | null = el;\n while (current) {\n const attrVal = current.getAttribute(attr)?.trim();\n if (attrVal) return attrVal;\n const styleVal = readStyleToken(current.getAttribute('style') || '', attr);\n if (styleVal) return styleVal;\n current = current.parentElement;\n }\n return null;\n };\n\n const fontFamily = getAttrOrStyle(textEl, 'font-family')\n ?.split(',')[0]\n ?.replace(/['\"]/g, '')\n .trim();\n\n const fontSizeStr = getAttrOrStyle(textEl, 'font-size') || '16';\n const fontSize = parseFloat(fontSizeStr);\n\n const fontWeightStr = getAttrOrStyle(textEl, 'font-weight') || '400';\n const fontWeight = Number.parseInt(fontWeightStr, 10) || 400;\n\n const fillColor = getAttrOrStyle(textEl, 'fill') || '#000000';\n const fillOpacity = getAttrOrStyle(textEl, 'fill-opacity') || '1';\n\n if (!fontFamily) {\n skippedCount++;\n continue;\n }\n\n // ── Load the PRIMARY font (the exact font shown in the preview) ──\n const primaryFont = await loadFont(fontFamily, fontWeight, baseUrl);\n const primaryBytes = await getFontBytes(fontFamily, fontWeight, baseUrl);\n\n // ── Devanagari font resolution ──\n // The browser canvas does NOT use curated siblings when a Latin font lacks\n // Hindi glyphs. It uses the CSS/font fallback stack; in our preview path\n // that fallback is Hind. For parity, pick the candidate whose shaped width\n // matches browser canvas measurement, with the chosen family itself first\n // for fonts like Poppins that genuinely include Devanagari glyphs.\n const hasDeva = containsDevanagari(fullText);\n const hasSymbol = [...fullText].some((char) => classifyCharForFontRun(char) === 'symbol');\n const hasMath = [...fullText].some((char) => classifyCharForFontRun(char) === 'math');\n const devaCandidateFamilies = hasDeva\n ? uniqueFamilies([fontFamily, FONT_FALLBACK_DEVANAGARI, resolveDevanagariSibling(fontFamily)])\n : [];\n type ResolvedRunFont = { family: string; font: opentype.Font; bytes: { bytes: Uint8Array; cacheKey: string } };\n const devaRunFontCache = new Map<string, Promise<ResolvedRunFont | null>>();\n const resolveDevaFontForRun = (runText: string, runFontSize: number): Promise<ResolvedRunFont | null> => {\n const cacheKey = `${runText}__${runFontSize}`;\n const cached = devaRunFontCache.get(cacheKey);\n if (cached) return cached;\n\n const promise = (async () => {\n const browserWidth = measureBrowserWidth(fontFamily, fontWeight, runFontSize, runText);\n let best: (ResolvedRunFont & { diff: number }) | null = null;\n\n for (const family of devaCandidateFamilies) {\n const font = family === fontFamily ? primaryFont : await loadFont(family, fontWeight, baseUrl);\n const bytes = family === fontFamily ? primaryBytes : await getFontBytes(family, fontWeight, baseUrl);\n if (!font || !bytes || !fontSupportsRun(font, runText, 'devanagari')) continue;\n\n const width = measureRunWidth(font, runText, runFontSize);\n const diff = browserWidth == null ? 0 : Math.abs(width - browserWidth);\n if (!best || diff < best.diff) best = { family, font, bytes, diff };\n\n // Exact/near-exact match to canvas measurement — no need to keep trying.\n if (browserWidth != null && diff <= 0.25) break;\n }\n\n if (best) {\n console.log('[text-to-path] Devanagari font resolved', {\n requestedFamily: fontFamily,\n resolvedFamily: best.family,\n browserWidth,\n diff: Math.round(best.diff * 100) / 100,\n });\n }\n return best;\n })();\n\n devaRunFontCache.set(cacheKey, promise);\n return promise;\n };\n\n const symbolRunFontPromise: Promise<ResolvedRunFont | null> | null = hasSymbol\n ? (async () => {\n const symbolFont = await loadFont(FONT_FALLBACK_SYMBOLS, 400, baseUrl);\n const symbolBytes = await getFontBytes(FONT_FALLBACK_SYMBOLS, 400, baseUrl);\n return symbolFont && symbolBytes ? { family: FONT_FALLBACK_SYMBOLS, font: symbolFont, bytes: symbolBytes } : null;\n })()\n : null;\n const mathRunFontPromise: Promise<ResolvedRunFont | null> | null = hasMath\n ? (async () => {\n const mathFont = await loadFont(FONT_FALLBACK_MATH, 400, baseUrl);\n const mathBytes = await getFontBytes(FONT_FALLBACK_MATH, 400, baseUrl);\n return mathFont && mathBytes ? { family: FONT_FALLBACK_MATH, font: mathFont, bytes: mathBytes } : null;\n })()\n : null;\n\n // If we have NEITHER a primary font nor (for Devanagari) a fallback font,\n // we can't outline this element — leave the original <text> in place so\n // the user still sees something rather than nothing.\n if (!primaryFont && !hasDeva && !hasSymbol && !hasMath) {\n console.warn(`[text-to-path] No font available for \"${fontFamily}\", leaving as <text>`);\n skippedCount++;\n continue;\n }\n\n // Resolve text-anchor (Fabric uses 'middle' for centered text). We measure the\n // run width and apply a leftward shift so the anchor point matches.\n const textAnchorRaw = (getAttrOrStyle(textEl, 'text-anchor') || 'start').toLowerCase();\n const baseTextAnchor: 'start' | 'middle' | 'end' =\n textAnchorRaw === 'middle' ? 'middle' : textAnchorRaw === 'end' ? 'end' : 'start';\n\n // Process tspan children or direct text\n const tspans = textEl.querySelectorAll('tspan');\n const elementsToProcess = tspans.length > 0 ? Array.from(tspans) : [textEl];\n\n // Create a <g> group to replace the <text> element\n const group = doc.createElementNS('http://www.w3.org/2000/svg', 'g');\n\n // Copy transform from <text> to <g>\n const textTransform = textEl.getAttribute('transform');\n if (textTransform) group.setAttribute('transform', textTransform);\n\n // Get base x/y from the <text> element\n const baseX = parseFloat(textEl.getAttribute('x') || '0');\n const baseY = parseFloat(textEl.getAttribute('y') || '0');\n\n for (const elem of elementsToProcess) {\n const text = elem.textContent || '';\n if (!text.trim()) continue;\n\n // Get element-specific position\n const elemX = parseFloat(elem.getAttribute('x') || String(baseX));\n const elemY = parseFloat(elem.getAttribute('y') || String(baseY));\n const elemAnchorRaw = (getAttrOrStyle(elem, 'text-anchor') || baseTextAnchor).toLowerCase();\n const elemTextAnchor: 'start' | 'middle' | 'end' =\n elemAnchorRaw === 'middle' ? 'middle' : elemAnchorRaw === 'end' ? 'end' : 'start';\n\n // Get element-specific transform\n const elemTransform = elem !== textEl ? parseTranslate(elem.getAttribute('transform')) : { tx: 0, ty: 0 };\n let x = elemX + elemTransform.tx;\n const y = elemY + elemTransform.ty;\n\n // Get element-specific fill\n const elemFill = getAttrOrStyle(elem, 'fill') || fillColor;\n const elemFillOpacity = getAttrOrStyle(elem, 'fill-opacity') || fillOpacity;\n\n // Get element-specific font size\n const elemFontSizeStr = getAttrOrStyle(elem, 'font-size') || String(fontSize);\n const elemFontSize = parseFloat(elemFontSizeStr);\n\n // ── Per-element font weight / style (markdown bold/italic support) ──\n // Each tspan may override font-weight (e.g. **bold**) and font-style\n // (e.g. *italic*). Resolve per-element so we load the matching font\n // variant for outlining instead of always using the parent font.\n const elemWeightStr = getAttrOrStyle(elem, 'font-weight') || String(fontWeight);\n const elemWeight = Number.parseInt(elemWeightStr, 10) || (/bold/i.test(elemWeightStr) ? 700 : fontWeight);\n const elemStyleRaw = (getAttrOrStyle(elem, 'font-style') || 'normal').toLowerCase();\n const elemItalic = /italic|oblique/i.test(elemStyleRaw);\n const sameAsPrimary = elemWeight === fontWeight && !elemItalic;\n const elemFont = sameAsPrimary\n ? primaryFont\n : await loadFont(fontFamily, elemWeight, baseUrl, elemItalic);\n const elemBytes = sameAsPrimary\n ? primaryBytes\n : await getFontBytes(fontFamily, elemWeight, baseUrl, elemItalic);\n // If the variant didn't load, fall back to the primary font so we still\n // render something — better than dropping the tspan entirely.\n const fontForElem = elemFont || primaryFont;\n const bytesForElem = elemBytes || primaryBytes;\n\n // Split into font runs so each run uses the appropriate font/shaper.\n const runs = splitByFontRun(text);\n\n // First pass — measure total advance for text-anchor alignment.\n // We can only compute this if every run has a font; otherwise skip and\n // assume start-anchor (Fabric defaults to start for left-aligned text).\n let totalAdvance = 0;\n let canMeasureAll = true;\n for (const r of runs) {\n if (r.runType === 'devanagari') {\n const resolved = await resolveDevaFontForRun(r.text, elemFontSize);\n if (resolved) totalAdvance += measureRunWidth(resolved.font, r.text, elemFontSize);\n else canMeasureAll = false;\n } else if (r.runType === 'symbol') {\n const resolved = await symbolRunFontPromise;\n if (resolved && fontSupportsRun(resolved.font, r.text, 'symbol')) totalAdvance += measureRunWidth(resolved.font, r.text, elemFontSize);\n else canMeasureAll = false;\n } else if (r.runType === 'math') {\n const resolved = await mathRunFontPromise;\n if (resolved && fontSupportsRun(resolved.font, r.text, 'math')) totalAdvance += measureRunWidth(resolved.font, r.text, elemFontSize);\n else canMeasureAll = false;\n } else {\n if (fontForElem) totalAdvance += measureRunWidth(fontForElem, r.text, elemFontSize);\n else canMeasureAll = false;\n }\n }\n\n if (canMeasureAll) {\n if (elemTextAnchor === 'middle') x -= totalAdvance / 2;\n else if (elemTextAnchor === 'end') x -= totalAdvance;\n }\n\n // Second pass — emit a <path> per run.\n let cursor = x;\n let elemConverted = false;\n for (const r of runs) {\n // Pick the font that matches the browser-rendered canvas for this run.\n const resolvedDeva = r.runType === 'devanagari' ? await resolveDevaFontForRun(r.text, elemFontSize) : null;\n const resolvedSymbol = r.runType === 'symbol' ? await symbolRunFontPromise : null;\n const resolvedMath = r.runType === 'math' ? await mathRunFontPromise : null;\n const useDeva = !!resolvedDeva;\n const fontForRun = resolvedDeva?.font ?? resolvedSymbol?.font ?? resolvedMath?.font ?? fontForElem;\n const bytesForRun = resolvedDeva?.bytes ?? resolvedSymbol?.bytes ?? resolvedMath?.bytes ?? bytesForElem;\n if (!fontForRun) continue;\n\n const result = await shapeRunToPath(\n r.text,\n !!useDeva,\n fontForRun,\n bytesForRun,\n resolvedDeva?.font ?? null,\n resolvedDeva?.bytes ?? null,\n cursor,\n y,\n elemFontSize,\n );\n\n if (result && result.pathData) {\n const pathEl = doc.createElementNS('http://www.w3.org/2000/svg', 'path');\n pathEl.setAttribute('d', result.pathData);\n pathEl.setAttribute('fill', elemFill);\n if (elemItalic && !fontLooksItalic(fontForRun)) {\n // Families like Excon have no italic file; the browser preview uses\n // synthetic oblique, so skew the outlined glyph paths to match it.\n pathEl.setAttribute('transform', syntheticItalicTransform(cursor, y));\n }\n if (elemFillOpacity !== '1') {\n pathEl.setAttribute('fill-opacity', elemFillOpacity);\n }\n group.appendChild(pathEl);\n elemConverted = true;\n }\n if (result) cursor += result.advance;\n }\n\n if (elemConverted) {\n convertedCount++;\n } else {\n // Couldn't outline — keep original element so something renders.\n if (elem === textEl) {\n const clone = elem.cloneNode(true) as Element;\n if (textTransform) clone.removeAttribute('transform');\n group.appendChild(clone);\n } else {\n const newText = doc.createElementNS('http://www.w3.org/2000/svg', 'text');\n newText.setAttribute('x', String(elemX));\n newText.setAttribute('y', String(elemY));\n if (elem.getAttribute('style')) newText.setAttribute('style', elem.getAttribute('style')!);\n if (elem.getAttribute('font-family')) newText.setAttribute('font-family', elem.getAttribute('font-family')!);\n if (elem.getAttribute('font-size')) newText.setAttribute('font-size', elem.getAttribute('font-size')!);\n if (elem.getAttribute('font-weight')) newText.setAttribute('font-weight', elem.getAttribute('font-weight')!);\n newText.textContent = text;\n group.appendChild(newText);\n }\n }\n }\n\n // Replace <text> with <g> containing paths\n if (group.childNodes.length > 0) {\n textEl.parentNode?.replaceChild(group, textEl);\n }\n }\n\n console.log(\n `[text-to-path] Universal outline complete: converted=${convertedCount} skipped=${skippedCount}`,\n );\n\n return new XMLSerializer().serializeToString(doc.documentElement);\n}\n\n/**\n * Alias for `convertDevanagariTextToPath` with a clearer name. Both call the\n * same universal outliner — the older name is kept for backwards compatibility.\n */\nexport const convertAllTextToPath = convertDevanagariTextToPath;\n\n/**\n * Pre-warm the font cache for Devanagari fonts.\n * Call this early to avoid latency during PDF export.\n */\nexport async function preloadDevanagariFont(fontBaseUrl?: string): Promise<void> {\n const baseUrl = fontBaseUrl ?? (typeof window !== 'undefined' ? window.location.origin + '/fonts/' : '/fonts/');\n for (const weight of [300, 400, 500, 600, 700]) {\n await loadFont(FONT_FALLBACK_DEVANAGARI, weight, baseUrl);\n }\n}\n"],"names":["_a"],"mappings":"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"}
|
|
@@ -483,7 +483,7 @@ function textHasGradientFill(textEl) {
|
|
|
483
483
|
return false;
|
|
484
484
|
}
|
|
485
485
|
function replaceGradientTextFillsWithFirstStop(svgStr) {
|
|
486
|
-
if (!svgStr ||
|
|
486
|
+
if (!svgStr || !/url\s*\(\s*['"]?#/i.test(svgStr)) return svgStr;
|
|
487
487
|
let doc;
|
|
488
488
|
try {
|
|
489
489
|
doc = new DOMParser().parseFromString(svgStr, "image/svg+xml");
|
|
@@ -540,6 +540,15 @@ function replaceGradientTextFillsWithFirstStop(svgStr) {
|
|
|
540
540
|
doc.querySelectorAll("text, tspan").forEach(resolveFillAttr);
|
|
541
541
|
return new XMLSerializer().serializeToString(doc.documentElement);
|
|
542
542
|
}
|
|
543
|
+
async function prepareSvgTextForPdfMode(svgStr, textMode = "selectable", fontBaseUrl) {
|
|
544
|
+
if (textMode === "pixel-perfect") {
|
|
545
|
+
return convertDevanagariTextToPath(svgStr, fontBaseUrl, { mode: "all" });
|
|
546
|
+
}
|
|
547
|
+
if (textMode === "auto") {
|
|
548
|
+
return convertDevanagariTextToPath(svgStr, fontBaseUrl, { mode: "gradient-only" });
|
|
549
|
+
}
|
|
550
|
+
return replaceGradientTextFillsWithFirstStop(svgStr);
|
|
551
|
+
}
|
|
543
552
|
async function convertDevanagariTextToPath(svgStr, fontBaseUrl, options = {}) {
|
|
544
553
|
var _a, _b, _c;
|
|
545
554
|
const baseUrl = fontBaseUrl ?? (typeof window !== "undefined" ? window.location.origin + "/fonts/" : "/fonts/");
|
|
@@ -823,5 +832,6 @@ async function preloadDevanagariFont(fontBaseUrl) {
|
|
|
823
832
|
exports.convertAllTextToPath = convertAllTextToPath;
|
|
824
833
|
exports.convertDevanagariTextToPath = convertDevanagariTextToPath;
|
|
825
834
|
exports.preloadDevanagariFont = preloadDevanagariFont;
|
|
835
|
+
exports.prepareSvgTextForPdfMode = prepareSvgTextForPdfMode;
|
|
826
836
|
exports.replaceGradientTextFillsWithFirstStop = replaceGradientTextFillsWithFirstStop;
|
|
827
|
-
//# sourceMappingURL=svgTextToPath-
|
|
837
|
+
//# sourceMappingURL=svgTextToPath-hYM9qTeC.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svgTextToPath-hYM9qTeC.cjs","sources":["../../../src/lib/harfbuzzShaper.ts","../../../src/lib/svgTextToPath.ts"],"sourcesContent":["/**\n * HarfBuzz Shaper — Browser-side complex-script text shaping for vector PDF export.\n *\n * `opentype.js`'s `getPath()` only does naive codepoint→glyph mapping with very\n * limited GSUB support. That breaks complex scripts like Devanagari (Hindi /\n * Marathi / Sanskrit) where glyphs require:\n * - Conjuncts (क्ष, ज्ञ, त्र — multi-consonant ligatures)\n * - Reph (र above following consonant)\n * - Half-forms before virama\n * - Matra reordering (कि — इ matra rendered before क but typed after)\n * - Indic-specific GSUB lookups (nukt, akhn, rphf, blwf, half, pstf, vatu,\n * cjct, pres, abvs, blws, psts, haln, rkrf)\n *\n * The browser canvas preview works because the browser's text renderer uses\n * HarfBuzz internally. Our EC2 PDF works because Puppeteer also uses HarfBuzz.\n * The client-side vector PDF was the only path missing a real shaper.\n *\n * This module wraps `harfbuzzjs` (HarfBuzz compiled to WASM, ~400KB) and\n * returns a single shaped SVG path string per text run, ready to drop into\n * the SVG that we hand to svg2pdf.js.\n *\n * Lazy-loaded — the WASM is only fetched the first time a Devanagari (or other\n * complex-script) string is encountered during PDF export.\n */\n\n// hb.wasm is served from /public/wasm/hb.wasm\nconst HB_WASM_URL = '/wasm/hb.wasm';\n\ntype HBFont = {\n ptr: number;\n setScale: (x: number, y: number) => void;\n glyphToPath: (glyphId: number) => string;\n destroy: () => void;\n};\n\ntype HBBuffer = {\n addText: (text: string) => void;\n guessSegmentProperties: () => void;\n setDirection: (dir: 'ltr' | 'rtl' | 'ttb' | 'btt') => void;\n setScript: (script: string) => void;\n setLanguage: (lang: string) => void;\n json: () => Array<{\n g: number;\n cl: number;\n ax: number;\n ay: number;\n dx: number;\n dy: number;\n flags?: number;\n }>;\n destroy: () => void;\n};\n\ntype HBInstance = {\n createBlob: (data: Uint8Array | ArrayBuffer) => { destroy: () => void };\n createFace: (blob: any, index: number) => { upem?: number; destroy: () => void; getUpem?: () => number };\n createFont: (face: any) => HBFont;\n createBuffer: () => HBBuffer;\n shape: (font: HBFont, buffer: HBBuffer, features?: string) => void;\n};\n\nlet hbInstancePromise: Promise<HBInstance> | null = null;\n\n/** Lazy-load and initialize HarfBuzz WASM. Idempotent. */\nasync function getHB(): Promise<HBInstance> {\n if (hbInstancePromise) return hbInstancePromise;\n\n hbInstancePromise = (async () => {\n // Dynamically import to keep HarfBuzz out of the main bundle.\n // hb.js is an Emscripten module that exports a `createHarfBuzz` factory.\n // It looks up the .wasm file via Module.locateFile.\n const [{ default: createHarfBuzz }, { default: hbjs }] = await Promise.all([\n import('harfbuzzjs/hb.js'),\n import('harfbuzzjs/hbjs.js'),\n ]);\n\n const moduleInstance = await createHarfBuzz({\n locateFile: (path: string) => {\n if (path.endsWith('.wasm')) return HB_WASM_URL;\n return path;\n },\n });\n\n return hbjs(moduleInstance) as HBInstance;\n })();\n\n return hbInstancePromise;\n}\n\n/** Per-font cache: parsed face/font ready for repeated shaping calls. */\nconst hbFontCache = new Map<string, { face: any; font: HBFont; upem: number }>();\n\n/**\n * Register (or fetch from cache) a font with HarfBuzz.\n * `cacheKey` should uniquely identify the font binary (e.g. `family|weight`).\n * `fontDataLoader` is only invoked on cache miss.\n */\nasync function getHBFont(\n cacheKey: string,\n fontDataLoader: () => Promise<Uint8Array>,\n): Promise<{ font: HBFont; upem: number }> {\n const cached = hbFontCache.get(cacheKey);\n if (cached) return { font: cached.font, upem: cached.upem };\n\n const hb = await getHB();\n const fontData = await fontDataLoader();\n const blob = hb.createBlob(fontData);\n const face = hb.createFace(blob, 0);\n const font = hb.createFont(face);\n\n // Use the font's native upem so glyphToPath coordinates are in font units.\n // `setScale(upem, upem)` keeps the natural EM-square coordinates.\n const upem = (face.getUpem ? face.getUpem() : (face.upem ?? 1000)) || 1000;\n font.setScale(upem, upem);\n\n // Blob can be destroyed; face/font hold their own references.\n blob.destroy();\n\n hbFontCache.set(cacheKey, { face, font, upem });\n return { font, upem };\n}\n\nexport interface ShapedRun {\n /** SVG path data ready to drop into a <path d=\"...\"> element. */\n pathData: string;\n /** Total advance width of the run, in user-space units (matching `fontSize`). */\n width: number;\n}\n\n/**\n * Shape `text` with `fontData` at `fontSize` and return a single SVG path\n * positioned starting at (`x`, `y`) on the SVG baseline.\n *\n * `fontSize` is the rendered point size (matches CSS `font-size`).\n * `cacheKey` must uniquely identify `fontData` so subsequent calls hit cache.\n */\nexport async function shapeRunToSvgPath(\n fontData: Uint8Array | (() => Promise<Uint8Array>),\n cacheKey: string,\n text: string,\n x: number,\n y: number,\n fontSize: number,\n opts?: { script?: string; language?: string; direction?: 'ltr' | 'rtl' },\n): Promise<ShapedRun> {\n const hb = await getHB();\n const loader = typeof fontData === 'function'\n ? (fontData as () => Promise<Uint8Array>)\n : async () => fontData as Uint8Array;\n\n const { font, upem } = await getHBFont(cacheKey, loader);\n\n const buffer = hb.createBuffer();\n buffer.addText(text);\n // Setting script + direction explicitly is more reliable than guess for\n // Indic mixed-content runs.\n if (opts?.direction) buffer.setDirection(opts.direction);\n if (opts?.script) buffer.setScript(opts.script);\n if (opts?.language) buffer.setLanguage(opts.language);\n buffer.guessSegmentProperties();\n\n hb.shape(font, buffer);\n const glyphs = buffer.json();\n\n // HarfBuzz coordinate system: Y is UP, units are font units (we set scale\n // to upem so 1 unit == 1 font-design-unit). Convert to SVG user-space:\n // svgUnits = fontUnits * (fontSize / upem)\n // and flip Y.\n const scale = fontSize / upem;\n\n // Pen position, in font units.\n let penX = 0;\n let penY = 0;\n const pieces: string[] = [];\n\n for (const g of glyphs) {\n const rawPath = font.glyphToPath(g.g);\n if (rawPath) {\n // Transform glyph path:\n // 1. Translate to (penX + dx, penY + dy) in font units\n // 2. Flip Y (since HB Y-up, SVG Y-down)\n // 3. Scale by `scale`\n // 4. Translate by (x, y) in user units\n const ox = (penX + g.dx) * scale + x;\n const oy = (penY + g.dy) * -scale + y;\n pieces.push(transformPathData(rawPath, scale, -scale, ox, oy));\n }\n penX += g.ax;\n penY += g.ay;\n }\n\n buffer.destroy();\n\n return {\n pathData: pieces.join(''),\n width: penX * scale,\n };\n}\n\n/**\n * Apply an affine transform (sx, sy, tx, ty) to an SVG path's absolute\n * coordinates. The path emitted by HarfBuzz uses only absolute commands\n * (M, L, C, Q, Z), so we only need to transform numeric coordinate pairs.\n */\nfunction transformPathData(d: string, sx: number, sy: number, tx: number, ty: number): string {\n // Tokenize into commands and number pairs.\n // HB output format: \"M x,y\", \"L x,y\", \"C cx1,cy1 cx2,cy2 x,y\", \"Q cx,cy x,y\", \"Z\"\n // Numbers may be negative or fractional.\n let out = '';\n let i = 0;\n const n = d.length;\n while (i < n) {\n const c = d[i];\n if (c === 'M' || c === 'L' || c === 'C' || c === 'Q') {\n out += c;\n i++;\n // Read remaining coordinate pairs until next command letter or end.\n // Pairs are separated by space or comma.\n let pairsBuf = '';\n while (i < n && d[i] !== 'M' && d[i] !== 'L' && d[i] !== 'C' && d[i] !== 'Q' && d[i] !== 'Z') {\n pairsBuf += d[i];\n i++;\n }\n // pairsBuf now holds something like \"12.5,30.2 -4,5 0,0\"\n const nums = pairsBuf.trim().split(/[ ,]+/).filter(Boolean).map(Number);\n for (let k = 0; k < nums.length; k += 2) {\n const px = nums[k] * sx + tx;\n const py = nums[k + 1] * sy + ty;\n if (k > 0) out += ' ';\n out += `${round(px)},${round(py)}`;\n }\n } else if (c === 'Z') {\n out += 'Z';\n i++;\n } else {\n // Skip unexpected character (e.g. stray whitespace)\n i++;\n }\n }\n return out;\n}\n\nfunction round(n: number): string {\n // 2 decimal places — same precision as opentype.js fallback.\n return (Math.round(n * 100) / 100).toString();\n}\n\n/**\n * Pre-warm HarfBuzz WASM so the first PDF export doesn't pay the load cost.\n * Safe to call multiple times.\n */\nexport function preloadHarfBuzz(): Promise<HBInstance> {\n return getHB();\n}\n","/**\n * SVG Text-to-Path Conversion (universal outlining)\n *\n * Converts EVERY <text> element to <path> elements using the actual font the\n * browser is rendering. This guarantees 100% visual parity between the canvas\n * preview and the downloaded vector PDF — what you see is what you get, for\n * any font (Google Fonts, local TTFs) and any script (Latin, Devanagari, etc.).\n *\n * Tradeoffs vs. leaving text as <text>:\n * + Pixel-perfect parity for any font, including unknown weights, custom kerns,\n * ligatures, and complex Indic shaping.\n * - Text is no longer selectable/searchable in the PDF.\n * - PDF file size grows ~30-50% (paths are bigger than text + embedded TTF).\n *\n * Mechanics:\n * - Latin runs → opentype.js getPath() (uses font's own metrics)\n * - Devanagari → HarfBuzz shaping for proper conjuncts/matra reordering\n * - Mixed runs → split by script, advance cursor between runs\n * - text-anchor → run width measured via the font; offset applied\n * - Failure → keep original <text> so the user still sees something\n */\n\nimport * as opentype from 'opentype.js';\nimport {\n FONT_FILES,\n FONT_FALLBACK_SYMBOLS,\n FONT_FALLBACK_DEVANAGARI,\n FONT_FALLBACK_MATH,\n resolveFontWeight,\n resolveDevanagariSibling,\n getGoogleFontBytes,\n getFontshareFontBytes,\n} from '@/lib/pdfFonts';\nimport type { FontWeightFiles } from '@/lib/pdfFonts';\nimport { shapeRunToSvgPath } from '@/lib/harfbuzzShaper';\n\n// Cache parsed fonts to avoid re-fetching\nconst fontCache = new Map<string, opentype.Font>();\n/** Raw font bytes cache — shared between opentype.js and HarfBuzz */\nconst fontBytesCache = new Map<string, Uint8Array>();\n\n/** True if the character is in the Devanagari Unicode block */\nfunction isDevanagari(char: string): boolean {\n const c = char.codePointAt(0) ?? 0;\n return (c >= 0x0900 && c <= 0x097F) || (c >= 0xA8E0 && c <= 0xA8FF) || (c >= 0x1CD0 && c <= 0x1CFF);\n}\n\n/** Check if a string contains any Devanagari characters */\nfunction containsDevanagari(text: string): boolean {\n if (!text) return false;\n for (const char of text) {\n if (isDevanagari(char)) return true;\n }\n return false;\n}\n\n/** True if the character is in Basic Latin, Latin-1, or Latin Extended. */\nfunction isBasicLatinOrLatinExtended(char: string): boolean {\n const c = char.codePointAt(0) ?? 0;\n return c <= 0x024F;\n}\n\ntype FontRunType = 'main' | 'devanagari' | 'math' | 'symbol';\n\n/** Math operators / arrows (≠ ≤ ≥ ≈ ∞ → ← ∑ √ ∈ …) — typically missing from\n * Latin fonts AND from the Noto Sans symbol fallback subset, so they need\n * the dedicated Noto Sans Math fallback to render correctly. */\nfunction isMathOperatorChar(char: string): boolean {\n const c = char.codePointAt(0) ?? 0;\n if (c >= 0x2190 && c <= 0x21FF) return true; // Arrows\n if (c >= 0x2200 && c <= 0x22FF) return true; // Mathematical Operators\n if (c >= 0x2300 && c <= 0x23FF) return true; // Misc Technical\n if (c >= 0x27C0 && c <= 0x27EF) return true; // Misc Math Symbols-A\n if (c >= 0x2980 && c <= 0x29FF) return true; // Misc Math Symbols-B\n if (c >= 0x2A00 && c <= 0x2AFF) return true; // Supplemental Math Operators\n if (c >= 0x2B00 && c <= 0x2B59) return true;\n return false;\n}\n\nfunction classifyCharForFontRun(char: string): FontRunType {\n if (isBasicLatinOrLatinExtended(char)) return 'main';\n if (isDevanagari(char)) return 'devanagari';\n if (isMathOperatorChar(char)) return 'math';\n return 'symbol';\n}\n\n/** Neutral marks/punctuation that don't need their own glyph coverage decision. */\nfunction isIgnorableForCoverage(char: string): boolean {\n return /\\s/.test(char) || /[\\u0964\\u0965\\u200c\\u200d]/u.test(char);\n}\n\n/** True when the parsed font can actually draw every relevant glyph in this run. */\nfunction fontSupportsRun(font: opentype.Font | null, text: string, runType: FontRunType): boolean {\n if (!font) return false;\n for (const char of text) {\n if (isIgnorableForCoverage(char)) continue;\n if (runType === 'devanagari' && !isDevanagari(char)) continue;\n if (runType === 'symbol' && classifyCharForFontRun(char) !== 'symbol') continue;\n if (runType === 'math' && classifyCharForFontRun(char) !== 'math') continue;\n const glyph = font.charToGlyph(char);\n if (!glyph || glyph.index === 0) return false;\n }\n return true;\n}\n\nconst browserMeasureCanvas: HTMLCanvasElement | null = typeof document !== 'undefined'\n ? document.createElement('canvas')\n : null;\n\n/** Measure with the browser's real canvas fallback stack — this is our preview truth source. */\nfunction measureBrowserWidth(fontFamily: string, weight: number, fontSize: number, text: string): number | null {\n const ctx = browserMeasureCanvas?.getContext('2d');\n if (!ctx) return null;\n ctx.font = `normal normal ${weight} ${fontSize}px \"${fontFamily}\"`;\n return ctx.measureText(text).width;\n}\n\nfunction uniqueFamilies(families: Array<string | null | undefined>): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const family of families) {\n const clean = family?.trim();\n if (!clean || seen.has(clean)) continue;\n seen.add(clean);\n out.push(clean);\n }\n return out;\n}\n\nfunction fontLooksItalic(font: opentype.Font | null | undefined): boolean {\n if (!font) return false;\n const names = font.names as unknown as Record<string, Record<string, string> | undefined>;\n const candidates = [\n names.fontSubfamily?.en,\n names.typographicSubfamily?.en,\n names.fullName?.en,\n names.postScriptName?.en,\n ].filter(Boolean).join(' ');\n return /italic|oblique/i.test(candidates);\n}\n\nfunction syntheticItalicTransform(anchorX: number, baselineY: number): string {\n return `translate(${anchorX} ${baselineY}) skewX(-12) translate(${-anchorX} ${-baselineY})`;\n}\n\n/** Get the font file path for a given weight */\nfunction getFontPath(\n fontFiles: FontWeightFiles,\n weight: number,\n isItalic = false,\n): string | null {\n const resolved = resolveFontWeight(weight);\n const uprightMap: Record<number, (keyof FontWeightFiles)[]> = {\n 300: ['light', 'regular'],\n 400: ['regular'],\n 500: ['medium', 'regular'],\n 600: ['semibold', 'bold'],\n 700: ['bold', 'semibold', 'regular'],\n };\n // Italic/oblique: prefer the matching italic variant, then fall back through\n // other italic variants, then upright as a last resort. Without this italic\n // tspans (e.g. *italic* markdown) silently rendered as upright in the PDF.\n const italicMap: Record<number, (keyof FontWeightFiles)[]> = {\n 300: ['lightItalic', 'italic', 'light', 'regular'],\n 400: ['italic', 'regular'],\n 500: ['mediumItalic', 'italic', 'medium', 'regular'],\n 600: ['semiboldItalic', 'boldItalic', 'italic', 'semibold', 'bold', 'regular'],\n 700: ['boldItalic', 'semiboldItalic', 'italic', 'bold', 'semibold', 'regular'],\n };\n const map = isItalic ? italicMap : uprightMap;\n for (const key of map[resolved] || ['regular']) {\n if (fontFiles[key]) return fontFiles[key]!;\n }\n return fontFiles.regular || null;\n}\n\n/** Build a fetchable font URL without accidentally producing /fonts/fonts/*. */\nfunction resolveFontUrl(fileName: string, fontBaseUrl: string): string {\n if (/^https?:\\/\\//i.test(fileName) || fileName.startsWith('data:')) return fileName;\n if (fileName.startsWith('/')) {\n return typeof window !== 'undefined' ? new URL(fileName, window.location.origin).toString() : fileName;\n }\n const baseUrl = fontBaseUrl.endsWith('/') ? fontBaseUrl : fontBaseUrl + '/';\n return new URL(fileName, baseUrl).toString();\n}\n\n/** Load and cache a font via opentype.js */\nasync function loadFont(fontFamily: string, weight: number, fontBaseUrl: string, isItalic = false): Promise<opentype.Font | null> {\n const cacheKey = `${fontFamily}__${weight}__${isItalic ? 'i' : 'n'}`;\n if (fontCache.has(cacheKey)) return fontCache.get(cacheKey)!;\n\n const fontFiles = FONT_FILES[fontFamily];\n\n // ── Local TTF path ──\n // Use local files for both upright and italic variants when available.\n // (Previously italic always fell through to Google/Fontshare and silently\n // dropped to the regular weight when those lookups failed, so *italic*\n // markdown text rendered upright in the vector PDF.)\n if (fontFiles) {\n const fileName = getFontPath(fontFiles, weight, isItalic);\n if (!fileName) return null;\n const url = resolveFontUrl(fileName, fontBaseUrl);\n try {\n const response = await fetch(url);\n if (!response.ok) {\n console.warn(`[text-to-path] Failed to fetch font ${url}: ${response.status}`);\n return null;\n }\n const buffer = await response.arrayBuffer();\n const bytes = new Uint8Array(buffer);\n fontBytesCache.set(cacheKey, bytes);\n const font = opentype.parse(buffer);\n fontCache.set(cacheKey, font);\n return font;\n } catch (err) {\n console.warn(`[text-to-path] Failed to load local font ${fontFamily}:`, err);\n return null;\n }\n }\n\n // ── Google Fonts fallback (e.g. Mukta, Tiro Devanagari Hindi, Kalam) ──\n try {\n const resolvedWeight = resolveFontWeight(weight);\n // Try Google first, then Fontshare. Both populate the same byte cache shape\n // (`gf:${family}:${weight}:n|i`) so downstream HarfBuzz lookups still hit.\n let bytes = await getGoogleFontBytes(fontFamily, resolvedWeight, isItalic);\n if (!bytes) {\n bytes = await getFontshareFontBytes(fontFamily, resolvedWeight, isItalic);\n }\n // For italic, fall back to upright if italic variant is unavailable so\n // the bold/italic combo still renders the right family.\n if (!bytes && isItalic) {\n bytes = await getGoogleFontBytes(fontFamily, resolvedWeight, false);\n if (!bytes) bytes = await getFontshareFontBytes(fontFamily, resolvedWeight, false);\n }\n if (!bytes) {\n console.warn(`[text-to-path] No TTF available for ${fontFamily} (${weight}) on Google Fonts or Fontshare`);\n return null;\n }\n fontBytesCache.set(cacheKey, bytes);\n // opentype.parse needs an ArrayBuffer (not a typed-array view).\n const ab = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n const font = opentype.parse(ab);\n fontCache.set(cacheKey, font);\n return font;\n } catch (err) {\n console.warn(`[text-to-path] Failed to load Google font ${fontFamily}:`, err);\n return null;\n }\n}\n\n/** Get raw font bytes (loads via opentype path if not yet cached). */\nasync function getFontBytes(\n fontFamily: string,\n weight: number,\n fontBaseUrl: string,\n isItalic = false,\n): Promise<{ bytes: Uint8Array; cacheKey: string } | null> {\n const cacheKey = `${fontFamily}__${weight}__${isItalic ? 'i' : 'n'}`;\n if (!fontBytesCache.has(cacheKey)) {\n // Triggers fetch + populates fontBytesCache as a side-effect.\n await loadFont(fontFamily, weight, fontBaseUrl, isItalic);\n }\n const bytes = fontBytesCache.get(cacheKey);\n return bytes ? { bytes, cacheKey } : null;\n}\n\n/** Measure run advance width using opentype's own metrics (kerning enabled). */\nfunction measureRunWidth(font: opentype.Font, text: string, fontSize: number): number {\n try {\n return font.getAdvanceWidth(text, fontSize, { kerning: true });\n } catch {\n try { return font.getAdvanceWidth(text, fontSize); } catch { return text.length * fontSize * 0.5; }\n }\n}\n\n/**\n * Split a string into consecutive runs by script/fallback font,\n * preserving order. Whitespace stays with its preceding run.\n */\nfunction splitByFontRun(text: string): Array<{ text: string; runType: FontRunType }> {\n if (!text) return [];\n const runs: Array<{ text: string; runType: FontRunType }> = [];\n let buf = '';\n let bufType = classifyCharForFontRun(text[0]);\n for (const ch of text) {\n const chType = classifyCharForFontRun(ch);\n // Whitespace and punctuation stick to the current run to avoid breaking shaping.\n const isNeutral = /\\s/.test(ch);\n if (chType === bufType || isNeutral) {\n buf += ch;\n } else {\n if (buf) runs.push({ text: buf, runType: bufType });\n buf = ch;\n bufType = chType;\n }\n }\n if (buf) runs.push({ text: buf, runType: bufType });\n return runs;\n}\n\n/**\n * Generate an SVG path `d` string for a text run using the appropriate engine.\n * Returns { pathData, advance } or null on failure.\n */\nasync function shapeRunToPath(\n run: string,\n isDeva: boolean,\n primaryFont: opentype.Font,\n primaryBytes: { bytes: Uint8Array; cacheKey: string } | null,\n devaFont: opentype.Font | null,\n devaBytes: { bytes: Uint8Array; cacheKey: string } | null,\n x: number,\n y: number,\n fontSize: number,\n): Promise<{ pathData: string; advance: number } | null> {\n // Devanagari → HarfBuzz with the resolved Devanagari font (sibling of the chosen Latin font)\n if (isDeva && devaBytes && devaFont) {\n try {\n const shaped = await shapeRunToSvgPath(\n devaBytes.bytes,\n devaBytes.cacheKey,\n run,\n x,\n y,\n fontSize,\n { script: 'deva', language: 'hin', direction: 'ltr' },\n );\n if (shaped.pathData) {\n const advance = measureRunWidth(devaFont, run, fontSize);\n return { pathData: shaped.pathData, advance };\n }\n } catch (err) {\n console.warn(`[text-to-path] HarfBuzz failed for \"${run}\":`, err);\n }\n // Fall through to opentype.js fallback below using devaFont.\n try {\n const path = devaFont.getPath(run, x, y, fontSize);\n const d = path.toPathData(2);\n if (d && d !== 'M0 0') return { pathData: d, advance: measureRunWidth(devaFont, run, fontSize) };\n } catch { /* noop */ }\n return null;\n }\n\n // Latin / non-Indic → opentype.js getPath with the actual chosen font\n try {\n const path = primaryFont.getPath(run, x, y, fontSize, { kerning: true });\n const d = path.toPathData(2);\n if (d && d !== 'M0 0') return { pathData: d, advance: measureRunWidth(primaryFont, run, fontSize) };\n return { pathData: '', advance: measureRunWidth(primaryFont, run, fontSize) };\n } catch (err) {\n console.warn(`[text-to-path] opentype getPath failed for \"${run}\":`, err);\n return null;\n }\n}\n\n/** Parse a CSS-style transform attribute to extract translate values */\nfunction parseTranslate(transform: string | null): { tx: number; ty: number } {\n if (!transform) return { tx: 0, ty: 0 };\n const m = transform.match(/translate\\(\\s*([-\\d.]+)[ \\s,]+([-\\d.]+)\\s*\\)/);\n if (m) return { tx: parseFloat(m[1]), ty: parseFloat(m[2]) };\n // matrix(a,b,c,d,e,f) — e=tx, f=ty\n const mm = transform.match(/matrix\\(\\s*([-\\d.]+)[ \\s,]+([-\\d.]+)[ \\s,]+([-\\d.]+)[ \\s,]+([-\\d.]+)[ \\s,]+([-\\d.]+)[ \\s,]+([-\\d.]+)\\s*\\)/);\n if (mm) return { tx: parseFloat(mm[5]), ty: parseFloat(mm[6]) };\n return { tx: 0, ty: 0 };\n}\n\n/** Heuristic: does this text need shaping (complex script) we can't trust the PDF font embedder for? */\nfunction needsComplexShaping(text: string): boolean {\n if (!text) return false;\n for (const ch of text) {\n const c = ch.codePointAt(0) ?? 0;\n // Devanagari + extensions\n if ((c >= 0x0900 && c <= 0x097F) || (c >= 0xA8E0 && c <= 0xA8FF) || (c >= 0x1CD0 && c <= 0x1CFF)) return true;\n // Bengali, Gurmukhi, Gujarati, Oriya, Tamil, Telugu, Kannada, Malayalam, Sinhala\n if (c >= 0x0980 && c <= 0x0DFF) return true;\n // Thai, Lao, Tibetan, Myanmar, Khmer\n if (c >= 0x0E00 && c <= 0x0FFF) return true;\n if (c >= 0x1000 && c <= 0x109F) return true;\n if (c >= 0x1780 && c <= 0x17FF) return true;\n // Arabic, Hebrew (RTL + shaping)\n if (c >= 0x0590 && c <= 0x06FF) return true;\n if (c >= 0x0700 && c <= 0x074F) return true;\n // CJK\n if (c >= 0x3000 && c <= 0x9FFF) return true;\n if (c >= 0xAC00 && c <= 0xD7AF) return true;\n // Emoji / supplementary\n if (c >= 0x1F000) return true;\n // Math operators / arrows / misc technical — Latin fonts almost never\n // contain these glyphs, so jsPDF renders them as `.notdef` (backtick).\n // Treat as complex-shaping so they get outlined via the math fallback.\n if (c >= 0x2190 && c <= 0x21FF) return true; // Arrows\n if (c >= 0x2200 && c <= 0x22FF) return true; // Mathematical Operators\n if (c >= 0x2300 && c <= 0x23FF) return true; // Misc Technical\n if (c >= 0x27C0 && c <= 0x27EF) return true; // Misc Math Symbols-A\n if (c >= 0x2980 && c <= 0x29FF) return true; // Misc Math Symbols-B\n if (c >= 0x2A00 && c <= 0x2AFF) return true; // Supplemental Math Operators\n if (c >= 0x2B00 && c <= 0x2B59) return true;\n }\n return false;\n}\n\n/**\n * Decorative / script Latin fonts whose jsPDF text embedding (svg2pdf path)\n * is unreliable in production: either the local TTF isn't shipped under\n * `/fonts/` and the Google Fonts fallback is silently rejected, or the font\n * is single-weight with custom kerning that jsPDF does not honor. Symptom:\n * the PDF shows a generic Times/Helvetica fallback instead of the actual\n * decorative face the user sees on the canvas preview.\n *\n * In `auto` mode we outline these via opentype.js (which fetches the same\n * TTF the browser uses, with Google Fonts fallback) so the downloaded PDF\n * matches the canvas pixel-for-pixel. Body fonts (Inter, Roboto, Montserrat,\n * Open Sans, etc.) stay selectable.\n *\n * Match is case- and space-insensitive against the resolved CSS family.\n */\nconst DECORATIVE_OUTLINE_FAMILIES = new Set<string>([\n 'greatvibes', 'great vibes',\n 'cinzel', 'cinzeldecorative', 'cinzel decorative',\n 'pacifico',\n 'allura',\n 'alexbrush', 'alex brush',\n 'abrilfatface', 'abril fatface',\n 'cormorant', 'cormorantgaramond', 'cormorant garamond',\n 'dancingscript', 'dancing script',\n 'sacramento',\n 'lobster',\n 'satisfy',\n 'kaushanscript', 'kaushan script',\n 'parisienne',\n 'tangerine',\n 'yellowtail',\n 'shadowsintolight', 'shadows into light',\n 'amaticsc', 'amatic sc',\n 'caveat',\n 'playball',\n 'marckscript', 'marck script',\n 'courgette',\n]);\n\nfunction isDecorativeFamily(family: string | null | undefined): boolean {\n if (!family) return false;\n const k = family.replace(/['\"]/g, '').replace(/\\s+/g, ' ').trim().toLowerCase();\n if (DECORATIVE_OUTLINE_FAMILIES.has(k)) return true;\n // Also match the no-space variant (e.g. \"GreatVibes\" CSS keyword).\n return DECORATIVE_OUTLINE_FAMILIES.has(k.replace(/\\s+/g, ''));\n}\n\n/**\n * Read the resolved font-family of a `<text>` element, walking up the\n * inheritance chain across both attribute and style declarations.\n */\nfunction readResolvedFontFamily(textEl: Element): string | null {\n const readStyleToken = (style: string, prop: string): string | null => {\n const m = style.match(new RegExp(`${prop}\\\\s*:\\\\s*([^;]+)`, 'i'));\n return m?.[1]?.trim() || null;\n };\n let current: Element | null = textEl;\n while (current) {\n const a = current.getAttribute('font-family')?.trim();\n if (a) return a.split(',')[0]?.replace(/['\"]/g, '').trim() || null;\n const s = readStyleToken(current.getAttribute('style') || '', 'font-family');\n if (s) return s.split(',')[0]?.replace(/['\"]/g, '').trim() || null;\n current = current.parentElement;\n }\n return null;\n}\n\nexport interface OutlineTextOptions {\n /**\n * - 'all' (default): outline every <text> for pixel-perfect parity.\n * - 'complex-only': only outline text that contains complex scripts (Indic, Arabic, CJK, emoji);\n * leave Latin text as <text> so it stays selectable in the PDF.\n * - 'mixed-style-only': only outline <text> elements whose tspans override\n * font-weight / font-style / fill (i.e. inline markdown formatting like\n * **bold**, *italic*, [c=primary]color[/c]). Plain text stays selectable\n * while formatted runs render as paths so bold/italic/colored spans don't\n * silently lose their styling because jsPDF only embedded one font weight.\n */\n /**\n * Additional mode (v0.5.130 PARITY):\n * - 'shadow-bound' → outline only <text> nodes whose nearest Fabric\n * textbox <g> ancestor carries `data-pd-shadow-blur` (set by\n * textBackgroundRenderer when a non-zero-blur shadow is emitted) OR\n * <text> nodes that live inside a `g.__pdShadowRaster` marker. Visible\n * text and shadow silhouette text then share IDENTICAL glyph outlines,\n * so the rasterized shadow PNG aligns pixel-perfect with the\n * svg2pdf-drawn vector text — fixing the small horizontal drift that\n * appeared on centered text with shadow > 0 in both client and server\n * PDF exports.\n */\n mode?: 'all' | 'complex-only' | 'mixed-style-only' | 'shadow-bound' | 'gradient-only';\n}\n\n/**\n * True if a <text> element (or any of its <tspan> descendants) carries a\n * gradient fill (`fill=\"url(#…)\"` or `style=\"fill:url(#…)\"`). svg2pdf.js\n * cannot honor gradient fills on live <text> — it falls back to a single\n * solid color via jsPDF's text() API — so callers use this to detect text\n * that must either be outlined to <path> (where gradients render correctly)\n * or have its fill resolved to a solid color before PDF generation.\n */\nfunction textHasGradientFill(textEl: Element): boolean {\n const isGradRef = (val: string | null | undefined): boolean =>\n !!val && /url\\s*\\(\\s*['\"]?#/i.test(val);\n const readFill = (el: Element): string => {\n const attr = el.getAttribute('fill') || '';\n if (attr) return attr;\n const style = el.getAttribute('style') || '';\n const m = style.match(/(?:^|;)\\s*fill\\s*:\\s*([^;]+)/i);\n return m ? m[1].trim() : '';\n };\n if (isGradRef(readFill(textEl))) return true;\n const tspans = textEl.querySelectorAll('tspan');\n for (const ts of tspans) {\n if (isGradRef(readFill(ts))) return true;\n }\n return false;\n}\n\n/**\n * For PDF pipelines that keep text live/selectable (no outlining), gradient\n * fills on <text> would otherwise render as solid black via svg2pdf's text\n * path. This helper rewrites every gradient-ref fill on <text>/<tspan> to\n * the first colour stop of the referenced gradient, so the PDF at least\n * shows the dominant gradient colour instead of black.\n *\n * Selectability is preserved. Visual parity with the canvas gradient is\n * lost — that's the documented trade-off of \"Selectable\" mode.\n */\nexport function replaceGradientTextFillsWithFirstStop(svgStr: string): string {\n if (!svgStr || !/url\\s*\\(\\s*['\"]?#/i.test(svgStr)) return svgStr;\n let doc: Document;\n try {\n doc = new DOMParser().parseFromString(svgStr, 'image/svg+xml');\n } catch {\n return svgStr;\n }\n // Build id → first-stop colour map from gradient defs.\n const stopColorFor = (gradEl: Element): string | null => {\n const stop = gradEl.querySelector('stop');\n if (!stop) return null;\n const attr = stop.getAttribute('stop-color') || '';\n if (attr) return attr;\n const style = stop.getAttribute('style') || '';\n const m = style.match(/(?:^|;)\\s*stop-color\\s*:\\s*([^;]+)/i);\n return m ? m[1].trim() : null;\n };\n const gradMap = new Map<string, string>();\n doc.querySelectorAll('linearGradient, radialGradient').forEach((g) => {\n const id = g.getAttribute('id');\n if (!id) return;\n // Follow xlink:href chains to a gradient that actually has stops.\n let cur: Element | null = g;\n let hops = 0;\n while (cur && cur.querySelectorAll('stop').length === 0 && hops < 4) {\n const href = cur.getAttribute('xlink:href') || cur.getAttribute('href');\n if (!href || !href.startsWith('#')) break;\n const next = doc.getElementById(href.slice(1));\n if (!next) break;\n cur = next;\n hops++;\n }\n const color = cur ? stopColorFor(cur) : null;\n if (color) gradMap.set(id, color);\n });\n if (gradMap.size === 0) return svgStr;\n\n const refRe = /url\\s*\\(\\s*['\"]?#([^)'\"]+)['\"]?\\s*\\)/i;\n const resolveFillAttr = (el: Element) => {\n const attr = el.getAttribute('fill');\n if (attr) {\n const m = attr.match(refRe);\n if (m) {\n const color = gradMap.get(m[1]);\n if (color) el.setAttribute('fill', color);\n }\n }\n const style = el.getAttribute('style');\n if (style && refRe.test(style)) {\n const newStyle = style.replace(/(^|;)\\s*fill\\s*:\\s*url\\s*\\([^)]+\\)/i, (whole, sep) => {\n const m = whole.match(refRe);\n const color = m ? gradMap.get(m[1]) : null;\n return color ? `${sep}fill:${color}` : whole;\n });\n if (newStyle !== style) el.setAttribute('style', newStyle);\n }\n };\n doc.querySelectorAll('text, tspan').forEach(resolveFillAttr);\n return new XMLSerializer().serializeToString(doc.documentElement);\n}\n\nexport type PdfTextMode = 'auto' | 'selectable' | 'pixel-perfect';\n\n/**\n * Single shared PDF text-prep contract for host exports and the published\n * canvas-renderer package:\n * - pixel-perfect: outline every text node, gradient or not\n * - auto: outline only gradient-filled text so gradients render as vectors\n * - selectable: keep live text and replace gradient fills with first stop\n */\nexport async function prepareSvgTextForPdfMode(\n svgStr: string,\n textMode: PdfTextMode = 'selectable',\n fontBaseUrl?: string,\n): Promise<string> {\n if (textMode === 'pixel-perfect') {\n return convertDevanagariTextToPath(svgStr, fontBaseUrl, { mode: 'all' });\n }\n\n if (textMode === 'auto') {\n return convertDevanagariTextToPath(svgStr, fontBaseUrl, { mode: 'gradient-only' });\n }\n\n return replaceGradientTextFillsWithFirstStop(svgStr);\n}\n\n/**\n * Convert EVERY <text> element in the SVG to <path> elements using the actual\n * font (Latin → opentype, Devanagari → HarfBuzz). Guarantees 100% font parity\n * between the canvas preview and the downloaded PDF.\n *\n * Name kept as `convertDevanagariTextToPath` for backwards compatibility with\n * existing callers (UsePackage.tsx). The behaviour is now universal — name is\n * a misnomer.\n *\n * @param svgStr - The SVG string from Fabric's toSVG()\n * @param fontBaseUrl - Base URL where TTF font files are served (e.g., '/fonts/')\n * @param options - Outline mode controls (default: outline all)\n * @returns Modified SVG string with text converted to paths\n */\nexport async function convertDevanagariTextToPath(\n svgStr: string,\n fontBaseUrl?: string,\n options: OutlineTextOptions = {},\n): Promise<string> {\n const baseUrl = fontBaseUrl ?? (typeof window !== 'undefined' ? window.location.origin + '/fonts/' : '/fonts/');\n const mode = options.mode ?? 'all';\n\n const parser = new DOMParser();\n const doc = parser.parseFromString(svgStr, 'image/svg+xml');\n\n // Find all top-level <text> elements (not nested tspans)\n const textEls = doc.querySelectorAll('text');\n let convertedCount = 0;\n let skippedCount = 0;\n\n for (const textEl of textEls) {\n // Universal mode: process every <text>, not just Devanagari ones.\n const fullText = textEl.textContent || '';\n if (!fullText.trim()) continue;\n\n // 'shadow-bound' mode: outline only text bound to a PDF-sensitive decoration.\n // We accept text that lives directly inside a __pdShadowRaster marker\n // (the silhouette layer) AND text whose nearest Fabric textbox <g> ancestor\n // was tagged for blurred shadow or text background (the visible layer).\n if (mode === 'shadow-bound') {\n const inShadowMarker =\n !!textEl.closest && !!(textEl as any).closest('g.__pdShadowRaster');\n let inTaggedTextbox = false;\n let cur: Element | null = textEl.parentElement;\n while (cur) {\n if (cur.tagName.toLowerCase() === 'g' && (cur.hasAttribute('data-pd-shadow-blur') || cur.hasAttribute('data-pd-text-bg'))) {\n inTaggedTextbox = true;\n break;\n }\n cur = cur.parentElement;\n }\n if (!inShadowMarker && !inTaggedTextbox) {\n skippedCount++;\n continue;\n }\n }\n\n // Helper: does any tspan override formatting (markdown bold/italic/color/decoration)?\n const hasMixedStyleTspans = (): boolean => {\n const tspansAll = textEl.querySelectorAll('tspan');\n const readProp = (el: Element, prop: string): string => {\n const attr = (el.getAttribute(prop) || '').trim();\n if (attr) return attr;\n const style = el.getAttribute('style') || '';\n const m = style.match(new RegExp(`(?:^|;)\\\\s*${prop}\\\\s*:\\\\s*([^;]+)`, 'i'));\n return m ? m[1].trim() : '';\n };\n const baseWeight = readProp(textEl, 'font-weight');\n const baseStyle = readProp(textEl, 'font-style');\n const baseFill = readProp(textEl, 'fill');\n const baseDeco = readProp(textEl, 'text-decoration');\n for (const ts of tspansAll) {\n const tw = readProp(ts, 'font-weight');\n const tst = readProp(ts, 'font-style');\n const tFill = readProp(ts, 'fill');\n const tDeco = readProp(ts, 'text-decoration');\n const tBg = (ts.getAttribute('style') || '').match(/background|text-background/i);\n if (\n (tw && tw !== baseWeight) ||\n (tst && tst !== baseStyle) ||\n (tFill && tFill !== baseFill) ||\n (tDeco && tDeco !== baseDeco) ||\n tBg\n ) {\n return true;\n }\n }\n return false;\n };\n\n // 'gradient-only' mode: leave every <text> selectable EXCEPT those with a\n // gradient fill (`url(#…)`). svg2pdf cannot render gradient fills on live\n // text — outlining the run to <path> preserves the gradient. Used by the\n // \"Auto\" PDF text mode so only gradient text loses selectability.\n if (mode === 'gradient-only') {\n if (!textHasGradientFill(textEl)) {\n skippedCount++;\n continue;\n }\n }\n\n // 'complex-only' mode: leave plain Latin text as real selectable text,\n // BUT still outline (a) anything with markdown formatting (mixed-style\n // tspans) since svg2pdf only embeds one font weight per element, and\n // (b) text rendered in a known decorative / script Latin family whose\n // jsPDF embedding is unreliable (GreatVibes, Cinzel, Pacifico, etc.).\n // Without (b) the downloaded PDF silently falls back to Times/Helvetica\n // for those fonts even though the canvas preview shows the real face.\n if (mode === 'complex-only') {\n const resolvedFamily = readResolvedFontFamily(textEl);\n const decorative = isDecorativeFamily(resolvedFamily);\n // (c) text whose fill is a gradient reference (`url(#…)`). svg2pdf.js\n // only feeds a single solid RGB to jsPDF's text() API, so a\n // gradient fill on live <text> silently degrades to the first\n // stop / black. Outlining the run into <path> keeps the gradient\n // because svg2pdf renders gradient fills correctly on paths.\n const hasGradientFill = textHasGradientFill(textEl);\n if (\n !needsComplexShaping(fullText) &&\n !hasMixedStyleTspans() &&\n !decorative &&\n !hasGradientFill\n ) {\n skippedCount++;\n continue;\n }\n }\n\n // 'mixed-style-only' mode: only outline <text> elements that have any\n // tspan with formatting overrides relative to the parent <text> (different\n // font-weight, font-style, fill, or text-decoration). These come from\n // inline markdown — bold/italic/highlight/color runs that svg2pdf can't\n // render correctly because jsPDF only has the element's base font embedded.\n if (mode === 'mixed-style-only' && !hasMixedStyleTspans()) {\n skippedCount++;\n continue;\n }\n\n // Resolve font properties\n const readStyleToken = (style: string, prop: string): string | null => {\n const m = style.match(new RegExp(`${prop}\\\\s*:\\\\s*([^;]+)`, 'i'));\n return m?.[1]?.trim() || null;\n };\n\n const getAttrOrStyle = (el: Element, attr: string): string | null => {\n let current: Element | null = el;\n while (current) {\n const attrVal = current.getAttribute(attr)?.trim();\n if (attrVal) return attrVal;\n const styleVal = readStyleToken(current.getAttribute('style') || '', attr);\n if (styleVal) return styleVal;\n current = current.parentElement;\n }\n return null;\n };\n\n const fontFamily = getAttrOrStyle(textEl, 'font-family')\n ?.split(',')[0]\n ?.replace(/['\"]/g, '')\n .trim();\n\n const fontSizeStr = getAttrOrStyle(textEl, 'font-size') || '16';\n const fontSize = parseFloat(fontSizeStr);\n\n const fontWeightStr = getAttrOrStyle(textEl, 'font-weight') || '400';\n const fontWeight = Number.parseInt(fontWeightStr, 10) || 400;\n\n const fillColor = getAttrOrStyle(textEl, 'fill') || '#000000';\n const fillOpacity = getAttrOrStyle(textEl, 'fill-opacity') || '1';\n\n if (!fontFamily) {\n skippedCount++;\n continue;\n }\n\n // ── Load the PRIMARY font (the exact font shown in the preview) ──\n const primaryFont = await loadFont(fontFamily, fontWeight, baseUrl);\n const primaryBytes = await getFontBytes(fontFamily, fontWeight, baseUrl);\n\n // ── Devanagari font resolution ──\n // The browser canvas does NOT use curated siblings when a Latin font lacks\n // Hindi glyphs. It uses the CSS/font fallback stack; in our preview path\n // that fallback is Hind. For parity, pick the candidate whose shaped width\n // matches browser canvas measurement, with the chosen family itself first\n // for fonts like Poppins that genuinely include Devanagari glyphs.\n const hasDeva = containsDevanagari(fullText);\n const hasSymbol = [...fullText].some((char) => classifyCharForFontRun(char) === 'symbol');\n const hasMath = [...fullText].some((char) => classifyCharForFontRun(char) === 'math');\n const devaCandidateFamilies = hasDeva\n ? uniqueFamilies([fontFamily, FONT_FALLBACK_DEVANAGARI, resolveDevanagariSibling(fontFamily)])\n : [];\n type ResolvedRunFont = { family: string; font: opentype.Font; bytes: { bytes: Uint8Array; cacheKey: string } };\n const devaRunFontCache = new Map<string, Promise<ResolvedRunFont | null>>();\n const resolveDevaFontForRun = (runText: string, runFontSize: number): Promise<ResolvedRunFont | null> => {\n const cacheKey = `${runText}__${runFontSize}`;\n const cached = devaRunFontCache.get(cacheKey);\n if (cached) return cached;\n\n const promise = (async () => {\n const browserWidth = measureBrowserWidth(fontFamily, fontWeight, runFontSize, runText);\n let best: (ResolvedRunFont & { diff: number }) | null = null;\n\n for (const family of devaCandidateFamilies) {\n const font = family === fontFamily ? primaryFont : await loadFont(family, fontWeight, baseUrl);\n const bytes = family === fontFamily ? primaryBytes : await getFontBytes(family, fontWeight, baseUrl);\n if (!font || !bytes || !fontSupportsRun(font, runText, 'devanagari')) continue;\n\n const width = measureRunWidth(font, runText, runFontSize);\n const diff = browserWidth == null ? 0 : Math.abs(width - browserWidth);\n if (!best || diff < best.diff) best = { family, font, bytes, diff };\n\n // Exact/near-exact match to canvas measurement — no need to keep trying.\n if (browserWidth != null && diff <= 0.25) break;\n }\n\n if (best) {\n console.log('[text-to-path] Devanagari font resolved', {\n requestedFamily: fontFamily,\n resolvedFamily: best.family,\n browserWidth,\n diff: Math.round(best.diff * 100) / 100,\n });\n }\n return best;\n })();\n\n devaRunFontCache.set(cacheKey, promise);\n return promise;\n };\n\n const symbolRunFontPromise: Promise<ResolvedRunFont | null> | null = hasSymbol\n ? (async () => {\n const symbolFont = await loadFont(FONT_FALLBACK_SYMBOLS, 400, baseUrl);\n const symbolBytes = await getFontBytes(FONT_FALLBACK_SYMBOLS, 400, baseUrl);\n return symbolFont && symbolBytes ? { family: FONT_FALLBACK_SYMBOLS, font: symbolFont, bytes: symbolBytes } : null;\n })()\n : null;\n const mathRunFontPromise: Promise<ResolvedRunFont | null> | null = hasMath\n ? (async () => {\n const mathFont = await loadFont(FONT_FALLBACK_MATH, 400, baseUrl);\n const mathBytes = await getFontBytes(FONT_FALLBACK_MATH, 400, baseUrl);\n return mathFont && mathBytes ? { family: FONT_FALLBACK_MATH, font: mathFont, bytes: mathBytes } : null;\n })()\n : null;\n\n // If we have NEITHER a primary font nor (for Devanagari) a fallback font,\n // we can't outline this element — leave the original <text> in place so\n // the user still sees something rather than nothing.\n if (!primaryFont && !hasDeva && !hasSymbol && !hasMath) {\n console.warn(`[text-to-path] No font available for \"${fontFamily}\", leaving as <text>`);\n skippedCount++;\n continue;\n }\n\n // Resolve text-anchor (Fabric uses 'middle' for centered text). We measure the\n // run width and apply a leftward shift so the anchor point matches.\n const textAnchorRaw = (getAttrOrStyle(textEl, 'text-anchor') || 'start').toLowerCase();\n const baseTextAnchor: 'start' | 'middle' | 'end' =\n textAnchorRaw === 'middle' ? 'middle' : textAnchorRaw === 'end' ? 'end' : 'start';\n\n // Process tspan children or direct text\n const tspans = textEl.querySelectorAll('tspan');\n const elementsToProcess = tspans.length > 0 ? Array.from(tspans) : [textEl];\n\n // Create a <g> group to replace the <text> element\n const group = doc.createElementNS('http://www.w3.org/2000/svg', 'g');\n\n // Copy transform from <text> to <g>\n const textTransform = textEl.getAttribute('transform');\n if (textTransform) group.setAttribute('transform', textTransform);\n\n // Get base x/y from the <text> element\n const baseX = parseFloat(textEl.getAttribute('x') || '0');\n const baseY = parseFloat(textEl.getAttribute('y') || '0');\n\n for (const elem of elementsToProcess) {\n const text = elem.textContent || '';\n if (!text.trim()) continue;\n\n // Get element-specific position\n const elemX = parseFloat(elem.getAttribute('x') || String(baseX));\n const elemY = parseFloat(elem.getAttribute('y') || String(baseY));\n const elemAnchorRaw = (getAttrOrStyle(elem, 'text-anchor') || baseTextAnchor).toLowerCase();\n const elemTextAnchor: 'start' | 'middle' | 'end' =\n elemAnchorRaw === 'middle' ? 'middle' : elemAnchorRaw === 'end' ? 'end' : 'start';\n\n // Get element-specific transform\n const elemTransform = elem !== textEl ? parseTranslate(elem.getAttribute('transform')) : { tx: 0, ty: 0 };\n let x = elemX + elemTransform.tx;\n const y = elemY + elemTransform.ty;\n\n // Get element-specific fill\n const elemFill = getAttrOrStyle(elem, 'fill') || fillColor;\n const elemFillOpacity = getAttrOrStyle(elem, 'fill-opacity') || fillOpacity;\n\n // Get element-specific font size\n const elemFontSizeStr = getAttrOrStyle(elem, 'font-size') || String(fontSize);\n const elemFontSize = parseFloat(elemFontSizeStr);\n\n // ── Per-element font weight / style (markdown bold/italic support) ──\n // Each tspan may override font-weight (e.g. **bold**) and font-style\n // (e.g. *italic*). Resolve per-element so we load the matching font\n // variant for outlining instead of always using the parent font.\n const elemWeightStr = getAttrOrStyle(elem, 'font-weight') || String(fontWeight);\n const elemWeight = Number.parseInt(elemWeightStr, 10) || (/bold/i.test(elemWeightStr) ? 700 : fontWeight);\n const elemStyleRaw = (getAttrOrStyle(elem, 'font-style') || 'normal').toLowerCase();\n const elemItalic = /italic|oblique/i.test(elemStyleRaw);\n const sameAsPrimary = elemWeight === fontWeight && !elemItalic;\n const elemFont = sameAsPrimary\n ? primaryFont\n : await loadFont(fontFamily, elemWeight, baseUrl, elemItalic);\n const elemBytes = sameAsPrimary\n ? primaryBytes\n : await getFontBytes(fontFamily, elemWeight, baseUrl, elemItalic);\n // If the variant didn't load, fall back to the primary font so we still\n // render something — better than dropping the tspan entirely.\n const fontForElem = elemFont || primaryFont;\n const bytesForElem = elemBytes || primaryBytes;\n\n // Split into font runs so each run uses the appropriate font/shaper.\n const runs = splitByFontRun(text);\n\n // First pass — measure total advance for text-anchor alignment.\n // We can only compute this if every run has a font; otherwise skip and\n // assume start-anchor (Fabric defaults to start for left-aligned text).\n let totalAdvance = 0;\n let canMeasureAll = true;\n for (const r of runs) {\n if (r.runType === 'devanagari') {\n const resolved = await resolveDevaFontForRun(r.text, elemFontSize);\n if (resolved) totalAdvance += measureRunWidth(resolved.font, r.text, elemFontSize);\n else canMeasureAll = false;\n } else if (r.runType === 'symbol') {\n const resolved = await symbolRunFontPromise;\n if (resolved && fontSupportsRun(resolved.font, r.text, 'symbol')) totalAdvance += measureRunWidth(resolved.font, r.text, elemFontSize);\n else canMeasureAll = false;\n } else if (r.runType === 'math') {\n const resolved = await mathRunFontPromise;\n if (resolved && fontSupportsRun(resolved.font, r.text, 'math')) totalAdvance += measureRunWidth(resolved.font, r.text, elemFontSize);\n else canMeasureAll = false;\n } else {\n if (fontForElem) totalAdvance += measureRunWidth(fontForElem, r.text, elemFontSize);\n else canMeasureAll = false;\n }\n }\n\n if (canMeasureAll) {\n if (elemTextAnchor === 'middle') x -= totalAdvance / 2;\n else if (elemTextAnchor === 'end') x -= totalAdvance;\n }\n\n // Second pass — emit a <path> per run.\n let cursor = x;\n let elemConverted = false;\n for (const r of runs) {\n // Pick the font that matches the browser-rendered canvas for this run.\n const resolvedDeva = r.runType === 'devanagari' ? await resolveDevaFontForRun(r.text, elemFontSize) : null;\n const resolvedSymbol = r.runType === 'symbol' ? await symbolRunFontPromise : null;\n const resolvedMath = r.runType === 'math' ? await mathRunFontPromise : null;\n const useDeva = !!resolvedDeva;\n const fontForRun = resolvedDeva?.font ?? resolvedSymbol?.font ?? resolvedMath?.font ?? fontForElem;\n const bytesForRun = resolvedDeva?.bytes ?? resolvedSymbol?.bytes ?? resolvedMath?.bytes ?? bytesForElem;\n if (!fontForRun) continue;\n\n const result = await shapeRunToPath(\n r.text,\n !!useDeva,\n fontForRun,\n bytesForRun,\n resolvedDeva?.font ?? null,\n resolvedDeva?.bytes ?? null,\n cursor,\n y,\n elemFontSize,\n );\n\n if (result && result.pathData) {\n const pathEl = doc.createElementNS('http://www.w3.org/2000/svg', 'path');\n pathEl.setAttribute('d', result.pathData);\n pathEl.setAttribute('fill', elemFill);\n if (elemItalic && !fontLooksItalic(fontForRun)) {\n // Families like Excon have no italic file; the browser preview uses\n // synthetic oblique, so skew the outlined glyph paths to match it.\n pathEl.setAttribute('transform', syntheticItalicTransform(cursor, y));\n }\n if (elemFillOpacity !== '1') {\n pathEl.setAttribute('fill-opacity', elemFillOpacity);\n }\n group.appendChild(pathEl);\n elemConverted = true;\n }\n if (result) cursor += result.advance;\n }\n\n if (elemConverted) {\n convertedCount++;\n } else {\n // Couldn't outline — keep original element so something renders.\n if (elem === textEl) {\n const clone = elem.cloneNode(true) as Element;\n if (textTransform) clone.removeAttribute('transform');\n group.appendChild(clone);\n } else {\n const newText = doc.createElementNS('http://www.w3.org/2000/svg', 'text');\n newText.setAttribute('x', String(elemX));\n newText.setAttribute('y', String(elemY));\n if (elem.getAttribute('style')) newText.setAttribute('style', elem.getAttribute('style')!);\n if (elem.getAttribute('font-family')) newText.setAttribute('font-family', elem.getAttribute('font-family')!);\n if (elem.getAttribute('font-size')) newText.setAttribute('font-size', elem.getAttribute('font-size')!);\n if (elem.getAttribute('font-weight')) newText.setAttribute('font-weight', elem.getAttribute('font-weight')!);\n newText.textContent = text;\n group.appendChild(newText);\n }\n }\n }\n\n // Replace <text> with <g> containing paths\n if (group.childNodes.length > 0) {\n textEl.parentNode?.replaceChild(group, textEl);\n }\n }\n\n console.log(\n `[text-to-path] Universal outline complete: converted=${convertedCount} skipped=${skippedCount}`,\n );\n\n return new XMLSerializer().serializeToString(doc.documentElement);\n}\n\n/**\n * Alias for `convertDevanagariTextToPath` with a clearer name. Both call the\n * same universal outliner — the older name is kept for backwards compatibility.\n */\nexport const convertAllTextToPath = convertDevanagariTextToPath;\n\n/**\n * Pre-warm the font cache for Devanagari fonts.\n * Call this early to avoid latency during PDF export.\n */\nexport async function preloadDevanagariFont(fontBaseUrl?: string): Promise<void> {\n const baseUrl = fontBaseUrl ?? (typeof window !== 'undefined' ? window.location.origin + '/fonts/' : '/fonts/');\n for (const weight of [300, 400, 500, 600, 700]) {\n await loadFont(FONT_FALLBACK_DEVANAGARI, weight, baseUrl);\n }\n}\n"],"names":["resolveFontWeight","FONT_FILES","opentype","getGoogleFontBytes","getFontshareFontBytes","_a","FONT_FALLBACK_DEVANAGARI","resolveDevanagariSibling","FONT_FALLBACK_SYMBOLS","FONT_FALLBACK_MATH"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAM,cAAc;AAmCpB,IAAI,oBAAgD;AAGpD,eAAe,QAA6B;AAC1C,MAAI,kBAAmB,QAAO;AAE9B,uBAAqB,YAAY;AAI/B,UAAM,CAAC,EAAE,SAAS,kBAAkB,EAAE,SAAS,MAAM,IAAI,MAAM,QAAQ,IAAI;AAAA,MACzE,OAAO,kBAAkB;AAAA,MACzB,OAAO,oBAAoB;AAAA,IAAA,CAC5B;AAED,UAAM,iBAAiB,MAAM,eAAe;AAAA,MAC1C,YAAY,CAAC,SAAiB;AAC5B,YAAI,KAAK,SAAS,OAAO,EAAG,QAAO;AACnC,eAAO;AAAA,MACT;AAAA,IAAA,CACD;AAED,WAAO,KAAK,cAAc;AAAA,EAC5B,GAAA;AAEA,SAAO;AACT;AAGA,MAAM,kCAAkB,IAAA;AAOxB,eAAe,UACb,UACA,gBACyC;AACzC,QAAM,SAAS,YAAY,IAAI,QAAQ;AACvC,MAAI,eAAe,EAAE,MAAM,OAAO,MAAM,MAAM,OAAO,KAAA;AAErD,QAAM,KAAK,MAAM,MAAA;AACjB,QAAM,WAAW,MAAM,eAAA;AACvB,QAAM,OAAO,GAAG,WAAW,QAAQ;AACnC,QAAM,OAAO,GAAG,WAAW,MAAM,CAAC;AAClC,QAAM,OAAO,GAAG,WAAW,IAAI;AAI/B,QAAM,QAAQ,KAAK,UAAU,KAAK,YAAa,KAAK,QAAQ,QAAU;AACtE,OAAK,SAAS,MAAM,IAAI;AAGxB,OAAK,QAAA;AAEL,cAAY,IAAI,UAAU,EAAE,MAAM,MAAM,MAAM;AAC9C,SAAO,EAAE,MAAM,KAAA;AACjB;AAgBA,eAAsB,kBACpB,UACA,UACA,MACA,GACA,GACA,UACA,MACoB;AACpB,QAAM,KAAK,MAAM,MAAA;AACjB,QAAM,SAAS,OAAO,aAAa,aAC9B,WACD,YAAY;AAEhB,QAAM,EAAE,MAAM,KAAA,IAAS,MAAM,UAAU,UAAU,MAAM;AAEvD,QAAM,SAAS,GAAG,aAAA;AAClB,SAAO,QAAQ,IAAI;AAGnB,MAAI,6BAAM,UAAW,QAAO,aAAa,KAAK,SAAS;AACvD,MAAI,6BAAM,OAAQ,QAAO,UAAU,KAAK,MAAM;AAC9C,MAAI,6BAAM,SAAU,QAAO,YAAY,KAAK,QAAQ;AACpD,SAAO,uBAAA;AAEP,KAAG,MAAM,MAAM,MAAM;AACrB,QAAM,SAAS,OAAO,KAAA;AAMtB,QAAM,QAAQ,WAAW;AAGzB,MAAI,OAAO;AACX,MAAI,OAAO;AACX,QAAM,SAAmB,CAAA;AAEzB,aAAW,KAAK,QAAQ;AACtB,UAAM,UAAU,KAAK,YAAY,EAAE,CAAC;AACpC,QAAI,SAAS;AAMX,YAAM,MAAM,OAAO,EAAE,MAAM,QAAQ;AACnC,YAAM,MAAM,OAAO,EAAE,MAAM,CAAC,QAAQ;AACpC,aAAO,KAAK,kBAAkB,SAAS,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;AAAA,IAC/D;AACA,YAAQ,EAAE;AACV,YAAQ,EAAE;AAAA,EACZ;AAEA,SAAO,QAAA;AAEP,SAAO;AAAA,IACL,UAAU,OAAO,KAAK,EAAE;AAAA,IACxB,OAAO,OAAO;AAAA,EAAA;AAElB;AAOA,SAAS,kBAAkB,GAAW,IAAY,IAAY,IAAY,IAAoB;AAI5F,MAAI,MAAM;AACV,MAAI,IAAI;AACR,QAAM,IAAI,EAAE;AACZ,SAAO,IAAI,GAAG;AACZ,UAAM,IAAI,EAAE,CAAC;AACb,QAAI,MAAM,OAAO,MAAM,OAAO,MAAM,OAAO,MAAM,KAAK;AACpD,aAAO;AACP;AAGA,UAAI,WAAW;AACf,aAAO,IAAI,KAAK,EAAE,CAAC,MAAM,OAAO,EAAE,CAAC,MAAM,OAAO,EAAE,CAAC,MAAM,OAAO,EAAE,CAAC,MAAM,OAAO,EAAE,CAAC,MAAM,KAAK;AAC5F,oBAAY,EAAE,CAAC;AACf;AAAA,MACF;AAEA,YAAM,OAAO,SAAS,KAAA,EAAO,MAAM,OAAO,EAAE,OAAO,OAAO,EAAE,IAAI,MAAM;AACtE,eAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,cAAM,KAAK,KAAK,CAAC,IAAI,KAAK;AAC1B,cAAM,KAAK,KAAK,IAAI,CAAC,IAAI,KAAK;AAC9B,YAAI,IAAI,EAAG,QAAO;AAClB,eAAO,GAAG,MAAM,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC;AAAA,MAClC;AAAA,IACF,WAAW,MAAM,KAAK;AACpB,aAAO;AACP;AAAA,IACF,OAAO;AAEL;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,MAAM,GAAmB;AAEhC,UAAQ,KAAK,MAAM,IAAI,GAAG,IAAI,KAAK,SAAA;AACrC;AChNA,MAAM,gCAAgB,IAAA;AAEtB,MAAM,qCAAqB,IAAA;AAG3B,SAAS,aAAa,MAAuB;AAC3C,QAAM,IAAI,KAAK,YAAY,CAAC,KAAK;AACjC,SAAQ,KAAK,QAAU,KAAK,QAAY,KAAK,SAAU,KAAK,SAAY,KAAK,QAAU,KAAK;AAC9F;AAGA,SAAS,mBAAmB,MAAuB;AACjD,MAAI,CAAC,KAAM,QAAO;AAClB,aAAW,QAAQ,MAAM;AACvB,QAAI,aAAa,IAAI,EAAG,QAAO;AAAA,EACjC;AACA,SAAO;AACT;AAGA,SAAS,4BAA4B,MAAuB;AAC1D,QAAM,IAAI,KAAK,YAAY,CAAC,KAAK;AACjC,SAAO,KAAK;AACd;AAOA,SAAS,mBAAmB,MAAuB;AACjD,QAAM,IAAI,KAAK,YAAY,CAAC,KAAK;AACjC,MAAI,KAAK,QAAU,KAAK,KAAQ,QAAO;AACvC,MAAI,KAAK,QAAU,KAAK,KAAQ,QAAO;AACvC,MAAI,KAAK,QAAU,KAAK,KAAQ,QAAO;AACvC,MAAI,KAAK,SAAU,KAAK,MAAQ,QAAO;AACvC,MAAI,KAAK,SAAU,KAAK,MAAQ,QAAO;AACvC,MAAI,KAAK,SAAU,KAAK,MAAQ,QAAO;AACvC,MAAI,KAAK,SAAU,KAAK,MAAQ,QAAO;AACvC,SAAO;AACT;AAEA,SAAS,uBAAuB,MAA2B;AACzD,MAAI,4BAA4B,IAAI,EAAG,QAAO;AAC9C,MAAI,aAAa,IAAI,EAAG,QAAO;AAC/B,MAAI,mBAAmB,IAAI,EAAG,QAAO;AACrC,SAAO;AACT;AAGA,SAAS,uBAAuB,MAAuB;AACrD,SAAO,KAAK,KAAK,IAAI,KAAK,8BAA8B,KAAK,IAAI;AACnE;AAGA,SAAS,gBAAgB,MAA4B,MAAc,SAA+B;AAChG,MAAI,CAAC,KAAM,QAAO;AAClB,aAAW,QAAQ,MAAM;AACvB,QAAI,uBAAuB,IAAI,EAAG;AAClC,QAAI,YAAY,gBAAgB,CAAC,aAAa,IAAI,EAAG;AACrD,QAAI,YAAY,YAAY,uBAAuB,IAAI,MAAM,SAAU;AACvE,QAAI,YAAY,UAAU,uBAAuB,IAAI,MAAM,OAAQ;AACnE,UAAM,QAAQ,KAAK,YAAY,IAAI;AACnC,QAAI,CAAC,SAAS,MAAM,UAAU,EAAG,QAAO;AAAA,EAC1C;AACA,SAAO;AACT;AAEA,MAAM,uBAAiD,OAAO,aAAa,cACvE,SAAS,cAAc,QAAQ,IAC/B;AAGJ,SAAS,oBAAoB,YAAoB,QAAgB,UAAkB,MAA6B;AAC9G,QAAM,MAAM,6DAAsB,WAAW;AAC7C,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI,OAAO,iBAAiB,MAAM,IAAI,QAAQ,OAAO,UAAU;AAC/D,SAAO,IAAI,YAAY,IAAI,EAAE;AAC/B;AAEA,SAAS,eAAe,UAAsD;AAC5E,QAAM,2BAAW,IAAA;AACjB,QAAM,MAAgB,CAAA;AACtB,aAAW,UAAU,UAAU;AAC7B,UAAM,QAAQ,iCAAQ;AACtB,QAAI,CAAC,SAAS,KAAK,IAAI,KAAK,EAAG;AAC/B,SAAK,IAAI,KAAK;AACd,QAAI,KAAK,KAAK;AAAA,EAChB;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,MAAiD;;AACxE,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,QAAQ,KAAK;AACnB,QAAM,aAAa;AAAA,KACjB,WAAM,kBAAN,mBAAqB;AAAA,KACrB,WAAM,yBAAN,mBAA4B;AAAA,KAC5B,WAAM,aAAN,mBAAgB;AAAA,KAChB,WAAM,mBAAN,mBAAsB;AAAA,EAAA,EACtB,OAAO,OAAO,EAAE,KAAK,GAAG;AAC1B,SAAO,kBAAkB,KAAK,UAAU;AAC1C;AAEA,SAAS,yBAAyB,SAAiB,WAA2B;AAC5E,SAAO,aAAa,OAAO,IAAI,SAAS,0BAA0B,CAAC,OAAO,IAAI,CAAC,SAAS;AAC1F;AAGA,SAAS,YACP,WACA,QACA,WAAW,OACI;AACf,QAAM,WAAWA,SAAAA,kBAAkB,MAAM;AACzC,QAAM,aAAwD;AAAA,IAC5D,KAAK,CAAC,SAAS,SAAS;AAAA,IACxB,KAAK,CAAC,SAAS;AAAA,IACf,KAAK,CAAC,UAAU,SAAS;AAAA,IACzB,KAAK,CAAC,YAAY,MAAM;AAAA,IACxB,KAAK,CAAC,QAAQ,YAAY,SAAS;AAAA,EAAA;AAKrC,QAAM,YAAuD;AAAA,IAC3D,KAAK,CAAC,eAAe,UAAU,SAAS,SAAS;AAAA,IACjD,KAAK,CAAC,UAAU,SAAS;AAAA,IACzB,KAAK,CAAC,gBAAgB,UAAU,UAAU,SAAS;AAAA,IACnD,KAAK,CAAC,kBAAkB,cAAc,UAAU,YAAY,QAAQ,SAAS;AAAA,IAC7E,KAAK,CAAC,cAAc,kBAAkB,UAAU,QAAQ,YAAY,SAAS;AAAA,EAAA;AAE/E,QAAM,MAAM,WAAW,YAAY;AACnC,aAAW,OAAO,IAAI,QAAQ,KAAK,CAAC,SAAS,GAAG;AAC9C,QAAI,UAAU,GAAG,EAAG,QAAO,UAAU,GAAG;AAAA,EAC1C;AACA,SAAO,UAAU,WAAW;AAC9B;AAGA,SAAS,eAAe,UAAkB,aAA6B;AACrE,MAAI,gBAAgB,KAAK,QAAQ,KAAK,SAAS,WAAW,OAAO,EAAG,QAAO;AAC3E,MAAI,SAAS,WAAW,GAAG,GAAG;AAC5B,WAAO,OAAO,WAAW,cAAc,IAAI,IAAI,UAAU,OAAO,SAAS,MAAM,EAAE,SAAA,IAAa;AAAA,EAChG;AACA,QAAM,UAAU,YAAY,SAAS,GAAG,IAAI,cAAc,cAAc;AACxE,SAAO,IAAI,IAAI,UAAU,OAAO,EAAE,SAAA;AACpC;AAGA,eAAe,SAAS,YAAoB,QAAgB,aAAqB,WAAW,OAAsC;AAChI,QAAM,WAAW,GAAG,UAAU,KAAK,MAAM,KAAK,WAAW,MAAM,GAAG;AAClE,MAAI,UAAU,IAAI,QAAQ,EAAG,QAAO,UAAU,IAAI,QAAQ;AAE1D,QAAM,YAAYC,SAAAA,WAAW,UAAU;AAOvC,MAAI,WAAW;AACb,UAAM,WAAW,YAAY,WAAW,QAAQ,QAAQ;AACxD,QAAI,CAAC,SAAU,QAAO;AACtB,UAAM,MAAM,eAAe,UAAU,WAAW;AAChD,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,GAAG;AAChC,UAAI,CAAC,SAAS,IAAI;AAChB,gBAAQ,KAAK,uCAAuC,GAAG,KAAK,SAAS,MAAM,EAAE;AAC7E,eAAO;AAAA,MACT;AACA,YAAM,SAAS,MAAM,SAAS,YAAA;AAC9B,YAAM,QAAQ,IAAI,WAAW,MAAM;AACnC,qBAAe,IAAI,UAAU,KAAK;AAClC,YAAM,OAAOC,oBAAS,MAAM,MAAM;AAClC,gBAAU,IAAI,UAAU,IAAI;AAC5B,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,cAAQ,KAAK,4CAA4C,UAAU,KAAK,GAAG;AAC3E,aAAO;AAAA,IACT;AAAA,EACF;AAGA,MAAI;AACF,UAAM,iBAAiBF,SAAAA,kBAAkB,MAAM;AAG/C,QAAI,QAAQ,MAAMG,SAAAA,mBAAmB,YAAY,gBAAgB,QAAQ;AACzE,QAAI,CAAC,OAAO;AACV,cAAQ,MAAMC,SAAAA,sBAAsB,YAAY,gBAAgB,QAAQ;AAAA,IAC1E;AAGA,QAAI,CAAC,SAAS,UAAU;AACtB,cAAQ,MAAMD,SAAAA,mBAAmB,YAAY,gBAAgB,KAAK;AAClE,UAAI,CAAC,MAAO,SAAQ,MAAMC,SAAAA,sBAAsB,YAAY,gBAAgB,KAAK;AAAA,IACnF;AACA,QAAI,CAAC,OAAO;AACV,cAAQ,KAAK,uCAAuC,UAAU,KAAK,MAAM,gCAAgC;AACzG,aAAO;AAAA,IACT;AACA,mBAAe,IAAI,UAAU,KAAK;AAElC,UAAM,KAAK,MAAM,OAAO,MAAM,MAAM,YAAY,MAAM,aAAa,MAAM,UAAU;AACnF,UAAM,OAAOF,oBAAS,MAAM,EAAE;AAC9B,cAAU,IAAI,UAAU,IAAI;AAC5B,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,YAAQ,KAAK,6CAA6C,UAAU,KAAK,GAAG;AAC5E,WAAO;AAAA,EACT;AACF;AAGA,eAAe,aACb,YACA,QACA,aACA,WAAW,OAC8C;AACzD,QAAM,WAAW,GAAG,UAAU,KAAK,MAAM,KAAK,WAAW,MAAM,GAAG;AAClE,MAAI,CAAC,eAAe,IAAI,QAAQ,GAAG;AAEjC,UAAM,SAAS,YAAY,QAAQ,aAAa,QAAQ;AAAA,EAC1D;AACA,QAAM,QAAQ,eAAe,IAAI,QAAQ;AACzC,SAAO,QAAQ,EAAE,OAAO,SAAA,IAAa;AACvC;AAGA,SAAS,gBAAgB,MAAqB,MAAc,UAA0B;AACpF,MAAI;AACF,WAAO,KAAK,gBAAgB,MAAM,UAAU,EAAE,SAAS,MAAM;AAAA,EAC/D,QAAQ;AACN,QAAI;AAAE,aAAO,KAAK,gBAAgB,MAAM,QAAQ;AAAA,IAAG,QAAQ;AAAE,aAAO,KAAK,SAAS,WAAW;AAAA,IAAK;AAAA,EACpG;AACF;AAMA,SAAS,eAAe,MAA6D;AACnF,MAAI,CAAC,KAAM,QAAO,CAAA;AAClB,QAAM,OAAsD,CAAA;AAC5D,MAAI,MAAM;AACV,MAAI,UAAU,uBAAuB,KAAK,CAAC,CAAC;AAC5C,aAAW,MAAM,MAAM;AACrB,UAAM,SAAS,uBAAuB,EAAE;AAExC,UAAM,YAAY,KAAK,KAAK,EAAE;AAC9B,QAAI,WAAW,WAAW,WAAW;AACnC,aAAO;AAAA,IACT,OAAO;AACL,UAAI,UAAU,KAAK,EAAE,MAAM,KAAK,SAAS,SAAS;AAClD,YAAM;AACN,gBAAU;AAAA,IACZ;AAAA,EACF;AACA,MAAI,UAAU,KAAK,EAAE,MAAM,KAAK,SAAS,SAAS;AAClD,SAAO;AACT;AAMA,eAAe,eACb,KACA,QACA,aACA,cACA,UACA,WACA,GACA,GACA,UACuD;AAEvD,MAAI,UAAU,aAAa,UAAU;AACnC,QAAI;AACF,YAAM,SAAS,MAAM;AAAA,QACnB,UAAU;AAAA,QACV,UAAU;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,EAAE,QAAQ,QAAQ,UAAU,OAAO,WAAW,MAAA;AAAA,MAAM;AAEtD,UAAI,OAAO,UAAU;AACnB,cAAM,UAAU,gBAAgB,UAAU,KAAK,QAAQ;AACvD,eAAO,EAAE,UAAU,OAAO,UAAU,QAAA;AAAA,MACtC;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,KAAK,uCAAuC,GAAG,MAAM,GAAG;AAAA,IAClE;AAEA,QAAI;AACF,YAAM,OAAO,SAAS,QAAQ,KAAK,GAAG,GAAG,QAAQ;AACjD,YAAM,IAAI,KAAK,WAAW,CAAC;AAC3B,UAAI,KAAK,MAAM,OAAQ,QAAO,EAAE,UAAU,GAAG,SAAS,gBAAgB,UAAU,KAAK,QAAQ,EAAA;AAAA,IAC/F,QAAQ;AAAA,IAAa;AACrB,WAAO;AAAA,EACT;AAGA,MAAI;AACF,UAAM,OAAO,YAAY,QAAQ,KAAK,GAAG,GAAG,UAAU,EAAE,SAAS,MAAM;AACvE,UAAM,IAAI,KAAK,WAAW,CAAC;AAC3B,QAAI,KAAK,MAAM,OAAQ,QAAO,EAAE,UAAU,GAAG,SAAS,gBAAgB,aAAa,KAAK,QAAQ,EAAA;AAChG,WAAO,EAAE,UAAU,IAAI,SAAS,gBAAgB,aAAa,KAAK,QAAQ,EAAA;AAAA,EAC5E,SAAS,KAAK;AACZ,YAAQ,KAAK,+CAA+C,GAAG,MAAM,GAAG;AACxE,WAAO;AAAA,EACT;AACF;AAGA,SAAS,eAAe,WAAsD;AAC5E,MAAI,CAAC,UAAW,QAAO,EAAE,IAAI,GAAG,IAAI,EAAA;AACpC,QAAM,IAAI,UAAU,MAAM,8CAA8C;AACxE,MAAI,EAAG,QAAO,EAAE,IAAI,WAAW,EAAE,CAAC,CAAC,GAAG,IAAI,WAAW,EAAE,CAAC,CAAC,EAAA;AAEzD,QAAM,KAAK,UAAU,MAAM,2GAA2G;AACtI,MAAI,GAAI,QAAO,EAAE,IAAI,WAAW,GAAG,CAAC,CAAC,GAAG,IAAI,WAAW,GAAG,CAAC,CAAC,EAAA;AAC5D,SAAO,EAAE,IAAI,GAAG,IAAI,EAAA;AACtB;AAGA,SAAS,oBAAoB,MAAuB;AAClD,MAAI,CAAC,KAAM,QAAO;AAClB,aAAW,MAAM,MAAM;AACrB,UAAM,IAAI,GAAG,YAAY,CAAC,KAAK;AAE/B,QAAK,KAAK,QAAU,KAAK,QAAY,KAAK,SAAU,KAAK,SAAY,KAAK,QAAU,KAAK,KAAS,QAAO;AAEzG,QAAI,KAAK,QAAU,KAAK,KAAQ,QAAO;AAEvC,QAAI,KAAK,QAAU,KAAK,KAAQ,QAAO;AACvC,QAAI,KAAK,QAAU,KAAK,KAAQ,QAAO;AACvC,QAAI,KAAK,QAAU,KAAK,KAAQ,QAAO;AAEvC,QAAI,KAAK,QAAU,KAAK,KAAQ,QAAO;AACvC,QAAI,KAAK,QAAU,KAAK,KAAQ,QAAO;AAEvC,QAAI,KAAK,SAAU,KAAK,MAAQ,QAAO;AACvC,QAAI,KAAK,SAAU,KAAK,MAAQ,QAAO;AAEvC,QAAI,KAAK,OAAS,QAAO;AAIzB,QAAI,KAAK,QAAU,KAAK,KAAQ,QAAO;AACvC,QAAI,KAAK,QAAU,KAAK,KAAQ,QAAO;AACvC,QAAI,KAAK,QAAU,KAAK,KAAQ,QAAO;AACvC,QAAI,KAAK,SAAU,KAAK,MAAQ,QAAO;AACvC,QAAI,KAAK,SAAU,KAAK,MAAQ,QAAO;AACvC,QAAI,KAAK,SAAU,KAAK,MAAQ,QAAO;AACvC,QAAI,KAAK,SAAU,KAAK,MAAQ,QAAO;AAAA,EACzC;AACA,SAAO;AACT;AAiBA,MAAM,kDAAkC,IAAY;AAAA,EAClD;AAAA,EAAc;AAAA,EACd;AAAA,EAAU;AAAA,EAAoB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EAAa;AAAA,EACb;AAAA,EAAgB;AAAA,EAChB;AAAA,EAAa;AAAA,EAAqB;AAAA,EAClC;AAAA,EAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAAoB;AAAA,EACpB;AAAA,EAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EAAe;AAAA,EACf;AACF,CAAC;AAED,SAAS,mBAAmB,QAA4C;AACtE,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,IAAI,OAAO,QAAQ,SAAS,EAAE,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAA,EAAO,YAAA;AAClE,MAAI,4BAA4B,IAAI,CAAC,EAAG,QAAO;AAE/C,SAAO,4BAA4B,IAAI,EAAE,QAAQ,QAAQ,EAAE,CAAC;AAC9D;AAMA,SAAS,uBAAuB,QAAgC;;AAC9D,QAAM,iBAAiB,CAAC,OAAe,SAAgC;;AACrE,UAAM,IAAI,MAAM,MAAM,IAAI,OAAO,GAAG,IAAI,oBAAoB,GAAG,CAAC;AAChE,aAAOG,MAAA,uBAAI,OAAJ,gBAAAA,IAAQ,WAAU;AAAA,EAC3B;AACA,MAAI,UAA0B;AAC9B,SAAO,SAAS;AACd,UAAM,KAAI,aAAQ,aAAa,aAAa,MAAlC,mBAAqC;AAC/C,QAAI,EAAG,UAAO,OAAE,MAAM,GAAG,EAAE,CAAC,MAAd,mBAAiB,QAAQ,SAAS,IAAI,WAAU;AAC9D,UAAM,IAAI,eAAe,QAAQ,aAAa,OAAO,KAAK,IAAI,aAAa;AAC3E,QAAI,EAAG,UAAO,OAAE,MAAM,GAAG,EAAE,CAAC,MAAd,mBAAiB,QAAQ,SAAS,IAAI,WAAU;AAC9D,cAAU,QAAQ;AAAA,EACpB;AACA,SAAO;AACT;AAoCA,SAAS,oBAAoB,QAA0B;AACrD,QAAM,YAAY,CAAC,QACjB,CAAC,CAAC,OAAO,qBAAqB,KAAK,GAAG;AACxC,QAAM,WAAW,CAAC,OAAwB;AACxC,UAAM,OAAO,GAAG,aAAa,MAAM,KAAK;AACxC,QAAI,KAAM,QAAO;AACjB,UAAM,QAAQ,GAAG,aAAa,OAAO,KAAK;AAC1C,UAAM,IAAI,MAAM,MAAM,+BAA+B;AACrD,WAAO,IAAI,EAAE,CAAC,EAAE,SAAS;AAAA,EAC3B;AACA,MAAI,UAAU,SAAS,MAAM,CAAC,EAAG,QAAO;AACxC,QAAM,SAAS,OAAO,iBAAiB,OAAO;AAC9C,aAAW,MAAM,QAAQ;AACvB,QAAI,UAAU,SAAS,EAAE,CAAC,EAAG,QAAO;AAAA,EACtC;AACA,SAAO;AACT;AAYO,SAAS,sCAAsC,QAAwB;AAC5E,MAAI,CAAC,UAAU,CAAC,qBAAqB,KAAK,MAAM,EAAG,QAAO;AAC1D,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,UAAA,EAAY,gBAAgB,QAAQ,eAAe;AAAA,EAC/D,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,CAAC,WAAmC;AACvD,UAAM,OAAO,OAAO,cAAc,MAAM;AACxC,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,OAAO,KAAK,aAAa,YAAY,KAAK;AAChD,QAAI,KAAM,QAAO;AACjB,UAAM,QAAQ,KAAK,aAAa,OAAO,KAAK;AAC5C,UAAM,IAAI,MAAM,MAAM,qCAAqC;AAC3D,WAAO,IAAI,EAAE,CAAC,EAAE,SAAS;AAAA,EAC3B;AACA,QAAM,8BAAc,IAAA;AACpB,MAAI,iBAAiB,gCAAgC,EAAE,QAAQ,CAAC,MAAM;AACpE,UAAM,KAAK,EAAE,aAAa,IAAI;AAC9B,QAAI,CAAC,GAAI;AAET,QAAI,MAAsB;AAC1B,QAAI,OAAO;AACX,WAAO,OAAO,IAAI,iBAAiB,MAAM,EAAE,WAAW,KAAK,OAAO,GAAG;AACnE,YAAM,OAAO,IAAI,aAAa,YAAY,KAAK,IAAI,aAAa,MAAM;AACtE,UAAI,CAAC,QAAQ,CAAC,KAAK,WAAW,GAAG,EAAG;AACpC,YAAM,OAAO,IAAI,eAAe,KAAK,MAAM,CAAC,CAAC;AAC7C,UAAI,CAAC,KAAM;AACX,YAAM;AACN;AAAA,IACF;AACA,UAAM,QAAQ,MAAM,aAAa,GAAG,IAAI;AACxC,QAAI,MAAO,SAAQ,IAAI,IAAI,KAAK;AAAA,EAClC,CAAC;AACD,MAAI,QAAQ,SAAS,EAAG,QAAO;AAE/B,QAAM,QAAQ;AACd,QAAM,kBAAkB,CAAC,OAAgB;AACvC,UAAM,OAAO,GAAG,aAAa,MAAM;AACnC,QAAI,MAAM;AACR,YAAM,IAAI,KAAK,MAAM,KAAK;AAC1B,UAAI,GAAG;AACL,cAAM,QAAQ,QAAQ,IAAI,EAAE,CAAC,CAAC;AAC9B,YAAI,MAAO,IAAG,aAAa,QAAQ,KAAK;AAAA,MAC1C;AAAA,IACF;AACA,UAAM,QAAQ,GAAG,aAAa,OAAO;AACrC,QAAI,SAAS,MAAM,KAAK,KAAK,GAAG;AAC9B,YAAM,WAAW,MAAM,QAAQ,uCAAuC,CAAC,OAAO,QAAQ;AACpF,cAAM,IAAI,MAAM,MAAM,KAAK;AAC3B,cAAM,QAAQ,IAAI,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI;AACtC,eAAO,QAAQ,GAAG,GAAG,QAAQ,KAAK,KAAK;AAAA,MACzC,CAAC;AACD,UAAI,aAAa,MAAO,IAAG,aAAa,SAAS,QAAQ;AAAA,IAC3D;AAAA,EACF;AACA,MAAI,iBAAiB,aAAa,EAAE,QAAQ,eAAe;AAC3D,SAAO,IAAI,cAAA,EAAgB,kBAAkB,IAAI,eAAe;AAClE;AAWA,eAAsB,yBACpB,QACA,WAAwB,cACxB,aACiB;AACjB,MAAI,aAAa,iBAAiB;AAChC,WAAO,4BAA4B,QAAQ,aAAa,EAAE,MAAM,OAAO;AAAA,EACzE;AAEA,MAAI,aAAa,QAAQ;AACvB,WAAO,4BAA4B,QAAQ,aAAa,EAAE,MAAM,iBAAiB;AAAA,EACnF;AAEA,SAAO,sCAAsC,MAAM;AACrD;AAgBA,eAAsB,4BACpB,QACA,aACA,UAA8B,CAAA,GACb;;AACjB,QAAM,UAAU,gBAAgB,OAAO,WAAW,cAAc,OAAO,SAAS,SAAS,YAAY;AACrG,QAAM,OAAO,QAAQ,QAAQ;AAE7B,QAAM,SAAS,IAAI,UAAA;AACnB,QAAM,MAAM,OAAO,gBAAgB,QAAQ,eAAe;AAG1D,QAAM,UAAU,IAAI,iBAAiB,MAAM;AAC3C,MAAI,iBAAiB;AACrB,MAAI,eAAe;AAEnB,aAAW,UAAU,SAAS;AAE5B,UAAM,WAAW,OAAO,eAAe;AACvC,QAAI,CAAC,SAAS,OAAQ;AAMtB,QAAI,SAAS,gBAAgB;AAC3B,YAAM,iBACJ,CAAC,CAAC,OAAO,WAAW,CAAC,CAAE,OAAe,QAAQ,oBAAoB;AACpE,UAAI,kBAAkB;AACtB,UAAI,MAAsB,OAAO;AACjC,aAAO,KAAK;AACV,YAAI,IAAI,QAAQ,YAAA,MAAkB,QAAQ,IAAI,aAAa,qBAAqB,KAAK,IAAI,aAAa,iBAAiB,IAAI;AACzH,4BAAkB;AAClB;AAAA,QACF;AACA,cAAM,IAAI;AAAA,MACZ;AACA,UAAI,CAAC,kBAAkB,CAAC,iBAAiB;AACvC;AACA;AAAA,MACF;AAAA,IACF;AAGA,UAAM,sBAAsB,MAAe;AACzC,YAAM,YAAY,OAAO,iBAAiB,OAAO;AACjD,YAAM,WAAW,CAAC,IAAa,SAAyB;AACtD,cAAM,QAAQ,GAAG,aAAa,IAAI,KAAK,IAAI,KAAA;AAC3C,YAAI,KAAM,QAAO;AACjB,cAAM,QAAQ,GAAG,aAAa,OAAO,KAAK;AAC1C,cAAM,IAAI,MAAM,MAAM,IAAI,OAAO,cAAc,IAAI,oBAAoB,GAAG,CAAC;AAC3E,eAAO,IAAI,EAAE,CAAC,EAAE,SAAS;AAAA,MAC3B;AACA,YAAM,aAAa,SAAS,QAAQ,aAAa;AACjD,YAAM,YAAY,SAAS,QAAQ,YAAY;AAC/C,YAAM,WAAW,SAAS,QAAQ,MAAM;AACxC,YAAM,WAAW,SAAS,QAAQ,iBAAiB;AACnD,iBAAW,MAAM,WAAW;AAC1B,cAAM,KAAK,SAAS,IAAI,aAAa;AACrC,cAAM,MAAM,SAAS,IAAI,YAAY;AACrC,cAAM,QAAQ,SAAS,IAAI,MAAM;AACjC,cAAM,QAAQ,SAAS,IAAI,iBAAiB;AAC5C,cAAM,OAAO,GAAG,aAAa,OAAO,KAAK,IAAI,MAAM,6BAA6B;AAChF,YACG,MAAM,OAAO,cACb,OAAO,QAAQ,aACf,SAAS,UAAU,YACnB,SAAS,UAAU,YACpB,KACA;AACA,iBAAO;AAAA,QACT;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAMA,QAAI,SAAS,iBAAiB;AAC5B,UAAI,CAAC,oBAAoB,MAAM,GAAG;AAChC;AACA;AAAA,MACF;AAAA,IACF;AASA,QAAI,SAAS,gBAAgB;AAC3B,YAAM,iBAAiB,uBAAuB,MAAM;AACpD,YAAM,aAAa,mBAAmB,cAAc;AAMpD,YAAM,kBAAkB,oBAAoB,MAAM;AAClD,UACE,CAAC,oBAAoB,QAAQ,KAC7B,CAAC,yBACD,CAAC,cACD,CAAC,iBACD;AACA;AACA;AAAA,MACF;AAAA,IACF;AAOA,QAAI,SAAS,sBAAsB,CAAC,uBAAuB;AACzD;AACA;AAAA,IACF;AAGA,UAAM,iBAAiB,CAAC,OAAe,SAAgC;;AACrE,YAAM,IAAI,MAAM,MAAM,IAAI,OAAO,GAAG,IAAI,oBAAoB,GAAG,CAAC;AAChE,eAAOA,MAAA,uBAAI,OAAJ,gBAAAA,IAAQ,WAAU;AAAA,IAC3B;AAEA,UAAM,iBAAiB,CAAC,IAAa,SAAgC;;AACnE,UAAI,UAA0B;AAC9B,aAAO,SAAS;AACd,cAAM,WAAUA,MAAA,QAAQ,aAAa,IAAI,MAAzB,gBAAAA,IAA4B;AAC5C,YAAI,QAAS,QAAO;AACpB,cAAM,WAAW,eAAe,QAAQ,aAAa,OAAO,KAAK,IAAI,IAAI;AACzE,YAAI,SAAU,QAAO;AACrB,kBAAU,QAAQ;AAAA,MACpB;AACA,aAAO;AAAA,IACT;AAEA,UAAM,cAAa,0BAAe,QAAQ,aAAa,MAApC,mBACf,MAAM,KAAK,OADI,mBAEf,QAAQ,SAAS,IAClB;AAEH,UAAM,cAAc,eAAe,QAAQ,WAAW,KAAK;AAC3D,UAAM,WAAW,WAAW,WAAW;AAEvC,UAAM,gBAAgB,eAAe,QAAQ,aAAa,KAAK;AAC/D,UAAM,aAAa,OAAO,SAAS,eAAe,EAAE,KAAK;AAEzD,UAAM,YAAY,eAAe,QAAQ,MAAM,KAAK;AACpD,UAAM,cAAc,eAAe,QAAQ,cAAc,KAAK;AAE9D,QAAI,CAAC,YAAY;AACf;AACA;AAAA,IACF;AAGA,UAAM,cAAc,MAAM,SAAS,YAAY,YAAY,OAAO;AAClE,UAAM,eAAe,MAAM,aAAa,YAAY,YAAY,OAAO;AAQvE,UAAM,UAAU,mBAAmB,QAAQ;AAC3C,UAAM,YAAY,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,SAAS,uBAAuB,IAAI,MAAM,QAAQ;AACxF,UAAM,UAAU,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,SAAS,uBAAuB,IAAI,MAAM,MAAM;AACpF,UAAM,wBAAwB,UAC1B,eAAe,CAAC,YAAYC,SAAAA,0BAA0BC,kCAAyB,UAAU,CAAC,CAAC,IAC3F,CAAA;AAEJ,UAAM,uCAAuB,IAAA;AAC7B,UAAM,wBAAwB,CAAC,SAAiB,gBAAyD;AACvG,YAAM,WAAW,GAAG,OAAO,KAAK,WAAW;AAC3C,YAAM,SAAS,iBAAiB,IAAI,QAAQ;AAC5C,UAAI,OAAQ,QAAO;AAEnB,YAAM,WAAW,YAAY;AAC3B,cAAM,eAAe,oBAAoB,YAAY,YAAY,aAAa,OAAO;AACrF,YAAI,OAAoD;AAExD,mBAAW,UAAU,uBAAuB;AAC1C,gBAAM,OAAO,WAAW,aAAa,cAAc,MAAM,SAAS,QAAQ,YAAY,OAAO;AAC7F,gBAAM,QAAQ,WAAW,aAAa,eAAe,MAAM,aAAa,QAAQ,YAAY,OAAO;AACnG,cAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,gBAAgB,MAAM,SAAS,YAAY,EAAG;AAEtE,gBAAM,QAAQ,gBAAgB,MAAM,SAAS,WAAW;AACxD,gBAAM,OAAO,gBAAgB,OAAO,IAAI,KAAK,IAAI,QAAQ,YAAY;AACrE,cAAI,CAAC,QAAQ,OAAO,KAAK,aAAa,EAAE,QAAQ,MAAM,OAAO,KAAA;AAG7D,cAAI,gBAAgB,QAAQ,QAAQ,KAAM;AAAA,QAC5C;AAEA,YAAI,MAAM;AACR,kBAAQ,IAAI,2CAA2C;AAAA,YACrD,iBAAiB;AAAA,YACjB,gBAAgB,KAAK;AAAA,YACrB;AAAA,YACA,MAAM,KAAK,MAAM,KAAK,OAAO,GAAG,IAAI;AAAA,UAAA,CACrC;AAAA,QACH;AACA,eAAO;AAAA,MACT,GAAA;AAEA,uBAAiB,IAAI,UAAU,OAAO;AACtC,aAAO;AAAA,IACT;AAEA,UAAM,uBAA+D,aAChE,YAAY;AACX,YAAM,aAAa,MAAM,SAASC,SAAAA,uBAAuB,KAAK,OAAO;AACrE,YAAM,cAAc,MAAM,aAAaA,SAAAA,uBAAuB,KAAK,OAAO;AAC1E,aAAO,cAAc,cAAc,EAAE,QAAQA,SAAAA,uBAAuB,MAAM,YAAY,OAAO,YAAA,IAAgB;AAAA,IAC/G,OACA;AACJ,UAAM,qBAA6D,WAC9D,YAAY;AACX,YAAM,WAAW,MAAM,SAASC,SAAAA,oBAAoB,KAAK,OAAO;AAChE,YAAM,YAAY,MAAM,aAAaA,SAAAA,oBAAoB,KAAK,OAAO;AACrE,aAAO,YAAY,YAAY,EAAE,QAAQA,SAAAA,oBAAoB,MAAM,UAAU,OAAO,UAAA,IAAc;AAAA,IACpG,OACA;AAKJ,QAAI,CAAC,eAAe,CAAC,WAAW,CAAC,aAAa,CAAC,SAAS;AACtD,cAAQ,KAAK,yCAAyC,UAAU,sBAAsB;AACtF;AACA;AAAA,IACF;AAIA,UAAM,iBAAiB,eAAe,QAAQ,aAAa,KAAK,SAAS,YAAA;AACzE,UAAM,iBACJ,kBAAkB,WAAW,WAAW,kBAAkB,QAAQ,QAAQ;AAG5E,UAAM,SAAS,OAAO,iBAAiB,OAAO;AAC9C,UAAM,oBAAoB,OAAO,SAAS,IAAI,MAAM,KAAK,MAAM,IAAI,CAAC,MAAM;AAG1E,UAAM,QAAQ,IAAI,gBAAgB,8BAA8B,GAAG;AAGnE,UAAM,gBAAgB,OAAO,aAAa,WAAW;AACrD,QAAI,cAAe,OAAM,aAAa,aAAa,aAAa;AAGhE,UAAM,QAAQ,WAAW,OAAO,aAAa,GAAG,KAAK,GAAG;AACxD,UAAM,QAAQ,WAAW,OAAO,aAAa,GAAG,KAAK,GAAG;AAExD,eAAW,QAAQ,mBAAmB;AACpC,YAAM,OAAO,KAAK,eAAe;AACjC,UAAI,CAAC,KAAK,OAAQ;AAGlB,YAAM,QAAQ,WAAW,KAAK,aAAa,GAAG,KAAK,OAAO,KAAK,CAAC;AAChE,YAAM,QAAQ,WAAW,KAAK,aAAa,GAAG,KAAK,OAAO,KAAK,CAAC;AAChE,YAAM,iBAAiB,eAAe,MAAM,aAAa,KAAK,gBAAgB,YAAA;AAC9E,YAAM,iBACJ,kBAAkB,WAAW,WAAW,kBAAkB,QAAQ,QAAQ;AAG5E,YAAM,gBAAgB,SAAS,SAAS,eAAe,KAAK,aAAa,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAA;AACtG,UAAI,IAAI,QAAQ,cAAc;AAC9B,YAAM,IAAI,QAAQ,cAAc;AAGhC,YAAM,WAAW,eAAe,MAAM,MAAM,KAAK;AACjD,YAAM,kBAAkB,eAAe,MAAM,cAAc,KAAK;AAGhE,YAAM,kBAAkB,eAAe,MAAM,WAAW,KAAK,OAAO,QAAQ;AAC5E,YAAM,eAAe,WAAW,eAAe;AAM/C,YAAM,gBAAgB,eAAe,MAAM,aAAa,KAAK,OAAO,UAAU;AAC9E,YAAM,aAAa,OAAO,SAAS,eAAe,EAAE,MAAM,QAAQ,KAAK,aAAa,IAAI,MAAM;AAC9F,YAAM,gBAAgB,eAAe,MAAM,YAAY,KAAK,UAAU,YAAA;AACtE,YAAM,aAAa,kBAAkB,KAAK,YAAY;AACtD,YAAM,gBAAgB,eAAe,cAAc,CAAC;AACpD,YAAM,WAAW,gBACb,cACA,MAAM,SAAS,YAAY,YAAY,SAAS,UAAU;AAC9D,YAAM,YAAY,gBACd,eACA,MAAM,aAAa,YAAY,YAAY,SAAS,UAAU;AAGlE,YAAM,cAAc,YAAY;AAChC,YAAM,eAAe,aAAa;AAGlC,YAAM,OAAO,eAAe,IAAI;AAKhC,UAAI,eAAe;AACnB,UAAI,gBAAgB;AACpB,iBAAW,KAAK,MAAM;AACpB,YAAI,EAAE,YAAY,cAAc;AAC9B,gBAAM,WAAW,MAAM,sBAAsB,EAAE,MAAM,YAAY;AACjE,cAAI,SAAU,iBAAgB,gBAAgB,SAAS,MAAM,EAAE,MAAM,YAAY;AAAA,cAC5E,iBAAgB;AAAA,QACvB,WAAW,EAAE,YAAY,UAAU;AACjC,gBAAM,WAAW,MAAM;AACvB,cAAI,YAAY,gBAAgB,SAAS,MAAM,EAAE,MAAM,QAAQ,EAAG,iBAAgB,gBAAgB,SAAS,MAAM,EAAE,MAAM,YAAY;AAAA,cAChI,iBAAgB;AAAA,QACvB,WAAW,EAAE,YAAY,QAAQ;AAC/B,gBAAM,WAAW,MAAM;AACvB,cAAI,YAAY,gBAAgB,SAAS,MAAM,EAAE,MAAM,MAAM,EAAG,iBAAgB,gBAAgB,SAAS,MAAM,EAAE,MAAM,YAAY;AAAA,cAC9H,iBAAgB;AAAA,QACvB,OAAO;AACL,cAAI,YAAa,iBAAgB,gBAAgB,aAAa,EAAE,MAAM,YAAY;AAAA,cAC7E,iBAAgB;AAAA,QACvB;AAAA,MACF;AAEA,UAAI,eAAe;AACjB,YAAI,mBAAmB,SAAU,MAAK,eAAe;AAAA,iBAC5C,mBAAmB,MAAO,MAAK;AAAA,MAC1C;AAGA,UAAI,SAAS;AACb,UAAI,gBAAgB;AACpB,iBAAW,KAAK,MAAM;AAEpB,cAAM,eAAe,EAAE,YAAY,eAAe,MAAM,sBAAsB,EAAE,MAAM,YAAY,IAAI;AACtG,cAAM,iBAAiB,EAAE,YAAY,WAAW,MAAM,uBAAuB;AAC7E,cAAM,eAAe,EAAE,YAAY,SAAS,MAAM,qBAAqB;AACvE,cAAM,UAAU,CAAC,CAAC;AAClB,cAAM,cAAa,6CAAc,UAAQ,iDAAgB,UAAQ,6CAAc,SAAQ;AACvF,cAAM,eAAc,6CAAc,WAAS,iDAAgB,WAAS,6CAAc,UAAS;AAC3F,YAAI,CAAC,WAAY;AAEjB,cAAM,SAAS,MAAM;AAAA,UACnB,EAAE;AAAA,UACF,CAAC,CAAC;AAAA,UACF;AAAA,UACA;AAAA,WACA,6CAAc,SAAQ;AAAA,WACtB,6CAAc,UAAS;AAAA,UACvB;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAGF,YAAI,UAAU,OAAO,UAAU;AAC7B,gBAAM,SAAS,IAAI,gBAAgB,8BAA8B,MAAM;AACvE,iBAAO,aAAa,KAAK,OAAO,QAAQ;AACxC,iBAAO,aAAa,QAAQ,QAAQ;AACpC,cAAI,cAAc,CAAC,gBAAgB,UAAU,GAAG;AAG9C,mBAAO,aAAa,aAAa,yBAAyB,QAAQ,CAAC,CAAC;AAAA,UACtE;AACA,cAAI,oBAAoB,KAAK;AAC3B,mBAAO,aAAa,gBAAgB,eAAe;AAAA,UACrD;AACA,gBAAM,YAAY,MAAM;AACxB,0BAAgB;AAAA,QAClB;AACA,YAAI,kBAAkB,OAAO;AAAA,MAC/B;AAEA,UAAI,eAAe;AACjB;AAAA,MACF,OAAO;AAEL,YAAI,SAAS,QAAQ;AACnB,gBAAM,QAAQ,KAAK,UAAU,IAAI;AACjC,cAAI,cAAe,OAAM,gBAAgB,WAAW;AACpD,gBAAM,YAAY,KAAK;AAAA,QACzB,OAAO;AACL,gBAAM,UAAU,IAAI,gBAAgB,8BAA8B,MAAM;AACxE,kBAAQ,aAAa,KAAK,OAAO,KAAK,CAAC;AACvC,kBAAQ,aAAa,KAAK,OAAO,KAAK,CAAC;AACvC,cAAI,KAAK,aAAa,OAAO,EAAG,SAAQ,aAAa,SAAS,KAAK,aAAa,OAAO,CAAE;AACzF,cAAI,KAAK,aAAa,aAAa,EAAG,SAAQ,aAAa,eAAe,KAAK,aAAa,aAAa,CAAE;AAC3G,cAAI,KAAK,aAAa,WAAW,EAAG,SAAQ,aAAa,aAAa,KAAK,aAAa,WAAW,CAAE;AACrG,cAAI,KAAK,aAAa,aAAa,EAAG,SAAQ,aAAa,eAAe,KAAK,aAAa,aAAa,CAAE;AAC3G,kBAAQ,cAAc;AACtB,gBAAM,YAAY,OAAO;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AAGA,QAAI,MAAM,WAAW,SAAS,GAAG;AAC/B,mBAAO,eAAP,mBAAmB,aAAa,OAAO;AAAA,IACzC;AAAA,EACF;AAEA,UAAQ;AAAA,IACN,wDAAwD,cAAc,YAAY,YAAY;AAAA,EAAA;AAGhG,SAAO,IAAI,cAAA,EAAgB,kBAAkB,IAAI,eAAe;AAClE;AAMO,MAAM,uBAAuB;AAMpC,eAAsB,sBAAsB,aAAqC;AAC/E,QAAM,UAAU,gBAAgB,OAAO,WAAW,cAAc,OAAO,SAAS,SAAS,YAAY;AACrG,aAAW,UAAU,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG,GAAG;AAC9C,UAAM,SAASH,SAAAA,0BAA0B,QAAQ,OAAO;AAAA,EAC1D;AACF;;;;;;"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsPDF, ShadingPattern } from "jspdf";
|
|
2
2
|
import { svg2pdf } from "svg2pdf.js";
|
|
3
3
|
import * as fabric from "fabric";
|
|
4
|
-
import { g as getCanvasForPage, c as captureFabricCanvasSvgForPdf, f as findNodeById, a as getAbsoluteBounds, p as parseTextMarkdown, r as renderSmartElementToSvg, n as normalizeShapeType, h as hasEdgeFade, b as getProxiedImageUrl, d as bakeEdgeFade, i as isElement, e as isGroup, j as buildRoundedTrianglePath, A as API_URL, k as getImageProxyFetchOptions, l as getRoundedRectRadii, T as TRIANGLE_STROKE_MITER_LIMIT, m as getTrianglePoints } from "./index-
|
|
4
|
+
import { g as getCanvasForPage, c as captureFabricCanvasSvgForPdf, f as findNodeById, a as getAbsoluteBounds, p as parseTextMarkdown, r as renderSmartElementToSvg, n as normalizeShapeType, h as hasEdgeFade, b as getProxiedImageUrl, d as bakeEdgeFade, i as isElement, e as isGroup, j as buildRoundedTrianglePath, A as API_URL, k as getImageProxyFetchOptions, l as getRoundedRectRadii, T as TRIANGLE_STROKE_MITER_LIMIT, m as getTrianglePoints } from "./index-C03-L2j3.js";
|
|
5
5
|
import { resetPdfFontRegistry, FONT_FALLBACK_SYMBOLS, FONT_FALLBACK_MATH, FONT_FALLBACK_DEVANAGARI, embedFontWithGoogleFallback, getEmbeddedVariantsList, isFontAvailable, isFamilyEmbedded, resolveBestRegisteredVariant, getEmbeddedJsPDFFontName, resolveFontWeight, doesVariantSupportChar } from "./pdfFonts-DhEaMTZl.js";
|
|
6
6
|
async function embedFontsForSvg(pdf, svgStr) {
|
|
7
7
|
var _a;
|
|
@@ -2500,7 +2500,7 @@ async function fetchSvgAsElement(imageUrl, colorMap) {
|
|
|
2500
2500
|
async function getRecoloredSvgDataUrl(imageUrl, colorMap) {
|
|
2501
2501
|
if (!colorMap || Object.keys(colorMap).length === 0) return null;
|
|
2502
2502
|
try {
|
|
2503
|
-
const { getNormalizedSvgUrl } = await import("./index-
|
|
2503
|
+
const { getNormalizedSvgUrl } = await import("./index-C03-L2j3.js").then((n) => n.a2);
|
|
2504
2504
|
return await getNormalizedSvgUrl(imageUrl, colorMap);
|
|
2505
2505
|
} catch {
|
|
2506
2506
|
return null;
|
|
@@ -3291,7 +3291,7 @@ async function fetchImageAsBase64(imageUrl, opts = {}) {
|
|
|
3291
3291
|
}
|
|
3292
3292
|
let fetchUrl = imageUrl;
|
|
3293
3293
|
if (imageUrl.startsWith("http://") || imageUrl.startsWith("https://")) {
|
|
3294
|
-
const { isPrivateUrl } = await import("./index-
|
|
3294
|
+
const { isPrivateUrl } = await import("./index-C03-L2j3.js").then((n) => n.a2);
|
|
3295
3295
|
if (isPrivateUrl(imageUrl)) return null;
|
|
3296
3296
|
const proxyUrl = new URL(`${API_URL}/image-proxy`);
|
|
3297
3297
|
proxyUrl.searchParams.set("url", imageUrl);
|
|
@@ -5059,20 +5059,12 @@ async function exportMultiPagePdf(pages, options) {
|
|
|
5059
5059
|
}
|
|
5060
5060
|
let preppedSvgString = liveSvgString;
|
|
5061
5061
|
try {
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
preppedSvgString = await convertDevanagariTextToPath(
|
|
5065
|
-
liveSvgString,
|
|
5066
|
-
void 0,
|
|
5067
|
-
{ mode: "gradient-only" }
|
|
5068
|
-
);
|
|
5069
|
-
} else {
|
|
5070
|
-
const { replaceGradientTextFillsWithFirstStop } = await import("./svgTextToPath-ra4EhtBL.js");
|
|
5071
|
-
preppedSvgString = replaceGradientTextFillsWithFirstStop(liveSvgString);
|
|
5072
|
-
}
|
|
5062
|
+
const { prepareSvgTextForPdfMode } = await import("./svgTextToPath-BoT6H7Lz.js");
|
|
5063
|
+
preppedSvgString = await prepareSvgTextForPdfMode(liveSvgString, pdfTextMode);
|
|
5073
5064
|
console.log("[client-pdf-export] gradient text mode", {
|
|
5074
5065
|
pdfTextMode,
|
|
5075
|
-
|
|
5066
|
+
outlinedAllText: pdfTextMode === "pixel-perfect",
|
|
5067
|
+
outlinedGradientText: pdfTextMode === "auto"
|
|
5076
5068
|
});
|
|
5077
5069
|
} catch (gradErr) {
|
|
5078
5070
|
console.warn("[client-pdf-export] gradient-text fill resolution failed", gradErr);
|
|
@@ -5295,4 +5287,4 @@ export {
|
|
|
5295
5287
|
preparePagesForExport,
|
|
5296
5288
|
rewriteSvgFontsForJsPDFWithSourceMeta
|
|
5297
5289
|
};
|
|
5298
|
-
//# sourceMappingURL=vectorPdfExport-
|
|
5290
|
+
//# sourceMappingURL=vectorPdfExport-CA6a-apV.js.map
|