@pixldocs/canvas-renderer 0.5.17 → 0.5.19
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 +94 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +94 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5670,6 +5670,7 @@ const PageCanvas = forwardRef(
|
|
|
5670
5670
|
onDynamicFieldClick,
|
|
5671
5671
|
canvasUpdateVersion = 0,
|
|
5672
5672
|
pageChildren,
|
|
5673
|
+
skipFontReadyWait = false,
|
|
5673
5674
|
onReady
|
|
5674
5675
|
}, ref) => {
|
|
5675
5676
|
const isEditorMode = mode === "editor";
|
|
@@ -5955,9 +5956,11 @@ const PageCanvas = forwardRef(
|
|
|
5955
5956
|
await Promise.all(fontFamilies.map((f) => ensureFontLoaded(f)));
|
|
5956
5957
|
}
|
|
5957
5958
|
}
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5959
|
+
if (!skipFontReadyWait) {
|
|
5960
|
+
await waitForFontsReady();
|
|
5961
|
+
await waitUntilFontsAvailable(fontFamilies, { timeoutMs: 3500, pollIntervalMs: 60 });
|
|
5962
|
+
await new Promise((r) => requestAnimationFrame(() => r()));
|
|
5963
|
+
}
|
|
5961
5964
|
clearFabricCharCache();
|
|
5962
5965
|
clearMeasurementCache();
|
|
5963
5966
|
setReady(true);
|
|
@@ -9394,6 +9397,7 @@ function PreviewCanvas({
|
|
|
9394
9397
|
pageIndex = 0,
|
|
9395
9398
|
zoom = 1,
|
|
9396
9399
|
absoluteZoom = false,
|
|
9400
|
+
skipFontReadyWait = false,
|
|
9397
9401
|
className,
|
|
9398
9402
|
onDynamicFieldClick,
|
|
9399
9403
|
onReady
|
|
@@ -9548,6 +9552,7 @@ function PreviewCanvas({
|
|
|
9548
9552
|
selectedIds: [],
|
|
9549
9553
|
activeTool: "select",
|
|
9550
9554
|
mode: "preview",
|
|
9555
|
+
skipFontReadyWait,
|
|
9551
9556
|
dynamicFieldIds,
|
|
9552
9557
|
onDynamicFieldClick: handleDynamicFieldClick,
|
|
9553
9558
|
onReady
|
|
@@ -11980,6 +11985,66 @@ function PixldocsPreview(props) {
|
|
|
11980
11985
|
!canvasSettled && /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) })
|
|
11981
11986
|
] });
|
|
11982
11987
|
}
|
|
11988
|
+
const inlinedAssetCache = /* @__PURE__ */ new Map();
|
|
11989
|
+
function shouldInlineImageUrl(url) {
|
|
11990
|
+
if (!url || url.startsWith("data:")) return false;
|
|
11991
|
+
if (url.startsWith("blob:")) return true;
|
|
11992
|
+
if (url.startsWith("/") && !url.startsWith("//")) return true;
|
|
11993
|
+
try {
|
|
11994
|
+
const parsed = new URL(url, window.location.href);
|
|
11995
|
+
const current = new URL(window.location.href);
|
|
11996
|
+
const host = parsed.hostname.toLowerCase();
|
|
11997
|
+
return parsed.origin === current.origin || host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host.endsWith(".local") || /^(10\.|192\.168\.|169\.254\.)/.test(host);
|
|
11998
|
+
} catch {
|
|
11999
|
+
return false;
|
|
12000
|
+
}
|
|
12001
|
+
}
|
|
12002
|
+
async function imageUrlToDataUrl(url) {
|
|
12003
|
+
if (inlinedAssetCache.has(url)) return inlinedAssetCache.get(url) ?? null;
|
|
12004
|
+
try {
|
|
12005
|
+
const response = await fetch(url);
|
|
12006
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
12007
|
+
const blob = await response.blob();
|
|
12008
|
+
if (blob.type.includes("text/html")) throw new Error("Expected image but got text/html");
|
|
12009
|
+
const dataUrl = await new Promise((resolve, reject) => {
|
|
12010
|
+
const reader = new FileReader();
|
|
12011
|
+
reader.onloadend = () => resolve(String(reader.result || ""));
|
|
12012
|
+
reader.onerror = reject;
|
|
12013
|
+
reader.readAsDataURL(blob);
|
|
12014
|
+
});
|
|
12015
|
+
inlinedAssetCache.set(url, dataUrl);
|
|
12016
|
+
return dataUrl;
|
|
12017
|
+
} catch (error) {
|
|
12018
|
+
console.warn("[@pixldocs/canvas-renderer] Failed to inline image asset:", url, error);
|
|
12019
|
+
inlinedAssetCache.set(url, null);
|
|
12020
|
+
return null;
|
|
12021
|
+
}
|
|
12022
|
+
}
|
|
12023
|
+
async function inlineNodeAssets(node) {
|
|
12024
|
+
if (node.type === "image") {
|
|
12025
|
+
const url = typeof node.src === "string" && node.src.trim() ? node.src.trim() : typeof node.imageUrl === "string" ? node.imageUrl.trim() : "";
|
|
12026
|
+
if (shouldInlineImageUrl(url)) {
|
|
12027
|
+
const dataUrl = await imageUrlToDataUrl(url);
|
|
12028
|
+
if (dataUrl) {
|
|
12029
|
+
node.src = dataUrl;
|
|
12030
|
+
node.imageUrl = dataUrl;
|
|
12031
|
+
}
|
|
12032
|
+
}
|
|
12033
|
+
}
|
|
12034
|
+
if (Array.isArray(node.children)) {
|
|
12035
|
+
await Promise.all(node.children.map(inlineNodeAssets));
|
|
12036
|
+
}
|
|
12037
|
+
}
|
|
12038
|
+
async function inlineBrowserReachableImageAssets(config) {
|
|
12039
|
+
if (typeof window === "undefined" || typeof fetch === "undefined" || typeof FileReader === "undefined") {
|
|
12040
|
+
return config;
|
|
12041
|
+
}
|
|
12042
|
+
const cloned = JSON.parse(JSON.stringify(config));
|
|
12043
|
+
await Promise.all(
|
|
12044
|
+
(cloned.pages || []).flatMap((page) => (page.children || []).map(inlineNodeAssets))
|
|
12045
|
+
);
|
|
12046
|
+
return cloned;
|
|
12047
|
+
}
|
|
11983
12048
|
class PixldocsRenderer {
|
|
11984
12049
|
constructor(config) {
|
|
11985
12050
|
__publicField(this, "config");
|
|
@@ -11990,21 +12055,22 @@ class PixldocsRenderer {
|
|
|
11990
12055
|
* Mounts a hidden PreviewCanvas component and captures the Fabric canvas output.
|
|
11991
12056
|
*/
|
|
11992
12057
|
async render(templateConfig, options = {}) {
|
|
12058
|
+
const renderConfig = await inlineBrowserReachableImageAssets(templateConfig);
|
|
11993
12059
|
const pageIndex = options.pageIndex ?? 0;
|
|
11994
12060
|
const format = options.format ?? "png";
|
|
11995
12061
|
const quality = options.quality ?? 0.92;
|
|
11996
12062
|
const pixelRatio = options.pixelRatio ?? this.config.pixelRatio ?? 2;
|
|
11997
|
-
const canvasWidth =
|
|
11998
|
-
const canvasHeight =
|
|
11999
|
-
const page =
|
|
12063
|
+
const canvasWidth = renderConfig.canvas.width;
|
|
12064
|
+
const canvasHeight = renderConfig.canvas.height;
|
|
12065
|
+
const page = renderConfig.pages[pageIndex];
|
|
12000
12066
|
if (!page) {
|
|
12001
|
-
throw new Error(`Page index ${pageIndex} not found (template has ${
|
|
12067
|
+
throw new Error(`Page index ${pageIndex} not found (template has ${renderConfig.pages.length} pages)`);
|
|
12002
12068
|
}
|
|
12003
|
-
await ensureFontsForResolvedConfig(
|
|
12069
|
+
await ensureFontsForResolvedConfig(renderConfig);
|
|
12004
12070
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
12005
12071
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
12006
12072
|
const dataUrl = await this.renderPageViaPreviewCanvas(
|
|
12007
|
-
|
|
12073
|
+
renderConfig,
|
|
12008
12074
|
pageIndex,
|
|
12009
12075
|
pixelRatio,
|
|
12010
12076
|
format,
|
|
@@ -12056,16 +12122,17 @@ class PixldocsRenderer {
|
|
|
12056
12122
|
* This is the key building block for client-side vector PDF export.
|
|
12057
12123
|
*/
|
|
12058
12124
|
async renderPageSvg(templateConfig, pageIndex = 0) {
|
|
12059
|
-
const
|
|
12125
|
+
const renderConfig = await inlineBrowserReachableImageAssets(templateConfig);
|
|
12126
|
+
const page = renderConfig.pages[pageIndex];
|
|
12060
12127
|
if (!page) {
|
|
12061
|
-
throw new Error(`Page index ${pageIndex} not found (template has ${
|
|
12128
|
+
throw new Error(`Page index ${pageIndex} not found (template has ${renderConfig.pages.length} pages)`);
|
|
12062
12129
|
}
|
|
12063
|
-
await ensureFontsForResolvedConfig(
|
|
12130
|
+
await ensureFontsForResolvedConfig(renderConfig);
|
|
12064
12131
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
12065
12132
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
12066
|
-
const canvasWidth =
|
|
12067
|
-
const canvasHeight =
|
|
12068
|
-
return this.captureSvgViaPreviewCanvas(
|
|
12133
|
+
const canvasWidth = renderConfig.canvas.width;
|
|
12134
|
+
const canvasHeight = renderConfig.canvas.height;
|
|
12135
|
+
return this.captureSvgViaPreviewCanvas(renderConfig, pageIndex, canvasWidth, canvasHeight);
|
|
12069
12136
|
}
|
|
12070
12137
|
/**
|
|
12071
12138
|
* Render all pages and return SVG strings for each.
|
|
@@ -12413,7 +12480,7 @@ class PixldocsRenderer {
|
|
|
12413
12480
|
cleanup();
|
|
12414
12481
|
reject(new Error("Render timeout (30s)"));
|
|
12415
12482
|
}, 3e4);
|
|
12416
|
-
let
|
|
12483
|
+
let finished = false;
|
|
12417
12484
|
const cleanup = () => {
|
|
12418
12485
|
clearTimeout(timeout);
|
|
12419
12486
|
try {
|
|
@@ -12423,25 +12490,15 @@ class PixldocsRenderer {
|
|
|
12423
12490
|
container.remove();
|
|
12424
12491
|
};
|
|
12425
12492
|
const onReady = () => {
|
|
12426
|
-
if (
|
|
12427
|
-
|
|
12428
|
-
root.render(
|
|
12429
|
-
createElement(PreviewCanvas2, {
|
|
12430
|
-
config,
|
|
12431
|
-
pageIndex,
|
|
12432
|
-
zoom: pixelRatio,
|
|
12433
|
-
absoluteZoom: true,
|
|
12434
|
-
onReady
|
|
12435
|
-
})
|
|
12436
|
-
);
|
|
12437
|
-
return;
|
|
12438
|
-
}
|
|
12493
|
+
if (finished) return;
|
|
12494
|
+
finished = true;
|
|
12439
12495
|
this.waitForCanvasScene(container, config, pageIndex).then(async () => {
|
|
12440
12496
|
try {
|
|
12497
|
+
await this.waitForStableTextMetrics(container, config);
|
|
12498
|
+
await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
|
|
12441
12499
|
const fabricInstance = this.getFabricCanvasFromContainer(container);
|
|
12442
12500
|
const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
|
|
12443
12501
|
await this.waitForCanvasImages(container, expectedImageCount);
|
|
12444
|
-
await this.waitForStableTextMetrics(container, config);
|
|
12445
12502
|
const fabricCanvas = container.querySelector("canvas.upper-canvas, canvas");
|
|
12446
12503
|
const sourceCanvas = (fabricInstance == null ? void 0 : fabricInstance.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || fabricCanvas;
|
|
12447
12504
|
if (!sourceCanvas) {
|
|
@@ -12480,6 +12537,7 @@ class PixldocsRenderer {
|
|
|
12480
12537
|
pageIndex,
|
|
12481
12538
|
zoom: pixelRatio,
|
|
12482
12539
|
absoluteZoom: true,
|
|
12540
|
+
skipFontReadyWait: true,
|
|
12483
12541
|
onReady
|
|
12484
12542
|
})
|
|
12485
12543
|
);
|
|
@@ -12508,7 +12566,7 @@ class PixldocsRenderer {
|
|
|
12508
12566
|
cleanup();
|
|
12509
12567
|
reject(new Error("SVG render timeout (30s)"));
|
|
12510
12568
|
}, 3e4);
|
|
12511
|
-
let
|
|
12569
|
+
let finished = false;
|
|
12512
12570
|
const cleanup = () => {
|
|
12513
12571
|
clearTimeout(timeout);
|
|
12514
12572
|
try {
|
|
@@ -12518,22 +12576,13 @@ class PixldocsRenderer {
|
|
|
12518
12576
|
container.remove();
|
|
12519
12577
|
};
|
|
12520
12578
|
const onReady = () => {
|
|
12521
|
-
if (
|
|
12522
|
-
|
|
12523
|
-
root.render(
|
|
12524
|
-
createElement(PreviewCanvas2, {
|
|
12525
|
-
config,
|
|
12526
|
-
pageIndex,
|
|
12527
|
-
zoom: 1,
|
|
12528
|
-
absoluteZoom: true,
|
|
12529
|
-
onReady
|
|
12530
|
-
})
|
|
12531
|
-
);
|
|
12532
|
-
return;
|
|
12533
|
-
}
|
|
12579
|
+
if (finished) return;
|
|
12580
|
+
finished = true;
|
|
12534
12581
|
this.waitForCanvasScene(container, config, pageIndex).then(async () => {
|
|
12535
12582
|
var _a, _b;
|
|
12536
12583
|
try {
|
|
12584
|
+
await this.waitForStableTextMetrics(container, config);
|
|
12585
|
+
await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
|
|
12537
12586
|
const fabricInstance = this.getFabricCanvasFromContainer(container);
|
|
12538
12587
|
if (!fabricInstance) {
|
|
12539
12588
|
cleanup();
|
|
@@ -12542,7 +12591,6 @@ class PixldocsRenderer {
|
|
|
12542
12591
|
}
|
|
12543
12592
|
const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
|
|
12544
12593
|
await this.waitForCanvasImages(container, expectedImageCount);
|
|
12545
|
-
await this.waitForStableTextMetrics(container, config);
|
|
12546
12594
|
const prevVPT = fabricInstance.viewportTransform ? [...fabricInstance.viewportTransform] : void 0;
|
|
12547
12595
|
const prevSvgVPT = fabricInstance.svgViewportTransformation;
|
|
12548
12596
|
const prevRetina = fabricInstance.enableRetinaScaling;
|
|
@@ -12593,6 +12641,7 @@ class PixldocsRenderer {
|
|
|
12593
12641
|
zoom: 1,
|
|
12594
12642
|
// 1:1 — no UI scaling for SVG capture
|
|
12595
12643
|
absoluteZoom: true,
|
|
12644
|
+
skipFontReadyWait: true,
|
|
12596
12645
|
onReady
|
|
12597
12646
|
})
|
|
12598
12647
|
);
|