@pixldocs/canvas-renderer 0.3.19 → 0.3.21

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
@@ -2662,6 +2662,17 @@ function isPrivateUrl(url) {
2662
2662
  }
2663
2663
  }
2664
2664
  function toPublicStorageUrl(url) {
2665
+ try {
2666
+ const parsed = new URL(url);
2667
+ const origin = parsed.origin;
2668
+ const signedMatch = parsed.pathname.match(/\/storage\/v1\/object\/sign\/([^?]+)/);
2669
+ if (signedMatch) {
2670
+ return `${origin}/storage/v1/object/public/${signedMatch[1]}`;
2671
+ }
2672
+ if (parsed.pathname.includes("/storage/v1/object/public/")) return url;
2673
+ } catch {
2674
+ return null;
2675
+ }
2665
2676
  return null;
2666
2677
  }
2667
2678
  function getProxiedImageUrl(imageUrl) {
@@ -2671,7 +2682,7 @@ function getProxiedImageUrl(imageUrl) {
2671
2682
  console.warn("[image-proxy] Skipping private URL:", imageUrl.substring(0, 80));
2672
2683
  return "";
2673
2684
  }
2674
- const publicUrl = toPublicStorageUrl();
2685
+ const publicUrl = toPublicStorageUrl(imageUrl);
2675
2686
  if (publicUrl) return publicUrl;
2676
2687
  return `${API_URL}/image-proxy?url=${encodeURIComponent(imageUrl)}`;
2677
2688
  }
@@ -10702,6 +10713,58 @@ class PixldocsRenderer {
10702
10713
  }
10703
10714
  return this.renderAllPages(configToRender, renderOpts);
10704
10715
  }
10716
+ /**
10717
+ * Render a page and capture the Fabric canvas SVG output (vector, not raster).
10718
+ * This is the key building block for client-side vector PDF export.
10719
+ */
10720
+ async renderPageSvg(templateConfig, pageIndex = 0) {
10721
+ const page = templateConfig.pages[pageIndex];
10722
+ if (!page) {
10723
+ throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
10724
+ }
10725
+ await ensureFontsForResolvedConfig(templateConfig);
10726
+ const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
10727
+ setPackageApiUrl2(this.config.imageProxyUrl);
10728
+ const canvasWidth = templateConfig.canvas.width;
10729
+ const canvasHeight = templateConfig.canvas.height;
10730
+ return this.captureSvgViaPreviewCanvas(templateConfig, pageIndex, canvasWidth, canvasHeight);
10731
+ }
10732
+ /**
10733
+ * Render all pages and return SVG strings for each.
10734
+ */
10735
+ async renderAllPageSvgs(templateConfig) {
10736
+ await ensureFontsForResolvedConfig(templateConfig);
10737
+ const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
10738
+ setPackageApiUrl2(this.config.imageProxyUrl);
10739
+ const results = [];
10740
+ for (let i = 0; i < templateConfig.pages.length; i++) {
10741
+ const canvasWidth = templateConfig.canvas.width;
10742
+ const canvasHeight = templateConfig.canvas.height;
10743
+ results.push(await this.captureSvgViaPreviewCanvas(templateConfig, i, canvasWidth, canvasHeight));
10744
+ }
10745
+ return results;
10746
+ }
10747
+ /**
10748
+ * Resolve from V2 sectionState and return SVGs for all pages (for server vector PDF).
10749
+ */
10750
+ async renderSvgsFromForm(options) {
10751
+ const { templateId, formSchemaId, sectionState, themeId, watermark } = options;
10752
+ const resolved = await resolveFromForm({
10753
+ templateId,
10754
+ formSchemaId,
10755
+ sectionState,
10756
+ themeId,
10757
+ supabaseUrl: this.config.supabaseUrl,
10758
+ supabaseAnonKey: this.config.supabaseAnonKey
10759
+ });
10760
+ const shouldWatermark = watermark ?? resolved.price > 0;
10761
+ let configToRender = resolved.config;
10762
+ if (shouldWatermark) {
10763
+ const { injectWatermark } = await Promise.resolve().then(() => require("./canvasWatermark-DAZIQ_IR.cjs"));
10764
+ configToRender = injectWatermark(configToRender);
10765
+ }
10766
+ return this.renderAllPageSvgs(configToRender);
10767
+ }
10705
10768
  /**
10706
10769
  * Convenience: fetch by ID with simple flat data and render.
10707
10770
  */
@@ -10714,6 +10777,18 @@ class PixldocsRenderer {
10714
10777
  });
10715
10778
  return this.render(resolved.config, options);
10716
10779
  }
10780
+ /**
10781
+ * Convenience: fetch by ID with flat data and render ALL pages.
10782
+ */
10783
+ async renderAllById(templateId, formData, options) {
10784
+ const resolved = await resolveTemplateData({
10785
+ templateId,
10786
+ formData,
10787
+ supabaseUrl: this.config.supabaseUrl,
10788
+ supabaseAnonKey: this.config.supabaseAnonKey
10789
+ });
10790
+ return this.renderAllPages(resolved.config, options);
10791
+ }
10717
10792
  // ─── Internal: render a page using the full PreviewCanvas engine ───
10718
10793
  getExpectedImageCount(config, pageIndex) {
10719
10794
  const page = config.pages[pageIndex];
@@ -10737,6 +10812,7 @@ class PixldocsRenderer {
10737
10812
  return new Promise((resolve) => {
10738
10813
  const start = Date.now();
10739
10814
  let stableFrames = 0;
10815
+ let lastSummary = "";
10740
10816
  const isRenderableImage = (value) => value instanceof HTMLImageElement && value.complete && value.naturalWidth > 0 && value.naturalHeight > 0;
10741
10817
  const collectRenderableImages = (obj, seen) => {
10742
10818
  if (!obj || typeof obj !== "object") return;
@@ -10766,6 +10842,25 @@ class PixldocsRenderer {
10766
10842
  return null;
10767
10843
  };
10768
10844
  const settle = () => requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
10845
+ const getImageDebugInfo = (obj, bucket) => {
10846
+ if (!obj || typeof obj !== "object") return;
10847
+ const candidates = [obj._element, obj._originalElement, obj._filteredEl, obj._cacheCanvasEl];
10848
+ for (const candidate of candidates) {
10849
+ if (candidate instanceof HTMLImageElement) {
10850
+ bucket.push({
10851
+ id: obj.__docuforgeId || obj.id || "unknown",
10852
+ src: (candidate.currentSrc || candidate.src || "").slice(0, 240),
10853
+ complete: candidate.complete,
10854
+ naturalWidth: candidate.naturalWidth,
10855
+ naturalHeight: candidate.naturalHeight
10856
+ });
10857
+ }
10858
+ }
10859
+ const nested = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
10860
+ for (const child of nested) {
10861
+ getImageDebugInfo(child, bucket);
10862
+ }
10863
+ };
10769
10864
  const check = () => {
10770
10865
  const elapsed = Date.now() - start;
10771
10866
  const domImages = Array.from(container.querySelectorAll("img"));
@@ -10775,12 +10870,18 @@ class PixldocsRenderer {
10775
10870
  const renderableImages = /* @__PURE__ */ new Set();
10776
10871
  const fabricReady = fabricObjects.every((obj) => collectRenderableImages(obj, renderableImages) !== false);
10777
10872
  const actualImageCount = Math.max(domImages.length, renderableImages.size);
10778
- !!fabricCanvas && !!(fabricCanvas.lowerCanvasEl || fabricCanvas.upperCanvasEl);
10873
+ const canvasReady = !!fabricCanvas && !!(fabricCanvas.lowerCanvasEl || fabricCanvas.upperCanvasEl);
10779
10874
  const hasExpectedAssets = expectedImageCount === 0 ? true : actualImageCount >= expectedImageCount;
10780
10875
  const ready = allDomLoaded && fabricReady && hasExpectedAssets;
10876
+ const summary = `expected=${expectedImageCount} actual=${actualImageCount} dom=${domImages.length} fabricReady=${fabricReady} domReady=${allDomLoaded} canvasReady=${canvasReady}`;
10877
+ if (summary !== lastSummary) {
10878
+ lastSummary = summary;
10879
+ console.log(`[canvas-renderer][asset-wait] ${summary}`);
10880
+ }
10781
10881
  if (ready) {
10782
10882
  stableFrames += 1;
10783
10883
  if (stableFrames >= 2) {
10884
+ console.log(`[canvas-renderer][asset-wait] ready after ${elapsed}ms (${summary})`);
10784
10885
  settle();
10785
10886
  return;
10786
10887
  }
@@ -10788,6 +10889,20 @@ class PixldocsRenderer {
10788
10889
  stableFrames = 0;
10789
10890
  }
10790
10891
  if (elapsed >= maxWaitMs) {
10892
+ const fabricImageDebug = [];
10893
+ for (const obj of fabricObjects) {
10894
+ getImageDebugInfo(obj, fabricImageDebug);
10895
+ }
10896
+ const domImageDebug = domImages.map((img, index) => ({
10897
+ index,
10898
+ src: (img.currentSrc || img.src || "").slice(0, 240),
10899
+ complete: img.complete,
10900
+ naturalWidth: img.naturalWidth,
10901
+ naturalHeight: img.naturalHeight
10902
+ }));
10903
+ console.warn(`[canvas-renderer][asset-wait-timeout] elapsed=${elapsed}ms ${summary}`);
10904
+ console.warn("[canvas-renderer][asset-wait-timeout][dom-images]", domImageDebug);
10905
+ console.warn("[canvas-renderer][asset-wait-timeout][fabric-images]", fabricImageDebug);
10791
10906
  settle();
10792
10907
  return;
10793
10908
  }
@@ -10941,6 +11056,93 @@ class PixldocsRenderer {
10941
11056
  );
10942
11057
  });
10943
11058
  }
11059
+ // ─── Internal: capture SVG from a rendered Fabric canvas ───
11060
+ captureSvgViaPreviewCanvas(config, pageIndex, canvasWidth, canvasHeight) {
11061
+ return new Promise(async (resolve, reject) => {
11062
+ const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
11063
+ const container = document.createElement("div");
11064
+ container.style.cssText = `
11065
+ position: fixed; left: -99999px; top: -99999px;
11066
+ width: ${canvasWidth}px; height: ${canvasHeight}px;
11067
+ overflow: hidden; pointer-events: none; opacity: 0;
11068
+ `;
11069
+ document.body.appendChild(container);
11070
+ const timeout = setTimeout(() => {
11071
+ cleanup();
11072
+ reject(new Error("SVG render timeout (30s)"));
11073
+ }, 3e4);
11074
+ const cleanup = () => {
11075
+ clearTimeout(timeout);
11076
+ try {
11077
+ root.unmount();
11078
+ } catch {
11079
+ }
11080
+ container.remove();
11081
+ };
11082
+ const onReady = () => {
11083
+ const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
11084
+ this.waitForCanvasImages(container, expectedImageCount).then(() => {
11085
+ var _a, _b;
11086
+ try {
11087
+ const fabricInstance = this.getFabricCanvasFromContainer(container);
11088
+ if (!fabricInstance) {
11089
+ cleanup();
11090
+ reject(new Error("No Fabric canvas instance found for SVG capture"));
11091
+ return;
11092
+ }
11093
+ const prevVPT = fabricInstance.viewportTransform ? [...fabricInstance.viewportTransform] : void 0;
11094
+ const prevSvgVPT = fabricInstance.svgViewportTransformation;
11095
+ fabricInstance.viewportTransform = [1, 0, 0, 1, 0, 0];
11096
+ fabricInstance.svgViewportTransformation = false;
11097
+ const svgString = fabricInstance.toSVG();
11098
+ if (prevVPT) fabricInstance.viewportTransform = prevVPT;
11099
+ fabricInstance.svgViewportTransformation = prevSvgVPT;
11100
+ const page = config.pages[pageIndex];
11101
+ const backgroundColor = ((_a = page == null ? void 0 : page.settings) == null ? void 0 : _a.backgroundColor) || "#ffffff";
11102
+ const backgroundGradient = (_b = page == null ? void 0 : page.settings) == null ? void 0 : _b.backgroundGradient;
11103
+ cleanup();
11104
+ resolve({
11105
+ svg: svgString,
11106
+ width: canvasWidth,
11107
+ height: canvasHeight,
11108
+ backgroundColor,
11109
+ backgroundGradient
11110
+ });
11111
+ } catch (err) {
11112
+ cleanup();
11113
+ reject(err);
11114
+ }
11115
+ });
11116
+ };
11117
+ const root = client.createRoot(container);
11118
+ root.render(
11119
+ react.createElement(PreviewCanvas2, {
11120
+ config,
11121
+ pageIndex,
11122
+ zoom: 1,
11123
+ // 1:1 — no scaling for SVG capture
11124
+ absoluteZoom: true,
11125
+ onReady
11126
+ })
11127
+ );
11128
+ });
11129
+ }
11130
+ /**
11131
+ * Find the Fabric.Canvas instance that belongs to a given container element,
11132
+ * using the global __fabricCanvasRegistry (set by PageCanvas).
11133
+ */
11134
+ getFabricCanvasFromContainer(container) {
11135
+ const registry2 = window.__fabricCanvasRegistry;
11136
+ if (registry2 instanceof Map) {
11137
+ for (const entry of registry2.values()) {
11138
+ const canvas = (entry == null ? void 0 : entry.canvas) || entry;
11139
+ if (!canvas || typeof canvas.toSVG !== "function") continue;
11140
+ const el = canvas.lowerCanvasEl || canvas.upperCanvasEl;
11141
+ if (el && container.contains(el)) return canvas;
11142
+ }
11143
+ }
11144
+ return null;
11145
+ }
10944
11146
  }
10945
11147
  function collectImageUrls(config) {
10946
11148
  const urls = [];