@pixldocs/canvas-renderer 0.5.104 → 0.5.106

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,
@@ -12343,38 +12972,6 @@ function applyContentBoundsPagination(config) {
12343
12972
  if (!mutated) return config;
12344
12973
  return { ...config, pages: resultPages };
12345
12974
  }
12346
- const FONTSHARE_SLUGS = {
12347
- "Satoshi": "satoshi",
12348
- "Cabinet Grotesk": "cabinet-grotesk",
12349
- "Clash Display": "clash-display",
12350
- "Clash Grotesk": "clash-grotesk",
12351
- "General Sans": "general-sans",
12352
- "Switzer": "switzer",
12353
- "Supreme": "supreme",
12354
- "Author": "author",
12355
- "Boska": "boska",
12356
- "Excon": "excon",
12357
- "Khand": "khand",
12358
- "Sentient": "sentient",
12359
- "Synonym": "synonym",
12360
- "Erode": "erode",
12361
- "Ranade": "ranade",
12362
- "Tanker": "tanker",
12363
- "Zodiak": "zodiak",
12364
- "Gambarino": "gambarino",
12365
- "Melodrama": "melodrama",
12366
- "Bespoke Serif": "bespoke-serif",
12367
- "Bespoke Stencil": "bespoke-stencil",
12368
- "Panchang": "panchang",
12369
- "Quincy CF": "quincy-cf",
12370
- "Pally": "pally",
12371
- "Tabular": "tabular",
12372
- "Sharpie": "sharpie",
12373
- "Stardom": "stardom",
12374
- "Rebond Grotesque": "rebond-grotesque",
12375
- "Telma": "telma",
12376
- "Nippo": "nippo"
12377
- };
12378
12975
  function normalizeFontFamily(fontStack) {
12379
12976
  const first = fontStack.split(",")[0].trim();
12380
12977
  return first.replace(/^['"]|['"]$/g, "");
@@ -12401,31 +12998,11 @@ async function loadGoogleFontCSS(rawFontFamily) {
12401
12998
  if (existing) return existing;
12402
12999
  const promise = (async () => {
12403
13000
  try {
12404
- const fontshareSlug = FONTSHARE_SLUGS[fontFamily];
12405
- if (fontshareSlug) {
12406
- 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
- });
13001
+ if (LOCAL_FONTS.has(fontFamily)) {
12415
13002
  loadedFonts.add(fontFamily);
12416
13003
  return;
12417
13004
  }
12418
- const encoded = encodeURIComponent(fontFamily);
12419
- 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
- });
13005
+ await withTimeout(loadFont(fontFamily), 4e3);
12429
13006
  loadedFonts.add(fontFamily);
12430
13007
  } catch (e) {
12431
13008
  console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
@@ -12495,6 +13072,23 @@ function collectFontDescriptorsFromConfig(config) {
12495
13072
  if (node.type === "text") {
12496
13073
  for (const w of [300, 400, 500, 600, 700]) {
12497
13074
  add(node.fontFamily, w, node.fontStyle);
13075
+ add(node.fontFamily, w, "italic");
13076
+ }
13077
+ }
13078
+ }
13079
+ if (node.formattingEnabled === true && node.fontFamily) {
13080
+ const parsed = parseTextMarkdown(String(node.text ?? ""));
13081
+ const parsedStyleEntries = Object.values(parsed.styles || {});
13082
+ for (const lineStyle of parsedStyleEntries) {
13083
+ if (lineStyle && typeof lineStyle === "object") {
13084
+ for (const charStyle of Object.values(lineStyle)) {
13085
+ if (!charStyle || typeof charStyle !== "object") continue;
13086
+ add(
13087
+ charStyle.fontFamily || node.fontFamily,
13088
+ charStyle.fontWeight ?? node.fontWeight,
13089
+ charStyle.fontStyle ?? node.fontStyle
13090
+ );
13091
+ }
12498
13092
  }
12499
13093
  }
12500
13094
  }
@@ -13404,11 +13998,20 @@ function PixldocsPreview(props) {
13404
13998
  !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
13999
  ] });
13406
14000
  }
13407
- const PACKAGE_VERSION = "0.5.104";
14001
+ const PACKAGE_VERSION = "0.5.105";
13408
14002
  const roundParityValue = (value) => {
13409
14003
  if (typeof value !== "number") return value;
13410
14004
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
13411
14005
  };
14006
+ function isolatePageForCapture(config, pageIndex) {
14007
+ var _a;
14008
+ const capturePageId = `__pixldocs_capture_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}_${pageIndex}`;
14009
+ const cloned = JSON.parse(JSON.stringify(config));
14010
+ if ((_a = cloned.pages) == null ? void 0 : _a[pageIndex]) {
14011
+ cloned.pages[pageIndex].id = capturePageId;
14012
+ }
14013
+ return { config: cloned, pageId: capturePageId };
14014
+ }
13412
14015
  function logJsonLine(tag, payload) {
13413
14016
  try {
13414
14017
  console.log(`${tag} ${JSON.stringify(payload, (_key, value) => roundParityValue(value))}`);
@@ -13446,6 +14049,9 @@ function installUnderlineFix(fab) {
13446
14049
  const hasOwn = !!this[type];
13447
14050
  const hasStyled = typeof this.styleHas === "function" && this.styleHas(type);
13448
14051
  if (!hasOwn && !hasStyled) return;
14052
+ if (!hasOwn && hasStyled) {
14053
+ return original.call(this, ctx, type);
14054
+ }
13449
14055
  const lines = this._textLines;
13450
14056
  const offsets = this.offsets;
13451
14057
  if (!Array.isArray(lines) || !offsets) {
@@ -13457,7 +14063,7 @@ function installUnderlineFix(fab) {
13457
14063
  let topOffset = this._getTopOffset();
13458
14064
  for (let i = 0, len = lines.length; i < len; i++) {
13459
14065
  const heightOfLine = this.getHeightOfLine(i);
13460
- const lineHas = !!this[type] || typeof this.styleHas === "function" && this.styleHas(type, i);
14066
+ const lineHas = !!this[type];
13461
14067
  if (!lineHas) {
13462
14068
  topOffset += heightOfLine;
13463
14069
  continue;
@@ -13987,9 +14593,11 @@ class PixldocsRenderer {
13987
14593
  }
13988
14594
  async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality, options = {}) {
13989
14595
  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);
14596
+ const capture = isolatePageForCapture(config, pageIndex);
14597
+ const renderConfig = capture.config;
14598
+ const canvasWidth = renderConfig.canvas.width;
14599
+ const canvasHeight = renderConfig.canvas.height;
14600
+ const hasAutoShrink = configHasAutoShrinkText(renderConfig);
13993
14601
  let firstMountSettled = false;
13994
14602
  let lateFontSettleDetected = false;
13995
14603
  if (typeof document !== "undefined" && document.fonts && hasAutoShrink) {
@@ -14037,12 +14645,12 @@ class PixldocsRenderer {
14037
14645
  root = createRoot(container);
14038
14646
  await new Promise((settle) => {
14039
14647
  const onReadyOnce = () => {
14040
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14648
+ this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
14041
14649
  const fabricInstance = this.getFabricCanvasFromContainer(container);
14042
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14650
+ const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
14043
14651
  await this.waitForCanvasImages(container, expectedImageCount);
14044
- await this.waitForStableTextMetrics(container, config);
14045
- await this.waitForCanvasScene(container, config, pageIndex);
14652
+ await this.waitForStableTextMetrics(container, renderConfig);
14653
+ await this.waitForCanvasScene(container, renderConfig, pageIndex);
14046
14654
  if (!fabricInstance) return settle();
14047
14655
  settle();
14048
14656
  }).catch(() => settle());
@@ -14050,8 +14658,9 @@ class PixldocsRenderer {
14050
14658
  root.render(
14051
14659
  createElement(PreviewCanvas2, {
14052
14660
  key: `remount-${mountKey}`,
14053
- config,
14661
+ config: renderConfig,
14054
14662
  pageIndex,
14663
+ pageIdOverride: capture.pageId,
14055
14664
  zoom: pixelRatio,
14056
14665
  absoluteZoom: true,
14057
14666
  skipFontReadyWait: false,
@@ -14061,13 +14670,13 @@ class PixldocsRenderer {
14061
14670
  });
14062
14671
  };
14063
14672
  const onReady = () => {
14064
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14673
+ this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
14065
14674
  try {
14066
14675
  const fabricInstance = this.getFabricCanvasFromContainer(container);
14067
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14676
+ const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
14068
14677
  await this.waitForCanvasImages(container, expectedImageCount);
14069
- await this.waitForStableTextMetrics(container, config);
14070
- await this.waitForCanvasScene(container, config, pageIndex);
14678
+ await this.waitForStableTextMetrics(container, renderConfig);
14679
+ await this.waitForCanvasScene(container, renderConfig, pageIndex);
14071
14680
  firstMountSettled = true;
14072
14681
  if (hasAutoShrink && lateFontSettleDetected) {
14073
14682
  console.log("[canvas-renderer][parity] late font-settle detected — remounting for auto-shrink reflow");
@@ -14093,7 +14702,7 @@ class PixldocsRenderer {
14093
14702
  }
14094
14703
  exportCtx.save();
14095
14704
  exportCtx.scale(sourceCanvasAfter.width / canvasWidth, sourceCanvasAfter.height / canvasHeight);
14096
- this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
14705
+ this.paintPageBackground(exportCtx, renderConfig.pages[pageIndex], canvasWidth, canvasHeight);
14097
14706
  exportCtx.restore();
14098
14707
  exportCtx.drawImage(sourceCanvasAfter, 0, 0);
14099
14708
  const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
@@ -14109,8 +14718,9 @@ class PixldocsRenderer {
14109
14718
  root = createRoot(container);
14110
14719
  root.render(
14111
14720
  createElement(PreviewCanvas2, {
14112
- config,
14721
+ config: renderConfig,
14113
14722
  pageIndex,
14723
+ pageIdOverride: capture.pageId,
14114
14724
  zoom: pixelRatio,
14115
14725
  absoluteZoom: true,
14116
14726
  skipFontReadyWait: false,
@@ -14131,6 +14741,8 @@ class PixldocsRenderer {
14131
14741
  captureSvgViaPreviewCanvas(config, pageIndex, canvasWidth, canvasHeight) {
14132
14742
  return new Promise(async (resolve, reject) => {
14133
14743
  const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
14744
+ const capture = isolatePageForCapture(config, pageIndex);
14745
+ const renderConfig = capture.config;
14134
14746
  const container = document.createElement("div");
14135
14747
  container.style.cssText = `
14136
14748
  position: fixed; left: -99999px; top: -99999px;
@@ -14157,8 +14769,9 @@ class PixldocsRenderer {
14157
14769
  root.render(
14158
14770
  createElement(PreviewCanvas2, {
14159
14771
  key: `svg-capture-${mountKey}`,
14160
- config,
14772
+ config: renderConfig,
14161
14773
  pageIndex,
14774
+ pageIdOverride: capture.pageId,
14162
14775
  zoom: 1,
14163
14776
  absoluteZoom: true,
14164
14777
  skipFontReadyWait: false,
@@ -14167,13 +14780,13 @@ class PixldocsRenderer {
14167
14780
  );
14168
14781
  };
14169
14782
  const onReady = () => {
14170
- this.waitForCanvasScene(container, config, pageIndex).then(async () => {
14783
+ this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
14171
14784
  var _a, _b;
14172
14785
  try {
14173
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
14786
+ const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
14174
14787
  await this.waitForCanvasImages(container, expectedImageCount);
14175
- await this.waitForStableTextMetrics(container, config);
14176
- await this.waitForCanvasScene(container, config, pageIndex);
14788
+ await this.waitForStableTextMetrics(container, renderConfig);
14789
+ await this.waitForCanvasScene(container, renderConfig, pageIndex);
14177
14790
  const fabricInstance = this.getFabricCanvasFromContainer(container);
14178
14791
  if (!fabricInstance) {
14179
14792
  cleanup();
@@ -14262,7 +14875,7 @@ class PixldocsRenderer {
14262
14875
  );
14263
14876
  if (prevVPT) fabricInstance.viewportTransform = prevVPT;
14264
14877
  fabricInstance.svgViewportTransformation = prevSvgVPT;
14265
- const page = config.pages[pageIndex];
14878
+ const page = renderConfig.pages[pageIndex];
14266
14879
  const backgroundColor = ((_a = page == null ? void 0 : page.settings) == null ? void 0 : _a.backgroundColor) || "#ffffff";
14267
14880
  const backgroundGradient = (_b = page == null ? void 0 : page.settings) == null ? void 0 : _b.backgroundGradient;
14268
14881
  cleanup();
@@ -14944,8 +15557,9 @@ function getFontPathForWeight(files, weight, isItalic = false) {
14944
15557
  }
14945
15558
  return files.regular;
14946
15559
  }
14947
- function isItalicPath(files, path) {
14948
- return path === files.italic || path === files.boldItalic || path === files.lightItalic || path === files.mediumItalic || path === files.semiboldItalic;
15560
+ function isExactWeightItalicMatch(files, weight, isItalic, path) {
15561
+ 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";
15562
+ return files[exactKey] === path;
14949
15563
  }
14950
15564
  function getJsPDFFontName(fontName) {
14951
15565
  return fontName.replace(/\s+/g, "");
@@ -15018,10 +15632,10 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
15018
15632
  const resolvedWeight = resolveFontWeight(weight);
15019
15633
  const fontPath = getFontPathForWeight(fontFiles, resolvedWeight, isItalic);
15020
15634
  if (!fontPath) return false;
15021
- const hasItalicFile = isItalic && isItalicPath(fontFiles, fontPath);
15022
- const jsPdfFontName = getEmbeddedJsPDFFontName(fontName, weight, hasItalicFile);
15635
+ if (!isExactWeightItalicMatch(fontFiles, resolvedWeight, isItalic, fontPath)) return false;
15636
+ const jsPdfFontName = getEmbeddedJsPDFFontName(fontName, weight, isItalic);
15023
15637
  const label = FONT_WEIGHT_LABELS[resolvedWeight];
15024
- const italicSuffix = hasItalicFile ? "Italic" : "";
15638
+ const italicSuffix = isItalic ? "Italic" : "";
15025
15639
  const fileName = `${getJsPDFFontName(fontName)}-${label}${italicSuffix}.ttf`;
15026
15640
  const url = baseUrl + fontPath;
15027
15641
  try {
@@ -15036,6 +15650,7 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
15036
15650
  }
15037
15651
  }
15038
15652
  registeredFamilies.add(fontName);
15653
+ registeredVariants.add(variantKey(fontName, resolvedWeight, isItalic));
15039
15654
  return true;
15040
15655
  } catch (e) {
15041
15656
  console.warn(`[pdf-fonts] Failed to embed ${fontName} w${weight}:`, e);
@@ -15044,6 +15659,18 @@ async function embedFont(pdf, fontName, weight, fontBaseUrl, isItalic = false) {
15044
15659
  }
15045
15660
  const googleFontNotFound = /* @__PURE__ */ new Set();
15046
15661
  const fontshareNotFound = /* @__PURE__ */ new Set();
15662
+ const remoteVariantKey = (family, weight, isItalic) => `${family}|${resolveFontWeight(weight)}|${isItalic ? "i" : "n"}`;
15663
+ const registeredVariants = /* @__PURE__ */ new Set();
15664
+ const variantKey = (family, weight, italic) => `${family}|${resolveFontWeight(weight)}|${italic ? "i" : "n"}`;
15665
+ const resolveBestRegisteredVariant = (family, weight, italic) => {
15666
+ const want = resolveFontWeight(weight);
15667
+ const weights = [300, 400, 500, 600, 700];
15668
+ if (registeredVariants.has(variantKey(family, want, italic))) return { weight: want, italic };
15669
+ const nearest = [...weights].sort((a, b) => Math.abs(a - want) - Math.abs(b - want) || (want >= 500 ? b - a : a - b));
15670
+ for (const w of nearest) if (registeredVariants.has(variantKey(family, w, italic))) return { weight: w, italic };
15671
+ for (const w of nearest) if (registeredVariants.has(variantKey(family, w, !italic))) return { weight: w, italic: !italic };
15672
+ return null;
15673
+ };
15047
15674
  function bytesToBase64(bytes) {
15048
15675
  let binary = "";
15049
15676
  for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
@@ -15072,7 +15699,8 @@ async function fetchTtfViaProxy(family, weight, isItalic, source) {
15072
15699
  async function fetchGoogleFontTTF(fontFamily, weight, isItalic = false) {
15073
15700
  const cacheKey = `gf:${fontFamily}:${weight}:${isItalic ? "i" : "n"}`;
15074
15701
  if (ttfCache.has(cacheKey)) return ttfCache.get(cacheKey);
15075
- if (googleFontNotFound.has(fontFamily)) return null;
15702
+ const notFoundKey = remoteVariantKey(fontFamily, weight, isItalic);
15703
+ if (googleFontNotFound.has(notFoundKey)) return null;
15076
15704
  const proxyBytes = await fetchTtfViaProxy(fontFamily, weight, isItalic, "google");
15077
15705
  if (proxyBytes) {
15078
15706
  const b64 = bytesToBase64(proxyBytes);
@@ -15090,7 +15718,7 @@ async function fetchGoogleFontTTF(fontFamily, weight, isItalic = false) {
15090
15718
  }
15091
15719
  });
15092
15720
  if (!cssRes.ok) {
15093
- if (cssRes.status === 400 || cssRes.status === 404) googleFontNotFound.add(fontFamily);
15721
+ if (cssRes.status === 400 || cssRes.status === 404) googleFontNotFound.add(notFoundKey);
15094
15722
  return null;
15095
15723
  }
15096
15724
  const css = await cssRes.text();
@@ -15168,6 +15796,7 @@ function registerJsPdfFont(pdf, fontName, resolvedWeight, isItalic, base64) {
15168
15796
  }
15169
15797
  }
15170
15798
  registeredFamilies.add(fontName);
15799
+ registeredVariants.add(variantKey(fontName, resolvedWeight, isItalic));
15171
15800
  return true;
15172
15801
  } catch (err) {
15173
15802
  console.warn(`[pdf-fonts] registerJsPdfFont failed for ${fontName}:`, err);
@@ -15182,16 +15811,8 @@ async function embedFontWithGoogleFallback(pdf, fontName, weight = 400, fontBase
15182
15811
  const resolved = resolveFontWeight(weight);
15183
15812
  const fsB64 = await fetchFontshareTTF(fontName, resolved, isItalic);
15184
15813
  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
15814
  const b64 = await fetchGoogleFontTTF(fontName, resolved, isItalic);
15190
15815
  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
15816
  return false;
15196
15817
  }
15197
15818
  async function embedFontsForConfig(pdf, config, fontBaseUrl) {
@@ -15211,9 +15832,32 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
15211
15832
  };
15212
15833
  const walkElements = (elements) => {
15213
15834
  for (const el of elements) {
15835
+ const addFontVariant = (family, weight, italic) => {
15836
+ fontKeys.add(`${family}${SEP}${weight}${SEP}${italic ? "italic" : "normal"}`);
15837
+ };
15214
15838
  if (el.fontFamily) {
15215
15839
  const w = normalizeWeight(el.fontWeight);
15216
- fontKeys.add(`${el.fontFamily}${SEP}${w}`);
15840
+ addFontVariant(el.fontFamily, w, /italic|oblique/i.test(String(el.fontStyle ?? "")));
15841
+ addFontVariant(el.fontFamily, 700, false);
15842
+ addFontVariant(el.fontFamily, 400, true);
15843
+ addFontVariant(el.fontFamily, 700, true);
15844
+ if (el.formattingEnabled === true && typeof el.text === "string") {
15845
+ try {
15846
+ const parsed = parseTextMarkdown(el.text);
15847
+ for (const lineStyles of Object.values(parsed.styles || {})) {
15848
+ if (!lineStyles || typeof lineStyles !== "object") continue;
15849
+ for (const s of Object.values(lineStyles)) {
15850
+ if (!s) continue;
15851
+ const family = s.fontFamily || el.fontFamily;
15852
+ if (!family) continue;
15853
+ const ww = normalizeWeight(s.fontWeight ?? el.fontWeight);
15854
+ const it = /italic|oblique/i.test(String(s.fontStyle ?? el.fontStyle ?? ""));
15855
+ addFontVariant(family, ww, it);
15856
+ }
15857
+ }
15858
+ } catch {
15859
+ }
15860
+ }
15217
15861
  }
15218
15862
  if (el.styles && typeof el.styles === "object") {
15219
15863
  for (const lineKey of Object.keys(el.styles)) {
@@ -15221,9 +15865,10 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
15221
15865
  if (lineStyles && typeof lineStyles === "object") {
15222
15866
  for (const charKey of Object.keys(lineStyles)) {
15223
15867
  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}`);
15868
+ const styledFamily = (s == null ? void 0 : s.fontFamily) || el.fontFamily;
15869
+ if (styledFamily) {
15870
+ const w = normalizeWeight(s.fontWeight ?? el.fontWeight);
15871
+ addFontVariant(styledFamily, w, /italic|oblique/i.test(String(s.fontStyle ?? el.fontStyle ?? "")));
15227
15872
  }
15228
15873
  }
15229
15874
  }
@@ -15237,19 +15882,21 @@ async function embedFontsForConfig(pdf, config, fontBaseUrl) {
15237
15882
  if (page.children) walkElements(page.children);
15238
15883
  if (page.elements) walkElements(page.elements);
15239
15884
  }
15240
- fontKeys.add(`${FONT_FALLBACK_SYMBOLS}${SEP}400`);
15885
+ fontKeys.add(`${FONT_FALLBACK_SYMBOLS}${SEP}400${SEP}normal`);
15241
15886
  for (const w of [300, 400, 500, 600, 700]) {
15242
- fontKeys.add(`${FONT_FALLBACK_DEVANAGARI}${SEP}${w}`);
15887
+ fontKeys.add(`${FONT_FALLBACK_DEVANAGARI}${SEP}${w}${SEP}normal`);
15243
15888
  }
15244
15889
  const embedded = /* @__PURE__ */ new Set();
15245
15890
  const tasks = [];
15246
15891
  for (const key of fontKeys) {
15247
15892
  const sep = key.indexOf(SEP);
15893
+ const secondSep = key.indexOf(SEP, sep + 1);
15248
15894
  const fontName = key.slice(0, sep);
15249
- const weight = parseInt(key.slice(sep + 1), 10);
15895
+ const weight = parseInt(key.slice(sep + 1, secondSep), 10);
15896
+ const italic = key.slice(secondSep + 1) === "italic";
15250
15897
  if (!isFontAvailable(fontName)) continue;
15251
15898
  tasks.push(
15252
- embedFont(pdf, fontName, weight, fontBaseUrl).then((ok) => {
15899
+ embedFont(pdf, fontName, weight, fontBaseUrl, italic).then((ok) => {
15253
15900
  if (ok) embedded.add(key);
15254
15901
  })
15255
15902
  );
@@ -15305,7 +15952,7 @@ function splitIntoRuns(text) {
15305
15952
  return runs;
15306
15953
  }
15307
15954
  function rewriteSvgFontsForJsPDF(svgStr) {
15308
- var _a, _b;
15955
+ var _a, _b, _c;
15309
15956
  const parser = new DOMParser();
15310
15957
  const doc = parser.parseFromString(svgStr, "image/svg+xml");
15311
15958
  const allTextEls = Array.from(doc.querySelectorAll("text, tspan, textPath"));
@@ -15337,6 +15984,13 @@ function rewriteSvgFontsForJsPDF(svgStr) {
15337
15984
  stylePairs.push(`font-style: normal`);
15338
15985
  return stylePairs.join("; ");
15339
15986
  };
15987
+ const applySyntheticItalicTransform = (el) => {
15988
+ const x = Number.parseFloat(resolveInheritedValue(el, "x") || "0") || 0;
15989
+ const y = Number.parseFloat(resolveInheritedValue(el, "y") || "0") || 0;
15990
+ const existing = el.getAttribute("transform") || "";
15991
+ const synth = `translate(${x} ${y}) skewX(-12) translate(${-x} ${-y})`;
15992
+ el.setAttribute("transform", existing ? `${existing} ${synth}` : synth);
15993
+ };
15340
15994
  const getDepth = (el) => {
15341
15995
  let depth = 0;
15342
15996
  let current = el.parentElement;
@@ -15356,20 +16010,40 @@ function rewriteSvgFontsForJsPDF(svgStr) {
15356
16010
  const weightRaw = resolveInheritedValue(el, "font-weight") || "400";
15357
16011
  const styleRaw = resolveInheritedValue(el, "font-style") || "normal";
15358
16012
  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 });
16013
+ const requested = resolveFontWeight(weight);
16014
+ const requestedItalic = /italic|oblique/i.test(styleRaw);
16015
+ const best = resolveBestRegisteredVariant(clean, requested, requestedItalic);
16016
+ if (!best) continue;
16017
+ const jsPdfName = getEmbeddedJsPDFFontName(clean, best.weight, best.italic);
16018
+ sourceMeta.set(el, { clean, requested, resolved: best.weight, isItalic: requestedItalic, actualItalic: best.italic, jsPdfName, inlineStyle, weight });
15363
16019
  }
15364
16020
  const textEls = allTextEls.sort((a, b) => getDepth(b) - getDepth(a));
15365
16021
  for (const el of textEls) {
15366
16022
  if (!el.isConnected) continue;
15367
16023
  const meta = sourceMeta.get(el);
15368
16024
  if (!meta) continue;
15369
- const { clean, resolved, isItalic, jsPdfName, inlineStyle, weight } = meta;
16025
+ const { clean, requested, resolved, isItalic, actualItalic, jsPdfName, inlineStyle, weight } = meta;
15370
16026
  el.setAttribute("data-source-font-family", clean);
15371
- el.setAttribute("data-source-font-weight", String(resolved));
16027
+ el.setAttribute("data-source-font-weight", String(requested));
15372
16028
  el.setAttribute("data-source-font-style", isItalic ? "italic" : "normal");
16029
+ if (requested >= 600 && resolved < 600) {
16030
+ const fill = resolveInheritedValue(el, "fill") || readStyleToken(inlineStyle, "fill") || "#000000";
16031
+ el.setAttribute("stroke", fill);
16032
+ el.setAttribute("stroke-width", String(requested === 700 ? 0.7 : 0.5));
16033
+ el.setAttribute("stroke-linejoin", "round");
16034
+ }
16035
+ if (isItalic && !actualItalic) {
16036
+ applySyntheticItalicTransform(el);
16037
+ try {
16038
+ console.log("[Vector PDF][synthetic-italic]", {
16039
+ tag: el.tagName,
16040
+ family: clean,
16041
+ weight: requested,
16042
+ textPreview: (el.textContent || "").slice(0, 40)
16043
+ });
16044
+ } catch {
16045
+ }
16046
+ }
15373
16047
  const directText = Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent || "").join("");
15374
16048
  const hasDevanagari = containsDevanagari(directText);
15375
16049
  const hasSymbol = containsSymbol(directText);
@@ -15412,6 +16086,28 @@ function rewriteSvgFontsForJsPDF(svgStr) {
15412
16086
  el.setAttribute("style", buildStyleString(inlineStyle, jsPdfName));
15413
16087
  }
15414
16088
  }
16089
+ const SVG_NS = "http://www.w3.org/2000/svg";
16090
+ const transformedTspans = Array.from(doc.querySelectorAll("tspan[transform]"));
16091
+ for (const tspan of transformedTspans) {
16092
+ const parentText = tspan.parentElement;
16093
+ if (!parentText || parentText.tagName.toLowerCase() !== "text") continue;
16094
+ const transformVal = tspan.getAttribute("transform") || "";
16095
+ if (!/skewX/i.test(transformVal)) continue;
16096
+ const wrapper = doc.createElementNS(SVG_NS, "text");
16097
+ let parentTransform = "";
16098
+ for (const attr of Array.from(parentText.attributes)) {
16099
+ if (attr.name === "transform") {
16100
+ parentTransform = attr.value;
16101
+ continue;
16102
+ }
16103
+ wrapper.setAttribute(attr.name, attr.value);
16104
+ }
16105
+ const combined = parentTransform ? `${parentTransform} ${transformVal}` : transformVal;
16106
+ wrapper.setAttribute("transform", combined);
16107
+ tspan.removeAttribute("transform");
16108
+ wrapper.appendChild(tspan);
16109
+ (_c = parentText.parentNode) == null ? void 0 : _c.insertBefore(wrapper, parentText.nextSibling);
16110
+ }
15415
16111
  return new XMLSerializer().serializeToString(doc.documentElement);
15416
16112
  }
15417
16113
  function extractFontFamiliesFromSvgs(svgs) {
@@ -15442,14 +16138,29 @@ async function embedFontsInPdf(pdf, fontFamilies, fontBaseUrl) {
15442
16138
  if (ok) embedded.add(`${family}${w}`);
15443
16139
  })
15444
16140
  );
16141
+ tasks.push(
16142
+ embedFont(pdf, family, w, fontBaseUrl, true).then((ok) => {
16143
+ if (ok) embedded.add(`${family}${w}i`);
16144
+ })
16145
+ );
15445
16146
  }
15446
16147
  } 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
- );
16148
+ const variants = [
16149
+ { w: 400, italic: false },
16150
+ { w: 700, italic: false },
16151
+ { w: 400, italic: true },
16152
+ { w: 700, italic: true }
16153
+ ];
16154
+ for (const v of variants) {
16155
+ tasks.push(
16156
+ embedFontWithGoogleFallback(pdf, family, v.w, fontBaseUrl, v.italic).then((ok) => {
16157
+ if (ok) embedded.add(`${family}${v.w}${v.italic ? "i" : ""}`);
16158
+ else if (v.w === 400 && !v.italic) {
16159
+ console.warn(`[pdf-fonts] No TTF (local/Google/Fontshare) for "${family}" — will use Helvetica fallback`);
16160
+ }
16161
+ })
16162
+ );
16163
+ }
15453
16164
  }
15454
16165
  }
15455
16166
  await Promise.all(tasks);
@@ -15470,6 +16181,7 @@ const pdfFonts = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProp
15470
16181
  getEmbeddedJsPDFFontName,
15471
16182
  getFontPathForWeight,
15472
16183
  isFontAvailable,
16184
+ resolveBestRegisteredVariant,
15473
16185
  resolveFontWeight,
15474
16186
  rewriteSvgFontsForJsPDF
15475
16187
  }, Symbol.toStringTag, { value: "Module" }));
@@ -15509,9 +16221,14 @@ function dumpSvgTextDiagnostics(svgStr, pageIndex, tag, stage, maxItems = 30) {
15509
16221
  const sample = texts.slice(0, maxItems).map((t, idx) => {
15510
16222
  var _a, _b;
15511
16223
  const tspans = Array.from(t.querySelectorAll("tspan"));
15512
- const tspanInfo = tspans.slice(0, 4).map((s) => ({
16224
+ const tspanInfo = tspans.slice(0, 8).map((s) => ({
15513
16225
  x: s.getAttribute("x"),
15514
16226
  y: s.getAttribute("y"),
16227
+ fs: s.getAttribute("font-style"),
16228
+ fw: s.getAttribute("font-weight"),
16229
+ ff: s.getAttribute("font-family"),
16230
+ td: s.getAttribute("text-decoration"),
16231
+ style: (s.getAttribute("style") || "").slice(0, 120),
15515
16232
  text: (s.textContent || "").slice(0, 40)
15516
16233
  }));
15517
16234
  let containerWidth = null;
@@ -16546,13 +17263,15 @@ async function convertTextDecorationsToLines(svg) {
16546
17263
  const textDecOnText = (textEl.getAttribute("text-decoration") || "").toLowerCase();
16547
17264
  const textStyleDec = (getInlineStyleValue(textEl, "text-decoration") || "").toLowerCase();
16548
17265
  const textHasUnderline = textDecOnText.includes("underline") || textStyleDec.includes("underline");
17266
+ const textHasLinethrough = textDecOnText.includes("line-through") || textStyleDec.includes("line-through");
16549
17267
  for (let si = 0; si < tspans.length; si++) {
16550
17268
  const tspan = tspans[si];
16551
17269
  const liveTspan = liveTspans == null ? void 0 : liveTspans[si];
16552
17270
  const tspanDec = (tspan.getAttribute("text-decoration") || "").toLowerCase();
16553
17271
  const tspanStyleDec = (getInlineStyleValue(tspan, "text-decoration") || "").toLowerCase();
16554
17272
  const hasUnderline = tspanDec.includes("underline") || tspanStyleDec.includes("underline") || textHasUnderline;
16555
- if (!hasUnderline) continue;
17273
+ const hasLinethrough = tspanDec.includes("line-through") || tspanStyleDec.includes("line-through") || textHasLinethrough;
17274
+ if (!hasUnderline && !hasLinethrough) continue;
16556
17275
  const content = tspan.textContent || "";
16557
17276
  if (!content.trim()) continue;
16558
17277
  const xAttr = tspan.getAttribute("x") ?? textEl.getAttribute("x") ?? "0";
@@ -16607,23 +17326,26 @@ async function convertTextDecorationsToLines(svg) {
16607
17326
  lineStartX = x - (textAnchor === "middle" ? textWidth / 2 : textAnchor === "end" ? textWidth : 0);
16608
17327
  lineEndX = lineStartX + textWidth;
16609
17328
  }
16610
- const underlineY = baselineY + fontSize * 0.15;
16611
17329
  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
- }
17330
+ const makeLine = (yPos) => {
17331
+ const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
17332
+ line.setAttribute("x1", String(lineStartX));
17333
+ line.setAttribute("y1", String(yPos));
17334
+ line.setAttribute("x2", String(lineEndX));
17335
+ line.setAttribute("y2", String(yPos));
17336
+ line.setAttribute("stroke", fill.startsWith("url(") ? "#000000" : fill);
17337
+ line.setAttribute("stroke-width", String(thickness));
17338
+ line.setAttribute("stroke-linecap", "butt");
17339
+ line.setAttribute("fill", "none");
17340
+ if (fillOpacity) line.setAttribute("stroke-opacity", fillOpacity);
17341
+ if (textEl.parentElement) {
17342
+ textEl.parentElement.insertBefore(line, textEl.nextSibling);
17343
+ }
17344
+ };
17345
+ if (hasUnderline) makeLine(baselineY + fontSize * 0.15);
17346
+ if (hasLinethrough) makeLine(baselineY - fontSize * 0.3);
16625
17347
  stripTextDecoration(tspan);
16626
- if (textHasUnderline) stripTextDecoration(textEl);
17348
+ if (textHasUnderline || textHasLinethrough) stripTextDecoration(textEl);
16627
17349
  }
16628
17350
  }
16629
17351
  if (tempContainer) {
@@ -16637,7 +17359,7 @@ async function convertSvgTextDecorationsToLinesString(svgStr) {
16637
17359
  if (typeof DOMParser === "undefined" || typeof XMLSerializer === "undefined") {
16638
17360
  return svgStr;
16639
17361
  }
16640
- if (!/text-decoration/i.test(svgStr) && !/underline/i.test(svgStr)) {
17362
+ if (!/text-decoration/i.test(svgStr) && !/underline/i.test(svgStr) && !/line-through/i.test(svgStr)) {
16641
17363
  return svgStr;
16642
17364
  }
16643
17365
  try {
@@ -16929,7 +17651,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
16929
17651
  const hasGradient = !!((_b = (_a = page.backgroundGradient) == null ? void 0 : _a.stops) == null ? void 0 : _b.length);
16930
17652
  drawPageBackground(pdf, i, page.width, page.height, page.backgroundColor, page.backgroundGradient);
16931
17653
  const shouldStripBg = stripPageBackground ?? hasGradient;
16932
- const textMode = options.textMode ?? (options.outlineText ? "pixel-perfect" : "selectable");
17654
+ const textMode = options.textMode ?? (options.outlineText === false ? "selectable" : "selectable");
16933
17655
  const shouldOutlineText = textMode === "pixel-perfect" || textMode === "auto";
16934
17656
  const outlineSubMode = textMode === "auto" ? "complex-only" : "all";
16935
17657
  let pageSvg = page.svg;
@@ -16943,7 +17665,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
16943
17665
  }
16944
17666
  if (shouldOutlineText) {
16945
17667
  try {
16946
- const { convertAllTextToPath } = await import("./svgTextToPath-CpWdqc8K.js");
17668
+ const { convertAllTextToPath } = await import("./svgTextToPath-C20Obtt2.js");
16947
17669
  pageSvg = await convertAllTextToPath(pageSvg, fontBaseUrl, { mode: outlineSubMode });
16948
17670
  try {
16949
17671
  dumpSvgTextDiagnostics(pageSvg, i, PARITY_TAG, "STAGE-1b-after-text-to-path-raw");