@pixldocs/canvas-renderer 0.5.104 → 0.5.106

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,
@@ -12362,38 +12991,6 @@ function applyContentBoundsPagination(config) {
12362
12991
  if (!mutated) return config;
12363
12992
  return { ...config, pages: resultPages };
12364
12993
  }
12365
- const FONTSHARE_SLUGS = {
12366
- "Satoshi": "satoshi",
12367
- "Cabinet Grotesk": "cabinet-grotesk",
12368
- "Clash Display": "clash-display",
12369
- "Clash Grotesk": "clash-grotesk",
12370
- "General Sans": "general-sans",
12371
- "Switzer": "switzer",
12372
- "Supreme": "supreme",
12373
- "Author": "author",
12374
- "Boska": "boska",
12375
- "Excon": "excon",
12376
- "Khand": "khand",
12377
- "Sentient": "sentient",
12378
- "Synonym": "synonym",
12379
- "Erode": "erode",
12380
- "Ranade": "ranade",
12381
- "Tanker": "tanker",
12382
- "Zodiak": "zodiak",
12383
- "Gambarino": "gambarino",
12384
- "Melodrama": "melodrama",
12385
- "Bespoke Serif": "bespoke-serif",
12386
- "Bespoke Stencil": "bespoke-stencil",
12387
- "Panchang": "panchang",
12388
- "Quincy CF": "quincy-cf",
12389
- "Pally": "pally",
12390
- "Tabular": "tabular",
12391
- "Sharpie": "sharpie",
12392
- "Stardom": "stardom",
12393
- "Rebond Grotesque": "rebond-grotesque",
12394
- "Telma": "telma",
12395
- "Nippo": "nippo"
12396
- };
12397
12994
  function normalizeFontFamily(fontStack) {
12398
12995
  const first = fontStack.split(",")[0].trim();
12399
12996
  return first.replace(/^['"]|['"]$/g, "");
@@ -12420,31 +13017,11 @@ async function loadGoogleFontCSS(rawFontFamily) {
12420
13017
  if (existing) return existing;
12421
13018
  const promise = (async () => {
12422
13019
  try {
12423
- const fontshareSlug = FONTSHARE_SLUGS[fontFamily];
12424
- if (fontshareSlug) {
12425
- 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
- });
13020
+ if (LOCAL_FONTS.has(fontFamily)) {
12434
13021
  loadedFonts.add(fontFamily);
12435
13022
  return;
12436
13023
  }
12437
- const encoded = encodeURIComponent(fontFamily);
12438
- 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
- });
13024
+ await withTimeout(loadFont(fontFamily), 4e3);
12448
13025
  loadedFonts.add(fontFamily);
12449
13026
  } catch (e) {
12450
13027
  console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
@@ -12514,6 +13091,23 @@ function collectFontDescriptorsFromConfig(config) {
12514
13091
  if (node.type === "text") {
12515
13092
  for (const w of [300, 400, 500, 600, 700]) {
12516
13093
  add(node.fontFamily, w, node.fontStyle);
13094
+ add(node.fontFamily, w, "italic");
13095
+ }
13096
+ }
13097
+ }
13098
+ if (node.formattingEnabled === true && node.fontFamily) {
13099
+ const parsed = parseTextMarkdown(String(node.text ?? ""));
13100
+ const parsedStyleEntries = Object.values(parsed.styles || {});
13101
+ for (const lineStyle of parsedStyleEntries) {
13102
+ if (lineStyle && typeof lineStyle === "object") {
13103
+ for (const charStyle of Object.values(lineStyle)) {
13104
+ if (!charStyle || typeof charStyle !== "object") continue;
13105
+ add(
13106
+ charStyle.fontFamily || node.fontFamily,
13107
+ charStyle.fontWeight ?? node.fontWeight,
13108
+ charStyle.fontStyle ?? node.fontStyle
13109
+ );
13110
+ }
12517
13111
  }
12518
13112
  }
12519
13113
  }
@@ -13423,11 +14017,20 @@ function PixldocsPreview(props) {
13423
14017
  !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
14018
  ] });
13425
14019
  }
13426
- const PACKAGE_VERSION = "0.5.104";
14020
+ const PACKAGE_VERSION = "0.5.105";
13427
14021
  const roundParityValue = (value) => {
13428
14022
  if (typeof value !== "number") return value;
13429
14023
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
13430
14024
  };
14025
+ function isolatePageForCapture(config, pageIndex) {
14026
+ var _a;
14027
+ const capturePageId = `__pixldocs_capture_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}_${pageIndex}`;
14028
+ const cloned = JSON.parse(JSON.stringify(config));
14029
+ if ((_a = cloned.pages) == null ? void 0 : _a[pageIndex]) {
14030
+ cloned.pages[pageIndex].id = capturePageId;
14031
+ }
14032
+ return { config: cloned, pageId: capturePageId };
14033
+ }
13431
14034
  function logJsonLine(tag, payload) {
13432
14035
  try {
13433
14036
  console.log(`${tag} ${JSON.stringify(payload, (_key, value) => roundParityValue(value))}`);
@@ -13465,6 +14068,9 @@ function installUnderlineFix(fab) {
13465
14068
  const hasOwn = !!this[type];
13466
14069
  const hasStyled = typeof this.styleHas === "function" && this.styleHas(type);
13467
14070
  if (!hasOwn && !hasStyled) return;
14071
+ if (!hasOwn && hasStyled) {
14072
+ return original.call(this, ctx, type);
14073
+ }
13468
14074
  const lines = this._textLines;
13469
14075
  const offsets = this.offsets;
13470
14076
  if (!Array.isArray(lines) || !offsets) {
@@ -13476,7 +14082,7 @@ function installUnderlineFix(fab) {
13476
14082
  let topOffset = this._getTopOffset();
13477
14083
  for (let i = 0, len = lines.length; i < len; i++) {
13478
14084
  const heightOfLine = this.getHeightOfLine(i);
13479
- const lineHas = !!this[type] || typeof this.styleHas === "function" && this.styleHas(type, i);
14085
+ const lineHas = !!this[type];
13480
14086
  if (!lineHas) {
13481
14087
  topOffset += heightOfLine;
13482
14088
  continue;
@@ -14006,9 +14612,11 @@ class PixldocsRenderer {
14006
14612
  }
14007
14613
  async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality, options = {}) {
14008
14614
  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);
14615
+ const capture = isolatePageForCapture(config, pageIndex);
14616
+ const renderConfig = capture.config;
14617
+ const canvasWidth = renderConfig.canvas.width;
14618
+ const canvasHeight = renderConfig.canvas.height;
14619
+ const hasAutoShrink = configHasAutoShrinkText(renderConfig);
14012
14620
  let firstMountSettled = false;
14013
14621
  let lateFontSettleDetected = false;
14014
14622
  if (typeof document !== "undefined" && document.fonts && hasAutoShrink) {
@@ -14056,12 +14664,12 @@ class PixldocsRenderer {
14056
14664
  root = client.createRoot(container);
14057
14665
  await new Promise((settle) => {
14058
14666
  const onReadyOnce = () => {
14059
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14667
+ this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
14060
14668
  const fabricInstance = this.getFabricCanvasFromContainer(container);
14061
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14669
+ const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
14062
14670
  await this.waitForCanvasImages(container, expectedImageCount);
14063
- await this.waitForStableTextMetrics(container, config);
14064
- await this.waitForCanvasScene(container, config, pageIndex);
14671
+ await this.waitForStableTextMetrics(container, renderConfig);
14672
+ await this.waitForCanvasScene(container, renderConfig, pageIndex);
14065
14673
  if (!fabricInstance) return settle();
14066
14674
  settle();
14067
14675
  }).catch(() => settle());
@@ -14069,8 +14677,9 @@ class PixldocsRenderer {
14069
14677
  root.render(
14070
14678
  react.createElement(PreviewCanvas2, {
14071
14679
  key: `remount-${mountKey}`,
14072
- config,
14680
+ config: renderConfig,
14073
14681
  pageIndex,
14682
+ pageIdOverride: capture.pageId,
14074
14683
  zoom: pixelRatio,
14075
14684
  absoluteZoom: true,
14076
14685
  skipFontReadyWait: false,
@@ -14080,13 +14689,13 @@ class PixldocsRenderer {
14080
14689
  });
14081
14690
  };
14082
14691
  const onReady = () => {
14083
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14692
+ this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
14084
14693
  try {
14085
14694
  const fabricInstance = this.getFabricCanvasFromContainer(container);
14086
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14695
+ const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
14087
14696
  await this.waitForCanvasImages(container, expectedImageCount);
14088
- await this.waitForStableTextMetrics(container, config);
14089
- await this.waitForCanvasScene(container, config, pageIndex);
14697
+ await this.waitForStableTextMetrics(container, renderConfig);
14698
+ await this.waitForCanvasScene(container, renderConfig, pageIndex);
14090
14699
  firstMountSettled = true;
14091
14700
  if (hasAutoShrink && lateFontSettleDetected) {
14092
14701
  console.log("[canvas-renderer][parity] late font-settle detected — remounting for auto-shrink reflow");
@@ -14112,7 +14721,7 @@ class PixldocsRenderer {
14112
14721
  }
14113
14722
  exportCtx.save();
14114
14723
  exportCtx.scale(sourceCanvasAfter.width / canvasWidth, sourceCanvasAfter.height / canvasHeight);
14115
- this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
14724
+ this.paintPageBackground(exportCtx, renderConfig.pages[pageIndex], canvasWidth, canvasHeight);
14116
14725
  exportCtx.restore();
14117
14726
  exportCtx.drawImage(sourceCanvasAfter, 0, 0);
14118
14727
  const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
@@ -14128,8 +14737,9 @@ class PixldocsRenderer {
14128
14737
  root = client.createRoot(container);
14129
14738
  root.render(
14130
14739
  react.createElement(PreviewCanvas2, {
14131
- config,
14740
+ config: renderConfig,
14132
14741
  pageIndex,
14742
+ pageIdOverride: capture.pageId,
14133
14743
  zoom: pixelRatio,
14134
14744
  absoluteZoom: true,
14135
14745
  skipFontReadyWait: false,
@@ -14150,6 +14760,8 @@ class PixldocsRenderer {
14150
14760
  captureSvgViaPreviewCanvas(config, pageIndex, canvasWidth, canvasHeight) {
14151
14761
  return new Promise(async (resolve, reject) => {
14152
14762
  const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
14763
+ const capture = isolatePageForCapture(config, pageIndex);
14764
+ const renderConfig = capture.config;
14153
14765
  const container = document.createElement("div");
14154
14766
  container.style.cssText = `
14155
14767
  position: fixed; left: -99999px; top: -99999px;
@@ -14176,8 +14788,9 @@ class PixldocsRenderer {
14176
14788
  root.render(
14177
14789
  react.createElement(PreviewCanvas2, {
14178
14790
  key: `svg-capture-${mountKey}`,
14179
- config,
14791
+ config: renderConfig,
14180
14792
  pageIndex,
14793
+ pageIdOverride: capture.pageId,
14181
14794
  zoom: 1,
14182
14795
  absoluteZoom: true,
14183
14796
  skipFontReadyWait: false,
@@ -14186,13 +14799,13 @@ class PixldocsRenderer {
14186
14799
  );
14187
14800
  };
14188
14801
  const onReady = () => {
14189
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14802
+ this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
14190
14803
  var _a, _b;
14191
14804
  try {
14192
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14805
+ const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
14193
14806
  await this.waitForCanvasImages(container, expectedImageCount);
14194
- await this.waitForStableTextMetrics(container, config);
14195
- await this.waitForCanvasScene(container, config, pageIndex);
14807
+ await this.waitForStableTextMetrics(container, renderConfig);
14808
+ await this.waitForCanvasScene(container, renderConfig, pageIndex);
14196
14809
  const fabricInstance = this.getFabricCanvasFromContainer(container);
14197
14810
  if (!fabricInstance) {
14198
14811
  cleanup();
@@ -14281,7 +14894,7 @@ class PixldocsRenderer {
14281
14894
  );
14282
14895
  if (prevVPT) fabricInstance.viewportTransform = prevVPT;
14283
14896
  fabricInstance.svgViewportTransformation = prevSvgVPT;
14284
- const page = config.pages[pageIndex];
14897
+ const page = renderConfig.pages[pageIndex];
14285
14898
  const backgroundColor = ((_a = page == null ? void 0 : page.settings) == null ? void 0 : _a.backgroundColor) || "#ffffff";
14286
14899
  const backgroundGradient = (_b = page == null ? void 0 : page.settings) == null ? void 0 : _b.backgroundGradient;
14287
14900
  cleanup();
@@ -14963,8 +15576,9 @@ function getFontPathForWeight(files, weight, isItalic = false) {
14963
15576
  }
14964
15577
  return files.regular;
14965
15578
  }
14966
- function isItalicPath(files, path) {
14967
- return path === files.italic || path === files.boldItalic || path === files.lightItalic || path === files.mediumItalic || path === files.semiboldItalic;
15579
+ function isExactWeightItalicMatch(files, weight, isItalic, path) {
15580
+ 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";
15581
+ return files[exactKey] === path;
14968
15582
  }
14969
15583
  function getJsPDFFontName(fontName) {
14970
15584
  return fontName.replace(/\s+/g, "");
@@ -15037,10 +15651,10 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
15037
15651
  const resolvedWeight = resolveFontWeight(weight);
15038
15652
  const fontPath = getFontPathForWeight(fontFiles, resolvedWeight, isItalic);
15039
15653
  if (!fontPath) return false;
15040
- const hasItalicFile = isItalic && isItalicPath(fontFiles, fontPath);
15041
- const jsPdfFontName = getEmbeddedJsPDFFontName(fontName, weight, hasItalicFile);
15654
+ if (!isExactWeightItalicMatch(fontFiles, resolvedWeight, isItalic, fontPath)) return false;
15655
+ const jsPdfFontName = getEmbeddedJsPDFFontName(fontName, weight, isItalic);
15042
15656
  const label = FONT_WEIGHT_LABELS[resolvedWeight];
15043
- const italicSuffix = hasItalicFile ? "Italic" : "";
15657
+ const italicSuffix = isItalic ? "Italic" : "";
15044
15658
  const fileName = `${getJsPDFFontName(fontName)}-${label}${italicSuffix}.ttf`;
15045
15659
  const url = baseUrl + fontPath;
15046
15660
  try {
@@ -15055,6 +15669,7 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
15055
15669
  }
15056
15670
  }
15057
15671
  registeredFamilies.add(fontName);
15672
+ registeredVariants.add(variantKey(fontName, resolvedWeight, isItalic));
15058
15673
  return true;
15059
15674
  } catch (e) {
15060
15675
  console.warn(`[pdf-fonts] Failed to embed ${fontName} w${weight}:`, e);
@@ -15063,6 +15678,18 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
15063
15678
  }
15064
15679
  const googleFontNotFound = /* @__PURE__ */ new Set();
15065
15680
  const fontshareNotFound = /* @__PURE__ */ new Set();
15681
+ const remoteVariantKey = (family, weight, isItalic) => `${family}|${resolveFontWeight(weight)}|${isItalic ? "i" : "n"}`;
15682
+ const registeredVariants = /* @__PURE__ */ new Set();
15683
+ const variantKey = (family, weight, italic) => `${family}|${resolveFontWeight(weight)}|${italic ? "i" : "n"}`;
15684
+ const resolveBestRegisteredVariant = (family, weight, italic) => {
15685
+ const want = resolveFontWeight(weight);
15686
+ const weights = [300, 400, 500, 600, 700];
15687
+ if (registeredVariants.has(variantKey(family, want, italic))) return { weight: want, italic };
15688
+ const nearest = [...weights].sort((a, b) => Math.abs(a - want) - Math.abs(b - want) || (want >= 500 ? b - a : a - b));
15689
+ for (const w of nearest) if (registeredVariants.has(variantKey(family, w, italic))) return { weight: w, italic };
15690
+ for (const w of nearest) if (registeredVariants.has(variantKey(family, w, !italic))) return { weight: w, italic: !italic };
15691
+ return null;
15692
+ };
15066
15693
  function bytesToBase64(bytes) {
15067
15694
  let binary = "";
15068
15695
  for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
@@ -15091,7 +15718,8 @@ async function fetchTtfViaProxy(family, weight, isItalic, source) {
15091
15718
  async function fetchGoogleFontTTF(fontFamily, weight, isItalic = false) {
15092
15719
  const cacheKey = `gf:${fontFamily}:${weight}:${isItalic ? "i" : "n"}`;
15093
15720
  if (ttfCache.has(cacheKey)) return ttfCache.get(cacheKey);
15094
- if (googleFontNotFound.has(fontFamily)) return null;
15721
+ const notFoundKey = remoteVariantKey(fontFamily, weight, isItalic);
15722
+ if (googleFontNotFound.has(notFoundKey)) return null;
15095
15723
  const proxyBytes = await fetchTtfViaProxy(fontFamily, weight, isItalic, "google");
15096
15724
  if (proxyBytes) {
15097
15725
  const b64 = bytesToBase64(proxyBytes);
@@ -15109,7 +15737,7 @@ async function fetchGoogleFontTTF(fontFamily, weight, isItalic = false) {
15109
15737
  }
15110
15738
  });
15111
15739
  if (!cssRes.ok) {
15112
- if (cssRes.status === 400 || cssRes.status === 404) googleFontNotFound.add(fontFamily);
15740
+ if (cssRes.status === 400 || cssRes.status === 404) googleFontNotFound.add(notFoundKey);
15113
15741
  return null;
15114
15742
  }
15115
15743
  const css = await cssRes.text();
@@ -15187,6 +15815,7 @@ function registerJsPdfFont(pdf, fontName, resolvedWeight, isItalic, base64) {
15187
15815
  }
15188
15816
  }
15189
15817
  registeredFamilies.add(fontName);
15818
+ registeredVariants.add(variantKey(fontName, resolvedWeight, isItalic));
15190
15819
  return true;
15191
15820
  } catch (err) {
15192
15821
  console.warn(`[pdf-fonts] registerJsPdfFont failed for ${fontName}:`, err);
@@ -15201,16 +15830,8 @@ async function embedFontWithGoogleFallback(pdf, fontName, weight = 400, fontBase
15201
15830
  const resolved = resolveFontWeight(weight);
15202
15831
  const fsB64 = await fetchFontshareTTF(fontName, resolved, isItalic);
15203
15832
  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
15833
  const b64 = await fetchGoogleFontTTF(fontName, resolved, isItalic);
15209
15834
  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
15835
  return false;
15215
15836
  }
15216
15837
  async function embedFontsForConfig(pdf, config, fontBaseUrl) {
@@ -15230,9 +15851,32 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
15230
15851
  };
15231
15852
  const walkElements = (elements) => {
15232
15853
  for (const el of elements) {
15854
+ const addFontVariant = (family, weight, italic) => {
15855
+ fontKeys.add(`${family}${SEP}${weight}${SEP}${italic ? "italic" : "normal"}`);
15856
+ };
15233
15857
  if (el.fontFamily) {
15234
15858
  const w = normalizeWeight(el.fontWeight);
15235
- fontKeys.add(`${el.fontFamily}${SEP}${w}`);
15859
+ addFontVariant(el.fontFamily, w, /italic|oblique/i.test(String(el.fontStyle ?? "")));
15860
+ addFontVariant(el.fontFamily, 700, false);
15861
+ addFontVariant(el.fontFamily, 400, true);
15862
+ addFontVariant(el.fontFamily, 700, true);
15863
+ if (el.formattingEnabled === true && typeof el.text === "string") {
15864
+ try {
15865
+ const parsed = parseTextMarkdown(el.text);
15866
+ for (const lineStyles of Object.values(parsed.styles || {})) {
15867
+ if (!lineStyles || typeof lineStyles !== "object") continue;
15868
+ for (const s of Object.values(lineStyles)) {
15869
+ if (!s) continue;
15870
+ const family = s.fontFamily || el.fontFamily;
15871
+ if (!family) continue;
15872
+ const ww = normalizeWeight(s.fontWeight ?? el.fontWeight);
15873
+ const it = /italic|oblique/i.test(String(s.fontStyle ?? el.fontStyle ?? ""));
15874
+ addFontVariant(family, ww, it);
15875
+ }
15876
+ }
15877
+ } catch {
15878
+ }
15879
+ }
15236
15880
  }
15237
15881
  if (el.styles && typeof el.styles === "object") {
15238
15882
  for (const lineKey of Object.keys(el.styles)) {
@@ -15240,9 +15884,10 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
15240
15884
  if (lineStyles && typeof lineStyles === "object") {
15241
15885
  for (const charKey of Object.keys(lineStyles)) {
15242
15886
  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}`);
15887
+ const styledFamily = (s == null ? void 0 : s.fontFamily) || el.fontFamily;
15888
+ if (styledFamily) {
15889
+ const w = normalizeWeight(s.fontWeight ?? el.fontWeight);
15890
+ addFontVariant(styledFamily, w, /italic|oblique/i.test(String(s.fontStyle ?? el.fontStyle ?? "")));
15246
15891
  }
15247
15892
  }
15248
15893
  }
@@ -15256,19 +15901,21 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
15256
15901
  if (page.children) walkElements(page.children);
15257
15902
  if (page.elements) walkElements(page.elements);
15258
15903
  }
15259
- fontKeys.add(`${FONT_FALLBACK_SYMBOLS}${SEP}400`);
15904
+ fontKeys.add(`${FONT_FALLBACK_SYMBOLS}${SEP}400${SEP}normal`);
15260
15905
  for (const w of [300, 400, 500, 600, 700]) {
15261
- fontKeys.add(`${FONT_FALLBACK_DEVANAGARI}${SEP}${w}`);
15906
+ fontKeys.add(`${FONT_FALLBACK_DEVANAGARI}${SEP}${w}${SEP}normal`);
15262
15907
  }
15263
15908
  const embedded = /* @__PURE__ */ new Set();
15264
15909
  const tasks = [];
15265
15910
  for (const key of fontKeys) {
15266
15911
  const sep = key.indexOf(SEP);
15912
+ const secondSep = key.indexOf(SEP, sep + 1);
15267
15913
  const fontName = key.slice(0, sep);
15268
- const weight = parseInt(key.slice(sep + 1), 10);
15914
+ const weight = parseInt(key.slice(sep + 1, secondSep), 10);
15915
+ const italic = key.slice(secondSep + 1) === "italic";
15269
15916
  if (!isFontAvailable(fontName)) continue;
15270
15917
  tasks.push(
15271
- embedFont(pdf, fontName, weight, fontBaseUrl).then((ok) => {
15918
+ embedFont(pdf, fontName, weight, fontBaseUrl, italic).then((ok) => {
15272
15919
  if (ok) embedded.add(key);
15273
15920
  })
15274
15921
  );
@@ -15324,7 +15971,7 @@ function splitIntoRuns(text) {
15324
15971
  return runs;
15325
15972
  }
15326
15973
  function rewriteSvgFontsForJsPDF(svgStr) {
15327
- var _a, _b;
15974
+ var _a, _b, _c;
15328
15975
  const parser = new DOMParser();
15329
15976
  const doc = parser.parseFromString(svgStr, "image/svg+xml");
15330
15977
  const allTextEls = Array.from(doc.querySelectorAll("text, tspan, textPath"));
@@ -15356,6 +16003,13 @@ function rewriteSvgFontsForJsPDF(svgStr) {
15356
16003
  stylePairs.push(`font-style: normal`);
15357
16004
  return stylePairs.join("; ");
15358
16005
  };
16006
+ const applySyntheticItalicTransform = (el) => {
16007
+ const x = Number.parseFloat(resolveInheritedValue(el, "x") || "0") || 0;
16008
+ const y = Number.parseFloat(resolveInheritedValue(el, "y") || "0") || 0;
16009
+ const existing = el.getAttribute("transform") || "";
16010
+ const synth = `translate(${x} ${y}) skewX(-12) translate(${-x} ${-y})`;
16011
+ el.setAttribute("transform", existing ? `${existing} ${synth}` : synth);
16012
+ };
15359
16013
  const getDepth = (el) => {
15360
16014
  let depth = 0;
15361
16015
  let current = el.parentElement;
@@ -15375,20 +16029,40 @@ function rewriteSvgFontsForJsPDF(svgStr) {
15375
16029
  const weightRaw = resolveInheritedValue(el, "font-weight") || "400";
15376
16030
  const styleRaw = resolveInheritedValue(el, "font-style") || "normal";
15377
16031
  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 });
16032
+ const requested = resolveFontWeight(weight);
16033
+ const requestedItalic = /italic|oblique/i.test(styleRaw);
16034
+ const best = resolveBestRegisteredVariant(clean, requested, requestedItalic);
16035
+ if (!best) continue;
16036
+ const jsPdfName = getEmbeddedJsPDFFontName(clean, best.weight, best.italic);
16037
+ sourceMeta.set(el, { clean, requested, resolved: best.weight, isItalic: requestedItalic, actualItalic: best.italic, jsPdfName, inlineStyle, weight });
15382
16038
  }
15383
16039
  const textEls = allTextEls.sort((a, b) => getDepth(b) - getDepth(a));
15384
16040
  for (const el of textEls) {
15385
16041
  if (!el.isConnected) continue;
15386
16042
  const meta = sourceMeta.get(el);
15387
16043
  if (!meta) continue;
15388
- const { clean, resolved, isItalic, jsPdfName, inlineStyle, weight } = meta;
16044
+ const { clean, requested, resolved, isItalic, actualItalic, jsPdfName, inlineStyle, weight } = meta;
15389
16045
  el.setAttribute("data-source-font-family", clean);
15390
- el.setAttribute("data-source-font-weight", String(resolved));
16046
+ el.setAttribute("data-source-font-weight", String(requested));
15391
16047
  el.setAttribute("data-source-font-style", isItalic ? "italic" : "normal");
16048
+ if (requested >= 600 && resolved < 600) {
16049
+ const fill = resolveInheritedValue(el, "fill") || readStyleToken(inlineStyle, "fill") || "#000000";
16050
+ el.setAttribute("stroke", fill);
16051
+ el.setAttribute("stroke-width", String(requested === 700 ? 0.7 : 0.5));
16052
+ el.setAttribute("stroke-linejoin", "round");
16053
+ }
16054
+ if (isItalic && !actualItalic) {
16055
+ applySyntheticItalicTransform(el);
16056
+ try {
16057
+ console.log("[Vector PDF][synthetic-italic]", {
16058
+ tag: el.tagName,
16059
+ family: clean,
16060
+ weight: requested,
16061
+ textPreview: (el.textContent || "").slice(0, 40)
16062
+ });
16063
+ } catch {
16064
+ }
16065
+ }
15392
16066
  const directText = Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent || "").join("");
15393
16067
  const hasDevanagari = containsDevanagari(directText);
15394
16068
  const hasSymbol = containsSymbol(directText);
@@ -15431,6 +16105,28 @@ function rewriteSvgFontsForJsPDF(svgStr) {
15431
16105
  el.setAttribute("style", buildStyleString(inlineStyle, jsPdfName));
15432
16106
  }
15433
16107
  }
16108
+ const SVG_NS = "http://www.w3.org/2000/svg";
16109
+ const transformedTspans = Array.from(doc.querySelectorAll("tspan[transform]"));
16110
+ for (const tspan of transformedTspans) {
16111
+ const parentText = tspan.parentElement;
16112
+ if (!parentText || parentText.tagName.toLowerCase() !== "text") continue;
16113
+ const transformVal = tspan.getAttribute("transform") || "";
16114
+ if (!/skewX/i.test(transformVal)) continue;
16115
+ const wrapper = doc.createElementNS(SVG_NS, "text");
16116
+ let parentTransform = "";
16117
+ for (const attr of Array.from(parentText.attributes)) {
16118
+ if (attr.name === "transform") {
16119
+ parentTransform = attr.value;
16120
+ continue;
16121
+ }
16122
+ wrapper.setAttribute(attr.name, attr.value);
16123
+ }
16124
+ const combined = parentTransform ? `${parentTransform} ${transformVal}` : transformVal;
16125
+ wrapper.setAttribute("transform", combined);
16126
+ tspan.removeAttribute("transform");
16127
+ wrapper.appendChild(tspan);
16128
+ (_c = parentText.parentNode) == null ? void 0 : _c.insertBefore(wrapper, parentText.nextSibling);
16129
+ }
15434
16130
  return new XMLSerializer().serializeToString(doc.documentElement);
15435
16131
  }
15436
16132
  function extractFontFamiliesFromSvgs(svgs) {
@@ -15461,14 +16157,29 @@ async function embedFontsInPdf(pdf, fontFamilies, fontBaseUrl) {
15461
16157
  if (ok) embedded.add(`${family}${w}`);
15462
16158
  })
15463
16159
  );
16160
+ tasks.push(
16161
+ embedFont(pdf, family, w, fontBaseUrl, true).then((ok) => {
16162
+ if (ok) embedded.add(`${family}${w}i`);
16163
+ })
16164
+ );
15464
16165
  }
15465
16166
  } 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
- );
16167
+ const variants = [
16168
+ { w: 400, italic: false },
16169
+ { w: 700, italic: false },
16170
+ { w: 400, italic: true },
16171
+ { w: 700, italic: true }
16172
+ ];
16173
+ for (const v of variants) {
16174
+ tasks.push(
16175
+ embedFontWithGoogleFallback(pdf, family, v.w, fontBaseUrl, v.italic).then((ok) => {
16176
+ if (ok) embedded.add(`${family}${v.w}${v.italic ? "i" : ""}`);
16177
+ else if (v.w === 400 && !v.italic) {
16178
+ console.warn(`[pdf-fonts] No TTF (local/Google/Fontshare) for "${family}" — will use Helvetica fallback`);
16179
+ }
16180
+ })
16181
+ );
16182
+ }
15472
16183
  }
15473
16184
  }
15474
16185
  await Promise.all(tasks);
@@ -15489,6 +16200,7 @@ const pdfFonts = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProp
15489
16200
  getEmbeddedJsPDFFontName,
15490
16201
  getFontPathForWeight,
15491
16202
  isFontAvailable,
16203
+ resolveBestRegisteredVariant,
15492
16204
  resolveFontWeight,
15493
16205
  rewriteSvgFontsForJsPDF
15494
16206
  }, Symbol.toStringTag, { value: "Module" }));
@@ -15528,9 +16240,14 @@ function dumpSvgTextDiagnostics(svgStr, pageIndex, tag, stage, maxItems = 30) {
15528
16240
  const sample = texts.slice(0, maxItems).map((t, idx) => {
15529
16241
  var _a, _b;
15530
16242
  const tspans = Array.from(t.querySelectorAll("tspan"));
15531
- const tspanInfo = tspans.slice(0, 4).map((s) => ({
16243
+ const tspanInfo = tspans.slice(0, 8).map((s) => ({
15532
16244
  x: s.getAttribute("x"),
15533
16245
  y: s.getAttribute("y"),
16246
+ fs: s.getAttribute("font-style"),
16247
+ fw: s.getAttribute("font-weight"),
16248
+ ff: s.getAttribute("font-family"),
16249
+ td: s.getAttribute("text-decoration"),
16250
+ style: (s.getAttribute("style") || "").slice(0, 120),
15534
16251
  text: (s.textContent || "").slice(0, 40)
15535
16252
  }));
15536
16253
  let containerWidth = null;
@@ -16565,13 +17282,15 @@ async function convertTextDecorationsToLines(svg) {
16565
17282
  const textDecOnText = (textEl.getAttribute("text-decoration") || "").toLowerCase();
16566
17283
  const textStyleDec = (getInlineStyleValue(textEl, "text-decoration") || "").toLowerCase();
16567
17284
  const textHasUnderline = textDecOnText.includes("underline") || textStyleDec.includes("underline");
17285
+ const textHasLinethrough = textDecOnText.includes("line-through") || textStyleDec.includes("line-through");
16568
17286
  for (let si = 0; si < tspans.length; si++) {
16569
17287
  const tspan = tspans[si];
16570
17288
  const liveTspan = liveTspans == null ? void 0 : liveTspans[si];
16571
17289
  const tspanDec = (tspan.getAttribute("text-decoration") || "").toLowerCase();
16572
17290
  const tspanStyleDec = (getInlineStyleValue(tspan, "text-decoration") || "").toLowerCase();
16573
17291
  const hasUnderline = tspanDec.includes("underline") || tspanStyleDec.includes("underline") || textHasUnderline;
16574
- if (!hasUnderline) continue;
17292
+ const hasLinethrough = tspanDec.includes("line-through") || tspanStyleDec.includes("line-through") || textHasLinethrough;
17293
+ if (!hasUnderline && !hasLinethrough) continue;
16575
17294
  const content = tspan.textContent || "";
16576
17295
  if (!content.trim()) continue;
16577
17296
  const xAttr = tspan.getAttribute("x") ?? textEl.getAttribute("x") ?? "0";
@@ -16626,23 +17345,26 @@ async function convertTextDecorationsToLines(svg) {
16626
17345
  lineStartX = x - (textAnchor === "middle" ? textWidth / 2 : textAnchor === "end" ? textWidth : 0);
16627
17346
  lineEndX = lineStartX + textWidth;
16628
17347
  }
16629
- const underlineY = baselineY + fontSize * 0.15;
16630
17348
  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
- }
17349
+ const makeLine = (yPos) => {
17350
+ const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
17351
+ line.setAttribute("x1", String(lineStartX));
17352
+ line.setAttribute("y1", String(yPos));
17353
+ line.setAttribute("x2", String(lineEndX));
17354
+ line.setAttribute("y2", String(yPos));
17355
+ line.setAttribute("stroke", fill.startsWith("url(") ? "#000000" : fill);
17356
+ line.setAttribute("stroke-width", String(thickness));
17357
+ line.setAttribute("stroke-linecap", "butt");
17358
+ line.setAttribute("fill", "none");
17359
+ if (fillOpacity) line.setAttribute("stroke-opacity", fillOpacity);
17360
+ if (textEl.parentElement) {
17361
+ textEl.parentElement.insertBefore(line, textEl.nextSibling);
17362
+ }
17363
+ };
17364
+ if (hasUnderline) makeLine(baselineY + fontSize * 0.15);
17365
+ if (hasLinethrough) makeLine(baselineY - fontSize * 0.3);
16644
17366
  stripTextDecoration(tspan);
16645
- if (textHasUnderline) stripTextDecoration(textEl);
17367
+ if (textHasUnderline || textHasLinethrough) stripTextDecoration(textEl);
16646
17368
  }
16647
17369
  }
16648
17370
  if (tempContainer) {
@@ -16656,7 +17378,7 @@ async function convertSvgTextDecorationsToLinesString(svgStr) {
16656
17378
  if (typeof DOMParser === "undefined" || typeof XMLSerializer === "undefined") {
16657
17379
  return svgStr;
16658
17380
  }
16659
- if (!/text-decoration/i.test(svgStr) && !/underline/i.test(svgStr)) {
17381
+ if (!/text-decoration/i.test(svgStr) && !/underline/i.test(svgStr) && !/line-through/i.test(svgStr)) {
16660
17382
  return svgStr;
16661
17383
  }
16662
17384
  try {
@@ -16948,7 +17670,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
16948
17670
  const hasGradient = !!((_b = (_a = page.backgroundGradient) == null ? void 0 : _a.stops) == null ? void 0 : _b.length);
16949
17671
  drawPageBackground(pdf, i, page.width, page.height, page.backgroundColor, page.backgroundGradient);
16950
17672
  const shouldStripBg = stripPageBackground ?? hasGradient;
16951
- const textMode = options.textMode ?? (options.outlineText ? "pixel-perfect" : "selectable");
17673
+ const textMode = options.textMode ?? (options.outlineText === false ? "selectable" : "selectable");
16952
17674
  const shouldOutlineText = textMode === "pixel-perfect" || textMode === "auto";
16953
17675
  const outlineSubMode = textMode === "auto" ? "complex-only" : "all";
16954
17676
  let pageSvg = page.svg;
@@ -16962,7 +17684,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
16962
17684
  }
16963
17685
  if (shouldOutlineText) {
16964
17686
  try {
16965
- const { convertAllTextToPath } = await Promise.resolve().then(() => require("./svgTextToPath-CWlhIf-q.cjs"));
17687
+ const { convertAllTextToPath } = await Promise.resolve().then(() => require("./svgTextToPath-DTKsddnS.cjs"));
16966
17688
  pageSvg = await convertAllTextToPath(pageSvg, fontBaseUrl, { mode: outlineSubMode });
16967
17689
  try {
16968
17690
  dumpSvgTextDiagnostics(pageSvg, i, PARITY_TAG, "STAGE-1b-after-text-to-path-raw");