@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.d.ts
CHANGED
|
@@ -184,15 +184,38 @@ export declare class PixldocsRenderer {
|
|
|
184
184
|
* This is the primary external API for the package.
|
|
185
185
|
*/
|
|
186
186
|
renderFromForm(options: RenderFromFormOptions): Promise<RenderResult[]>;
|
|
187
|
+
/**
|
|
188
|
+
* Render a page and capture the Fabric canvas SVG output (vector, not raster).
|
|
189
|
+
* This is the key building block for client-side vector PDF export.
|
|
190
|
+
*/
|
|
191
|
+
renderPageSvg(templateConfig: TemplateConfig, pageIndex?: number): Promise<SvgRenderResult>;
|
|
192
|
+
/**
|
|
193
|
+
* Render all pages and return SVG strings for each.
|
|
194
|
+
*/
|
|
195
|
+
renderAllPageSvgs(templateConfig: TemplateConfig): Promise<SvgRenderResult[]>;
|
|
196
|
+
/**
|
|
197
|
+
* Resolve from V2 sectionState and return SVGs for all pages (for server vector PDF).
|
|
198
|
+
*/
|
|
199
|
+
renderSvgsFromForm(options: Omit<RenderFromFormOptions, 'format' | 'quality' | 'scale' | 'pixelRatio'>): Promise<SvgRenderResult[]>;
|
|
187
200
|
/**
|
|
188
201
|
* Convenience: fetch by ID with simple flat data and render.
|
|
189
202
|
*/
|
|
190
203
|
renderById(templateId: string, formData?: Record<string, any>, options?: RenderOptions): Promise<RenderResult>;
|
|
204
|
+
/**
|
|
205
|
+
* Convenience: fetch by ID with flat data and render ALL pages.
|
|
206
|
+
*/
|
|
207
|
+
renderAllById(templateId: string, formData?: Record<string, any>, options?: Omit<RenderOptions, 'pageIndex'>): Promise<RenderResult[]>;
|
|
191
208
|
private getExpectedImageCount;
|
|
192
209
|
private waitForCanvasImages;
|
|
193
210
|
private getNormalizedGradientStops;
|
|
194
211
|
private paintPageBackground;
|
|
195
212
|
private renderPageViaPreviewCanvas;
|
|
213
|
+
private captureSvgViaPreviewCanvas;
|
|
214
|
+
/**
|
|
215
|
+
* Find the Fabric.Canvas instance that belongs to a given container element,
|
|
216
|
+
* using the global __fabricCanvasRegistry (set by PageCanvas).
|
|
217
|
+
*/
|
|
218
|
+
private getFabricCanvasFromContainer;
|
|
196
219
|
}
|
|
197
220
|
|
|
198
221
|
export declare interface RendererConfig {
|
|
@@ -291,6 +314,19 @@ export { SmartElementProps }
|
|
|
291
314
|
|
|
292
315
|
export { SmartElementType }
|
|
293
316
|
|
|
317
|
+
export declare interface SvgRenderResult {
|
|
318
|
+
/** Raw SVG string from Fabric's toSVG() — clean, no viewport transforms */
|
|
319
|
+
svg: string;
|
|
320
|
+
/** Page width in CSS pixels */
|
|
321
|
+
width: number;
|
|
322
|
+
/** Page height in CSS pixels */
|
|
323
|
+
height: number;
|
|
324
|
+
/** Background color of the page */
|
|
325
|
+
backgroundColor: string;
|
|
326
|
+
/** Background gradient (if any) */
|
|
327
|
+
backgroundGradient?: any;
|
|
328
|
+
}
|
|
329
|
+
|
|
294
330
|
export declare interface TemplateConfig {
|
|
295
331
|
version?: string;
|
|
296
332
|
name?: string;
|
package/dist/index.js
CHANGED
|
@@ -2643,6 +2643,17 @@ function isPrivateUrl(url) {
|
|
|
2643
2643
|
}
|
|
2644
2644
|
}
|
|
2645
2645
|
function toPublicStorageUrl(url) {
|
|
2646
|
+
try {
|
|
2647
|
+
const parsed = new URL(url);
|
|
2648
|
+
const origin = parsed.origin;
|
|
2649
|
+
const signedMatch = parsed.pathname.match(/\/storage\/v1\/object\/sign\/([^?]+)/);
|
|
2650
|
+
if (signedMatch) {
|
|
2651
|
+
return `${origin}/storage/v1/object/public/${signedMatch[1]}`;
|
|
2652
|
+
}
|
|
2653
|
+
if (parsed.pathname.includes("/storage/v1/object/public/")) return url;
|
|
2654
|
+
} catch {
|
|
2655
|
+
return null;
|
|
2656
|
+
}
|
|
2646
2657
|
return null;
|
|
2647
2658
|
}
|
|
2648
2659
|
function getProxiedImageUrl(imageUrl) {
|
|
@@ -2652,7 +2663,7 @@ function getProxiedImageUrl(imageUrl) {
|
|
|
2652
2663
|
console.warn("[image-proxy] Skipping private URL:", imageUrl.substring(0, 80));
|
|
2653
2664
|
return "";
|
|
2654
2665
|
}
|
|
2655
|
-
const publicUrl = toPublicStorageUrl();
|
|
2666
|
+
const publicUrl = toPublicStorageUrl(imageUrl);
|
|
2656
2667
|
if (publicUrl) return publicUrl;
|
|
2657
2668
|
return `${API_URL}/image-proxy?url=${encodeURIComponent(imageUrl)}`;
|
|
2658
2669
|
}
|
|
@@ -10683,6 +10694,58 @@ class PixldocsRenderer {
|
|
|
10683
10694
|
}
|
|
10684
10695
|
return this.renderAllPages(configToRender, renderOpts);
|
|
10685
10696
|
}
|
|
10697
|
+
/**
|
|
10698
|
+
* Render a page and capture the Fabric canvas SVG output (vector, not raster).
|
|
10699
|
+
* This is the key building block for client-side vector PDF export.
|
|
10700
|
+
*/
|
|
10701
|
+
async renderPageSvg(templateConfig, pageIndex = 0) {
|
|
10702
|
+
const page = templateConfig.pages[pageIndex];
|
|
10703
|
+
if (!page) {
|
|
10704
|
+
throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
|
|
10705
|
+
}
|
|
10706
|
+
await ensureFontsForResolvedConfig(templateConfig);
|
|
10707
|
+
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
10708
|
+
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
10709
|
+
const canvasWidth = templateConfig.canvas.width;
|
|
10710
|
+
const canvasHeight = templateConfig.canvas.height;
|
|
10711
|
+
return this.captureSvgViaPreviewCanvas(templateConfig, pageIndex, canvasWidth, canvasHeight);
|
|
10712
|
+
}
|
|
10713
|
+
/**
|
|
10714
|
+
* Render all pages and return SVG strings for each.
|
|
10715
|
+
*/
|
|
10716
|
+
async renderAllPageSvgs(templateConfig) {
|
|
10717
|
+
await ensureFontsForResolvedConfig(templateConfig);
|
|
10718
|
+
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
10719
|
+
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
10720
|
+
const results = [];
|
|
10721
|
+
for (let i = 0; i < templateConfig.pages.length; i++) {
|
|
10722
|
+
const canvasWidth = templateConfig.canvas.width;
|
|
10723
|
+
const canvasHeight = templateConfig.canvas.height;
|
|
10724
|
+
results.push(await this.captureSvgViaPreviewCanvas(templateConfig, i, canvasWidth, canvasHeight));
|
|
10725
|
+
}
|
|
10726
|
+
return results;
|
|
10727
|
+
}
|
|
10728
|
+
/**
|
|
10729
|
+
* Resolve from V2 sectionState and return SVGs for all pages (for server vector PDF).
|
|
10730
|
+
*/
|
|
10731
|
+
async renderSvgsFromForm(options) {
|
|
10732
|
+
const { templateId, formSchemaId, sectionState, themeId, watermark } = options;
|
|
10733
|
+
const resolved = await resolveFromForm({
|
|
10734
|
+
templateId,
|
|
10735
|
+
formSchemaId,
|
|
10736
|
+
sectionState,
|
|
10737
|
+
themeId,
|
|
10738
|
+
supabaseUrl: this.config.supabaseUrl,
|
|
10739
|
+
supabaseAnonKey: this.config.supabaseAnonKey
|
|
10740
|
+
});
|
|
10741
|
+
const shouldWatermark = watermark ?? resolved.price > 0;
|
|
10742
|
+
let configToRender = resolved.config;
|
|
10743
|
+
if (shouldWatermark) {
|
|
10744
|
+
const { injectWatermark } = await import("./canvasWatermark-CM85x4k7.js");
|
|
10745
|
+
configToRender = injectWatermark(configToRender);
|
|
10746
|
+
}
|
|
10747
|
+
return this.renderAllPageSvgs(configToRender);
|
|
10748
|
+
}
|
|
10686
10749
|
/**
|
|
10687
10750
|
* Convenience: fetch by ID with simple flat data and render.
|
|
10688
10751
|
*/
|
|
@@ -10695,6 +10758,18 @@ class PixldocsRenderer {
|
|
|
10695
10758
|
});
|
|
10696
10759
|
return this.render(resolved.config, options);
|
|
10697
10760
|
}
|
|
10761
|
+
/**
|
|
10762
|
+
* Convenience: fetch by ID with flat data and render ALL pages.
|
|
10763
|
+
*/
|
|
10764
|
+
async renderAllById(templateId, formData, options) {
|
|
10765
|
+
const resolved = await resolveTemplateData({
|
|
10766
|
+
templateId,
|
|
10767
|
+
formData,
|
|
10768
|
+
supabaseUrl: this.config.supabaseUrl,
|
|
10769
|
+
supabaseAnonKey: this.config.supabaseAnonKey
|
|
10770
|
+
});
|
|
10771
|
+
return this.renderAllPages(resolved.config, options);
|
|
10772
|
+
}
|
|
10698
10773
|
// ─── Internal: render a page using the full PreviewCanvas engine ───
|
|
10699
10774
|
getExpectedImageCount(config, pageIndex) {
|
|
10700
10775
|
const page = config.pages[pageIndex];
|
|
@@ -10718,6 +10793,7 @@ class PixldocsRenderer {
|
|
|
10718
10793
|
return new Promise((resolve) => {
|
|
10719
10794
|
const start = Date.now();
|
|
10720
10795
|
let stableFrames = 0;
|
|
10796
|
+
let lastSummary = "";
|
|
10721
10797
|
const isRenderableImage = (value) => value instanceof HTMLImageElement && value.complete && value.naturalWidth > 0 && value.naturalHeight > 0;
|
|
10722
10798
|
const collectRenderableImages = (obj, seen) => {
|
|
10723
10799
|
if (!obj || typeof obj !== "object") return;
|
|
@@ -10747,6 +10823,25 @@ class PixldocsRenderer {
|
|
|
10747
10823
|
return null;
|
|
10748
10824
|
};
|
|
10749
10825
|
const settle = () => requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
|
10826
|
+
const getImageDebugInfo = (obj, bucket) => {
|
|
10827
|
+
if (!obj || typeof obj !== "object") return;
|
|
10828
|
+
const candidates = [obj._element, obj._originalElement, obj._filteredEl, obj._cacheCanvasEl];
|
|
10829
|
+
for (const candidate of candidates) {
|
|
10830
|
+
if (candidate instanceof HTMLImageElement) {
|
|
10831
|
+
bucket.push({
|
|
10832
|
+
id: obj.__docuforgeId || obj.id || "unknown",
|
|
10833
|
+
src: (candidate.currentSrc || candidate.src || "").slice(0, 240),
|
|
10834
|
+
complete: candidate.complete,
|
|
10835
|
+
naturalWidth: candidate.naturalWidth,
|
|
10836
|
+
naturalHeight: candidate.naturalHeight
|
|
10837
|
+
});
|
|
10838
|
+
}
|
|
10839
|
+
}
|
|
10840
|
+
const nested = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
|
|
10841
|
+
for (const child of nested) {
|
|
10842
|
+
getImageDebugInfo(child, bucket);
|
|
10843
|
+
}
|
|
10844
|
+
};
|
|
10750
10845
|
const check = () => {
|
|
10751
10846
|
const elapsed = Date.now() - start;
|
|
10752
10847
|
const domImages = Array.from(container.querySelectorAll("img"));
|
|
@@ -10756,12 +10851,18 @@ class PixldocsRenderer {
|
|
|
10756
10851
|
const renderableImages = /* @__PURE__ */ new Set();
|
|
10757
10852
|
const fabricReady = fabricObjects.every((obj) => collectRenderableImages(obj, renderableImages) !== false);
|
|
10758
10853
|
const actualImageCount = Math.max(domImages.length, renderableImages.size);
|
|
10759
|
-
!!fabricCanvas && !!(fabricCanvas.lowerCanvasEl || fabricCanvas.upperCanvasEl);
|
|
10854
|
+
const canvasReady = !!fabricCanvas && !!(fabricCanvas.lowerCanvasEl || fabricCanvas.upperCanvasEl);
|
|
10760
10855
|
const hasExpectedAssets = expectedImageCount === 0 ? true : actualImageCount >= expectedImageCount;
|
|
10761
10856
|
const ready = allDomLoaded && fabricReady && hasExpectedAssets;
|
|
10857
|
+
const summary = `expected=${expectedImageCount} actual=${actualImageCount} dom=${domImages.length} fabricReady=${fabricReady} domReady=${allDomLoaded} canvasReady=${canvasReady}`;
|
|
10858
|
+
if (summary !== lastSummary) {
|
|
10859
|
+
lastSummary = summary;
|
|
10860
|
+
console.log(`[canvas-renderer][asset-wait] ${summary}`);
|
|
10861
|
+
}
|
|
10762
10862
|
if (ready) {
|
|
10763
10863
|
stableFrames += 1;
|
|
10764
10864
|
if (stableFrames >= 2) {
|
|
10865
|
+
console.log(`[canvas-renderer][asset-wait] ready after ${elapsed}ms (${summary})`);
|
|
10765
10866
|
settle();
|
|
10766
10867
|
return;
|
|
10767
10868
|
}
|
|
@@ -10769,6 +10870,20 @@ class PixldocsRenderer {
|
|
|
10769
10870
|
stableFrames = 0;
|
|
10770
10871
|
}
|
|
10771
10872
|
if (elapsed >= maxWaitMs) {
|
|
10873
|
+
const fabricImageDebug = [];
|
|
10874
|
+
for (const obj of fabricObjects) {
|
|
10875
|
+
getImageDebugInfo(obj, fabricImageDebug);
|
|
10876
|
+
}
|
|
10877
|
+
const domImageDebug = domImages.map((img, index) => ({
|
|
10878
|
+
index,
|
|
10879
|
+
src: (img.currentSrc || img.src || "").slice(0, 240),
|
|
10880
|
+
complete: img.complete,
|
|
10881
|
+
naturalWidth: img.naturalWidth,
|
|
10882
|
+
naturalHeight: img.naturalHeight
|
|
10883
|
+
}));
|
|
10884
|
+
console.warn(`[canvas-renderer][asset-wait-timeout] elapsed=${elapsed}ms ${summary}`);
|
|
10885
|
+
console.warn("[canvas-renderer][asset-wait-timeout][dom-images]", domImageDebug);
|
|
10886
|
+
console.warn("[canvas-renderer][asset-wait-timeout][fabric-images]", fabricImageDebug);
|
|
10772
10887
|
settle();
|
|
10773
10888
|
return;
|
|
10774
10889
|
}
|
|
@@ -10922,6 +11037,93 @@ class PixldocsRenderer {
|
|
|
10922
11037
|
);
|
|
10923
11038
|
});
|
|
10924
11039
|
}
|
|
11040
|
+
// ─── Internal: capture SVG from a rendered Fabric canvas ───
|
|
11041
|
+
captureSvgViaPreviewCanvas(config, pageIndex, canvasWidth, canvasHeight) {
|
|
11042
|
+
return new Promise(async (resolve, reject) => {
|
|
11043
|
+
const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
|
|
11044
|
+
const container = document.createElement("div");
|
|
11045
|
+
container.style.cssText = `
|
|
11046
|
+
position: fixed; left: -99999px; top: -99999px;
|
|
11047
|
+
width: ${canvasWidth}px; height: ${canvasHeight}px;
|
|
11048
|
+
overflow: hidden; pointer-events: none; opacity: 0;
|
|
11049
|
+
`;
|
|
11050
|
+
document.body.appendChild(container);
|
|
11051
|
+
const timeout = setTimeout(() => {
|
|
11052
|
+
cleanup();
|
|
11053
|
+
reject(new Error("SVG render timeout (30s)"));
|
|
11054
|
+
}, 3e4);
|
|
11055
|
+
const cleanup = () => {
|
|
11056
|
+
clearTimeout(timeout);
|
|
11057
|
+
try {
|
|
11058
|
+
root.unmount();
|
|
11059
|
+
} catch {
|
|
11060
|
+
}
|
|
11061
|
+
container.remove();
|
|
11062
|
+
};
|
|
11063
|
+
const onReady = () => {
|
|
11064
|
+
const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
|
|
11065
|
+
this.waitForCanvasImages(container, expectedImageCount).then(() => {
|
|
11066
|
+
var _a, _b;
|
|
11067
|
+
try {
|
|
11068
|
+
const fabricInstance = this.getFabricCanvasFromContainer(container);
|
|
11069
|
+
if (!fabricInstance) {
|
|
11070
|
+
cleanup();
|
|
11071
|
+
reject(new Error("No Fabric canvas instance found for SVG capture"));
|
|
11072
|
+
return;
|
|
11073
|
+
}
|
|
11074
|
+
const prevVPT = fabricInstance.viewportTransform ? [...fabricInstance.viewportTransform] : void 0;
|
|
11075
|
+
const prevSvgVPT = fabricInstance.svgViewportTransformation;
|
|
11076
|
+
fabricInstance.viewportTransform = [1, 0, 0, 1, 0, 0];
|
|
11077
|
+
fabricInstance.svgViewportTransformation = false;
|
|
11078
|
+
const svgString = fabricInstance.toSVG();
|
|
11079
|
+
if (prevVPT) fabricInstance.viewportTransform = prevVPT;
|
|
11080
|
+
fabricInstance.svgViewportTransformation = prevSvgVPT;
|
|
11081
|
+
const page = config.pages[pageIndex];
|
|
11082
|
+
const backgroundColor = ((_a = page == null ? void 0 : page.settings) == null ? void 0 : _a.backgroundColor) || "#ffffff";
|
|
11083
|
+
const backgroundGradient = (_b = page == null ? void 0 : page.settings) == null ? void 0 : _b.backgroundGradient;
|
|
11084
|
+
cleanup();
|
|
11085
|
+
resolve({
|
|
11086
|
+
svg: svgString,
|
|
11087
|
+
width: canvasWidth,
|
|
11088
|
+
height: canvasHeight,
|
|
11089
|
+
backgroundColor,
|
|
11090
|
+
backgroundGradient
|
|
11091
|
+
});
|
|
11092
|
+
} catch (err) {
|
|
11093
|
+
cleanup();
|
|
11094
|
+
reject(err);
|
|
11095
|
+
}
|
|
11096
|
+
});
|
|
11097
|
+
};
|
|
11098
|
+
const root = createRoot(container);
|
|
11099
|
+
root.render(
|
|
11100
|
+
createElement(PreviewCanvas2, {
|
|
11101
|
+
config,
|
|
11102
|
+
pageIndex,
|
|
11103
|
+
zoom: 1,
|
|
11104
|
+
// 1:1 — no scaling for SVG capture
|
|
11105
|
+
absoluteZoom: true,
|
|
11106
|
+
onReady
|
|
11107
|
+
})
|
|
11108
|
+
);
|
|
11109
|
+
});
|
|
11110
|
+
}
|
|
11111
|
+
/**
|
|
11112
|
+
* Find the Fabric.Canvas instance that belongs to a given container element,
|
|
11113
|
+
* using the global __fabricCanvasRegistry (set by PageCanvas).
|
|
11114
|
+
*/
|
|
11115
|
+
getFabricCanvasFromContainer(container) {
|
|
11116
|
+
const registry2 = window.__fabricCanvasRegistry;
|
|
11117
|
+
if (registry2 instanceof Map) {
|
|
11118
|
+
for (const entry of registry2.values()) {
|
|
11119
|
+
const canvas = (entry == null ? void 0 : entry.canvas) || entry;
|
|
11120
|
+
if (!canvas || typeof canvas.toSVG !== "function") continue;
|
|
11121
|
+
const el = canvas.lowerCanvasEl || canvas.upperCanvasEl;
|
|
11122
|
+
if (el && container.contains(el)) return canvas;
|
|
11123
|
+
}
|
|
11124
|
+
}
|
|
11125
|
+
return null;
|
|
11126
|
+
}
|
|
10925
11127
|
}
|
|
10926
11128
|
function collectImageUrls(config) {
|
|
10927
11129
|
const urls = [];
|