@pixldocs/canvas-renderer 0.3.19 → 0.3.20
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 +192 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +32 -0
- package/dist/index.js +192 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
*/
|
|
@@ -10737,6 +10800,7 @@ class PixldocsRenderer {
|
|
|
10737
10800
|
return new Promise((resolve) => {
|
|
10738
10801
|
const start = Date.now();
|
|
10739
10802
|
let stableFrames = 0;
|
|
10803
|
+
let lastSummary = "";
|
|
10740
10804
|
const isRenderableImage = (value) => value instanceof HTMLImageElement && value.complete && value.naturalWidth > 0 && value.naturalHeight > 0;
|
|
10741
10805
|
const collectRenderableImages = (obj, seen) => {
|
|
10742
10806
|
if (!obj || typeof obj !== "object") return;
|
|
@@ -10766,6 +10830,25 @@ class PixldocsRenderer {
|
|
|
10766
10830
|
return null;
|
|
10767
10831
|
};
|
|
10768
10832
|
const settle = () => requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
|
10833
|
+
const getImageDebugInfo = (obj, bucket) => {
|
|
10834
|
+
if (!obj || typeof obj !== "object") return;
|
|
10835
|
+
const candidates = [obj._element, obj._originalElement, obj._filteredEl, obj._cacheCanvasEl];
|
|
10836
|
+
for (const candidate of candidates) {
|
|
10837
|
+
if (candidate instanceof HTMLImageElement) {
|
|
10838
|
+
bucket.push({
|
|
10839
|
+
id: obj.__docuforgeId || obj.id || "unknown",
|
|
10840
|
+
src: (candidate.currentSrc || candidate.src || "").slice(0, 240),
|
|
10841
|
+
complete: candidate.complete,
|
|
10842
|
+
naturalWidth: candidate.naturalWidth,
|
|
10843
|
+
naturalHeight: candidate.naturalHeight
|
|
10844
|
+
});
|
|
10845
|
+
}
|
|
10846
|
+
}
|
|
10847
|
+
const nested = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
|
|
10848
|
+
for (const child of nested) {
|
|
10849
|
+
getImageDebugInfo(child, bucket);
|
|
10850
|
+
}
|
|
10851
|
+
};
|
|
10769
10852
|
const check = () => {
|
|
10770
10853
|
const elapsed = Date.now() - start;
|
|
10771
10854
|
const domImages = Array.from(container.querySelectorAll("img"));
|
|
@@ -10775,12 +10858,18 @@ class PixldocsRenderer {
|
|
|
10775
10858
|
const renderableImages = /* @__PURE__ */ new Set();
|
|
10776
10859
|
const fabricReady = fabricObjects.every((obj) => collectRenderableImages(obj, renderableImages) !== false);
|
|
10777
10860
|
const actualImageCount = Math.max(domImages.length, renderableImages.size);
|
|
10778
|
-
!!fabricCanvas && !!(fabricCanvas.lowerCanvasEl || fabricCanvas.upperCanvasEl);
|
|
10861
|
+
const canvasReady = !!fabricCanvas && !!(fabricCanvas.lowerCanvasEl || fabricCanvas.upperCanvasEl);
|
|
10779
10862
|
const hasExpectedAssets = expectedImageCount === 0 ? true : actualImageCount >= expectedImageCount;
|
|
10780
10863
|
const ready = allDomLoaded && fabricReady && hasExpectedAssets;
|
|
10864
|
+
const summary = `expected=${expectedImageCount} actual=${actualImageCount} dom=${domImages.length} fabricReady=${fabricReady} domReady=${allDomLoaded} canvasReady=${canvasReady}`;
|
|
10865
|
+
if (summary !== lastSummary) {
|
|
10866
|
+
lastSummary = summary;
|
|
10867
|
+
console.log(`[canvas-renderer][asset-wait] ${summary}`);
|
|
10868
|
+
}
|
|
10781
10869
|
if (ready) {
|
|
10782
10870
|
stableFrames += 1;
|
|
10783
10871
|
if (stableFrames >= 2) {
|
|
10872
|
+
console.log(`[canvas-renderer][asset-wait] ready after ${elapsed}ms (${summary})`);
|
|
10784
10873
|
settle();
|
|
10785
10874
|
return;
|
|
10786
10875
|
}
|
|
@@ -10788,6 +10877,20 @@ class PixldocsRenderer {
|
|
|
10788
10877
|
stableFrames = 0;
|
|
10789
10878
|
}
|
|
10790
10879
|
if (elapsed >= maxWaitMs) {
|
|
10880
|
+
const fabricImageDebug = [];
|
|
10881
|
+
for (const obj of fabricObjects) {
|
|
10882
|
+
getImageDebugInfo(obj, fabricImageDebug);
|
|
10883
|
+
}
|
|
10884
|
+
const domImageDebug = domImages.map((img, index) => ({
|
|
10885
|
+
index,
|
|
10886
|
+
src: (img.currentSrc || img.src || "").slice(0, 240),
|
|
10887
|
+
complete: img.complete,
|
|
10888
|
+
naturalWidth: img.naturalWidth,
|
|
10889
|
+
naturalHeight: img.naturalHeight
|
|
10890
|
+
}));
|
|
10891
|
+
console.warn(`[canvas-renderer][asset-wait-timeout] elapsed=${elapsed}ms ${summary}`);
|
|
10892
|
+
console.warn("[canvas-renderer][asset-wait-timeout][dom-images]", domImageDebug);
|
|
10893
|
+
console.warn("[canvas-renderer][asset-wait-timeout][fabric-images]", fabricImageDebug);
|
|
10791
10894
|
settle();
|
|
10792
10895
|
return;
|
|
10793
10896
|
}
|
|
@@ -10941,6 +11044,93 @@ class PixldocsRenderer {
|
|
|
10941
11044
|
);
|
|
10942
11045
|
});
|
|
10943
11046
|
}
|
|
11047
|
+
// ─── Internal: capture SVG from a rendered Fabric canvas ───
|
|
11048
|
+
captureSvgViaPreviewCanvas(config, pageIndex, canvasWidth, canvasHeight) {
|
|
11049
|
+
return new Promise(async (resolve, reject) => {
|
|
11050
|
+
const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
|
|
11051
|
+
const container = document.createElement("div");
|
|
11052
|
+
container.style.cssText = `
|
|
11053
|
+
position: fixed; left: -99999px; top: -99999px;
|
|
11054
|
+
width: ${canvasWidth}px; height: ${canvasHeight}px;
|
|
11055
|
+
overflow: hidden; pointer-events: none; opacity: 0;
|
|
11056
|
+
`;
|
|
11057
|
+
document.body.appendChild(container);
|
|
11058
|
+
const timeout = setTimeout(() => {
|
|
11059
|
+
cleanup();
|
|
11060
|
+
reject(new Error("SVG render timeout (30s)"));
|
|
11061
|
+
}, 3e4);
|
|
11062
|
+
const cleanup = () => {
|
|
11063
|
+
clearTimeout(timeout);
|
|
11064
|
+
try {
|
|
11065
|
+
root.unmount();
|
|
11066
|
+
} catch {
|
|
11067
|
+
}
|
|
11068
|
+
container.remove();
|
|
11069
|
+
};
|
|
11070
|
+
const onReady = () => {
|
|
11071
|
+
const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
|
|
11072
|
+
this.waitForCanvasImages(container, expectedImageCount).then(() => {
|
|
11073
|
+
var _a, _b;
|
|
11074
|
+
try {
|
|
11075
|
+
const fabricInstance = this.getFabricCanvasFromContainer(container);
|
|
11076
|
+
if (!fabricInstance) {
|
|
11077
|
+
cleanup();
|
|
11078
|
+
reject(new Error("No Fabric canvas instance found for SVG capture"));
|
|
11079
|
+
return;
|
|
11080
|
+
}
|
|
11081
|
+
const prevVPT = fabricInstance.viewportTransform ? [...fabricInstance.viewportTransform] : void 0;
|
|
11082
|
+
const prevSvgVPT = fabricInstance.svgViewportTransformation;
|
|
11083
|
+
fabricInstance.viewportTransform = [1, 0, 0, 1, 0, 0];
|
|
11084
|
+
fabricInstance.svgViewportTransformation = false;
|
|
11085
|
+
const svgString = fabricInstance.toSVG();
|
|
11086
|
+
if (prevVPT) fabricInstance.viewportTransform = prevVPT;
|
|
11087
|
+
fabricInstance.svgViewportTransformation = prevSvgVPT;
|
|
11088
|
+
const page = config.pages[pageIndex];
|
|
11089
|
+
const backgroundColor = ((_a = page == null ? void 0 : page.settings) == null ? void 0 : _a.backgroundColor) || "#ffffff";
|
|
11090
|
+
const backgroundGradient = (_b = page == null ? void 0 : page.settings) == null ? void 0 : _b.backgroundGradient;
|
|
11091
|
+
cleanup();
|
|
11092
|
+
resolve({
|
|
11093
|
+
svg: svgString,
|
|
11094
|
+
width: canvasWidth,
|
|
11095
|
+
height: canvasHeight,
|
|
11096
|
+
backgroundColor,
|
|
11097
|
+
backgroundGradient
|
|
11098
|
+
});
|
|
11099
|
+
} catch (err) {
|
|
11100
|
+
cleanup();
|
|
11101
|
+
reject(err);
|
|
11102
|
+
}
|
|
11103
|
+
});
|
|
11104
|
+
};
|
|
11105
|
+
const root = client.createRoot(container);
|
|
11106
|
+
root.render(
|
|
11107
|
+
react.createElement(PreviewCanvas2, {
|
|
11108
|
+
config,
|
|
11109
|
+
pageIndex,
|
|
11110
|
+
zoom: 1,
|
|
11111
|
+
// 1:1 — no scaling for SVG capture
|
|
11112
|
+
absoluteZoom: true,
|
|
11113
|
+
onReady
|
|
11114
|
+
})
|
|
11115
|
+
);
|
|
11116
|
+
});
|
|
11117
|
+
}
|
|
11118
|
+
/**
|
|
11119
|
+
* Find the Fabric.Canvas instance that belongs to a given container element,
|
|
11120
|
+
* using the global __fabricCanvasRegistry (set by PageCanvas).
|
|
11121
|
+
*/
|
|
11122
|
+
getFabricCanvasFromContainer(container) {
|
|
11123
|
+
const registry2 = window.__fabricCanvasRegistry;
|
|
11124
|
+
if (registry2 instanceof Map) {
|
|
11125
|
+
for (const entry of registry2.values()) {
|
|
11126
|
+
const canvas = (entry == null ? void 0 : entry.canvas) || entry;
|
|
11127
|
+
if (!canvas || typeof canvas.toSVG !== "function") continue;
|
|
11128
|
+
const el = canvas.lowerCanvasEl || canvas.upperCanvasEl;
|
|
11129
|
+
if (el && container.contains(el)) return canvas;
|
|
11130
|
+
}
|
|
11131
|
+
}
|
|
11132
|
+
return null;
|
|
11133
|
+
}
|
|
10944
11134
|
}
|
|
10945
11135
|
function collectImageUrls(config) {
|
|
10946
11136
|
const urls = [];
|