@pixldocs/canvas-renderer 0.5.104 → 0.5.105

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,
@@ -6372,6 +6932,10 @@ const PageCanvas = forwardRef(
6372
6932
  skipFontReadyWait = false,
6373
6933
  onReady
6374
6934
  }, ref) => {
6935
+ setMarkdownThemeColors({
6936
+ primary: projectSettings.primaryColor || "#7c3aed",
6937
+ secondary: projectSettings.secondaryColor || "#ffe066"
6938
+ });
6375
6939
  const isEditorMode = mode === "editor";
6376
6940
  const isPreviewMode = mode === "preview";
6377
6941
  const allowEditing = isEditorMode;
@@ -6467,8 +7031,8 @@ const PageCanvas = forwardRef(
6467
7031
  }, [selectedIds]);
6468
7032
  useEffect(() => {
6469
7033
  isActiveRef.current = isActive;
6470
- if (isActive && fabricRef.current) ;
6471
- }, [isActive, pageId]);
7034
+ if (isEditorMode && isActive && fabricRef.current) ;
7035
+ }, [isActive, isEditorMode, pageId]);
6472
7036
  const getObjId = useCallback((obj) => {
6473
7037
  return obj.__docuforgeId;
6474
7038
  }, []);
@@ -6606,11 +7170,18 @@ const PageCanvas = forwardRef(
6606
7170
  const targetWidth = Math.max(1, Number(element.width) > 0 ? Number(element.width) : Number(obj.width ?? 200));
6607
7171
  const overflowPolicy = element.overflowPolicy || "grow-and-push";
6608
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
+ }
6609
7180
  obj.set({
6610
7181
  width: targetWidth,
6611
7182
  minWidth: 1,
6612
7183
  dynamicMinWidth: 0,
6613
- text: element.text || "Text",
7184
+ text: reflowText,
6614
7185
  fontSize: element.fontSize || 16,
6615
7186
  fontFamily: element.fontFamily || "Open Sans",
6616
7187
  fontWeight: element.fontWeight || 400,
@@ -6619,6 +7190,11 @@ const PageCanvas = forwardRef(
6619
7190
  charSpacing: element.charSpacing || 0,
6620
7191
  splitByGrapheme
6621
7192
  });
7193
+ if (element.formattingEnabled === true) {
7194
+ obj.styles = reflowParsedStyles || {};
7195
+ }
7196
+ obj.editable = element.formattingEnabled !== true;
7197
+ obj.__formattingEnabled = element.formattingEnabled === true;
6622
7198
  obj.initDimensions();
6623
7199
  if (Math.abs((obj.width ?? 0) - targetWidth) > 0.01) {
6624
7200
  obj.width = targetWidth;
@@ -6626,10 +7202,26 @@ const PageCanvas = forwardRef(
6626
7202
  obj.dynamicMinWidth = 0;
6627
7203
  obj.setCoords();
6628
7204
  obj.dirty = true;
7205
+ try {
7206
+ obj._forceClearCache = true;
7207
+ if (typeof obj._clearCache === "function") obj._clearCache();
7208
+ } catch {
7209
+ }
6629
7210
  didReflow = true;
6630
7211
  };
6631
7212
  canvas2.getObjects().forEach(reflowObject);
6632
- 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
+ }
6633
7225
  }, []);
6634
7226
  useEffect(() => {
6635
7227
  if (!canvasElRef.current) return;
@@ -6676,7 +7268,8 @@ const PageCanvas = forwardRef(
6676
7268
  absolutePositioned: true
6677
7269
  });
6678
7270
  fabricRef.current = fabricCanvas;
6679
- registerFabricCanvas(pageId, fabricCanvas);
7271
+ const storeRegistryKey = registerFabricCanvas(pageId, fabricCanvas);
7272
+ fabricCanvas.__storeRegistryKey = storeRegistryKey;
6680
7273
  const initFonts = async () => {
6681
7274
  try {
6682
7275
  await preloadAllFonts();
@@ -6969,7 +7562,10 @@ const PageCanvas = forwardRef(
6969
7562
  if (!window.__fabricCanvasRegistry || !(window.__fabricCanvasRegistry instanceof Map)) {
6970
7563
  window.__fabricCanvasRegistry = /* @__PURE__ */ new Map();
6971
7564
  }
6972
- 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, {
6973
7569
  canvas: fabricCanvas,
6974
7570
  forceUnlockEdits
6975
7571
  });
@@ -7739,9 +8335,6 @@ const PageCanvas = forwardRef(
7739
8335
  scaleY: finalScaleY,
7740
8336
  transformMatrix: finalAbsoluteMatrix
7741
8337
  };
7742
- if (obj instanceof fabric.Textbox) {
7743
- elementUpdate.text = obj.text || "";
7744
- }
7745
8338
  if (sourceElement && sourceElement.opacity !== void 0) {
7746
8339
  elementUpdate.opacity = sourceElement.opacity;
7747
8340
  }
@@ -7820,6 +8413,12 @@ const PageCanvas = forwardRef(
7820
8413
  }
7821
8414
  if (target && target instanceof fabric.Textbox) {
7822
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
+ }
7823
8422
  editingTextIdRef.current = elementId || null;
7824
8423
  target.enterEditing();
7825
8424
  target.selectAll();
@@ -7884,7 +8483,19 @@ const PageCanvas = forwardRef(
7884
8483
  });
7885
8484
  return () => {
7886
8485
  setReady(false);
7887
- 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
+ }
7888
8499
  if (fabricCanvas.__fontCleanup) {
7889
8500
  fabricCanvas.__fontCleanup();
7890
8501
  }
@@ -9246,6 +9857,12 @@ const PageCanvas = forwardRef(
9246
9857
  } else if (obj instanceof fabric.Textbox) {
9247
9858
  const overflowPolicy = element.overflowPolicy || "grow-and-push";
9248
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
+ }
9249
9866
  let fontSize = element.fontSize || 16;
9250
9867
  element.minFontSize || 8;
9251
9868
  const maxLines = element.maxLines || 3;
@@ -9342,6 +9959,11 @@ const PageCanvas = forwardRef(
9342
9959
  splitByGrapheme,
9343
9960
  text
9344
9961
  });
9962
+ if (element.formattingEnabled === true) {
9963
+ obj.styles = parsedStyles || {};
9964
+ } else {
9965
+ obj.styles = element.styles || {};
9966
+ }
9345
9967
  obj.initDimensions();
9346
9968
  if (Math.abs((obj.width ?? 0) - textboxWidth) > 0.01) {
9347
9969
  obj.width = textboxWidth;
@@ -10346,6 +10968,7 @@ function PreviewCanvas({
10346
10968
  zoom = 1,
10347
10969
  absoluteZoom = false,
10348
10970
  skipFontReadyWait = false,
10971
+ pageIdOverride,
10349
10972
  className,
10350
10973
  onDynamicFieldClick,
10351
10974
  onReady
@@ -10412,13 +11035,19 @@ function PreviewCanvas({
10412
11035
  backgroundGradient: (_b2 = page == null ? void 0 : page.settings) == null ? void 0 : _b2.backgroundGradient
10413
11036
  };
10414
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]);
10415
- const projectSettings = useMemo(() => ({
10416
- showGrid: false,
10417
- snapToGrid: false,
10418
- gridSize: 10,
10419
- snapToGuides: false,
10420
- snapThreshold: 5
10421
- }), []);
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]);
10422
11051
  const handleDynamicFieldClick = useCallback((elementId) => {
10423
11052
  const fieldInfo = elementToFieldMap.get(elementId);
10424
11053
  if (fieldInfo && onDynamicFieldClick) {
@@ -10488,7 +11117,7 @@ function PreviewCanvas({
10488
11117
  PageCanvas,
10489
11118
  {
10490
11119
  ref: canvasRef,
10491
- pageId: page.id || `page-${pageIndex}`,
11120
+ pageId: pageIdOverride || page.id || `page-${pageIndex}`,
10492
11121
  elements,
10493
11122
  pageChildren: laidOutPageChildren,
10494
11123
  pageSettings,
@@ -12379,6 +13008,17 @@ function normalizeFontFamily(fontStack) {
12379
13008
  const first = fontStack.split(",")[0].trim();
12380
13009
  return first.replace(/^['"]|['"]$/g, "");
12381
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
+ }
12382
13022
  const loadedFonts = /* @__PURE__ */ new Set();
12383
13023
  const loadingPromises = /* @__PURE__ */ new Map();
12384
13024
  function withTimeout(promise, timeoutMs = 4e3) {
@@ -12404,28 +13044,19 @@ async function loadGoogleFontCSS(rawFontFamily) {
12404
13044
  const fontshareSlug = FONTSHARE_SLUGS[fontFamily];
12405
13045
  if (fontshareSlug) {
12406
13046
  const url2 = `https://api.fontshare.com/v2/css?f[]=${fontshareSlug}@300,400,500,700&display=swap`;
12407
- const link2 = document.createElement("link");
12408
- link2.rel = "stylesheet";
12409
- link2.href = url2;
12410
- await new Promise((resolve, reject) => {
12411
- link2.onload = () => resolve();
12412
- link2.onerror = () => reject(new Error(`Failed to load Fontshare font: ${fontFamily}`));
12413
- document.head.appendChild(link2);
12414
- });
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);
12415
13050
  loadedFonts.add(fontFamily);
12416
13051
  return;
12417
13052
  }
12418
13053
  const encoded = encodeURIComponent(fontFamily);
12419
13054
  const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
12420
- const link = document.createElement("link");
12421
- link.rel = "stylesheet";
12422
- link.href = url;
12423
- link.crossOrigin = "anonymous";
12424
- await new Promise((resolve, reject) => {
12425
- link.onload = () => resolve();
12426
- link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
12427
- document.head.appendChild(link);
12428
- });
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);
12429
13060
  loadedFonts.add(fontFamily);
12430
13061
  } catch (e) {
12431
13062
  console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
@@ -12495,6 +13126,23 @@ function collectFontDescriptorsFromConfig(config) {
12495
13126
  if (node.type === "text") {
12496
13127
  for (const w of [300, 400, 500, 600, 700]) {
12497
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
+ }
12498
13146
  }
12499
13147
  }
12500
13148
  }
@@ -13404,11 +14052,20 @@ function PixldocsPreview(props) {
13404
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..." }) })
13405
14053
  ] });
13406
14054
  }
13407
- const PACKAGE_VERSION = "0.5.104";
14055
+ const PACKAGE_VERSION = "0.5.105";
13408
14056
  const roundParityValue = (value) => {
13409
14057
  if (typeof value !== "number") return value;
13410
14058
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
13411
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
+ }
13412
14069
  function logJsonLine(tag, payload) {
13413
14070
  try {
13414
14071
  console.log(`${tag} ${JSON.stringify(payload, (_key, value) => roundParityValue(value))}`);
@@ -13446,6 +14103,9 @@ function installUnderlineFix(fab) {
13446
14103
  const hasOwn = !!this[type];
13447
14104
  const hasStyled = typeof this.styleHas === "function" && this.styleHas(type);
13448
14105
  if (!hasOwn && !hasStyled) return;
14106
+ if (!hasOwn && hasStyled) {
14107
+ return original.call(this, ctx, type);
14108
+ }
13449
14109
  const lines = this._textLines;
13450
14110
  const offsets = this.offsets;
13451
14111
  if (!Array.isArray(lines) || !offsets) {
@@ -13457,7 +14117,7 @@ function installUnderlineFix(fab) {
13457
14117
  let topOffset = this._getTopOffset();
13458
14118
  for (let i = 0, len = lines.length; i < len; i++) {
13459
14119
  const heightOfLine = this.getHeightOfLine(i);
13460
- const lineHas = !!this[type] || typeof this.styleHas === "function" && this.styleHas(type, i);
14120
+ const lineHas = !!this[type];
13461
14121
  if (!lineHas) {
13462
14122
  topOffset += heightOfLine;
13463
14123
  continue;
@@ -13987,9 +14647,11 @@ class PixldocsRenderer {
13987
14647
  }
13988
14648
  async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality, options = {}) {
13989
14649
  const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
13990
- const canvasWidth = config.canvas.width;
13991
- const canvasHeight = config.canvas.height;
13992
- 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);
13993
14655
  let firstMountSettled = false;
13994
14656
  let lateFontSettleDetected = false;
13995
14657
  if (typeof document !== "undefined" && document.fonts && hasAutoShrink) {
@@ -14037,12 +14699,12 @@ class PixldocsRenderer {
14037
14699
  root = createRoot(container);
14038
14700
  await new Promise((settle) => {
14039
14701
  const onReadyOnce = () => {
14040
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14702
+ this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
14041
14703
  const fabricInstance = this.getFabricCanvasFromContainer(container);
14042
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14704
+ const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
14043
14705
  await this.waitForCanvasImages(container, expectedImageCount);
14044
- await this.waitForStableTextMetrics(container, config);
14045
- await this.waitForCanvasScene(container, config, pageIndex);
14706
+ await this.waitForStableTextMetrics(container, renderConfig);
14707
+ await this.waitForCanvasScene(container, renderConfig, pageIndex);
14046
14708
  if (!fabricInstance) return settle();
14047
14709
  settle();
14048
14710
  }).catch(() => settle());
@@ -14050,8 +14712,9 @@ class PixldocsRenderer {
14050
14712
  root.render(
14051
14713
  createElement(PreviewCanvas2, {
14052
14714
  key: `remount-${mountKey}`,
14053
- config,
14715
+ config: renderConfig,
14054
14716
  pageIndex,
14717
+ pageIdOverride: capture.pageId,
14055
14718
  zoom: pixelRatio,
14056
14719
  absoluteZoom: true,
14057
14720
  skipFontReadyWait: false,
@@ -14061,13 +14724,13 @@ class PixldocsRenderer {
14061
14724
  });
14062
14725
  };
14063
14726
  const onReady = () => {
14064
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14727
+ this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
14065
14728
  try {
14066
14729
  const fabricInstance = this.getFabricCanvasFromContainer(container);
14067
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14730
+ const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
14068
14731
  await this.waitForCanvasImages(container, expectedImageCount);
14069
- await this.waitForStableTextMetrics(container, config);
14070
- await this.waitForCanvasScene(container, config, pageIndex);
14732
+ await this.waitForStableTextMetrics(container, renderConfig);
14733
+ await this.waitForCanvasScene(container, renderConfig, pageIndex);
14071
14734
  firstMountSettled = true;
14072
14735
  if (hasAutoShrink && lateFontSettleDetected) {
14073
14736
  console.log("[canvas-renderer][parity] late font-settle detected — remounting for auto-shrink reflow");
@@ -14093,7 +14756,7 @@ class PixldocsRenderer {
14093
14756
  }
14094
14757
  exportCtx.save();
14095
14758
  exportCtx.scale(sourceCanvasAfter.width / canvasWidth, sourceCanvasAfter.height / canvasHeight);
14096
- this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
14759
+ this.paintPageBackground(exportCtx, renderConfig.pages[pageIndex], canvasWidth, canvasHeight);
14097
14760
  exportCtx.restore();
14098
14761
  exportCtx.drawImage(sourceCanvasAfter, 0, 0);
14099
14762
  const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
@@ -14109,8 +14772,9 @@ class PixldocsRenderer {
14109
14772
  root = createRoot(container);
14110
14773
  root.render(
14111
14774
  createElement(PreviewCanvas2, {
14112
- config,
14775
+ config: renderConfig,
14113
14776
  pageIndex,
14777
+ pageIdOverride: capture.pageId,
14114
14778
  zoom: pixelRatio,
14115
14779
  absoluteZoom: true,
14116
14780
  skipFontReadyWait: false,
@@ -14131,6 +14795,8 @@ class PixldocsRenderer {
14131
14795
  captureSvgViaPreviewCanvas(config, pageIndex, canvasWidth, canvasHeight) {
14132
14796
  return new Promise(async (resolve, reject) => {
14133
14797
  const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
14798
+ const capture = isolatePageForCapture(config, pageIndex);
14799
+ const renderConfig = capture.config;
14134
14800
  const container = document.createElement("div");
14135
14801
  container.style.cssText = `
14136
14802
  position: fixed; left: -99999px; top: -99999px;
@@ -14157,8 +14823,9 @@ class PixldocsRenderer {
14157
14823
  root.render(
14158
14824
  createElement(PreviewCanvas2, {
14159
14825
  key: `svg-capture-${mountKey}`,
14160
- config,
14826
+ config: renderConfig,
14161
14827
  pageIndex,
14828
+ pageIdOverride: capture.pageId,
14162
14829
  zoom: 1,
14163
14830
  absoluteZoom: true,
14164
14831
  skipFontReadyWait: false,
@@ -14167,13 +14834,13 @@ class PixldocsRenderer {
14167
14834
  );
14168
14835
  };
14169
14836
  const onReady = () => {
14170
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14837
+ this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
14171
14838
  var _a, _b;
14172
14839
  try {
14173
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14840
+ const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
14174
14841
  await this.waitForCanvasImages(container, expectedImageCount);
14175
- await this.waitForStableTextMetrics(container, config);
14176
- await this.waitForCanvasScene(container, config, pageIndex);
14842
+ await this.waitForStableTextMetrics(container, renderConfig);
14843
+ await this.waitForCanvasScene(container, renderConfig, pageIndex);
14177
14844
  const fabricInstance = this.getFabricCanvasFromContainer(container);
14178
14845
  if (!fabricInstance) {
14179
14846
  cleanup();
@@ -14262,7 +14929,7 @@ class PixldocsRenderer {
14262
14929
  );
14263
14930
  if (prevVPT) fabricInstance.viewportTransform = prevVPT;
14264
14931
  fabricInstance.svgViewportTransformation = prevSvgVPT;
14265
- const page = config.pages[pageIndex];
14932
+ const page = renderConfig.pages[pageIndex];
14266
14933
  const backgroundColor = ((_a = page == null ? void 0 : page.settings) == null ? void 0 : _a.backgroundColor) || "#ffffff";
14267
14934
  const backgroundGradient = (_b = page == null ? void 0 : page.settings) == null ? void 0 : _b.backgroundGradient;
14268
14935
  cleanup();
@@ -14944,8 +15611,9 @@ function getFontPathForWeight(files, weight, isItalic = false) {
14944
15611
  }
14945
15612
  return files.regular;
14946
15613
  }
14947
- function isItalicPath(files, path) {
14948
- 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;
14949
15617
  }
14950
15618
  function getJsPDFFontName(fontName) {
14951
15619
  return fontName.replace(/\s+/g, "");
@@ -15018,10 +15686,10 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
15018
15686
  const resolvedWeight = resolveFontWeight(weight);
15019
15687
  const fontPath = getFontPathForWeight(fontFiles, resolvedWeight, isItalic);
15020
15688
  if (!fontPath) return false;
15021
- const hasItalicFile = isItalic && isItalicPath(fontFiles, fontPath);
15022
- const jsPdfFontName = getEmbeddedJsPDFFontName(fontName, weight, hasItalicFile);
15689
+ if (!isExactWeightItalicMatch(fontFiles, resolvedWeight, isItalic, fontPath)) return false;
15690
+ const jsPdfFontName = getEmbeddedJsPDFFontName(fontName, weight, isItalic);
15023
15691
  const label = FONT_WEIGHT_LABELS[resolvedWeight];
15024
- const italicSuffix = hasItalicFile ? "Italic" : "";
15692
+ const italicSuffix = isItalic ? "Italic" : "";
15025
15693
  const fileName = `${getJsPDFFontName(fontName)}-${label}${italicSuffix}.ttf`;
15026
15694
  const url = baseUrl + fontPath;
15027
15695
  try {
@@ -15036,6 +15704,7 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
15036
15704
  }
15037
15705
  }
15038
15706
  registeredFamilies.add(fontName);
15707
+ registeredVariants.add(variantKey(fontName, resolvedWeight, isItalic));
15039
15708
  return true;
15040
15709
  } catch (e) {
15041
15710
  console.warn(`[pdf-fonts] Failed to embed ${fontName} w${weight}:`, e);
@@ -15044,6 +15713,18 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
15044
15713
  }
15045
15714
  const googleFontNotFound = /* @__PURE__ */ new Set();
15046
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
+ };
15047
15728
  function bytesToBase64(bytes) {
15048
15729
  let binary = "";
15049
15730
  for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
@@ -15072,7 +15753,8 @@ async function fetchTtfViaProxy(family, weight, isItalic, source) {
15072
15753
  async function fetchGoogleFontTTF(fontFamily, weight, isItalic = false) {
15073
15754
  const cacheKey = `gf:${fontFamily}:${weight}:${isItalic ? "i" : "n"}`;
15074
15755
  if (ttfCache.has(cacheKey)) return ttfCache.get(cacheKey);
15075
- if (googleFontNotFound.has(fontFamily)) return null;
15756
+ const notFoundKey = remoteVariantKey(fontFamily, weight, isItalic);
15757
+ if (googleFontNotFound.has(notFoundKey)) return null;
15076
15758
  const proxyBytes = await fetchTtfViaProxy(fontFamily, weight, isItalic, "google");
15077
15759
  if (proxyBytes) {
15078
15760
  const b64 = bytesToBase64(proxyBytes);
@@ -15090,7 +15772,7 @@ async function fetchGoogleFontTTF(fontFamily, weight, isItalic = false) {
15090
15772
  }
15091
15773
  });
15092
15774
  if (!cssRes.ok) {
15093
- if (cssRes.status === 400 || cssRes.status === 404) googleFontNotFound.add(fontFamily);
15775
+ if (cssRes.status === 400 || cssRes.status === 404) googleFontNotFound.add(notFoundKey);
15094
15776
  return null;
15095
15777
  }
15096
15778
  const css = await cssRes.text();
@@ -15168,6 +15850,7 @@ function registerJsPdfFont(pdf, fontName, resolvedWeight, isItalic, base64) {
15168
15850
  }
15169
15851
  }
15170
15852
  registeredFamilies.add(fontName);
15853
+ registeredVariants.add(variantKey(fontName, resolvedWeight, isItalic));
15171
15854
  return true;
15172
15855
  } catch (err) {
15173
15856
  console.warn(`[pdf-fonts] registerJsPdfFont failed for ${fontName}:`, err);
@@ -15182,16 +15865,8 @@ async function embedFontWithGoogleFallback(pdf, fontName, weight = 400, fontBase
15182
15865
  const resolved = resolveFontWeight(weight);
15183
15866
  const fsB64 = await fetchFontshareTTF(fontName, resolved, isItalic);
15184
15867
  if (fsB64) return registerJsPdfFont(pdf, fontName, resolved, isItalic, fsB64);
15185
- if (isItalic) {
15186
- const fsUpright = await fetchFontshareTTF(fontName, resolved, false);
15187
- if (fsUpright) return registerJsPdfFont(pdf, fontName, resolved, isItalic, fsUpright);
15188
- }
15189
15868
  const b64 = await fetchGoogleFontTTF(fontName, resolved, isItalic);
15190
15869
  if (b64) return registerJsPdfFont(pdf, fontName, resolved, isItalic, b64);
15191
- if (isItalic) {
15192
- const uprightB64 = await fetchGoogleFontTTF(fontName, resolved, false);
15193
- if (uprightB64) return registerJsPdfFont(pdf, fontName, resolved, isItalic, uprightB64);
15194
- }
15195
15870
  return false;
15196
15871
  }
15197
15872
  async function embedFontsForConfig(pdf, config, fontBaseUrl) {
@@ -15211,9 +15886,15 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
15211
15886
  };
15212
15887
  const walkElements = (elements) => {
15213
15888
  for (const el of elements) {
15889
+ const addFontVariant = (family, weight, italic) => {
15890
+ fontKeys.add(`${family}${SEP}${weight}${SEP}${italic ? "italic" : "normal"}`);
15891
+ };
15214
15892
  if (el.fontFamily) {
15215
15893
  const w = normalizeWeight(el.fontWeight);
15216
- 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);
15217
15898
  }
15218
15899
  if (el.styles && typeof el.styles === "object") {
15219
15900
  for (const lineKey of Object.keys(el.styles)) {
@@ -15221,9 +15902,10 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
15221
15902
  if (lineStyles && typeof lineStyles === "object") {
15222
15903
  for (const charKey of Object.keys(lineStyles)) {
15223
15904
  const s = lineStyles[charKey];
15224
- if (s == null ? void 0 : s.fontFamily) {
15225
- const w = normalizeWeight(s.fontWeight);
15226
- 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 ?? "")));
15227
15909
  }
15228
15910
  }
15229
15911
  }
@@ -15237,19 +15919,21 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
15237
15919
  if (page.children) walkElements(page.children);
15238
15920
  if (page.elements) walkElements(page.elements);
15239
15921
  }
15240
- fontKeys.add(`${FONT_FALLBACK_SYMBOLS}${SEP}400`);
15922
+ fontKeys.add(`${FONT_FALLBACK_SYMBOLS}${SEP}400${SEP}normal`);
15241
15923
  for (const w of [300, 400, 500, 600, 700]) {
15242
- fontKeys.add(`${FONT_FALLBACK_DEVANAGARI}${SEP}${w}`);
15924
+ fontKeys.add(`${FONT_FALLBACK_DEVANAGARI}${SEP}${w}${SEP}normal`);
15243
15925
  }
15244
15926
  const embedded = /* @__PURE__ */ new Set();
15245
15927
  const tasks = [];
15246
15928
  for (const key of fontKeys) {
15247
15929
  const sep = key.indexOf(SEP);
15930
+ const secondSep = key.indexOf(SEP, sep + 1);
15248
15931
  const fontName = key.slice(0, sep);
15249
- 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";
15250
15934
  if (!isFontAvailable(fontName)) continue;
15251
15935
  tasks.push(
15252
- embedFont(pdf, fontName, weight, fontBaseUrl).then((ok) => {
15936
+ embedFont(pdf, fontName, weight, fontBaseUrl, italic).then((ok) => {
15253
15937
  if (ok) embedded.add(key);
15254
15938
  })
15255
15939
  );
@@ -15305,7 +15989,7 @@ function splitIntoRuns(text) {
15305
15989
  return runs;
15306
15990
  }
15307
15991
  function rewriteSvgFontsForJsPDF(svgStr) {
15308
- var _a, _b;
15992
+ var _a, _b, _c;
15309
15993
  const parser = new DOMParser();
15310
15994
  const doc = parser.parseFromString(svgStr, "image/svg+xml");
15311
15995
  const allTextEls = Array.from(doc.querySelectorAll("text, tspan, textPath"));
@@ -15337,6 +16021,13 @@ function rewriteSvgFontsForJsPDF(svgStr) {
15337
16021
  stylePairs.push(`font-style: normal`);
15338
16022
  return stylePairs.join("; ");
15339
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
+ };
15340
16031
  const getDepth = (el) => {
15341
16032
  let depth = 0;
15342
16033
  let current = el.parentElement;
@@ -15356,20 +16047,40 @@ function rewriteSvgFontsForJsPDF(svgStr) {
15356
16047
  const weightRaw = resolveInheritedValue(el, "font-weight") || "400";
15357
16048
  const styleRaw = resolveInheritedValue(el, "font-style") || "normal";
15358
16049
  const weight = resolveWeightNum(weightRaw);
15359
- const resolved = resolveFontWeight(weight);
15360
- const isItalic = /italic|oblique/i.test(styleRaw);
15361
- const jsPdfName = getEmbeddedJsPDFFontName(clean, resolved, isItalic);
15362
- 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 });
15363
16056
  }
15364
16057
  const textEls = allTextEls.sort((a, b) => getDepth(b) - getDepth(a));
15365
16058
  for (const el of textEls) {
15366
16059
  if (!el.isConnected) continue;
15367
16060
  const meta = sourceMeta.get(el);
15368
16061
  if (!meta) continue;
15369
- const { clean, resolved, isItalic, jsPdfName, inlineStyle, weight } = meta;
16062
+ const { clean, requested, resolved, isItalic, actualItalic, jsPdfName, inlineStyle, weight } = meta;
15370
16063
  el.setAttribute("data-source-font-family", clean);
15371
- el.setAttribute("data-source-font-weight", String(resolved));
16064
+ el.setAttribute("data-source-font-weight", String(requested));
15372
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
+ }
15373
16084
  const directText = Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent || "").join("");
15374
16085
  const hasDevanagari = containsDevanagari(directText);
15375
16086
  const hasSymbol = containsSymbol(directText);
@@ -15412,6 +16123,28 @@ function rewriteSvgFontsForJsPDF(svgStr) {
15412
16123
  el.setAttribute("style", buildStyleString(inlineStyle, jsPdfName));
15413
16124
  }
15414
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
+ }
15415
16148
  return new XMLSerializer().serializeToString(doc.documentElement);
15416
16149
  }
15417
16150
  function extractFontFamiliesFromSvgs(svgs) {
@@ -15442,14 +16175,29 @@ async function embedFontsInPdf(pdf, fontFamilies, fontBaseUrl) {
15442
16175
  if (ok) embedded.add(`${family}${w}`);
15443
16176
  })
15444
16177
  );
16178
+ tasks.push(
16179
+ embedFont(pdf, family, w, fontBaseUrl, true).then((ok) => {
16180
+ if (ok) embedded.add(`${family}${w}i`);
16181
+ })
16182
+ );
15445
16183
  }
15446
16184
  } else {
15447
- tasks.push(
15448
- embedFontWithGoogleFallback(pdf, family, 400, fontBaseUrl, false).then((ok) => {
15449
- if (ok) embedded.add(`${family}400`);
15450
- else console.warn(`[pdf-fonts] No TTF (local/Google/Fontshare) for "${family}" — will use Helvetica fallback`);
15451
- })
15452
- );
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
+ }
15453
16201
  }
15454
16202
  }
15455
16203
  await Promise.all(tasks);
@@ -15470,6 +16218,7 @@ const pdfFonts = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProp
15470
16218
  getEmbeddedJsPDFFontName,
15471
16219
  getFontPathForWeight,
15472
16220
  isFontAvailable,
16221
+ resolveBestRegisteredVariant,
15473
16222
  resolveFontWeight,
15474
16223
  rewriteSvgFontsForJsPDF
15475
16224
  }, Symbol.toStringTag, { value: "Module" }));
@@ -15509,9 +16258,14 @@ function dumpSvgTextDiagnostics(svgStr, pageIndex, tag, stage, maxItems = 30) {
15509
16258
  const sample = texts.slice(0, maxItems).map((t, idx) => {
15510
16259
  var _a, _b;
15511
16260
  const tspans = Array.from(t.querySelectorAll("tspan"));
15512
- const tspanInfo = tspans.slice(0, 4).map((s) => ({
16261
+ const tspanInfo = tspans.slice(0, 8).map((s) => ({
15513
16262
  x: s.getAttribute("x"),
15514
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),
15515
16269
  text: (s.textContent || "").slice(0, 40)
15516
16270
  }));
15517
16271
  let containerWidth = null;
@@ -16546,13 +17300,15 @@ async function convertTextDecorationsToLines(svg) {
16546
17300
  const textDecOnText = (textEl.getAttribute("text-decoration") || "").toLowerCase();
16547
17301
  const textStyleDec = (getInlineStyleValue(textEl, "text-decoration") || "").toLowerCase();
16548
17302
  const textHasUnderline = textDecOnText.includes("underline") || textStyleDec.includes("underline");
17303
+ const textHasLinethrough = textDecOnText.includes("line-through") || textStyleDec.includes("line-through");
16549
17304
  for (let si = 0; si < tspans.length; si++) {
16550
17305
  const tspan = tspans[si];
16551
17306
  const liveTspan = liveTspans == null ? void 0 : liveTspans[si];
16552
17307
  const tspanDec = (tspan.getAttribute("text-decoration") || "").toLowerCase();
16553
17308
  const tspanStyleDec = (getInlineStyleValue(tspan, "text-decoration") || "").toLowerCase();
16554
17309
  const hasUnderline = tspanDec.includes("underline") || tspanStyleDec.includes("underline") || textHasUnderline;
16555
- if (!hasUnderline) continue;
17310
+ const hasLinethrough = tspanDec.includes("line-through") || tspanStyleDec.includes("line-through") || textHasLinethrough;
17311
+ if (!hasUnderline && !hasLinethrough) continue;
16556
17312
  const content = tspan.textContent || "";
16557
17313
  if (!content.trim()) continue;
16558
17314
  const xAttr = tspan.getAttribute("x") ?? textEl.getAttribute("x") ?? "0";
@@ -16607,23 +17363,26 @@ async function convertTextDecorationsToLines(svg) {
16607
17363
  lineStartX = x - (textAnchor === "middle" ? textWidth / 2 : textAnchor === "end" ? textWidth : 0);
16608
17364
  lineEndX = lineStartX + textWidth;
16609
17365
  }
16610
- const underlineY = baselineY + fontSize * 0.15;
16611
17366
  const thickness = Math.max(0.5, fontSize * 0.066667);
16612
- const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
16613
- line.setAttribute("x1", String(lineStartX));
16614
- line.setAttribute("y1", String(underlineY));
16615
- line.setAttribute("x2", String(lineEndX));
16616
- line.setAttribute("y2", String(underlineY));
16617
- line.setAttribute("stroke", fill.startsWith("url(") ? "#000000" : fill);
16618
- line.setAttribute("stroke-width", String(thickness));
16619
- line.setAttribute("stroke-linecap", "butt");
16620
- line.setAttribute("fill", "none");
16621
- if (fillOpacity) line.setAttribute("stroke-opacity", fillOpacity);
16622
- if (textEl.parentElement) {
16623
- textEl.parentElement.insertBefore(line, textEl.nextSibling);
16624
- }
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);
16625
17384
  stripTextDecoration(tspan);
16626
- if (textHasUnderline) stripTextDecoration(textEl);
17385
+ if (textHasUnderline || textHasLinethrough) stripTextDecoration(textEl);
16627
17386
  }
16628
17387
  }
16629
17388
  if (tempContainer) {
@@ -16637,7 +17396,7 @@ async function convertSvgTextDecorationsToLinesString(svgStr) {
16637
17396
  if (typeof DOMParser === "undefined" || typeof XMLSerializer === "undefined") {
16638
17397
  return svgStr;
16639
17398
  }
16640
- 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)) {
16641
17400
  return svgStr;
16642
17401
  }
16643
17402
  try {
@@ -16929,7 +17688,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
16929
17688
  const hasGradient = !!((_b = (_a = page.backgroundGradient) == null ? void 0 : _a.stops) == null ? void 0 : _b.length);
16930
17689
  drawPageBackground(pdf, i, page.width, page.height, page.backgroundColor, page.backgroundGradient);
16931
17690
  const shouldStripBg = stripPageBackground ?? hasGradient;
16932
- const textMode = options.textMode ?? (options.outlineText ? "pixel-perfect" : "selectable");
17691
+ const textMode = options.textMode ?? (options.outlineText === false ? "selectable" : "selectable");
16933
17692
  const shouldOutlineText = textMode === "pixel-perfect" || textMode === "auto";
16934
17693
  const outlineSubMode = textMode === "auto" ? "complex-only" : "all";
16935
17694
  let pageSvg = page.svg;
@@ -16943,7 +17702,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
16943
17702
  }
16944
17703
  if (shouldOutlineText) {
16945
17704
  try {
16946
- const { convertAllTextToPath } = await import("./svgTextToPath-CpWdqc8K.js");
17705
+ const { convertAllTextToPath } = await import("./svgTextToPath-C20Obtt2.js");
16947
17706
  pageSvg = await convertAllTextToPath(pageSvg, fontBaseUrl, { mode: outlineSubMode });
16948
17707
  try {
16949
17708
  dumpSvgTextDiagnostics(pageSvg, i, PARITY_TAG, "STAGE-1b-after-text-to-path-raw");