@pixldocs/canvas-renderer 0.5.192 → 0.5.193

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/README.md CHANGED
@@ -273,9 +273,20 @@ const renderer = new PixldocsRenderer({
273
273
  supabaseAnonKey: string,
274
274
  imageProxyUrl?: string, // CORS proxy for external images
275
275
  pixelRatio?: number, // default: 2
276
+ assetWaitTimeoutMs?: number, // default: 15000; use ~30000 for multi-photo PDF exports
277
+ assetWaitEarlyExitMs?: number, // default: 1500; ignored for strict PDF image-ID waits
278
+ maxImageEdgePx?: number, // recommended: 2048 for user-uploaded PDF photos
279
+ debug?: boolean, // logs image wait/SVG completeness diagnostics
276
280
  });
277
281
  ```
278
282
 
283
+ For BioMaker-style PDFs with user-uploaded profile/gallery photos, use the live
284
+ SVG path with `maxImageEdgePx: 2048` and **do not** set
285
+ `forcePerElementPdf: true`. Since `0.5.193`, PDF export waits for every expected
286
+ bound image ID, stamps those IDs into the captured SVG, preloads/decodes them
287
+ before `svg2pdf`, and throws if any expected photo is missing instead of
288
+ silently exporting an incomplete PDF.
289
+
279
290
  #### `renderer.renderFromForm(options)`
280
291
 
281
292
  Renders all pages from a V2 sectionState payload (same format as the server `/render-from-form` API).
@@ -16782,6 +16782,23 @@ function stampFabricLineMetricsOnTextSvg(svg, obj) {
16782
16782
  return `<tspan${cleaned} data-pd-line-width="${Number(lineWidth.toFixed(3))}" data-pd-line-start="${Number(lineStart.toFixed(3))}">`;
16783
16783
  });
16784
16784
  }
16785
+ function escapeSvgDataAttr(value) {
16786
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
16787
+ }
16788
+ function hasRenderableRasterCandidate(obj) {
16789
+ if (!obj || typeof obj !== "object") return false;
16790
+ const candidates = [obj._element, obj._originalElement, obj._filteredEl, obj._cacheCanvasEl];
16791
+ for (const candidate of candidates) {
16792
+ if (candidate instanceof HTMLImageElement || candidate instanceof HTMLCanvasElement) return true;
16793
+ }
16794
+ const children = Array.isArray(obj == null ? void 0 : obj._objects) ? obj._objects : [];
16795
+ return children.some((child) => hasRenderableRasterCandidate(child));
16796
+ }
16797
+ function stampPixldocsImageIdOnSvg(svg, id) {
16798
+ if (!id || !/<image\b/i.test(svg) || /data-pixldocs-image-id=/i.test(svg)) return svg;
16799
+ const attr = ` data-pixldocs-image-id="${escapeSvgDataAttr(id)}"`;
16800
+ return svg.replace(/<image\b/i, `<image${attr}`);
16801
+ }
16785
16802
  function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight) {
16786
16803
  const prevVPT = fabricInstance.viewportTransform ? [...fabricInstance.viewportTransform] : void 0;
16787
16804
  const prevSvgVPT = fabricInstance.svgViewportTransformation;
@@ -16798,14 +16815,20 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
16798
16815
  );
16799
16816
  } catch {
16800
16817
  }
16801
- const textSvgPatchRecords = [];
16818
+ const svgPatchRecords = [];
16802
16819
  try {
16803
16820
  const visit = (obj) => {
16804
16821
  if (!obj) return;
16805
- if (isTextboxLike(obj) && typeof obj.toSVG === "function") {
16822
+ const imageId = typeof obj.__docuforgeId === "string" && hasRenderableRasterCandidate(obj) ? obj.__docuforgeId : "";
16823
+ if ((isTextboxLike(obj) || imageId) && typeof obj.toSVG === "function") {
16806
16824
  const originalToSVG = obj.toSVG.bind(obj);
16807
- obj.toSVG = (reviver) => stampFabricLineMetricsOnTextSvg(originalToSVG(reviver), obj);
16808
- textSvgPatchRecords.push({ obj, originalToSVG });
16825
+ obj.toSVG = (reviver) => {
16826
+ let svg = originalToSVG(reviver);
16827
+ if (isTextboxLike(obj)) svg = stampFabricLineMetricsOnTextSvg(svg, obj);
16828
+ if (imageId) svg = stampPixldocsImageIdOnSvg(svg, imageId);
16829
+ return svg;
16830
+ };
16831
+ svgPatchRecords.push({ obj, originalToSVG });
16809
16832
  }
16810
16833
  const children = Array.isArray(obj == null ? void 0 : obj._objects) ? obj._objects : [];
16811
16834
  for (const child of children) visit(child);
@@ -16838,6 +16861,13 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
16838
16861
  evented: false,
16839
16862
  objectCaching: false
16840
16863
  });
16864
+ if (typeof obj.__docuforgeId === "string") {
16865
+ replacement.__docuforgeId = obj.__docuforgeId;
16866
+ if (typeof replacement.toSVG === "function") {
16867
+ const originalReplacementToSVG = replacement.toSVG.bind(replacement);
16868
+ replacement.toSVG = (reviver) => stampPixldocsImageIdOnSvg(originalReplacementToSVG(reviver), obj.__docuforgeId);
16869
+ }
16870
+ }
16841
16871
  const insertIndex = fabricInstance._objects.indexOf(obj);
16842
16872
  const prevExclude = obj.excludeFromExport;
16843
16873
  obj.excludeFromExport = true;
@@ -16870,7 +16900,7 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
16870
16900
  } catch {
16871
16901
  }
16872
16902
  }
16873
- for (const rec of textSvgPatchRecords) {
16903
+ for (const rec of svgPatchRecords) {
16874
16904
  try {
16875
16905
  rec.obj.toSVG = rec.originalToSVG;
16876
16906
  } catch {
@@ -16889,9 +16919,9 @@ function captureFabricCanvasSvgForPdf(fabricInstance, canvasWidth, canvasHeight)
16889
16919
  }
16890
16920
  return svgString;
16891
16921
  }
16892
- const resolvedPackageVersion = "0.5.192";
16922
+ const resolvedPackageVersion = "0.5.193";
16893
16923
  const PACKAGE_VERSION = resolvedPackageVersion;
16894
- const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.192";
16924
+ const DEPLOYMENT_VERSION_MARKER = "__PIXLDOCS_CANVAS_RENDERER_VERSION__:0.5.193";
16895
16925
  const roundParityValue = (value) => {
16896
16926
  if (typeof value !== "number") return value;
16897
16927
  return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
@@ -17512,13 +17542,13 @@ class PixldocsRenderer {
17512
17542
  );
17513
17543
  });
17514
17544
  await this.waitForCanvasScene(container, cloned, i);
17515
- const expected = this.getExpectedImageCount(cloned, i);
17545
+ const expected = this.getExpectedImageIds(cloned, i);
17516
17546
  await this.waitForCanvasImages(container, expected);
17517
17547
  await this.waitForStableTextMetrics(container, cloned, { clearGlobalCharCache: false });
17518
17548
  await this.waitForCanvasScene(container, cloned, i);
17519
17549
  }
17520
17550
  console.log(`[canvas-renderer][pdf-unified] mounted ${cloned.pages.length} page(s), handing off to client exportMultiPagePdf`);
17521
- const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-DuVMxQSS.js");
17551
+ const { exportMultiPagePdf, preparePagesForExport } = await import("./vectorPdfExport-Cd--tBky.js");
17522
17552
  const prepared = preparePagesForExport(
17523
17553
  cloned.pages,
17524
17554
  canvasWidth,
@@ -17590,17 +17620,21 @@ class PixldocsRenderer {
17590
17620
  walk(page.children);
17591
17621
  return ids;
17592
17622
  }
17593
- waitForCanvasImages(container, expectedImageCount, maxWaitMs, pollMs = 120) {
17623
+ waitForCanvasImages(container, expectedImages, maxWaitMs, pollMs = 120) {
17594
17624
  const timeout = Math.max(500, maxWaitMs ?? this.config.assetWaitTimeoutMs ?? 15e3);
17595
17625
  const earlyExitMs = Math.max(250, this.config.assetWaitEarlyExitMs ?? 1500);
17596
17626
  const debug = !!this.config.debug;
17597
- return new Promise((resolve) => {
17627
+ const expectedImageIds = Array.isArray(expectedImages) ? [...new Set(expectedImages)] : [];
17628
+ const expectedImageCount = Array.isArray(expectedImages) ? expectedImageIds.length : expectedImages;
17629
+ const strictById = expectedImageIds.length > 0;
17630
+ return new Promise((resolve, reject) => {
17598
17631
  const start = Date.now();
17599
17632
  let stableFrames = 0;
17600
17633
  let lastSummary = "";
17601
- let lastActual = -1;
17634
+ let lastProgressKey = "";
17602
17635
  let lastProgressAt = Date.now();
17603
17636
  const isRenderableImage = (value) => value instanceof HTMLImageElement && value.complete && value.naturalWidth > 0 && value.naturalHeight > 0;
17637
+ const isRenderableCanvas = (value) => value instanceof HTMLCanvasElement && value.width > 0 && value.height > 0;
17604
17638
  const collectRenderableImages = (obj, seen) => {
17605
17639
  if (!obj || typeof obj !== "object") return;
17606
17640
  const candidates = [obj._element, obj._originalElement, obj._filteredEl, obj._cacheCanvasEl];
@@ -17617,6 +17651,25 @@ class PixldocsRenderer {
17617
17651
  }
17618
17652
  return true;
17619
17653
  };
17654
+ const collectRenderableImageIds = (obj, readyIds) => {
17655
+ if (!obj || typeof obj !== "object") return { loaded: false, pending: false };
17656
+ let loaded = false;
17657
+ let pending = false;
17658
+ const candidates = [obj._element, obj._originalElement, obj._filteredEl, obj._cacheCanvasEl];
17659
+ for (const candidate of candidates) {
17660
+ if (isRenderableImage(candidate) || isRenderableCanvas(candidate)) loaded = true;
17661
+ else if (candidate instanceof HTMLImageElement) pending = true;
17662
+ }
17663
+ const nested = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
17664
+ for (const child of nested) {
17665
+ const childState = collectRenderableImageIds(child, readyIds);
17666
+ loaded = loaded || childState.loaded;
17667
+ pending = pending || childState.pending;
17668
+ }
17669
+ const id = typeof obj.__docuforgeId === "string" ? obj.__docuforgeId : "";
17670
+ if (id && loaded && !pending) readyIds.add(id);
17671
+ return { loaded, pending };
17672
+ };
17620
17673
  const getFabricCanvas = () => {
17621
17674
  const registry2 = window.__fabricCanvasRegistry;
17622
17675
  if (registry2 instanceof Map) {
@@ -17656,17 +17709,21 @@ class PixldocsRenderer {
17656
17709
  const fabricObjects = fabricCanvas && typeof fabricCanvas.getObjects === "function" ? fabricCanvas.getObjects() : [];
17657
17710
  const renderableImages = /* @__PURE__ */ new Set();
17658
17711
  const fabricReady = fabricObjects.every((obj) => collectRenderableImages(obj, renderableImages) !== false);
17712
+ const renderableImageIds = /* @__PURE__ */ new Set();
17713
+ for (const obj of fabricObjects) collectRenderableImageIds(obj, renderableImageIds);
17714
+ const missingImageIds = strictById ? expectedImageIds.filter((id) => !renderableImageIds.has(id)) : [];
17659
17715
  const actualImageCount = Math.max(domImages.length, renderableImages.size);
17660
17716
  const canvasReady = !!fabricCanvas && !!(fabricCanvas.lowerCanvasEl || fabricCanvas.upperCanvasEl);
17661
- const hasExpectedAssets = expectedImageCount === 0 ? true : actualImageCount >= expectedImageCount;
17717
+ const hasExpectedAssets = strictById ? missingImageIds.length === 0 : expectedImageCount === 0 ? true : actualImageCount >= expectedImageCount;
17662
17718
  const ready = allDomLoaded && fabricReady && hasExpectedAssets;
17663
- const summary = `expected=${expectedImageCount} actual=${actualImageCount} dom=${domImages.length} fabricReady=${fabricReady} domReady=${allDomLoaded} canvasReady=${canvasReady}`;
17719
+ const summary = `expected=${expectedImageCount} actual=${actualImageCount} fabricIds=${renderableImageIds.size} dom=${domImages.length} fabricReady=${fabricReady} domReady=${allDomLoaded} canvasReady=${canvasReady}${missingImageIds.length ? ` missing=${missingImageIds.join(",")}` : ""}`;
17664
17720
  if (summary !== lastSummary) {
17665
17721
  lastSummary = summary;
17666
17722
  if (debug) console.log(`[canvas-renderer][asset-wait] ${summary}`);
17667
17723
  }
17668
- if (actualImageCount !== lastActual) {
17669
- lastActual = actualImageCount;
17724
+ const progressKey = strictById ? expectedImageIds.filter((id) => renderableImageIds.has(id)).join("|") : String(actualImageCount);
17725
+ if (progressKey !== lastProgressKey) {
17726
+ lastProgressKey = progressKey;
17670
17727
  lastProgressAt = Date.now();
17671
17728
  }
17672
17729
  if (ready) {
@@ -17681,7 +17738,7 @@ class PixldocsRenderer {
17681
17738
  }
17682
17739
  const sceneSettled = fabricReady && allDomLoaded && canvasReady && fabricObjects.length > 0;
17683
17740
  const idleFor = Date.now() - lastProgressAt;
17684
- if (sceneSettled && actualImageCount < expectedImageCount && idleFor >= earlyExitMs) {
17741
+ if (!strictById && sceneSettled && actualImageCount < expectedImageCount && idleFor >= earlyExitMs) {
17685
17742
  if (debug) {
17686
17743
  console.log(
17687
17744
  `[canvas-renderer][asset-wait] early-exit after ${elapsed}ms (idle=${idleFor}ms, ${summary})`
@@ -17705,6 +17762,10 @@ class PixldocsRenderer {
17705
17762
  console.warn(`[canvas-renderer][asset-wait-timeout] elapsed=${elapsed}ms ${summary}`);
17706
17763
  console.warn("[canvas-renderer][asset-wait-timeout][dom-images]", domImageDebug);
17707
17764
  console.warn("[canvas-renderer][asset-wait-timeout][fabric-images]", fabricImageDebug);
17765
+ if (strictById && missingImageIds.length > 0) {
17766
+ reject(new Error(`[canvas-renderer][asset-wait-timeout] Missing expected image(s): ${missingImageIds.join(", ")}`));
17767
+ return;
17768
+ }
17708
17769
  settle();
17709
17770
  return;
17710
17771
  }
@@ -17905,8 +17966,8 @@ class PixldocsRenderer {
17905
17966
  const onReadyOnce = () => {
17906
17967
  this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
17907
17968
  const fabricInstance = this.getFabricCanvasFromContainer(container);
17908
- const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
17909
- await this.waitForCanvasImages(container, expectedImageCount);
17969
+ const expectedImageIds = this.getExpectedImageIds(renderConfig, pageIndex);
17970
+ await this.waitForCanvasImages(container, expectedImageIds);
17910
17971
  await this.waitForStableTextMetrics(container, renderConfig);
17911
17972
  await this.waitForCanvasScene(container, renderConfig, pageIndex);
17912
17973
  if (!fabricInstance) return settle();
@@ -17932,8 +17993,8 @@ class PixldocsRenderer {
17932
17993
  this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
17933
17994
  try {
17934
17995
  const fabricInstance = this.getFabricCanvasFromContainer(container);
17935
- const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
17936
- await this.waitForCanvasImages(container, expectedImageCount);
17996
+ const expectedImageIds = this.getExpectedImageIds(renderConfig, pageIndex);
17997
+ await this.waitForCanvasImages(container, expectedImageIds);
17937
17998
  await this.waitForStableTextMetrics(container, renderConfig);
17938
17999
  await this.waitForCanvasScene(container, renderConfig, pageIndex);
17939
18000
  firstMountSettled = true;
@@ -18044,8 +18105,8 @@ class PixldocsRenderer {
18044
18105
  this.waitForCanvasScene(container, renderConfig, pageIndex).then(async () => {
18045
18106
  var _a, _b;
18046
18107
  try {
18047
- const expectedImageCount = this.getExpectedImageCount(renderConfig, pageIndex);
18048
- await this.waitForCanvasImages(container, expectedImageCount);
18108
+ const expectedImageIds = this.getExpectedImageIds(renderConfig, pageIndex);
18109
+ await this.waitForCanvasImages(container, expectedImageIds);
18049
18110
  await this.waitForStableTextMetrics(container, renderConfig);
18050
18111
  await this.waitForCanvasScene(container, renderConfig, pageIndex);
18051
18112
  const fabricInstance = this.getFabricCanvasFromContainer(container);
@@ -19684,7 +19745,7 @@ async function prepareLiveCanvasSvgForPdf(rawSvg, pageWidth, pageHeight, pageKey
19684
19745
  if (options == null ? void 0 : options.stripPageBackground) stripRootPageBackgroundFromSvg(svgToDraw);
19685
19746
  sanitizeSvgTreeForPdf(svgToDraw);
19686
19747
  try {
19687
- const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-DuVMxQSS.js");
19748
+ const { bakeTextAnchorPositionsFromLiveSvg, logTextMeasurementDiagnostic } = await import("./vectorPdfExport-Cd--tBky.js");
19688
19749
  try {
19689
19750
  await logTextMeasurementDiagnostic(svgToDraw);
19690
19751
  } catch {
@@ -20084,4 +20145,4 @@ export {
20084
20145
  buildTeaserBlurFlatKeys as y,
20085
20146
  collectFontDescriptorsFromConfig as z
20086
20147
  };
20087
- //# sourceMappingURL=index-CqgOWYwR.js.map
20148
+ //# sourceMappingURL=index-CDoyeKa8.js.map