@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 +204 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +36 -0
- package/dist/index.js +204 -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
|
*/
|
|
@@ -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 = [];
|