@pixldocs/canvas-renderer 0.5.108 → 0.5.110

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.
@@ -67,6 +67,7 @@ function resolveFontWeight(weight) {
67
67
  return 700;
68
68
  }
69
69
  const FONT_FALLBACK_SYMBOLS = "Noto Sans";
70
+ const FONT_FALLBACK_MATH = "Noto Sans Math";
70
71
  const FONT_FALLBACK_DEVANAGARI = "Hind";
71
72
  const FONT_FILES = {
72
73
  "Playfair Display": {
@@ -540,6 +541,14 @@ const FONT_FILES = {
540
541
  light: "/fonts/Hind-Light.ttf",
541
542
  medium: "/fonts/Hind-Medium.ttf",
542
543
  semibold: "/fonts/Hind-SemiBold.ttf"
544
+ },
545
+ // ── Math / Operator Symbol Fallback ──
546
+ // Carries glyphs that NotoSans-Regular's Latin subset is missing
547
+ // (≠ ≤ ≥ ≈ ∞ → ← × ÷ ∑ √ ∈ ∀ ∃ etc.). Used as a tertiary fallback
548
+ // ONLY for chars classified as 'math' that the main font + Noto Sans
549
+ // both lack.
550
+ "Noto Sans Math": {
551
+ regular: "/fonts/NotoSansMath-Regular.ttf"
543
552
  }
544
553
  };
545
554
  function isJsPdfEmbeddableTrueType(bytes) {
@@ -876,9 +885,21 @@ function isBasicLatinOrLatinExtended(char) {
876
885
  const c = char.codePointAt(0) ?? 0;
877
886
  return c <= 591;
878
887
  }
888
+ function isMathOperatorChar(char) {
889
+ const c = char.codePointAt(0) ?? 0;
890
+ if (c >= 8592 && c <= 8703) return true;
891
+ if (c >= 8704 && c <= 8959) return true;
892
+ if (c >= 8960 && c <= 9215) return true;
893
+ if (c >= 10176 && c <= 10223) return true;
894
+ if (c >= 10624 && c <= 10751) return true;
895
+ if (c >= 10752 && c <= 11007) return true;
896
+ if (c >= 11008 && c <= 11097) return true;
897
+ return false;
898
+ }
879
899
  function classifyCharForFontRun(char) {
880
900
  if (isBasicLatinOrLatinExtended(char)) return "main";
881
901
  if (isDevanagari(char)) return "devanagari";
902
+ if (isMathOperatorChar(char)) return "math";
882
903
  return "symbol";
883
904
  }
884
905
  function isIgnorableForCoverage(char) {
@@ -890,6 +911,7 @@ function fontSupportsRun(font, text, runType) {
890
911
  if (isIgnorableForCoverage(char)) continue;
891
912
  if (runType === "devanagari" && !isDevanagari(char)) continue;
892
913
  if (runType === "symbol" && classifyCharForFontRun(char) !== "symbol") continue;
914
+ if (runType === "math" && classifyCharForFontRun(char) !== "math") continue;
893
915
  const glyph = font.charToGlyph(char);
894
916
  if (!glyph || glyph.index === 0) return false;
895
917
  }
@@ -1104,6 +1126,13 @@ function needsComplexShaping(text) {
1104
1126
  if (c >= 12288 && c <= 40959) return true;
1105
1127
  if (c >= 44032 && c <= 55215) return true;
1106
1128
  if (c >= 126976) return true;
1129
+ if (c >= 8592 && c <= 8703) return true;
1130
+ if (c >= 8704 && c <= 8959) return true;
1131
+ if (c >= 8960 && c <= 9215) return true;
1132
+ if (c >= 10176 && c <= 10223) return true;
1133
+ if (c >= 10624 && c <= 10751) return true;
1134
+ if (c >= 10752 && c <= 11007) return true;
1135
+ if (c >= 11008 && c <= 11097) return true;
1107
1136
  }
1108
1137
  return false;
1109
1138
  }
@@ -1184,6 +1213,7 @@ async function convertDevanagariTextToPath(svgStr, fontBaseUrl, options = {}) {
1184
1213
  const primaryBytes = await getFontBytes(fontFamily, fontWeight, baseUrl);
1185
1214
  const hasDeva = containsDevanagari(fullText);
1186
1215
  const hasSymbol = [...fullText].some((char) => classifyCharForFontRun(char) === "symbol");
1216
+ const hasMath = [...fullText].some((char) => classifyCharForFontRun(char) === "math");
1187
1217
  const devaCandidateFamilies = hasDeva ? uniqueFamilies([fontFamily, FONT_FALLBACK_DEVANAGARI, resolveDevanagariSibling(fontFamily)]) : [];
1188
1218
  const devaRunFontCache = /* @__PURE__ */ new Map();
1189
1219
  const resolveDevaFontForRun = (runText, runFontSize) => {
@@ -1220,7 +1250,12 @@ async function convertDevanagariTextToPath(svgStr, fontBaseUrl, options = {}) {
1220
1250
  const symbolBytes = await getFontBytes(FONT_FALLBACK_SYMBOLS, 400, baseUrl);
1221
1251
  return symbolFont && symbolBytes ? { family: FONT_FALLBACK_SYMBOLS, font: symbolFont, bytes: symbolBytes } : null;
1222
1252
  })() : null;
1223
- if (!primaryFont && !hasDeva && !hasSymbol) {
1253
+ const mathRunFontPromise = hasMath ? (async () => {
1254
+ const mathFont = await loadFont(FONT_FALLBACK_MATH, 400, baseUrl);
1255
+ const mathBytes = await getFontBytes(FONT_FALLBACK_MATH, 400, baseUrl);
1256
+ return mathFont && mathBytes ? { family: FONT_FALLBACK_MATH, font: mathFont, bytes: mathBytes } : null;
1257
+ })() : null;
1258
+ if (!primaryFont && !hasDeva && !hasSymbol && !hasMath) {
1224
1259
  console.warn(`[text-to-path] No font available for "${fontFamily}", leaving as <text>`);
1225
1260
  skippedCount++;
1226
1261
  continue;
@@ -1267,6 +1302,10 @@ async function convertDevanagariTextToPath(svgStr, fontBaseUrl, options = {}) {
1267
1302
  const resolved = await symbolRunFontPromise;
1268
1303
  if (resolved && fontSupportsRun(resolved.font, r.text, "symbol")) totalAdvance += measureRunWidth(resolved.font, r.text, elemFontSize);
1269
1304
  else canMeasureAll = false;
1305
+ } else if (r.runType === "math") {
1306
+ const resolved = await mathRunFontPromise;
1307
+ if (resolved && fontSupportsRun(resolved.font, r.text, "math")) totalAdvance += measureRunWidth(resolved.font, r.text, elemFontSize);
1308
+ else canMeasureAll = false;
1270
1309
  } else {
1271
1310
  if (fontForElem) totalAdvance += measureRunWidth(fontForElem, r.text, elemFontSize);
1272
1311
  else canMeasureAll = false;
@@ -1281,9 +1320,10 @@ async function convertDevanagariTextToPath(svgStr, fontBaseUrl, options = {}) {
1281
1320
  for (const r of runs) {
1282
1321
  const resolvedDeva = r.runType === "devanagari" ? await resolveDevaFontForRun(r.text, elemFontSize) : null;
1283
1322
  const resolvedSymbol = r.runType === "symbol" ? await symbolRunFontPromise : null;
1323
+ const resolvedMath = r.runType === "math" ? await mathRunFontPromise : null;
1284
1324
  const useDeva = !!resolvedDeva;
1285
- const fontForRun = (resolvedDeva == null ? void 0 : resolvedDeva.font) ?? (resolvedSymbol == null ? void 0 : resolvedSymbol.font) ?? fontForElem;
1286
- const bytesForRun = (resolvedDeva == null ? void 0 : resolvedDeva.bytes) ?? (resolvedSymbol == null ? void 0 : resolvedSymbol.bytes) ?? bytesForElem;
1325
+ const fontForRun = (resolvedDeva == null ? void 0 : resolvedDeva.font) ?? (resolvedSymbol == null ? void 0 : resolvedSymbol.font) ?? (resolvedMath == null ? void 0 : resolvedMath.font) ?? fontForElem;
1326
+ const bytesForRun = (resolvedDeva == null ? void 0 : resolvedDeva.bytes) ?? (resolvedSymbol == null ? void 0 : resolvedSymbol.bytes) ?? (resolvedMath == null ? void 0 : resolvedMath.bytes) ?? bytesForElem;
1287
1327
  if (!fontForRun) continue;
1288
1328
  const result = await shapeRunToPath(
1289
1329
  r.text,
@@ -1350,4 +1390,4 @@ async function preloadDevanagariFont(fontBaseUrl) {
1350
1390
  exports.convertAllTextToPath = convertAllTextToPath;
1351
1391
  exports.convertDevanagariTextToPath = convertDevanagariTextToPath;
1352
1392
  exports.preloadDevanagariFont = preloadDevanagariFont;
1353
- //# sourceMappingURL=svgTextToPath-DTKsddnS.cjs.map
1393
+ //# sourceMappingURL=svgTextToPath-4Y_THSBg.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svgTextToPath-4Y_THSBg.cjs","sources":["../../../src/lib/pdfFonts.ts","../../../src/lib/harfbuzzShaper.ts","../../../src/lib/svgTextToPath.ts"],"sourcesContent":["// Utility for loading local TTF fonts into jsPDF for vector PDF rendering\n\nconst fontCache: Map<string, string> = new Map();\n/** Cache for raw font bytes (used by HarfBuzz / opentype.js for path conversion) */\nconst fontBytesCache: Map<string, Uint8Array> = new Map();\n/** Cache for Google Fonts CSS-parsed TTF URLs (so we don't re-fetch CSS for every weight) */\nconst googleFontUrlCache: Map<string, string> = new Map();\n/** Negative cache — specific Google Font variants known not to exist; don't keep retrying */\nconst googleFontNotFound: Set<string> = new Set();\n/** Runtime registry of families successfully embedded into the active jsPDF bundle. */\nconst registeredFamilies: Set<string> = new Set();\n\nexport const isFamilyEmbedded = (family: string): boolean => registeredFamilies.has(family);\n\n/**\n * Per-variant registry: which (family, weight, italic) tuples were actually\n * registered into the active jsPDF document. The SVG rewriter uses this to\n * avoid emitting a font-family like \"DMSerifDisplay-Bold\" when only the\n * Regular variant was embedded — emitting an unregistered name causes jsPDF\n * to silently fall back to Helvetica, breaking visual font parity with the\n * canvas preview.\n */\nconst registeredVariants: Set<string> = new Set();\n\nconst variantKey = (family: string, weight: number, italic: boolean): string =>\n `${family}|${weight}|${italic ? 'i' : 'n'}`;\n\nconst remoteVariantKey = (family: string, weight: number, italic: boolean): string =>\n `${family}|${resolveFontWeight(weight)}|${italic ? 'i' : 'n'}`;\n\nexport const resetPdfFontRegistry = (): void => {\n registeredFamilies.clear();\n registeredVariants.clear();\n};\n\nexport const isVariantEmbedded = (family: string, weight: number, italic: boolean): boolean =>\n registeredVariants.has(variantKey(family, resolveFontWeight(weight), italic));\n\n/**\n * Pick the closest registered (weight, italic) variant for a family. Used by\n * the SVG-to-jsPDF rewriter when the requested exact variant isn't embedded\n * (e.g. user requested Bold but the family has no real Bold and Google's\n * fallback fetch returned 404). Returns null if the family has no registered\n * variants at all.\n */\nexport const resolveBestRegisteredVariant = (\n family: string,\n weight: number,\n italic: boolean,\n): { weight: number; italic: boolean } | null => {\n const want = resolveFontWeight(weight);\n const weights = [300, 400, 500, 600, 700];\n // 1. Exact match\n if (registeredVariants.has(variantKey(family, want, italic))) {\n return { weight: want, italic };\n }\n // 2. Same italic, nearest weight (prefer heavier when bold requested)\n const sortedByDistance = [...weights].sort((a, b) => {\n const da = Math.abs(a - want);\n const db = Math.abs(b - want);\n if (da !== db) return da - db;\n // tie: when wanting bold, prefer heavier; when wanting light, prefer lighter\n return want >= 500 ? b - a : a - b;\n });\n for (const w of sortedByDistance) {\n if (registeredVariants.has(variantKey(family, w, italic))) {\n return { weight: w, italic };\n }\n }\n // 3. Opposite italic, nearest weight\n for (const w of sortedByDistance) {\n if (registeredVariants.has(variantKey(family, w, !italic))) {\n return { weight: w, italic: !italic };\n }\n }\n return null;\n};\n\n// Server-side font proxy: needed because the browser sends modern UA hints\n// (sec-ch-ua) that override our custom User-Agent header, so Google Fonts\n// returns WOFF2 instead of TTF. The edge function spoofs a legacy UA and\n// returns embeddable TTF bytes for any Google Font / Fontshare family.\nconst FONT_PROXY_URL = `${\n (import.meta as any).env?.VITE_SUPABASE_URL ?? ''\n}/functions/v1/font-proxy`;\n\nasync function fetchTtfViaProxy(\n family: string,\n weight: number,\n isItalic: boolean,\n source: 'google' | 'fontshare',\n): Promise<Uint8Array | null> {\n if (!FONT_PROXY_URL || FONT_PROXY_URL.startsWith('/functions')) return null;\n try {\n const url = `${FONT_PROXY_URL}?family=${encodeURIComponent(family)}&weight=${weight}&italic=${isItalic ? 1 : 0}&source=${source}`;\n const res = await fetch(url);\n if (!res.ok) return null;\n const buf = await res.arrayBuffer();\n return new Uint8Array(buf);\n } catch {\n return null;\n }\n}\n\n// Weight labels for jsPDF font names (editor uses 300, 400, 500, 600, 700)\nexport const FONT_WEIGHT_LABELS: Record<number, string> = {\n 300: 'Light',\n 400: 'Regular',\n 500: 'Medium',\n 600: 'SemiBold',\n 700: 'Bold',\n};\n\n// Resolve numeric weight to the closest supported key (300, 400, 500, 600, 700)\nexport function resolveFontWeight(weight: number): number {\n if (weight <= 350) return 300;\n if (weight <= 450) return 400;\n if (weight <= 550) return 500;\n if (weight <= 650) return 600;\n return 700;\n}\n\n// Font file mapping - maps font names to optional weight-specific TTF paths\nexport type FontWeightFiles = {\n regular: string;\n bold?: string;\n light?: string;\n medium?: string;\n semibold?: string;\n // Italic variants\n italic?: string;\n boldItalic?: string;\n lightItalic?: string;\n mediumItalic?: string;\n semiboldItalic?: string;\n};\n\n/** Font used for symbols (● ◆ ★ etc.) when the main font lacks the glyph. Must be in FONT_FILES. */\nexport const FONT_FALLBACK_SYMBOLS = 'Noto Sans';\n\n/** Tertiary fallback for math operators / arrows (≠ ≤ ≥ ≈ ∞ → ← × ÷ ∑ √ ∈ …)\n * that are not present in the main font OR in FONT_FALLBACK_SYMBOLS. Must be in FONT_FILES. */\nexport const FONT_FALLBACK_MATH = 'Noto Sans Math';\n\n/** Font used for Devanagari / Hindi script when the main font lacks the glyphs. Must be in FONT_FILES. */\nexport const FONT_FALLBACK_DEVANAGARI = 'Hind';\n\n// List paths under public/fonts.\n// All entries now use static (per-weight) TTF files for reliable PDF export.\n// Variable font files are kept in fonts.css for browser rendering only.\nexport const FONT_FILES: Record<string, FontWeightFiles> = {\n 'Playfair Display': {\n regular: '/fonts/PlayfairDisplay-Regular.ttf',\n bold: '/fonts/PlayfairDisplay-Bold.ttf',\n italic: '/fonts/PlayfairDisplay-Italic.ttf',\n boldItalic: '/fonts/PlayfairDisplay-BoldItalic.ttf',\n },\n 'Merriweather': {\n regular: '/fonts/Merriweather-Regular.ttf',\n bold: '/fonts/Merriweather-Bold.ttf',\n light: '/fonts/Merriweather-Light.ttf',\n italic: '/fonts/Merriweather-Italic.ttf',\n boldItalic: '/fonts/Merriweather-BoldItalic.ttf',\n lightItalic: '/fonts/Merriweather-LightItalic.ttf',\n },\n 'Lora': {\n regular: '/fonts/Lora-Regular.ttf',\n bold: '/fonts/Lora-Bold.ttf',\n italic: '/fonts/Lora-Italic.ttf',\n boldItalic: '/fonts/Lora-BoldItalic.ttf',\n },\n 'Montserrat': {\n regular: '/fonts/Montserrat-Regular.ttf',\n bold: '/fonts/Montserrat-Bold.ttf',\n light: '/fonts/Montserrat-Light.ttf',\n medium: '/fonts/Montserrat-Medium.ttf',\n semibold: '/fonts/Montserrat-SemiBold.ttf',\n italic: '/fonts/Montserrat-Italic.ttf',\n boldItalic: '/fonts/Montserrat-BoldItalic.ttf',\n lightItalic: '/fonts/Montserrat-LightItalic.ttf',\n mediumItalic: '/fonts/Montserrat-MediumItalic.ttf',\n semiboldItalic: '/fonts/Montserrat-SemiBoldItalic.ttf',\n },\n 'Open Sans': {\n regular: '/fonts/OpenSans-Regular.ttf',\n bold: '/fonts/OpenSans-Bold.ttf',\n light: '/fonts/OpenSans-Light.ttf',\n semibold: '/fonts/OpenSans-SemiBold.ttf',\n italic: '/fonts/OpenSans-Italic.ttf',\n boldItalic: '/fonts/OpenSans-BoldItalic.ttf',\n lightItalic: '/fonts/OpenSans-LightItalic.ttf',\n semiboldItalic: '/fonts/OpenSans-SemiBoldItalic.ttf',\n },\n 'Roboto': {\n regular: '/fonts/Roboto-Regular.ttf',\n bold: '/fonts/Roboto-Bold.ttf',\n light: '/fonts/Roboto-Light.ttf',\n medium: '/fonts/Roboto-Medium.ttf',\n italic: '/fonts/Roboto-Italic.ttf',\n boldItalic: '/fonts/Roboto-BoldItalic.ttf',\n lightItalic: '/fonts/Roboto-LightItalic.ttf',\n mediumItalic: '/fonts/Roboto-MediumItalic.ttf',\n },\n 'Lato': {\n regular: '/fonts/Lato-Regular.ttf',\n bold: '/fonts/Lato-Bold.ttf',\n light: '/fonts/Lato-Light.ttf',\n italic: '/fonts/Lato-Italic.ttf',\n boldItalic: '/fonts/Lato-BoldItalic.ttf',\n lightItalic: '/fonts/Lato-LightItalic.ttf',\n },\n 'Raleway': {\n regular: '/fonts/Raleway-Regular.ttf',\n bold: '/fonts/Raleway-Bold.ttf',\n light: '/fonts/Raleway-Light.ttf',\n medium: '/fonts/Raleway-Medium.ttf',\n semibold: '/fonts/Raleway-SemiBold.ttf',\n italic: '/fonts/Raleway-Italic.ttf',\n boldItalic: '/fonts/Raleway-BoldItalic.ttf',\n lightItalic: '/fonts/Raleway-LightItalic.ttf',\n },\n 'Poppins': {\n regular: '/fonts/Poppins-Regular.ttf',\n bold: '/fonts/Poppins-Bold.ttf',\n light: '/fonts/Poppins-Light.ttf',\n medium: '/fonts/Poppins-Medium.ttf',\n semibold: '/fonts/Poppins-SemiBold.ttf',\n italic: '/fonts/Poppins-Italic.ttf',\n boldItalic: '/fonts/Poppins-BoldItalic.ttf',\n lightItalic: '/fonts/Poppins-LightItalic.ttf',\n mediumItalic: '/fonts/Poppins-MediumItalic.ttf',\n semiboldItalic: '/fonts/Poppins-SemiBoldItalic.ttf',\n },\n 'Inter': {\n regular: '/fonts/Inter-Regular.ttf',\n bold: '/fonts/Inter-Bold.ttf',\n italic: '/fonts/Inter-Italic.ttf',\n boldItalic: '/fonts/Inter-BoldItalic.ttf',\n },\n 'Nunito': {\n regular: '/fonts/Nunito-Regular.ttf',\n bold: '/fonts/Nunito-Bold.ttf',\n light: '/fonts/Nunito-Light.ttf',\n medium: '/fonts/Nunito-Medium.ttf',\n semibold: '/fonts/Nunito-SemiBold.ttf',\n italic: '/fonts/Nunito-Italic.ttf',\n boldItalic: '/fonts/Nunito-BoldItalic.ttf',\n },\n 'Source Sans Pro': {\n regular: '/fonts/SourceSansPro-Regular.ttf',\n bold: '/fonts/SourceSansPro-Bold.ttf',\n light: '/fonts/SourceSansPro-Light.ttf',\n italic: '/fonts/SourceSansPro-Italic.ttf',\n boldItalic: '/fonts/SourceSansPro-BoldItalic.ttf',\n },\n 'Work Sans': {\n regular: '/fonts/WorkSans-Regular.ttf',\n bold: '/fonts/WorkSans-Bold.ttf',\n italic: '/fonts/WorkSans-Italic.ttf',\n boldItalic: '/fonts/WorkSans-BoldItalic.ttf',\n },\n 'Oswald': { regular: '/fonts/Oswald-Regular.ttf', bold: '/fonts/Oswald-Bold.ttf' },\n 'Bebas Neue': { regular: '/fonts/BebasNeue-Regular.ttf' },\n 'Abril Fatface': { regular: '/fonts/AbrilFatface-Regular.ttf' },\n 'Dancing Script': { regular: '/fonts/DancingScript-Regular.ttf', bold: '/fonts/DancingScript-Bold.ttf' },\n 'Pacifico': { regular: '/fonts/Pacifico-Regular.ttf' },\n 'Great Vibes': { regular: '/fonts/GreatVibes-Regular.ttf' },\n\n // ── Previously variable-only fonts — now with static per-weight TTFs for PDF export ──\n 'DM Sans': {\n regular: '/fonts/DMSans-Regular.ttf',\n bold: '/fonts/DMSans-Bold.ttf',\n light: '/fonts/DMSans-Light.ttf',\n medium: '/fonts/DMSans-Medium.ttf',\n semibold: '/fonts/DMSans-SemiBold.ttf',\n italic: '/fonts/DMSans-RegularItalic.ttf',\n boldItalic: '/fonts/DMSans-BoldItalic.ttf',\n lightItalic: '/fonts/DMSans-LightItalic.ttf',\n mediumItalic: '/fonts/DMSans-MediumItalic.ttf',\n semiboldItalic: '/fonts/DMSans-SemiBoldItalic.ttf',\n },\n 'Outfit': {\n regular: '/fonts/Outfit-Regular.ttf',\n bold: '/fonts/Outfit-Bold.ttf',\n light: '/fonts/Outfit-Light.ttf',\n medium: '/fonts/Outfit-Medium.ttf',\n semibold: '/fonts/Outfit-SemiBold.ttf',\n },\n 'Figtree': {\n regular: '/fonts/Figtree-Regular.ttf',\n bold: '/fonts/Figtree-Bold.ttf',\n light: '/fonts/Figtree-Light.ttf',\n medium: '/fonts/Figtree-Medium.ttf',\n semibold: '/fonts/Figtree-SemiBold.ttf',\n italic: '/fonts/Figtree-RegularItalic.ttf',\n boldItalic: '/fonts/Figtree-BoldItalic.ttf',\n lightItalic: '/fonts/Figtree-LightItalic.ttf',\n mediumItalic: '/fonts/Figtree-MediumItalic.ttf',\n semiboldItalic: '/fonts/Figtree-SemiBoldItalic.ttf',\n },\n 'Manrope': {\n regular: '/fonts/Manrope-Regular.ttf',\n bold: '/fonts/Manrope-Bold.ttf',\n light: '/fonts/Manrope-Light.ttf',\n medium: '/fonts/Manrope-Medium.ttf',\n semibold: '/fonts/Manrope-SemiBold.ttf',\n },\n 'Space Grotesk': {\n regular: '/fonts/SpaceGrotesk-Regular.ttf',\n bold: '/fonts/SpaceGrotesk-Bold.ttf',\n light: '/fonts/SpaceGrotesk-Light.ttf',\n medium: '/fonts/SpaceGrotesk-Medium.ttf',\n semibold: '/fonts/SpaceGrotesk-SemiBold.ttf',\n },\n 'League Spartan': {\n regular: '/fonts/LeagueSpartan-Regular.ttf',\n bold: '/fonts/LeagueSpartan-Bold.ttf',\n light: '/fonts/LeagueSpartan-Light.ttf',\n medium: '/fonts/LeagueSpartan-Medium.ttf',\n semibold: '/fonts/LeagueSpartan-SemiBold.ttf',\n },\n 'EB Garamond': {\n regular: '/fonts/EBGaramond-Regular.ttf',\n bold: '/fonts/EBGaramond-Bold.ttf',\n medium: '/fonts/EBGaramond-Medium.ttf',\n semibold: '/fonts/EBGaramond-SemiBold.ttf',\n italic: '/fonts/EBGaramond-RegularItalic.ttf',\n boldItalic: '/fonts/EBGaramond-BoldItalic.ttf',\n mediumItalic: '/fonts/EBGaramond-MediumItalic.ttf',\n semiboldItalic: '/fonts/EBGaramond-SemiBoldItalic.ttf',\n },\n 'Libre Baskerville': {\n regular: '/fonts/LibreBaskerville-Regular.ttf',\n bold: '/fonts/LibreBaskerville-Bold.ttf',\n italic: '/fonts/LibreBaskerville-RegularItalic.ttf',\n boldItalic: '/fonts/LibreBaskerville-BoldItalic.ttf',\n },\n 'Crimson Text': {\n regular: '/fonts/CrimsonText-Regular.ttf',\n bold: '/fonts/CrimsonText-Bold.ttf',\n italic: '/fonts/CrimsonText-Italic.ttf',\n boldItalic: '/fonts/CrimsonText-BoldItalic.ttf',\n },\n 'DM Serif Display': {\n regular: '/fonts/DMSerifDisplay-Regular.ttf',\n italic: '/fonts/DMSerifDisplay-Italic.ttf',\n },\n 'Libre Franklin': {\n regular: '/fonts/LibreFranklin-Regular.ttf',\n bold: '/fonts/LibreFranklin-Bold.ttf',\n light: '/fonts/LibreFranklin-Light.ttf',\n medium: '/fonts/LibreFranklin-Medium.ttf',\n semibold: '/fonts/LibreFranklin-SemiBold.ttf',\n italic: '/fonts/LibreFranklin-RegularItalic.ttf',\n boldItalic: '/fonts/LibreFranklin-BoldItalic.ttf',\n lightItalic: '/fonts/LibreFranklin-LightItalic.ttf',\n mediumItalic: '/fonts/LibreFranklin-MediumItalic.ttf',\n semiboldItalic: '/fonts/LibreFranklin-SemiBoldItalic.ttf',\n },\n 'Mulish': {\n regular: '/fonts/Mulish-Regular.ttf',\n bold: '/fonts/Mulish-Bold.ttf',\n light: '/fonts/Mulish-Light.ttf',\n medium: '/fonts/Mulish-Medium.ttf',\n semibold: '/fonts/Mulish-SemiBold.ttf',\n italic: '/fonts/Mulish-RegularItalic.ttf',\n boldItalic: '/fonts/Mulish-BoldItalic.ttf',\n lightItalic: '/fonts/Mulish-LightItalic.ttf',\n mediumItalic: '/fonts/Mulish-MediumItalic.ttf',\n semiboldItalic: '/fonts/Mulish-SemiBoldItalic.ttf',\n },\n 'Quicksand': {\n regular: '/fonts/Quicksand-Regular.ttf',\n bold: '/fonts/Quicksand-Bold.ttf',\n light: '/fonts/Quicksand-Light.ttf',\n medium: '/fonts/Quicksand-Medium.ttf',\n semibold: '/fonts/Quicksand-SemiBold.ttf',\n },\n 'Rubik': {\n regular: '/fonts/Rubik-Regular.ttf',\n bold: '/fonts/Rubik-Bold.ttf',\n light: '/fonts/Rubik-Light.ttf',\n medium: '/fonts/Rubik-Medium.ttf',\n semibold: '/fonts/Rubik-SemiBold.ttf',\n italic: '/fonts/Rubik-RegularItalic.ttf',\n boldItalic: '/fonts/Rubik-BoldItalic.ttf',\n lightItalic: '/fonts/Rubik-LightItalic.ttf',\n mediumItalic: '/fonts/Rubik-MediumItalic.ttf',\n semiboldItalic: '/fonts/Rubik-SemiBoldItalic.ttf',\n },\n 'Karla': {\n regular: '/fonts/Karla-Regular.ttf',\n bold: '/fonts/Karla-Bold.ttf',\n light: '/fonts/Karla-Light.ttf',\n medium: '/fonts/Karla-Medium.ttf',\n semibold: '/fonts/Karla-SemiBold.ttf',\n italic: '/fonts/Karla-RegularItalic.ttf',\n boldItalic: '/fonts/Karla-BoldItalic.ttf',\n lightItalic: '/fonts/Karla-LightItalic.ttf',\n mediumItalic: '/fonts/Karla-MediumItalic.ttf',\n semiboldItalic: '/fonts/Karla-SemiBoldItalic.ttf',\n },\n 'Plus Jakarta Sans': {\n regular: '/fonts/PlusJakartaSans-Regular.ttf',\n bold: '/fonts/PlusJakartaSans-Bold.ttf',\n light: '/fonts/PlusJakartaSans-Light.ttf',\n medium: '/fonts/PlusJakartaSans-Medium.ttf',\n semibold: '/fonts/PlusJakartaSans-SemiBold.ttf',\n italic: '/fonts/PlusJakartaSans-RegularItalic.ttf',\n boldItalic: '/fonts/PlusJakartaSans-BoldItalic.ttf',\n lightItalic: '/fonts/PlusJakartaSans-LightItalic.ttf',\n mediumItalic: '/fonts/PlusJakartaSans-MediumItalic.ttf',\n semiboldItalic: '/fonts/PlusJakartaSans-SemiBoldItalic.ttf',\n },\n 'Sora': {\n regular: '/fonts/Sora-Regular.ttf',\n bold: '/fonts/Sora-Bold.ttf',\n light: '/fonts/Sora-Light.ttf',\n medium: '/fonts/Sora-Medium.ttf',\n semibold: '/fonts/Sora-SemiBold.ttf',\n },\n 'Urbanist': {\n regular: '/fonts/Urbanist-Regular.ttf',\n bold: '/fonts/Urbanist-Bold.ttf',\n light: '/fonts/Urbanist-Light.ttf',\n medium: '/fonts/Urbanist-Medium.ttf',\n semibold: '/fonts/Urbanist-SemiBold.ttf',\n italic: '/fonts/Urbanist-RegularItalic.ttf',\n boldItalic: '/fonts/Urbanist-BoldItalic.ttf',\n lightItalic: '/fonts/Urbanist-LightItalic.ttf',\n mediumItalic: '/fonts/Urbanist-MediumItalic.ttf',\n semiboldItalic: '/fonts/Urbanist-SemiBoldItalic.ttf',\n },\n 'Lexend': {\n regular: '/fonts/Lexend-Regular.ttf',\n bold: '/fonts/Lexend-Bold.ttf',\n light: '/fonts/Lexend-Light.ttf',\n medium: '/fonts/Lexend-Medium.ttf',\n semibold: '/fonts/Lexend-SemiBold.ttf',\n },\n 'Albert Sans': {\n regular: '/fonts/AlbertSans-Regular.ttf',\n bold: '/fonts/AlbertSans-Bold.ttf',\n light: '/fonts/AlbertSans-Light.ttf',\n medium: '/fonts/AlbertSans-Medium.ttf',\n semibold: '/fonts/AlbertSans-SemiBold.ttf',\n italic: '/fonts/AlbertSans-RegularItalic.ttf',\n boldItalic: '/fonts/AlbertSans-BoldItalic.ttf',\n lightItalic: '/fonts/AlbertSans-LightItalic.ttf',\n mediumItalic: '/fonts/AlbertSans-MediumItalic.ttf',\n semiboldItalic: '/fonts/AlbertSans-SemiBoldItalic.ttf',\n },\n 'Noto Sans': {\n regular: '/fonts/NotoSans-Regular.ttf',\n bold: '/fonts/NotoSans-Bold.ttf',\n light: '/fonts/NotoSans-Light.ttf',\n medium: '/fonts/NotoSans-Medium.ttf',\n semibold: '/fonts/NotoSans-SemiBold.ttf',\n italic: '/fonts/NotoSans-RegularItalic.ttf',\n boldItalic: '/fonts/NotoSans-BoldItalic.ttf',\n lightItalic: '/fonts/NotoSans-LightItalic.ttf',\n mediumItalic: '/fonts/NotoSans-MediumItalic.ttf',\n semiboldItalic: '/fonts/NotoSans-SemiBoldItalic.ttf',\n },\n 'Cabin': {\n regular: '/fonts/Cabin-Regular.ttf',\n bold: '/fonts/Cabin-Bold.ttf',\n medium: '/fonts/Cabin-Medium.ttf',\n semibold: '/fonts/Cabin-SemiBold.ttf',\n italic: '/fonts/Cabin-RegularItalic.ttf',\n boldItalic: '/fonts/Cabin-BoldItalic.ttf',\n mediumItalic: '/fonts/Cabin-MediumItalic.ttf',\n semiboldItalic: '/fonts/Cabin-SemiBoldItalic.ttf',\n },\n 'Barlow': {\n regular: '/fonts/Barlow-Regular.ttf',\n bold: '/fonts/Barlow-Bold.ttf',\n light: '/fonts/Barlow-Light.ttf',\n medium: '/fonts/Barlow-Medium.ttf',\n semibold: '/fonts/Barlow-SemiBold.ttf',\n italic: '/fonts/Barlow-Italic.ttf',\n boldItalic: '/fonts/Barlow-BoldItalic.ttf',\n },\n 'Josefin Sans': {\n regular: '/fonts/JosefinSans-Regular.ttf',\n bold: '/fonts/JosefinSans-Bold.ttf',\n light: '/fonts/JosefinSans-Light.ttf',\n medium: '/fonts/JosefinSans-Medium.ttf',\n semibold: '/fonts/JosefinSans-SemiBold.ttf',\n italic: '/fonts/JosefinSans-RegularItalic.ttf',\n boldItalic: '/fonts/JosefinSans-BoldItalic.ttf',\n lightItalic: '/fonts/JosefinSans-LightItalic.ttf',\n mediumItalic: '/fonts/JosefinSans-MediumItalic.ttf',\n semiboldItalic: '/fonts/JosefinSans-SemiBoldItalic.ttf',\n },\n 'Archivo': {\n regular: '/fonts/Archivo-Regular.ttf',\n bold: '/fonts/Archivo-Bold.ttf',\n light: '/fonts/Archivo-Light.ttf',\n medium: '/fonts/Archivo-Medium.ttf',\n semibold: '/fonts/Archivo-SemiBold.ttf',\n italic: '/fonts/Archivo-RegularItalic.ttf',\n boldItalic: '/fonts/Archivo-BoldItalic.ttf',\n lightItalic: '/fonts/Archivo-LightItalic.ttf',\n mediumItalic: '/fonts/Archivo-MediumItalic.ttf',\n semiboldItalic: '/fonts/Archivo-SemiBoldItalic.ttf',\n },\n 'Overpass': {\n regular: '/fonts/Overpass-Regular.ttf',\n bold: '/fonts/Overpass-Bold.ttf',\n light: '/fonts/Overpass-Light.ttf',\n medium: '/fonts/Overpass-Medium.ttf',\n semibold: '/fonts/Overpass-SemiBold.ttf',\n italic: '/fonts/Overpass-RegularItalic.ttf',\n boldItalic: '/fonts/Overpass-BoldItalic.ttf',\n lightItalic: '/fonts/Overpass-LightItalic.ttf',\n mediumItalic: '/fonts/Overpass-MediumItalic.ttf',\n semiboldItalic: '/fonts/Overpass-SemiBoldItalic.ttf',\n },\n 'Exo 2': {\n regular: '/fonts/Exo2-Regular.ttf',\n bold: '/fonts/Exo2-Bold.ttf',\n light: '/fonts/Exo2-Light.ttf',\n medium: '/fonts/Exo2-Medium.ttf',\n semibold: '/fonts/Exo2-SemiBold.ttf',\n italic: '/fonts/Exo2-RegularItalic.ttf',\n boldItalic: '/fonts/Exo2-BoldItalic.ttf',\n lightItalic: '/fonts/Exo2-LightItalic.ttf',\n mediumItalic: '/fonts/Exo2-MediumItalic.ttf',\n semiboldItalic: '/fonts/Exo2-SemiBoldItalic.ttf',\n },\n 'Roboto Mono': {\n regular: '/fonts/RobotoMono-Regular.ttf',\n bold: '/fonts/RobotoMono-Bold.ttf',\n light: '/fonts/RobotoMono-Light.ttf',\n medium: '/fonts/RobotoMono-Medium.ttf',\n semibold: '/fonts/RobotoMono-SemiBold.ttf',\n italic: '/fonts/RobotoMono-RegularItalic.ttf',\n boldItalic: '/fonts/RobotoMono-BoldItalic.ttf',\n lightItalic: '/fonts/RobotoMono-LightItalic.ttf',\n mediumItalic: '/fonts/RobotoMono-MediumItalic.ttf',\n semiboldItalic: '/fonts/RobotoMono-SemiBoldItalic.ttf',\n },\n 'Fira Code': {\n regular: '/fonts/FiraCode-Regular.ttf',\n bold: '/fonts/FiraCode-Bold.ttf',\n light: '/fonts/FiraCode-Light.ttf',\n medium: '/fonts/FiraCode-Medium.ttf',\n semibold: '/fonts/FiraCode-SemiBold.ttf',\n },\n 'JetBrains Mono': {\n regular: '/fonts/JetBrainsMono-Regular.ttf',\n bold: '/fonts/JetBrainsMono-Bold.ttf',\n light: '/fonts/JetBrainsMono-Light.ttf',\n medium: '/fonts/JetBrainsMono-Medium.ttf',\n semibold: '/fonts/JetBrainsMono-SemiBold.ttf',\n italic: '/fonts/JetBrainsMono-RegularItalic.ttf',\n boldItalic: '/fonts/JetBrainsMono-BoldItalic.ttf',\n lightItalic: '/fonts/JetBrainsMono-LightItalic.ttf',\n mediumItalic: '/fonts/JetBrainsMono-MediumItalic.ttf',\n semiboldItalic: '/fonts/JetBrainsMono-SemiBoldItalic.ttf',\n },\n 'Source Code Pro': {\n regular: '/fonts/SourceCodePro-Regular.ttf',\n bold: '/fonts/SourceCodePro-Bold.ttf',\n light: '/fonts/SourceCodePro-Light.ttf',\n medium: '/fonts/SourceCodePro-Medium.ttf',\n semibold: '/fonts/SourceCodePro-SemiBold.ttf',\n italic: '/fonts/SourceCodePro-RegularItalic.ttf',\n boldItalic: '/fonts/SourceCodePro-BoldItalic.ttf',\n lightItalic: '/fonts/SourceCodePro-LightItalic.ttf',\n mediumItalic: '/fonts/SourceCodePro-MediumItalic.ttf',\n semiboldItalic: '/fonts/SourceCodePro-SemiBoldItalic.ttf',\n },\n 'IBM Plex Mono': {\n regular: '/fonts/IBMPlexMono-Regular.ttf',\n bold: '/fonts/IBMPlexMono-Bold.ttf',\n },\n 'Space Mono': {\n regular: '/fonts/SpaceMono-Regular.ttf',\n bold: '/fonts/SpaceMono-Bold.ttf',\n italic: '/fonts/SpaceMono-Italic.ttf',\n boldItalic: '/fonts/SpaceMono-BoldItalic.ttf',\n },\n 'Sacramento': { regular: '/fonts/Sacramento-Regular.ttf' },\n 'Alex Brush': { regular: '/fonts/AlexBrush-Regular.ttf' },\n 'Allura': { regular: '/fonts/Allura-Regular.ttf' },\n 'Caveat': {\n regular: '/fonts/Caveat-Regular.ttf',\n bold: '/fonts/Caveat-Bold.ttf',\n medium: '/fonts/Caveat-Medium.ttf',\n semibold: '/fonts/Caveat-SemiBold.ttf',\n },\n 'Lobster': { regular: '/fonts/Lobster-Regular.ttf' },\n 'Comfortaa': {\n regular: '/fonts/Comfortaa-Regular.ttf',\n bold: '/fonts/Comfortaa-Bold.ttf',\n light: '/fonts/Comfortaa-Light.ttf',\n medium: '/fonts/Comfortaa-Medium.ttf',\n semibold: '/fonts/Comfortaa-SemiBold.ttf',\n },\n 'Anton': { regular: '/fonts/Anton-Regular.ttf' },\n 'Teko': {\n regular: '/fonts/Teko-Regular.ttf',\n bold: '/fonts/Teko-Bold.ttf',\n light: '/fonts/Teko-Light.ttf',\n medium: '/fonts/Teko-Medium.ttf',\n semibold: '/fonts/Teko-SemiBold.ttf',\n },\n // ── Indic Script Fallback Fonts ──\n 'Noto Sans Devanagari': {\n regular: '/fonts/NotoSansDevanagari-Regular.ttf',\n bold: '/fonts/NotoSansDevanagari-Bold.ttf',\n light: '/fonts/NotoSansDevanagari-Light.ttf',\n medium: '/fonts/NotoSansDevanagari-Medium.ttf',\n semibold: '/fonts/NotoSansDevanagari-SemiBold.ttf',\n },\n 'Hind': {\n regular: '/fonts/Hind-Regular.ttf',\n bold: '/fonts/Hind-Bold.ttf',\n light: '/fonts/Hind-Light.ttf',\n medium: '/fonts/Hind-Medium.ttf',\n semibold: '/fonts/Hind-SemiBold.ttf',\n },\n // ── Math / Operator Symbol Fallback ──\n // Carries glyphs that NotoSans-Regular's Latin subset is missing\n // (≠ ≤ ≥ ≈ ∞ → ← × ÷ ∑ √ ∈ ∀ ∃ etc.). Used as a tertiary fallback\n // ONLY for chars classified as 'math' that the main font + Noto Sans\n // both lack.\n 'Noto Sans Math': {\n regular: '/fonts/NotoSansMath-Regular.ttf',\n },\n};\n\n// Convert font file to Base64\nconst fontToBase64 = async (url: string): Promise<string> => {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to fetch font: ${response.statusText}`);\n }\n const arrayBuffer = await response.arrayBuffer();\n \n // Convert ArrayBuffer to Base64\n const bytes = new Uint8Array(arrayBuffer);\n if (!isJsPdfEmbeddableTrueType(bytes)) {\n throw new Error(`Font is not a jsPDF-compatible TrueType file: ${url}`);\n }\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary);\n};\n\nfunction isJsPdfEmbeddableTrueType(bytes: Uint8Array): boolean {\n if (bytes.length < 12) return false;\n const signature = String.fromCharCode(bytes[0], bytes[1], bytes[2], bytes[3]);\n if (signature !== '\\x00\\x01\\x00\\x00' && signature !== 'true') return false;\n\n const u16 = (offset: number) => (bytes[offset] << 8) | bytes[offset + 1];\n const u32 = (offset: number) => ((bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]) >>> 0;\n const tableCount = u16(4);\n for (let i = 0; i < tableCount; i++) {\n const recordOffset = 12 + i * 16;\n if (recordOffset + 16 > bytes.length) return false;\n const tag = String.fromCharCode(bytes[recordOffset], bytes[recordOffset + 1], bytes[recordOffset + 2], bytes[recordOffset + 3]);\n if (tag !== 'cmap') continue;\n const cmapOffset = u32(recordOffset + 8);\n const cmapLength = u32(recordOffset + 12);\n if (cmapOffset + Math.min(cmapLength, 4) > bytes.length) return false;\n const subtables = u16(cmapOffset + 2);\n for (let j = 0; j < subtables; j++) {\n const encOffset = cmapOffset + 4 + j * 8;\n if (encOffset + 8 > bytes.length) return false;\n const platform = u16(encOffset);\n const encoding = u16(encOffset + 2);\n if (platform === 0 || (platform === 3 && (encoding === 1 || encoding === 10))) return true;\n }\n return false;\n }\n return false;\n}\n\n// Get jsPDF-compatible font name (sanitized - no spaces)\nexport const getJsPDFFontName = (fontName: string): string => {\n return fontName.replace(/\\s+/g, '');\n};\n\n// Weight → file key and fallback order for resolving TTF path (normal style)\nconst WEIGHT_TO_KEYS: Record<number, (keyof FontWeightFiles)[]> = {\n 300: ['light', 'regular'],\n 400: ['regular'],\n 500: ['medium', 'regular'],\n 600: ['semibold', 'bold', 'regular'],\n 700: ['bold', 'regular'],\n};\n\n// Weight → file key and fallback order for resolving TTF path (italic style)\nconst WEIGHT_TO_ITALIC_KEYS: 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', 'italic', 'bold', 'regular'],\n};\n\nfunction getFontPathForWeight(fontFiles: FontWeightFiles, resolvedWeight: number, isItalic = false): string {\n const keys = isItalic ? WEIGHT_TO_ITALIC_KEYS[resolvedWeight] : WEIGHT_TO_KEYS[resolvedWeight];\n if (!keys) return fontFiles.regular;\n for (const k of keys) {\n const path = fontFiles[k];\n if (path) return path;\n }\n return fontFiles.regular;\n}\n\n/**\n * True when the path resolved by getFontPathForWeight is actually the file for\n * the requested (weight, italic) — not a fallback (e.g. resolving Bold to\n * Regular because the family has no Bold file). Used so we can avoid\n * registering Regular bytes under a \"Bold\" name (which makes jsPDF render\n * inline bold spans in regular weight).\n */\nfunction isExactWeightItalicMatch(\n fontFiles: FontWeightFiles,\n resolvedWeight: number,\n isItalic: boolean,\n resolvedPath: string,\n): boolean {\n const exactKey: keyof FontWeightFiles | null = isItalic\n ? resolvedWeight === 300 ? 'lightItalic'\n : resolvedWeight === 500 ? 'mediumItalic'\n : resolvedWeight === 600 ? 'semiboldItalic'\n : resolvedWeight === 700 ? 'boldItalic'\n : 'italic'\n : resolvedWeight === 300 ? 'light'\n : resolvedWeight === 500 ? 'medium'\n : resolvedWeight === 600 ? 'semibold'\n : resolvedWeight === 700 ? 'bold'\n : 'regular';\n return !!exactKey && fontFiles[exactKey] === resolvedPath;\n}\n\n/** Check if the resolved path is actually an italic-specific file */\nfunction isItalicPath(fontFiles: FontWeightFiles, path: string): boolean {\n return path === fontFiles.italic ||\n path === fontFiles.boldItalic ||\n path === fontFiles.lightItalic ||\n path === fontFiles.mediumItalic ||\n path === fontFiles.semiboldItalic;\n}\n\n// Returns the jsPDF font name used when embedding; includes italic suffix when applicable\nexport const getEmbeddedJsPDFFontName = (fontName: string, weight: number, isItalic = false): string => {\n const resolved = resolveFontWeight(weight);\n const label = FONT_WEIGHT_LABELS[resolved];\n const italicSuffix = isItalic ? 'Italic' : '';\n return `${getJsPDFFontName(fontName)}-${label}${italicSuffix}`;\n};\n\n// Load and embed a font into jsPDF from local TTF files (one jsPDF font per weight+style, style always \"normal\")\nexport const embedFont = async (\n pdf: any,\n fontName: string,\n weight: number = 400,\n isItalic = false,\n): Promise<boolean> => {\n const fontFiles = FONT_FILES[fontName];\n if (!fontFiles) {\n console.warn(`Font ${fontName} not found in local fonts`);\n return false;\n }\n\n const resolvedWeight = resolveFontWeight(weight);\n const fontPath = getFontPathForWeight(fontFiles, resolvedWeight, isItalic);\n if (!fontPath) return false;\n\n // If the local family lacks a TTF for this exact (weight, italic) and would\n // fall back to Regular, refuse — let embedFontWithGoogleFallback fetch a\n // real variant from Google Fonts / Fontshare. Registering Regular bytes\n // under e.g. \"DMSerifDisplay-Bold\" makes jsPDF render bold spans in\n // regular weight, breaking inline bold/italic in selectable PDFs.\n if (!isExactWeightItalicMatch(fontFiles, resolvedWeight, isItalic, fontPath)) {\n return false;\n }\n\n const label = FONT_WEIGHT_LABELS[resolvedWeight];\n const italicSuffix = isItalic ? 'Italic' : '';\n const cacheKey = `${fontName}-${resolvedWeight}${isItalic ? '-italic' : ''}`;\n const jsPdfFontName = getEmbeddedJsPDFFontName(fontName, weight, isItalic);\n\n try {\n let base64Font = fontCache.get(cacheKey);\n if (!base64Font) {\n base64Font = await fontToBase64(fontPath);\n fontCache.set(cacheKey, base64Font);\n }\n\n const fileName = `${getJsPDFFontName(fontName)}-${label}${italicSuffix}.ttf`;\n pdf.addFileToVFS(fileName, base64Font);\n pdf.addFont(fileName, jsPdfFontName, 'normal');\n if (fontName !== jsPdfFontName) {\n try {\n pdf.addFont(fileName, fontName, 'normal');\n } catch {\n // Ignore duplicate alias registration; svg2pdf only needs one successful alias.\n }\n }\n registeredFamilies.add(fontName);\n registeredVariants.add(variantKey(fontName, resolvedWeight, isItalic));\n return true;\n } catch (error) {\n console.error(`Failed to embed font ${fontName} (weight ${weight}, italic=${isItalic}):`, error);\n return false;\n }\n};\n\n// Pre-load all font weights into cache (can be called at app start for faster PDF export)\nexport const preloadFonts = async (): Promise<void> => {\n const loadPromises: Promise<void>[] = [];\n const weights = [300, 400, 500, 600, 700] as const;\n\n for (const [fontName, files] of Object.entries(FONT_FILES)) {\n for (const w of weights) {\n // Normal\n const path = getFontPathForWeight(files as FontWeightFiles, w);\n if (path) {\n const cacheKey = `${fontName}-${w}`;\n loadPromises.push(\n fontToBase64(path)\n .then(base64 => { fontCache.set(cacheKey, base64); })\n .catch(err => console.warn(`Failed to preload ${fontName} weight ${w}:`, err))\n );\n }\n // Italic\n const italicPath = getFontPathForWeight(files as FontWeightFiles, w, true);\n if (italicPath && italicPath !== path) {\n const cacheKeyItalic = `${fontName}-${w}-italic`;\n loadPromises.push(\n fontToBase64(italicPath)\n .then(base64 => { fontCache.set(cacheKeyItalic, base64); })\n .catch(err => console.warn(`Failed to preload ${fontName} weight ${w} italic:`, err))\n );\n }\n }\n }\n\n await Promise.all(loadPromises);\n};\n\n// Check if a font is available locally\nexport const isFontAvailable = (fontName: string): boolean => {\n return fontName in FONT_FILES;\n};\n\n// Check if a font has an italic variant for the given weight\nexport const hasItalicVariant = (fontName: string, weight: number = 400): boolean => {\n const files = FONT_FILES[fontName];\n if (!files) return false;\n const resolvedWeight = resolveFontWeight(weight);\n const normalPath = getFontPathForWeight(files, resolvedWeight, false);\n const italicPath = getFontPathForWeight(files, resolvedWeight, true);\n return italicPath !== normalPath && isItalicPath(files, italicPath);\n};\n\n// Get all available font names\nexport const getAvailableFonts = (): string[] => {\n return Object.keys(FONT_FILES);\n};\n\n/**\n * Get available font weights for a given font family.\n * Returns only weights that have a dedicated TTF file (not fallbacks).\n */\nexport const getAvailableWeights = (fontFamily: string): number[] => {\n const files = FONT_FILES[fontFamily];\n if (!files) return [400]; // Fallback: at least regular\n\n const weights: number[] = [];\n if (files.light) weights.push(300);\n // regular always exists\n weights.push(400);\n if (files.medium) weights.push(500);\n if (files.semibold) weights.push(600);\n if (files.bold) weights.push(700);\n\n return weights;\n};\n\n/**\n * Check if a font has any italic variant at all.\n */\nexport const hasAnyItalicVariant = (fontFamily: string): boolean => {\n const files = FONT_FILES[fontFamily];\n if (!files) return false;\n return !!(files.italic || files.boldItalic || files.lightItalic || files.mediumItalic || files.semiboldItalic);\n};\n\n// ═══════════════════════════════════════════════════════════════\n// Google Fonts dynamic TTF fetching (parity with EC2 server)\n// ═══════════════════════════════════════════════════════════════\n\n/** Fetch a TTF (as base64) for any Google Font. Caches per (family, weight, style). */\nexport async function fetchGoogleFontTTF(\n fontFamily: string,\n weight: number = 400,\n isItalic = false,\n): Promise<string | null> {\n const cacheKey = `gf:${fontFamily}:${weight}:${isItalic ? 'i' : 'n'}`;\n if (fontCache.has(cacheKey)) return fontCache.get(cacheKey)!;\n const notFoundKey = remoteVariantKey(fontFamily, weight, isItalic);\n if (googleFontNotFound.has(notFoundKey)) return null;\n // Try the server-side proxy first — it reliably returns TTF since the edge\n // function can spoof a legacy UA. Direct browser fetch is unreliable because\n // sec-ch-ua client hints override our custom User-Agent.\n const proxyBytes = await fetchTtfViaProxy(fontFamily, weight, isItalic, 'google');\n if (proxyBytes && isJsPdfEmbeddableTrueType(proxyBytes)) {\n fontBytesCache.set(cacheKey, proxyBytes);\n let binary = '';\n for (let i = 0; i < proxyBytes.length; i++) binary += String.fromCharCode(proxyBytes[i]);\n const b64 = btoa(binary);\n fontCache.set(cacheKey, b64);\n return b64;\n }\n try {\n const ital = isItalic ? '1' : '0';\n const cssUrl = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(\n fontFamily,\n )}:ital,wght@${ital},${weight}&display=swap`;\n // The User-Agent below tricks Google Fonts into returning legacy TTF (not WOFF2),\n // matching what the EC2 server does so jsPDF can consume it.\n const cssRes = await fetch(cssUrl, {\n headers: {\n 'User-Agent':\n 'Mozilla/5.0 (Linux; U; Android 2.2; en-us) AppleWebKit/533.1 (KHTML, like Gecko)',\n },\n });\n if (!cssRes.ok) {\n if (cssRes.status === 400 || cssRes.status === 404) googleFontNotFound.add(notFoundKey);\n return null;\n }\n const css = await cssRes.text();\n const urlMatch = css.match(/url\\(([^)]+)\\)\\s+format\\(['\"]?truetype['\"]?\\)/i)\n || css.match(/url\\(([^)]+)\\)/);\n if (!urlMatch) return null;\n const ttfUrl = urlMatch[1].replace(/['\"]/g, '');\n googleFontUrlCache.set(cacheKey, ttfUrl);\n const ttfRes = await fetch(ttfUrl);\n if (!ttfRes.ok) return null;\n const buf = await ttfRes.arrayBuffer();\n const bytes = new Uint8Array(buf);\n if (!isJsPdfEmbeddableTrueType(bytes)) {\n console.warn(`[pdfFonts] Google Fonts returned a non-TTF font for ${fontFamily} (${weight}); skipping jsPDF embed`);\n return null;\n }\n fontBytesCache.set(cacheKey, bytes);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);\n const b64 = btoa(binary);\n fontCache.set(cacheKey, b64);\n return b64;\n } catch (err) {\n console.warn(`[pdfFonts] fetchGoogleFontTTF failed for ${fontFamily} (${weight}):`, err);\n return null;\n }\n}\n\n/** Get raw TTF bytes for a Google Font (used by opentype.js / HarfBuzz). */\nexport async function getGoogleFontBytes(\n fontFamily: string,\n weight: number = 400,\n isItalic = false,\n): Promise<Uint8Array | null> {\n const cacheKey = `gf:${fontFamily}:${weight}:${isItalic ? 'i' : 'n'}`;\n if (fontBytesCache.has(cacheKey)) return fontBytesCache.get(cacheKey)!;\n await fetchGoogleFontTTF(fontFamily, weight, isItalic);\n return fontBytesCache.get(cacheKey) || null;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Fontshare dynamic TTF fetching (Satoshi, Clash Display, etc.)\n// ═══════════════════════════════════════════════════════════════\n\nconst fontshareNotFound: Set<string> = new Set();\n\n/** Convert a font family display name to the slug Fontshare's API expects. */\nfunction toFontshareSlug(family: string): string {\n return family.trim().toLowerCase().replace(/\\s+/g, '-');\n}\n\n/**\n * Fetch a TTF (as base64) for a Fontshare family. Mirrors `fetchGoogleFontTTF`:\n * pulls the CSS for the requested weight/style, picks the `format('truetype')`\n * URL, downloads the bytes, validates, and caches under the same key shape used\n * for Google Fonts so all downstream consumers work without changes.\n */\nexport async function fetchFontshareTTF(\n fontFamily: string,\n weight: number = 400,\n isItalic = false,\n slug?: string,\n): Promise<string | null> {\n const cacheKey = `gf:${fontFamily}:${weight}:${isItalic ? 'i' : 'n'}`;\n if (fontCache.has(cacheKey)) return fontCache.get(cacheKey)!;\n if (fontshareNotFound.has(fontFamily)) return null;\n const proxyBytes = await fetchTtfViaProxy(fontFamily, weight, isItalic, 'fontshare');\n if (proxyBytes && isJsPdfEmbeddableTrueType(proxyBytes)) {\n fontBytesCache.set(cacheKey, proxyBytes);\n let binary = '';\n for (let i = 0; i < proxyBytes.length; i++) binary += String.fromCharCode(proxyBytes[i]);\n const b64 = btoa(binary);\n fontCache.set(cacheKey, b64);\n return b64;\n }\n const finalSlug = slug || toFontshareSlug(fontFamily);\n try {\n const styleSuffix = isItalic ? 'i' : '';\n const cssUrl = `https://api.fontshare.com/v2/css?f[]=${finalSlug}@${weight}${styleSuffix}&display=swap`;\n const cssRes = await fetch(cssUrl);\n if (!cssRes.ok) {\n if (cssRes.status === 400 || cssRes.status === 404) fontshareNotFound.add(fontFamily);\n return null;\n }\n const css = await cssRes.text();\n // Fontshare CSS includes woff2/woff/ttf; pick truetype explicitly.\n const ttMatch = css.match(/url\\(([^)]+)\\)\\s+format\\(['\"]?truetype['\"]?\\)/i);\n if (!ttMatch) {\n fontshareNotFound.add(fontFamily);\n return null;\n }\n let ttfUrl = ttMatch[1].replace(/['\"]/g, '').trim();\n if (ttfUrl.startsWith('//')) ttfUrl = `https:${ttfUrl}`;\n const ttfRes = await fetch(ttfUrl);\n if (!ttfRes.ok) return null;\n const buf = await ttfRes.arrayBuffer();\n const bytes = new Uint8Array(buf);\n if (!isJsPdfEmbeddableTrueType(bytes)) {\n console.warn(`[pdfFonts] Fontshare returned a non-TTF font for ${fontFamily} (${weight}); skipping jsPDF embed`);\n return null;\n }\n fontBytesCache.set(cacheKey, bytes);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);\n const b64 = btoa(binary);\n fontCache.set(cacheKey, b64);\n return b64;\n } catch (err) {\n console.warn(`[pdfFonts] fetchFontshareTTF failed for ${fontFamily} (${weight}):`, err);\n return null;\n }\n}\n\n/** Get raw TTF bytes for a Fontshare font (used by opentype.js / HarfBuzz). */\nexport async function getFontshareFontBytes(\n fontFamily: string,\n weight: number = 400,\n isItalic = false,\n slug?: string,\n): Promise<Uint8Array | null> {\n const cacheKey = `gf:${fontFamily}:${weight}:${isItalic ? 'i' : 'n'}`;\n if (fontBytesCache.has(cacheKey)) return fontBytesCache.get(cacheKey)!;\n await fetchFontshareTTF(fontFamily, weight, isItalic, slug);\n return fontBytesCache.get(cacheKey) || null;\n}\n\n/**\n * Embed a font into jsPDF — local TTF if available, otherwise Google Fonts.\n * Returns true on success. Registers under the SAME jsPDF font name as\n * `getEmbeddedJsPDFFontName(...)` so SVG rewriting matches.\n */\nexport const embedFontWithGoogleFallback = async (\n pdf: any,\n fontName: string,\n weight: number = 400,\n isItalic = false,\n): Promise<boolean> => {\n // 1. Local first\n if (FONT_FILES[fontName]) {\n const ok = await embedFont(pdf, fontName, weight, isItalic);\n if (ok) return true;\n }\n\n // 2. Fontshare fallback (Satoshi, Clash Display, etc.) — try before Google\n // since these families don't exist on Google Fonts at all.\n const resolvedFs = resolveFontWeight(weight);\n const fsB64 = await fetchFontshareTTF(fontName, resolvedFs, isItalic);\n if (fsB64) {\n return registerJsPdfFont(pdf, fontName, resolvedFs, isItalic, fsB64);\n }\n\n // 3. Google Fonts fallback\n const resolved = resolveFontWeight(weight);\n const b64 = await fetchGoogleFontTTF(fontName, resolved, isItalic);\n if (!b64) {\n // For italic requests, do NOT silently register upright bytes under the\n // italic variant key — that previously caused inline italic spans to\n // render in upright glyphs in the PDF. Instead we let\n // `resolveBestRegisteredVariant` (in the SVG rewriter) fall back to a\n // lighter italic weight that actually carries italic glyphs, with\n // synthetic bold (stroke) applied for the weight delta.\n return false;\n }\n return registerJsPdfFont(pdf, fontName, resolved, isItalic, b64);\n};\n\n/** Register a base64 TTF inside jsPDF under the canonical embedded name + alias. */\nfunction registerJsPdfFont(\n pdf: any,\n fontName: string,\n resolvedWeight: number,\n isItalic: boolean,\n base64: string,\n): boolean {\n const label = FONT_WEIGHT_LABELS[resolvedWeight];\n const italicSuffix = isItalic ? 'Italic' : '';\n const jsPdfFontName = getEmbeddedJsPDFFontName(fontName, resolvedWeight, isItalic);\n const fileName = `${getJsPDFFontName(fontName)}-${label}${italicSuffix}.ttf`;\n try {\n pdf.addFileToVFS(fileName, base64);\n pdf.addFont(fileName, jsPdfFontName, 'normal');\n if (fontName !== jsPdfFontName) {\n try {\n pdf.addFont(fileName, fontName, 'normal');\n } catch {\n /* duplicate alias; ignore */\n }\n }\n registeredFamilies.add(fontName);\n registeredVariants.add(variantKey(fontName, resolvedWeight, isItalic));\n return true;\n } catch (err) {\n console.warn(`[pdfFonts] registerJsPdfFont failed for ${fontName}:`, err);\n return false;\n }\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Latin → Devanagari font sibling mapping\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Map of Latin Google Fonts → their Devanagari sibling family on Google Fonts.\n * When a user picks a Latin font and types Hindi, we use the matching Devanagari\n * family for path conversion so the visual style is preserved (instead of always\n * falling back to Hind). Sourced from Google's own family pairings.\n */\nexport const LATIN_TO_DEVANAGARI: Record<string, string> = {\n // Direct Devanagari subsets — same family carries both scripts\n 'Poppins': 'Poppins',\n 'Noto Sans': 'Noto Sans Devanagari',\n 'Noto Serif': 'Noto Serif Devanagari',\n 'Hind': 'Hind',\n 'Mukta': 'Mukta',\n\n // Curated visual matches (Google's own recommendations)\n 'Montserrat': 'Mukta',\n 'Open Sans': 'Mukta',\n 'Lato': 'Mukta',\n 'Roboto': 'Mukta',\n 'Inter': 'Mukta',\n 'Nunito': 'Mukta',\n 'Source Sans Pro': 'Mukta',\n 'Work Sans': 'Mukta',\n 'DM Sans': 'Mukta',\n 'Outfit': 'Mukta',\n 'Figtree': 'Mukta',\n 'Manrope': 'Mukta',\n 'Plus Jakarta Sans': 'Mukta',\n 'Sora': 'Mukta',\n 'Urbanist': 'Mukta',\n 'Lexend': 'Mukta',\n 'Albert Sans': 'Mukta',\n 'Cabin': 'Mukta',\n 'Karla': 'Mukta',\n 'Mulish': 'Mukta',\n 'Rubik': 'Mukta',\n 'Quicksand': 'Mukta',\n 'Libre Franklin': 'Mukta',\n 'Barlow': 'Mukta',\n 'Archivo': 'Mukta',\n 'Overpass': 'Mukta',\n 'Josefin Sans': 'Mukta',\n 'Exo 2': 'Mukta',\n 'Space Grotesk': 'Mukta',\n\n // Display / heavy\n 'Oswald': 'Teko',\n 'Bebas Neue': 'Teko',\n 'Anton': 'Teko',\n 'Teko': 'Teko',\n 'League Spartan': 'Teko',\n\n // Serifs → Tiro Devanagari Hindi (a refined Devanagari serif)\n 'Playfair Display': 'Tiro Devanagari Hindi',\n 'Merriweather': 'Tiro Devanagari Hindi',\n 'Lora': 'Tiro Devanagari Hindi',\n 'EB Garamond': 'Tiro Devanagari Hindi',\n 'Libre Baskerville': 'Tiro Devanagari Hindi',\n 'Crimson Text': 'Tiro Devanagari Hindi',\n 'DM Serif Display': 'Tiro Devanagari Sanskrit',\n 'Abril Fatface': 'Tiro Devanagari Sanskrit',\n\n // Handwriting → Kalam (the only serious Devanagari handwriting family on GF)\n 'Dancing Script': 'Kalam',\n 'Pacifico': 'Kalam',\n 'Great Vibes': 'Kalam',\n 'Sacramento': 'Kalam',\n 'Alex Brush': 'Kalam',\n 'Allura': 'Kalam',\n 'Caveat': 'Kalam',\n 'Lobster': 'Kalam',\n 'Comfortaa': 'Mukta',\n\n // Monospace → there's no proper Devanagari mono; fall back to Mukta\n 'Roboto Mono': 'Mukta',\n 'Fira Code': 'Mukta',\n 'JetBrains Mono': 'Mukta',\n 'Source Code Pro': 'Mukta',\n 'IBM Plex Mono': 'Mukta',\n 'Space Mono': 'Mukta',\n};\n\n/** Pick the best Devanagari family for a given Latin font (returns FONT_FALLBACK_DEVANAGARI as last resort). */\nexport function resolveDevanagariSibling(latinFont: string | null | undefined): string {\n if (!latinFont) return FONT_FALLBACK_DEVANAGARI;\n return LATIN_TO_DEVANAGARI[latinFont] || FONT_FALLBACK_DEVANAGARI;\n}\n","/**\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\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 mode?: 'all' | 'complex-only' | 'mixed-style-only';\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 // 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 // 'complex-only' mode: leave plain Latin text as real selectable text,\n // BUT still outline anything with markdown formatting (mixed-style tspans)\n // since svg2pdf only embeds one font weight per element.\n if (mode === 'complex-only' && !needsComplexShaping(fullText) && !hasMixedStyleTspans()) {\n skippedCount++;\n continue;\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 textAnchor: '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\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 (textAnchor === 'middle') x -= totalAdvance / 2;\n else if (textAnchor === '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":["fontCache","fontBytesCache","opentype","_a"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,MAAMA,kCAAqC,IAAA;AAE3C,MAAMC,uCAA8C,IAAA;AAEpD,MAAM,yCAA8C,IAAA;AAEpD,MAAM,yCAAsC,IAAA;AAmB5C,MAAM,mBAAmB,CAAC,QAAgB,QAAgB,WACxD,GAAG,MAAM,IAAI,kBAAkB,MAAM,CAAC,IAAI,SAAS,MAAM,GAAG;AAsD9D,MAAM,iBAAiB,IACpB,qEAAyB,sBAAqB,EACjD;AAEA,eAAe,iBACb,QACA,QACA,UACA,QAC4B;AAC5B,MAAI,CAAC,kBAAkB,eAAe,WAAW,YAAY,EAAG,QAAO;AACvE,MAAI;AACF,UAAM,MAAM,GAAG,cAAc,WAAW,mBAAmB,MAAM,CAAC,WAAW,MAAM,WAAW,WAAW,IAAI,CAAC,WAAW,MAAM;AAC/H,UAAM,MAAM,MAAM,MAAM,GAAG;AAC3B,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,UAAM,MAAM,MAAM,IAAI,YAAA;AACtB,WAAO,IAAI,WAAW,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAYO,SAAS,kBAAkB,QAAwB;AACxD,MAAI,UAAU,IAAK,QAAO;AAC1B,MAAI,UAAU,IAAK,QAAO;AAC1B,MAAI,UAAU,IAAK,QAAO;AAC1B,MAAI,UAAU,IAAK,QAAO;AAC1B,SAAO;AACT;AAkBO,MAAM,wBAAwB;AAI9B,MAAM,qBAAqB;AAG3B,MAAM,2BAA2B;AAKjC,MAAM,aAA8C;AAAA,EACzD,oBAAoB;AAAA,IAClB,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,YAAY;AAAA,EAAA;AAAA,EAEd,gBAAgB;AAAA,IACd,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf,QAAQ;AAAA,IACN,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,YAAY;AAAA,EAAA;AAAA,EAEd,cAAc;AAAA,IACZ,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,aAAa;AAAA,IACX,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,gBAAgB;AAAA,EAAA;AAAA,EAElB,UAAU;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,EAAA;AAAA,EAEhB,QAAQ;AAAA,IACN,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf,WAAW;AAAA,IACT,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,EAAA;AAAA,EAEf,WAAW;AAAA,IACT,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,SAAS;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,YAAY;AAAA,EAAA;AAAA,EAEd,UAAU;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,EAAA;AAAA,EAEd,mBAAmB;AAAA,IACjB,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,YAAY;AAAA,EAAA;AAAA,EAEd,aAAa;AAAA,IACX,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,YAAY;AAAA,EAAA;AAAA,EAEd,UAAU,EAAE,SAAS,6BAA6B,MAAM,yBAAA;AAAA,EACxD,cAAc,EAAE,SAAS,+BAAA;AAAA,EACzB,iBAAiB,EAAE,SAAS,kCAAA;AAAA,EAC5B,kBAAkB,EAAE,SAAS,oCAAoC,MAAM,gCAAA;AAAA,EACvE,YAAY,EAAE,SAAS,8BAAA;AAAA,EACvB,eAAe,EAAE,SAAS,gCAAA;AAAA;AAAA,EAG1B,WAAW;AAAA,IACT,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,UAAU;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EAAA;AAAA,EAEZ,WAAW;AAAA,IACT,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,WAAW;AAAA,IACT,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EAAA;AAAA,EAEZ,iBAAiB;AAAA,IACf,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EAAA;AAAA,EAEZ,kBAAkB;AAAA,IAChB,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EAAA;AAAA,EAEZ,eAAe;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,qBAAqB;AAAA,IACnB,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,YAAY;AAAA,EAAA;AAAA,EAEd,gBAAgB;AAAA,IACd,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,YAAY;AAAA,EAAA;AAAA,EAEd,oBAAoB;AAAA,IAClB,SAAS;AAAA,IACT,QAAQ;AAAA,EAAA;AAAA,EAEV,kBAAkB;AAAA,IAChB,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,UAAU;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,aAAa;AAAA,IACX,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EAAA;AAAA,EAEZ,SAAS;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,SAAS;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,qBAAqB;AAAA,IACnB,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,QAAQ;AAAA,IACN,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EAAA;AAAA,EAEZ,YAAY;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,UAAU;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EAAA;AAAA,EAEZ,eAAe;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,aAAa;AAAA,IACX,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,SAAS;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,UAAU;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,EAAA;AAAA,EAEd,gBAAgB;AAAA,IACd,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,WAAW;AAAA,IACT,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,YAAY;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,SAAS;AAAA,IACP,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,eAAe;AAAA,IACb,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,aAAa;AAAA,IACX,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EAAA;AAAA,EAEZ,kBAAkB;AAAA,IAChB,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,mBAAmB;AAAA,IACjB,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,gBAAgB;AAAA,EAAA;AAAA,EAElB,iBAAiB;AAAA,IACf,SAAS;AAAA,IACT,MAAM;AAAA,EAAA;AAAA,EAER,cAAc;AAAA,IACZ,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,YAAY;AAAA,EAAA;AAAA,EAEd,cAAc,EAAE,SAAS,gCAAA;AAAA,EACzB,cAAc,EAAE,SAAS,+BAAA;AAAA,EACzB,UAAU,EAAE,SAAS,4BAAA;AAAA,EACrB,UAAU;AAAA,IACR,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,UAAU;AAAA,EAAA;AAAA,EAEZ,WAAW,EAAE,SAAS,6BAAA;AAAA,EACtB,aAAa;AAAA,IACX,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EAAA;AAAA,EAEZ,SAAS,EAAE,SAAS,2BAAA;AAAA,EACpB,QAAQ;AAAA,IACN,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EAAA;AAAA;AAAA,EAGZ,wBAAwB;AAAA,IACtB,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EAAA;AAAA,EAEZ,QAAQ;AAAA,IACN,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOZ,kBAAkB;AAAA,IAChB,SAAS;AAAA,EAAA;AAEb;AAsBA,SAAS,0BAA0B,OAA4B;AAC7D,MAAI,MAAM,SAAS,GAAI,QAAO;AAC9B,QAAM,YAAY,OAAO,aAAa,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;AAC5E,MAAI,cAAc,aAAsB,cAAc,OAAQ,QAAO;AAErE,QAAM,MAAM,CAAC,WAAoB,MAAM,MAAM,KAAK,IAAK,MAAM,SAAS,CAAC;AACvE,QAAM,MAAM,CAAC,YAAqB,MAAM,MAAM,KAAK,KAAO,MAAM,SAAS,CAAC,KAAK,KAAO,MAAM,SAAS,CAAC,KAAK,IAAK,MAAM,SAAS,CAAC,OAAO;AACvI,QAAM,aAAa,IAAI,CAAC;AACxB,WAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,UAAM,eAAe,KAAK,IAAI;AAC9B,QAAI,eAAe,KAAK,MAAM,OAAQ,QAAO;AAC7C,UAAM,MAAM,OAAO,aAAa,MAAM,YAAY,GAAG,MAAM,eAAe,CAAC,GAAG,MAAM,eAAe,CAAC,GAAG,MAAM,eAAe,CAAC,CAAC;AAC9H,QAAI,QAAQ,OAAQ;AACpB,UAAM,aAAa,IAAI,eAAe,CAAC;AACvC,UAAM,aAAa,IAAI,eAAe,EAAE;AACxC,QAAI,aAAa,KAAK,IAAI,YAAY,CAAC,IAAI,MAAM,OAAQ,QAAO;AAChE,UAAM,YAAY,IAAI,aAAa,CAAC;AACpC,aAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,YAAM,YAAY,aAAa,IAAI,IAAI;AACvC,UAAI,YAAY,IAAI,MAAM,OAAQ,QAAO;AACzC,YAAM,WAAW,IAAI,SAAS;AAC9B,YAAM,WAAW,IAAI,YAAY,CAAC;AAClC,UAAI,aAAa,KAAM,aAAa,MAAM,aAAa,KAAK,aAAa,IAAM,QAAO;AAAA,IACxF;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AA8NA,eAAsB,mBACpB,YACA,SAAiB,KACjB,WAAW,OACa;AACxB,QAAM,WAAW,MAAM,UAAU,IAAI,MAAM,IAAI,WAAW,MAAM,GAAG;AACnE,MAAID,YAAU,IAAI,QAAQ,EAAG,QAAOA,YAAU,IAAI,QAAQ;AAC1D,QAAM,cAAc,iBAAiB,YAAY,QAAQ,QAAQ;AACjE,MAAI,mBAAmB,IAAI,WAAW,EAAG,QAAO;AAIhD,QAAM,aAAa,MAAM,iBAAiB,YAAY,QAAQ,UAAU,QAAQ;AAChF,MAAI,cAAc,0BAA0B,UAAU,GAAG;AACvDC,qBAAe,IAAI,UAAU,UAAU;AACvC,QAAI,SAAS;AACb,aAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,IAAK,WAAU,OAAO,aAAa,WAAW,CAAC,CAAC;AACvF,UAAM,MAAM,KAAK,MAAM;AACvBD,gBAAU,IAAI,UAAU,GAAG;AAC3B,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,OAAO,WAAW,MAAM;AAC9B,UAAM,SAAS,4CAA4C;AAAA,MACzD;AAAA,IAAA,CACD,cAAc,IAAI,IAAI,MAAM;AAG7B,UAAM,SAAS,MAAM,MAAM,QAAQ;AAAA,MACjC,SAAS;AAAA,QACP,cACE;AAAA,MAAA;AAAA,IACJ,CACD;AACD,QAAI,CAAC,OAAO,IAAI;AACd,UAAI,OAAO,WAAW,OAAO,OAAO,WAAW,IAAK,oBAAmB,IAAI,WAAW;AACtF,aAAO;AAAA,IACT;AACA,UAAM,MAAM,MAAM,OAAO,KAAA;AACzB,UAAM,WAAW,IAAI,MAAM,gDAAgD,KACtE,IAAI,MAAM,gBAAgB;AAC/B,QAAI,CAAC,SAAU,QAAO;AACtB,UAAM,SAAS,SAAS,CAAC,EAAE,QAAQ,SAAS,EAAE;AAC9C,uBAAmB,IAAI,UAAU,MAAM;AACvC,UAAM,SAAS,MAAM,MAAM,MAAM;AACjC,QAAI,CAAC,OAAO,GAAI,QAAO;AACvB,UAAM,MAAM,MAAM,OAAO,YAAA;AACzB,UAAM,QAAQ,IAAI,WAAW,GAAG;AAChC,QAAI,CAAC,0BAA0B,KAAK,GAAG;AACrC,cAAQ,KAAK,uDAAuD,UAAU,KAAK,MAAM,yBAAyB;AAClH,aAAO;AAAA,IACT;AACAC,qBAAe,IAAI,UAAU,KAAK;AAClC,QAAI,SAAS;AACb,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,IAAK,WAAU,OAAO,aAAa,MAAM,CAAC,CAAC;AAC7E,UAAM,MAAM,KAAK,MAAM;AACvBD,gBAAU,IAAI,UAAU,GAAG;AAC3B,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,YAAQ,KAAK,4CAA4C,UAAU,KAAK,MAAM,MAAM,GAAG;AACvF,WAAO;AAAA,EACT;AACF;AAGA,eAAsB,mBACpB,YACA,SAAiB,KACjB,WAAW,OACiB;AAC5B,QAAM,WAAW,MAAM,UAAU,IAAI,MAAM,IAAI,WAAW,MAAM,GAAG;AACnE,MAAIC,iBAAe,IAAI,QAAQ,EAAG,QAAOA,iBAAe,IAAI,QAAQ;AACpE,QAAM,mBAAmB,YAAY,QAAQ,QAAQ;AACrD,SAAOA,iBAAe,IAAI,QAAQ,KAAK;AACzC;AAMA,MAAM,wCAAqC,IAAA;AAG3C,SAAS,gBAAgB,QAAwB;AAC/C,SAAO,OAAO,OAAO,cAAc,QAAQ,QAAQ,GAAG;AACxD;AAQA,eAAsB,kBACpB,YACA,SAAiB,KACjB,WAAW,OACX,MACwB;AACxB,QAAM,WAAW,MAAM,UAAU,IAAI,MAAM,IAAI,WAAW,MAAM,GAAG;AACnE,MAAID,YAAU,IAAI,QAAQ,EAAG,QAAOA,YAAU,IAAI,QAAQ;AAC1D,MAAI,kBAAkB,IAAI,UAAU,EAAG,QAAO;AAC9C,QAAM,aAAa,MAAM,iBAAiB,YAAY,QAAQ,UAAU,WAAW;AACnF,MAAI,cAAc,0BAA0B,UAAU,GAAG;AACvDC,qBAAe,IAAI,UAAU,UAAU;AACvC,QAAI,SAAS;AACb,aAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,IAAK,WAAU,OAAO,aAAa,WAAW,CAAC,CAAC;AACvF,UAAM,MAAM,KAAK,MAAM;AACvBD,gBAAU,IAAI,UAAU,GAAG;AAC3B,WAAO;AAAA,EACT;AACA,QAAM,YAAoB,gBAAgB,UAAU;AACpD,MAAI;AACF,UAAM,cAAc,WAAW,MAAM;AACrC,UAAM,SAAS,wCAAwC,SAAS,IAAI,MAAM,GAAG,WAAW;AACxF,UAAM,SAAS,MAAM,MAAM,MAAM;AACjC,QAAI,CAAC,OAAO,IAAI;AACd,UAAI,OAAO,WAAW,OAAO,OAAO,WAAW,IAAK,mBAAkB,IAAI,UAAU;AACpF,aAAO;AAAA,IACT;AACA,UAAM,MAAM,MAAM,OAAO,KAAA;AAEzB,UAAM,UAAU,IAAI,MAAM,gDAAgD;AAC1E,QAAI,CAAC,SAAS;AACZ,wBAAkB,IAAI,UAAU;AAChC,aAAO;AAAA,IACT;AACA,QAAI,SAAS,QAAQ,CAAC,EAAE,QAAQ,SAAS,EAAE,EAAE,KAAA;AAC7C,QAAI,OAAO,WAAW,IAAI,EAAG,UAAS,SAAS,MAAM;AACrD,UAAM,SAAS,MAAM,MAAM,MAAM;AACjC,QAAI,CAAC,OAAO,GAAI,QAAO;AACvB,UAAM,MAAM,MAAM,OAAO,YAAA;AACzB,UAAM,QAAQ,IAAI,WAAW,GAAG;AAChC,QAAI,CAAC,0BAA0B,KAAK,GAAG;AACrC,cAAQ,KAAK,oDAAoD,UAAU,KAAK,MAAM,yBAAyB;AAC/G,aAAO;AAAA,IACT;AACAC,qBAAe,IAAI,UAAU,KAAK;AAClC,QAAI,SAAS;AACb,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,IAAK,WAAU,OAAO,aAAa,MAAM,CAAC,CAAC;AAC7E,UAAM,MAAM,KAAK,MAAM;AACvBD,gBAAU,IAAI,UAAU,GAAG;AAC3B,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,YAAQ,KAAK,2CAA2C,UAAU,KAAK,MAAM,MAAM,GAAG;AACtF,WAAO;AAAA,EACT;AACF;AAGA,eAAsB,sBACpB,YACA,SAAiB,KACjB,WAAW,OACX,MAC4B;AAC5B,QAAM,WAAW,MAAM,UAAU,IAAI,MAAM,IAAI,WAAW,MAAM,GAAG;AACnE,MAAIC,iBAAe,IAAI,QAAQ,EAAG,QAAOA,iBAAe,IAAI,QAAQ;AACpE,QAAM,kBAAkB,YAAY,QAAQ,QAAc;AAC1D,SAAOA,iBAAe,IAAI,QAAQ,KAAK;AACzC;AAmFO,MAAM,sBAA8C;AAAA;AAAA,EAEzD,WAAW;AAAA,EACX,aAAa;AAAA,EACb,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,SAAS;AAAA;AAAA,EAGT,cAAc;AAAA,EACd,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA,EACV,mBAAmB;AAAA,EACnB,aAAa;AAAA,EACb,WAAW;AAAA,EACX,UAAU;AAAA,EACV,WAAW;AAAA,EACX,WAAW;AAAA,EACX,qBAAqB;AAAA,EACrB,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,eAAe;AAAA,EACf,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,SAAS;AAAA,EACT,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,UAAU;AAAA,EACV,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,SAAS;AAAA,EACT,iBAAiB;AAAA;AAAA,EAGjB,UAAU;AAAA,EACV,cAAc;AAAA,EACd,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,kBAAkB;AAAA;AAAA,EAGlB,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,QAAQ;AAAA,EACR,eAAe;AAAA,EACf,qBAAqB;AAAA,EACrB,gBAAgB;AAAA,EAChB,oBAAoB;AAAA,EACpB,iBAAiB;AAAA;AAAA,EAGjB,kBAAkB;AAAA,EAClB,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,cAAc;AAAA,EACd,cAAc;AAAA,EACd,UAAU;AAAA,EACV,UAAU;AAAA,EACV,WAAW;AAAA,EACX,aAAa;AAAA;AAAA,EAGb,eAAe;AAAA,EACf,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,iBAAiB;AAAA,EACjB,cAAc;AAChB;AAGO,SAAS,yBAAyB,WAA8C;AACrF,MAAI,CAAC,UAAW,QAAO;AACvB,SAAO,oBAAoB,SAAS,KAAK;AAC3C;AChrCA,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,WAAW,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,YAAY,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,iBAAiB,kBAAkB,MAAM;AAG/C,QAAI,QAAQ,MAAM,mBAAmB,YAAY,gBAAgB,QAAQ;AACzE,QAAI,CAAC,OAAO;AACV,cAAQ,MAAM,sBAAsB,YAAY,gBAAgB,QAAQ;AAAA,IAC1E;AAGA,QAAI,CAAC,SAAS,UAAU;AACtB,cAAQ,MAAM,mBAAmB,YAAY,gBAAgB,KAAK;AAClE,UAAI,CAAC,MAAO,SAAQ,MAAM,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,OAAOA,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;AA8BA,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;AAGtB,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;AAKA,QAAI,SAAS,kBAAkB,CAAC,oBAAoB,QAAQ,KAAK,CAAC,uBAAuB;AACvF;AACA;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,eAAOC,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,YAAY,0BAA0B,yBAAyB,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,SAAS,uBAAuB,KAAK,OAAO;AACrE,YAAM,cAAc,MAAM,aAAa,uBAAuB,KAAK,OAAO;AAC1E,aAAO,cAAc,cAAc,EAAE,QAAQ,uBAAuB,MAAM,YAAY,OAAO,YAAA,IAAgB;AAAA,IAC/G,OACA;AACJ,UAAM,qBAA6D,WAC9D,YAAY;AACX,YAAM,WAAW,MAAM,SAAS,oBAAoB,KAAK,OAAO;AAChE,YAAM,YAAY,MAAM,aAAa,oBAAoB,KAAK,OAAO;AACrE,aAAO,YAAY,YAAY,EAAE,QAAQ,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,aACJ,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;AAGhE,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,eAAe,SAAU,MAAK,eAAe;AAAA,iBACxC,eAAe,MAAO,MAAK;AAAA,MACtC;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,SAAS,0BAA0B,QAAQ,OAAO;AAAA,EAC1D;AACF;;;;"}
@@ -26,6 +26,7 @@ function resolveFontWeight(weight) {
26
26
  return 700;
27
27
  }
28
28
  const FONT_FALLBACK_SYMBOLS = "Noto Sans";
29
+ const FONT_FALLBACK_MATH = "Noto Sans Math";
29
30
  const FONT_FALLBACK_DEVANAGARI = "Hind";
30
31
  const FONT_FILES = {
31
32
  "Playfair Display": {
@@ -499,6 +500,14 @@ const FONT_FILES = {
499
500
  light: "/fonts/Hind-Light.ttf",
500
501
  medium: "/fonts/Hind-Medium.ttf",
501
502
  semibold: "/fonts/Hind-SemiBold.ttf"
503
+ },
504
+ // ── Math / Operator Symbol Fallback ──
505
+ // Carries glyphs that NotoSans-Regular's Latin subset is missing
506
+ // (≠ ≤ ≥ ≈ ∞ → ← × ÷ ∑ √ ∈ ∀ ∃ etc.). Used as a tertiary fallback
507
+ // ONLY for chars classified as 'math' that the main font + Noto Sans
508
+ // both lack.
509
+ "Noto Sans Math": {
510
+ regular: "/fonts/NotoSansMath-Regular.ttf"
502
511
  }
503
512
  };
504
513
  function isJsPdfEmbeddableTrueType(bytes) {
@@ -835,9 +844,21 @@ function isBasicLatinOrLatinExtended(char) {
835
844
  const c = char.codePointAt(0) ?? 0;
836
845
  return c <= 591;
837
846
  }
847
+ function isMathOperatorChar(char) {
848
+ const c = char.codePointAt(0) ?? 0;
849
+ if (c >= 8592 && c <= 8703) return true;
850
+ if (c >= 8704 && c <= 8959) return true;
851
+ if (c >= 8960 && c <= 9215) return true;
852
+ if (c >= 10176 && c <= 10223) return true;
853
+ if (c >= 10624 && c <= 10751) return true;
854
+ if (c >= 10752 && c <= 11007) return true;
855
+ if (c >= 11008 && c <= 11097) return true;
856
+ return false;
857
+ }
838
858
  function classifyCharForFontRun(char) {
839
859
  if (isBasicLatinOrLatinExtended(char)) return "main";
840
860
  if (isDevanagari(char)) return "devanagari";
861
+ if (isMathOperatorChar(char)) return "math";
841
862
  return "symbol";
842
863
  }
843
864
  function isIgnorableForCoverage(char) {
@@ -849,6 +870,7 @@ function fontSupportsRun(font, text, runType) {
849
870
  if (isIgnorableForCoverage(char)) continue;
850
871
  if (runType === "devanagari" && !isDevanagari(char)) continue;
851
872
  if (runType === "symbol" && classifyCharForFontRun(char) !== "symbol") continue;
873
+ if (runType === "math" && classifyCharForFontRun(char) !== "math") continue;
852
874
  const glyph = font.charToGlyph(char);
853
875
  if (!glyph || glyph.index === 0) return false;
854
876
  }
@@ -1063,6 +1085,13 @@ function needsComplexShaping(text) {
1063
1085
  if (c >= 12288 && c <= 40959) return true;
1064
1086
  if (c >= 44032 && c <= 55215) return true;
1065
1087
  if (c >= 126976) return true;
1088
+ if (c >= 8592 && c <= 8703) return true;
1089
+ if (c >= 8704 && c <= 8959) return true;
1090
+ if (c >= 8960 && c <= 9215) return true;
1091
+ if (c >= 10176 && c <= 10223) return true;
1092
+ if (c >= 10624 && c <= 10751) return true;
1093
+ if (c >= 10752 && c <= 11007) return true;
1094
+ if (c >= 11008 && c <= 11097) return true;
1066
1095
  }
1067
1096
  return false;
1068
1097
  }
@@ -1143,6 +1172,7 @@ async function convertDevanagariTextToPath(svgStr, fontBaseUrl, options = {}) {
1143
1172
  const primaryBytes = await getFontBytes(fontFamily, fontWeight, baseUrl);
1144
1173
  const hasDeva = containsDevanagari(fullText);
1145
1174
  const hasSymbol = [...fullText].some((char) => classifyCharForFontRun(char) === "symbol");
1175
+ const hasMath = [...fullText].some((char) => classifyCharForFontRun(char) === "math");
1146
1176
  const devaCandidateFamilies = hasDeva ? uniqueFamilies([fontFamily, FONT_FALLBACK_DEVANAGARI, resolveDevanagariSibling(fontFamily)]) : [];
1147
1177
  const devaRunFontCache = /* @__PURE__ */ new Map();
1148
1178
  const resolveDevaFontForRun = (runText, runFontSize) => {
@@ -1179,7 +1209,12 @@ async function convertDevanagariTextToPath(svgStr, fontBaseUrl, options = {}) {
1179
1209
  const symbolBytes = await getFontBytes(FONT_FALLBACK_SYMBOLS, 400, baseUrl);
1180
1210
  return symbolFont && symbolBytes ? { family: FONT_FALLBACK_SYMBOLS, font: symbolFont, bytes: symbolBytes } : null;
1181
1211
  })() : null;
1182
- if (!primaryFont && !hasDeva && !hasSymbol) {
1212
+ const mathRunFontPromise = hasMath ? (async () => {
1213
+ const mathFont = await loadFont(FONT_FALLBACK_MATH, 400, baseUrl);
1214
+ const mathBytes = await getFontBytes(FONT_FALLBACK_MATH, 400, baseUrl);
1215
+ return mathFont && mathBytes ? { family: FONT_FALLBACK_MATH, font: mathFont, bytes: mathBytes } : null;
1216
+ })() : null;
1217
+ if (!primaryFont && !hasDeva && !hasSymbol && !hasMath) {
1183
1218
  console.warn(`[text-to-path] No font available for "${fontFamily}", leaving as <text>`);
1184
1219
  skippedCount++;
1185
1220
  continue;
@@ -1226,6 +1261,10 @@ async function convertDevanagariTextToPath(svgStr, fontBaseUrl, options = {}) {
1226
1261
  const resolved = await symbolRunFontPromise;
1227
1262
  if (resolved && fontSupportsRun(resolved.font, r.text, "symbol")) totalAdvance += measureRunWidth(resolved.font, r.text, elemFontSize);
1228
1263
  else canMeasureAll = false;
1264
+ } else if (r.runType === "math") {
1265
+ const resolved = await mathRunFontPromise;
1266
+ if (resolved && fontSupportsRun(resolved.font, r.text, "math")) totalAdvance += measureRunWidth(resolved.font, r.text, elemFontSize);
1267
+ else canMeasureAll = false;
1229
1268
  } else {
1230
1269
  if (fontForElem) totalAdvance += measureRunWidth(fontForElem, r.text, elemFontSize);
1231
1270
  else canMeasureAll = false;
@@ -1240,9 +1279,10 @@ async function convertDevanagariTextToPath(svgStr, fontBaseUrl, options = {}) {
1240
1279
  for (const r of runs) {
1241
1280
  const resolvedDeva = r.runType === "devanagari" ? await resolveDevaFontForRun(r.text, elemFontSize) : null;
1242
1281
  const resolvedSymbol = r.runType === "symbol" ? await symbolRunFontPromise : null;
1282
+ const resolvedMath = r.runType === "math" ? await mathRunFontPromise : null;
1243
1283
  const useDeva = !!resolvedDeva;
1244
- const fontForRun = (resolvedDeva == null ? void 0 : resolvedDeva.font) ?? (resolvedSymbol == null ? void 0 : resolvedSymbol.font) ?? fontForElem;
1245
- const bytesForRun = (resolvedDeva == null ? void 0 : resolvedDeva.bytes) ?? (resolvedSymbol == null ? void 0 : resolvedSymbol.bytes) ?? bytesForElem;
1284
+ const fontForRun = (resolvedDeva == null ? void 0 : resolvedDeva.font) ?? (resolvedSymbol == null ? void 0 : resolvedSymbol.font) ?? (resolvedMath == null ? void 0 : resolvedMath.font) ?? fontForElem;
1285
+ const bytesForRun = (resolvedDeva == null ? void 0 : resolvedDeva.bytes) ?? (resolvedSymbol == null ? void 0 : resolvedSymbol.bytes) ?? (resolvedMath == null ? void 0 : resolvedMath.bytes) ?? bytesForElem;
1246
1286
  if (!fontForRun) continue;
1247
1287
  const result = await shapeRunToPath(
1248
1288
  r.text,
@@ -1311,4 +1351,4 @@ export {
1311
1351
  convertDevanagariTextToPath,
1312
1352
  preloadDevanagariFont
1313
1353
  };
1314
- //# sourceMappingURL=svgTextToPath-C20Obtt2.js.map
1354
+ //# sourceMappingURL=svgTextToPath-CQ2Tp03U.js.map