@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.d.ts
CHANGED
|
@@ -184,6 +184,19 @@ 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
|
*/
|
|
@@ -193,6 +206,12 @@ export declare class PixldocsRenderer {
|
|
|
193
206
|
private getNormalizedGradientStops;
|
|
194
207
|
private paintPageBackground;
|
|
195
208
|
private renderPageViaPreviewCanvas;
|
|
209
|
+
private captureSvgViaPreviewCanvas;
|
|
210
|
+
/**
|
|
211
|
+
* Find the Fabric.Canvas instance that belongs to a given container element,
|
|
212
|
+
* using the global __fabricCanvasRegistry (set by PageCanvas).
|
|
213
|
+
*/
|
|
214
|
+
private getFabricCanvasFromContainer;
|
|
196
215
|
}
|
|
197
216
|
|
|
198
217
|
export declare interface RendererConfig {
|
|
@@ -291,6 +310,19 @@ export { SmartElementProps }
|
|
|
291
310
|
|
|
292
311
|
export { SmartElementType }
|
|
293
312
|
|
|
313
|
+
export declare interface SvgRenderResult {
|
|
314
|
+
/** Raw SVG string from Fabric's toSVG() — clean, no viewport transforms */
|
|
315
|
+
svg: string;
|
|
316
|
+
/** Page width in CSS pixels */
|
|
317
|
+
width: number;
|
|
318
|
+
/** Page height in CSS pixels */
|
|
319
|
+
height: number;
|
|
320
|
+
/** Background color of the page */
|
|
321
|
+
backgroundColor: string;
|
|
322
|
+
/** Background gradient (if any) */
|
|
323
|
+
backgroundGradient?: any;
|
|
324
|
+
}
|
|
325
|
+
|
|
294
326
|
export declare interface TemplateConfig {
|
|
295
327
|
version?: string;
|
|
296
328
|
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
|
*/
|
|
@@ -10718,6 +10781,7 @@ class PixldocsRenderer {
|
|
|
10718
10781
|
return new Promise((resolve) => {
|
|
10719
10782
|
const start = Date.now();
|
|
10720
10783
|
let stableFrames = 0;
|
|
10784
|
+
let lastSummary = "";
|
|
10721
10785
|
const isRenderableImage = (value) => value instanceof HTMLImageElement && value.complete && value.naturalWidth > 0 && value.naturalHeight > 0;
|
|
10722
10786
|
const collectRenderableImages = (obj, seen) => {
|
|
10723
10787
|
if (!obj || typeof obj !== "object") return;
|
|
@@ -10747,6 +10811,25 @@ class PixldocsRenderer {
|
|
|
10747
10811
|
return null;
|
|
10748
10812
|
};
|
|
10749
10813
|
const settle = () => requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
|
10814
|
+
const getImageDebugInfo = (obj, bucket) => {
|
|
10815
|
+
if (!obj || typeof obj !== "object") return;
|
|
10816
|
+
const candidates = [obj._element, obj._originalElement, obj._filteredEl, obj._cacheCanvasEl];
|
|
10817
|
+
for (const candidate of candidates) {
|
|
10818
|
+
if (candidate instanceof HTMLImageElement) {
|
|
10819
|
+
bucket.push({
|
|
10820
|
+
id: obj.__docuforgeId || obj.id || "unknown",
|
|
10821
|
+
src: (candidate.currentSrc || candidate.src || "").slice(0, 240),
|
|
10822
|
+
complete: candidate.complete,
|
|
10823
|
+
naturalWidth: candidate.naturalWidth,
|
|
10824
|
+
naturalHeight: candidate.naturalHeight
|
|
10825
|
+
});
|
|
10826
|
+
}
|
|
10827
|
+
}
|
|
10828
|
+
const nested = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
|
|
10829
|
+
for (const child of nested) {
|
|
10830
|
+
getImageDebugInfo(child, bucket);
|
|
10831
|
+
}
|
|
10832
|
+
};
|
|
10750
10833
|
const check = () => {
|
|
10751
10834
|
const elapsed = Date.now() - start;
|
|
10752
10835
|
const domImages = Array.from(container.querySelectorAll("img"));
|
|
@@ -10756,12 +10839,18 @@ class PixldocsRenderer {
|
|
|
10756
10839
|
const renderableImages = /* @__PURE__ */ new Set();
|
|
10757
10840
|
const fabricReady = fabricObjects.every((obj) => collectRenderableImages(obj, renderableImages) !== false);
|
|
10758
10841
|
const actualImageCount = Math.max(domImages.length, renderableImages.size);
|
|
10759
|
-
!!fabricCanvas && !!(fabricCanvas.lowerCanvasEl || fabricCanvas.upperCanvasEl);
|
|
10842
|
+
const canvasReady = !!fabricCanvas && !!(fabricCanvas.lowerCanvasEl || fabricCanvas.upperCanvasEl);
|
|
10760
10843
|
const hasExpectedAssets = expectedImageCount === 0 ? true : actualImageCount >= expectedImageCount;
|
|
10761
10844
|
const ready = allDomLoaded && fabricReady && hasExpectedAssets;
|
|
10845
|
+
const summary = `expected=${expectedImageCount} actual=${actualImageCount} dom=${domImages.length} fabricReady=${fabricReady} domReady=${allDomLoaded} canvasReady=${canvasReady}`;
|
|
10846
|
+
if (summary !== lastSummary) {
|
|
10847
|
+
lastSummary = summary;
|
|
10848
|
+
console.log(`[canvas-renderer][asset-wait] ${summary}`);
|
|
10849
|
+
}
|
|
10762
10850
|
if (ready) {
|
|
10763
10851
|
stableFrames += 1;
|
|
10764
10852
|
if (stableFrames >= 2) {
|
|
10853
|
+
console.log(`[canvas-renderer][asset-wait] ready after ${elapsed}ms (${summary})`);
|
|
10765
10854
|
settle();
|
|
10766
10855
|
return;
|
|
10767
10856
|
}
|
|
@@ -10769,6 +10858,20 @@ class PixldocsRenderer {
|
|
|
10769
10858
|
stableFrames = 0;
|
|
10770
10859
|
}
|
|
10771
10860
|
if (elapsed >= maxWaitMs) {
|
|
10861
|
+
const fabricImageDebug = [];
|
|
10862
|
+
for (const obj of fabricObjects) {
|
|
10863
|
+
getImageDebugInfo(obj, fabricImageDebug);
|
|
10864
|
+
}
|
|
10865
|
+
const domImageDebug = domImages.map((img, index) => ({
|
|
10866
|
+
index,
|
|
10867
|
+
src: (img.currentSrc || img.src || "").slice(0, 240),
|
|
10868
|
+
complete: img.complete,
|
|
10869
|
+
naturalWidth: img.naturalWidth,
|
|
10870
|
+
naturalHeight: img.naturalHeight
|
|
10871
|
+
}));
|
|
10872
|
+
console.warn(`[canvas-renderer][asset-wait-timeout] elapsed=${elapsed}ms ${summary}`);
|
|
10873
|
+
console.warn("[canvas-renderer][asset-wait-timeout][dom-images]", domImageDebug);
|
|
10874
|
+
console.warn("[canvas-renderer][asset-wait-timeout][fabric-images]", fabricImageDebug);
|
|
10772
10875
|
settle();
|
|
10773
10876
|
return;
|
|
10774
10877
|
}
|
|
@@ -10922,6 +11025,93 @@ class PixldocsRenderer {
|
|
|
10922
11025
|
);
|
|
10923
11026
|
});
|
|
10924
11027
|
}
|
|
11028
|
+
// ─── Internal: capture SVG from a rendered Fabric canvas ───
|
|
11029
|
+
captureSvgViaPreviewCanvas(config, pageIndex, canvasWidth, canvasHeight) {
|
|
11030
|
+
return new Promise(async (resolve, reject) => {
|
|
11031
|
+
const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
|
|
11032
|
+
const container = document.createElement("div");
|
|
11033
|
+
container.style.cssText = `
|
|
11034
|
+
position: fixed; left: -99999px; top: -99999px;
|
|
11035
|
+
width: ${canvasWidth}px; height: ${canvasHeight}px;
|
|
11036
|
+
overflow: hidden; pointer-events: none; opacity: 0;
|
|
11037
|
+
`;
|
|
11038
|
+
document.body.appendChild(container);
|
|
11039
|
+
const timeout = setTimeout(() => {
|
|
11040
|
+
cleanup();
|
|
11041
|
+
reject(new Error("SVG render timeout (30s)"));
|
|
11042
|
+
}, 3e4);
|
|
11043
|
+
const cleanup = () => {
|
|
11044
|
+
clearTimeout(timeout);
|
|
11045
|
+
try {
|
|
11046
|
+
root.unmount();
|
|
11047
|
+
} catch {
|
|
11048
|
+
}
|
|
11049
|
+
container.remove();
|
|
11050
|
+
};
|
|
11051
|
+
const onReady = () => {
|
|
11052
|
+
const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
|
|
11053
|
+
this.waitForCanvasImages(container, expectedImageCount).then(() => {
|
|
11054
|
+
var _a, _b;
|
|
11055
|
+
try {
|
|
11056
|
+
const fabricInstance = this.getFabricCanvasFromContainer(container);
|
|
11057
|
+
if (!fabricInstance) {
|
|
11058
|
+
cleanup();
|
|
11059
|
+
reject(new Error("No Fabric canvas instance found for SVG capture"));
|
|
11060
|
+
return;
|
|
11061
|
+
}
|
|
11062
|
+
const prevVPT = fabricInstance.viewportTransform ? [...fabricInstance.viewportTransform] : void 0;
|
|
11063
|
+
const prevSvgVPT = fabricInstance.svgViewportTransformation;
|
|
11064
|
+
fabricInstance.viewportTransform = [1, 0, 0, 1, 0, 0];
|
|
11065
|
+
fabricInstance.svgViewportTransformation = false;
|
|
11066
|
+
const svgString = fabricInstance.toSVG();
|
|
11067
|
+
if (prevVPT) fabricInstance.viewportTransform = prevVPT;
|
|
11068
|
+
fabricInstance.svgViewportTransformation = prevSvgVPT;
|
|
11069
|
+
const page = config.pages[pageIndex];
|
|
11070
|
+
const backgroundColor = ((_a = page == null ? void 0 : page.settings) == null ? void 0 : _a.backgroundColor) || "#ffffff";
|
|
11071
|
+
const backgroundGradient = (_b = page == null ? void 0 : page.settings) == null ? void 0 : _b.backgroundGradient;
|
|
11072
|
+
cleanup();
|
|
11073
|
+
resolve({
|
|
11074
|
+
svg: svgString,
|
|
11075
|
+
width: canvasWidth,
|
|
11076
|
+
height: canvasHeight,
|
|
11077
|
+
backgroundColor,
|
|
11078
|
+
backgroundGradient
|
|
11079
|
+
});
|
|
11080
|
+
} catch (err) {
|
|
11081
|
+
cleanup();
|
|
11082
|
+
reject(err);
|
|
11083
|
+
}
|
|
11084
|
+
});
|
|
11085
|
+
};
|
|
11086
|
+
const root = createRoot(container);
|
|
11087
|
+
root.render(
|
|
11088
|
+
createElement(PreviewCanvas2, {
|
|
11089
|
+
config,
|
|
11090
|
+
pageIndex,
|
|
11091
|
+
zoom: 1,
|
|
11092
|
+
// 1:1 — no scaling for SVG capture
|
|
11093
|
+
absoluteZoom: true,
|
|
11094
|
+
onReady
|
|
11095
|
+
})
|
|
11096
|
+
);
|
|
11097
|
+
});
|
|
11098
|
+
}
|
|
11099
|
+
/**
|
|
11100
|
+
* Find the Fabric.Canvas instance that belongs to a given container element,
|
|
11101
|
+
* using the global __fabricCanvasRegistry (set by PageCanvas).
|
|
11102
|
+
*/
|
|
11103
|
+
getFabricCanvasFromContainer(container) {
|
|
11104
|
+
const registry2 = window.__fabricCanvasRegistry;
|
|
11105
|
+
if (registry2 instanceof Map) {
|
|
11106
|
+
for (const entry of registry2.values()) {
|
|
11107
|
+
const canvas = (entry == null ? void 0 : entry.canvas) || entry;
|
|
11108
|
+
if (!canvas || typeof canvas.toSVG !== "function") continue;
|
|
11109
|
+
const el = canvas.lowerCanvasEl || canvas.upperCanvasEl;
|
|
11110
|
+
if (el && container.contains(el)) return canvas;
|
|
11111
|
+
}
|
|
11112
|
+
}
|
|
11113
|
+
return null;
|
|
11114
|
+
}
|
|
10925
11115
|
}
|
|
10926
11116
|
function collectImageUrls(config) {
|
|
10927
11117
|
const urls = [];
|