@pixldocs/canvas-renderer 0.5.104 → 0.5.105

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -6,6 +6,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
6
6
  const jsxRuntime = require("react/jsx-runtime");
7
7
  const react = require("react");
8
8
  const reactDom = require("react-dom");
9
+ const sonner = require("sonner");
9
10
  const zustand = require("zustand");
10
11
  const fabric = require("fabric");
11
12
  const client = require("react-dom/client");
@@ -2506,9 +2507,17 @@ const useEditorStore = zustand.create((set, get) => ({
2506
2507
  }));
2507
2508
  let canvasRegistry = /* @__PURE__ */ new Map();
2508
2509
  function registerFabricCanvas(pageId, canvas) {
2509
- canvasRegistry.set(pageId, canvas);
2510
- }
2511
- function unregisterFabricCanvas(pageId) {
2510
+ const existing = canvasRegistry.get(pageId);
2511
+ const registryKey = existing && existing !== canvas ? `${pageId}#${Math.random().toString(36).slice(2, 10)}` : pageId;
2512
+ canvasRegistry.set(registryKey, canvas);
2513
+ return registryKey;
2514
+ }
2515
+ function unregisterFabricCanvas(pageId, canvas) {
2516
+ if (canvas) {
2517
+ const existing = canvasRegistry.get(pageId);
2518
+ if (existing === canvas) canvasRegistry.delete(pageId);
2519
+ return;
2520
+ }
2512
2521
  canvasRegistry.delete(pageId);
2513
2522
  }
2514
2523
  const LOCAL_FONTS = /* @__PURE__ */ new Set([
@@ -2580,6 +2589,8 @@ const LOCAL_FONTS = /* @__PURE__ */ new Set([
2580
2589
  ]);
2581
2590
  const loadedGoogleFonts = /* @__PURE__ */ new Set();
2582
2591
  const failedGoogleFonts = /* @__PURE__ */ new Set();
2592
+ const loadedFontshareFonts = /* @__PURE__ */ new Set();
2593
+ const failedFontshareFonts = /* @__PURE__ */ new Set();
2583
2594
  const loadingPromises$1 = /* @__PURE__ */ new Map();
2584
2595
  async function loadGoogleFont(fontFamily, weights) {
2585
2596
  if (LOCAL_FONTS.has(fontFamily)) return true;
@@ -2639,6 +2650,346 @@ async function _doLoadGoogleFont(fontFamily, weights) {
2639
2650
  console.warn(`[GoogleFonts] Failed to load: ${fontFamily}`);
2640
2651
  return false;
2641
2652
  }
2653
+ async function loadFontshareFont(fontFamily, slug, weights) {
2654
+ if (loadedFontshareFonts.has(fontFamily)) return true;
2655
+ if (failedFontshareFonts.has(fontFamily)) return false;
2656
+ const existing = loadingPromises$1.get(`fontshare:${fontFamily}`);
2657
+ if (existing) return existing;
2658
+ const promise = (async () => {
2659
+ try {
2660
+ const weightStr = (weights || [300, 400, 500, 700]).join(",");
2661
+ const url = `https://api.fontshare.com/v2/css?f[]=${slug}@${weightStr}&display=swap`;
2662
+ if (document.querySelector(`link[href="${url}"]`)) return true;
2663
+ const link = document.createElement("link");
2664
+ link.rel = "stylesheet";
2665
+ link.href = url;
2666
+ return await new Promise((resolve) => {
2667
+ link.onload = async () => {
2668
+ try {
2669
+ await document.fonts.load(`16px "${fontFamily}"`);
2670
+ await document.fonts.load(`bold 16px "${fontFamily}"`);
2671
+ } catch {
2672
+ }
2673
+ resolve(true);
2674
+ };
2675
+ link.onerror = () => {
2676
+ console.warn(`[Fontshare] Failed to load: ${fontFamily}`);
2677
+ resolve(false);
2678
+ };
2679
+ document.head.appendChild(link);
2680
+ });
2681
+ } catch (e) {
2682
+ console.warn(`[Fontshare] Error loading ${fontFamily}:`, e);
2683
+ return false;
2684
+ }
2685
+ })();
2686
+ loadingPromises$1.set(`fontshare:${fontFamily}`, promise);
2687
+ try {
2688
+ const result = await promise;
2689
+ if (result) loadedFontshareFonts.add(fontFamily);
2690
+ else failedFontshareFonts.add(fontFamily);
2691
+ return result;
2692
+ } finally {
2693
+ loadingPromises$1.delete(`fontshare:${fontFamily}`);
2694
+ }
2695
+ }
2696
+ async function loadFont(fontFamily) {
2697
+ if (LOCAL_FONTS.has(fontFamily)) return true;
2698
+ const entry = findFontEntry(fontFamily);
2699
+ if ((entry == null ? void 0 : entry.source) === "fontshare" && entry.fontshareSlug) {
2700
+ return loadFontshareFont(fontFamily, entry.fontshareSlug);
2701
+ }
2702
+ return loadGoogleFont(fontFamily);
2703
+ }
2704
+ const EXTENDED_FONT_LIST = [
2705
+ // ═══════════════════════════════════════════════════════════════════
2706
+ // PREMIUM (Fontshare) — modern, professional aesthetic
2707
+ // ═══════════════════════════════════════════════════════════════════
2708
+ { name: "Satoshi", category: "Premium", local: false, source: "fontshare", fontshareSlug: "satoshi", popular: true },
2709
+ { name: "Cabinet Grotesk", category: "Premium", local: false, source: "fontshare", fontshareSlug: "cabinet-grotesk", popular: true },
2710
+ { name: "Clash Display", category: "Premium", local: false, source: "fontshare", fontshareSlug: "clash-display", popular: true },
2711
+ { name: "Clash Grotesk", category: "Premium", local: false, source: "fontshare", fontshareSlug: "clash-grotesk", popular: true },
2712
+ { name: "General Sans", category: "Premium", local: false, source: "fontshare", fontshareSlug: "general-sans", popular: true },
2713
+ { name: "Switzer", category: "Premium", local: false, source: "fontshare", fontshareSlug: "switzer" },
2714
+ { name: "Supreme", category: "Premium", local: false, source: "fontshare", fontshareSlug: "supreme" },
2715
+ { name: "Author", category: "Premium", local: false, source: "fontshare", fontshareSlug: "author" },
2716
+ { name: "Boska", category: "Premium", local: false, source: "fontshare", fontshareSlug: "boska" },
2717
+ { name: "Excon", category: "Premium", local: false, source: "fontshare", fontshareSlug: "excon" },
2718
+ { name: "Khand", category: "Premium", local: false, source: "fontshare", fontshareSlug: "khand" },
2719
+ { name: "Sentient", category: "Premium", local: false, source: "fontshare", fontshareSlug: "sentient" },
2720
+ { name: "Synonym", category: "Premium", local: false, source: "fontshare", fontshareSlug: "synonym" },
2721
+ { name: "Erode", category: "Premium", local: false, source: "fontshare", fontshareSlug: "erode" },
2722
+ { name: "Ranade", category: "Premium", local: false, source: "fontshare", fontshareSlug: "ranade" },
2723
+ { name: "Tanker", category: "Premium", local: false, source: "fontshare", fontshareSlug: "tanker" },
2724
+ { name: "Zodiak", category: "Premium", local: false, source: "fontshare", fontshareSlug: "zodiak" },
2725
+ { name: "Gambarino", category: "Premium", local: false, source: "fontshare", fontshareSlug: "gambarino" },
2726
+ { name: "Melodrama", category: "Premium", local: false, source: "fontshare", fontshareSlug: "melodrama" },
2727
+ { name: "Bespoke Serif", category: "Premium", local: false, source: "fontshare", fontshareSlug: "bespoke-serif" },
2728
+ { name: "Bespoke Stencil", category: "Premium", local: false, source: "fontshare", fontshareSlug: "bespoke-stencil" },
2729
+ { name: "Panchang", category: "Premium", local: false, source: "fontshare", fontshareSlug: "panchang" },
2730
+ { name: "Pally", category: "Premium", local: false, source: "fontshare", fontshareSlug: "pally" },
2731
+ { name: "Tabular", category: "Premium", local: false, source: "fontshare", fontshareSlug: "tabular" },
2732
+ { name: "Sharpie", category: "Premium", local: false, source: "fontshare", fontshareSlug: "sharpie" },
2733
+ { name: "Stardom", category: "Premium", local: false, source: "fontshare", fontshareSlug: "stardom" },
2734
+ { name: "Telma", category: "Premium", local: false, source: "fontshare", fontshareSlug: "telma" },
2735
+ { name: "Nippo", category: "Premium", local: false, source: "fontshare", fontshareSlug: "nippo" },
2736
+ // ═══════════════════════════════════════════════════════════════════
2737
+ // SERIF — editorial, classical, elegant
2738
+ // ═══════════════════════════════════════════════════════════════════
2739
+ { name: "Playfair Display", category: "Serif", local: true, popular: true },
2740
+ { name: "Cormorant", category: "Serif", local: false, popular: true },
2741
+ { name: "Cormorant Garamond", category: "Serif", local: false },
2742
+ { name: "Cinzel", category: "Serif", local: false, popular: true },
2743
+ { name: "Cinzel Decorative", category: "Serif", local: false },
2744
+ { name: "Bodoni Moda", category: "Serif", local: false, popular: true },
2745
+ { name: "DM Serif Display", category: "Serif", local: true },
2746
+ { name: "DM Serif Text", category: "Serif", local: false },
2747
+ { name: "Italiana", category: "Serif", local: false },
2748
+ { name: "Marcellus", category: "Serif", local: false },
2749
+ { name: "Marcellus SC", category: "Serif", local: false },
2750
+ { name: "Yeseva One", category: "Serif", local: false },
2751
+ { name: "Prata", category: "Serif", local: false },
2752
+ { name: "Tenor Sans", category: "Serif", local: false },
2753
+ { name: "Fraunces", category: "Serif", local: false, popular: true },
2754
+ { name: "Newsreader", category: "Serif", local: false },
2755
+ { name: "Source Serif Pro", category: "Serif", local: false },
2756
+ { name: "Merriweather", category: "Serif", local: true },
2757
+ { name: "Lora", category: "Serif", local: true },
2758
+ { name: "EB Garamond", category: "Serif", local: true },
2759
+ { name: "Libre Baskerville", category: "Serif", local: true },
2760
+ { name: "Libre Caslon Text", category: "Serif", local: false },
2761
+ { name: "Libre Caslon Display", category: "Serif", local: false },
2762
+ { name: "Crimson Text", category: "Serif", local: true },
2763
+ { name: "Crimson Pro", category: "Serif", local: false },
2764
+ { name: "Noto Serif", category: "Serif", local: false },
2765
+ { name: "Noto Serif Display", category: "Serif", local: false },
2766
+ { name: "PT Serif", category: "Serif", local: false },
2767
+ { name: "Bitter", category: "Serif", local: false },
2768
+ { name: "Spectral", category: "Serif", local: false },
2769
+ { name: "Cardo", category: "Serif", local: false },
2770
+ { name: "Old Standard TT", category: "Serif", local: false },
2771
+ { name: "Vollkorn", category: "Serif", local: false },
2772
+ { name: "Cantata One", category: "Serif", local: false },
2773
+ { name: "Domine", category: "Serif", local: false },
2774
+ { name: "Gentium Plus", category: "Serif", local: false },
2775
+ { name: "Tinos", category: "Serif", local: false },
2776
+ { name: "Trirong", category: "Serif", local: false },
2777
+ { name: "Sorts Mill Goudy", category: "Serif", local: false },
2778
+ { name: "IM Fell English", category: "Serif", local: false },
2779
+ { name: "IM Fell DW Pica", category: "Serif", local: false },
2780
+ { name: "Petrona", category: "Serif", local: false },
2781
+ { name: "Rozha One", category: "Serif", local: false },
2782
+ { name: "Tiro Devanagari Hindi", category: "Serif", local: false },
2783
+ // ═══════════════════════════════════════════════════════════════════
2784
+ // SANS-SERIF — clean, modern, workhorse
2785
+ // ═══════════════════════════════════════════════════════════════════
2786
+ { name: "Inter", category: "Sans-Serif", local: true, popular: true },
2787
+ { name: "Montserrat", category: "Sans-Serif", local: true, popular: true },
2788
+ { name: "Poppins", category: "Sans-Serif", local: true, popular: true },
2789
+ { name: "Open Sans", category: "Sans-Serif", local: true },
2790
+ { name: "Roboto", category: "Sans-Serif", local: true },
2791
+ { name: "Lato", category: "Sans-Serif", local: true },
2792
+ { name: "Raleway", category: "Sans-Serif", local: true },
2793
+ { name: "Nunito", category: "Sans-Serif", local: true },
2794
+ { name: "Source Sans Pro", category: "Sans-Serif", local: true },
2795
+ { name: "Work Sans", category: "Sans-Serif", local: true },
2796
+ { name: "DM Sans", category: "Sans-Serif", local: true, popular: true },
2797
+ { name: "Outfit", category: "Sans-Serif", local: true, popular: true },
2798
+ { name: "Figtree", category: "Sans-Serif", local: true },
2799
+ { name: "Manrope", category: "Sans-Serif", local: true, popular: true },
2800
+ { name: "Space Grotesk", category: "Sans-Serif", local: true, popular: true },
2801
+ { name: "Mulish", category: "Sans-Serif", local: true },
2802
+ { name: "Quicksand", category: "Sans-Serif", local: true },
2803
+ { name: "Rubik", category: "Sans-Serif", local: true },
2804
+ { name: "Karla", category: "Sans-Serif", local: true },
2805
+ { name: "Plus Jakarta Sans", category: "Sans-Serif", local: true },
2806
+ { name: "Libre Franklin", category: "Sans-Serif", local: true },
2807
+ { name: "Sora", category: "Sans-Serif", local: true },
2808
+ { name: "Urbanist", category: "Sans-Serif", local: true },
2809
+ { name: "Lexend", category: "Sans-Serif", local: true },
2810
+ { name: "Albert Sans", category: "Sans-Serif", local: true },
2811
+ { name: "Noto Sans", category: "Sans-Serif", local: true },
2812
+ { name: "Cabin", category: "Sans-Serif", local: true },
2813
+ { name: "Barlow", category: "Sans-Serif", local: true },
2814
+ { name: "Barlow Condensed", category: "Sans-Serif", local: false },
2815
+ { name: "Josefin Sans", category: "Sans-Serif", local: true },
2816
+ { name: "Archivo", category: "Sans-Serif", local: true },
2817
+ { name: "Archivo Narrow", category: "Sans-Serif", local: false },
2818
+ { name: "Overpass", category: "Sans-Serif", local: true },
2819
+ { name: "Exo 2", category: "Sans-Serif", local: true },
2820
+ { name: "Onest", category: "Sans-Serif", local: false, popular: true },
2821
+ { name: "Be Vietnam Pro", category: "Sans-Serif", local: false },
2822
+ { name: "Public Sans", category: "Sans-Serif", local: false },
2823
+ { name: "Red Hat Display", category: "Sans-Serif", local: false },
2824
+ { name: "Red Hat Text", category: "Sans-Serif", local: false },
2825
+ { name: "Sen", category: "Sans-Serif", local: false },
2826
+ { name: "Hanken Grotesk", category: "Sans-Serif", local: false },
2827
+ { name: "Schibsted Grotesk", category: "Sans-Serif", local: false },
2828
+ { name: "Reddit Sans", category: "Sans-Serif", local: false },
2829
+ { name: "Instrument Sans", category: "Sans-Serif", local: false },
2830
+ { name: "Geist", category: "Sans-Serif", local: false, popular: true },
2831
+ { name: "Nunito Sans", category: "Sans-Serif", local: false },
2832
+ { name: "PT Sans", category: "Sans-Serif", local: false },
2833
+ { name: "PT Sans Narrow", category: "Sans-Serif", local: false },
2834
+ { name: "Mukta", category: "Sans-Serif", local: false },
2835
+ { name: "Anek Devanagari", category: "Sans-Serif", local: false },
2836
+ { name: "Hind", category: "Sans-Serif", local: true },
2837
+ { name: "Hind Vadodara", category: "Sans-Serif", local: false },
2838
+ // ═══════════════════════════════════════════════════════════════════
2839
+ // DISPLAY — bold, attention-grabbing headlines
2840
+ // ═══════════════════════════════════════════════════════════════════
2841
+ { name: "Bebas Neue", category: "Display", local: true, popular: true },
2842
+ { name: "Anton", category: "Display", local: true, popular: true },
2843
+ { name: "Oswald", category: "Display", local: true },
2844
+ { name: "Abril Fatface", category: "Display", local: true, popular: true },
2845
+ { name: "League Spartan", category: "Display", local: true },
2846
+ { name: "Teko", category: "Display", local: true },
2847
+ { name: "Righteous", category: "Display", local: false },
2848
+ { name: "Alfa Slab One", category: "Display", local: false, popular: true },
2849
+ { name: "Archivo Black", category: "Display", local: false },
2850
+ { name: "Fredoka", category: "Display", local: false },
2851
+ { name: "Passion One", category: "Display", local: false },
2852
+ { name: "Bowlby One", category: "Display", local: false },
2853
+ { name: "Bowlby One SC", category: "Display", local: false },
2854
+ { name: "Secular One", category: "Display", local: false },
2855
+ { name: "Lilita One", category: "Display", local: false },
2856
+ { name: "Titan One", category: "Display", local: false },
2857
+ { name: "Russo One", category: "Display", local: false },
2858
+ { name: "Staatliches", category: "Display", local: false, popular: true },
2859
+ { name: "Dela Gothic One", category: "Display", local: false },
2860
+ { name: "Ultra", category: "Display", local: false },
2861
+ { name: "Sigmar One", category: "Display", local: false },
2862
+ { name: "Sigmar", category: "Display", local: false },
2863
+ { name: "Modak", category: "Display", local: false },
2864
+ { name: "Bagel Fat One", category: "Display", local: false },
2865
+ { name: "Climate Crisis", category: "Display", local: false },
2866
+ { name: "Yatra One", category: "Display", local: false },
2867
+ { name: "Bungee", category: "Display", local: false },
2868
+ { name: "Bungee Shade", category: "Display", local: false },
2869
+ { name: "Bungee Outline", category: "Display", local: false },
2870
+ { name: "Bungee Inline", category: "Display", local: false },
2871
+ { name: "Monoton", category: "Display", local: false, popular: true },
2872
+ { name: "Black Ops One", category: "Display", local: false },
2873
+ { name: "Faster One", category: "Display", local: false },
2874
+ { name: "Rubik Glitch", category: "Display", local: false },
2875
+ { name: "Rubik Mono One", category: "Display", local: false },
2876
+ { name: "Rubik Wet Paint", category: "Display", local: false },
2877
+ { name: "Rubik Bubbles", category: "Display", local: false },
2878
+ { name: "Rubik Beastly", category: "Display", local: false },
2879
+ { name: "Rubik Burned", category: "Display", local: false },
2880
+ { name: "Rubik Distressed", category: "Display", local: false },
2881
+ { name: "Rubik Iso", category: "Display", local: false },
2882
+ { name: "Rubik Marker Hatch", category: "Display", local: false },
2883
+ { name: "Rubik Maze", category: "Display", local: false },
2884
+ { name: "Rubik Pixels", category: "Display", local: false },
2885
+ { name: "Rubik Puddles", category: "Display", local: false },
2886
+ { name: "Rubik Spray Paint", category: "Display", local: false },
2887
+ { name: "Rubik Vinyl", category: "Display", local: false },
2888
+ { name: "Saira Stencil One", category: "Display", local: false },
2889
+ { name: "Audiowide", category: "Display", local: false },
2890
+ { name: "Orbitron", category: "Display", local: false },
2891
+ { name: "Plaster", category: "Display", local: false },
2892
+ // ═══════════════════════════════════════════════════════════════════
2893
+ // HANDWRITING / SCRIPT — fluid, personal, calligraphic
2894
+ // ═══════════════════════════════════════════════════════════════════
2895
+ { name: "Dancing Script", category: "Handwriting", local: true, popular: true },
2896
+ { name: "Pacifico", category: "Handwriting", local: true, popular: true },
2897
+ { name: "Great Vibes", category: "Handwriting", local: true, popular: true },
2898
+ { name: "Sacramento", category: "Handwriting", local: true },
2899
+ { name: "Alex Brush", category: "Handwriting", local: true },
2900
+ { name: "Allura", category: "Handwriting", local: true },
2901
+ { name: "Caveat", category: "Handwriting", local: true },
2902
+ { name: "Caveat Brush", category: "Handwriting", local: false },
2903
+ { name: "Lobster", category: "Handwriting", local: true },
2904
+ { name: "Lobster Two", category: "Handwriting", local: false },
2905
+ { name: "Comfortaa", category: "Handwriting", local: true },
2906
+ { name: "Tangerine", category: "Handwriting", local: false, popular: true },
2907
+ { name: "Yellowtail", category: "Handwriting", local: false, popular: true },
2908
+ { name: "Kaushan Script", category: "Handwriting", local: false, popular: true },
2909
+ { name: "Parisienne", category: "Handwriting", local: false },
2910
+ { name: "Petit Formal Script", category: "Handwriting", local: false },
2911
+ { name: "Pinyon Script", category: "Handwriting", local: false },
2912
+ { name: "Mrs Saint Delafield", category: "Handwriting", local: false },
2913
+ { name: "Marck Script", category: "Handwriting", local: false },
2914
+ { name: "Niconne", category: "Handwriting", local: false },
2915
+ { name: "Homemade Apple", category: "Handwriting", local: false },
2916
+ { name: "Permanent Marker", category: "Handwriting", local: false },
2917
+ { name: "Reenie Beanie", category: "Handwriting", local: false },
2918
+ { name: "Satisfy", category: "Handwriting", local: false },
2919
+ { name: "Kalam", category: "Handwriting", local: false },
2920
+ { name: "Indie Flower", category: "Handwriting", local: false },
2921
+ { name: "Courgette", category: "Handwriting", local: false },
2922
+ { name: "Cookie", category: "Handwriting", local: false },
2923
+ { name: "Shadows Into Light", category: "Handwriting", local: false },
2924
+ { name: "Patrick Hand", category: "Handwriting", local: false },
2925
+ { name: "Amatic SC", category: "Handwriting", local: false },
2926
+ { name: "Architects Daughter", category: "Handwriting", local: false },
2927
+ { name: "Gloria Hallelujah", category: "Handwriting", local: false },
2928
+ { name: "La Belle Aurore", category: "Handwriting", local: false },
2929
+ { name: "Mr Dafoe", category: "Handwriting", local: false },
2930
+ { name: "Italianno", category: "Handwriting", local: false },
2931
+ { name: "Rouge Script", category: "Handwriting", local: false },
2932
+ { name: "Grand Hotel", category: "Handwriting", local: false },
2933
+ { name: "Bilbo Swash Caps", category: "Handwriting", local: false },
2934
+ // ═══════════════════════════════════════════════════════════════════
2935
+ // DECORATIVE / FUN — quirky, themed, special-occasion
2936
+ // ═══════════════════════════════════════════════════════════════════
2937
+ { name: "Frijole", category: "Decorative", local: false },
2938
+ { name: "Creepster", category: "Decorative", local: false },
2939
+ { name: "Nosifer", category: "Decorative", local: false },
2940
+ { name: "Ewert", category: "Decorative", local: false },
2941
+ { name: "Lakki Reddy", category: "Decorative", local: false },
2942
+ { name: "Henny Penny", category: "Decorative", local: false },
2943
+ { name: "Special Elite", category: "Decorative", local: false, popular: true },
2944
+ { name: "Vast Shadow", category: "Decorative", local: false },
2945
+ { name: "Almendra Display", category: "Decorative", local: false },
2946
+ { name: "Eater", category: "Decorative", local: false },
2947
+ { name: "Butcherman", category: "Decorative", local: false },
2948
+ { name: "Pirata One", category: "Decorative", local: false },
2949
+ { name: "Metamorphous", category: "Decorative", local: false },
2950
+ { name: "MedievalSharp", category: "Decorative", local: false },
2951
+ { name: "Fascinate", category: "Decorative", local: false },
2952
+ { name: "Fascinate Inline", category: "Decorative", local: false },
2953
+ { name: "Sancreek", category: "Decorative", local: false },
2954
+ { name: "Smokum", category: "Decorative", local: false },
2955
+ { name: "Vampiro One", category: "Decorative", local: false },
2956
+ { name: "Mountains of Christmas", category: "Decorative", local: false },
2957
+ { name: "Caesar Dressing", category: "Decorative", local: false },
2958
+ { name: "Megrim", category: "Decorative", local: false },
2959
+ // ═══════════════════════════════════════════════════════════════════
2960
+ // BLACKLETTER — gothic, medieval, formal
2961
+ // ═══════════════════════════════════════════════════════════════════
2962
+ { name: "UnifrakturCook", category: "Blackletter", local: false },
2963
+ { name: "UnifrakturMaguntia", category: "Blackletter", local: false },
2964
+ // ═══════════════════════════════════════════════════════════════════
2965
+ // MONOSPACE — code, technical
2966
+ // ═══════════════════════════════════════════════════════════════════
2967
+ { name: "Roboto Mono", category: "Monospace", local: true },
2968
+ { name: "Fira Code", category: "Monospace", local: true },
2969
+ { name: "JetBrains Mono", category: "Monospace", local: true },
2970
+ { name: "Source Code Pro", category: "Monospace", local: true },
2971
+ { name: "IBM Plex Mono", category: "Monospace", local: true },
2972
+ { name: "Space Mono", category: "Monospace", local: true },
2973
+ { name: "Geist Mono", category: "Monospace", local: false },
2974
+ { name: "DM Mono", category: "Monospace", local: false },
2975
+ { name: "Inconsolata", category: "Monospace", local: false },
2976
+ { name: "Cousine", category: "Monospace", local: false },
2977
+ { name: "Anonymous Pro", category: "Monospace", local: false },
2978
+ { name: "Cutive Mono", category: "Monospace", local: false },
2979
+ { name: "Major Mono Display", category: "Monospace", local: false },
2980
+ { name: "VT323", category: "Monospace", local: false },
2981
+ { name: "Share Tech Mono", category: "Monospace", local: false }
2982
+ ];
2983
+ const _fontLookupCache = /* @__PURE__ */ new Map();
2984
+ function findFontEntry(name) {
2985
+ const key = name.toLowerCase().replace(/[\s\-_]/g, "");
2986
+ if (_fontLookupCache.has(key)) return _fontLookupCache.get(key);
2987
+ const entry = EXTENDED_FONT_LIST.find(
2988
+ (f) => f.name.toLowerCase().replace(/[\s\-_]/g, "") === key
2989
+ );
2990
+ if (entry) _fontLookupCache.set(key, entry);
2991
+ return entry;
2992
+ }
2642
2993
  const getObjectId = (obj) => obj.__docuforgeId;
2643
2994
  const setObjectData = (obj, id) => {
2644
2995
  obj.__docuforgeId = id;
@@ -2860,9 +3211,27 @@ const ensureFontLoaded = async (fontFamily) => {
2860
3211
  return;
2861
3212
  }
2862
3213
  try {
2863
- await loadGoogleFont(fontFamily);
3214
+ await loadFont(fontFamily);
2864
3215
  } catch (e) {
2865
- console.warn(`Failed to load Google Font: ${fontFamily}`, e);
3216
+ console.warn(`Failed to load font: ${fontFamily}`, e);
3217
+ try {
3218
+ await loadGoogleFont(fontFamily);
3219
+ } catch {
3220
+ }
3221
+ }
3222
+ try {
3223
+ if (document.fonts) {
3224
+ await Promise.race([
3225
+ Promise.all([
3226
+ document.fonts.load(`16px "${fontFamily}"`),
3227
+ document.fonts.load(`bold 16px "${fontFamily}"`),
3228
+ document.fonts.load(`italic 16px "${fontFamily}"`),
3229
+ document.fonts.load(`bold italic 16px "${fontFamily}"`)
3230
+ ]),
3231
+ new Promise((r) => setTimeout(r, 2500))
3232
+ ]);
3233
+ }
3234
+ } catch {
2866
3235
  }
2867
3236
  };
2868
3237
  const setupFontLoadingListener = (canvas, afterRerender) => {
@@ -5363,6 +5732,172 @@ function buildRoundedTrianglePath(w, h, rTop, rBR, rBL) {
5363
5732
  ];
5364
5733
  return parts.join(" ");
5365
5734
  }
5735
+ let activeThemeColors = {};
5736
+ function setMarkdownThemeColors(c) {
5737
+ activeThemeColors = { ...c };
5738
+ }
5739
+ function resolveColorToken(token, theme) {
5740
+ const raw = token.trim();
5741
+ const t = raw.toLowerCase();
5742
+ if (t === "primary") return theme.primary;
5743
+ if (t === "secondary") return theme.secondary;
5744
+ if (/^#([0-9a-f]{3,8})$/i.test(raw)) return raw;
5745
+ if (/^(rgb|rgba|hsl|hsla)\(/i.test(raw)) return raw;
5746
+ if (/^[a-z]+$/i.test(raw)) return raw;
5747
+ return void 0;
5748
+ }
5749
+ function mergeStyle(a, b) {
5750
+ return { ...a, ...b };
5751
+ }
5752
+ function tokenize(input, theme) {
5753
+ const runs = [];
5754
+ const stack = [];
5755
+ let buf = "";
5756
+ const activeStyle = () => {
5757
+ let s = {};
5758
+ for (const e of stack) s = mergeStyle(s, e.style);
5759
+ return s;
5760
+ };
5761
+ const flush = () => {
5762
+ if (buf.length === 0) return;
5763
+ runs.push({ text: buf, style: activeStyle() });
5764
+ buf = "";
5765
+ };
5766
+ let i = 0;
5767
+ const n = input.length;
5768
+ const peek = (s, at = i) => input.startsWith(s, at);
5769
+ const findUnescaped = (needle, from) => {
5770
+ let p = from;
5771
+ while (p < n) {
5772
+ if (input[p] === "\\" && p + 1 < n) {
5773
+ p += 2;
5774
+ continue;
5775
+ }
5776
+ if (input.startsWith(needle, p)) return p;
5777
+ p++;
5778
+ }
5779
+ return -1;
5780
+ };
5781
+ const tryOpenBracket = () => {
5782
+ if (input[i] !== "[") return -1;
5783
+ const m = /^\[(c|bg)=([^\]]+)\]/.exec(input.slice(i));
5784
+ if (!m) return -1;
5785
+ const kind = m[1];
5786
+ const tokenRaw = m[2];
5787
+ const closer = kind === "c" ? "[/c]" : "[/bg]";
5788
+ if (findUnescaped(closer, i + m[0].length) === -1) return -1;
5789
+ const color = resolveColorToken(tokenRaw, theme);
5790
+ const style = {};
5791
+ if (color) {
5792
+ if (kind === "c") style.fill = color;
5793
+ else style.textBackgroundColor = color;
5794
+ }
5795
+ flush();
5796
+ stack.push({ kind, style, closer });
5797
+ return i + m[0].length;
5798
+ };
5799
+ const tryCloseBracket = () => {
5800
+ for (let s = stack.length - 1; s >= 0; s--) {
5801
+ const top = stack[s];
5802
+ if (top.kind !== "c" && top.kind !== "bg") continue;
5803
+ if (peek(top.closer)) {
5804
+ if (s !== stack.length - 1) stack.length = s + 1;
5805
+ flush();
5806
+ stack.pop();
5807
+ return i + top.closer.length;
5808
+ }
5809
+ break;
5810
+ }
5811
+ return -1;
5812
+ };
5813
+ const toggle = (delim, kind, style) => {
5814
+ if (!peek(delim)) return null;
5815
+ const topIdx = stack.findIndex((e) => e.kind === kind);
5816
+ if (topIdx >= 0) {
5817
+ if (topIdx !== stack.length - 1) return null;
5818
+ flush();
5819
+ stack.length = topIdx;
5820
+ return i + delim.length;
5821
+ }
5822
+ if (findUnescaped(delim, i + delim.length) === -1) return null;
5823
+ flush();
5824
+ stack.push({ kind, style, closer: delim });
5825
+ return i + delim.length;
5826
+ };
5827
+ while (i < n) {
5828
+ const ch = input[i];
5829
+ if (ch === "\\" && i + 1 < n) {
5830
+ buf += input[i + 1];
5831
+ i += 2;
5832
+ continue;
5833
+ }
5834
+ if (ch === "[") {
5835
+ const closed = tryCloseBracket();
5836
+ if (closed > 0) {
5837
+ i = closed;
5838
+ continue;
5839
+ }
5840
+ const opened = tryOpenBracket();
5841
+ if (opened > 0) {
5842
+ i = opened;
5843
+ continue;
5844
+ }
5845
+ }
5846
+ let next;
5847
+ if ((next = toggle("**", "bold", { fontWeight: 700 })) !== null) {
5848
+ i = next;
5849
+ continue;
5850
+ }
5851
+ if ((next = toggle("__", "under", { underline: true })) !== null) {
5852
+ i = next;
5853
+ continue;
5854
+ }
5855
+ if ((next = toggle("~~", "strike", { linethrough: true })) !== null) {
5856
+ i = next;
5857
+ continue;
5858
+ }
5859
+ if ((next = toggle("==", "highlight", { textBackgroundColor: theme.secondary || "#ffe066" })) !== null) {
5860
+ i = next;
5861
+ continue;
5862
+ }
5863
+ if ((next = toggle("*", "italic", { fontStyle: "italic" })) !== null) {
5864
+ i = next;
5865
+ continue;
5866
+ }
5867
+ buf += ch;
5868
+ i++;
5869
+ }
5870
+ flush();
5871
+ return runs;
5872
+ }
5873
+ function parseTextMarkdown(input, themeColors) {
5874
+ const theme = activeThemeColors;
5875
+ const runs = tokenize(input ?? "", theme);
5876
+ let plain = "";
5877
+ const styles = {};
5878
+ let lineIdx = 0;
5879
+ let charIdx = 0;
5880
+ let hasFormatting = false;
5881
+ for (const run of runs) {
5882
+ const styleHasContent = Object.keys(run.style).length > 0;
5883
+ if (styleHasContent) hasFormatting = true;
5884
+ for (const ch of run.text) {
5885
+ if (ch === "\n") {
5886
+ plain += "\n";
5887
+ lineIdx++;
5888
+ charIdx = 0;
5889
+ continue;
5890
+ }
5891
+ plain += ch;
5892
+ if (styleHasContent) {
5893
+ if (!styles[lineIdx]) styles[lineIdx] = {};
5894
+ styles[lineIdx][charIdx] = { ...run.style };
5895
+ }
5896
+ charIdx++;
5897
+ }
5898
+ }
5899
+ return { plainText: plain, styles, hasFormatting };
5900
+ }
5366
5901
  const roundDiag = (value) => {
5367
5902
  if (typeof value !== "number") return value;
5368
5903
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -5501,6 +6036,13 @@ function createText(element) {
5501
6036
  let fontSize = element.fontSize || 16;
5502
6037
  const minFontSize = element.minFontSize || 8;
5503
6038
  const maxLines = element.maxLines || 3;
6039
+ const formattingEnabled = element.formattingEnabled === true;
6040
+ let parsedStyles = {};
6041
+ if (formattingEnabled) {
6042
+ const parsed = parseTextMarkdown(text);
6043
+ text = parsed.plainText || " ";
6044
+ parsedStyles = parsed.styles;
6045
+ }
5504
6046
  const baseWidth = element.width && element.width > 0 ? element.width : 200;
5505
6047
  const baseHeight = element.height;
5506
6048
  const fixedWidth = Math.max(baseWidth, 1);
@@ -5521,7 +6063,8 @@ function createText(element) {
5521
6063
  fontStyle: element.fontStyle || "normal",
5522
6064
  lineHeight: element.lineHeight || 1.2,
5523
6065
  charSpacing: element.charSpacing || 0,
5524
- splitByGrapheme: false
6066
+ splitByGrapheme: false,
6067
+ ...formattingEnabled ? { styles: parsedStyles } : {}
5525
6068
  });
5526
6069
  testTextbox.initDimensions();
5527
6070
  const textHeight = testTextbox.height || 0;
@@ -5633,8 +6176,16 @@ function createText(element) {
5633
6176
  objectCaching: false,
5634
6177
  noScaleCache: true,
5635
6178
  splitByGrapheme,
5636
- ...element.styles ? { styles: element.styles } : {}
6179
+ // When inline markdown formatting is enabled, the displayed text is the
6180
+ // PARSED plain text (markdown source lives separately on the element).
6181
+ // Allowing canvas inline editing would let the user edit that plain text
6182
+ // and on commit we'd save it back as the new markdown source — wiping all
6183
+ // formatting tokens (**, __, [c=...], etc). Disable inline edit and steer
6184
+ // users to the right-panel text field which exposes the raw markdown.
6185
+ editable: !formattingEnabled,
6186
+ ...formattingEnabled ? { styles: parsedStyles } : element.styles ? { styles: element.styles } : {}
5637
6187
  });
6188
+ textbox.__formattingEnabled = formattingEnabled;
5638
6189
  textbox.initDimensions();
5639
6190
  textbox.set({
5640
6191
  width: targetWidth,
@@ -5655,6 +6206,15 @@ function createText(element) {
5655
6206
  textbox.setCoords();
5656
6207
  }
5657
6208
  textbox.dirty = true;
6209
+ if (formattingEnabled) {
6210
+ try {
6211
+ textbox._forceClearCache = true;
6212
+ if (typeof textbox._clearCache === "function") {
6213
+ textbox._clearCache();
6214
+ }
6215
+ } catch {
6216
+ }
6217
+ }
5658
6218
  if (overflowPolicy === "auto-shrink" && typeof window !== "undefined" && window.__pixldocsDebugAutoShrink === true) {
5659
6219
  console.log("[auto-shrink][final-textbox] " + stringifyDiag({
5660
6220
  id: element.id,
@@ -6391,6 +6951,10 @@ const PageCanvas = react.forwardRef(
6391
6951
  skipFontReadyWait = false,
6392
6952
  onReady
6393
6953
  }, ref) => {
6954
+ setMarkdownThemeColors({
6955
+ primary: projectSettings.primaryColor || "#7c3aed",
6956
+ secondary: projectSettings.secondaryColor || "#ffe066"
6957
+ });
6394
6958
  const isEditorMode = mode === "editor";
6395
6959
  const isPreviewMode = mode === "preview";
6396
6960
  const allowEditing = isEditorMode;
@@ -6486,8 +7050,8 @@ const PageCanvas = react.forwardRef(
6486
7050
  }, [selectedIds]);
6487
7051
  react.useEffect(() => {
6488
7052
  isActiveRef.current = isActive;
6489
- if (isActive && fabricRef.current) ;
6490
- }, [isActive, pageId]);
7053
+ if (isEditorMode && isActive && fabricRef.current) ;
7054
+ }, [isActive, isEditorMode, pageId]);
6491
7055
  const getObjId = react.useCallback((obj) => {
6492
7056
  return obj.__docuforgeId;
6493
7057
  }, []);
@@ -6625,11 +7189,18 @@ const PageCanvas = react.forwardRef(
6625
7189
  const targetWidth = Math.max(1, Number(element.width) > 0 ? Number(element.width) : Number(obj.width ?? 200));
6626
7190
  const overflowPolicy = element.overflowPolicy || "grow-and-push";
6627
7191
  const splitByGrapheme = overflowPolicy === "auto-shrink" ? false : element.splitByGrapheme ?? element.wordWrap === "break-word";
7192
+ let reflowText = element.text || "Text";
7193
+ let reflowParsedStyles = null;
7194
+ if (element.formattingEnabled === true) {
7195
+ const parsed = parseTextMarkdown(reflowText);
7196
+ reflowText = parsed.plainText || " ";
7197
+ reflowParsedStyles = parsed.styles;
7198
+ }
6628
7199
  obj.set({
6629
7200
  width: targetWidth,
6630
7201
  minWidth: 1,
6631
7202
  dynamicMinWidth: 0,
6632
- text: element.text || "Text",
7203
+ text: reflowText,
6633
7204
  fontSize: element.fontSize || 16,
6634
7205
  fontFamily: element.fontFamily || "Open Sans",
6635
7206
  fontWeight: element.fontWeight || 400,
@@ -6638,6 +7209,11 @@ const PageCanvas = react.forwardRef(
6638
7209
  charSpacing: element.charSpacing || 0,
6639
7210
  splitByGrapheme
6640
7211
  });
7212
+ if (element.formattingEnabled === true) {
7213
+ obj.styles = reflowParsedStyles || {};
7214
+ }
7215
+ obj.editable = element.formattingEnabled !== true;
7216
+ obj.__formattingEnabled = element.formattingEnabled === true;
6641
7217
  obj.initDimensions();
6642
7218
  if (Math.abs((obj.width ?? 0) - targetWidth) > 0.01) {
6643
7219
  obj.width = targetWidth;
@@ -6645,10 +7221,26 @@ const PageCanvas = react.forwardRef(
6645
7221
  obj.dynamicMinWidth = 0;
6646
7222
  obj.setCoords();
6647
7223
  obj.dirty = true;
7224
+ try {
7225
+ obj._forceClearCache = true;
7226
+ if (typeof obj._clearCache === "function") obj._clearCache();
7227
+ } catch {
7228
+ }
6648
7229
  didReflow = true;
6649
7230
  };
6650
7231
  canvas2.getObjects().forEach(reflowObject);
6651
- if (didReflow) canvas2.requestRenderAll();
7232
+ if (didReflow) {
7233
+ canvas2.requestRenderAll();
7234
+ try {
7235
+ requestAnimationFrame(() => {
7236
+ try {
7237
+ canvas2.requestRenderAll();
7238
+ } catch {
7239
+ }
7240
+ });
7241
+ } catch {
7242
+ }
7243
+ }
6652
7244
  }, []);
6653
7245
  react.useEffect(() => {
6654
7246
  if (!canvasElRef.current) return;
@@ -6695,7 +7287,8 @@ const PageCanvas = react.forwardRef(
6695
7287
  absolutePositioned: true
6696
7288
  });
6697
7289
  fabricRef.current = fabricCanvas;
6698
- registerFabricCanvas(pageId, fabricCanvas);
7290
+ const storeRegistryKey = registerFabricCanvas(pageId, fabricCanvas);
7291
+ fabricCanvas.__storeRegistryKey = storeRegistryKey;
6699
7292
  const initFonts = async () => {
6700
7293
  try {
6701
7294
  await preloadAllFonts();
@@ -6988,7 +7581,10 @@ const PageCanvas = react.forwardRef(
6988
7581
  if (!window.__fabricCanvasRegistry || !(window.__fabricCanvasRegistry instanceof Map)) {
6989
7582
  window.__fabricCanvasRegistry = /* @__PURE__ */ new Map();
6990
7583
  }
6991
- window.__fabricCanvasRegistry.set(pageId, {
7584
+ const reg = window.__fabricCanvasRegistry;
7585
+ const registryKey = reg.has(pageId) ? `${pageId}#${Math.random().toString(36).slice(2, 10)}` : pageId;
7586
+ fabricCanvas.__registryKey = registryKey;
7587
+ reg.set(registryKey, {
6992
7588
  canvas: fabricCanvas,
6993
7589
  forceUnlockEdits
6994
7590
  });
@@ -7758,9 +8354,6 @@ const PageCanvas = react.forwardRef(
7758
8354
  scaleY: finalScaleY,
7759
8355
  transformMatrix: finalAbsoluteMatrix
7760
8356
  };
7761
- if (obj instanceof fabric__namespace.Textbox) {
7762
- elementUpdate.text = obj.text || "";
7763
- }
7764
8357
  if (sourceElement && sourceElement.opacity !== void 0) {
7765
8358
  elementUpdate.opacity = sourceElement.opacity;
7766
8359
  }
@@ -7839,6 +8432,12 @@ const PageCanvas = react.forwardRef(
7839
8432
  }
7840
8433
  if (target && target instanceof fabric__namespace.Textbox) {
7841
8434
  const elementId = getObjectId(target);
8435
+ if (target.__formattingEnabled === true || target.editable === false) {
8436
+ sonner.toast.info("Inline formatting is on — edit the text in the right panel.", {
8437
+ description: "This protects your **bold**, [c=...] and other formatting tokens."
8438
+ });
8439
+ return;
8440
+ }
7842
8441
  editingTextIdRef.current = elementId || null;
7843
8442
  target.enterEditing();
7844
8443
  target.selectAll();
@@ -7903,7 +8502,19 @@ const PageCanvas = react.forwardRef(
7903
8502
  });
7904
8503
  return () => {
7905
8504
  setReady(false);
7906
- unregisterFabricCanvas(pageId);
8505
+ unregisterFabricCanvas(fabricCanvas.__storeRegistryKey ?? pageId, fabricCanvas);
8506
+ try {
8507
+ if (typeof window !== "undefined") {
8508
+ const reg = window.__fabricCanvasRegistry;
8509
+ if (reg instanceof Map) {
8510
+ const key = fabricCanvas.__registryKey ?? pageId;
8511
+ const entry = reg.get(key);
8512
+ const entryCanvas = (entry == null ? void 0 : entry.canvas) ?? entry;
8513
+ if (entryCanvas === fabricCanvas) reg.delete(key);
8514
+ }
8515
+ }
8516
+ } catch {
8517
+ }
7907
8518
  if (fabricCanvas.__fontCleanup) {
7908
8519
  fabricCanvas.__fontCleanup();
7909
8520
  }
@@ -9265,6 +9876,12 @@ const PageCanvas = react.forwardRef(
9265
9876
  } else if (obj instanceof fabric__namespace.Textbox) {
9266
9877
  const overflowPolicy = element.overflowPolicy || "grow-and-push";
9267
9878
  let text = element.text || "Text";
9879
+ let parsedStyles = null;
9880
+ if (element.formattingEnabled === true) {
9881
+ const parsed = parseTextMarkdown(text);
9882
+ text = parsed.plainText || " ";
9883
+ parsedStyles = parsed.styles;
9884
+ }
9268
9885
  let fontSize = element.fontSize || 16;
9269
9886
  element.minFontSize || 8;
9270
9887
  const maxLines = element.maxLines || 3;
@@ -9361,6 +9978,11 @@ const PageCanvas = react.forwardRef(
9361
9978
  splitByGrapheme,
9362
9979
  text
9363
9980
  });
9981
+ if (element.formattingEnabled === true) {
9982
+ obj.styles = parsedStyles || {};
9983
+ } else {
9984
+ obj.styles = element.styles || {};
9985
+ }
9364
9986
  obj.initDimensions();
9365
9987
  if (Math.abs((obj.width ?? 0) - textboxWidth) > 0.01) {
9366
9988
  obj.width = textboxWidth;
@@ -10365,6 +10987,7 @@ function PreviewCanvas({
10365
10987
  zoom = 1,
10366
10988
  absoluteZoom = false,
10367
10989
  skipFontReadyWait = false,
10990
+ pageIdOverride,
10368
10991
  className,
10369
10992
  onDynamicFieldClick,
10370
10993
  onReady
@@ -10431,13 +11054,19 @@ function PreviewCanvas({
10431
11054
  backgroundGradient: (_b2 = page == null ? void 0 : page.settings) == null ? void 0 : _b2.backgroundGradient
10432
11055
  };
10433
11056
  }, [(_d = page == null ? void 0 : page.settings) == null ? void 0 : _d.backgroundColor, (_e = page == null ? void 0 : page.settings) == null ? void 0 : _e.backgroundGradient]);
10434
- const projectSettings = react.useMemo(() => ({
10435
- showGrid: false,
10436
- snapToGrid: false,
10437
- gridSize: 10,
10438
- snapToGuides: false,
10439
- snapThreshold: 5
10440
- }), []);
11057
+ const projectSettings = react.useMemo(() => {
11058
+ var _a2, _b2, _c2;
11059
+ const vars = ((_a2 = config.themeConfig) == null ? void 0 : _a2.variables) || {};
11060
+ return {
11061
+ showGrid: false,
11062
+ snapToGrid: false,
11063
+ gridSize: 10,
11064
+ snapToGuides: false,
11065
+ snapThreshold: 5,
11066
+ primaryColor: (_b2 = vars.primary) == null ? void 0 : _b2.value,
11067
+ secondaryColor: (_c2 = vars.secondary) == null ? void 0 : _c2.value
11068
+ };
11069
+ }, [config.themeConfig]);
10441
11070
  const handleDynamicFieldClick = react.useCallback((elementId) => {
10442
11071
  const fieldInfo = elementToFieldMap.get(elementId);
10443
11072
  if (fieldInfo && onDynamicFieldClick) {
@@ -10507,7 +11136,7 @@ function PreviewCanvas({
10507
11136
  PageCanvas,
10508
11137
  {
10509
11138
  ref: canvasRef,
10510
- pageId: page.id || `page-${pageIndex}`,
11139
+ pageId: pageIdOverride || page.id || `page-${pageIndex}`,
10511
11140
  elements,
10512
11141
  pageChildren: laidOutPageChildren,
10513
11142
  pageSettings,
@@ -12398,6 +13027,17 @@ function normalizeFontFamily(fontStack) {
12398
13027
  const first = fontStack.split(",")[0].trim();
12399
13028
  return first.replace(/^['"]|['"]$/g, "");
12400
13029
  }
13030
+ function appendStylesheet(url, rejectOnError = true) {
13031
+ return new Promise((resolve, reject) => {
13032
+ const link = document.createElement("link");
13033
+ link.rel = "stylesheet";
13034
+ link.href = url;
13035
+ link.crossOrigin = "anonymous";
13036
+ link.onload = () => resolve();
13037
+ link.onerror = () => rejectOnError ? reject(new Error(`Failed to load stylesheet: ${url}`)) : resolve();
13038
+ document.head.appendChild(link);
13039
+ });
13040
+ }
12401
13041
  const loadedFonts = /* @__PURE__ */ new Set();
12402
13042
  const loadingPromises = /* @__PURE__ */ new Map();
12403
13043
  function withTimeout(promise, timeoutMs = 4e3) {
@@ -12423,28 +13063,19 @@ async function loadGoogleFontCSS(rawFontFamily) {
12423
13063
  const fontshareSlug = FONTSHARE_SLUGS[fontFamily];
12424
13064
  if (fontshareSlug) {
12425
13065
  const url2 = `https://api.fontshare.com/v2/css?f[]=${fontshareSlug}@300,400,500,700&display=swap`;
12426
- const link2 = document.createElement("link");
12427
- link2.rel = "stylesheet";
12428
- link2.href = url2;
12429
- await new Promise((resolve, reject) => {
12430
- link2.onload = () => resolve();
12431
- link2.onerror = () => reject(new Error(`Failed to load Fontshare font: ${fontFamily}`));
12432
- document.head.appendChild(link2);
12433
- });
13066
+ await appendStylesheet(url2);
13067
+ const italicUrl = `https://api.fontshare.com/v2/css?f[]=${fontshareSlug}@300i,400i,500i,700i&display=swap`;
13068
+ await withTimeout(appendStylesheet(italicUrl, false), 1500);
12434
13069
  loadedFonts.add(fontFamily);
12435
13070
  return;
12436
13071
  }
12437
13072
  const encoded = encodeURIComponent(fontFamily);
12438
13073
  const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
12439
- const link = document.createElement("link");
12440
- link.rel = "stylesheet";
12441
- link.href = url;
12442
- link.crossOrigin = "anonymous";
12443
- await new Promise((resolve, reject) => {
12444
- link.onload = () => resolve();
12445
- link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
12446
- document.head.appendChild(link);
12447
- });
13074
+ await appendStylesheet(url);
13075
+ await withTimeout(Promise.all([300, 400, 500, 600, 700].map((weight) => {
13076
+ const italicUrl = `https://fonts.googleapis.com/css2?family=${encoded}:ital,wght@1,${weight}&display=swap`;
13077
+ return appendStylesheet(italicUrl, false);
13078
+ })), 1800);
12448
13079
  loadedFonts.add(fontFamily);
12449
13080
  } catch (e) {
12450
13081
  console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
@@ -12514,6 +13145,23 @@ function collectFontDescriptorsFromConfig(config) {
12514
13145
  if (node.type === "text") {
12515
13146
  for (const w of [300, 400, 500, 600, 700]) {
12516
13147
  add(node.fontFamily, w, node.fontStyle);
13148
+ add(node.fontFamily, w, "italic");
13149
+ }
13150
+ }
13151
+ }
13152
+ if (node.formattingEnabled === true && node.fontFamily) {
13153
+ const parsed = parseTextMarkdown(String(node.text ?? ""));
13154
+ const parsedStyleEntries = Object.values(parsed.styles || {});
13155
+ for (const lineStyle of parsedStyleEntries) {
13156
+ if (lineStyle && typeof lineStyle === "object") {
13157
+ for (const charStyle of Object.values(lineStyle)) {
13158
+ if (!charStyle || typeof charStyle !== "object") continue;
13159
+ add(
13160
+ charStyle.fontFamily || node.fontFamily,
13161
+ charStyle.fontWeight ?? node.fontWeight,
13162
+ charStyle.fontStyle ?? node.fontStyle
13163
+ );
13164
+ }
12517
13165
  }
12518
13166
  }
12519
13167
  }
@@ -13423,11 +14071,20 @@ function PixldocsPreview(props) {
13423
14071
  !canvasSettled && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) })
13424
14072
  ] });
13425
14073
  }
13426
- const PACKAGE_VERSION = "0.5.104";
14074
+ const PACKAGE_VERSION = "0.5.105";
13427
14075
  const roundParityValue = (value) => {
13428
14076
  if (typeof value !== "number") return value;
13429
14077
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
13430
14078
  };
14079
+ function isolatePageForCapture(config, pageIndex) {
14080
+ var _a;
14081
+ const capturePageId = `__pixldocs_capture_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}_${pageIndex}`;
14082
+ const cloned = JSON.parse(JSON.stringify(config));
14083
+ if ((_a = cloned.pages) == null ? void 0 : _a[pageIndex]) {
14084
+ cloned.pages[pageIndex].id = capturePageId;
14085
+ }
14086
+ return { config: cloned, pageId: capturePageId };
14087
+ }
13431
14088
  function logJsonLine(tag, payload) {
13432
14089
  try {
13433
14090
  console.log(`${tag} ${JSON.stringify(payload, (_key, value) => roundParityValue(value))}`);
@@ -13465,6 +14122,9 @@ function installUnderlineFix(fab) {
13465
14122
  const hasOwn = !!this[type];
13466
14123
  const hasStyled = typeof this.styleHas === "function" && this.styleHas(type);
13467
14124
  if (!hasOwn && !hasStyled) return;
14125
+ if (!hasOwn && hasStyled) {
14126
+ return original.call(this, ctx, type);
14127
+ }
13468
14128
  const lines = this._textLines;
13469
14129
  const offsets = this.offsets;
13470
14130
  if (!Array.isArray(lines) || !offsets) {
@@ -13476,7 +14136,7 @@ function installUnderlineFix(fab) {
13476
14136
  let topOffset = this._getTopOffset();
13477
14137
  for (let i = 0, len = lines.length; i < len; i++) {
13478
14138
  const heightOfLine = this.getHeightOfLine(i);
13479
- const lineHas = !!this[type] || typeof this.styleHas === "function" && this.styleHas(type, i);
14139
+ const lineHas = !!this[type];
13480
14140
  if (!lineHas) {
13481
14141
  topOffset += heightOfLine;
13482
14142
  continue;
@@ -14006,9 +14666,11 @@ class PixldocsRenderer {
14006
14666
  }
14007
14667
  async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality, options = {}) {
14008
14668
  const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
14009
- const canvasWidth = config.canvas.width;
14010
- const canvasHeight = config.canvas.height;
14011
- const hasAutoShrink = configHasAutoShrinkText(config);
14669
+ const capture = isolatePageForCapture(config, pageIndex);
14670
+ const renderConfig = capture.config;
14671
+ const canvasWidth = renderConfig.canvas.width;
14672
+ const canvasHeight = renderConfig.canvas.height;
14673
+ const hasAutoShrink = configHasAutoShrinkText(renderConfig);
14012
14674
  let firstMountSettled = false;
14013
14675
  let lateFontSettleDetected = false;
14014
14676
  if (typeof document !== "undefined" && document.fonts && hasAutoShrink) {
@@ -14056,12 +14718,12 @@ class PixldocsRenderer {
14056
14718
  root = client.createRoot(container);
14057
14719
  await new Promise((settle) => {
14058
14720
  const onReadyOnce = () => {
14059
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14721
+ this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
14060
14722
  const fabricInstance = this.getFabricCanvasFromContainer(container);
14061
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14723
+ const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
14062
14724
  await this.waitForCanvasImages(container, expectedImageCount);
14063
- await this.waitForStableTextMetrics(container, config);
14064
- await this.waitForCanvasScene(container, config, pageIndex);
14725
+ await this.waitForStableTextMetrics(container, renderConfig);
14726
+ await this.waitForCanvasScene(container, renderConfig, pageIndex);
14065
14727
  if (!fabricInstance) return settle();
14066
14728
  settle();
14067
14729
  }).catch(() => settle());
@@ -14069,8 +14731,9 @@ class PixldocsRenderer {
14069
14731
  root.render(
14070
14732
  react.createElement(PreviewCanvas2, {
14071
14733
  key: `remount-${mountKey}`,
14072
- config,
14734
+ config: renderConfig,
14073
14735
  pageIndex,
14736
+ pageIdOverride: capture.pageId,
14074
14737
  zoom: pixelRatio,
14075
14738
  absoluteZoom: true,
14076
14739
  skipFontReadyWait: false,
@@ -14080,13 +14743,13 @@ class PixldocsRenderer {
14080
14743
  });
14081
14744
  };
14082
14745
  const onReady = () => {
14083
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14746
+ this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
14084
14747
  try {
14085
14748
  const fabricInstance = this.getFabricCanvasFromContainer(container);
14086
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14749
+ const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
14087
14750
  await this.waitForCanvasImages(container, expectedImageCount);
14088
- await this.waitForStableTextMetrics(container, config);
14089
- await this.waitForCanvasScene(container, config, pageIndex);
14751
+ await this.waitForStableTextMetrics(container, renderConfig);
14752
+ await this.waitForCanvasScene(container, renderConfig, pageIndex);
14090
14753
  firstMountSettled = true;
14091
14754
  if (hasAutoShrink && lateFontSettleDetected) {
14092
14755
  console.log("[canvas-renderer][parity] late font-settle detected — remounting for auto-shrink reflow");
@@ -14112,7 +14775,7 @@ class PixldocsRenderer {
14112
14775
  }
14113
14776
  exportCtx.save();
14114
14777
  exportCtx.scale(sourceCanvasAfter.width / canvasWidth, sourceCanvasAfter.height / canvasHeight);
14115
- this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
14778
+ this.paintPageBackground(exportCtx, renderConfig.pages[pageIndex], canvasWidth, canvasHeight);
14116
14779
  exportCtx.restore();
14117
14780
  exportCtx.drawImage(sourceCanvasAfter, 0, 0);
14118
14781
  const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
@@ -14128,8 +14791,9 @@ class PixldocsRenderer {
14128
14791
  root = client.createRoot(container);
14129
14792
  root.render(
14130
14793
  react.createElement(PreviewCanvas2, {
14131
- config,
14794
+ config: renderConfig,
14132
14795
  pageIndex,
14796
+ pageIdOverride: capture.pageId,
14133
14797
  zoom: pixelRatio,
14134
14798
  absoluteZoom: true,
14135
14799
  skipFontReadyWait: false,
@@ -14150,6 +14814,8 @@ class PixldocsRenderer {
14150
14814
  captureSvgViaPreviewCanvas(config, pageIndex, canvasWidth, canvasHeight) {
14151
14815
  return new Promise(async (resolve, reject) => {
14152
14816
  const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
14817
+ const capture = isolatePageForCapture(config, pageIndex);
14818
+ const renderConfig = capture.config;
14153
14819
  const container = document.createElement("div");
14154
14820
  container.style.cssText = `
14155
14821
  position: fixed; left: -99999px; top: -99999px;
@@ -14176,8 +14842,9 @@ class PixldocsRenderer {
14176
14842
  root.render(
14177
14843
  react.createElement(PreviewCanvas2, {
14178
14844
  key: `svg-capture-${mountKey}`,
14179
- config,
14845
+ config: renderConfig,
14180
14846
  pageIndex,
14847
+ pageIdOverride: capture.pageId,
14181
14848
  zoom: 1,
14182
14849
  absoluteZoom: true,
14183
14850
  skipFontReadyWait: false,
@@ -14186,13 +14853,13 @@ class PixldocsRenderer {
14186
14853
  );
14187
14854
  };
14188
14855
  const onReady = () => {
14189
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14856
+ this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
14190
14857
  var _a, _b;
14191
14858
  try {
14192
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14859
+ const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
14193
14860
  await this.waitForCanvasImages(container, expectedImageCount);
14194
- await this.waitForStableTextMetrics(container, config);
14195
- await this.waitForCanvasScene(container, config, pageIndex);
14861
+ await this.waitForStableTextMetrics(container, renderConfig);
14862
+ await this.waitForCanvasScene(container, renderConfig, pageIndex);
14196
14863
  const fabricInstance = this.getFabricCanvasFromContainer(container);
14197
14864
  if (!fabricInstance) {
14198
14865
  cleanup();
@@ -14281,7 +14948,7 @@ class PixldocsRenderer {
14281
14948
  );
14282
14949
  if (prevVPT) fabricInstance.viewportTransform = prevVPT;
14283
14950
  fabricInstance.svgViewportTransformation = prevSvgVPT;
14284
- const page = config.pages[pageIndex];
14951
+ const page = renderConfig.pages[pageIndex];
14285
14952
  const backgroundColor = ((_a = page == null ? void 0 : page.settings) == null ? void 0 : _a.backgroundColor) || "#ffffff";
14286
14953
  const backgroundGradient = (_b = page == null ? void 0 : page.settings) == null ? void 0 : _b.backgroundGradient;
14287
14954
  cleanup();
@@ -14963,8 +15630,9 @@ function getFontPathForWeight(files, weight, isItalic = false) {
14963
15630
  }
14964
15631
  return files.regular;
14965
15632
  }
14966
- function isItalicPath(files, path) {
14967
- return path === files.italic || path === files.boldItalic || path === files.lightItalic || path === files.mediumItalic || path === files.semiboldItalic;
15633
+ function isExactWeightItalicMatch(files, weight, isItalic, path) {
15634
+ const exactKey = isItalic ? weight === 300 ? "lightItalic" : weight === 500 ? "mediumItalic" : weight === 600 ? "semiboldItalic" : weight === 700 ? "boldItalic" : "italic" : weight === 300 ? "light" : weight === 500 ? "medium" : weight === 600 ? "semibold" : weight === 700 ? "bold" : "regular";
15635
+ return files[exactKey] === path;
14968
15636
  }
14969
15637
  function getJsPDFFontName(fontName) {
14970
15638
  return fontName.replace(/\s+/g, "");
@@ -15037,10 +15705,10 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
15037
15705
  const resolvedWeight = resolveFontWeight(weight);
15038
15706
  const fontPath = getFontPathForWeight(fontFiles, resolvedWeight, isItalic);
15039
15707
  if (!fontPath) return false;
15040
- const hasItalicFile = isItalic && isItalicPath(fontFiles, fontPath);
15041
- const jsPdfFontName = getEmbeddedJsPDFFontName(fontName, weight, hasItalicFile);
15708
+ if (!isExactWeightItalicMatch(fontFiles, resolvedWeight, isItalic, fontPath)) return false;
15709
+ const jsPdfFontName = getEmbeddedJsPDFFontName(fontName, weight, isItalic);
15042
15710
  const label = FONT_WEIGHT_LABELS[resolvedWeight];
15043
- const italicSuffix = hasItalicFile ? "Italic" : "";
15711
+ const italicSuffix = isItalic ? "Italic" : "";
15044
15712
  const fileName = `${getJsPDFFontName(fontName)}-${label}${italicSuffix}.ttf`;
15045
15713
  const url = baseUrl + fontPath;
15046
15714
  try {
@@ -15055,6 +15723,7 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
15055
15723
  }
15056
15724
  }
15057
15725
  registeredFamilies.add(fontName);
15726
+ registeredVariants.add(variantKey(fontName, resolvedWeight, isItalic));
15058
15727
  return true;
15059
15728
  } catch (e) {
15060
15729
  console.warn(`[pdf-fonts] Failed to embed ${fontName} w${weight}:`, e);
@@ -15063,6 +15732,18 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
15063
15732
  }
15064
15733
  const googleFontNotFound = /* @__PURE__ */ new Set();
15065
15734
  const fontshareNotFound = /* @__PURE__ */ new Set();
15735
+ const remoteVariantKey = (family, weight, isItalic) => `${family}|${resolveFontWeight(weight)}|${isItalic ? "i" : "n"}`;
15736
+ const registeredVariants = /* @__PURE__ */ new Set();
15737
+ const variantKey = (family, weight, italic) => `${family}|${resolveFontWeight(weight)}|${italic ? "i" : "n"}`;
15738
+ const resolveBestRegisteredVariant = (family, weight, italic) => {
15739
+ const want = resolveFontWeight(weight);
15740
+ const weights = [300, 400, 500, 600, 700];
15741
+ if (registeredVariants.has(variantKey(family, want, italic))) return { weight: want, italic };
15742
+ const nearest = [...weights].sort((a, b) => Math.abs(a - want) - Math.abs(b - want) || (want >= 500 ? b - a : a - b));
15743
+ for (const w of nearest) if (registeredVariants.has(variantKey(family, w, italic))) return { weight: w, italic };
15744
+ for (const w of nearest) if (registeredVariants.has(variantKey(family, w, !italic))) return { weight: w, italic: !italic };
15745
+ return null;
15746
+ };
15066
15747
  function bytesToBase64(bytes) {
15067
15748
  let binary = "";
15068
15749
  for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
@@ -15091,7 +15772,8 @@ async function fetchTtfViaProxy(family, weight, isItalic, source) {
15091
15772
  async function fetchGoogleFontTTF(fontFamily, weight, isItalic = false) {
15092
15773
  const cacheKey = `gf:${fontFamily}:${weight}:${isItalic ? "i" : "n"}`;
15093
15774
  if (ttfCache.has(cacheKey)) return ttfCache.get(cacheKey);
15094
- if (googleFontNotFound.has(fontFamily)) return null;
15775
+ const notFoundKey = remoteVariantKey(fontFamily, weight, isItalic);
15776
+ if (googleFontNotFound.has(notFoundKey)) return null;
15095
15777
  const proxyBytes = await fetchTtfViaProxy(fontFamily, weight, isItalic, "google");
15096
15778
  if (proxyBytes) {
15097
15779
  const b64 = bytesToBase64(proxyBytes);
@@ -15109,7 +15791,7 @@ async function fetchGoogleFontTTF(fontFamily, weight, isItalic = false) {
15109
15791
  }
15110
15792
  });
15111
15793
  if (!cssRes.ok) {
15112
- if (cssRes.status === 400 || cssRes.status === 404) googleFontNotFound.add(fontFamily);
15794
+ if (cssRes.status === 400 || cssRes.status === 404) googleFontNotFound.add(notFoundKey);
15113
15795
  return null;
15114
15796
  }
15115
15797
  const css = await cssRes.text();
@@ -15187,6 +15869,7 @@ function registerJsPdfFont(pdf, fontName, resolvedWeight, isItalic, base64) {
15187
15869
  }
15188
15870
  }
15189
15871
  registeredFamilies.add(fontName);
15872
+ registeredVariants.add(variantKey(fontName, resolvedWeight, isItalic));
15190
15873
  return true;
15191
15874
  } catch (err) {
15192
15875
  console.warn(`[pdf-fonts] registerJsPdfFont failed for ${fontName}:`, err);
@@ -15201,16 +15884,8 @@ async function embedFontWithGoogleFallback(pdf, fontName, weight = 400, fontBase
15201
15884
  const resolved = resolveFontWeight(weight);
15202
15885
  const fsB64 = await fetchFontshareTTF(fontName, resolved, isItalic);
15203
15886
  if (fsB64) return registerJsPdfFont(pdf, fontName, resolved, isItalic, fsB64);
15204
- if (isItalic) {
15205
- const fsUpright = await fetchFontshareTTF(fontName, resolved, false);
15206
- if (fsUpright) return registerJsPdfFont(pdf, fontName, resolved, isItalic, fsUpright);
15207
- }
15208
15887
  const b64 = await fetchGoogleFontTTF(fontName, resolved, isItalic);
15209
15888
  if (b64) return registerJsPdfFont(pdf, fontName, resolved, isItalic, b64);
15210
- if (isItalic) {
15211
- const uprightB64 = await fetchGoogleFontTTF(fontName, resolved, false);
15212
- if (uprightB64) return registerJsPdfFont(pdf, fontName, resolved, isItalic, uprightB64);
15213
- }
15214
15889
  return false;
15215
15890
  }
15216
15891
  async function embedFontsForConfig(pdf, config, fontBaseUrl) {
@@ -15230,9 +15905,15 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
15230
15905
  };
15231
15906
  const walkElements = (elements) => {
15232
15907
  for (const el of elements) {
15908
+ const addFontVariant = (family, weight, italic) => {
15909
+ fontKeys.add(`${family}${SEP}${weight}${SEP}${italic ? "italic" : "normal"}`);
15910
+ };
15233
15911
  if (el.fontFamily) {
15234
15912
  const w = normalizeWeight(el.fontWeight);
15235
- fontKeys.add(`${el.fontFamily}${SEP}${w}`);
15913
+ addFontVariant(el.fontFamily, w, /italic|oblique/i.test(String(el.fontStyle ?? "")));
15914
+ addFontVariant(el.fontFamily, 700, false);
15915
+ addFontVariant(el.fontFamily, 400, true);
15916
+ addFontVariant(el.fontFamily, 700, true);
15236
15917
  }
15237
15918
  if (el.styles && typeof el.styles === "object") {
15238
15919
  for (const lineKey of Object.keys(el.styles)) {
@@ -15240,9 +15921,10 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
15240
15921
  if (lineStyles && typeof lineStyles === "object") {
15241
15922
  for (const charKey of Object.keys(lineStyles)) {
15242
15923
  const s = lineStyles[charKey];
15243
- if (s == null ? void 0 : s.fontFamily) {
15244
- const w = normalizeWeight(s.fontWeight);
15245
- fontKeys.add(`${s.fontFamily}${SEP}${w}`);
15924
+ const styledFamily = (s == null ? void 0 : s.fontFamily) || el.fontFamily;
15925
+ if (styledFamily) {
15926
+ const w = normalizeWeight(s.fontWeight ?? el.fontWeight);
15927
+ addFontVariant(styledFamily, w, /italic|oblique/i.test(String(s.fontStyle ?? el.fontStyle ?? "")));
15246
15928
  }
15247
15929
  }
15248
15930
  }
@@ -15256,19 +15938,21 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
15256
15938
  if (page.children) walkElements(page.children);
15257
15939
  if (page.elements) walkElements(page.elements);
15258
15940
  }
15259
- fontKeys.add(`${FONT_FALLBACK_SYMBOLS}${SEP}400`);
15941
+ fontKeys.add(`${FONT_FALLBACK_SYMBOLS}${SEP}400${SEP}normal`);
15260
15942
  for (const w of [300, 400, 500, 600, 700]) {
15261
- fontKeys.add(`${FONT_FALLBACK_DEVANAGARI}${SEP}${w}`);
15943
+ fontKeys.add(`${FONT_FALLBACK_DEVANAGARI}${SEP}${w}${SEP}normal`);
15262
15944
  }
15263
15945
  const embedded = /* @__PURE__ */ new Set();
15264
15946
  const tasks = [];
15265
15947
  for (const key of fontKeys) {
15266
15948
  const sep = key.indexOf(SEP);
15949
+ const secondSep = key.indexOf(SEP, sep + 1);
15267
15950
  const fontName = key.slice(0, sep);
15268
- const weight = parseInt(key.slice(sep + 1), 10);
15951
+ const weight = parseInt(key.slice(sep + 1, secondSep), 10);
15952
+ const italic = key.slice(secondSep + 1) === "italic";
15269
15953
  if (!isFontAvailable(fontName)) continue;
15270
15954
  tasks.push(
15271
- embedFont(pdf, fontName, weight, fontBaseUrl).then((ok) => {
15955
+ embedFont(pdf, fontName, weight, fontBaseUrl, italic).then((ok) => {
15272
15956
  if (ok) embedded.add(key);
15273
15957
  })
15274
15958
  );
@@ -15324,7 +16008,7 @@ function splitIntoRuns(text) {
15324
16008
  return runs;
15325
16009
  }
15326
16010
  function rewriteSvgFontsForJsPDF(svgStr) {
15327
- var _a, _b;
16011
+ var _a, _b, _c;
15328
16012
  const parser = new DOMParser();
15329
16013
  const doc = parser.parseFromString(svgStr, "image/svg+xml");
15330
16014
  const allTextEls = Array.from(doc.querySelectorAll("text, tspan, textPath"));
@@ -15356,6 +16040,13 @@ function rewriteSvgFontsForJsPDF(svgStr) {
15356
16040
  stylePairs.push(`font-style: normal`);
15357
16041
  return stylePairs.join("; ");
15358
16042
  };
16043
+ const applySyntheticItalicTransform = (el) => {
16044
+ const x = Number.parseFloat(resolveInheritedValue(el, "x") || "0") || 0;
16045
+ const y = Number.parseFloat(resolveInheritedValue(el, "y") || "0") || 0;
16046
+ const existing = el.getAttribute("transform") || "";
16047
+ const synth = `translate(${x} ${y}) skewX(-12) translate(${-x} ${-y})`;
16048
+ el.setAttribute("transform", existing ? `${existing} ${synth}` : synth);
16049
+ };
15359
16050
  const getDepth = (el) => {
15360
16051
  let depth = 0;
15361
16052
  let current = el.parentElement;
@@ -15375,20 +16066,40 @@ function rewriteSvgFontsForJsPDF(svgStr) {
15375
16066
  const weightRaw = resolveInheritedValue(el, "font-weight") || "400";
15376
16067
  const styleRaw = resolveInheritedValue(el, "font-style") || "normal";
15377
16068
  const weight = resolveWeightNum(weightRaw);
15378
- const resolved = resolveFontWeight(weight);
15379
- const isItalic = /italic|oblique/i.test(styleRaw);
15380
- const jsPdfName = getEmbeddedJsPDFFontName(clean, resolved, isItalic);
15381
- sourceMeta.set(el, { clean, resolved, isItalic, jsPdfName, inlineStyle, weight });
16069
+ const requested = resolveFontWeight(weight);
16070
+ const requestedItalic = /italic|oblique/i.test(styleRaw);
16071
+ const best = resolveBestRegisteredVariant(clean, requested, requestedItalic);
16072
+ if (!best) continue;
16073
+ const jsPdfName = getEmbeddedJsPDFFontName(clean, best.weight, best.italic);
16074
+ sourceMeta.set(el, { clean, requested, resolved: best.weight, isItalic: requestedItalic, actualItalic: best.italic, jsPdfName, inlineStyle, weight });
15382
16075
  }
15383
16076
  const textEls = allTextEls.sort((a, b) => getDepth(b) - getDepth(a));
15384
16077
  for (const el of textEls) {
15385
16078
  if (!el.isConnected) continue;
15386
16079
  const meta = sourceMeta.get(el);
15387
16080
  if (!meta) continue;
15388
- const { clean, resolved, isItalic, jsPdfName, inlineStyle, weight } = meta;
16081
+ const { clean, requested, resolved, isItalic, actualItalic, jsPdfName, inlineStyle, weight } = meta;
15389
16082
  el.setAttribute("data-source-font-family", clean);
15390
- el.setAttribute("data-source-font-weight", String(resolved));
16083
+ el.setAttribute("data-source-font-weight", String(requested));
15391
16084
  el.setAttribute("data-source-font-style", isItalic ? "italic" : "normal");
16085
+ if (requested >= 600 && resolved < 600) {
16086
+ const fill = resolveInheritedValue(el, "fill") || readStyleToken(inlineStyle, "fill") || "#000000";
16087
+ el.setAttribute("stroke", fill);
16088
+ el.setAttribute("stroke-width", String(requested === 700 ? 0.7 : 0.5));
16089
+ el.setAttribute("stroke-linejoin", "round");
16090
+ }
16091
+ if (isItalic && !actualItalic) {
16092
+ applySyntheticItalicTransform(el);
16093
+ try {
16094
+ console.log("[Vector PDF][synthetic-italic]", {
16095
+ tag: el.tagName,
16096
+ family: clean,
16097
+ weight: requested,
16098
+ textPreview: (el.textContent || "").slice(0, 40)
16099
+ });
16100
+ } catch {
16101
+ }
16102
+ }
15392
16103
  const directText = Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent || "").join("");
15393
16104
  const hasDevanagari = containsDevanagari(directText);
15394
16105
  const hasSymbol = containsSymbol(directText);
@@ -15431,6 +16142,28 @@ function rewriteSvgFontsForJsPDF(svgStr) {
15431
16142
  el.setAttribute("style", buildStyleString(inlineStyle, jsPdfName));
15432
16143
  }
15433
16144
  }
16145
+ const SVG_NS = "http://www.w3.org/2000/svg";
16146
+ const transformedTspans = Array.from(doc.querySelectorAll("tspan[transform]"));
16147
+ for (const tspan of transformedTspans) {
16148
+ const parentText = tspan.parentElement;
16149
+ if (!parentText || parentText.tagName.toLowerCase() !== "text") continue;
16150
+ const transformVal = tspan.getAttribute("transform") || "";
16151
+ if (!/skewX/i.test(transformVal)) continue;
16152
+ const wrapper = doc.createElementNS(SVG_NS, "text");
16153
+ let parentTransform = "";
16154
+ for (const attr of Array.from(parentText.attributes)) {
16155
+ if (attr.name === "transform") {
16156
+ parentTransform = attr.value;
16157
+ continue;
16158
+ }
16159
+ wrapper.setAttribute(attr.name, attr.value);
16160
+ }
16161
+ const combined = parentTransform ? `${parentTransform} ${transformVal}` : transformVal;
16162
+ wrapper.setAttribute("transform", combined);
16163
+ tspan.removeAttribute("transform");
16164
+ wrapper.appendChild(tspan);
16165
+ (_c = parentText.parentNode) == null ? void 0 : _c.insertBefore(wrapper, parentText.nextSibling);
16166
+ }
15434
16167
  return new XMLSerializer().serializeToString(doc.documentElement);
15435
16168
  }
15436
16169
  function extractFontFamiliesFromSvgs(svgs) {
@@ -15461,14 +16194,29 @@ async function embedFontsInPdf(pdf, fontFamilies, fontBaseUrl) {
15461
16194
  if (ok) embedded.add(`${family}${w}`);
15462
16195
  })
15463
16196
  );
16197
+ tasks.push(
16198
+ embedFont(pdf, family, w, fontBaseUrl, true).then((ok) => {
16199
+ if (ok) embedded.add(`${family}${w}i`);
16200
+ })
16201
+ );
15464
16202
  }
15465
16203
  } else {
15466
- tasks.push(
15467
- embedFontWithGoogleFallback(pdf, family, 400, fontBaseUrl, false).then((ok) => {
15468
- if (ok) embedded.add(`${family}400`);
15469
- else console.warn(`[pdf-fonts] No TTF (local/Google/Fontshare) for "${family}" — will use Helvetica fallback`);
15470
- })
15471
- );
16204
+ const variants = [
16205
+ { w: 400, italic: false },
16206
+ { w: 700, italic: false },
16207
+ { w: 400, italic: true },
16208
+ { w: 700, italic: true }
16209
+ ];
16210
+ for (const v of variants) {
16211
+ tasks.push(
16212
+ embedFontWithGoogleFallback(pdf, family, v.w, fontBaseUrl, v.italic).then((ok) => {
16213
+ if (ok) embedded.add(`${family}${v.w}${v.italic ? "i" : ""}`);
16214
+ else if (v.w === 400 && !v.italic) {
16215
+ console.warn(`[pdf-fonts] No TTF (local/Google/Fontshare) for "${family}" — will use Helvetica fallback`);
16216
+ }
16217
+ })
16218
+ );
16219
+ }
15472
16220
  }
15473
16221
  }
15474
16222
  await Promise.all(tasks);
@@ -15489,6 +16237,7 @@ const pdfFonts = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProp
15489
16237
  getEmbeddedJsPDFFontName,
15490
16238
  getFontPathForWeight,
15491
16239
  isFontAvailable,
16240
+ resolveBestRegisteredVariant,
15492
16241
  resolveFontWeight,
15493
16242
  rewriteSvgFontsForJsPDF
15494
16243
  }, Symbol.toStringTag, { value: "Module" }));
@@ -15528,9 +16277,14 @@ function dumpSvgTextDiagnostics(svgStr, pageIndex, tag, stage, maxItems = 30) {
15528
16277
  const sample = texts.slice(0, maxItems).map((t, idx) => {
15529
16278
  var _a, _b;
15530
16279
  const tspans = Array.from(t.querySelectorAll("tspan"));
15531
- const tspanInfo = tspans.slice(0, 4).map((s) => ({
16280
+ const tspanInfo = tspans.slice(0, 8).map((s) => ({
15532
16281
  x: s.getAttribute("x"),
15533
16282
  y: s.getAttribute("y"),
16283
+ fs: s.getAttribute("font-style"),
16284
+ fw: s.getAttribute("font-weight"),
16285
+ ff: s.getAttribute("font-family"),
16286
+ td: s.getAttribute("text-decoration"),
16287
+ style: (s.getAttribute("style") || "").slice(0, 120),
15534
16288
  text: (s.textContent || "").slice(0, 40)
15535
16289
  }));
15536
16290
  let containerWidth = null;
@@ -16565,13 +17319,15 @@ async function convertTextDecorationsToLines(svg) {
16565
17319
  const textDecOnText = (textEl.getAttribute("text-decoration") || "").toLowerCase();
16566
17320
  const textStyleDec = (getInlineStyleValue(textEl, "text-decoration") || "").toLowerCase();
16567
17321
  const textHasUnderline = textDecOnText.includes("underline") || textStyleDec.includes("underline");
17322
+ const textHasLinethrough = textDecOnText.includes("line-through") || textStyleDec.includes("line-through");
16568
17323
  for (let si = 0; si < tspans.length; si++) {
16569
17324
  const tspan = tspans[si];
16570
17325
  const liveTspan = liveTspans == null ? void 0 : liveTspans[si];
16571
17326
  const tspanDec = (tspan.getAttribute("text-decoration") || "").toLowerCase();
16572
17327
  const tspanStyleDec = (getInlineStyleValue(tspan, "text-decoration") || "").toLowerCase();
16573
17328
  const hasUnderline = tspanDec.includes("underline") || tspanStyleDec.includes("underline") || textHasUnderline;
16574
- if (!hasUnderline) continue;
17329
+ const hasLinethrough = tspanDec.includes("line-through") || tspanStyleDec.includes("line-through") || textHasLinethrough;
17330
+ if (!hasUnderline && !hasLinethrough) continue;
16575
17331
  const content = tspan.textContent || "";
16576
17332
  if (!content.trim()) continue;
16577
17333
  const xAttr = tspan.getAttribute("x") ?? textEl.getAttribute("x") ?? "0";
@@ -16626,23 +17382,26 @@ async function convertTextDecorationsToLines(svg) {
16626
17382
  lineStartX = x - (textAnchor === "middle" ? textWidth / 2 : textAnchor === "end" ? textWidth : 0);
16627
17383
  lineEndX = lineStartX + textWidth;
16628
17384
  }
16629
- const underlineY = baselineY + fontSize * 0.15;
16630
17385
  const thickness = Math.max(0.5, fontSize * 0.066667);
16631
- const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
16632
- line.setAttribute("x1", String(lineStartX));
16633
- line.setAttribute("y1", String(underlineY));
16634
- line.setAttribute("x2", String(lineEndX));
16635
- line.setAttribute("y2", String(underlineY));
16636
- line.setAttribute("stroke", fill.startsWith("url(") ? "#000000" : fill);
16637
- line.setAttribute("stroke-width", String(thickness));
16638
- line.setAttribute("stroke-linecap", "butt");
16639
- line.setAttribute("fill", "none");
16640
- if (fillOpacity) line.setAttribute("stroke-opacity", fillOpacity);
16641
- if (textEl.parentElement) {
16642
- textEl.parentElement.insertBefore(line, textEl.nextSibling);
16643
- }
17386
+ const makeLine = (yPos) => {
17387
+ const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
17388
+ line.setAttribute("x1", String(lineStartX));
17389
+ line.setAttribute("y1", String(yPos));
17390
+ line.setAttribute("x2", String(lineEndX));
17391
+ line.setAttribute("y2", String(yPos));
17392
+ line.setAttribute("stroke", fill.startsWith("url(") ? "#000000" : fill);
17393
+ line.setAttribute("stroke-width", String(thickness));
17394
+ line.setAttribute("stroke-linecap", "butt");
17395
+ line.setAttribute("fill", "none");
17396
+ if (fillOpacity) line.setAttribute("stroke-opacity", fillOpacity);
17397
+ if (textEl.parentElement) {
17398
+ textEl.parentElement.insertBefore(line, textEl.nextSibling);
17399
+ }
17400
+ };
17401
+ if (hasUnderline) makeLine(baselineY + fontSize * 0.15);
17402
+ if (hasLinethrough) makeLine(baselineY - fontSize * 0.3);
16644
17403
  stripTextDecoration(tspan);
16645
- if (textHasUnderline) stripTextDecoration(textEl);
17404
+ if (textHasUnderline || textHasLinethrough) stripTextDecoration(textEl);
16646
17405
  }
16647
17406
  }
16648
17407
  if (tempContainer) {
@@ -16656,7 +17415,7 @@ async function convertSvgTextDecorationsToLinesString(svgStr) {
16656
17415
  if (typeof DOMParser === "undefined" || typeof XMLSerializer === "undefined") {
16657
17416
  return svgStr;
16658
17417
  }
16659
- if (!/text-decoration/i.test(svgStr) && !/underline/i.test(svgStr)) {
17418
+ if (!/text-decoration/i.test(svgStr) && !/underline/i.test(svgStr) && !/line-through/i.test(svgStr)) {
16660
17419
  return svgStr;
16661
17420
  }
16662
17421
  try {
@@ -16948,7 +17707,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
16948
17707
  const hasGradient = !!((_b = (_a = page.backgroundGradient) == null ? void 0 : _a.stops) == null ? void 0 : _b.length);
16949
17708
  drawPageBackground(pdf, i, page.width, page.height, page.backgroundColor, page.backgroundGradient);
16950
17709
  const shouldStripBg = stripPageBackground ?? hasGradient;
16951
- const textMode = options.textMode ?? (options.outlineText ? "pixel-perfect" : "selectable");
17710
+ const textMode = options.textMode ?? (options.outlineText === false ? "selectable" : "selectable");
16952
17711
  const shouldOutlineText = textMode === "pixel-perfect" || textMode === "auto";
16953
17712
  const outlineSubMode = textMode === "auto" ? "complex-only" : "all";
16954
17713
  let pageSvg = page.svg;
@@ -16962,7 +17721,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
16962
17721
  }
16963
17722
  if (shouldOutlineText) {
16964
17723
  try {
16965
- const { convertAllTextToPath } = await Promise.resolve().then(() => require("./svgTextToPath-CWlhIf-q.cjs"));
17724
+ const { convertAllTextToPath } = await Promise.resolve().then(() => require("./svgTextToPath-DTKsddnS.cjs"));
16966
17725
  pageSvg = await convertAllTextToPath(pageSvg, fontBaseUrl, { mode: outlineSubMode });
16967
17726
  try {
16968
17727
  dumpSvgTextDiagnostics(pageSvg, i, PARITY_TAG, "STAGE-1b-after-text-to-path-raw");