@pixldocs/canvas-renderer 0.5.25 → 0.5.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -9632,6 +9632,10 @@ function PreviewCanvas({
9632
9632
  }
9633
9633
  );
9634
9634
  }
9635
+ const PreviewCanvas$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
9636
+ __proto__: null,
9637
+ PreviewCanvas
9638
+ }, Symbol.toStringTag, { value: "Module" }));
9635
9639
  function applyThemeToConfig(config, themeOverrides) {
9636
9640
  var _a, _b, _c;
9637
9641
  if (!themeOverrides || Object.keys(themeOverrides).length === 0) return config;
@@ -11848,74 +11852,17 @@ async function ensureFontsForResolvedConfig(config) {
11848
11852
  if (typeof document === "undefined") return;
11849
11853
  const descriptors = collectFontDescriptorsFromConfig(config);
11850
11854
  const families = new Set(descriptors.map((d) => d.family));
11851
- await withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 6e3);
11855
+ void withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 2500);
11852
11856
  if (document.fonts) {
11853
- await withTimeout(Promise.all(descriptors.map((d) => {
11857
+ descriptors.forEach((d) => {
11854
11858
  const stylePrefix = d.style === "italic" ? "italic " : "";
11855
11859
  const weightStr = String(d.weight);
11856
11860
  const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
11857
- return document.fonts.load(spec).catch(() => {
11861
+ document.fonts.load(spec).catch(() => {
11858
11862
  });
11859
- })), 6e3);
11860
- await withTimeout(document.fonts.ready, 6e3);
11863
+ });
11861
11864
  }
11862
11865
  }
11863
- const TEXT_TYPES = /* @__PURE__ */ new Set(["textbox", "text", "i-text"]);
11864
- function getFabricCanvasFromContainer(container) {
11865
- const registry2 = window.__fabricCanvasRegistry;
11866
- if (registry2 instanceof Map) {
11867
- for (const entry of registry2.values()) {
11868
- const canvas = (entry == null ? void 0 : entry.canvas) || entry;
11869
- if (!canvas || typeof canvas.toSVG !== "function") continue;
11870
- const el = canvas.lowerCanvasEl || canvas.upperCanvasEl;
11871
- if (el && container.contains(el)) return canvas;
11872
- }
11873
- }
11874
- return null;
11875
- }
11876
- function stabilizeFabricTextObjects(fabricInstance) {
11877
- var _a, _b, _c;
11878
- if (!(fabricInstance == null ? void 0 : fabricInstance.getObjects)) return;
11879
- clearFabricCharCache();
11880
- clearMeasurementCache();
11881
- const walk = (obj) => {
11882
- var _a2, _b2, _c2, _d;
11883
- if (!obj) return;
11884
- const children = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
11885
- if (children.length) children.forEach(walk);
11886
- const isTextObject = typeof obj.text === "string" && typeof obj.initDimensions === "function" && (TEXT_TYPES.has(obj.type) || obj.isEditing !== void 0);
11887
- if (!isTextObject) return;
11888
- const saved = { width: obj.width, scaleX: obj.scaleX, scaleY: obj.scaleY };
11889
- const reset = () => {
11890
- var _a3;
11891
- (_a3 = obj._clearCache) == null ? void 0 : _a3.call(obj);
11892
- obj.__charBounds = [];
11893
- obj.__lineWidths = [];
11894
- obj.__lineHeights = [];
11895
- obj.__graphemeLines = [];
11896
- obj._textLines = [];
11897
- obj.textLines = [];
11898
- obj._styleMap = null;
11899
- obj.styleMap = null;
11900
- obj.dirty = true;
11901
- };
11902
- reset();
11903
- obj.initDimensions();
11904
- if (saved.width != null) {
11905
- (_a2 = obj.set) == null ? void 0 : _a2.call(obj, { width: saved.width, scaleX: saved.scaleX, scaleY: saved.scaleY });
11906
- reset();
11907
- obj.initDimensions();
11908
- (_b2 = obj.set) == null ? void 0 : _b2.call(obj, { width: saved.width, scaleX: saved.scaleX, scaleY: saved.scaleY });
11909
- }
11910
- obj.dirty = true;
11911
- (_c2 = obj._clearCache) == null ? void 0 : _c2.call(obj);
11912
- (_d = obj.setCoords) == null ? void 0 : _d.call(obj);
11913
- };
11914
- fabricInstance.getObjects().forEach(walk);
11915
- (_a = fabricInstance.calcOffset) == null ? void 0 : _a.call(fabricInstance);
11916
- (_b = fabricInstance.renderAll) == null ? void 0 : _b.call(fabricInstance);
11917
- (_c = fabricInstance.requestRenderAll) == null ? void 0 : _c.call(fabricInstance);
11918
- }
11919
11866
  function PixldocsPreview(props) {
11920
11867
  const {
11921
11868
  pageIndex = 0,
@@ -11926,7 +11873,8 @@ function PixldocsPreview(props) {
11926
11873
  style,
11927
11874
  onDynamicFieldClick,
11928
11875
  onReady,
11929
- onError
11876
+ onError,
11877
+ skipFontReadyWait = true
11930
11878
  } = props;
11931
11879
  react.useEffect(() => {
11932
11880
  setPackageApiUrl(imageProxyUrl);
@@ -11999,6 +11947,7 @@ function PixldocsPreview(props) {
11999
11947
  const bump = () => {
12000
11948
  if (cancelled) return;
12001
11949
  clearMeasurementCache();
11950
+ clearFabricCharCache();
12002
11951
  setFontsReadyVersion((v) => v + 1);
12003
11952
  };
12004
11953
  (_b = (_a = document.fonts) == null ? void 0 : _a.ready) == null ? void 0 : _b.then(bump);
@@ -12012,13 +11961,6 @@ function PixldocsPreview(props) {
12012
11961
  () => `${pageIndex}-${fontsReadyVersion}-${stabilizationPass}`,
12013
11962
  [pageIndex, fontsReadyVersion, stabilizationPass]
12014
11963
  );
12015
- const previewWrapRef = react.useCallback((node) => {
12016
- if (!node || !config) return;
12017
- requestAnimationFrame(() => {
12018
- const fabricCanvas = getFabricCanvasFromContainer(node);
12019
- if (fabricCanvas) stabilizeFabricTextObjects(fabricCanvas);
12020
- });
12021
- }, [config, previewKey]);
12022
11964
  react.useEffect(() => {
12023
11965
  if (isResolveMode) return;
12024
11966
  if (!config) {
@@ -12049,13 +11991,14 @@ function PixldocsPreview(props) {
12049
11991
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style: { ...style, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) });
12050
11992
  }
12051
11993
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: { ...style, position: "relative" }, children: [
12052
- /* @__PURE__ */ jsxRuntime.jsx("div", { ref: previewWrapRef, style: { visibility: canvasSettled ? "visible" : "hidden" }, children: /* @__PURE__ */ jsxRuntime.jsx(
11994
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { visibility: canvasSettled ? "visible" : "hidden" }, children: /* @__PURE__ */ jsxRuntime.jsx(
12053
11995
  PreviewCanvas,
12054
11996
  {
12055
11997
  config,
12056
11998
  pageIndex,
12057
11999
  zoom,
12058
12000
  absoluteZoom,
12001
+ skipFontReadyWait,
12059
12002
  onDynamicFieldClick,
12060
12003
  onReady: handleCanvasReady
12061
12004
  },
@@ -12064,70 +12007,6 @@ function PixldocsPreview(props) {
12064
12007
  !canvasSettled && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) })
12065
12008
  ] });
12066
12009
  }
12067
- const PixldocsPreview$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
12068
- __proto__: null,
12069
- PixldocsPreview
12070
- }, Symbol.toStringTag, { value: "Module" }));
12071
- const inlinedAssetCache = /* @__PURE__ */ new Map();
12072
- function shouldInlineImageUrl(url) {
12073
- if (!url || url.startsWith("data:")) return false;
12074
- if (url.startsWith("blob:")) return true;
12075
- if (url.startsWith("/") && !url.startsWith("//")) return true;
12076
- try {
12077
- const parsed = new URL(url, window.location.href);
12078
- const current = new URL(window.location.href);
12079
- const host = parsed.hostname.toLowerCase();
12080
- return parsed.origin === current.origin || host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host.endsWith(".local") || /^(10\.|192\.168\.|169\.254\.)/.test(host);
12081
- } catch {
12082
- return false;
12083
- }
12084
- }
12085
- async function imageUrlToDataUrl(url) {
12086
- if (inlinedAssetCache.has(url)) return inlinedAssetCache.get(url) ?? null;
12087
- try {
12088
- const response = await fetch(url);
12089
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
12090
- const blob = await response.blob();
12091
- if (blob.type.includes("text/html")) throw new Error("Expected image but got text/html");
12092
- const dataUrl = await new Promise((resolve, reject) => {
12093
- const reader = new FileReader();
12094
- reader.onloadend = () => resolve(String(reader.result || ""));
12095
- reader.onerror = reject;
12096
- reader.readAsDataURL(blob);
12097
- });
12098
- inlinedAssetCache.set(url, dataUrl);
12099
- return dataUrl;
12100
- } catch (error) {
12101
- console.warn("[@pixldocs/canvas-renderer] Failed to inline image asset:", url, error);
12102
- inlinedAssetCache.set(url, null);
12103
- return null;
12104
- }
12105
- }
12106
- async function inlineNodeAssets(node) {
12107
- if (node.type === "image") {
12108
- const url = typeof node.src === "string" && node.src.trim() ? node.src.trim() : typeof node.imageUrl === "string" ? node.imageUrl.trim() : "";
12109
- if (shouldInlineImageUrl(url)) {
12110
- const dataUrl = await imageUrlToDataUrl(url);
12111
- if (dataUrl) {
12112
- node.src = dataUrl;
12113
- node.imageUrl = dataUrl;
12114
- }
12115
- }
12116
- }
12117
- if (Array.isArray(node.children)) {
12118
- await Promise.all(node.children.map(inlineNodeAssets));
12119
- }
12120
- }
12121
- async function inlineBrowserReachableImageAssets(config) {
12122
- if (typeof window === "undefined" || typeof fetch === "undefined" || typeof FileReader === "undefined") {
12123
- return config;
12124
- }
12125
- const cloned = JSON.parse(JSON.stringify(config));
12126
- await Promise.all(
12127
- (cloned.pages || []).flatMap((page) => (page.children || []).map(inlineNodeAssets))
12128
- );
12129
- return cloned;
12130
- }
12131
12010
  class PixldocsRenderer {
12132
12011
  constructor(config) {
12133
12012
  __publicField(this, "config");
@@ -12138,22 +12017,21 @@ class PixldocsRenderer {
12138
12017
  * Mounts a hidden PreviewCanvas component and captures the Fabric canvas output.
12139
12018
  */
12140
12019
  async render(templateConfig, options = {}) {
12141
- const renderConfig = await inlineBrowserReachableImageAssets(templateConfig);
12142
12020
  const pageIndex = options.pageIndex ?? 0;
12143
12021
  const format = options.format ?? "png";
12144
12022
  const quality = options.quality ?? 0.92;
12145
12023
  const pixelRatio = options.pixelRatio ?? this.config.pixelRatio ?? 2;
12146
- const canvasWidth = renderConfig.canvas.width;
12147
- const canvasHeight = renderConfig.canvas.height;
12148
- const page = renderConfig.pages[pageIndex];
12024
+ const canvasWidth = templateConfig.canvas.width;
12025
+ const canvasHeight = templateConfig.canvas.height;
12026
+ const page = templateConfig.pages[pageIndex];
12149
12027
  if (!page) {
12150
- throw new Error(`Page index ${pageIndex} not found (template has ${renderConfig.pages.length} pages)`);
12028
+ throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12151
12029
  }
12152
- await ensureFontsForResolvedConfig(renderConfig);
12030
+ await ensureFontsForResolvedConfig(templateConfig);
12153
12031
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12154
12032
  setPackageApiUrl2(this.config.imageProxyUrl);
12155
12033
  const dataUrl = await this.renderPageViaPreviewCanvas(
12156
- renderConfig,
12034
+ templateConfig,
12157
12035
  pageIndex,
12158
12036
  pixelRatio,
12159
12037
  format,
@@ -12205,17 +12083,16 @@ class PixldocsRenderer {
12205
12083
  * This is the key building block for client-side vector PDF export.
12206
12084
  */
12207
12085
  async renderPageSvg(templateConfig, pageIndex = 0) {
12208
- const renderConfig = await inlineBrowserReachableImageAssets(templateConfig);
12209
- const page = renderConfig.pages[pageIndex];
12086
+ const page = templateConfig.pages[pageIndex];
12210
12087
  if (!page) {
12211
- throw new Error(`Page index ${pageIndex} not found (template has ${renderConfig.pages.length} pages)`);
12088
+ throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12212
12089
  }
12213
- await ensureFontsForResolvedConfig(renderConfig);
12090
+ await ensureFontsForResolvedConfig(templateConfig);
12214
12091
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12215
12092
  setPackageApiUrl2(this.config.imageProxyUrl);
12216
- const canvasWidth = renderConfig.canvas.width;
12217
- const canvasHeight = renderConfig.canvas.height;
12218
- return this.captureSvgViaPreviewCanvas(renderConfig, pageIndex, canvasWidth, canvasHeight);
12093
+ const canvasWidth = templateConfig.canvas.width;
12094
+ const canvasHeight = templateConfig.canvas.height;
12095
+ return this.captureSvgViaPreviewCanvas(templateConfig, pageIndex, canvasWidth, canvasHeight);
12219
12096
  }
12220
12097
  /**
12221
12098
  * Render all pages and return SVG strings for each.
@@ -12548,7 +12425,7 @@ class PixldocsRenderer {
12548
12425
  }
12549
12426
  }
12550
12427
  async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality) {
12551
- const { PixldocsPreview: PixldocsPreview2 } = await Promise.resolve().then(() => PixldocsPreview$1);
12428
+ const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
12552
12429
  const canvasWidth = config.canvas.width;
12553
12430
  const canvasHeight = config.canvas.height;
12554
12431
  return new Promise((resolve, reject) => {
@@ -12556,14 +12433,13 @@ class PixldocsRenderer {
12556
12433
  container.style.cssText = `
12557
12434
  position: fixed; left: -99999px; top: -99999px;
12558
12435
  width: ${canvasWidth}px; height: ${canvasHeight}px;
12559
- overflow: hidden; pointer-events: none; visibility: hidden;
12436
+ overflow: hidden; pointer-events: none; opacity: 0;
12560
12437
  `;
12561
12438
  document.body.appendChild(container);
12562
12439
  const timeout = setTimeout(() => {
12563
12440
  cleanup();
12564
12441
  reject(new Error("Render timeout (30s)"));
12565
12442
  }, 3e4);
12566
- let finished = false;
12567
12443
  const cleanup = () => {
12568
12444
  clearTimeout(timeout);
12569
12445
  try {
@@ -12573,12 +12449,8 @@ class PixldocsRenderer {
12573
12449
  container.remove();
12574
12450
  };
12575
12451
  const onReady = () => {
12576
- if (finished) return;
12577
- finished = true;
12578
12452
  this.waitForCanvasScene(container, config, pageIndex).then(async () => {
12579
12453
  try {
12580
- await this.waitForStableTextMetrics(container, config, true);
12581
- await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
12582
12454
  const fabricInstance = this.getFabricCanvasFromContainer(container);
12583
12455
  const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
12584
12456
  await this.waitForCanvasImages(container, expectedImageCount);
@@ -12590,8 +12462,8 @@ class PixldocsRenderer {
12590
12462
  return;
12591
12463
  }
12592
12464
  const exportCanvas = document.createElement("canvas");
12593
- exportCanvas.width = Math.round(canvasWidth * pixelRatio);
12594
- exportCanvas.height = Math.round(canvasHeight * pixelRatio);
12465
+ exportCanvas.width = sourceCanvas.width;
12466
+ exportCanvas.height = sourceCanvas.height;
12595
12467
  const exportCtx = exportCanvas.getContext("2d");
12596
12468
  if (!exportCtx) {
12597
12469
  cleanup();
@@ -12599,10 +12471,10 @@ class PixldocsRenderer {
12599
12471
  return;
12600
12472
  }
12601
12473
  exportCtx.save();
12602
- exportCtx.scale(exportCanvas.width / canvasWidth, exportCanvas.height / canvasHeight);
12474
+ exportCtx.scale(sourceCanvas.width / canvasWidth, sourceCanvas.height / canvasHeight);
12603
12475
  this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
12604
12476
  exportCtx.restore();
12605
- exportCtx.drawImage(sourceCanvas, 0, 0, exportCanvas.width, exportCanvas.height);
12477
+ exportCtx.drawImage(sourceCanvas, 0, 0);
12606
12478
  const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
12607
12479
  const dataUrl = exportCanvas.toDataURL(mimeType, quality);
12608
12480
  cleanup();
@@ -12615,11 +12487,12 @@ class PixldocsRenderer {
12615
12487
  };
12616
12488
  const root = client.createRoot(container);
12617
12489
  root.render(
12618
- react.createElement(PixldocsPreview2, {
12490
+ react.createElement(PreviewCanvas2, {
12619
12491
  config,
12620
12492
  pageIndex,
12621
- zoom: 1,
12493
+ zoom: pixelRatio,
12622
12494
  absoluteZoom: true,
12495
+ skipFontReadyWait: true,
12623
12496
  onReady
12624
12497
  })
12625
12498
  );
@@ -12636,19 +12509,18 @@ class PixldocsRenderer {
12636
12509
  // document space (e.g. 612x792) instead of inflated pixel space.
12637
12510
  captureSvgViaPreviewCanvas(config, pageIndex, canvasWidth, canvasHeight) {
12638
12511
  return new Promise(async (resolve, reject) => {
12639
- const { PixldocsPreview: PixldocsPreview2 } = await Promise.resolve().then(() => PixldocsPreview$1);
12512
+ const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
12640
12513
  const container = document.createElement("div");
12641
12514
  container.style.cssText = `
12642
12515
  position: fixed; left: -99999px; top: -99999px;
12643
12516
  width: ${canvasWidth}px; height: ${canvasHeight}px;
12644
- overflow: hidden; pointer-events: none; visibility: hidden;
12517
+ overflow: hidden; pointer-events: none; opacity: 0;
12645
12518
  `;
12646
12519
  document.body.appendChild(container);
12647
12520
  const timeout = setTimeout(() => {
12648
12521
  cleanup();
12649
12522
  reject(new Error("SVG render timeout (30s)"));
12650
12523
  }, 3e4);
12651
- let finished = false;
12652
12524
  const cleanup = () => {
12653
12525
  clearTimeout(timeout);
12654
12526
  try {
@@ -12658,13 +12530,9 @@ class PixldocsRenderer {
12658
12530
  container.remove();
12659
12531
  };
12660
12532
  const onReady = () => {
12661
- if (finished) return;
12662
- finished = true;
12663
12533
  this.waitForCanvasScene(container, config, pageIndex).then(async () => {
12664
12534
  var _a, _b;
12665
12535
  try {
12666
- await this.waitForStableTextMetrics(container, config, true);
12667
- await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
12668
12536
  const fabricInstance = this.getFabricCanvasFromContainer(container);
12669
12537
  if (!fabricInstance) {
12670
12538
  cleanup();
@@ -12717,12 +12585,13 @@ class PixldocsRenderer {
12717
12585
  };
12718
12586
  const root = client.createRoot(container);
12719
12587
  root.render(
12720
- react.createElement(PixldocsPreview2, {
12588
+ react.createElement(PreviewCanvas2, {
12721
12589
  config,
12722
12590
  pageIndex,
12723
12591
  zoom: 1,
12724
12592
  // 1:1 — no UI scaling for SVG capture
12725
12593
  absoluteZoom: true,
12594
+ skipFontReadyWait: true,
12726
12595
  onReady
12727
12596
  })
12728
12597
  );
@@ -12783,12 +12652,21 @@ class PixldocsRenderer {
12783
12652
  * using the global __fabricCanvasRegistry (set by PageCanvas).
12784
12653
  */
12785
12654
  getFabricCanvasFromContainer(container) {
12786
- return getFabricCanvasFromContainer(container);
12655
+ const registry2 = window.__fabricCanvasRegistry;
12656
+ if (registry2 instanceof Map) {
12657
+ for (const entry of registry2.values()) {
12658
+ const canvas = (entry == null ? void 0 : entry.canvas) || entry;
12659
+ if (!canvas || typeof canvas.toSVG !== "function") continue;
12660
+ const el = canvas.lowerCanvasEl || canvas.upperCanvasEl;
12661
+ if (el && container.contains(el)) return canvas;
12662
+ }
12663
+ }
12664
+ return null;
12787
12665
  }
12788
- async waitForStableTextMetrics(container, config, strictFontWait = false) {
12666
+ async waitForStableTextMetrics(container, config) {
12789
12667
  if (typeof document !== "undefined") {
12790
- await ensureFontsForResolvedConfig(config);
12791
- await this.waitForRelevantFonts(config, strictFontWait ? 6e3 : 1800);
12668
+ void ensureFontsForResolvedConfig(config);
12669
+ await this.waitForRelevantFonts(config);
12792
12670
  }
12793
12671
  const fabricInstance = this.getFabricCanvasFromContainer(container);
12794
12672
  if (!(fabricInstance == null ? void 0 : fabricInstance.getObjects)) return;
@@ -12797,7 +12675,59 @@ class PixldocsRenderer {
12797
12675
  clearFabricCharCache();
12798
12676
  clearMeasurementCache();
12799
12677
  };
12800
- const reflowTextboxes = () => stabilizeFabricTextObjects(fabricInstance);
12678
+ const reflowTextboxes = () => {
12679
+ var _a, _b, _c;
12680
+ const walk = (obj) => {
12681
+ var _a2, _b2, _c2, _d;
12682
+ if (!obj) return;
12683
+ const children = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
12684
+ if (children.length) children.forEach(walk);
12685
+ const isTextObject = typeof obj.text === "string" && typeof obj.initDimensions === "function" && (obj.type === "textbox" || obj.type === "text" || obj.type === "i-text" || obj.isEditing !== void 0);
12686
+ if (isTextObject) {
12687
+ const saved = {
12688
+ width: obj.width,
12689
+ scaleX: obj.scaleX,
12690
+ scaleY: obj.scaleY
12691
+ };
12692
+ const resetTextboxLayoutInternals = () => {
12693
+ var _a3;
12694
+ (_a3 = obj._clearCache) == null ? void 0 : _a3.call(obj);
12695
+ obj.__charBounds = [];
12696
+ obj.__lineWidths = [];
12697
+ obj.__lineHeights = [];
12698
+ obj.__graphemeLines = [];
12699
+ obj._textLines = [];
12700
+ obj.textLines = [];
12701
+ obj._styleMap = null;
12702
+ obj.styleMap = null;
12703
+ obj.dirty = true;
12704
+ };
12705
+ resetTextboxLayoutInternals();
12706
+ obj.initDimensions();
12707
+ if (saved.width != null) {
12708
+ (_a2 = obj.set) == null ? void 0 : _a2.call(obj, {
12709
+ width: saved.width,
12710
+ scaleX: saved.scaleX,
12711
+ scaleY: saved.scaleY
12712
+ });
12713
+ resetTextboxLayoutInternals();
12714
+ obj.initDimensions();
12715
+ }
12716
+ (_b2 = obj.set) == null ? void 0 : _b2.call(obj, {
12717
+ width: saved.width,
12718
+ scaleX: saved.scaleX,
12719
+ scaleY: saved.scaleY,
12720
+ dirty: true
12721
+ });
12722
+ (_c2 = obj._clearCache) == null ? void 0 : _c2.call(obj);
12723
+ (_d = obj.setCoords) == null ? void 0 : _d.call(obj);
12724
+ }
12725
+ };
12726
+ fabricInstance.getObjects().forEach(walk);
12727
+ (_a = fabricInstance.calcOffset) == null ? void 0 : _a.call(fabricInstance);
12728
+ (_b = fabricInstance.renderAll) == null ? void 0 : _b.call(fabricInstance);
12729
+ (_c = fabricInstance.requestRenderAll) == null ? void 0 : _c.call(fabricInstance);
12730
+ };
12801
12731
  clearTextMetricCaches();
12802
12732
  await waitForPaint();
12803
12733
  reflowTextboxes();
@@ -13531,6 +13461,9 @@ function rewriteSvgFontsForJsPDF(svgStr) {
13531
13461
  const resolved = resolveFontWeight(weight);
13532
13462
  const isItalic = /italic|oblique/i.test(styleRaw);
13533
13463
  const jsPdfName = getEmbeddedJsPDFFontName(clean, resolved, isItalic);
13464
+ el.setAttribute("data-source-font-family", clean);
13465
+ el.setAttribute("data-source-font-weight", String(resolved));
13466
+ el.setAttribute("data-source-font-style", isItalic ? "italic" : "normal");
13534
13467
  const directText = Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent || "").join("");
13535
13468
  const hasDevanagari = containsDevanagari(directText);
13536
13469
  if (hasDevanagari && directText.length > 0) {
@@ -14512,24 +14445,89 @@ async function svg2pdfWithDomMount(svg, pdf, opts) {
14512
14445
  if (wrap.parentNode) document.body.removeChild(wrap);
14513
14446
  }
14514
14447
  }
14515
- function convertTextDecorationsToLines(svg) {
14448
+ async function convertTextDecorationsToLines(svg) {
14516
14449
  const doc = svg.ownerDocument;
14517
14450
  if (!doc) return;
14518
- let measureCanvas = null;
14451
+ const resolveInheritedSvgValue = (el, attr, styleProp = attr) => {
14452
+ var _a, _b;
14453
+ let current = el;
14454
+ while (current) {
14455
+ const attrValue = (_a = current.getAttribute(attr)) == null ? void 0 : _a.trim();
14456
+ if (attrValue) return attrValue;
14457
+ const styleValue = (_b = getInlineStyleValue(current, styleProp)) == null ? void 0 : _b.trim();
14458
+ if (styleValue) return styleValue;
14459
+ current = current.parentElement;
14460
+ }
14461
+ return null;
14462
+ };
14463
+ if (typeof document !== "undefined" && document.fonts) {
14464
+ const fontFamilies = /* @__PURE__ */ new Set();
14465
+ for (const textEl of svg.querySelectorAll("text")) {
14466
+ const ff = textEl.getAttribute("data-source-font-family") || textEl.getAttribute("font-family");
14467
+ if (ff) fontFamilies.add(ff.replace(/'/g, ""));
14468
+ for (const tspan of textEl.querySelectorAll("tspan")) {
14469
+ const tff = tspan.getAttribute("data-source-font-family") || tspan.getAttribute("font-family");
14470
+ if (tff) fontFamilies.add(tff.replace(/'/g, ""));
14471
+ }
14472
+ }
14473
+ await Promise.all(Array.from(fontFamilies).flatMap((ff) => [
14474
+ document.fonts.load(`16px "${ff}"`).then(() => {
14475
+ }).catch(() => {
14476
+ }),
14477
+ document.fonts.load(`bold 16px "${ff}"`).then(() => {
14478
+ }).catch(() => {
14479
+ })
14480
+ ]));
14481
+ await document.fonts.ready;
14482
+ }
14483
+ let tempContainer = null;
14484
+ let liveSvg = null;
14485
+ try {
14486
+ if (typeof document !== "undefined") {
14487
+ tempContainer = document.createElement("div");
14488
+ tempContainer.style.cssText = "position:fixed;left:-9999px;top:-9999px;visibility:hidden;pointer-events:none;";
14489
+ document.body.appendChild(tempContainer);
14490
+ const clone = svg.cloneNode(true);
14491
+ for (const textNode of clone.querySelectorAll("text, tspan, textPath")) {
14492
+ const sourceFamily = textNode.getAttribute("data-source-font-family");
14493
+ const sourceWeight = textNode.getAttribute("data-source-font-weight");
14494
+ const sourceStyle = textNode.getAttribute("data-source-font-style");
14495
+ if (sourceFamily) textNode.setAttribute("font-family", sourceFamily);
14496
+ if (sourceWeight) textNode.setAttribute("font-weight", sourceWeight);
14497
+ if (sourceStyle) textNode.setAttribute("font-style", sourceStyle);
14498
+ const inlineStyle = textNode.getAttribute("style") || "";
14499
+ const stylePairs = inlineStyle.split(";").map((part) => part.trim()).filter(Boolean).filter((part) => !/^font-family\s*:/i.test(part) && !/^font-weight\s*:/i.test(part) && !/^font-style\s*:/i.test(part));
14500
+ if (sourceFamily) stylePairs.push(`font-family: ${sourceFamily}`);
14501
+ if (sourceWeight) stylePairs.push(`font-weight: ${sourceWeight}`);
14502
+ if (sourceStyle) stylePairs.push(`font-style: ${sourceStyle}`);
14503
+ if (stylePairs.length > 0) textNode.setAttribute("style", stylePairs.join("; "));
14504
+ }
14505
+ tempContainer.appendChild(clone);
14506
+ liveSvg = clone;
14507
+ }
14508
+ } catch {
14509
+ }
14519
14510
  let ctx = null;
14520
14511
  try {
14521
- measureCanvas = doc.createElement("canvas");
14512
+ const realDoc = typeof document !== "undefined" ? document : doc;
14513
+ const measureCanvas = realDoc.createElement("canvas");
14522
14514
  ctx = measureCanvas.getContext("2d");
14523
14515
  } catch {
14524
14516
  }
14525
14517
  const textEls = Array.from(svg.querySelectorAll("text"));
14526
- for (const textEl of textEls) {
14518
+ const liveTextEls = liveSvg ? Array.from(liveSvg.querySelectorAll("text")) : null;
14519
+ for (let ti = 0; ti < textEls.length; ti++) {
14520
+ const textEl = textEls[ti];
14521
+ const liveTextEl = liveTextEls == null ? void 0 : liveTextEls[ti];
14527
14522
  const tspans = Array.from(textEl.querySelectorAll("tspan"));
14528
14523
  if (tspans.length === 0) continue;
14524
+ const liveTspans = liveTextEl ? Array.from(liveTextEl.querySelectorAll("tspan")) : null;
14529
14525
  const textDecOnText = (textEl.getAttribute("text-decoration") || "").toLowerCase();
14530
14526
  const textStyleDec = (getInlineStyleValue(textEl, "text-decoration") || "").toLowerCase();
14531
14527
  const textHasUnderline = textDecOnText.includes("underline") || textStyleDec.includes("underline");
14532
- for (const tspan of tspans) {
14528
+ for (let si = 0; si < tspans.length; si++) {
14529
+ const tspan = tspans[si];
14530
+ const liveTspan = liveTspans == null ? void 0 : liveTspans[si];
14533
14531
  const tspanDec = (tspan.getAttribute("text-decoration") || "").toLowerCase();
14534
14532
  const tspanStyleDec = (getInlineStyleValue(tspan, "text-decoration") || "").toLowerCase();
14535
14533
  const hasUnderline = tspanDec.includes("underline") || tspanStyleDec.includes("underline") || textHasUnderline;
@@ -14543,41 +14541,73 @@ function convertTextDecorationsToLines(svg) {
14543
14541
  const fontSize = parseFloat(
14544
14542
  tspan.getAttribute("font-size") || textEl.getAttribute("font-size") || "16"
14545
14543
  );
14546
- const fontFamily = tspan.getAttribute("font-family") || textEl.getAttribute("font-family") || "sans-serif";
14547
- const fontWeight = tspan.getAttribute("font-weight") || textEl.getAttribute("font-weight") || "normal";
14544
+ const fontFamily = tspan.getAttribute("data-source-font-family") || textEl.getAttribute("data-source-font-family") || resolveInheritedSvgValue(tspan, "font-family") || "sans-serif";
14545
+ const fontWeight = tspan.getAttribute("data-source-font-weight") || textEl.getAttribute("data-source-font-weight") || resolveInheritedSvgValue(tspan, "font-weight") || "normal";
14546
+ const fontStyle = tspan.getAttribute("data-source-font-style") || textEl.getAttribute("data-source-font-style") || resolveInheritedSvgValue(tspan, "font-style") || "normal";
14548
14547
  const fill = tspan.getAttribute("fill") || textEl.getAttribute("fill") || "#000000";
14549
- let textWidth = 0;
14550
- if (typeof tspan.getComputedTextLength === "function") {
14548
+ let textWidth = content.length * fontSize * 0.6;
14549
+ if (ctx) {
14550
+ ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily.replace(/'/g, "")}`;
14551
+ textWidth = ctx.measureText(content).width;
14552
+ }
14553
+ const textAnchorRaw = (resolveInheritedSvgValue(tspan, "text-anchor") || "start").toLowerCase();
14554
+ const textAnchor = textAnchorRaw === "middle" || textAnchorRaw === "end" ? textAnchorRaw : "start";
14555
+ let lineStartX = x - (textAnchor === "middle" ? textWidth / 2 : textAnchor === "end" ? textWidth : 0);
14556
+ let lineEndX = lineStartX + textWidth;
14557
+ let baselineY = y;
14558
+ if (liveTspan) {
14551
14559
  try {
14552
- textWidth = tspan.getComputedTextLength();
14560
+ const liveCharCount = liveTspan.getNumberOfChars();
14561
+ const svgWidth = liveTspan.getComputedTextLength();
14562
+ if (Number.isFinite(svgWidth) && svgWidth > 0) {
14563
+ textWidth = Math.max(textWidth, svgWidth);
14564
+ }
14565
+ if (liveCharCount > 0) {
14566
+ const start = liveTspan.getStartPositionOfChar(0);
14567
+ const end = liveTspan.getEndPositionOfChar(liveCharCount - 1);
14568
+ if (Number.isFinite(start.x)) lineStartX = start.x;
14569
+ if (Number.isFinite(start.y)) baselineY = start.y;
14570
+ if (Number.isFinite(end.x)) lineEndX = Math.max(end.x, lineStartX + textWidth);
14571
+ if (Number.isFinite(end.y) && !Number.isFinite(baselineY)) baselineY = end.y;
14572
+ } else {
14573
+ lineEndX = lineStartX + textWidth;
14574
+ }
14575
+ const bbox = liveTspan.getBBox();
14576
+ if (Number.isFinite(bbox.x) && Number.isFinite(bbox.width)) {
14577
+ lineStartX = Math.min(lineStartX, bbox.x);
14578
+ lineEndX = Math.max(lineEndX, bbox.x + bbox.width);
14579
+ }
14553
14580
  } catch {
14554
14581
  }
14555
14582
  }
14556
- if (!Number.isFinite(textWidth) || textWidth <= 0) {
14557
- if (ctx) {
14558
- ctx.font = `${fontWeight} ${fontSize}px ${fontFamily.replace(/'/g, "")}`;
14559
- textWidth = ctx.measureText(content).width;
14560
- } else {
14561
- textWidth = content.length * fontSize * 0.6;
14562
- }
14583
+ if (!(lineEndX > lineStartX)) {
14584
+ lineStartX = x - (textAnchor === "middle" ? textWidth / 2 : textAnchor === "end" ? textWidth : 0);
14585
+ lineEndX = lineStartX + textWidth;
14563
14586
  }
14564
- const underlineY = y + fontSize * 0.15;
14587
+ const underlineY = baselineY + fontSize * 0.15;
14565
14588
  const thickness = Math.max(0.5, fontSize * 0.066667);
14566
14589
  const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
14567
- line.setAttribute("x1", String(x));
14590
+ line.setAttribute("x1", String(lineStartX));
14568
14591
  line.setAttribute("y1", String(underlineY));
14569
- line.setAttribute("x2", String(x + textWidth));
14592
+ line.setAttribute("x2", String(lineEndX));
14570
14593
  line.setAttribute("y2", String(underlineY));
14571
14594
  line.setAttribute("stroke", fill.startsWith("url(") ? "#000000" : fill);
14572
14595
  line.setAttribute("stroke-width", String(thickness));
14596
+ line.setAttribute("stroke-linecap", "butt");
14573
14597
  line.setAttribute("fill", "none");
14574
14598
  if (textEl.parentElement) {
14575
14599
  textEl.parentElement.insertBefore(line, textEl.nextSibling);
14576
14600
  }
14577
14601
  }
14578
14602
  }
14603
+ if (tempContainer) {
14604
+ try {
14605
+ document.body.removeChild(tempContainer);
14606
+ } catch {
14607
+ }
14608
+ }
14579
14609
  }
14580
- function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, options) {
14610
+ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, options) {
14581
14611
  try {
14582
14612
  const parser = new DOMParser();
14583
14613
  const processedSvg = inlineNestedSvgImageDataUris(rawSvg, parser);
@@ -14606,7 +14636,6 @@ function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, opti
14606
14636
  stripSuspiciousFullPageOverlayNodes(svgToDraw);
14607
14637
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
14608
14638
  sanitizeSvgTreeForPdf(svgToDraw);
14609
- convertTextDecorationsToLines(svgToDraw);
14610
14639
  return svgToDraw;
14611
14640
  } catch {
14612
14641
  return null;
@@ -14709,7 +14738,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
14709
14738
  const hasGradient = !!((_b = (_a = page.backgroundGradient) == null ? void 0 : _a.stops) == null ? void 0 : _b.length);
14710
14739
  drawPageBackground(pdf, i, page.width, page.height, page.backgroundColor, page.backgroundGradient);
14711
14740
  const shouldStripBg = stripPageBackground ?? hasGradient;
14712
- let processedSvg = prepareLiveCanvasSvgForPdf(page.svg, page.width, page.height, `page-${i + 1}`, {
14741
+ let processedSvg = await prepareLiveCanvasSvgForPdf(page.svg, page.width, page.height, `page-${i + 1}`, {
14713
14742
  stripPageBackground: shouldStripBg
14714
14743
  });
14715
14744
  if (processedSvg) {
@@ -14718,6 +14747,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
14718
14747
  const reParser = new DOMParser();
14719
14748
  const reDoc = reParser.parseFromString(rewrittenSvg, "image/svg+xml");
14720
14749
  processedSvg = reDoc.documentElement;
14750
+ await convertTextDecorationsToLines(processedSvg);
14721
14751
  }
14722
14752
  if (processedSvg) {
14723
14753
  pdf.setFillColor(0, 0, 0);