@pixldocs/canvas-renderer 0.5.103 → 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.js CHANGED
@@ -4,6 +4,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
4
4
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
5
5
  import { forwardRef, useRef, useState, useMemo, useEffect, useCallback, useImperativeHandle, createElement } from "react";
6
6
  import { flushSync } from "react-dom";
7
+ import { toast } from "sonner";
7
8
  import { create } from "zustand";
8
9
  import * as fabric from "fabric";
9
10
  import { createRoot } from "react-dom/client";
@@ -2487,9 +2488,17 @@ const useEditorStore = create((set, get) => ({
2487
2488
  }));
2488
2489
  let canvasRegistry = /* @__PURE__ */ new Map();
2489
2490
  function registerFabricCanvas(pageId, canvas) {
2490
- canvasRegistry.set(pageId, canvas);
2491
- }
2492
- function unregisterFabricCanvas(pageId) {
2491
+ const existing = canvasRegistry.get(pageId);
2492
+ const registryKey = existing && existing !== canvas ? `${pageId}#${Math.random().toString(36).slice(2, 10)}` : pageId;
2493
+ canvasRegistry.set(registryKey, canvas);
2494
+ return registryKey;
2495
+ }
2496
+ function unregisterFabricCanvas(pageId, canvas) {
2497
+ if (canvas) {
2498
+ const existing = canvasRegistry.get(pageId);
2499
+ if (existing === canvas) canvasRegistry.delete(pageId);
2500
+ return;
2501
+ }
2493
2502
  canvasRegistry.delete(pageId);
2494
2503
  }
2495
2504
  const LOCAL_FONTS = /* @__PURE__ */ new Set([
@@ -2561,6 +2570,8 @@ const LOCAL_FONTS = /* @__PURE__ */ new Set([
2561
2570
  ]);
2562
2571
  const loadedGoogleFonts = /* @__PURE__ */ new Set();
2563
2572
  const failedGoogleFonts = /* @__PURE__ */ new Set();
2573
+ const loadedFontshareFonts = /* @__PURE__ */ new Set();
2574
+ const failedFontshareFonts = /* @__PURE__ */ new Set();
2564
2575
  const loadingPromises$1 = /* @__PURE__ */ new Map();
2565
2576
  async function loadGoogleFont(fontFamily, weights) {
2566
2577
  if (LOCAL_FONTS.has(fontFamily)) return true;
@@ -2620,6 +2631,346 @@ async function _doLoadGoogleFont(fontFamily, weights) {
2620
2631
  console.warn(`[GoogleFonts] Failed to load: ${fontFamily}`);
2621
2632
  return false;
2622
2633
  }
2634
+ async function loadFontshareFont(fontFamily, slug, weights) {
2635
+ if (loadedFontshareFonts.has(fontFamily)) return true;
2636
+ if (failedFontshareFonts.has(fontFamily)) return false;
2637
+ const existing = loadingPromises$1.get(`fontshare:${fontFamily}`);
2638
+ if (existing) return existing;
2639
+ const promise = (async () => {
2640
+ try {
2641
+ const weightStr = (weights || [300, 400, 500, 700]).join(",");
2642
+ const url = `https://api.fontshare.com/v2/css?f[]=${slug}@${weightStr}&display=swap`;
2643
+ if (document.querySelector(`link[href="${url}"]`)) return true;
2644
+ const link = document.createElement("link");
2645
+ link.rel = "stylesheet";
2646
+ link.href = url;
2647
+ return await new Promise((resolve) => {
2648
+ link.onload = async () => {
2649
+ try {
2650
+ await document.fonts.load(`16px "${fontFamily}"`);
2651
+ await document.fonts.load(`bold 16px "${fontFamily}"`);
2652
+ } catch {
2653
+ }
2654
+ resolve(true);
2655
+ };
2656
+ link.onerror = () => {
2657
+ console.warn(`[Fontshare] Failed to load: ${fontFamily}`);
2658
+ resolve(false);
2659
+ };
2660
+ document.head.appendChild(link);
2661
+ });
2662
+ } catch (e) {
2663
+ console.warn(`[Fontshare] Error loading ${fontFamily}:`, e);
2664
+ return false;
2665
+ }
2666
+ })();
2667
+ loadingPromises$1.set(`fontshare:${fontFamily}`, promise);
2668
+ try {
2669
+ const result = await promise;
2670
+ if (result) loadedFontshareFonts.add(fontFamily);
2671
+ else failedFontshareFonts.add(fontFamily);
2672
+ return result;
2673
+ } finally {
2674
+ loadingPromises$1.delete(`fontshare:${fontFamily}`);
2675
+ }
2676
+ }
2677
+ async function loadFont(fontFamily) {
2678
+ if (LOCAL_FONTS.has(fontFamily)) return true;
2679
+ const entry = findFontEntry(fontFamily);
2680
+ if ((entry == null ? void 0 : entry.source) === "fontshare" && entry.fontshareSlug) {
2681
+ return loadFontshareFont(fontFamily, entry.fontshareSlug);
2682
+ }
2683
+ return loadGoogleFont(fontFamily);
2684
+ }
2685
+ const EXTENDED_FONT_LIST = [
2686
+ // ═══════════════════════════════════════════════════════════════════
2687
+ // PREMIUM (Fontshare) — modern, professional aesthetic
2688
+ // ═══════════════════════════════════════════════════════════════════
2689
+ { name: "Satoshi", category: "Premium", local: false, source: "fontshare", fontshareSlug: "satoshi", popular: true },
2690
+ { name: "Cabinet Grotesk", category: "Premium", local: false, source: "fontshare", fontshareSlug: "cabinet-grotesk", popular: true },
2691
+ { name: "Clash Display", category: "Premium", local: false, source: "fontshare", fontshareSlug: "clash-display", popular: true },
2692
+ { name: "Clash Grotesk", category: "Premium", local: false, source: "fontshare", fontshareSlug: "clash-grotesk", popular: true },
2693
+ { name: "General Sans", category: "Premium", local: false, source: "fontshare", fontshareSlug: "general-sans", popular: true },
2694
+ { name: "Switzer", category: "Premium", local: false, source: "fontshare", fontshareSlug: "switzer" },
2695
+ { name: "Supreme", category: "Premium", local: false, source: "fontshare", fontshareSlug: "supreme" },
2696
+ { name: "Author", category: "Premium", local: false, source: "fontshare", fontshareSlug: "author" },
2697
+ { name: "Boska", category: "Premium", local: false, source: "fontshare", fontshareSlug: "boska" },
2698
+ { name: "Excon", category: "Premium", local: false, source: "fontshare", fontshareSlug: "excon" },
2699
+ { name: "Khand", category: "Premium", local: false, source: "fontshare", fontshareSlug: "khand" },
2700
+ { name: "Sentient", category: "Premium", local: false, source: "fontshare", fontshareSlug: "sentient" },
2701
+ { name: "Synonym", category: "Premium", local: false, source: "fontshare", fontshareSlug: "synonym" },
2702
+ { name: "Erode", category: "Premium", local: false, source: "fontshare", fontshareSlug: "erode" },
2703
+ { name: "Ranade", category: "Premium", local: false, source: "fontshare", fontshareSlug: "ranade" },
2704
+ { name: "Tanker", category: "Premium", local: false, source: "fontshare", fontshareSlug: "tanker" },
2705
+ { name: "Zodiak", category: "Premium", local: false, source: "fontshare", fontshareSlug: "zodiak" },
2706
+ { name: "Gambarino", category: "Premium", local: false, source: "fontshare", fontshareSlug: "gambarino" },
2707
+ { name: "Melodrama", category: "Premium", local: false, source: "fontshare", fontshareSlug: "melodrama" },
2708
+ { name: "Bespoke Serif", category: "Premium", local: false, source: "fontshare", fontshareSlug: "bespoke-serif" },
2709
+ { name: "Bespoke Stencil", category: "Premium", local: false, source: "fontshare", fontshareSlug: "bespoke-stencil" },
2710
+ { name: "Panchang", category: "Premium", local: false, source: "fontshare", fontshareSlug: "panchang" },
2711
+ { name: "Pally", category: "Premium", local: false, source: "fontshare", fontshareSlug: "pally" },
2712
+ { name: "Tabular", category: "Premium", local: false, source: "fontshare", fontshareSlug: "tabular" },
2713
+ { name: "Sharpie", category: "Premium", local: false, source: "fontshare", fontshareSlug: "sharpie" },
2714
+ { name: "Stardom", category: "Premium", local: false, source: "fontshare", fontshareSlug: "stardom" },
2715
+ { name: "Telma", category: "Premium", local: false, source: "fontshare", fontshareSlug: "telma" },
2716
+ { name: "Nippo", category: "Premium", local: false, source: "fontshare", fontshareSlug: "nippo" },
2717
+ // ═══════════════════════════════════════════════════════════════════
2718
+ // SERIF — editorial, classical, elegant
2719
+ // ═══════════════════════════════════════════════════════════════════
2720
+ { name: "Playfair Display", category: "Serif", local: true, popular: true },
2721
+ { name: "Cormorant", category: "Serif", local: false, popular: true },
2722
+ { name: "Cormorant Garamond", category: "Serif", local: false },
2723
+ { name: "Cinzel", category: "Serif", local: false, popular: true },
2724
+ { name: "Cinzel Decorative", category: "Serif", local: false },
2725
+ { name: "Bodoni Moda", category: "Serif", local: false, popular: true },
2726
+ { name: "DM Serif Display", category: "Serif", local: true },
2727
+ { name: "DM Serif Text", category: "Serif", local: false },
2728
+ { name: "Italiana", category: "Serif", local: false },
2729
+ { name: "Marcellus", category: "Serif", local: false },
2730
+ { name: "Marcellus SC", category: "Serif", local: false },
2731
+ { name: "Yeseva One", category: "Serif", local: false },
2732
+ { name: "Prata", category: "Serif", local: false },
2733
+ { name: "Tenor Sans", category: "Serif", local: false },
2734
+ { name: "Fraunces", category: "Serif", local: false, popular: true },
2735
+ { name: "Newsreader", category: "Serif", local: false },
2736
+ { name: "Source Serif Pro", category: "Serif", local: false },
2737
+ { name: "Merriweather", category: "Serif", local: true },
2738
+ { name: "Lora", category: "Serif", local: true },
2739
+ { name: "EB Garamond", category: "Serif", local: true },
2740
+ { name: "Libre Baskerville", category: "Serif", local: true },
2741
+ { name: "Libre Caslon Text", category: "Serif", local: false },
2742
+ { name: "Libre Caslon Display", category: "Serif", local: false },
2743
+ { name: "Crimson Text", category: "Serif", local: true },
2744
+ { name: "Crimson Pro", category: "Serif", local: false },
2745
+ { name: "Noto Serif", category: "Serif", local: false },
2746
+ { name: "Noto Serif Display", category: "Serif", local: false },
2747
+ { name: "PT Serif", category: "Serif", local: false },
2748
+ { name: "Bitter", category: "Serif", local: false },
2749
+ { name: "Spectral", category: "Serif", local: false },
2750
+ { name: "Cardo", category: "Serif", local: false },
2751
+ { name: "Old Standard TT", category: "Serif", local: false },
2752
+ { name: "Vollkorn", category: "Serif", local: false },
2753
+ { name: "Cantata One", category: "Serif", local: false },
2754
+ { name: "Domine", category: "Serif", local: false },
2755
+ { name: "Gentium Plus", category: "Serif", local: false },
2756
+ { name: "Tinos", category: "Serif", local: false },
2757
+ { name: "Trirong", category: "Serif", local: false },
2758
+ { name: "Sorts Mill Goudy", category: "Serif", local: false },
2759
+ { name: "IM Fell English", category: "Serif", local: false },
2760
+ { name: "IM Fell DW Pica", category: "Serif", local: false },
2761
+ { name: "Petrona", category: "Serif", local: false },
2762
+ { name: "Rozha One", category: "Serif", local: false },
2763
+ { name: "Tiro Devanagari Hindi", category: "Serif", local: false },
2764
+ // ═══════════════════════════════════════════════════════════════════
2765
+ // SANS-SERIF — clean, modern, workhorse
2766
+ // ═══════════════════════════════════════════════════════════════════
2767
+ { name: "Inter", category: "Sans-Serif", local: true, popular: true },
2768
+ { name: "Montserrat", category: "Sans-Serif", local: true, popular: true },
2769
+ { name: "Poppins", category: "Sans-Serif", local: true, popular: true },
2770
+ { name: "Open Sans", category: "Sans-Serif", local: true },
2771
+ { name: "Roboto", category: "Sans-Serif", local: true },
2772
+ { name: "Lato", category: "Sans-Serif", local: true },
2773
+ { name: "Raleway", category: "Sans-Serif", local: true },
2774
+ { name: "Nunito", category: "Sans-Serif", local: true },
2775
+ { name: "Source Sans Pro", category: "Sans-Serif", local: true },
2776
+ { name: "Work Sans", category: "Sans-Serif", local: true },
2777
+ { name: "DM Sans", category: "Sans-Serif", local: true, popular: true },
2778
+ { name: "Outfit", category: "Sans-Serif", local: true, popular: true },
2779
+ { name: "Figtree", category: "Sans-Serif", local: true },
2780
+ { name: "Manrope", category: "Sans-Serif", local: true, popular: true },
2781
+ { name: "Space Grotesk", category: "Sans-Serif", local: true, popular: true },
2782
+ { name: "Mulish", category: "Sans-Serif", local: true },
2783
+ { name: "Quicksand", category: "Sans-Serif", local: true },
2784
+ { name: "Rubik", category: "Sans-Serif", local: true },
2785
+ { name: "Karla", category: "Sans-Serif", local: true },
2786
+ { name: "Plus Jakarta Sans", category: "Sans-Serif", local: true },
2787
+ { name: "Libre Franklin", category: "Sans-Serif", local: true },
2788
+ { name: "Sora", category: "Sans-Serif", local: true },
2789
+ { name: "Urbanist", category: "Sans-Serif", local: true },
2790
+ { name: "Lexend", category: "Sans-Serif", local: true },
2791
+ { name: "Albert Sans", category: "Sans-Serif", local: true },
2792
+ { name: "Noto Sans", category: "Sans-Serif", local: true },
2793
+ { name: "Cabin", category: "Sans-Serif", local: true },
2794
+ { name: "Barlow", category: "Sans-Serif", local: true },
2795
+ { name: "Barlow Condensed", category: "Sans-Serif", local: false },
2796
+ { name: "Josefin Sans", category: "Sans-Serif", local: true },
2797
+ { name: "Archivo", category: "Sans-Serif", local: true },
2798
+ { name: "Archivo Narrow", category: "Sans-Serif", local: false },
2799
+ { name: "Overpass", category: "Sans-Serif", local: true },
2800
+ { name: "Exo 2", category: "Sans-Serif", local: true },
2801
+ { name: "Onest", category: "Sans-Serif", local: false, popular: true },
2802
+ { name: "Be Vietnam Pro", category: "Sans-Serif", local: false },
2803
+ { name: "Public Sans", category: "Sans-Serif", local: false },
2804
+ { name: "Red Hat Display", category: "Sans-Serif", local: false },
2805
+ { name: "Red Hat Text", category: "Sans-Serif", local: false },
2806
+ { name: "Sen", category: "Sans-Serif", local: false },
2807
+ { name: "Hanken Grotesk", category: "Sans-Serif", local: false },
2808
+ { name: "Schibsted Grotesk", category: "Sans-Serif", local: false },
2809
+ { name: "Reddit Sans", category: "Sans-Serif", local: false },
2810
+ { name: "Instrument Sans", category: "Sans-Serif", local: false },
2811
+ { name: "Geist", category: "Sans-Serif", local: false, popular: true },
2812
+ { name: "Nunito Sans", category: "Sans-Serif", local: false },
2813
+ { name: "PT Sans", category: "Sans-Serif", local: false },
2814
+ { name: "PT Sans Narrow", category: "Sans-Serif", local: false },
2815
+ { name: "Mukta", category: "Sans-Serif", local: false },
2816
+ { name: "Anek Devanagari", category: "Sans-Serif", local: false },
2817
+ { name: "Hind", category: "Sans-Serif", local: true },
2818
+ { name: "Hind Vadodara", category: "Sans-Serif", local: false },
2819
+ // ═══════════════════════════════════════════════════════════════════
2820
+ // DISPLAY — bold, attention-grabbing headlines
2821
+ // ═══════════════════════════════════════════════════════════════════
2822
+ { name: "Bebas Neue", category: "Display", local: true, popular: true },
2823
+ { name: "Anton", category: "Display", local: true, popular: true },
2824
+ { name: "Oswald", category: "Display", local: true },
2825
+ { name: "Abril Fatface", category: "Display", local: true, popular: true },
2826
+ { name: "League Spartan", category: "Display", local: true },
2827
+ { name: "Teko", category: "Display", local: true },
2828
+ { name: "Righteous", category: "Display", local: false },
2829
+ { name: "Alfa Slab One", category: "Display", local: false, popular: true },
2830
+ { name: "Archivo Black", category: "Display", local: false },
2831
+ { name: "Fredoka", category: "Display", local: false },
2832
+ { name: "Passion One", category: "Display", local: false },
2833
+ { name: "Bowlby One", category: "Display", local: false },
2834
+ { name: "Bowlby One SC", category: "Display", local: false },
2835
+ { name: "Secular One", category: "Display", local: false },
2836
+ { name: "Lilita One", category: "Display", local: false },
2837
+ { name: "Titan One", category: "Display", local: false },
2838
+ { name: "Russo One", category: "Display", local: false },
2839
+ { name: "Staatliches", category: "Display", local: false, popular: true },
2840
+ { name: "Dela Gothic One", category: "Display", local: false },
2841
+ { name: "Ultra", category: "Display", local: false },
2842
+ { name: "Sigmar One", category: "Display", local: false },
2843
+ { name: "Sigmar", category: "Display", local: false },
2844
+ { name: "Modak", category: "Display", local: false },
2845
+ { name: "Bagel Fat One", category: "Display", local: false },
2846
+ { name: "Climate Crisis", category: "Display", local: false },
2847
+ { name: "Yatra One", category: "Display", local: false },
2848
+ { name: "Bungee", category: "Display", local: false },
2849
+ { name: "Bungee Shade", category: "Display", local: false },
2850
+ { name: "Bungee Outline", category: "Display", local: false },
2851
+ { name: "Bungee Inline", category: "Display", local: false },
2852
+ { name: "Monoton", category: "Display", local: false, popular: true },
2853
+ { name: "Black Ops One", category: "Display", local: false },
2854
+ { name: "Faster One", category: "Display", local: false },
2855
+ { name: "Rubik Glitch", category: "Display", local: false },
2856
+ { name: "Rubik Mono One", category: "Display", local: false },
2857
+ { name: "Rubik Wet Paint", category: "Display", local: false },
2858
+ { name: "Rubik Bubbles", category: "Display", local: false },
2859
+ { name: "Rubik Beastly", category: "Display", local: false },
2860
+ { name: "Rubik Burned", category: "Display", local: false },
2861
+ { name: "Rubik Distressed", category: "Display", local: false },
2862
+ { name: "Rubik Iso", category: "Display", local: false },
2863
+ { name: "Rubik Marker Hatch", category: "Display", local: false },
2864
+ { name: "Rubik Maze", category: "Display", local: false },
2865
+ { name: "Rubik Pixels", category: "Display", local: false },
2866
+ { name: "Rubik Puddles", category: "Display", local: false },
2867
+ { name: "Rubik Spray Paint", category: "Display", local: false },
2868
+ { name: "Rubik Vinyl", category: "Display", local: false },
2869
+ { name: "Saira Stencil One", category: "Display", local: false },
2870
+ { name: "Audiowide", category: "Display", local: false },
2871
+ { name: "Orbitron", category: "Display", local: false },
2872
+ { name: "Plaster", category: "Display", local: false },
2873
+ // ═══════════════════════════════════════════════════════════════════
2874
+ // HANDWRITING / SCRIPT — fluid, personal, calligraphic
2875
+ // ═══════════════════════════════════════════════════════════════════
2876
+ { name: "Dancing Script", category: "Handwriting", local: true, popular: true },
2877
+ { name: "Pacifico", category: "Handwriting", local: true, popular: true },
2878
+ { name: "Great Vibes", category: "Handwriting", local: true, popular: true },
2879
+ { name: "Sacramento", category: "Handwriting", local: true },
2880
+ { name: "Alex Brush", category: "Handwriting", local: true },
2881
+ { name: "Allura", category: "Handwriting", local: true },
2882
+ { name: "Caveat", category: "Handwriting", local: true },
2883
+ { name: "Caveat Brush", category: "Handwriting", local: false },
2884
+ { name: "Lobster", category: "Handwriting", local: true },
2885
+ { name: "Lobster Two", category: "Handwriting", local: false },
2886
+ { name: "Comfortaa", category: "Handwriting", local: true },
2887
+ { name: "Tangerine", category: "Handwriting", local: false, popular: true },
2888
+ { name: "Yellowtail", category: "Handwriting", local: false, popular: true },
2889
+ { name: "Kaushan Script", category: "Handwriting", local: false, popular: true },
2890
+ { name: "Parisienne", category: "Handwriting", local: false },
2891
+ { name: "Petit Formal Script", category: "Handwriting", local: false },
2892
+ { name: "Pinyon Script", category: "Handwriting", local: false },
2893
+ { name: "Mrs Saint Delafield", category: "Handwriting", local: false },
2894
+ { name: "Marck Script", category: "Handwriting", local: false },
2895
+ { name: "Niconne", category: "Handwriting", local: false },
2896
+ { name: "Homemade Apple", category: "Handwriting", local: false },
2897
+ { name: "Permanent Marker", category: "Handwriting", local: false },
2898
+ { name: "Reenie Beanie", category: "Handwriting", local: false },
2899
+ { name: "Satisfy", category: "Handwriting", local: false },
2900
+ { name: "Kalam", category: "Handwriting", local: false },
2901
+ { name: "Indie Flower", category: "Handwriting", local: false },
2902
+ { name: "Courgette", category: "Handwriting", local: false },
2903
+ { name: "Cookie", category: "Handwriting", local: false },
2904
+ { name: "Shadows Into Light", category: "Handwriting", local: false },
2905
+ { name: "Patrick Hand", category: "Handwriting", local: false },
2906
+ { name: "Amatic SC", category: "Handwriting", local: false },
2907
+ { name: "Architects Daughter", category: "Handwriting", local: false },
2908
+ { name: "Gloria Hallelujah", category: "Handwriting", local: false },
2909
+ { name: "La Belle Aurore", category: "Handwriting", local: false },
2910
+ { name: "Mr Dafoe", category: "Handwriting", local: false },
2911
+ { name: "Italianno", category: "Handwriting", local: false },
2912
+ { name: "Rouge Script", category: "Handwriting", local: false },
2913
+ { name: "Grand Hotel", category: "Handwriting", local: false },
2914
+ { name: "Bilbo Swash Caps", category: "Handwriting", local: false },
2915
+ // ═══════════════════════════════════════════════════════════════════
2916
+ // DECORATIVE / FUN — quirky, themed, special-occasion
2917
+ // ═══════════════════════════════════════════════════════════════════
2918
+ { name: "Frijole", category: "Decorative", local: false },
2919
+ { name: "Creepster", category: "Decorative", local: false },
2920
+ { name: "Nosifer", category: "Decorative", local: false },
2921
+ { name: "Ewert", category: "Decorative", local: false },
2922
+ { name: "Lakki Reddy", category: "Decorative", local: false },
2923
+ { name: "Henny Penny", category: "Decorative", local: false },
2924
+ { name: "Special Elite", category: "Decorative", local: false, popular: true },
2925
+ { name: "Vast Shadow", category: "Decorative", local: false },
2926
+ { name: "Almendra Display", category: "Decorative", local: false },
2927
+ { name: "Eater", category: "Decorative", local: false },
2928
+ { name: "Butcherman", category: "Decorative", local: false },
2929
+ { name: "Pirata One", category: "Decorative", local: false },
2930
+ { name: "Metamorphous", category: "Decorative", local: false },
2931
+ { name: "MedievalSharp", category: "Decorative", local: false },
2932
+ { name: "Fascinate", category: "Decorative", local: false },
2933
+ { name: "Fascinate Inline", category: "Decorative", local: false },
2934
+ { name: "Sancreek", category: "Decorative", local: false },
2935
+ { name: "Smokum", category: "Decorative", local: false },
2936
+ { name: "Vampiro One", category: "Decorative", local: false },
2937
+ { name: "Mountains of Christmas", category: "Decorative", local: false },
2938
+ { name: "Caesar Dressing", category: "Decorative", local: false },
2939
+ { name: "Megrim", category: "Decorative", local: false },
2940
+ // ═══════════════════════════════════════════════════════════════════
2941
+ // BLACKLETTER — gothic, medieval, formal
2942
+ // ═══════════════════════════════════════════════════════════════════
2943
+ { name: "UnifrakturCook", category: "Blackletter", local: false },
2944
+ { name: "UnifrakturMaguntia", category: "Blackletter", local: false },
2945
+ // ═══════════════════════════════════════════════════════════════════
2946
+ // MONOSPACE — code, technical
2947
+ // ═══════════════════════════════════════════════════════════════════
2948
+ { name: "Roboto Mono", category: "Monospace", local: true },
2949
+ { name: "Fira Code", category: "Monospace", local: true },
2950
+ { name: "JetBrains Mono", category: "Monospace", local: true },
2951
+ { name: "Source Code Pro", category: "Monospace", local: true },
2952
+ { name: "IBM Plex Mono", category: "Monospace", local: true },
2953
+ { name: "Space Mono", category: "Monospace", local: true },
2954
+ { name: "Geist Mono", category: "Monospace", local: false },
2955
+ { name: "DM Mono", category: "Monospace", local: false },
2956
+ { name: "Inconsolata", category: "Monospace", local: false },
2957
+ { name: "Cousine", category: "Monospace", local: false },
2958
+ { name: "Anonymous Pro", category: "Monospace", local: false },
2959
+ { name: "Cutive Mono", category: "Monospace", local: false },
2960
+ { name: "Major Mono Display", category: "Monospace", local: false },
2961
+ { name: "VT323", category: "Monospace", local: false },
2962
+ { name: "Share Tech Mono", category: "Monospace", local: false }
2963
+ ];
2964
+ const _fontLookupCache = /* @__PURE__ */ new Map();
2965
+ function findFontEntry(name) {
2966
+ const key = name.toLowerCase().replace(/[\s\-_]/g, "");
2967
+ if (_fontLookupCache.has(key)) return _fontLookupCache.get(key);
2968
+ const entry = EXTENDED_FONT_LIST.find(
2969
+ (f) => f.name.toLowerCase().replace(/[\s\-_]/g, "") === key
2970
+ );
2971
+ if (entry) _fontLookupCache.set(key, entry);
2972
+ return entry;
2973
+ }
2623
2974
  const getObjectId = (obj) => obj.__docuforgeId;
2624
2975
  const setObjectData = (obj, id) => {
2625
2976
  obj.__docuforgeId = id;
@@ -2841,9 +3192,27 @@ const ensureFontLoaded = async (fontFamily) => {
2841
3192
  return;
2842
3193
  }
2843
3194
  try {
2844
- await loadGoogleFont(fontFamily);
3195
+ await loadFont(fontFamily);
2845
3196
  } catch (e) {
2846
- console.warn(`Failed to load Google Font: ${fontFamily}`, e);
3197
+ console.warn(`Failed to load font: ${fontFamily}`, e);
3198
+ try {
3199
+ await loadGoogleFont(fontFamily);
3200
+ } catch {
3201
+ }
3202
+ }
3203
+ try {
3204
+ if (document.fonts) {
3205
+ await Promise.race([
3206
+ Promise.all([
3207
+ document.fonts.load(`16px "${fontFamily}"`),
3208
+ document.fonts.load(`bold 16px "${fontFamily}"`),
3209
+ document.fonts.load(`italic 16px "${fontFamily}"`),
3210
+ document.fonts.load(`bold italic 16px "${fontFamily}"`)
3211
+ ]),
3212
+ new Promise((r) => setTimeout(r, 2500))
3213
+ ]);
3214
+ }
3215
+ } catch {
2847
3216
  }
2848
3217
  };
2849
3218
  const setupFontLoadingListener = (canvas, afterRerender) => {
@@ -5344,6 +5713,172 @@ function buildRoundedTrianglePath(w, h, rTop, rBR, rBL) {
5344
5713
  ];
5345
5714
  return parts.join(" ");
5346
5715
  }
5716
+ let activeThemeColors = {};
5717
+ function setMarkdownThemeColors(c) {
5718
+ activeThemeColors = { ...c };
5719
+ }
5720
+ function resolveColorToken(token, theme) {
5721
+ const raw = token.trim();
5722
+ const t = raw.toLowerCase();
5723
+ if (t === "primary") return theme.primary;
5724
+ if (t === "secondary") return theme.secondary;
5725
+ if (/^#([0-9a-f]{3,8})$/i.test(raw)) return raw;
5726
+ if (/^(rgb|rgba|hsl|hsla)\(/i.test(raw)) return raw;
5727
+ if (/^[a-z]+$/i.test(raw)) return raw;
5728
+ return void 0;
5729
+ }
5730
+ function mergeStyle(a, b) {
5731
+ return { ...a, ...b };
5732
+ }
5733
+ function tokenize(input, theme) {
5734
+ const runs = [];
5735
+ const stack = [];
5736
+ let buf = "";
5737
+ const activeStyle = () => {
5738
+ let s = {};
5739
+ for (const e of stack) s = mergeStyle(s, e.style);
5740
+ return s;
5741
+ };
5742
+ const flush = () => {
5743
+ if (buf.length === 0) return;
5744
+ runs.push({ text: buf, style: activeStyle() });
5745
+ buf = "";
5746
+ };
5747
+ let i = 0;
5748
+ const n = input.length;
5749
+ const peek = (s, at = i) => input.startsWith(s, at);
5750
+ const findUnescaped = (needle, from) => {
5751
+ let p = from;
5752
+ while (p < n) {
5753
+ if (input[p] === "\\" && p + 1 < n) {
5754
+ p += 2;
5755
+ continue;
5756
+ }
5757
+ if (input.startsWith(needle, p)) return p;
5758
+ p++;
5759
+ }
5760
+ return -1;
5761
+ };
5762
+ const tryOpenBracket = () => {
5763
+ if (input[i] !== "[") return -1;
5764
+ const m = /^\[(c|bg)=([^\]]+)\]/.exec(input.slice(i));
5765
+ if (!m) return -1;
5766
+ const kind = m[1];
5767
+ const tokenRaw = m[2];
5768
+ const closer = kind === "c" ? "[/c]" : "[/bg]";
5769
+ if (findUnescaped(closer, i + m[0].length) === -1) return -1;
5770
+ const color = resolveColorToken(tokenRaw, theme);
5771
+ const style = {};
5772
+ if (color) {
5773
+ if (kind === "c") style.fill = color;
5774
+ else style.textBackgroundColor = color;
5775
+ }
5776
+ flush();
5777
+ stack.push({ kind, style, closer });
5778
+ return i + m[0].length;
5779
+ };
5780
+ const tryCloseBracket = () => {
5781
+ for (let s = stack.length - 1; s >= 0; s--) {
5782
+ const top = stack[s];
5783
+ if (top.kind !== "c" && top.kind !== "bg") continue;
5784
+ if (peek(top.closer)) {
5785
+ if (s !== stack.length - 1) stack.length = s + 1;
5786
+ flush();
5787
+ stack.pop();
5788
+ return i + top.closer.length;
5789
+ }
5790
+ break;
5791
+ }
5792
+ return -1;
5793
+ };
5794
+ const toggle = (delim, kind, style) => {
5795
+ if (!peek(delim)) return null;
5796
+ const topIdx = stack.findIndex((e) => e.kind === kind);
5797
+ if (topIdx >= 0) {
5798
+ if (topIdx !== stack.length - 1) return null;
5799
+ flush();
5800
+ stack.length = topIdx;
5801
+ return i + delim.length;
5802
+ }
5803
+ if (findUnescaped(delim, i + delim.length) === -1) return null;
5804
+ flush();
5805
+ stack.push({ kind, style, closer: delim });
5806
+ return i + delim.length;
5807
+ };
5808
+ while (i < n) {
5809
+ const ch = input[i];
5810
+ if (ch === "\\" && i + 1 < n) {
5811
+ buf += input[i + 1];
5812
+ i += 2;
5813
+ continue;
5814
+ }
5815
+ if (ch === "[") {
5816
+ const closed = tryCloseBracket();
5817
+ if (closed > 0) {
5818
+ i = closed;
5819
+ continue;
5820
+ }
5821
+ const opened = tryOpenBracket();
5822
+ if (opened > 0) {
5823
+ i = opened;
5824
+ continue;
5825
+ }
5826
+ }
5827
+ let next;
5828
+ if ((next = toggle("**", "bold", { fontWeight: 700 })) !== null) {
5829
+ i = next;
5830
+ continue;
5831
+ }
5832
+ if ((next = toggle("__", "under", { underline: true })) !== null) {
5833
+ i = next;
5834
+ continue;
5835
+ }
5836
+ if ((next = toggle("~~", "strike", { linethrough: true })) !== null) {
5837
+ i = next;
5838
+ continue;
5839
+ }
5840
+ if ((next = toggle("==", "highlight", { textBackgroundColor: theme.secondary || "#ffe066" })) !== null) {
5841
+ i = next;
5842
+ continue;
5843
+ }
5844
+ if ((next = toggle("*", "italic", { fontStyle: "italic" })) !== null) {
5845
+ i = next;
5846
+ continue;
5847
+ }
5848
+ buf += ch;
5849
+ i++;
5850
+ }
5851
+ flush();
5852
+ return runs;
5853
+ }
5854
+ function parseTextMarkdown(input, themeColors) {
5855
+ const theme = activeThemeColors;
5856
+ const runs = tokenize(input ?? "", theme);
5857
+ let plain = "";
5858
+ const styles = {};
5859
+ let lineIdx = 0;
5860
+ let charIdx = 0;
5861
+ let hasFormatting = false;
5862
+ for (const run of runs) {
5863
+ const styleHasContent = Object.keys(run.style).length > 0;
5864
+ if (styleHasContent) hasFormatting = true;
5865
+ for (const ch of run.text) {
5866
+ if (ch === "\n") {
5867
+ plain += "\n";
5868
+ lineIdx++;
5869
+ charIdx = 0;
5870
+ continue;
5871
+ }
5872
+ plain += ch;
5873
+ if (styleHasContent) {
5874
+ if (!styles[lineIdx]) styles[lineIdx] = {};
5875
+ styles[lineIdx][charIdx] = { ...run.style };
5876
+ }
5877
+ charIdx++;
5878
+ }
5879
+ }
5880
+ return { plainText: plain, styles, hasFormatting };
5881
+ }
5347
5882
  const roundDiag = (value) => {
5348
5883
  if (typeof value !== "number") return value;
5349
5884
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -5482,6 +6017,13 @@ function createText(element) {
5482
6017
  let fontSize = element.fontSize || 16;
5483
6018
  const minFontSize = element.minFontSize || 8;
5484
6019
  const maxLines = element.maxLines || 3;
6020
+ const formattingEnabled = element.formattingEnabled === true;
6021
+ let parsedStyles = {};
6022
+ if (formattingEnabled) {
6023
+ const parsed = parseTextMarkdown(text);
6024
+ text = parsed.plainText || " ";
6025
+ parsedStyles = parsed.styles;
6026
+ }
5485
6027
  const baseWidth = element.width && element.width > 0 ? element.width : 200;
5486
6028
  const baseHeight = element.height;
5487
6029
  const fixedWidth = Math.max(baseWidth, 1);
@@ -5502,7 +6044,8 @@ function createText(element) {
5502
6044
  fontStyle: element.fontStyle || "normal",
5503
6045
  lineHeight: element.lineHeight || 1.2,
5504
6046
  charSpacing: element.charSpacing || 0,
5505
- splitByGrapheme: false
6047
+ splitByGrapheme: false,
6048
+ ...formattingEnabled ? { styles: parsedStyles } : {}
5506
6049
  });
5507
6050
  testTextbox.initDimensions();
5508
6051
  const textHeight = testTextbox.height || 0;
@@ -5614,8 +6157,16 @@ function createText(element) {
5614
6157
  objectCaching: false,
5615
6158
  noScaleCache: true,
5616
6159
  splitByGrapheme,
5617
- ...element.styles ? { styles: element.styles } : {}
6160
+ // When inline markdown formatting is enabled, the displayed text is the
6161
+ // PARSED plain text (markdown source lives separately on the element).
6162
+ // Allowing canvas inline editing would let the user edit that plain text
6163
+ // and on commit we'd save it back as the new markdown source — wiping all
6164
+ // formatting tokens (**, __, [c=...], etc). Disable inline edit and steer
6165
+ // users to the right-panel text field which exposes the raw markdown.
6166
+ editable: !formattingEnabled,
6167
+ ...formattingEnabled ? { styles: parsedStyles } : element.styles ? { styles: element.styles } : {}
5618
6168
  });
6169
+ textbox.__formattingEnabled = formattingEnabled;
5619
6170
  textbox.initDimensions();
5620
6171
  textbox.set({
5621
6172
  width: targetWidth,
@@ -5636,6 +6187,15 @@ function createText(element) {
5636
6187
  textbox.setCoords();
5637
6188
  }
5638
6189
  textbox.dirty = true;
6190
+ if (formattingEnabled) {
6191
+ try {
6192
+ textbox._forceClearCache = true;
6193
+ if (typeof textbox._clearCache === "function") {
6194
+ textbox._clearCache();
6195
+ }
6196
+ } catch {
6197
+ }
6198
+ }
5639
6199
  if (overflowPolicy === "auto-shrink" && typeof window !== "undefined" && window.__pixldocsDebugAutoShrink === true) {
5640
6200
  console.log("[auto-shrink][final-textbox] " + stringifyDiag({
5641
6201
  id: element.id,
@@ -6250,7 +6810,6 @@ function bakeEdgeFade(source, fade) {
6250
6810
  let x = 0, y = 0, rectW = mask.width, rectH = mask.height;
6251
6811
  const STOPS = 64;
6252
6812
  const gamma = 1 / hardness;
6253
- const seamGuardPx = 2;
6254
6813
  const innerAlpha = (t) => {
6255
6814
  const keepProgress = Math.pow(t, gamma);
6256
6815
  const erase = (1 - amount) * (1 - keepProgress);
@@ -6288,16 +6847,6 @@ function bakeEdgeFade(source, fade) {
6288
6847
  }
6289
6848
  mctx.fillStyle = g;
6290
6849
  mctx.fillRect(x, y, rectW, rectH);
6291
- {
6292
- const edgePx = Math.min(seamGuardPx, side === "top" || side === "bottom" ? rectH : rectW);
6293
- mctx.fillStyle = "rgba(0,0,0,0)";
6294
- mctx.globalCompositeOperation = "copy";
6295
- if (side === "top") mctx.fillRect(0, 0, mask.width, edgePx);
6296
- if (side === "bottom") mctx.fillRect(0, mask.height - edgePx, mask.width, edgePx);
6297
- if (side === "left") mctx.fillRect(0, 0, edgePx, mask.height);
6298
- if (side === "right") mctx.fillRect(mask.width - edgePx, 0, edgePx, mask.height);
6299
- mctx.globalCompositeOperation = "source-over";
6300
- }
6301
6850
  ctx.globalCompositeOperation = "destination-in";
6302
6851
  ctx.drawImage(mask, 0, 0);
6303
6852
  ctx.globalCompositeOperation = "source-over";
@@ -6383,6 +6932,10 @@ const PageCanvas = forwardRef(
6383
6932
  skipFontReadyWait = false,
6384
6933
  onReady
6385
6934
  }, ref) => {
6935
+ setMarkdownThemeColors({
6936
+ primary: projectSettings.primaryColor || "#7c3aed",
6937
+ secondary: projectSettings.secondaryColor || "#ffe066"
6938
+ });
6386
6939
  const isEditorMode = mode === "editor";
6387
6940
  const isPreviewMode = mode === "preview";
6388
6941
  const allowEditing = isEditorMode;
@@ -6478,8 +7031,8 @@ const PageCanvas = forwardRef(
6478
7031
  }, [selectedIds]);
6479
7032
  useEffect(() => {
6480
7033
  isActiveRef.current = isActive;
6481
- if (isActive && fabricRef.current) ;
6482
- }, [isActive, pageId]);
7034
+ if (isEditorMode && isActive && fabricRef.current) ;
7035
+ }, [isActive, isEditorMode, pageId]);
6483
7036
  const getObjId = useCallback((obj) => {
6484
7037
  return obj.__docuforgeId;
6485
7038
  }, []);
@@ -6617,11 +7170,18 @@ const PageCanvas = forwardRef(
6617
7170
  const targetWidth = Math.max(1, Number(element.width) > 0 ? Number(element.width) : Number(obj.width ?? 200));
6618
7171
  const overflowPolicy = element.overflowPolicy || "grow-and-push";
6619
7172
  const splitByGrapheme = overflowPolicy === "auto-shrink" ? false : element.splitByGrapheme ?? element.wordWrap === "break-word";
7173
+ let reflowText = element.text || "Text";
7174
+ let reflowParsedStyles = null;
7175
+ if (element.formattingEnabled === true) {
7176
+ const parsed = parseTextMarkdown(reflowText);
7177
+ reflowText = parsed.plainText || " ";
7178
+ reflowParsedStyles = parsed.styles;
7179
+ }
6620
7180
  obj.set({
6621
7181
  width: targetWidth,
6622
7182
  minWidth: 1,
6623
7183
  dynamicMinWidth: 0,
6624
- text: element.text || "Text",
7184
+ text: reflowText,
6625
7185
  fontSize: element.fontSize || 16,
6626
7186
  fontFamily: element.fontFamily || "Open Sans",
6627
7187
  fontWeight: element.fontWeight || 400,
@@ -6630,6 +7190,11 @@ const PageCanvas = forwardRef(
6630
7190
  charSpacing: element.charSpacing || 0,
6631
7191
  splitByGrapheme
6632
7192
  });
7193
+ if (element.formattingEnabled === true) {
7194
+ obj.styles = reflowParsedStyles || {};
7195
+ }
7196
+ obj.editable = element.formattingEnabled !== true;
7197
+ obj.__formattingEnabled = element.formattingEnabled === true;
6633
7198
  obj.initDimensions();
6634
7199
  if (Math.abs((obj.width ?? 0) - targetWidth) > 0.01) {
6635
7200
  obj.width = targetWidth;
@@ -6637,10 +7202,26 @@ const PageCanvas = forwardRef(
6637
7202
  obj.dynamicMinWidth = 0;
6638
7203
  obj.setCoords();
6639
7204
  obj.dirty = true;
7205
+ try {
7206
+ obj._forceClearCache = true;
7207
+ if (typeof obj._clearCache === "function") obj._clearCache();
7208
+ } catch {
7209
+ }
6640
7210
  didReflow = true;
6641
7211
  };
6642
7212
  canvas2.getObjects().forEach(reflowObject);
6643
- if (didReflow) canvas2.requestRenderAll();
7213
+ if (didReflow) {
7214
+ canvas2.requestRenderAll();
7215
+ try {
7216
+ requestAnimationFrame(() => {
7217
+ try {
7218
+ canvas2.requestRenderAll();
7219
+ } catch {
7220
+ }
7221
+ });
7222
+ } catch {
7223
+ }
7224
+ }
6644
7225
  }, []);
6645
7226
  useEffect(() => {
6646
7227
  if (!canvasElRef.current) return;
@@ -6687,7 +7268,8 @@ const PageCanvas = forwardRef(
6687
7268
  absolutePositioned: true
6688
7269
  });
6689
7270
  fabricRef.current = fabricCanvas;
6690
- registerFabricCanvas(pageId, fabricCanvas);
7271
+ const storeRegistryKey = registerFabricCanvas(pageId, fabricCanvas);
7272
+ fabricCanvas.__storeRegistryKey = storeRegistryKey;
6691
7273
  const initFonts = async () => {
6692
7274
  try {
6693
7275
  await preloadAllFonts();
@@ -6980,7 +7562,10 @@ const PageCanvas = forwardRef(
6980
7562
  if (!window.__fabricCanvasRegistry || !(window.__fabricCanvasRegistry instanceof Map)) {
6981
7563
  window.__fabricCanvasRegistry = /* @__PURE__ */ new Map();
6982
7564
  }
6983
- window.__fabricCanvasRegistry.set(pageId, {
7565
+ const reg = window.__fabricCanvasRegistry;
7566
+ const registryKey = reg.has(pageId) ? `${pageId}#${Math.random().toString(36).slice(2, 10)}` : pageId;
7567
+ fabricCanvas.__registryKey = registryKey;
7568
+ reg.set(registryKey, {
6984
7569
  canvas: fabricCanvas,
6985
7570
  forceUnlockEdits
6986
7571
  });
@@ -7750,9 +8335,6 @@ const PageCanvas = forwardRef(
7750
8335
  scaleY: finalScaleY,
7751
8336
  transformMatrix: finalAbsoluteMatrix
7752
8337
  };
7753
- if (obj instanceof fabric.Textbox) {
7754
- elementUpdate.text = obj.text || "";
7755
- }
7756
8338
  if (sourceElement && sourceElement.opacity !== void 0) {
7757
8339
  elementUpdate.opacity = sourceElement.opacity;
7758
8340
  }
@@ -7831,6 +8413,12 @@ const PageCanvas = forwardRef(
7831
8413
  }
7832
8414
  if (target && target instanceof fabric.Textbox) {
7833
8415
  const elementId = getObjectId(target);
8416
+ if (target.__formattingEnabled === true || target.editable === false) {
8417
+ toast.info("Inline formatting is on — edit the text in the right panel.", {
8418
+ description: "This protects your **bold**, [c=...] and other formatting tokens."
8419
+ });
8420
+ return;
8421
+ }
7834
8422
  editingTextIdRef.current = elementId || null;
7835
8423
  target.enterEditing();
7836
8424
  target.selectAll();
@@ -7895,7 +8483,19 @@ const PageCanvas = forwardRef(
7895
8483
  });
7896
8484
  return () => {
7897
8485
  setReady(false);
7898
- unregisterFabricCanvas(pageId);
8486
+ unregisterFabricCanvas(fabricCanvas.__storeRegistryKey ?? pageId, fabricCanvas);
8487
+ try {
8488
+ if (typeof window !== "undefined") {
8489
+ const reg = window.__fabricCanvasRegistry;
8490
+ if (reg instanceof Map) {
8491
+ const key = fabricCanvas.__registryKey ?? pageId;
8492
+ const entry = reg.get(key);
8493
+ const entryCanvas = (entry == null ? void 0 : entry.canvas) ?? entry;
8494
+ if (entryCanvas === fabricCanvas) reg.delete(key);
8495
+ }
8496
+ }
8497
+ } catch {
8498
+ }
7899
8499
  if (fabricCanvas.__fontCleanup) {
7900
8500
  fabricCanvas.__fontCleanup();
7901
8501
  }
@@ -9257,6 +9857,12 @@ const PageCanvas = forwardRef(
9257
9857
  } else if (obj instanceof fabric.Textbox) {
9258
9858
  const overflowPolicy = element.overflowPolicy || "grow-and-push";
9259
9859
  let text = element.text || "Text";
9860
+ let parsedStyles = null;
9861
+ if (element.formattingEnabled === true) {
9862
+ const parsed = parseTextMarkdown(text);
9863
+ text = parsed.plainText || " ";
9864
+ parsedStyles = parsed.styles;
9865
+ }
9260
9866
  let fontSize = element.fontSize || 16;
9261
9867
  element.minFontSize || 8;
9262
9868
  const maxLines = element.maxLines || 3;
@@ -9353,6 +9959,11 @@ const PageCanvas = forwardRef(
9353
9959
  splitByGrapheme,
9354
9960
  text
9355
9961
  });
9962
+ if (element.formattingEnabled === true) {
9963
+ obj.styles = parsedStyles || {};
9964
+ } else {
9965
+ obj.styles = element.styles || {};
9966
+ }
9356
9967
  obj.initDimensions();
9357
9968
  if (Math.abs((obj.width ?? 0) - textboxWidth) > 0.01) {
9358
9969
  obj.width = textboxWidth;
@@ -9714,7 +10325,6 @@ const PageCanvas = forwardRef(
9714
10325
  const eraseAtEdge = 1 - amount;
9715
10326
  const STOPS = 64;
9716
10327
  const gamma = 1 / hardness;
9717
- const seamGuardPx = 2;
9718
10328
  const addStops = (g) => {
9719
10329
  for (let i = 0; i <= STOPS; i++) {
9720
10330
  const t = i / STOPS;
@@ -9729,32 +10339,24 @@ const PageCanvas = forwardRef(
9729
10339
  addStops(gradient);
9730
10340
  ctx.fillStyle = gradient;
9731
10341
  ctx.fillRect(x, y, w, band);
9732
- ctx.fillStyle = "rgba(0,0,0,1)";
9733
- ctx.fillRect(x, y, w, Math.min(seamGuardPx, band));
9734
10342
  } else if (side === "bottom") {
9735
10343
  const band = Math.max(1, h * size);
9736
10344
  gradient = ctx.createLinearGradient(0, y + h, 0, y + h - band);
9737
10345
  addStops(gradient);
9738
10346
  ctx.fillStyle = gradient;
9739
10347
  ctx.fillRect(x, y + h - band, w, band);
9740
- ctx.fillStyle = "rgba(0,0,0,1)";
9741
- ctx.fillRect(x, y + h - Math.min(seamGuardPx, band), w, Math.min(seamGuardPx, band));
9742
10348
  } else if (side === "left") {
9743
10349
  const band = Math.max(1, w * size);
9744
10350
  gradient = ctx.createLinearGradient(x, 0, x + band, 0);
9745
10351
  addStops(gradient);
9746
10352
  ctx.fillStyle = gradient;
9747
10353
  ctx.fillRect(x, y, band, h);
9748
- ctx.fillStyle = "rgba(0,0,0,1)";
9749
- ctx.fillRect(x, y, Math.min(seamGuardPx, band), h);
9750
10354
  } else {
9751
10355
  const band = Math.max(1, w * size);
9752
10356
  gradient = ctx.createLinearGradient(x + w, 0, x + w - band, 0);
9753
10357
  addStops(gradient);
9754
10358
  ctx.fillStyle = gradient;
9755
10359
  ctx.fillRect(x + w - band, y, band, h);
9756
- ctx.fillStyle = "rgba(0,0,0,1)";
9757
- ctx.fillRect(x + w - Math.min(seamGuardPx, band), y, Math.min(seamGuardPx, band), h);
9758
10360
  }
9759
10361
  ctx.restore();
9760
10362
  };
@@ -10366,6 +10968,7 @@ function PreviewCanvas({
10366
10968
  zoom = 1,
10367
10969
  absoluteZoom = false,
10368
10970
  skipFontReadyWait = false,
10971
+ pageIdOverride,
10369
10972
  className,
10370
10973
  onDynamicFieldClick,
10371
10974
  onReady
@@ -10432,13 +11035,19 @@ function PreviewCanvas({
10432
11035
  backgroundGradient: (_b2 = page == null ? void 0 : page.settings) == null ? void 0 : _b2.backgroundGradient
10433
11036
  };
10434
11037
  }, [(_d = page == null ? void 0 : page.settings) == null ? void 0 : _d.backgroundColor, (_e = page == null ? void 0 : page.settings) == null ? void 0 : _e.backgroundGradient]);
10435
- const projectSettings = useMemo(() => ({
10436
- showGrid: false,
10437
- snapToGrid: false,
10438
- gridSize: 10,
10439
- snapToGuides: false,
10440
- snapThreshold: 5
10441
- }), []);
11038
+ const projectSettings = useMemo(() => {
11039
+ var _a2, _b2, _c2;
11040
+ const vars = ((_a2 = config.themeConfig) == null ? void 0 : _a2.variables) || {};
11041
+ return {
11042
+ showGrid: false,
11043
+ snapToGrid: false,
11044
+ gridSize: 10,
11045
+ snapToGuides: false,
11046
+ snapThreshold: 5,
11047
+ primaryColor: (_b2 = vars.primary) == null ? void 0 : _b2.value,
11048
+ secondaryColor: (_c2 = vars.secondary) == null ? void 0 : _c2.value
11049
+ };
11050
+ }, [config.themeConfig]);
10442
11051
  const handleDynamicFieldClick = useCallback((elementId) => {
10443
11052
  const fieldInfo = elementToFieldMap.get(elementId);
10444
11053
  if (fieldInfo && onDynamicFieldClick) {
@@ -10508,7 +11117,7 @@ function PreviewCanvas({
10508
11117
  PageCanvas,
10509
11118
  {
10510
11119
  ref: canvasRef,
10511
- pageId: page.id || `page-${pageIndex}`,
11120
+ pageId: pageIdOverride || page.id || `page-${pageIndex}`,
10512
11121
  elements,
10513
11122
  pageChildren: laidOutPageChildren,
10514
11123
  pageSettings,
@@ -12399,6 +13008,17 @@ function normalizeFontFamily(fontStack) {
12399
13008
  const first = fontStack.split(",")[0].trim();
12400
13009
  return first.replace(/^['"]|['"]$/g, "");
12401
13010
  }
13011
+ function appendStylesheet(url, rejectOnError = true) {
13012
+ return new Promise((resolve, reject) => {
13013
+ const link = document.createElement("link");
13014
+ link.rel = "stylesheet";
13015
+ link.href = url;
13016
+ link.crossOrigin = "anonymous";
13017
+ link.onload = () => resolve();
13018
+ link.onerror = () => rejectOnError ? reject(new Error(`Failed to load stylesheet: ${url}`)) : resolve();
13019
+ document.head.appendChild(link);
13020
+ });
13021
+ }
12402
13022
  const loadedFonts = /* @__PURE__ */ new Set();
12403
13023
  const loadingPromises = /* @__PURE__ */ new Map();
12404
13024
  function withTimeout(promise, timeoutMs = 4e3) {
@@ -12424,28 +13044,19 @@ async function loadGoogleFontCSS(rawFontFamily) {
12424
13044
  const fontshareSlug = FONTSHARE_SLUGS[fontFamily];
12425
13045
  if (fontshareSlug) {
12426
13046
  const url2 = `https://api.fontshare.com/v2/css?f[]=${fontshareSlug}@300,400,500,700&display=swap`;
12427
- const link2 = document.createElement("link");
12428
- link2.rel = "stylesheet";
12429
- link2.href = url2;
12430
- await new Promise((resolve, reject) => {
12431
- link2.onload = () => resolve();
12432
- link2.onerror = () => reject(new Error(`Failed to load Fontshare font: ${fontFamily}`));
12433
- document.head.appendChild(link2);
12434
- });
13047
+ await appendStylesheet(url2);
13048
+ const italicUrl = `https://api.fontshare.com/v2/css?f[]=${fontshareSlug}@300i,400i,500i,700i&display=swap`;
13049
+ await withTimeout(appendStylesheet(italicUrl, false), 1500);
12435
13050
  loadedFonts.add(fontFamily);
12436
13051
  return;
12437
13052
  }
12438
13053
  const encoded = encodeURIComponent(fontFamily);
12439
13054
  const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
12440
- const link = document.createElement("link");
12441
- link.rel = "stylesheet";
12442
- link.href = url;
12443
- link.crossOrigin = "anonymous";
12444
- await new Promise((resolve, reject) => {
12445
- link.onload = () => resolve();
12446
- link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
12447
- document.head.appendChild(link);
12448
- });
13055
+ await appendStylesheet(url);
13056
+ await withTimeout(Promise.all([300, 400, 500, 600, 700].map((weight) => {
13057
+ const italicUrl = `https://fonts.googleapis.com/css2?family=${encoded}:ital,wght@1,${weight}&display=swap`;
13058
+ return appendStylesheet(italicUrl, false);
13059
+ })), 1800);
12449
13060
  loadedFonts.add(fontFamily);
12450
13061
  } catch (e) {
12451
13062
  console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
@@ -12515,6 +13126,23 @@ function collectFontDescriptorsFromConfig(config) {
12515
13126
  if (node.type === "text") {
12516
13127
  for (const w of [300, 400, 500, 600, 700]) {
12517
13128
  add(node.fontFamily, w, node.fontStyle);
13129
+ add(node.fontFamily, w, "italic");
13130
+ }
13131
+ }
13132
+ }
13133
+ if (node.formattingEnabled === true && node.fontFamily) {
13134
+ const parsed = parseTextMarkdown(String(node.text ?? ""));
13135
+ const parsedStyleEntries = Object.values(parsed.styles || {});
13136
+ for (const lineStyle of parsedStyleEntries) {
13137
+ if (lineStyle && typeof lineStyle === "object") {
13138
+ for (const charStyle of Object.values(lineStyle)) {
13139
+ if (!charStyle || typeof charStyle !== "object") continue;
13140
+ add(
13141
+ charStyle.fontFamily || node.fontFamily,
13142
+ charStyle.fontWeight ?? node.fontWeight,
13143
+ charStyle.fontStyle ?? node.fontStyle
13144
+ );
13145
+ }
12518
13146
  }
12519
13147
  }
12520
13148
  }
@@ -13424,11 +14052,20 @@ function PixldocsPreview(props) {
13424
14052
  !canvasSettled && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) })
13425
14053
  ] });
13426
14054
  }
13427
- const PACKAGE_VERSION = "0.5.103";
14055
+ const PACKAGE_VERSION = "0.5.105";
13428
14056
  const roundParityValue = (value) => {
13429
14057
  if (typeof value !== "number") return value;
13430
14058
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
13431
14059
  };
14060
+ function isolatePageForCapture(config, pageIndex) {
14061
+ var _a;
14062
+ const capturePageId = `__pixldocs_capture_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}_${pageIndex}`;
14063
+ const cloned = JSON.parse(JSON.stringify(config));
14064
+ if ((_a = cloned.pages) == null ? void 0 : _a[pageIndex]) {
14065
+ cloned.pages[pageIndex].id = capturePageId;
14066
+ }
14067
+ return { config: cloned, pageId: capturePageId };
14068
+ }
13432
14069
  function logJsonLine(tag, payload) {
13433
14070
  try {
13434
14071
  console.log(`${tag} ${JSON.stringify(payload, (_key, value) => roundParityValue(value))}`);
@@ -13466,6 +14103,9 @@ function installUnderlineFix(fab) {
13466
14103
  const hasOwn = !!this[type];
13467
14104
  const hasStyled = typeof this.styleHas === "function" && this.styleHas(type);
13468
14105
  if (!hasOwn && !hasStyled) return;
14106
+ if (!hasOwn && hasStyled) {
14107
+ return original.call(this, ctx, type);
14108
+ }
13469
14109
  const lines = this._textLines;
13470
14110
  const offsets = this.offsets;
13471
14111
  if (!Array.isArray(lines) || !offsets) {
@@ -13477,7 +14117,7 @@ function installUnderlineFix(fab) {
13477
14117
  let topOffset = this._getTopOffset();
13478
14118
  for (let i = 0, len = lines.length; i < len; i++) {
13479
14119
  const heightOfLine = this.getHeightOfLine(i);
13480
- const lineHas = !!this[type] || typeof this.styleHas === "function" && this.styleHas(type, i);
14120
+ const lineHas = !!this[type];
13481
14121
  if (!lineHas) {
13482
14122
  topOffset += heightOfLine;
13483
14123
  continue;
@@ -14007,9 +14647,11 @@ class PixldocsRenderer {
14007
14647
  }
14008
14648
  async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality, options = {}) {
14009
14649
  const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
14010
- const canvasWidth = config.canvas.width;
14011
- const canvasHeight = config.canvas.height;
14012
- const hasAutoShrink = configHasAutoShrinkText(config);
14650
+ const capture = isolatePageForCapture(config, pageIndex);
14651
+ const renderConfig = capture.config;
14652
+ const canvasWidth = renderConfig.canvas.width;
14653
+ const canvasHeight = renderConfig.canvas.height;
14654
+ const hasAutoShrink = configHasAutoShrinkText(renderConfig);
14013
14655
  let firstMountSettled = false;
14014
14656
  let lateFontSettleDetected = false;
14015
14657
  if (typeof document !== "undefined" && document.fonts && hasAutoShrink) {
@@ -14057,12 +14699,12 @@ class PixldocsRenderer {
14057
14699
  root = createRoot(container);
14058
14700
  await new Promise((settle) => {
14059
14701
  const onReadyOnce = () => {
14060
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14702
+ this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
14061
14703
  const fabricInstance = this.getFabricCanvasFromContainer(container);
14062
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14704
+ const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
14063
14705
  await this.waitForCanvasImages(container, expectedImageCount);
14064
- await this.waitForStableTextMetrics(container, config);
14065
- await this.waitForCanvasScene(container, config, pageIndex);
14706
+ await this.waitForStableTextMetrics(container, renderConfig);
14707
+ await this.waitForCanvasScene(container, renderConfig, pageIndex);
14066
14708
  if (!fabricInstance) return settle();
14067
14709
  settle();
14068
14710
  }).catch(() => settle());
@@ -14070,8 +14712,9 @@ class PixldocsRenderer {
14070
14712
  root.render(
14071
14713
  createElement(PreviewCanvas2, {
14072
14714
  key: `remount-${mountKey}`,
14073
- config,
14715
+ config: renderConfig,
14074
14716
  pageIndex,
14717
+ pageIdOverride: capture.pageId,
14075
14718
  zoom: pixelRatio,
14076
14719
  absoluteZoom: true,
14077
14720
  skipFontReadyWait: false,
@@ -14081,13 +14724,13 @@ class PixldocsRenderer {
14081
14724
  });
14082
14725
  };
14083
14726
  const onReady = () => {
14084
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14727
+ this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
14085
14728
  try {
14086
14729
  const fabricInstance = this.getFabricCanvasFromContainer(container);
14087
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14730
+ const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
14088
14731
  await this.waitForCanvasImages(container, expectedImageCount);
14089
- await this.waitForStableTextMetrics(container, config);
14090
- await this.waitForCanvasScene(container, config, pageIndex);
14732
+ await this.waitForStableTextMetrics(container, renderConfig);
14733
+ await this.waitForCanvasScene(container, renderConfig, pageIndex);
14091
14734
  firstMountSettled = true;
14092
14735
  if (hasAutoShrink && lateFontSettleDetected) {
14093
14736
  console.log("[canvas-renderer][parity] late font-settle detected — remounting for auto-shrink reflow");
@@ -14113,7 +14756,7 @@ class PixldocsRenderer {
14113
14756
  }
14114
14757
  exportCtx.save();
14115
14758
  exportCtx.scale(sourceCanvasAfter.width / canvasWidth, sourceCanvasAfter.height / canvasHeight);
14116
- this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
14759
+ this.paintPageBackground(exportCtx, renderConfig.pages[pageIndex], canvasWidth, canvasHeight);
14117
14760
  exportCtx.restore();
14118
14761
  exportCtx.drawImage(sourceCanvasAfter, 0, 0);
14119
14762
  const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
@@ -14129,8 +14772,9 @@ class PixldocsRenderer {
14129
14772
  root = createRoot(container);
14130
14773
  root.render(
14131
14774
  createElement(PreviewCanvas2, {
14132
- config,
14775
+ config: renderConfig,
14133
14776
  pageIndex,
14777
+ pageIdOverride: capture.pageId,
14134
14778
  zoom: pixelRatio,
14135
14779
  absoluteZoom: true,
14136
14780
  skipFontReadyWait: false,
@@ -14151,6 +14795,8 @@ class PixldocsRenderer {
14151
14795
  captureSvgViaPreviewCanvas(config, pageIndex, canvasWidth, canvasHeight) {
14152
14796
  return new Promise(async (resolve, reject) => {
14153
14797
  const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
14798
+ const capture = isolatePageForCapture(config, pageIndex);
14799
+ const renderConfig = capture.config;
14154
14800
  const container = document.createElement("div");
14155
14801
  container.style.cssText = `
14156
14802
  position: fixed; left: -99999px; top: -99999px;
@@ -14177,8 +14823,9 @@ class PixldocsRenderer {
14177
14823
  root.render(
14178
14824
  createElement(PreviewCanvas2, {
14179
14825
  key: `svg-capture-${mountKey}`,
14180
- config,
14826
+ config: renderConfig,
14181
14827
  pageIndex,
14828
+ pageIdOverride: capture.pageId,
14182
14829
  zoom: 1,
14183
14830
  absoluteZoom: true,
14184
14831
  skipFontReadyWait: false,
@@ -14187,13 +14834,13 @@ class PixldocsRenderer {
14187
14834
  );
14188
14835
  };
14189
14836
  const onReady = () => {
14190
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14837
+ this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
14191
14838
  var _a, _b;
14192
14839
  try {
14193
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14840
+ const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
14194
14841
  await this.waitForCanvasImages(container, expectedImageCount);
14195
- await this.waitForStableTextMetrics(container, config);
14196
- await this.waitForCanvasScene(container, config, pageIndex);
14842
+ await this.waitForStableTextMetrics(container, renderConfig);
14843
+ await this.waitForCanvasScene(container, renderConfig, pageIndex);
14197
14844
  const fabricInstance = this.getFabricCanvasFromContainer(container);
14198
14845
  if (!fabricInstance) {
14199
14846
  cleanup();
@@ -14282,7 +14929,7 @@ class PixldocsRenderer {
14282
14929
  );
14283
14930
  if (prevVPT) fabricInstance.viewportTransform = prevVPT;
14284
14931
  fabricInstance.svgViewportTransformation = prevSvgVPT;
14285
- const page = config.pages[pageIndex];
14932
+ const page = renderConfig.pages[pageIndex];
14286
14933
  const backgroundColor = ((_a = page == null ? void 0 : page.settings) == null ? void 0 : _a.backgroundColor) || "#ffffff";
14287
14934
  const backgroundGradient = (_b = page == null ? void 0 : page.settings) == null ? void 0 : _b.backgroundGradient;
14288
14935
  cleanup();
@@ -14964,8 +15611,9 @@ function getFontPathForWeight(files, weight, isItalic = false) {
14964
15611
  }
14965
15612
  return files.regular;
14966
15613
  }
14967
- function isItalicPath(files, path) {
14968
- return path === files.italic || path === files.boldItalic || path === files.lightItalic || path === files.mediumItalic || path === files.semiboldItalic;
15614
+ function isExactWeightItalicMatch(files, weight, isItalic, path) {
15615
+ 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";
15616
+ return files[exactKey] === path;
14969
15617
  }
14970
15618
  function getJsPDFFontName(fontName) {
14971
15619
  return fontName.replace(/\s+/g, "");
@@ -15038,10 +15686,10 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
15038
15686
  const resolvedWeight = resolveFontWeight(weight);
15039
15687
  const fontPath = getFontPathForWeight(fontFiles, resolvedWeight, isItalic);
15040
15688
  if (!fontPath) return false;
15041
- const hasItalicFile = isItalic && isItalicPath(fontFiles, fontPath);
15042
- const jsPdfFontName = getEmbeddedJsPDFFontName(fontName, weight, hasItalicFile);
15689
+ if (!isExactWeightItalicMatch(fontFiles, resolvedWeight, isItalic, fontPath)) return false;
15690
+ const jsPdfFontName = getEmbeddedJsPDFFontName(fontName, weight, isItalic);
15043
15691
  const label = FONT_WEIGHT_LABELS[resolvedWeight];
15044
- const italicSuffix = hasItalicFile ? "Italic" : "";
15692
+ const italicSuffix = isItalic ? "Italic" : "";
15045
15693
  const fileName = `${getJsPDFFontName(fontName)}-${label}${italicSuffix}.ttf`;
15046
15694
  const url = baseUrl + fontPath;
15047
15695
  try {
@@ -15056,6 +15704,7 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
15056
15704
  }
15057
15705
  }
15058
15706
  registeredFamilies.add(fontName);
15707
+ registeredVariants.add(variantKey(fontName, resolvedWeight, isItalic));
15059
15708
  return true;
15060
15709
  } catch (e) {
15061
15710
  console.warn(`[pdf-fonts] Failed to embed ${fontName} w${weight}:`, e);
@@ -15064,6 +15713,18 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
15064
15713
  }
15065
15714
  const googleFontNotFound = /* @__PURE__ */ new Set();
15066
15715
  const fontshareNotFound = /* @__PURE__ */ new Set();
15716
+ const remoteVariantKey = (family, weight, isItalic) => `${family}|${resolveFontWeight(weight)}|${isItalic ? "i" : "n"}`;
15717
+ const registeredVariants = /* @__PURE__ */ new Set();
15718
+ const variantKey = (family, weight, italic) => `${family}|${resolveFontWeight(weight)}|${italic ? "i" : "n"}`;
15719
+ const resolveBestRegisteredVariant = (family, weight, italic) => {
15720
+ const want = resolveFontWeight(weight);
15721
+ const weights = [300, 400, 500, 600, 700];
15722
+ if (registeredVariants.has(variantKey(family, want, italic))) return { weight: want, italic };
15723
+ const nearest = [...weights].sort((a, b) => Math.abs(a - want) - Math.abs(b - want) || (want >= 500 ? b - a : a - b));
15724
+ for (const w of nearest) if (registeredVariants.has(variantKey(family, w, italic))) return { weight: w, italic };
15725
+ for (const w of nearest) if (registeredVariants.has(variantKey(family, w, !italic))) return { weight: w, italic: !italic };
15726
+ return null;
15727
+ };
15067
15728
  function bytesToBase64(bytes) {
15068
15729
  let binary = "";
15069
15730
  for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
@@ -15092,7 +15753,8 @@ async function fetchTtfViaProxy(family, weight, isItalic, source) {
15092
15753
  async function fetchGoogleFontTTF(fontFamily, weight, isItalic = false) {
15093
15754
  const cacheKey = `gf:${fontFamily}:${weight}:${isItalic ? "i" : "n"}`;
15094
15755
  if (ttfCache.has(cacheKey)) return ttfCache.get(cacheKey);
15095
- if (googleFontNotFound.has(fontFamily)) return null;
15756
+ const notFoundKey = remoteVariantKey(fontFamily, weight, isItalic);
15757
+ if (googleFontNotFound.has(notFoundKey)) return null;
15096
15758
  const proxyBytes = await fetchTtfViaProxy(fontFamily, weight, isItalic, "google");
15097
15759
  if (proxyBytes) {
15098
15760
  const b64 = bytesToBase64(proxyBytes);
@@ -15110,7 +15772,7 @@ async function fetchGoogleFontTTF(fontFamily, weight, isItalic = false) {
15110
15772
  }
15111
15773
  });
15112
15774
  if (!cssRes.ok) {
15113
- if (cssRes.status === 400 || cssRes.status === 404) googleFontNotFound.add(fontFamily);
15775
+ if (cssRes.status === 400 || cssRes.status === 404) googleFontNotFound.add(notFoundKey);
15114
15776
  return null;
15115
15777
  }
15116
15778
  const css = await cssRes.text();
@@ -15188,6 +15850,7 @@ function registerJsPdfFont(pdf, fontName, resolvedWeight, isItalic, base64) {
15188
15850
  }
15189
15851
  }
15190
15852
  registeredFamilies.add(fontName);
15853
+ registeredVariants.add(variantKey(fontName, resolvedWeight, isItalic));
15191
15854
  return true;
15192
15855
  } catch (err) {
15193
15856
  console.warn(`[pdf-fonts] registerJsPdfFont failed for ${fontName}:`, err);
@@ -15202,16 +15865,8 @@ async function embedFontWithGoogleFallback(pdf, fontName, weight = 400, fontBase
15202
15865
  const resolved = resolveFontWeight(weight);
15203
15866
  const fsB64 = await fetchFontshareTTF(fontName, resolved, isItalic);
15204
15867
  if (fsB64) return registerJsPdfFont(pdf, fontName, resolved, isItalic, fsB64);
15205
- if (isItalic) {
15206
- const fsUpright = await fetchFontshareTTF(fontName, resolved, false);
15207
- if (fsUpright) return registerJsPdfFont(pdf, fontName, resolved, isItalic, fsUpright);
15208
- }
15209
15868
  const b64 = await fetchGoogleFontTTF(fontName, resolved, isItalic);
15210
15869
  if (b64) return registerJsPdfFont(pdf, fontName, resolved, isItalic, b64);
15211
- if (isItalic) {
15212
- const uprightB64 = await fetchGoogleFontTTF(fontName, resolved, false);
15213
- if (uprightB64) return registerJsPdfFont(pdf, fontName, resolved, isItalic, uprightB64);
15214
- }
15215
15870
  return false;
15216
15871
  }
15217
15872
  async function embedFontsForConfig(pdf, config, fontBaseUrl) {
@@ -15231,9 +15886,15 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
15231
15886
  };
15232
15887
  const walkElements = (elements) => {
15233
15888
  for (const el of elements) {
15889
+ const addFontVariant = (family, weight, italic) => {
15890
+ fontKeys.add(`${family}${SEP}${weight}${SEP}${italic ? "italic" : "normal"}`);
15891
+ };
15234
15892
  if (el.fontFamily) {
15235
15893
  const w = normalizeWeight(el.fontWeight);
15236
- fontKeys.add(`${el.fontFamily}${SEP}${w}`);
15894
+ addFontVariant(el.fontFamily, w, /italic|oblique/i.test(String(el.fontStyle ?? "")));
15895
+ addFontVariant(el.fontFamily, 700, false);
15896
+ addFontVariant(el.fontFamily, 400, true);
15897
+ addFontVariant(el.fontFamily, 700, true);
15237
15898
  }
15238
15899
  if (el.styles && typeof el.styles === "object") {
15239
15900
  for (const lineKey of Object.keys(el.styles)) {
@@ -15241,9 +15902,10 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
15241
15902
  if (lineStyles && typeof lineStyles === "object") {
15242
15903
  for (const charKey of Object.keys(lineStyles)) {
15243
15904
  const s = lineStyles[charKey];
15244
- if (s == null ? void 0 : s.fontFamily) {
15245
- const w = normalizeWeight(s.fontWeight);
15246
- fontKeys.add(`${s.fontFamily}${SEP}${w}`);
15905
+ const styledFamily = (s == null ? void 0 : s.fontFamily) || el.fontFamily;
15906
+ if (styledFamily) {
15907
+ const w = normalizeWeight(s.fontWeight ?? el.fontWeight);
15908
+ addFontVariant(styledFamily, w, /italic|oblique/i.test(String(s.fontStyle ?? el.fontStyle ?? "")));
15247
15909
  }
15248
15910
  }
15249
15911
  }
@@ -15257,19 +15919,21 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
15257
15919
  if (page.children) walkElements(page.children);
15258
15920
  if (page.elements) walkElements(page.elements);
15259
15921
  }
15260
- fontKeys.add(`${FONT_FALLBACK_SYMBOLS}${SEP}400`);
15922
+ fontKeys.add(`${FONT_FALLBACK_SYMBOLS}${SEP}400${SEP}normal`);
15261
15923
  for (const w of [300, 400, 500, 600, 700]) {
15262
- fontKeys.add(`${FONT_FALLBACK_DEVANAGARI}${SEP}${w}`);
15924
+ fontKeys.add(`${FONT_FALLBACK_DEVANAGARI}${SEP}${w}${SEP}normal`);
15263
15925
  }
15264
15926
  const embedded = /* @__PURE__ */ new Set();
15265
15927
  const tasks = [];
15266
15928
  for (const key of fontKeys) {
15267
15929
  const sep = key.indexOf(SEP);
15930
+ const secondSep = key.indexOf(SEP, sep + 1);
15268
15931
  const fontName = key.slice(0, sep);
15269
- const weight = parseInt(key.slice(sep + 1), 10);
15932
+ const weight = parseInt(key.slice(sep + 1, secondSep), 10);
15933
+ const italic = key.slice(secondSep + 1) === "italic";
15270
15934
  if (!isFontAvailable(fontName)) continue;
15271
15935
  tasks.push(
15272
- embedFont(pdf, fontName, weight, fontBaseUrl).then((ok) => {
15936
+ embedFont(pdf, fontName, weight, fontBaseUrl, italic).then((ok) => {
15273
15937
  if (ok) embedded.add(key);
15274
15938
  })
15275
15939
  );
@@ -15325,7 +15989,7 @@ function splitIntoRuns(text) {
15325
15989
  return runs;
15326
15990
  }
15327
15991
  function rewriteSvgFontsForJsPDF(svgStr) {
15328
- var _a, _b;
15992
+ var _a, _b, _c;
15329
15993
  const parser = new DOMParser();
15330
15994
  const doc = parser.parseFromString(svgStr, "image/svg+xml");
15331
15995
  const allTextEls = Array.from(doc.querySelectorAll("text, tspan, textPath"));
@@ -15357,6 +16021,13 @@ function rewriteSvgFontsForJsPDF(svgStr) {
15357
16021
  stylePairs.push(`font-style: normal`);
15358
16022
  return stylePairs.join("; ");
15359
16023
  };
16024
+ const applySyntheticItalicTransform = (el) => {
16025
+ const x = Number.parseFloat(resolveInheritedValue(el, "x") || "0") || 0;
16026
+ const y = Number.parseFloat(resolveInheritedValue(el, "y") || "0") || 0;
16027
+ const existing = el.getAttribute("transform") || "";
16028
+ const synth = `translate(${x} ${y}) skewX(-12) translate(${-x} ${-y})`;
16029
+ el.setAttribute("transform", existing ? `${existing} ${synth}` : synth);
16030
+ };
15360
16031
  const getDepth = (el) => {
15361
16032
  let depth = 0;
15362
16033
  let current = el.parentElement;
@@ -15376,20 +16047,40 @@ function rewriteSvgFontsForJsPDF(svgStr) {
15376
16047
  const weightRaw = resolveInheritedValue(el, "font-weight") || "400";
15377
16048
  const styleRaw = resolveInheritedValue(el, "font-style") || "normal";
15378
16049
  const weight = resolveWeightNum(weightRaw);
15379
- const resolved = resolveFontWeight(weight);
15380
- const isItalic = /italic|oblique/i.test(styleRaw);
15381
- const jsPdfName = getEmbeddedJsPDFFontName(clean, resolved, isItalic);
15382
- sourceMeta.set(el, { clean, resolved, isItalic, jsPdfName, inlineStyle, weight });
16050
+ const requested = resolveFontWeight(weight);
16051
+ const requestedItalic = /italic|oblique/i.test(styleRaw);
16052
+ const best = resolveBestRegisteredVariant(clean, requested, requestedItalic);
16053
+ if (!best) continue;
16054
+ const jsPdfName = getEmbeddedJsPDFFontName(clean, best.weight, best.italic);
16055
+ sourceMeta.set(el, { clean, requested, resolved: best.weight, isItalic: requestedItalic, actualItalic: best.italic, jsPdfName, inlineStyle, weight });
15383
16056
  }
15384
16057
  const textEls = allTextEls.sort((a, b) => getDepth(b) - getDepth(a));
15385
16058
  for (const el of textEls) {
15386
16059
  if (!el.isConnected) continue;
15387
16060
  const meta = sourceMeta.get(el);
15388
16061
  if (!meta) continue;
15389
- const { clean, resolved, isItalic, jsPdfName, inlineStyle, weight } = meta;
16062
+ const { clean, requested, resolved, isItalic, actualItalic, jsPdfName, inlineStyle, weight } = meta;
15390
16063
  el.setAttribute("data-source-font-family", clean);
15391
- el.setAttribute("data-source-font-weight", String(resolved));
16064
+ el.setAttribute("data-source-font-weight", String(requested));
15392
16065
  el.setAttribute("data-source-font-style", isItalic ? "italic" : "normal");
16066
+ if (requested >= 600 && resolved < 600) {
16067
+ const fill = resolveInheritedValue(el, "fill") || readStyleToken(inlineStyle, "fill") || "#000000";
16068
+ el.setAttribute("stroke", fill);
16069
+ el.setAttribute("stroke-width", String(requested === 700 ? 0.7 : 0.5));
16070
+ el.setAttribute("stroke-linejoin", "round");
16071
+ }
16072
+ if (isItalic && !actualItalic) {
16073
+ applySyntheticItalicTransform(el);
16074
+ try {
16075
+ console.log("[Vector PDF][synthetic-italic]", {
16076
+ tag: el.tagName,
16077
+ family: clean,
16078
+ weight: requested,
16079
+ textPreview: (el.textContent || "").slice(0, 40)
16080
+ });
16081
+ } catch {
16082
+ }
16083
+ }
15393
16084
  const directText = Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent || "").join("");
15394
16085
  const hasDevanagari = containsDevanagari(directText);
15395
16086
  const hasSymbol = containsSymbol(directText);
@@ -15432,6 +16123,28 @@ function rewriteSvgFontsForJsPDF(svgStr) {
15432
16123
  el.setAttribute("style", buildStyleString(inlineStyle, jsPdfName));
15433
16124
  }
15434
16125
  }
16126
+ const SVG_NS = "http://www.w3.org/2000/svg";
16127
+ const transformedTspans = Array.from(doc.querySelectorAll("tspan[transform]"));
16128
+ for (const tspan of transformedTspans) {
16129
+ const parentText = tspan.parentElement;
16130
+ if (!parentText || parentText.tagName.toLowerCase() !== "text") continue;
16131
+ const transformVal = tspan.getAttribute("transform") || "";
16132
+ if (!/skewX/i.test(transformVal)) continue;
16133
+ const wrapper = doc.createElementNS(SVG_NS, "text");
16134
+ let parentTransform = "";
16135
+ for (const attr of Array.from(parentText.attributes)) {
16136
+ if (attr.name === "transform") {
16137
+ parentTransform = attr.value;
16138
+ continue;
16139
+ }
16140
+ wrapper.setAttribute(attr.name, attr.value);
16141
+ }
16142
+ const combined = parentTransform ? `${parentTransform} ${transformVal}` : transformVal;
16143
+ wrapper.setAttribute("transform", combined);
16144
+ tspan.removeAttribute("transform");
16145
+ wrapper.appendChild(tspan);
16146
+ (_c = parentText.parentNode) == null ? void 0 : _c.insertBefore(wrapper, parentText.nextSibling);
16147
+ }
15435
16148
  return new XMLSerializer().serializeToString(doc.documentElement);
15436
16149
  }
15437
16150
  function extractFontFamiliesFromSvgs(svgs) {
@@ -15462,14 +16175,29 @@ async function embedFontsInPdf(pdf, fontFamilies, fontBaseUrl) {
15462
16175
  if (ok) embedded.add(`${family}${w}`);
15463
16176
  })
15464
16177
  );
16178
+ tasks.push(
16179
+ embedFont(pdf, family, w, fontBaseUrl, true).then((ok) => {
16180
+ if (ok) embedded.add(`${family}${w}i`);
16181
+ })
16182
+ );
15465
16183
  }
15466
16184
  } else {
15467
- tasks.push(
15468
- embedFontWithGoogleFallback(pdf, family, 400, fontBaseUrl, false).then((ok) => {
15469
- if (ok) embedded.add(`${family}400`);
15470
- else console.warn(`[pdf-fonts] No TTF (local/Google/Fontshare) for "${family}" — will use Helvetica fallback`);
15471
- })
15472
- );
16185
+ const variants = [
16186
+ { w: 400, italic: false },
16187
+ { w: 700, italic: false },
16188
+ { w: 400, italic: true },
16189
+ { w: 700, italic: true }
16190
+ ];
16191
+ for (const v of variants) {
16192
+ tasks.push(
16193
+ embedFontWithGoogleFallback(pdf, family, v.w, fontBaseUrl, v.italic).then((ok) => {
16194
+ if (ok) embedded.add(`${family}${v.w}${v.italic ? "i" : ""}`);
16195
+ else if (v.w === 400 && !v.italic) {
16196
+ console.warn(`[pdf-fonts] No TTF (local/Google/Fontshare) for "${family}" — will use Helvetica fallback`);
16197
+ }
16198
+ })
16199
+ );
16200
+ }
15473
16201
  }
15474
16202
  }
15475
16203
  await Promise.all(tasks);
@@ -15490,6 +16218,7 @@ const pdfFonts = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProp
15490
16218
  getEmbeddedJsPDFFontName,
15491
16219
  getFontPathForWeight,
15492
16220
  isFontAvailable,
16221
+ resolveBestRegisteredVariant,
15493
16222
  resolveFontWeight,
15494
16223
  rewriteSvgFontsForJsPDF
15495
16224
  }, Symbol.toStringTag, { value: "Module" }));
@@ -15529,9 +16258,14 @@ function dumpSvgTextDiagnostics(svgStr, pageIndex, tag, stage, maxItems = 30) {
15529
16258
  const sample = texts.slice(0, maxItems).map((t, idx) => {
15530
16259
  var _a, _b;
15531
16260
  const tspans = Array.from(t.querySelectorAll("tspan"));
15532
- const tspanInfo = tspans.slice(0, 4).map((s) => ({
16261
+ const tspanInfo = tspans.slice(0, 8).map((s) => ({
15533
16262
  x: s.getAttribute("x"),
15534
16263
  y: s.getAttribute("y"),
16264
+ fs: s.getAttribute("font-style"),
16265
+ fw: s.getAttribute("font-weight"),
16266
+ ff: s.getAttribute("font-family"),
16267
+ td: s.getAttribute("text-decoration"),
16268
+ style: (s.getAttribute("style") || "").slice(0, 120),
15535
16269
  text: (s.textContent || "").slice(0, 40)
15536
16270
  }));
15537
16271
  let containerWidth = null;
@@ -16566,13 +17300,15 @@ async function convertTextDecorationsToLines(svg) {
16566
17300
  const textDecOnText = (textEl.getAttribute("text-decoration") || "").toLowerCase();
16567
17301
  const textStyleDec = (getInlineStyleValue(textEl, "text-decoration") || "").toLowerCase();
16568
17302
  const textHasUnderline = textDecOnText.includes("underline") || textStyleDec.includes("underline");
17303
+ const textHasLinethrough = textDecOnText.includes("line-through") || textStyleDec.includes("line-through");
16569
17304
  for (let si = 0; si < tspans.length; si++) {
16570
17305
  const tspan = tspans[si];
16571
17306
  const liveTspan = liveTspans == null ? void 0 : liveTspans[si];
16572
17307
  const tspanDec = (tspan.getAttribute("text-decoration") || "").toLowerCase();
16573
17308
  const tspanStyleDec = (getInlineStyleValue(tspan, "text-decoration") || "").toLowerCase();
16574
17309
  const hasUnderline = tspanDec.includes("underline") || tspanStyleDec.includes("underline") || textHasUnderline;
16575
- if (!hasUnderline) continue;
17310
+ const hasLinethrough = tspanDec.includes("line-through") || tspanStyleDec.includes("line-through") || textHasLinethrough;
17311
+ if (!hasUnderline && !hasLinethrough) continue;
16576
17312
  const content = tspan.textContent || "";
16577
17313
  if (!content.trim()) continue;
16578
17314
  const xAttr = tspan.getAttribute("x") ?? textEl.getAttribute("x") ?? "0";
@@ -16627,23 +17363,26 @@ async function convertTextDecorationsToLines(svg) {
16627
17363
  lineStartX = x - (textAnchor === "middle" ? textWidth / 2 : textAnchor === "end" ? textWidth : 0);
16628
17364
  lineEndX = lineStartX + textWidth;
16629
17365
  }
16630
- const underlineY = baselineY + fontSize * 0.15;
16631
17366
  const thickness = Math.max(0.5, fontSize * 0.066667);
16632
- const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
16633
- line.setAttribute("x1", String(lineStartX));
16634
- line.setAttribute("y1", String(underlineY));
16635
- line.setAttribute("x2", String(lineEndX));
16636
- line.setAttribute("y2", String(underlineY));
16637
- line.setAttribute("stroke", fill.startsWith("url(") ? "#000000" : fill);
16638
- line.setAttribute("stroke-width", String(thickness));
16639
- line.setAttribute("stroke-linecap", "butt");
16640
- line.setAttribute("fill", "none");
16641
- if (fillOpacity) line.setAttribute("stroke-opacity", fillOpacity);
16642
- if (textEl.parentElement) {
16643
- textEl.parentElement.insertBefore(line, textEl.nextSibling);
16644
- }
17367
+ const makeLine = (yPos) => {
17368
+ const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
17369
+ line.setAttribute("x1", String(lineStartX));
17370
+ line.setAttribute("y1", String(yPos));
17371
+ line.setAttribute("x2", String(lineEndX));
17372
+ line.setAttribute("y2", String(yPos));
17373
+ line.setAttribute("stroke", fill.startsWith("url(") ? "#000000" : fill);
17374
+ line.setAttribute("stroke-width", String(thickness));
17375
+ line.setAttribute("stroke-linecap", "butt");
17376
+ line.setAttribute("fill", "none");
17377
+ if (fillOpacity) line.setAttribute("stroke-opacity", fillOpacity);
17378
+ if (textEl.parentElement) {
17379
+ textEl.parentElement.insertBefore(line, textEl.nextSibling);
17380
+ }
17381
+ };
17382
+ if (hasUnderline) makeLine(baselineY + fontSize * 0.15);
17383
+ if (hasLinethrough) makeLine(baselineY - fontSize * 0.3);
16645
17384
  stripTextDecoration(tspan);
16646
- if (textHasUnderline) stripTextDecoration(textEl);
17385
+ if (textHasUnderline || textHasLinethrough) stripTextDecoration(textEl);
16647
17386
  }
16648
17387
  }
16649
17388
  if (tempContainer) {
@@ -16657,7 +17396,7 @@ async function convertSvgTextDecorationsToLinesString(svgStr) {
16657
17396
  if (typeof DOMParser === "undefined" || typeof XMLSerializer === "undefined") {
16658
17397
  return svgStr;
16659
17398
  }
16660
- if (!/text-decoration/i.test(svgStr) && !/underline/i.test(svgStr)) {
17399
+ if (!/text-decoration/i.test(svgStr) && !/underline/i.test(svgStr) && !/line-through/i.test(svgStr)) {
16661
17400
  return svgStr;
16662
17401
  }
16663
17402
  try {
@@ -16949,7 +17688,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
16949
17688
  const hasGradient = !!((_b = (_a = page.backgroundGradient) == null ? void 0 : _a.stops) == null ? void 0 : _b.length);
16950
17689
  drawPageBackground(pdf, i, page.width, page.height, page.backgroundColor, page.backgroundGradient);
16951
17690
  const shouldStripBg = stripPageBackground ?? hasGradient;
16952
- const textMode = options.textMode ?? (options.outlineText ? "pixel-perfect" : "selectable");
17691
+ const textMode = options.textMode ?? (options.outlineText === false ? "selectable" : "selectable");
16953
17692
  const shouldOutlineText = textMode === "pixel-perfect" || textMode === "auto";
16954
17693
  const outlineSubMode = textMode === "auto" ? "complex-only" : "all";
16955
17694
  let pageSvg = page.svg;
@@ -16963,7 +17702,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
16963
17702
  }
16964
17703
  if (shouldOutlineText) {
16965
17704
  try {
16966
- const { convertAllTextToPath } = await import("./svgTextToPath-CpWdqc8K.js");
17705
+ const { convertAllTextToPath } = await import("./svgTextToPath-C20Obtt2.js");
16967
17706
  pageSvg = await convertAllTextToPath(pageSvg, fontBaseUrl, { mode: outlineSubMode });
16968
17707
  try {
16969
17708
  dumpSvgTextDiagnostics(pageSvg, i, PARITY_TAG, "STAGE-1b-after-text-to-path-raw");