@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.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);
@@ -11980,6 +11928,7 @@ function PixldocsPreview(props) {
11980
11928
  const bump = () => {
11981
11929
  if (cancelled) return;
11982
11930
  clearMeasurementCache();
11931
+ clearFabricCharCache();
11983
11932
  setFontsReadyVersion((v) => v + 1);
11984
11933
  };
11985
11934
  (_b = (_a = document.fonts) == null ? void 0 : _a.ready) == null ? void 0 : _b.then(bump);
@@ -11993,13 +11942,6 @@ function PixldocsPreview(props) {
11993
11942
  () => `${pageIndex}-${fontsReadyVersion}-${stabilizationPass}`,
11994
11943
  [pageIndex, fontsReadyVersion, stabilizationPass]
11995
11944
  );
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
11945
  useEffect(() => {
12004
11946
  if (isResolveMode) return;
12005
11947
  if (!config) {
@@ -12030,13 +11972,14 @@ function PixldocsPreview(props) {
12030
11972
  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
11973
  }
12032
11974
  return /* @__PURE__ */ jsxs("div", { className, style: { ...style, position: "relative" }, children: [
12033
- /* @__PURE__ */ jsx("div", { ref: previewWrapRef, style: { visibility: canvasSettled ? "visible" : "hidden" }, children: /* @__PURE__ */ jsx(
11975
+ /* @__PURE__ */ jsx("div", { style: { visibility: canvasSettled ? "visible" : "hidden" }, children: /* @__PURE__ */ jsx(
12034
11976
  PreviewCanvas,
12035
11977
  {
12036
11978
  config,
12037
11979
  pageIndex,
12038
11980
  zoom,
12039
11981
  absoluteZoom,
11982
+ skipFontReadyWait,
12040
11983
  onDynamicFieldClick,
12041
11984
  onReady: handleCanvasReady
12042
11985
  },
@@ -12045,70 +11988,6 @@ function PixldocsPreview(props) {
12045
11988
  !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
11989
  ] });
12047
11990
  }
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
11991
  class PixldocsRenderer {
12113
11992
  constructor(config) {
12114
11993
  __publicField(this, "config");
@@ -12119,22 +11998,21 @@ class PixldocsRenderer {
12119
11998
  * Mounts a hidden PreviewCanvas component and captures the Fabric canvas output.
12120
11999
  */
12121
12000
  async render(templateConfig, options = {}) {
12122
- const renderConfig = await inlineBrowserReachableImageAssets(templateConfig);
12123
12001
  const pageIndex = options.pageIndex ?? 0;
12124
12002
  const format = options.format ?? "png";
12125
12003
  const quality = options.quality ?? 0.92;
12126
12004
  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];
12005
+ const canvasWidth = templateConfig.canvas.width;
12006
+ const canvasHeight = templateConfig.canvas.height;
12007
+ const page = templateConfig.pages[pageIndex];
12130
12008
  if (!page) {
12131
- throw new Error(`Page index ${pageIndex} not found (template has ${renderConfig.pages.length} pages)`);
12009
+ throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12132
12010
  }
12133
- await ensureFontsForResolvedConfig(renderConfig);
12011
+ await ensureFontsForResolvedConfig(templateConfig);
12134
12012
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12135
12013
  setPackageApiUrl2(this.config.imageProxyUrl);
12136
12014
  const dataUrl = await this.renderPageViaPreviewCanvas(
12137
- renderConfig,
12015
+ templateConfig,
12138
12016
  pageIndex,
12139
12017
  pixelRatio,
12140
12018
  format,
@@ -12186,17 +12064,16 @@ class PixldocsRenderer {
12186
12064
  * This is the key building block for client-side vector PDF export.
12187
12065
  */
12188
12066
  async renderPageSvg(templateConfig, pageIndex = 0) {
12189
- const renderConfig = await inlineBrowserReachableImageAssets(templateConfig);
12190
- const page = renderConfig.pages[pageIndex];
12067
+ const page = templateConfig.pages[pageIndex];
12191
12068
  if (!page) {
12192
- throw new Error(`Page index ${pageIndex} not found (template has ${renderConfig.pages.length} pages)`);
12069
+ throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12193
12070
  }
12194
- await ensureFontsForResolvedConfig(renderConfig);
12071
+ await ensureFontsForResolvedConfig(templateConfig);
12195
12072
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12196
12073
  setPackageApiUrl2(this.config.imageProxyUrl);
12197
- const canvasWidth = renderConfig.canvas.width;
12198
- const canvasHeight = renderConfig.canvas.height;
12199
- return this.captureSvgViaPreviewCanvas(renderConfig, pageIndex, canvasWidth, canvasHeight);
12074
+ const canvasWidth = templateConfig.canvas.width;
12075
+ const canvasHeight = templateConfig.canvas.height;
12076
+ return this.captureSvgViaPreviewCanvas(templateConfig, pageIndex, canvasWidth, canvasHeight);
12200
12077
  }
12201
12078
  /**
12202
12079
  * Render all pages and return SVG strings for each.
@@ -12529,7 +12406,7 @@ class PixldocsRenderer {
12529
12406
  }
12530
12407
  }
12531
12408
  async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality) {
12532
- const { PixldocsPreview: PixldocsPreview2 } = await Promise.resolve().then(() => PixldocsPreview$1);
12409
+ const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
12533
12410
  const canvasWidth = config.canvas.width;
12534
12411
  const canvasHeight = config.canvas.height;
12535
12412
  return new Promise((resolve, reject) => {
@@ -12537,14 +12414,13 @@ class PixldocsRenderer {
12537
12414
  container.style.cssText = `
12538
12415
  position: fixed; left: -99999px; top: -99999px;
12539
12416
  width: ${canvasWidth}px; height: ${canvasHeight}px;
12540
- overflow: hidden; pointer-events: none; visibility: hidden;
12417
+ overflow: hidden; pointer-events: none; opacity: 0;
12541
12418
  `;
12542
12419
  document.body.appendChild(container);
12543
12420
  const timeout = setTimeout(() => {
12544
12421
  cleanup();
12545
12422
  reject(new Error("Render timeout (30s)"));
12546
12423
  }, 3e4);
12547
- let finished = false;
12548
12424
  const cleanup = () => {
12549
12425
  clearTimeout(timeout);
12550
12426
  try {
@@ -12554,12 +12430,8 @@ class PixldocsRenderer {
12554
12430
  container.remove();
12555
12431
  };
12556
12432
  const onReady = () => {
12557
- if (finished) return;
12558
- finished = true;
12559
12433
  this.waitForCanvasScene(container, config, pageIndex).then(async () => {
12560
12434
  try {
12561
- await this.waitForStableTextMetrics(container, config, true);
12562
- await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
12563
12435
  const fabricInstance = this.getFabricCanvasFromContainer(container);
12564
12436
  const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
12565
12437
  await this.waitForCanvasImages(container, expectedImageCount);
@@ -12571,8 +12443,8 @@ class PixldocsRenderer {
12571
12443
  return;
12572
12444
  }
12573
12445
  const exportCanvas = document.createElement("canvas");
12574
- exportCanvas.width = Math.round(canvasWidth * pixelRatio);
12575
- exportCanvas.height = Math.round(canvasHeight * pixelRatio);
12446
+ exportCanvas.width = sourceCanvas.width;
12447
+ exportCanvas.height = sourceCanvas.height;
12576
12448
  const exportCtx = exportCanvas.getContext("2d");
12577
12449
  if (!exportCtx) {
12578
12450
  cleanup();
@@ -12580,10 +12452,10 @@ class PixldocsRenderer {
12580
12452
  return;
12581
12453
  }
12582
12454
  exportCtx.save();
12583
- exportCtx.scale(exportCanvas.width / canvasWidth, exportCanvas.height / canvasHeight);
12455
+ exportCtx.scale(sourceCanvas.width / canvasWidth, sourceCanvas.height / canvasHeight);
12584
12456
  this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
12585
12457
  exportCtx.restore();
12586
- exportCtx.drawImage(sourceCanvas, 0, 0, exportCanvas.width, exportCanvas.height);
12458
+ exportCtx.drawImage(sourceCanvas, 0, 0);
12587
12459
  const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
12588
12460
  const dataUrl = exportCanvas.toDataURL(mimeType, quality);
12589
12461
  cleanup();
@@ -12596,11 +12468,12 @@ class PixldocsRenderer {
12596
12468
  };
12597
12469
  const root = createRoot(container);
12598
12470
  root.render(
12599
- createElement(PixldocsPreview2, {
12471
+ createElement(PreviewCanvas2, {
12600
12472
  config,
12601
12473
  pageIndex,
12602
- zoom: 1,
12474
+ zoom: pixelRatio,
12603
12475
  absoluteZoom: true,
12476
+ skipFontReadyWait: true,
12604
12477
  onReady
12605
12478
  })
12606
12479
  );
@@ -12617,19 +12490,18 @@ class PixldocsRenderer {
12617
12490
  // document space (e.g. 612x792) instead of inflated pixel space.
12618
12491
  captureSvgViaPreviewCanvas(config, pageIndex, canvasWidth, canvasHeight) {
12619
12492
  return new Promise(async (resolve, reject) => {
12620
- const { PixldocsPreview: PixldocsPreview2 } = await Promise.resolve().then(() => PixldocsPreview$1);
12493
+ const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
12621
12494
  const container = document.createElement("div");
12622
12495
  container.style.cssText = `
12623
12496
  position: fixed; left: -99999px; top: -99999px;
12624
12497
  width: ${canvasWidth}px; height: ${canvasHeight}px;
12625
- overflow: hidden; pointer-events: none; visibility: hidden;
12498
+ overflow: hidden; pointer-events: none; opacity: 0;
12626
12499
  `;
12627
12500
  document.body.appendChild(container);
12628
12501
  const timeout = setTimeout(() => {
12629
12502
  cleanup();
12630
12503
  reject(new Error("SVG render timeout (30s)"));
12631
12504
  }, 3e4);
12632
- let finished = false;
12633
12505
  const cleanup = () => {
12634
12506
  clearTimeout(timeout);
12635
12507
  try {
@@ -12639,13 +12511,9 @@ class PixldocsRenderer {
12639
12511
  container.remove();
12640
12512
  };
12641
12513
  const onReady = () => {
12642
- if (finished) return;
12643
- finished = true;
12644
12514
  this.waitForCanvasScene(container, config, pageIndex).then(async () => {
12645
12515
  var _a, _b;
12646
12516
  try {
12647
- await this.waitForStableTextMetrics(container, config, true);
12648
- await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
12649
12517
  const fabricInstance = this.getFabricCanvasFromContainer(container);
12650
12518
  if (!fabricInstance) {
12651
12519
  cleanup();
@@ -12698,12 +12566,13 @@ class PixldocsRenderer {
12698
12566
  };
12699
12567
  const root = createRoot(container);
12700
12568
  root.render(
12701
- createElement(PixldocsPreview2, {
12569
+ createElement(PreviewCanvas2, {
12702
12570
  config,
12703
12571
  pageIndex,
12704
12572
  zoom: 1,
12705
12573
  // 1:1 — no UI scaling for SVG capture
12706
12574
  absoluteZoom: true,
12575
+ skipFontReadyWait: true,
12707
12576
  onReady
12708
12577
  })
12709
12578
  );
@@ -12764,12 +12633,21 @@ class PixldocsRenderer {
12764
12633
  * using the global __fabricCanvasRegistry (set by PageCanvas).
12765
12634
  */
12766
12635
  getFabricCanvasFromContainer(container) {
12767
- return getFabricCanvasFromContainer(container);
12636
+ const registry2 = window.__fabricCanvasRegistry;
12637
+ if (registry2 instanceof Map) {
12638
+ for (const entry of registry2.values()) {
12639
+ const canvas = (entry == null ? void 0 : entry.canvas) || entry;
12640
+ if (!canvas || typeof canvas.toSVG !== "function") continue;
12641
+ const el = canvas.lowerCanvasEl || canvas.upperCanvasEl;
12642
+ if (el && container.contains(el)) return canvas;
12643
+ }
12644
+ }
12645
+ return null;
12768
12646
  }
12769
- async waitForStableTextMetrics(container, config, strictFontWait = false) {
12647
+ async waitForStableTextMetrics(container, config) {
12770
12648
  if (typeof document !== "undefined") {
12771
- await ensureFontsForResolvedConfig(config);
12772
- await this.waitForRelevantFonts(config, strictFontWait ? 6e3 : 1800);
12649
+ void ensureFontsForResolvedConfig(config);
12650
+ await this.waitForRelevantFonts(config);
12773
12651
  }
12774
12652
  const fabricInstance = this.getFabricCanvasFromContainer(container);
12775
12653
  if (!(fabricInstance == null ? void 0 : fabricInstance.getObjects)) return;
@@ -12778,7 +12656,59 @@ class PixldocsRenderer {
12778
12656
  clearFabricCharCache();
12779
12657
  clearMeasurementCache();
12780
12658
  };
12781
- const reflowTextboxes = () => stabilizeFabricTextObjects(fabricInstance);
12659
+ const reflowTextboxes = () => {
12660
+ var _a, _b, _c;
12661
+ const walk = (obj) => {
12662
+ var _a2, _b2, _c2, _d;
12663
+ if (!obj) return;
12664
+ const children = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
12665
+ if (children.length) children.forEach(walk);
12666
+ const isTextObject = typeof obj.text === "string" && typeof obj.initDimensions === "function" && (obj.type === "textbox" || obj.type === "text" || obj.type === "i-text" || obj.isEditing !== void 0);
12667
+ if (isTextObject) {
12668
+ const saved = {
12669
+ width: obj.width,
12670
+ scaleX: obj.scaleX,
12671
+ scaleY: obj.scaleY
12672
+ };
12673
+ const resetTextboxLayoutInternals = () => {
12674
+ var _a3;
12675
+ (_a3 = obj._clearCache) == null ? void 0 : _a3.call(obj);
12676
+ obj.__charBounds = [];
12677
+ obj.__lineWidths = [];
12678
+ obj.__lineHeights = [];
12679
+ obj.__graphemeLines = [];
12680
+ obj._textLines = [];
12681
+ obj.textLines = [];
12682
+ obj._styleMap = null;
12683
+ obj.styleMap = null;
12684
+ obj.dirty = true;
12685
+ };
12686
+ resetTextboxLayoutInternals();
12687
+ obj.initDimensions();
12688
+ if (saved.width != null) {
12689
+ (_a2 = obj.set) == null ? void 0 : _a2.call(obj, {
12690
+ width: saved.width,
12691
+ scaleX: saved.scaleX,
12692
+ scaleY: saved.scaleY
12693
+ });
12694
+ resetTextboxLayoutInternals();
12695
+ obj.initDimensions();
12696
+ }
12697
+ (_b2 = obj.set) == null ? void 0 : _b2.call(obj, {
12698
+ width: saved.width,
12699
+ scaleX: saved.scaleX,
12700
+ scaleY: saved.scaleY,
12701
+ dirty: true
12702
+ });
12703
+ (_c2 = obj._clearCache) == null ? void 0 : _c2.call(obj);
12704
+ (_d = obj.setCoords) == null ? void 0 : _d.call(obj);
12705
+ }
12706
+ };
12707
+ fabricInstance.getObjects().forEach(walk);
12708
+ (_a = fabricInstance.calcOffset) == null ? void 0 : _a.call(fabricInstance);
12709
+ (_b = fabricInstance.renderAll) == null ? void 0 : _b.call(fabricInstance);
12710
+ (_c = fabricInstance.requestRenderAll) == null ? void 0 : _c.call(fabricInstance);
12711
+ };
12782
12712
  clearTextMetricCaches();
12783
12713
  await waitForPaint();
12784
12714
  reflowTextboxes();
@@ -13512,6 +13442,9 @@ function rewriteSvgFontsForJsPDF(svgStr) {
13512
13442
  const resolved = resolveFontWeight(weight);
13513
13443
  const isItalic = /italic|oblique/i.test(styleRaw);
13514
13444
  const jsPdfName = getEmbeddedJsPDFFontName(clean, resolved, isItalic);
13445
+ el.setAttribute("data-source-font-family", clean);
13446
+ el.setAttribute("data-source-font-weight", String(resolved));
13447
+ el.setAttribute("data-source-font-style", isItalic ? "italic" : "normal");
13515
13448
  const directText = Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent || "").join("");
13516
13449
  const hasDevanagari = containsDevanagari(directText);
13517
13450
  if (hasDevanagari && directText.length > 0) {
@@ -14493,24 +14426,89 @@ async function svg2pdfWithDomMount(svg, pdf, opts) {
14493
14426
  if (wrap.parentNode) document.body.removeChild(wrap);
14494
14427
  }
14495
14428
  }
14496
- function convertTextDecorationsToLines(svg) {
14429
+ async function convertTextDecorationsToLines(svg) {
14497
14430
  const doc = svg.ownerDocument;
14498
14431
  if (!doc) return;
14499
- let measureCanvas = null;
14432
+ const resolveInheritedSvgValue = (el, attr, styleProp = attr) => {
14433
+ var _a, _b;
14434
+ let current = el;
14435
+ while (current) {
14436
+ const attrValue = (_a = current.getAttribute(attr)) == null ? void 0 : _a.trim();
14437
+ if (attrValue) return attrValue;
14438
+ const styleValue = (_b = getInlineStyleValue(current, styleProp)) == null ? void 0 : _b.trim();
14439
+ if (styleValue) return styleValue;
14440
+ current = current.parentElement;
14441
+ }
14442
+ return null;
14443
+ };
14444
+ if (typeof document !== "undefined" && document.fonts) {
14445
+ const fontFamilies = /* @__PURE__ */ new Set();
14446
+ for (const textEl of svg.querySelectorAll("text")) {
14447
+ const ff = textEl.getAttribute("data-source-font-family") || textEl.getAttribute("font-family");
14448
+ if (ff) fontFamilies.add(ff.replace(/'/g, ""));
14449
+ for (const tspan of textEl.querySelectorAll("tspan")) {
14450
+ const tff = tspan.getAttribute("data-source-font-family") || tspan.getAttribute("font-family");
14451
+ if (tff) fontFamilies.add(tff.replace(/'/g, ""));
14452
+ }
14453
+ }
14454
+ await Promise.all(Array.from(fontFamilies).flatMap((ff) => [
14455
+ document.fonts.load(`16px "${ff}"`).then(() => {
14456
+ }).catch(() => {
14457
+ }),
14458
+ document.fonts.load(`bold 16px "${ff}"`).then(() => {
14459
+ }).catch(() => {
14460
+ })
14461
+ ]));
14462
+ await document.fonts.ready;
14463
+ }
14464
+ let tempContainer = null;
14465
+ let liveSvg = null;
14466
+ try {
14467
+ if (typeof document !== "undefined") {
14468
+ tempContainer = document.createElement("div");
14469
+ tempContainer.style.cssText = "position:fixed;left:-9999px;top:-9999px;visibility:hidden;pointer-events:none;";
14470
+ document.body.appendChild(tempContainer);
14471
+ const clone = svg.cloneNode(true);
14472
+ for (const textNode of clone.querySelectorAll("text, tspan, textPath")) {
14473
+ const sourceFamily = textNode.getAttribute("data-source-font-family");
14474
+ const sourceWeight = textNode.getAttribute("data-source-font-weight");
14475
+ const sourceStyle = textNode.getAttribute("data-source-font-style");
14476
+ if (sourceFamily) textNode.setAttribute("font-family", sourceFamily);
14477
+ if (sourceWeight) textNode.setAttribute("font-weight", sourceWeight);
14478
+ if (sourceStyle) textNode.setAttribute("font-style", sourceStyle);
14479
+ const inlineStyle = textNode.getAttribute("style") || "";
14480
+ 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));
14481
+ if (sourceFamily) stylePairs.push(`font-family: ${sourceFamily}`);
14482
+ if (sourceWeight) stylePairs.push(`font-weight: ${sourceWeight}`);
14483
+ if (sourceStyle) stylePairs.push(`font-style: ${sourceStyle}`);
14484
+ if (stylePairs.length > 0) textNode.setAttribute("style", stylePairs.join("; "));
14485
+ }
14486
+ tempContainer.appendChild(clone);
14487
+ liveSvg = clone;
14488
+ }
14489
+ } catch {
14490
+ }
14500
14491
  let ctx = null;
14501
14492
  try {
14502
- measureCanvas = doc.createElement("canvas");
14493
+ const realDoc = typeof document !== "undefined" ? document : doc;
14494
+ const measureCanvas = realDoc.createElement("canvas");
14503
14495
  ctx = measureCanvas.getContext("2d");
14504
14496
  } catch {
14505
14497
  }
14506
14498
  const textEls = Array.from(svg.querySelectorAll("text"));
14507
- for (const textEl of textEls) {
14499
+ const liveTextEls = liveSvg ? Array.from(liveSvg.querySelectorAll("text")) : null;
14500
+ for (let ti = 0; ti < textEls.length; ti++) {
14501
+ const textEl = textEls[ti];
14502
+ const liveTextEl = liveTextEls == null ? void 0 : liveTextEls[ti];
14508
14503
  const tspans = Array.from(textEl.querySelectorAll("tspan"));
14509
14504
  if (tspans.length === 0) continue;
14505
+ const liveTspans = liveTextEl ? Array.from(liveTextEl.querySelectorAll("tspan")) : null;
14510
14506
  const textDecOnText = (textEl.getAttribute("text-decoration") || "").toLowerCase();
14511
14507
  const textStyleDec = (getInlineStyleValue(textEl, "text-decoration") || "").toLowerCase();
14512
14508
  const textHasUnderline = textDecOnText.includes("underline") || textStyleDec.includes("underline");
14513
- for (const tspan of tspans) {
14509
+ for (let si = 0; si < tspans.length; si++) {
14510
+ const tspan = tspans[si];
14511
+ const liveTspan = liveTspans == null ? void 0 : liveTspans[si];
14514
14512
  const tspanDec = (tspan.getAttribute("text-decoration") || "").toLowerCase();
14515
14513
  const tspanStyleDec = (getInlineStyleValue(tspan, "text-decoration") || "").toLowerCase();
14516
14514
  const hasUnderline = tspanDec.includes("underline") || tspanStyleDec.includes("underline") || textHasUnderline;
@@ -14524,41 +14522,73 @@ function convertTextDecorationsToLines(svg) {
14524
14522
  const fontSize = parseFloat(
14525
14523
  tspan.getAttribute("font-size") || textEl.getAttribute("font-size") || "16"
14526
14524
  );
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";
14525
+ const fontFamily = tspan.getAttribute("data-source-font-family") || textEl.getAttribute("data-source-font-family") || resolveInheritedSvgValue(tspan, "font-family") || "sans-serif";
14526
+ const fontWeight = tspan.getAttribute("data-source-font-weight") || textEl.getAttribute("data-source-font-weight") || resolveInheritedSvgValue(tspan, "font-weight") || "normal";
14527
+ const fontStyle = tspan.getAttribute("data-source-font-style") || textEl.getAttribute("data-source-font-style") || resolveInheritedSvgValue(tspan, "font-style") || "normal";
14529
14528
  const fill = tspan.getAttribute("fill") || textEl.getAttribute("fill") || "#000000";
14530
- let textWidth = 0;
14531
- if (typeof tspan.getComputedTextLength === "function") {
14529
+ let textWidth = content.length * fontSize * 0.6;
14530
+ if (ctx) {
14531
+ ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily.replace(/'/g, "")}`;
14532
+ textWidth = ctx.measureText(content).width;
14533
+ }
14534
+ const textAnchorRaw = (resolveInheritedSvgValue(tspan, "text-anchor") || "start").toLowerCase();
14535
+ const textAnchor = textAnchorRaw === "middle" || textAnchorRaw === "end" ? textAnchorRaw : "start";
14536
+ let lineStartX = x - (textAnchor === "middle" ? textWidth / 2 : textAnchor === "end" ? textWidth : 0);
14537
+ let lineEndX = lineStartX + textWidth;
14538
+ let baselineY = y;
14539
+ if (liveTspan) {
14532
14540
  try {
14533
- textWidth = tspan.getComputedTextLength();
14541
+ const liveCharCount = liveTspan.getNumberOfChars();
14542
+ const svgWidth = liveTspan.getComputedTextLength();
14543
+ if (Number.isFinite(svgWidth) && svgWidth > 0) {
14544
+ textWidth = Math.max(textWidth, svgWidth);
14545
+ }
14546
+ if (liveCharCount > 0) {
14547
+ const start = liveTspan.getStartPositionOfChar(0);
14548
+ const end = liveTspan.getEndPositionOfChar(liveCharCount - 1);
14549
+ if (Number.isFinite(start.x)) lineStartX = start.x;
14550
+ if (Number.isFinite(start.y)) baselineY = start.y;
14551
+ if (Number.isFinite(end.x)) lineEndX = Math.max(end.x, lineStartX + textWidth);
14552
+ if (Number.isFinite(end.y) && !Number.isFinite(baselineY)) baselineY = end.y;
14553
+ } else {
14554
+ lineEndX = lineStartX + textWidth;
14555
+ }
14556
+ const bbox = liveTspan.getBBox();
14557
+ if (Number.isFinite(bbox.x) && Number.isFinite(bbox.width)) {
14558
+ lineStartX = Math.min(lineStartX, bbox.x);
14559
+ lineEndX = Math.max(lineEndX, bbox.x + bbox.width);
14560
+ }
14534
14561
  } catch {
14535
14562
  }
14536
14563
  }
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
- }
14564
+ if (!(lineEndX > lineStartX)) {
14565
+ lineStartX = x - (textAnchor === "middle" ? textWidth / 2 : textAnchor === "end" ? textWidth : 0);
14566
+ lineEndX = lineStartX + textWidth;
14544
14567
  }
14545
- const underlineY = y + fontSize * 0.15;
14568
+ const underlineY = baselineY + fontSize * 0.15;
14546
14569
  const thickness = Math.max(0.5, fontSize * 0.066667);
14547
14570
  const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
14548
- line.setAttribute("x1", String(x));
14571
+ line.setAttribute("x1", String(lineStartX));
14549
14572
  line.setAttribute("y1", String(underlineY));
14550
- line.setAttribute("x2", String(x + textWidth));
14573
+ line.setAttribute("x2", String(lineEndX));
14551
14574
  line.setAttribute("y2", String(underlineY));
14552
14575
  line.setAttribute("stroke", fill.startsWith("url(") ? "#000000" : fill);
14553
14576
  line.setAttribute("stroke-width", String(thickness));
14577
+ line.setAttribute("stroke-linecap", "butt");
14554
14578
  line.setAttribute("fill", "none");
14555
14579
  if (textEl.parentElement) {
14556
14580
  textEl.parentElement.insertBefore(line, textEl.nextSibling);
14557
14581
  }
14558
14582
  }
14559
14583
  }
14584
+ if (tempContainer) {
14585
+ try {
14586
+ document.body.removeChild(tempContainer);
14587
+ } catch {
14588
+ }
14589
+ }
14560
14590
  }
14561
- function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, options) {
14591
+ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, options) {
14562
14592
  try {
14563
14593
  const parser = new DOMParser();
14564
14594
  const processedSvg = inlineNestedSvgImageDataUris(rawSvg, parser);
@@ -14587,7 +14617,6 @@ function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, opti
14587
14617
  stripSuspiciousFullPageOverlayNodes(svgToDraw);
14588
14618
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
14589
14619
  sanitizeSvgTreeForPdf(svgToDraw);
14590
- convertTextDecorationsToLines(svgToDraw);
14591
14620
  return svgToDraw;
14592
14621
  } catch {
14593
14622
  return null;
@@ -14690,7 +14719,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
14690
14719
  const hasGradient = !!((_b = (_a = page.backgroundGradient) == null ? void 0 : _a.stops) == null ? void 0 : _b.length);
14691
14720
  drawPageBackground(pdf, i, page.width, page.height, page.backgroundColor, page.backgroundGradient);
14692
14721
  const shouldStripBg = stripPageBackground ?? hasGradient;
14693
- let processedSvg = prepareLiveCanvasSvgForPdf(page.svg, page.width, page.height, `page-${i + 1}`, {
14722
+ let processedSvg = await prepareLiveCanvasSvgForPdf(page.svg, page.width, page.height, `page-${i + 1}`, {
14694
14723
  stripPageBackground: shouldStripBg
14695
14724
  });
14696
14725
  if (processedSvg) {
@@ -14699,6 +14728,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
14699
14728
  const reParser = new DOMParser();
14700
14729
  const reDoc = reParser.parseFromString(rewrittenSvg, "image/svg+xml");
14701
14730
  processedSvg = reDoc.documentElement;
14731
+ await convertTextDecorationsToLines(processedSvg);
14702
14732
  }
14703
14733
  if (processedSvg) {
14704
14734
  pdf.setFillColor(0, 0, 0);