@pixldocs/canvas-renderer 0.5.25 → 0.5.26

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
@@ -9613,6 +9613,10 @@ function PreviewCanvas({
9613
9613
  }
9614
9614
  );
9615
9615
  }
9616
+ const PreviewCanvas$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
9617
+ __proto__: null,
9618
+ PreviewCanvas
9619
+ }, Symbol.toStringTag, { value: "Module" }));
9616
9620
  function applyThemeToConfig(config, themeOverrides) {
9617
9621
  var _a, _b, _c;
9618
9622
  if (!themeOverrides || Object.keys(themeOverrides).length === 0) return config;
@@ -11829,74 +11833,17 @@ async function ensureFontsForResolvedConfig(config) {
11829
11833
  if (typeof document === "undefined") return;
11830
11834
  const descriptors = collectFontDescriptorsFromConfig(config);
11831
11835
  const families = new Set(descriptors.map((d) => d.family));
11832
- await withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 6e3);
11836
+ void withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 2500);
11833
11837
  if (document.fonts) {
11834
- await withTimeout(Promise.all(descriptors.map((d) => {
11838
+ descriptors.forEach((d) => {
11835
11839
  const stylePrefix = d.style === "italic" ? "italic " : "";
11836
11840
  const weightStr = String(d.weight);
11837
11841
  const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
11838
- return document.fonts.load(spec).catch(() => {
11842
+ document.fonts.load(spec).catch(() => {
11839
11843
  });
11840
- })), 6e3);
11841
- await withTimeout(document.fonts.ready, 6e3);
11844
+ });
11842
11845
  }
11843
11846
  }
11844
- const TEXT_TYPES = /* @__PURE__ */ new Set(["textbox", "text", "i-text"]);
11845
- function getFabricCanvasFromContainer(container) {
11846
- const registry2 = window.__fabricCanvasRegistry;
11847
- if (registry2 instanceof Map) {
11848
- for (const entry of registry2.values()) {
11849
- const canvas = (entry == null ? void 0 : entry.canvas) || entry;
11850
- if (!canvas || typeof canvas.toSVG !== "function") continue;
11851
- const el = canvas.lowerCanvasEl || canvas.upperCanvasEl;
11852
- if (el && container.contains(el)) return canvas;
11853
- }
11854
- }
11855
- return null;
11856
- }
11857
- function stabilizeFabricTextObjects(fabricInstance) {
11858
- var _a, _b, _c;
11859
- if (!(fabricInstance == null ? void 0 : fabricInstance.getObjects)) return;
11860
- clearFabricCharCache();
11861
- clearMeasurementCache();
11862
- const walk = (obj) => {
11863
- var _a2, _b2, _c2, _d;
11864
- if (!obj) return;
11865
- const children = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
11866
- if (children.length) children.forEach(walk);
11867
- const isTextObject = typeof obj.text === "string" && typeof obj.initDimensions === "function" && (TEXT_TYPES.has(obj.type) || obj.isEditing !== void 0);
11868
- if (!isTextObject) return;
11869
- const saved = { width: obj.width, scaleX: obj.scaleX, scaleY: obj.scaleY };
11870
- const reset = () => {
11871
- var _a3;
11872
- (_a3 = obj._clearCache) == null ? void 0 : _a3.call(obj);
11873
- obj.__charBounds = [];
11874
- obj.__lineWidths = [];
11875
- obj.__lineHeights = [];
11876
- obj.__graphemeLines = [];
11877
- obj._textLines = [];
11878
- obj.textLines = [];
11879
- obj._styleMap = null;
11880
- obj.styleMap = null;
11881
- obj.dirty = true;
11882
- };
11883
- reset();
11884
- obj.initDimensions();
11885
- if (saved.width != null) {
11886
- (_a2 = obj.set) == null ? void 0 : _a2.call(obj, { width: saved.width, scaleX: saved.scaleX, scaleY: saved.scaleY });
11887
- reset();
11888
- obj.initDimensions();
11889
- (_b2 = obj.set) == null ? void 0 : _b2.call(obj, { width: saved.width, scaleX: saved.scaleX, scaleY: saved.scaleY });
11890
- }
11891
- obj.dirty = true;
11892
- (_c2 = obj._clearCache) == null ? void 0 : _c2.call(obj);
11893
- (_d = obj.setCoords) == null ? void 0 : _d.call(obj);
11894
- };
11895
- fabricInstance.getObjects().forEach(walk);
11896
- (_a = fabricInstance.calcOffset) == null ? void 0 : _a.call(fabricInstance);
11897
- (_b = fabricInstance.renderAll) == null ? void 0 : _b.call(fabricInstance);
11898
- (_c = fabricInstance.requestRenderAll) == null ? void 0 : _c.call(fabricInstance);
11899
- }
11900
11847
  function PixldocsPreview(props) {
11901
11848
  const {
11902
11849
  pageIndex = 0,
@@ -11907,7 +11854,8 @@ function PixldocsPreview(props) {
11907
11854
  style,
11908
11855
  onDynamicFieldClick,
11909
11856
  onReady,
11910
- onError
11857
+ onError,
11858
+ skipFontReadyWait = true
11911
11859
  } = props;
11912
11860
  useEffect(() => {
11913
11861
  setPackageApiUrl(imageProxyUrl);
@@ -11993,13 +11941,6 @@ function PixldocsPreview(props) {
11993
11941
  () => `${pageIndex}-${fontsReadyVersion}-${stabilizationPass}`,
11994
11942
  [pageIndex, fontsReadyVersion, stabilizationPass]
11995
11943
  );
11996
- const previewWrapRef = useCallback((node) => {
11997
- if (!node || !config) return;
11998
- requestAnimationFrame(() => {
11999
- const fabricCanvas = getFabricCanvasFromContainer(node);
12000
- if (fabricCanvas) stabilizeFabricTextObjects(fabricCanvas);
12001
- });
12002
- }, [config, previewKey]);
12003
11944
  useEffect(() => {
12004
11945
  if (isResolveMode) return;
12005
11946
  if (!config) {
@@ -12030,13 +11971,14 @@ function PixldocsPreview(props) {
12030
11971
  return /* @__PURE__ */ jsx("div", { className, style: { ...style, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) });
12031
11972
  }
12032
11973
  return /* @__PURE__ */ jsxs("div", { className, style: { ...style, position: "relative" }, children: [
12033
- /* @__PURE__ */ jsx("div", { ref: previewWrapRef, style: { visibility: canvasSettled ? "visible" : "hidden" }, children: /* @__PURE__ */ jsx(
11974
+ /* @__PURE__ */ jsx("div", { style: { visibility: canvasSettled ? "visible" : "hidden" }, children: /* @__PURE__ */ jsx(
12034
11975
  PreviewCanvas,
12035
11976
  {
12036
11977
  config,
12037
11978
  pageIndex,
12038
11979
  zoom,
12039
11980
  absoluteZoom,
11981
+ skipFontReadyWait,
12040
11982
  onDynamicFieldClick,
12041
11983
  onReady: handleCanvasReady
12042
11984
  },
@@ -12045,70 +11987,6 @@ function PixldocsPreview(props) {
12045
11987
  !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..." }) })
12046
11988
  ] });
12047
11989
  }
12048
- const PixldocsPreview$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
12049
- __proto__: null,
12050
- PixldocsPreview
12051
- }, Symbol.toStringTag, { value: "Module" }));
12052
- const inlinedAssetCache = /* @__PURE__ */ new Map();
12053
- function shouldInlineImageUrl(url) {
12054
- if (!url || url.startsWith("data:")) return false;
12055
- if (url.startsWith("blob:")) return true;
12056
- if (url.startsWith("/") && !url.startsWith("//")) return true;
12057
- try {
12058
- const parsed = new URL(url, window.location.href);
12059
- const current = new URL(window.location.href);
12060
- const host = parsed.hostname.toLowerCase();
12061
- 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);
12062
- } catch {
12063
- return false;
12064
- }
12065
- }
12066
- async function imageUrlToDataUrl(url) {
12067
- if (inlinedAssetCache.has(url)) return inlinedAssetCache.get(url) ?? null;
12068
- try {
12069
- const response = await fetch(url);
12070
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
12071
- const blob = await response.blob();
12072
- if (blob.type.includes("text/html")) throw new Error("Expected image but got text/html");
12073
- const dataUrl = await new Promise((resolve, reject) => {
12074
- const reader = new FileReader();
12075
- reader.onloadend = () => resolve(String(reader.result || ""));
12076
- reader.onerror = reject;
12077
- reader.readAsDataURL(blob);
12078
- });
12079
- inlinedAssetCache.set(url, dataUrl);
12080
- return dataUrl;
12081
- } catch (error) {
12082
- console.warn("[@pixldocs/canvas-renderer] Failed to inline image asset:", url, error);
12083
- inlinedAssetCache.set(url, null);
12084
- return null;
12085
- }
12086
- }
12087
- async function inlineNodeAssets(node) {
12088
- if (node.type === "image") {
12089
- const url = typeof node.src === "string" && node.src.trim() ? node.src.trim() : typeof node.imageUrl === "string" ? node.imageUrl.trim() : "";
12090
- if (shouldInlineImageUrl(url)) {
12091
- const dataUrl = await imageUrlToDataUrl(url);
12092
- if (dataUrl) {
12093
- node.src = dataUrl;
12094
- node.imageUrl = dataUrl;
12095
- }
12096
- }
12097
- }
12098
- if (Array.isArray(node.children)) {
12099
- await Promise.all(node.children.map(inlineNodeAssets));
12100
- }
12101
- }
12102
- async function inlineBrowserReachableImageAssets(config) {
12103
- if (typeof window === "undefined" || typeof fetch === "undefined" || typeof FileReader === "undefined") {
12104
- return config;
12105
- }
12106
- const cloned = JSON.parse(JSON.stringify(config));
12107
- await Promise.all(
12108
- (cloned.pages || []).flatMap((page) => (page.children || []).map(inlineNodeAssets))
12109
- );
12110
- return cloned;
12111
- }
12112
11990
  class PixldocsRenderer {
12113
11991
  constructor(config) {
12114
11992
  __publicField(this, "config");
@@ -12119,22 +11997,21 @@ class PixldocsRenderer {
12119
11997
  * Mounts a hidden PreviewCanvas component and captures the Fabric canvas output.
12120
11998
  */
12121
11999
  async render(templateConfig, options = {}) {
12122
- const renderConfig = await inlineBrowserReachableImageAssets(templateConfig);
12123
12000
  const pageIndex = options.pageIndex ?? 0;
12124
12001
  const format = options.format ?? "png";
12125
12002
  const quality = options.quality ?? 0.92;
12126
12003
  const pixelRatio = options.pixelRatio ?? this.config.pixelRatio ?? 2;
12127
- const canvasWidth = renderConfig.canvas.width;
12128
- const canvasHeight = renderConfig.canvas.height;
12129
- const page = renderConfig.pages[pageIndex];
12004
+ const canvasWidth = templateConfig.canvas.width;
12005
+ const canvasHeight = templateConfig.canvas.height;
12006
+ const page = templateConfig.pages[pageIndex];
12130
12007
  if (!page) {
12131
- throw new Error(`Page index ${pageIndex} not found (template has ${renderConfig.pages.length} pages)`);
12008
+ throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12132
12009
  }
12133
- await ensureFontsForResolvedConfig(renderConfig);
12010
+ await ensureFontsForResolvedConfig(templateConfig);
12134
12011
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12135
12012
  setPackageApiUrl2(this.config.imageProxyUrl);
12136
12013
  const dataUrl = await this.renderPageViaPreviewCanvas(
12137
- renderConfig,
12014
+ templateConfig,
12138
12015
  pageIndex,
12139
12016
  pixelRatio,
12140
12017
  format,
@@ -12186,17 +12063,16 @@ class PixldocsRenderer {
12186
12063
  * This is the key building block for client-side vector PDF export.
12187
12064
  */
12188
12065
  async renderPageSvg(templateConfig, pageIndex = 0) {
12189
- const renderConfig = await inlineBrowserReachableImageAssets(templateConfig);
12190
- const page = renderConfig.pages[pageIndex];
12066
+ const page = templateConfig.pages[pageIndex];
12191
12067
  if (!page) {
12192
- throw new Error(`Page index ${pageIndex} not found (template has ${renderConfig.pages.length} pages)`);
12068
+ throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12193
12069
  }
12194
- await ensureFontsForResolvedConfig(renderConfig);
12070
+ await ensureFontsForResolvedConfig(templateConfig);
12195
12071
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12196
12072
  setPackageApiUrl2(this.config.imageProxyUrl);
12197
- const canvasWidth = renderConfig.canvas.width;
12198
- const canvasHeight = renderConfig.canvas.height;
12199
- return this.captureSvgViaPreviewCanvas(renderConfig, pageIndex, canvasWidth, canvasHeight);
12073
+ const canvasWidth = templateConfig.canvas.width;
12074
+ const canvasHeight = templateConfig.canvas.height;
12075
+ return this.captureSvgViaPreviewCanvas(templateConfig, pageIndex, canvasWidth, canvasHeight);
12200
12076
  }
12201
12077
  /**
12202
12078
  * Render all pages and return SVG strings for each.
@@ -12529,7 +12405,7 @@ class PixldocsRenderer {
12529
12405
  }
12530
12406
  }
12531
12407
  async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality) {
12532
- const { PixldocsPreview: PixldocsPreview2 } = await Promise.resolve().then(() => PixldocsPreview$1);
12408
+ const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
12533
12409
  const canvasWidth = config.canvas.width;
12534
12410
  const canvasHeight = config.canvas.height;
12535
12411
  return new Promise((resolve, reject) => {
@@ -12537,14 +12413,13 @@ class PixldocsRenderer {
12537
12413
  container.style.cssText = `
12538
12414
  position: fixed; left: -99999px; top: -99999px;
12539
12415
  width: ${canvasWidth}px; height: ${canvasHeight}px;
12540
- overflow: hidden; pointer-events: none; visibility: hidden;
12416
+ overflow: hidden; pointer-events: none; opacity: 0;
12541
12417
  `;
12542
12418
  document.body.appendChild(container);
12543
12419
  const timeout = setTimeout(() => {
12544
12420
  cleanup();
12545
12421
  reject(new Error("Render timeout (30s)"));
12546
12422
  }, 3e4);
12547
- let finished = false;
12548
12423
  const cleanup = () => {
12549
12424
  clearTimeout(timeout);
12550
12425
  try {
@@ -12554,12 +12429,8 @@ class PixldocsRenderer {
12554
12429
  container.remove();
12555
12430
  };
12556
12431
  const onReady = () => {
12557
- if (finished) return;
12558
- finished = true;
12559
12432
  this.waitForCanvasScene(container, config, pageIndex).then(async () => {
12560
12433
  try {
12561
- await this.waitForStableTextMetrics(container, config, true);
12562
- await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
12563
12434
  const fabricInstance = this.getFabricCanvasFromContainer(container);
12564
12435
  const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
12565
12436
  await this.waitForCanvasImages(container, expectedImageCount);
@@ -12571,8 +12442,8 @@ class PixldocsRenderer {
12571
12442
  return;
12572
12443
  }
12573
12444
  const exportCanvas = document.createElement("canvas");
12574
- exportCanvas.width = Math.round(canvasWidth * pixelRatio);
12575
- exportCanvas.height = Math.round(canvasHeight * pixelRatio);
12445
+ exportCanvas.width = sourceCanvas.width;
12446
+ exportCanvas.height = sourceCanvas.height;
12576
12447
  const exportCtx = exportCanvas.getContext("2d");
12577
12448
  if (!exportCtx) {
12578
12449
  cleanup();
@@ -12580,10 +12451,10 @@ class PixldocsRenderer {
12580
12451
  return;
12581
12452
  }
12582
12453
  exportCtx.save();
12583
- exportCtx.scale(exportCanvas.width / canvasWidth, exportCanvas.height / canvasHeight);
12454
+ exportCtx.scale(sourceCanvas.width / canvasWidth, sourceCanvas.height / canvasHeight);
12584
12455
  this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
12585
12456
  exportCtx.restore();
12586
- exportCtx.drawImage(sourceCanvas, 0, 0, exportCanvas.width, exportCanvas.height);
12457
+ exportCtx.drawImage(sourceCanvas, 0, 0);
12587
12458
  const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
12588
12459
  const dataUrl = exportCanvas.toDataURL(mimeType, quality);
12589
12460
  cleanup();
@@ -12596,11 +12467,12 @@ class PixldocsRenderer {
12596
12467
  };
12597
12468
  const root = createRoot(container);
12598
12469
  root.render(
12599
- createElement(PixldocsPreview2, {
12470
+ createElement(PreviewCanvas2, {
12600
12471
  config,
12601
12472
  pageIndex,
12602
- zoom: 1,
12473
+ zoom: pixelRatio,
12603
12474
  absoluteZoom: true,
12475
+ skipFontReadyWait: true,
12604
12476
  onReady
12605
12477
  })
12606
12478
  );
@@ -12617,19 +12489,18 @@ class PixldocsRenderer {
12617
12489
  // document space (e.g. 612x792) instead of inflated pixel space.
12618
12490
  captureSvgViaPreviewCanvas(config, pageIndex, canvasWidth, canvasHeight) {
12619
12491
  return new Promise(async (resolve, reject) => {
12620
- const { PixldocsPreview: PixldocsPreview2 } = await Promise.resolve().then(() => PixldocsPreview$1);
12492
+ const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
12621
12493
  const container = document.createElement("div");
12622
12494
  container.style.cssText = `
12623
12495
  position: fixed; left: -99999px; top: -99999px;
12624
12496
  width: ${canvasWidth}px; height: ${canvasHeight}px;
12625
- overflow: hidden; pointer-events: none; visibility: hidden;
12497
+ overflow: hidden; pointer-events: none; opacity: 0;
12626
12498
  `;
12627
12499
  document.body.appendChild(container);
12628
12500
  const timeout = setTimeout(() => {
12629
12501
  cleanup();
12630
12502
  reject(new Error("SVG render timeout (30s)"));
12631
12503
  }, 3e4);
12632
- let finished = false;
12633
12504
  const cleanup = () => {
12634
12505
  clearTimeout(timeout);
12635
12506
  try {
@@ -12639,13 +12510,9 @@ class PixldocsRenderer {
12639
12510
  container.remove();
12640
12511
  };
12641
12512
  const onReady = () => {
12642
- if (finished) return;
12643
- finished = true;
12644
12513
  this.waitForCanvasScene(container, config, pageIndex).then(async () => {
12645
12514
  var _a, _b;
12646
12515
  try {
12647
- await this.waitForStableTextMetrics(container, config, true);
12648
- await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
12649
12516
  const fabricInstance = this.getFabricCanvasFromContainer(container);
12650
12517
  if (!fabricInstance) {
12651
12518
  cleanup();
@@ -12698,12 +12565,13 @@ class PixldocsRenderer {
12698
12565
  };
12699
12566
  const root = createRoot(container);
12700
12567
  root.render(
12701
- createElement(PixldocsPreview2, {
12568
+ createElement(PreviewCanvas2, {
12702
12569
  config,
12703
12570
  pageIndex,
12704
12571
  zoom: 1,
12705
12572
  // 1:1 — no UI scaling for SVG capture
12706
12573
  absoluteZoom: true,
12574
+ skipFontReadyWait: true,
12707
12575
  onReady
12708
12576
  })
12709
12577
  );
@@ -12764,12 +12632,21 @@ class PixldocsRenderer {
12764
12632
  * using the global __fabricCanvasRegistry (set by PageCanvas).
12765
12633
  */
12766
12634
  getFabricCanvasFromContainer(container) {
12767
- return getFabricCanvasFromContainer(container);
12635
+ const registry2 = window.__fabricCanvasRegistry;
12636
+ if (registry2 instanceof Map) {
12637
+ for (const entry of registry2.values()) {
12638
+ const canvas = (entry == null ? void 0 : entry.canvas) || entry;
12639
+ if (!canvas || typeof canvas.toSVG !== "function") continue;
12640
+ const el = canvas.lowerCanvasEl || canvas.upperCanvasEl;
12641
+ if (el && container.contains(el)) return canvas;
12642
+ }
12643
+ }
12644
+ return null;
12768
12645
  }
12769
- async waitForStableTextMetrics(container, config, strictFontWait = false) {
12646
+ async waitForStableTextMetrics(container, config) {
12770
12647
  if (typeof document !== "undefined") {
12771
- await ensureFontsForResolvedConfig(config);
12772
- await this.waitForRelevantFonts(config, strictFontWait ? 6e3 : 1800);
12648
+ void ensureFontsForResolvedConfig(config);
12649
+ await this.waitForRelevantFonts(config);
12773
12650
  }
12774
12651
  const fabricInstance = this.getFabricCanvasFromContainer(container);
12775
12652
  if (!(fabricInstance == null ? void 0 : fabricInstance.getObjects)) return;
@@ -12778,7 +12655,59 @@ class PixldocsRenderer {
12778
12655
  clearFabricCharCache();
12779
12656
  clearMeasurementCache();
12780
12657
  };
12781
- const reflowTextboxes = () => stabilizeFabricTextObjects(fabricInstance);
12658
+ const reflowTextboxes = () => {
12659
+ var _a, _b, _c;
12660
+ const walk = (obj) => {
12661
+ var _a2, _b2, _c2, _d;
12662
+ if (!obj) return;
12663
+ const children = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
12664
+ if (children.length) children.forEach(walk);
12665
+ const isTextObject = typeof obj.text === "string" && typeof obj.initDimensions === "function" && (obj.type === "textbox" || obj.type === "text" || obj.type === "i-text" || obj.isEditing !== void 0);
12666
+ if (isTextObject) {
12667
+ const saved = {
12668
+ width: obj.width,
12669
+ scaleX: obj.scaleX,
12670
+ scaleY: obj.scaleY
12671
+ };
12672
+ const resetTextboxLayoutInternals = () => {
12673
+ var _a3;
12674
+ (_a3 = obj._clearCache) == null ? void 0 : _a3.call(obj);
12675
+ obj.__charBounds = [];
12676
+ obj.__lineWidths = [];
12677
+ obj.__lineHeights = [];
12678
+ obj.__graphemeLines = [];
12679
+ obj._textLines = [];
12680
+ obj.textLines = [];
12681
+ obj._styleMap = null;
12682
+ obj.styleMap = null;
12683
+ obj.dirty = true;
12684
+ };
12685
+ resetTextboxLayoutInternals();
12686
+ obj.initDimensions();
12687
+ if (saved.width != null) {
12688
+ (_a2 = obj.set) == null ? void 0 : _a2.call(obj, {
12689
+ width: saved.width,
12690
+ scaleX: saved.scaleX,
12691
+ scaleY: saved.scaleY
12692
+ });
12693
+ resetTextboxLayoutInternals();
12694
+ obj.initDimensions();
12695
+ }
12696
+ (_b2 = obj.set) == null ? void 0 : _b2.call(obj, {
12697
+ width: saved.width,
12698
+ scaleX: saved.scaleX,
12699
+ scaleY: saved.scaleY,
12700
+ dirty: true
12701
+ });
12702
+ (_c2 = obj._clearCache) == null ? void 0 : _c2.call(obj);
12703
+ (_d = obj.setCoords) == null ? void 0 : _d.call(obj);
12704
+ }
12705
+ };
12706
+ fabricInstance.getObjects().forEach(walk);
12707
+ (_a = fabricInstance.calcOffset) == null ? void 0 : _a.call(fabricInstance);
12708
+ (_b = fabricInstance.renderAll) == null ? void 0 : _b.call(fabricInstance);
12709
+ (_c = fabricInstance.requestRenderAll) == null ? void 0 : _c.call(fabricInstance);
12710
+ };
12782
12711
  clearTextMetricCaches();
12783
12712
  await waitForPaint();
12784
12713
  reflowTextboxes();
@@ -13512,6 +13441,9 @@ function rewriteSvgFontsForJsPDF(svgStr) {
13512
13441
  const resolved = resolveFontWeight(weight);
13513
13442
  const isItalic = /italic|oblique/i.test(styleRaw);
13514
13443
  const jsPdfName = getEmbeddedJsPDFFontName(clean, resolved, isItalic);
13444
+ el.setAttribute("data-source-font-family", clean);
13445
+ el.setAttribute("data-source-font-weight", String(resolved));
13446
+ el.setAttribute("data-source-font-style", isItalic ? "italic" : "normal");
13515
13447
  const directText = Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent || "").join("");
13516
13448
  const hasDevanagari = containsDevanagari(directText);
13517
13449
  if (hasDevanagari && directText.length > 0) {
@@ -14493,24 +14425,89 @@ async function svg2pdfWithDomMount(svg, pdf, opts) {
14493
14425
  if (wrap.parentNode) document.body.removeChild(wrap);
14494
14426
  }
14495
14427
  }
14496
- function convertTextDecorationsToLines(svg) {
14428
+ async function convertTextDecorationsToLines(svg) {
14497
14429
  const doc = svg.ownerDocument;
14498
14430
  if (!doc) return;
14499
- let measureCanvas = null;
14431
+ const resolveInheritedSvgValue = (el, attr, styleProp = attr) => {
14432
+ var _a, _b;
14433
+ let current = el;
14434
+ while (current) {
14435
+ const attrValue = (_a = current.getAttribute(attr)) == null ? void 0 : _a.trim();
14436
+ if (attrValue) return attrValue;
14437
+ const styleValue = (_b = getInlineStyleValue(current, styleProp)) == null ? void 0 : _b.trim();
14438
+ if (styleValue) return styleValue;
14439
+ current = current.parentElement;
14440
+ }
14441
+ return null;
14442
+ };
14443
+ if (typeof document !== "undefined" && document.fonts) {
14444
+ const fontFamilies = /* @__PURE__ */ new Set();
14445
+ for (const textEl of svg.querySelectorAll("text")) {
14446
+ const ff = textEl.getAttribute("data-source-font-family") || textEl.getAttribute("font-family");
14447
+ if (ff) fontFamilies.add(ff.replace(/'/g, ""));
14448
+ for (const tspan of textEl.querySelectorAll("tspan")) {
14449
+ const tff = tspan.getAttribute("data-source-font-family") || tspan.getAttribute("font-family");
14450
+ if (tff) fontFamilies.add(tff.replace(/'/g, ""));
14451
+ }
14452
+ }
14453
+ await Promise.all(Array.from(fontFamilies).flatMap((ff) => [
14454
+ document.fonts.load(`16px "${ff}"`).then(() => {
14455
+ }).catch(() => {
14456
+ }),
14457
+ document.fonts.load(`bold 16px "${ff}"`).then(() => {
14458
+ }).catch(() => {
14459
+ })
14460
+ ]));
14461
+ await document.fonts.ready;
14462
+ }
14463
+ let tempContainer = null;
14464
+ let liveSvg = null;
14465
+ try {
14466
+ if (typeof document !== "undefined") {
14467
+ tempContainer = document.createElement("div");
14468
+ tempContainer.style.cssText = "position:fixed;left:-9999px;top:-9999px;visibility:hidden;pointer-events:none;";
14469
+ document.body.appendChild(tempContainer);
14470
+ const clone = svg.cloneNode(true);
14471
+ for (const textNode of clone.querySelectorAll("text, tspan, textPath")) {
14472
+ const sourceFamily = textNode.getAttribute("data-source-font-family");
14473
+ const sourceWeight = textNode.getAttribute("data-source-font-weight");
14474
+ const sourceStyle = textNode.getAttribute("data-source-font-style");
14475
+ if (sourceFamily) textNode.setAttribute("font-family", sourceFamily);
14476
+ if (sourceWeight) textNode.setAttribute("font-weight", sourceWeight);
14477
+ if (sourceStyle) textNode.setAttribute("font-style", sourceStyle);
14478
+ const inlineStyle = textNode.getAttribute("style") || "";
14479
+ 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));
14480
+ if (sourceFamily) stylePairs.push(`font-family: ${sourceFamily}`);
14481
+ if (sourceWeight) stylePairs.push(`font-weight: ${sourceWeight}`);
14482
+ if (sourceStyle) stylePairs.push(`font-style: ${sourceStyle}`);
14483
+ if (stylePairs.length > 0) textNode.setAttribute("style", stylePairs.join("; "));
14484
+ }
14485
+ tempContainer.appendChild(clone);
14486
+ liveSvg = clone;
14487
+ }
14488
+ } catch {
14489
+ }
14500
14490
  let ctx = null;
14501
14491
  try {
14502
- measureCanvas = doc.createElement("canvas");
14492
+ const realDoc = typeof document !== "undefined" ? document : doc;
14493
+ const measureCanvas = realDoc.createElement("canvas");
14503
14494
  ctx = measureCanvas.getContext("2d");
14504
14495
  } catch {
14505
14496
  }
14506
14497
  const textEls = Array.from(svg.querySelectorAll("text"));
14507
- for (const textEl of textEls) {
14498
+ const liveTextEls = liveSvg ? Array.from(liveSvg.querySelectorAll("text")) : null;
14499
+ for (let ti = 0; ti < textEls.length; ti++) {
14500
+ const textEl = textEls[ti];
14501
+ const liveTextEl = liveTextEls == null ? void 0 : liveTextEls[ti];
14508
14502
  const tspans = Array.from(textEl.querySelectorAll("tspan"));
14509
14503
  if (tspans.length === 0) continue;
14504
+ const liveTspans = liveTextEl ? Array.from(liveTextEl.querySelectorAll("tspan")) : null;
14510
14505
  const textDecOnText = (textEl.getAttribute("text-decoration") || "").toLowerCase();
14511
14506
  const textStyleDec = (getInlineStyleValue(textEl, "text-decoration") || "").toLowerCase();
14512
14507
  const textHasUnderline = textDecOnText.includes("underline") || textStyleDec.includes("underline");
14513
- for (const tspan of tspans) {
14508
+ for (let si = 0; si < tspans.length; si++) {
14509
+ const tspan = tspans[si];
14510
+ const liveTspan = liveTspans == null ? void 0 : liveTspans[si];
14514
14511
  const tspanDec = (tspan.getAttribute("text-decoration") || "").toLowerCase();
14515
14512
  const tspanStyleDec = (getInlineStyleValue(tspan, "text-decoration") || "").toLowerCase();
14516
14513
  const hasUnderline = tspanDec.includes("underline") || tspanStyleDec.includes("underline") || textHasUnderline;
@@ -14524,41 +14521,73 @@ function convertTextDecorationsToLines(svg) {
14524
14521
  const fontSize = parseFloat(
14525
14522
  tspan.getAttribute("font-size") || textEl.getAttribute("font-size") || "16"
14526
14523
  );
14527
- const fontFamily = tspan.getAttribute("font-family") || textEl.getAttribute("font-family") || "sans-serif";
14528
- const fontWeight = tspan.getAttribute("font-weight") || textEl.getAttribute("font-weight") || "normal";
14524
+ const fontFamily = tspan.getAttribute("data-source-font-family") || textEl.getAttribute("data-source-font-family") || resolveInheritedSvgValue(tspan, "font-family") || "sans-serif";
14525
+ const fontWeight = tspan.getAttribute("data-source-font-weight") || textEl.getAttribute("data-source-font-weight") || resolveInheritedSvgValue(tspan, "font-weight") || "normal";
14526
+ const fontStyle = tspan.getAttribute("data-source-font-style") || textEl.getAttribute("data-source-font-style") || resolveInheritedSvgValue(tspan, "font-style") || "normal";
14529
14527
  const fill = tspan.getAttribute("fill") || textEl.getAttribute("fill") || "#000000";
14530
- let textWidth = 0;
14531
- if (typeof tspan.getComputedTextLength === "function") {
14528
+ let textWidth = content.length * fontSize * 0.6;
14529
+ if (ctx) {
14530
+ ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily.replace(/'/g, "")}`;
14531
+ textWidth = ctx.measureText(content).width;
14532
+ }
14533
+ const textAnchorRaw = (resolveInheritedSvgValue(tspan, "text-anchor") || "start").toLowerCase();
14534
+ const textAnchor = textAnchorRaw === "middle" || textAnchorRaw === "end" ? textAnchorRaw : "start";
14535
+ let lineStartX = x - (textAnchor === "middle" ? textWidth / 2 : textAnchor === "end" ? textWidth : 0);
14536
+ let lineEndX = lineStartX + textWidth;
14537
+ let baselineY = y;
14538
+ if (liveTspan) {
14532
14539
  try {
14533
- textWidth = tspan.getComputedTextLength();
14540
+ const liveCharCount = liveTspan.getNumberOfChars();
14541
+ const svgWidth = liveTspan.getComputedTextLength();
14542
+ if (Number.isFinite(svgWidth) && svgWidth > 0) {
14543
+ textWidth = Math.max(textWidth, svgWidth);
14544
+ }
14545
+ if (liveCharCount > 0) {
14546
+ const start = liveTspan.getStartPositionOfChar(0);
14547
+ const end = liveTspan.getEndPositionOfChar(liveCharCount - 1);
14548
+ if (Number.isFinite(start.x)) lineStartX = start.x;
14549
+ if (Number.isFinite(start.y)) baselineY = start.y;
14550
+ if (Number.isFinite(end.x)) lineEndX = Math.max(end.x, lineStartX + textWidth);
14551
+ if (Number.isFinite(end.y) && !Number.isFinite(baselineY)) baselineY = end.y;
14552
+ } else {
14553
+ lineEndX = lineStartX + textWidth;
14554
+ }
14555
+ const bbox = liveTspan.getBBox();
14556
+ if (Number.isFinite(bbox.x) && Number.isFinite(bbox.width)) {
14557
+ lineStartX = Math.min(lineStartX, bbox.x);
14558
+ lineEndX = Math.max(lineEndX, bbox.x + bbox.width);
14559
+ }
14534
14560
  } catch {
14535
14561
  }
14536
14562
  }
14537
- if (!Number.isFinite(textWidth) || textWidth <= 0) {
14538
- if (ctx) {
14539
- ctx.font = `${fontWeight} ${fontSize}px ${fontFamily.replace(/'/g, "")}`;
14540
- textWidth = ctx.measureText(content).width;
14541
- } else {
14542
- textWidth = content.length * fontSize * 0.6;
14543
- }
14563
+ if (!(lineEndX > lineStartX)) {
14564
+ lineStartX = x - (textAnchor === "middle" ? textWidth / 2 : textAnchor === "end" ? textWidth : 0);
14565
+ lineEndX = lineStartX + textWidth;
14544
14566
  }
14545
- const underlineY = y + fontSize * 0.15;
14567
+ const underlineY = baselineY + fontSize * 0.15;
14546
14568
  const thickness = Math.max(0.5, fontSize * 0.066667);
14547
14569
  const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
14548
- line.setAttribute("x1", String(x));
14570
+ line.setAttribute("x1", String(lineStartX));
14549
14571
  line.setAttribute("y1", String(underlineY));
14550
- line.setAttribute("x2", String(x + textWidth));
14572
+ line.setAttribute("x2", String(lineEndX));
14551
14573
  line.setAttribute("y2", String(underlineY));
14552
14574
  line.setAttribute("stroke", fill.startsWith("url(") ? "#000000" : fill);
14553
14575
  line.setAttribute("stroke-width", String(thickness));
14576
+ line.setAttribute("stroke-linecap", "butt");
14554
14577
  line.setAttribute("fill", "none");
14555
14578
  if (textEl.parentElement) {
14556
14579
  textEl.parentElement.insertBefore(line, textEl.nextSibling);
14557
14580
  }
14558
14581
  }
14559
14582
  }
14583
+ if (tempContainer) {
14584
+ try {
14585
+ document.body.removeChild(tempContainer);
14586
+ } catch {
14587
+ }
14588
+ }
14560
14589
  }
14561
- function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, options) {
14590
+ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, options) {
14562
14591
  try {
14563
14592
  const parser = new DOMParser();
14564
14593
  const processedSvg = inlineNestedSvgImageDataUris(rawSvg, parser);
@@ -14587,7 +14616,6 @@ function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, opti
14587
14616
  stripSuspiciousFullPageOverlayNodes(svgToDraw);
14588
14617
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
14589
14618
  sanitizeSvgTreeForPdf(svgToDraw);
14590
- convertTextDecorationsToLines(svgToDraw);
14591
14619
  return svgToDraw;
14592
14620
  } catch {
14593
14621
  return null;
@@ -14690,7 +14718,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
14690
14718
  const hasGradient = !!((_b = (_a = page.backgroundGradient) == null ? void 0 : _a.stops) == null ? void 0 : _b.length);
14691
14719
  drawPageBackground(pdf, i, page.width, page.height, page.backgroundColor, page.backgroundGradient);
14692
14720
  const shouldStripBg = stripPageBackground ?? hasGradient;
14693
- let processedSvg = prepareLiveCanvasSvgForPdf(page.svg, page.width, page.height, `page-${i + 1}`, {
14721
+ let processedSvg = await prepareLiveCanvasSvgForPdf(page.svg, page.width, page.height, `page-${i + 1}`, {
14694
14722
  stripPageBackground: shouldStripBg
14695
14723
  });
14696
14724
  if (processedSvg) {
@@ -14699,6 +14727,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
14699
14727
  const reParser = new DOMParser();
14700
14728
  const reDoc = reParser.parseFromString(rewrittenSvg, "image/svg+xml");
14701
14729
  processedSvg = reDoc.documentElement;
14730
+ await convertTextDecorationsToLines(processedSvg);
14702
14731
  }
14703
14732
  if (processedSvg) {
14704
14733
  pdf.setFillColor(0, 0, 0);