@pixldocs/canvas-renderer 0.5.24 → 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.cjs CHANGED
@@ -11852,73 +11852,16 @@ async function ensureFontsForResolvedConfig(config) {
11852
11852
  if (typeof document === "undefined") return;
11853
11853
  const descriptors = collectFontDescriptorsFromConfig(config);
11854
11854
  const families = new Set(descriptors.map((d) => d.family));
11855
- await withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 6e3);
11855
+ void withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 2500);
11856
11856
  if (document.fonts) {
11857
- await withTimeout(Promise.all(descriptors.map((d) => {
11857
+ descriptors.forEach((d) => {
11858
11858
  const stylePrefix = d.style === "italic" ? "italic " : "";
11859
11859
  const weightStr = String(d.weight);
11860
11860
  const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
11861
- return document.fonts.load(spec).catch(() => {
11861
+ document.fonts.load(spec).catch(() => {
11862
11862
  });
11863
- })), 6e3);
11864
- await withTimeout(document.fonts.ready, 6e3);
11865
- }
11866
- }
11867
- const TEXT_TYPES = /* @__PURE__ */ new Set(["textbox", "text", "i-text"]);
11868
- function getFabricCanvasFromContainer(container) {
11869
- const registry2 = window.__fabricCanvasRegistry;
11870
- if (registry2 instanceof Map) {
11871
- for (const entry of registry2.values()) {
11872
- const canvas = (entry == null ? void 0 : entry.canvas) || entry;
11873
- if (!canvas || typeof canvas.toSVG !== "function") continue;
11874
- const el = canvas.lowerCanvasEl || canvas.upperCanvasEl;
11875
- if (el && container.contains(el)) return canvas;
11876
- }
11863
+ });
11877
11864
  }
11878
- return null;
11879
- }
11880
- function stabilizeFabricTextObjects(fabricInstance) {
11881
- var _a, _b, _c;
11882
- if (!(fabricInstance == null ? void 0 : fabricInstance.getObjects)) return;
11883
- clearFabricCharCache();
11884
- clearMeasurementCache();
11885
- const walk = (obj) => {
11886
- var _a2, _b2, _c2, _d;
11887
- if (!obj) return;
11888
- const children = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
11889
- if (children.length) children.forEach(walk);
11890
- const isTextObject = typeof obj.text === "string" && typeof obj.initDimensions === "function" && (TEXT_TYPES.has(obj.type) || obj.isEditing !== void 0);
11891
- if (!isTextObject) return;
11892
- const saved = { width: obj.width, scaleX: obj.scaleX, scaleY: obj.scaleY };
11893
- const reset = () => {
11894
- var _a3;
11895
- (_a3 = obj._clearCache) == null ? void 0 : _a3.call(obj);
11896
- obj.__charBounds = [];
11897
- obj.__lineWidths = [];
11898
- obj.__lineHeights = [];
11899
- obj.__graphemeLines = [];
11900
- obj._textLines = [];
11901
- obj.textLines = [];
11902
- obj._styleMap = null;
11903
- obj.styleMap = null;
11904
- obj.dirty = true;
11905
- };
11906
- reset();
11907
- obj.initDimensions();
11908
- if (saved.width != null) {
11909
- (_a2 = obj.set) == null ? void 0 : _a2.call(obj, { width: saved.width, scaleX: saved.scaleX, scaleY: saved.scaleY });
11910
- reset();
11911
- obj.initDimensions();
11912
- (_b2 = obj.set) == null ? void 0 : _b2.call(obj, { width: saved.width, scaleX: saved.scaleX, scaleY: saved.scaleY });
11913
- }
11914
- obj.dirty = true;
11915
- (_c2 = obj._clearCache) == null ? void 0 : _c2.call(obj);
11916
- (_d = obj.setCoords) == null ? void 0 : _d.call(obj);
11917
- };
11918
- fabricInstance.getObjects().forEach(walk);
11919
- (_a = fabricInstance.calcOffset) == null ? void 0 : _a.call(fabricInstance);
11920
- (_b = fabricInstance.renderAll) == null ? void 0 : _b.call(fabricInstance);
11921
- (_c = fabricInstance.requestRenderAll) == null ? void 0 : _c.call(fabricInstance);
11922
11865
  }
11923
11866
  function PixldocsPreview(props) {
11924
11867
  const {
@@ -11930,7 +11873,8 @@ function PixldocsPreview(props) {
11930
11873
  style,
11931
11874
  onDynamicFieldClick,
11932
11875
  onReady,
11933
- onError
11876
+ onError,
11877
+ skipFontReadyWait = true
11934
11878
  } = props;
11935
11879
  react.useEffect(() => {
11936
11880
  setPackageApiUrl(imageProxyUrl);
@@ -12016,13 +11960,6 @@ function PixldocsPreview(props) {
12016
11960
  () => `${pageIndex}-${fontsReadyVersion}-${stabilizationPass}`,
12017
11961
  [pageIndex, fontsReadyVersion, stabilizationPass]
12018
11962
  );
12019
- const previewWrapRef = react.useCallback((node) => {
12020
- if (!node || !config) return;
12021
- requestAnimationFrame(() => {
12022
- const fabricCanvas = getFabricCanvasFromContainer(node);
12023
- if (fabricCanvas) stabilizeFabricTextObjects(fabricCanvas);
12024
- });
12025
- }, [config, previewKey]);
12026
11963
  react.useEffect(() => {
12027
11964
  if (isResolveMode) return;
12028
11965
  if (!config) {
@@ -12053,13 +11990,14 @@ function PixldocsPreview(props) {
12053
11990
  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..." }) });
12054
11991
  }
12055
11992
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, style: { ...style, position: "relative" }, children: [
12056
- /* @__PURE__ */ jsxRuntime.jsx("div", { ref: previewWrapRef, style: { visibility: canvasSettled ? "visible" : "hidden" }, children: /* @__PURE__ */ jsxRuntime.jsx(
11993
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { visibility: canvasSettled ? "visible" : "hidden" }, children: /* @__PURE__ */ jsxRuntime.jsx(
12057
11994
  PreviewCanvas,
12058
11995
  {
12059
11996
  config,
12060
11997
  pageIndex,
12061
11998
  zoom,
12062
11999
  absoluteZoom,
12000
+ skipFontReadyWait,
12063
12001
  onDynamicFieldClick,
12064
12002
  onReady: handleCanvasReady
12065
12003
  },
@@ -12068,66 +12006,6 @@ function PixldocsPreview(props) {
12068
12006
  !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..." }) })
12069
12007
  ] });
12070
12008
  }
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
12009
  class PixldocsRenderer {
12132
12010
  constructor(config) {
12133
12011
  __publicField(this, "config");
@@ -12138,22 +12016,21 @@ class PixldocsRenderer {
12138
12016
  * Mounts a hidden PreviewCanvas component and captures the Fabric canvas output.
12139
12017
  */
12140
12018
  async render(templateConfig, options = {}) {
12141
- const renderConfig = await inlineBrowserReachableImageAssets(templateConfig);
12142
12019
  const pageIndex = options.pageIndex ?? 0;
12143
12020
  const format = options.format ?? "png";
12144
12021
  const quality = options.quality ?? 0.92;
12145
12022
  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];
12023
+ const canvasWidth = templateConfig.canvas.width;
12024
+ const canvasHeight = templateConfig.canvas.height;
12025
+ const page = templateConfig.pages[pageIndex];
12149
12026
  if (!page) {
12150
- throw new Error(`Page index ${pageIndex} not found (template has ${renderConfig.pages.length} pages)`);
12027
+ throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12151
12028
  }
12152
- await ensureFontsForResolvedConfig(renderConfig);
12029
+ await ensureFontsForResolvedConfig(templateConfig);
12153
12030
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12154
12031
  setPackageApiUrl2(this.config.imageProxyUrl);
12155
12032
  const dataUrl = await this.renderPageViaPreviewCanvas(
12156
- renderConfig,
12033
+ templateConfig,
12157
12034
  pageIndex,
12158
12035
  pixelRatio,
12159
12036
  format,
@@ -12205,17 +12082,16 @@ class PixldocsRenderer {
12205
12082
  * This is the key building block for client-side vector PDF export.
12206
12083
  */
12207
12084
  async renderPageSvg(templateConfig, pageIndex = 0) {
12208
- const renderConfig = await inlineBrowserReachableImageAssets(templateConfig);
12209
- const page = renderConfig.pages[pageIndex];
12085
+ const page = templateConfig.pages[pageIndex];
12210
12086
  if (!page) {
12211
- throw new Error(`Page index ${pageIndex} not found (template has ${renderConfig.pages.length} pages)`);
12087
+ throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12212
12088
  }
12213
- await ensureFontsForResolvedConfig(renderConfig);
12089
+ await ensureFontsForResolvedConfig(templateConfig);
12214
12090
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12215
12091
  setPackageApiUrl2(this.config.imageProxyUrl);
12216
- const canvasWidth = renderConfig.canvas.width;
12217
- const canvasHeight = renderConfig.canvas.height;
12218
- return this.captureSvgViaPreviewCanvas(renderConfig, pageIndex, canvasWidth, canvasHeight);
12092
+ const canvasWidth = templateConfig.canvas.width;
12093
+ const canvasHeight = templateConfig.canvas.height;
12094
+ return this.captureSvgViaPreviewCanvas(templateConfig, pageIndex, canvasWidth, canvasHeight);
12219
12095
  }
12220
12096
  /**
12221
12097
  * Render all pages and return SVG strings for each.
@@ -12556,14 +12432,13 @@ class PixldocsRenderer {
12556
12432
  container.style.cssText = `
12557
12433
  position: fixed; left: -99999px; top: -99999px;
12558
12434
  width: ${canvasWidth}px; height: ${canvasHeight}px;
12559
- overflow: hidden; pointer-events: none; visibility: hidden;
12435
+ overflow: hidden; pointer-events: none; opacity: 0;
12560
12436
  `;
12561
12437
  document.body.appendChild(container);
12562
12438
  const timeout = setTimeout(() => {
12563
12439
  cleanup();
12564
12440
  reject(new Error("Render timeout (30s)"));
12565
12441
  }, 3e4);
12566
- let finished = false;
12567
12442
  const cleanup = () => {
12568
12443
  clearTimeout(timeout);
12569
12444
  try {
@@ -12573,12 +12448,8 @@ class PixldocsRenderer {
12573
12448
  container.remove();
12574
12449
  };
12575
12450
  const onReady = () => {
12576
- if (finished) return;
12577
- finished = true;
12578
12451
  this.waitForCanvasScene(container, config, pageIndex).then(async () => {
12579
12452
  try {
12580
- await this.waitForStableTextMetrics(container, config, true);
12581
- await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
12582
12453
  const fabricInstance = this.getFabricCanvasFromContainer(container);
12583
12454
  const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
12584
12455
  await this.waitForCanvasImages(container, expectedImageCount);
@@ -12590,8 +12461,8 @@ class PixldocsRenderer {
12590
12461
  return;
12591
12462
  }
12592
12463
  const exportCanvas = document.createElement("canvas");
12593
- exportCanvas.width = Math.round(canvasWidth * pixelRatio);
12594
- exportCanvas.height = Math.round(canvasHeight * pixelRatio);
12464
+ exportCanvas.width = sourceCanvas.width;
12465
+ exportCanvas.height = sourceCanvas.height;
12595
12466
  const exportCtx = exportCanvas.getContext("2d");
12596
12467
  if (!exportCtx) {
12597
12468
  cleanup();
@@ -12599,10 +12470,10 @@ class PixldocsRenderer {
12599
12470
  return;
12600
12471
  }
12601
12472
  exportCtx.save();
12602
- exportCtx.scale(exportCanvas.width / canvasWidth, exportCanvas.height / canvasHeight);
12473
+ exportCtx.scale(sourceCanvas.width / canvasWidth, sourceCanvas.height / canvasHeight);
12603
12474
  this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
12604
12475
  exportCtx.restore();
12605
- exportCtx.drawImage(sourceCanvas, 0, 0, exportCanvas.width, exportCanvas.height);
12476
+ exportCtx.drawImage(sourceCanvas, 0, 0);
12606
12477
  const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
12607
12478
  const dataUrl = exportCanvas.toDataURL(mimeType, quality);
12608
12479
  cleanup();
@@ -12618,8 +12489,9 @@ class PixldocsRenderer {
12618
12489
  react.createElement(PreviewCanvas2, {
12619
12490
  config,
12620
12491
  pageIndex,
12621
- zoom: 1,
12492
+ zoom: pixelRatio,
12622
12493
  absoluteZoom: true,
12494
+ skipFontReadyWait: true,
12623
12495
  onReady
12624
12496
  })
12625
12497
  );
@@ -12641,14 +12513,13 @@ class PixldocsRenderer {
12641
12513
  container.style.cssText = `
12642
12514
  position: fixed; left: -99999px; top: -99999px;
12643
12515
  width: ${canvasWidth}px; height: ${canvasHeight}px;
12644
- overflow: hidden; pointer-events: none; visibility: hidden;
12516
+ overflow: hidden; pointer-events: none; opacity: 0;
12645
12517
  `;
12646
12518
  document.body.appendChild(container);
12647
12519
  const timeout = setTimeout(() => {
12648
12520
  cleanup();
12649
12521
  reject(new Error("SVG render timeout (30s)"));
12650
12522
  }, 3e4);
12651
- let finished = false;
12652
12523
  const cleanup = () => {
12653
12524
  clearTimeout(timeout);
12654
12525
  try {
@@ -12658,13 +12529,9 @@ class PixldocsRenderer {
12658
12529
  container.remove();
12659
12530
  };
12660
12531
  const onReady = () => {
12661
- if (finished) return;
12662
- finished = true;
12663
12532
  this.waitForCanvasScene(container, config, pageIndex).then(async () => {
12664
12533
  var _a, _b;
12665
12534
  try {
12666
- await this.waitForStableTextMetrics(container, config, true);
12667
- await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
12668
12535
  const fabricInstance = this.getFabricCanvasFromContainer(container);
12669
12536
  if (!fabricInstance) {
12670
12537
  cleanup();
@@ -12723,6 +12590,7 @@ class PixldocsRenderer {
12723
12590
  zoom: 1,
12724
12591
  // 1:1 — no UI scaling for SVG capture
12725
12592
  absoluteZoom: true,
12593
+ skipFontReadyWait: true,
12726
12594
  onReady
12727
12595
  })
12728
12596
  );
@@ -12783,12 +12651,21 @@ class PixldocsRenderer {
12783
12651
  * using the global __fabricCanvasRegistry (set by PageCanvas).
12784
12652
  */
12785
12653
  getFabricCanvasFromContainer(container) {
12786
- return getFabricCanvasFromContainer(container);
12654
+ const registry2 = window.__fabricCanvasRegistry;
12655
+ if (registry2 instanceof Map) {
12656
+ for (const entry of registry2.values()) {
12657
+ const canvas = (entry == null ? void 0 : entry.canvas) || entry;
12658
+ if (!canvas || typeof canvas.toSVG !== "function") continue;
12659
+ const el = canvas.lowerCanvasEl || canvas.upperCanvasEl;
12660
+ if (el && container.contains(el)) return canvas;
12661
+ }
12662
+ }
12663
+ return null;
12787
12664
  }
12788
- async waitForStableTextMetrics(container, config, strictFontWait = false) {
12665
+ async waitForStableTextMetrics(container, config) {
12789
12666
  if (typeof document !== "undefined") {
12790
- await ensureFontsForResolvedConfig(config);
12791
- await this.waitForRelevantFonts(config, strictFontWait ? 6e3 : 1800);
12667
+ void ensureFontsForResolvedConfig(config);
12668
+ await this.waitForRelevantFonts(config);
12792
12669
  }
12793
12670
  const fabricInstance = this.getFabricCanvasFromContainer(container);
12794
12671
  if (!(fabricInstance == null ? void 0 : fabricInstance.getObjects)) return;
@@ -12797,7 +12674,59 @@ class PixldocsRenderer {
12797
12674
  clearFabricCharCache();
12798
12675
  clearMeasurementCache();
12799
12676
  };
12800
- const reflowTextboxes = () => stabilizeFabricTextObjects(fabricInstance);
12677
+ const reflowTextboxes = () => {
12678
+ var _a, _b, _c;
12679
+ const walk = (obj) => {
12680
+ var _a2, _b2, _c2, _d;
12681
+ if (!obj) return;
12682
+ const children = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
12683
+ if (children.length) children.forEach(walk);
12684
+ const isTextObject = typeof obj.text === "string" && typeof obj.initDimensions === "function" && (obj.type === "textbox" || obj.type === "text" || obj.type === "i-text" || obj.isEditing !== void 0);
12685
+ if (isTextObject) {
12686
+ const saved = {
12687
+ width: obj.width,
12688
+ scaleX: obj.scaleX,
12689
+ scaleY: obj.scaleY
12690
+ };
12691
+ const resetTextboxLayoutInternals = () => {
12692
+ var _a3;
12693
+ (_a3 = obj._clearCache) == null ? void 0 : _a3.call(obj);
12694
+ obj.__charBounds = [];
12695
+ obj.__lineWidths = [];
12696
+ obj.__lineHeights = [];
12697
+ obj.__graphemeLines = [];
12698
+ obj._textLines = [];
12699
+ obj.textLines = [];
12700
+ obj._styleMap = null;
12701
+ obj.styleMap = null;
12702
+ obj.dirty = true;
12703
+ };
12704
+ resetTextboxLayoutInternals();
12705
+ obj.initDimensions();
12706
+ if (saved.width != null) {
12707
+ (_a2 = obj.set) == null ? void 0 : _a2.call(obj, {
12708
+ width: saved.width,
12709
+ scaleX: saved.scaleX,
12710
+ scaleY: saved.scaleY
12711
+ });
12712
+ resetTextboxLayoutInternals();
12713
+ obj.initDimensions();
12714
+ }
12715
+ (_b2 = obj.set) == null ? void 0 : _b2.call(obj, {
12716
+ width: saved.width,
12717
+ scaleX: saved.scaleX,
12718
+ scaleY: saved.scaleY,
12719
+ dirty: true
12720
+ });
12721
+ (_c2 = obj._clearCache) == null ? void 0 : _c2.call(obj);
12722
+ (_d = obj.setCoords) == null ? void 0 : _d.call(obj);
12723
+ }
12724
+ };
12725
+ fabricInstance.getObjects().forEach(walk);
12726
+ (_a = fabricInstance.calcOffset) == null ? void 0 : _a.call(fabricInstance);
12727
+ (_b = fabricInstance.renderAll) == null ? void 0 : _b.call(fabricInstance);
12728
+ (_c = fabricInstance.requestRenderAll) == null ? void 0 : _c.call(fabricInstance);
12729
+ };
12801
12730
  clearTextMetricCaches();
12802
12731
  await waitForPaint();
12803
12732
  reflowTextboxes();
@@ -13531,6 +13460,9 @@ function rewriteSvgFontsForJsPDF(svgStr) {
13531
13460
  const resolved = resolveFontWeight(weight);
13532
13461
  const isItalic = /italic|oblique/i.test(styleRaw);
13533
13462
  const jsPdfName = getEmbeddedJsPDFFontName(clean, resolved, isItalic);
13463
+ el.setAttribute("data-source-font-family", clean);
13464
+ el.setAttribute("data-source-font-weight", String(resolved));
13465
+ el.setAttribute("data-source-font-style", isItalic ? "italic" : "normal");
13534
13466
  const directText = Array.from(el.childNodes).filter((n) => n.nodeType === 3).map((n) => n.textContent || "").join("");
13535
13467
  const hasDevanagari = containsDevanagari(directText);
13536
13468
  if (hasDevanagari && directText.length > 0) {
@@ -14512,24 +14444,89 @@ async function svg2pdfWithDomMount(svg, pdf, opts) {
14512
14444
  if (wrap.parentNode) document.body.removeChild(wrap);
14513
14445
  }
14514
14446
  }
14515
- function convertTextDecorationsToLines(svg) {
14447
+ async function convertTextDecorationsToLines(svg) {
14516
14448
  const doc = svg.ownerDocument;
14517
14449
  if (!doc) return;
14518
- let measureCanvas = null;
14450
+ const resolveInheritedSvgValue = (el, attr, styleProp = attr) => {
14451
+ var _a, _b;
14452
+ let current = el;
14453
+ while (current) {
14454
+ const attrValue = (_a = current.getAttribute(attr)) == null ? void 0 : _a.trim();
14455
+ if (attrValue) return attrValue;
14456
+ const styleValue = (_b = getInlineStyleValue(current, styleProp)) == null ? void 0 : _b.trim();
14457
+ if (styleValue) return styleValue;
14458
+ current = current.parentElement;
14459
+ }
14460
+ return null;
14461
+ };
14462
+ if (typeof document !== "undefined" && document.fonts) {
14463
+ const fontFamilies = /* @__PURE__ */ new Set();
14464
+ for (const textEl of svg.querySelectorAll("text")) {
14465
+ const ff = textEl.getAttribute("data-source-font-family") || textEl.getAttribute("font-family");
14466
+ if (ff) fontFamilies.add(ff.replace(/'/g, ""));
14467
+ for (const tspan of textEl.querySelectorAll("tspan")) {
14468
+ const tff = tspan.getAttribute("data-source-font-family") || tspan.getAttribute("font-family");
14469
+ if (tff) fontFamilies.add(tff.replace(/'/g, ""));
14470
+ }
14471
+ }
14472
+ await Promise.all(Array.from(fontFamilies).flatMap((ff) => [
14473
+ document.fonts.load(`16px "${ff}"`).then(() => {
14474
+ }).catch(() => {
14475
+ }),
14476
+ document.fonts.load(`bold 16px "${ff}"`).then(() => {
14477
+ }).catch(() => {
14478
+ })
14479
+ ]));
14480
+ await document.fonts.ready;
14481
+ }
14482
+ let tempContainer = null;
14483
+ let liveSvg = null;
14484
+ try {
14485
+ if (typeof document !== "undefined") {
14486
+ tempContainer = document.createElement("div");
14487
+ tempContainer.style.cssText = "position:fixed;left:-9999px;top:-9999px;visibility:hidden;pointer-events:none;";
14488
+ document.body.appendChild(tempContainer);
14489
+ const clone = svg.cloneNode(true);
14490
+ for (const textNode of clone.querySelectorAll("text, tspan, textPath")) {
14491
+ const sourceFamily = textNode.getAttribute("data-source-font-family");
14492
+ const sourceWeight = textNode.getAttribute("data-source-font-weight");
14493
+ const sourceStyle = textNode.getAttribute("data-source-font-style");
14494
+ if (sourceFamily) textNode.setAttribute("font-family", sourceFamily);
14495
+ if (sourceWeight) textNode.setAttribute("font-weight", sourceWeight);
14496
+ if (sourceStyle) textNode.setAttribute("font-style", sourceStyle);
14497
+ const inlineStyle = textNode.getAttribute("style") || "";
14498
+ 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));
14499
+ if (sourceFamily) stylePairs.push(`font-family: ${sourceFamily}`);
14500
+ if (sourceWeight) stylePairs.push(`font-weight: ${sourceWeight}`);
14501
+ if (sourceStyle) stylePairs.push(`font-style: ${sourceStyle}`);
14502
+ if (stylePairs.length > 0) textNode.setAttribute("style", stylePairs.join("; "));
14503
+ }
14504
+ tempContainer.appendChild(clone);
14505
+ liveSvg = clone;
14506
+ }
14507
+ } catch {
14508
+ }
14519
14509
  let ctx = null;
14520
14510
  try {
14521
- measureCanvas = doc.createElement("canvas");
14511
+ const realDoc = typeof document !== "undefined" ? document : doc;
14512
+ const measureCanvas = realDoc.createElement("canvas");
14522
14513
  ctx = measureCanvas.getContext("2d");
14523
14514
  } catch {
14524
14515
  }
14525
14516
  const textEls = Array.from(svg.querySelectorAll("text"));
14526
- for (const textEl of textEls) {
14517
+ const liveTextEls = liveSvg ? Array.from(liveSvg.querySelectorAll("text")) : null;
14518
+ for (let ti = 0; ti < textEls.length; ti++) {
14519
+ const textEl = textEls[ti];
14520
+ const liveTextEl = liveTextEls == null ? void 0 : liveTextEls[ti];
14527
14521
  const tspans = Array.from(textEl.querySelectorAll("tspan"));
14528
14522
  if (tspans.length === 0) continue;
14523
+ const liveTspans = liveTextEl ? Array.from(liveTextEl.querySelectorAll("tspan")) : null;
14529
14524
  const textDecOnText = (textEl.getAttribute("text-decoration") || "").toLowerCase();
14530
14525
  const textStyleDec = (getInlineStyleValue(textEl, "text-decoration") || "").toLowerCase();
14531
14526
  const textHasUnderline = textDecOnText.includes("underline") || textStyleDec.includes("underline");
14532
- for (const tspan of tspans) {
14527
+ for (let si = 0; si < tspans.length; si++) {
14528
+ const tspan = tspans[si];
14529
+ const liveTspan = liveTspans == null ? void 0 : liveTspans[si];
14533
14530
  const tspanDec = (tspan.getAttribute("text-decoration") || "").toLowerCase();
14534
14531
  const tspanStyleDec = (getInlineStyleValue(tspan, "text-decoration") || "").toLowerCase();
14535
14532
  const hasUnderline = tspanDec.includes("underline") || tspanStyleDec.includes("underline") || textHasUnderline;
@@ -14543,41 +14540,73 @@ function convertTextDecorationsToLines(svg) {
14543
14540
  const fontSize = parseFloat(
14544
14541
  tspan.getAttribute("font-size") || textEl.getAttribute("font-size") || "16"
14545
14542
  );
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";
14543
+ const fontFamily = tspan.getAttribute("data-source-font-family") || textEl.getAttribute("data-source-font-family") || resolveInheritedSvgValue(tspan, "font-family") || "sans-serif";
14544
+ const fontWeight = tspan.getAttribute("data-source-font-weight") || textEl.getAttribute("data-source-font-weight") || resolveInheritedSvgValue(tspan, "font-weight") || "normal";
14545
+ const fontStyle = tspan.getAttribute("data-source-font-style") || textEl.getAttribute("data-source-font-style") || resolveInheritedSvgValue(tspan, "font-style") || "normal";
14548
14546
  const fill = tspan.getAttribute("fill") || textEl.getAttribute("fill") || "#000000";
14549
- let textWidth = 0;
14550
- if (typeof tspan.getComputedTextLength === "function") {
14547
+ let textWidth = content.length * fontSize * 0.6;
14548
+ if (ctx) {
14549
+ ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily.replace(/'/g, "")}`;
14550
+ textWidth = ctx.measureText(content).width;
14551
+ }
14552
+ const textAnchorRaw = (resolveInheritedSvgValue(tspan, "text-anchor") || "start").toLowerCase();
14553
+ const textAnchor = textAnchorRaw === "middle" || textAnchorRaw === "end" ? textAnchorRaw : "start";
14554
+ let lineStartX = x - (textAnchor === "middle" ? textWidth / 2 : textAnchor === "end" ? textWidth : 0);
14555
+ let lineEndX = lineStartX + textWidth;
14556
+ let baselineY = y;
14557
+ if (liveTspan) {
14551
14558
  try {
14552
- textWidth = tspan.getComputedTextLength();
14559
+ const liveCharCount = liveTspan.getNumberOfChars();
14560
+ const svgWidth = liveTspan.getComputedTextLength();
14561
+ if (Number.isFinite(svgWidth) && svgWidth > 0) {
14562
+ textWidth = Math.max(textWidth, svgWidth);
14563
+ }
14564
+ if (liveCharCount > 0) {
14565
+ const start = liveTspan.getStartPositionOfChar(0);
14566
+ const end = liveTspan.getEndPositionOfChar(liveCharCount - 1);
14567
+ if (Number.isFinite(start.x)) lineStartX = start.x;
14568
+ if (Number.isFinite(start.y)) baselineY = start.y;
14569
+ if (Number.isFinite(end.x)) lineEndX = Math.max(end.x, lineStartX + textWidth);
14570
+ if (Number.isFinite(end.y) && !Number.isFinite(baselineY)) baselineY = end.y;
14571
+ } else {
14572
+ lineEndX = lineStartX + textWidth;
14573
+ }
14574
+ const bbox = liveTspan.getBBox();
14575
+ if (Number.isFinite(bbox.x) && Number.isFinite(bbox.width)) {
14576
+ lineStartX = Math.min(lineStartX, bbox.x);
14577
+ lineEndX = Math.max(lineEndX, bbox.x + bbox.width);
14578
+ }
14553
14579
  } catch {
14554
14580
  }
14555
14581
  }
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
- }
14582
+ if (!(lineEndX > lineStartX)) {
14583
+ lineStartX = x - (textAnchor === "middle" ? textWidth / 2 : textAnchor === "end" ? textWidth : 0);
14584
+ lineEndX = lineStartX + textWidth;
14563
14585
  }
14564
- const underlineY = y + fontSize * 0.15;
14586
+ const underlineY = baselineY + fontSize * 0.15;
14565
14587
  const thickness = Math.max(0.5, fontSize * 0.066667);
14566
14588
  const line = doc.createElementNS("http://www.w3.org/2000/svg", "line");
14567
- line.setAttribute("x1", String(x));
14589
+ line.setAttribute("x1", String(lineStartX));
14568
14590
  line.setAttribute("y1", String(underlineY));
14569
- line.setAttribute("x2", String(x + textWidth));
14591
+ line.setAttribute("x2", String(lineEndX));
14570
14592
  line.setAttribute("y2", String(underlineY));
14571
14593
  line.setAttribute("stroke", fill.startsWith("url(") ? "#000000" : fill);
14572
14594
  line.setAttribute("stroke-width", String(thickness));
14595
+ line.setAttribute("stroke-linecap", "butt");
14573
14596
  line.setAttribute("fill", "none");
14574
14597
  if (textEl.parentElement) {
14575
14598
  textEl.parentElement.insertBefore(line, textEl.nextSibling);
14576
14599
  }
14577
14600
  }
14578
14601
  }
14602
+ if (tempContainer) {
14603
+ try {
14604
+ document.body.removeChild(tempContainer);
14605
+ } catch {
14606
+ }
14607
+ }
14579
14608
  }
14580
- function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, options) {
14609
+ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, options) {
14581
14610
  try {
14582
14611
  const parser = new DOMParser();
14583
14612
  const processedSvg = inlineNestedSvgImageDataUris(rawSvg, parser);
@@ -14606,7 +14635,6 @@ function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey, opti
14606
14635
  stripSuspiciousFullPageOverlayNodes(svgToDraw);
14607
14636
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
14608
14637
  sanitizeSvgTreeForPdf(svgToDraw);
14609
- convertTextDecorationsToLines(svgToDraw);
14610
14638
  return svgToDraw;
14611
14639
  } catch {
14612
14640
  return null;
@@ -14709,7 +14737,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
14709
14737
  const hasGradient = !!((_b = (_a = page.backgroundGradient) == null ? void 0 : _a.stops) == null ? void 0 : _b.length);
14710
14738
  drawPageBackground(pdf, i, page.width, page.height, page.backgroundColor, page.backgroundGradient);
14711
14739
  const shouldStripBg = stripPageBackground ?? hasGradient;
14712
- let processedSvg = prepareLiveCanvasSvgForPdf(page.svg, page.width, page.height, `page-${i + 1}`, {
14740
+ let processedSvg = await prepareLiveCanvasSvgForPdf(page.svg, page.width, page.height, `page-${i + 1}`, {
14713
14741
  stripPageBackground: shouldStripBg
14714
14742
  });
14715
14743
  if (processedSvg) {
@@ -14718,6 +14746,7 @@ async function assemblePdfFromSvgs(svgResults, options = {}) {
14718
14746
  const reParser = new DOMParser();
14719
14747
  const reDoc = reParser.parseFromString(rewrittenSvg, "image/svg+xml");
14720
14748
  processedSvg = reDoc.documentElement;
14749
+ await convertTextDecorationsToLines(processedSvg);
14721
14750
  }
14722
14751
  if (processedSvg) {
14723
14752
  pdf.setFillColor(0, 0, 0);