@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.cjs
CHANGED
|
@@ -5689,6 +5689,7 @@ const PageCanvas = react.forwardRef(
|
|
|
5689
5689
|
onDynamicFieldClick,
|
|
5690
5690
|
canvasUpdateVersion = 0,
|
|
5691
5691
|
pageChildren,
|
|
5692
|
+
skipFontReadyWait = false,
|
|
5692
5693
|
onReady
|
|
5693
5694
|
}, ref) => {
|
|
5694
5695
|
const isEditorMode = mode === "editor";
|
|
@@ -5974,9 +5975,11 @@ const PageCanvas = react.forwardRef(
|
|
|
5974
5975
|
await Promise.all(fontFamilies.map((f) => ensureFontLoaded(f)));
|
|
5975
5976
|
}
|
|
5976
5977
|
}
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5978
|
+
if (!skipFontReadyWait) {
|
|
5979
|
+
await waitForFontsReady();
|
|
5980
|
+
await waitUntilFontsAvailable(fontFamilies, { timeoutMs: 3500, pollIntervalMs: 60 });
|
|
5981
|
+
await new Promise((r) => requestAnimationFrame(() => r()));
|
|
5982
|
+
}
|
|
5980
5983
|
clearFabricCharCache();
|
|
5981
5984
|
clearMeasurementCache();
|
|
5982
5985
|
setReady(true);
|
|
@@ -9413,6 +9416,7 @@ function PreviewCanvas({
|
|
|
9413
9416
|
pageIndex = 0,
|
|
9414
9417
|
zoom = 1,
|
|
9415
9418
|
absoluteZoom = false,
|
|
9419
|
+
skipFontReadyWait = false,
|
|
9416
9420
|
className,
|
|
9417
9421
|
onDynamicFieldClick,
|
|
9418
9422
|
onReady
|
|
@@ -9567,6 +9571,7 @@ function PreviewCanvas({
|
|
|
9567
9571
|
selectedIds: [],
|
|
9568
9572
|
activeTool: "select",
|
|
9569
9573
|
mode: "preview",
|
|
9574
|
+
skipFontReadyWait,
|
|
9570
9575
|
dynamicFieldIds,
|
|
9571
9576
|
onDynamicFieldClick: handleDynamicFieldClick,
|
|
9572
9577
|
onReady
|
|
@@ -11999,6 +12004,66 @@ function PixldocsPreview(props) {
|
|
|
11999
12004
|
!canvasSettled && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#888", fontSize: 14 }, children: "Loading preview..." }) })
|
|
12000
12005
|
] });
|
|
12001
12006
|
}
|
|
12007
|
+
const inlinedAssetCache = /* @__PURE__ */ new Map();
|
|
12008
|
+
function shouldInlineImageUrl(url) {
|
|
12009
|
+
if (!url || url.startsWith("data:")) return false;
|
|
12010
|
+
if (url.startsWith("blob:")) return true;
|
|
12011
|
+
if (url.startsWith("/") && !url.startsWith("//")) return true;
|
|
12012
|
+
try {
|
|
12013
|
+
const parsed = new URL(url, window.location.href);
|
|
12014
|
+
const current = new URL(window.location.href);
|
|
12015
|
+
const host = parsed.hostname.toLowerCase();
|
|
12016
|
+
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);
|
|
12017
|
+
} catch {
|
|
12018
|
+
return false;
|
|
12019
|
+
}
|
|
12020
|
+
}
|
|
12021
|
+
async function imageUrlToDataUrl(url) {
|
|
12022
|
+
if (inlinedAssetCache.has(url)) return inlinedAssetCache.get(url) ?? null;
|
|
12023
|
+
try {
|
|
12024
|
+
const response = await fetch(url);
|
|
12025
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
12026
|
+
const blob = await response.blob();
|
|
12027
|
+
if (blob.type.includes("text/html")) throw new Error("Expected image but got text/html");
|
|
12028
|
+
const dataUrl = await new Promise((resolve, reject) => {
|
|
12029
|
+
const reader = new FileReader();
|
|
12030
|
+
reader.onloadend = () => resolve(String(reader.result || ""));
|
|
12031
|
+
reader.onerror = reject;
|
|
12032
|
+
reader.readAsDataURL(blob);
|
|
12033
|
+
});
|
|
12034
|
+
inlinedAssetCache.set(url, dataUrl);
|
|
12035
|
+
return dataUrl;
|
|
12036
|
+
} catch (error) {
|
|
12037
|
+
console.warn("[@pixldocs/canvas-renderer] Failed to inline image asset:", url, error);
|
|
12038
|
+
inlinedAssetCache.set(url, null);
|
|
12039
|
+
return null;
|
|
12040
|
+
}
|
|
12041
|
+
}
|
|
12042
|
+
async function inlineNodeAssets(node) {
|
|
12043
|
+
if (node.type === "image") {
|
|
12044
|
+
const url = typeof node.src === "string" && node.src.trim() ? node.src.trim() : typeof node.imageUrl === "string" ? node.imageUrl.trim() : "";
|
|
12045
|
+
if (shouldInlineImageUrl(url)) {
|
|
12046
|
+
const dataUrl = await imageUrlToDataUrl(url);
|
|
12047
|
+
if (dataUrl) {
|
|
12048
|
+
node.src = dataUrl;
|
|
12049
|
+
node.imageUrl = dataUrl;
|
|
12050
|
+
}
|
|
12051
|
+
}
|
|
12052
|
+
}
|
|
12053
|
+
if (Array.isArray(node.children)) {
|
|
12054
|
+
await Promise.all(node.children.map(inlineNodeAssets));
|
|
12055
|
+
}
|
|
12056
|
+
}
|
|
12057
|
+
async function inlineBrowserReachableImageAssets(config) {
|
|
12058
|
+
if (typeof window === "undefined" || typeof fetch === "undefined" || typeof FileReader === "undefined") {
|
|
12059
|
+
return config;
|
|
12060
|
+
}
|
|
12061
|
+
const cloned = JSON.parse(JSON.stringify(config));
|
|
12062
|
+
await Promise.all(
|
|
12063
|
+
(cloned.pages || []).flatMap((page) => (page.children || []).map(inlineNodeAssets))
|
|
12064
|
+
);
|
|
12065
|
+
return cloned;
|
|
12066
|
+
}
|
|
12002
12067
|
class PixldocsRenderer {
|
|
12003
12068
|
constructor(config) {
|
|
12004
12069
|
__publicField(this, "config");
|
|
@@ -12009,21 +12074,22 @@ class PixldocsRenderer {
|
|
|
12009
12074
|
* Mounts a hidden PreviewCanvas component and captures the Fabric canvas output.
|
|
12010
12075
|
*/
|
|
12011
12076
|
async render(templateConfig, options = {}) {
|
|
12077
|
+
const renderConfig = await inlineBrowserReachableImageAssets(templateConfig);
|
|
12012
12078
|
const pageIndex = options.pageIndex ?? 0;
|
|
12013
12079
|
const format = options.format ?? "png";
|
|
12014
12080
|
const quality = options.quality ?? 0.92;
|
|
12015
12081
|
const pixelRatio = options.pixelRatio ?? this.config.pixelRatio ?? 2;
|
|
12016
|
-
const canvasWidth =
|
|
12017
|
-
const canvasHeight =
|
|
12018
|
-
const page =
|
|
12082
|
+
const canvasWidth = renderConfig.canvas.width;
|
|
12083
|
+
const canvasHeight = renderConfig.canvas.height;
|
|
12084
|
+
const page = renderConfig.pages[pageIndex];
|
|
12019
12085
|
if (!page) {
|
|
12020
|
-
throw new Error(`Page index ${pageIndex} not found (template has ${
|
|
12086
|
+
throw new Error(`Page index ${pageIndex} not found (template has ${renderConfig.pages.length} pages)`);
|
|
12021
12087
|
}
|
|
12022
|
-
await ensureFontsForResolvedConfig(
|
|
12088
|
+
await ensureFontsForResolvedConfig(renderConfig);
|
|
12023
12089
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
12024
12090
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
12025
12091
|
const dataUrl = await this.renderPageViaPreviewCanvas(
|
|
12026
|
-
|
|
12092
|
+
renderConfig,
|
|
12027
12093
|
pageIndex,
|
|
12028
12094
|
pixelRatio,
|
|
12029
12095
|
format,
|
|
@@ -12075,16 +12141,17 @@ class PixldocsRenderer {
|
|
|
12075
12141
|
* This is the key building block for client-side vector PDF export.
|
|
12076
12142
|
*/
|
|
12077
12143
|
async renderPageSvg(templateConfig, pageIndex = 0) {
|
|
12078
|
-
const
|
|
12144
|
+
const renderConfig = await inlineBrowserReachableImageAssets(templateConfig);
|
|
12145
|
+
const page = renderConfig.pages[pageIndex];
|
|
12079
12146
|
if (!page) {
|
|
12080
|
-
throw new Error(`Page index ${pageIndex} not found (template has ${
|
|
12147
|
+
throw new Error(`Page index ${pageIndex} not found (template has ${renderConfig.pages.length} pages)`);
|
|
12081
12148
|
}
|
|
12082
|
-
await ensureFontsForResolvedConfig(
|
|
12149
|
+
await ensureFontsForResolvedConfig(renderConfig);
|
|
12083
12150
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
12084
12151
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
12085
|
-
const canvasWidth =
|
|
12086
|
-
const canvasHeight =
|
|
12087
|
-
return this.captureSvgViaPreviewCanvas(
|
|
12152
|
+
const canvasWidth = renderConfig.canvas.width;
|
|
12153
|
+
const canvasHeight = renderConfig.canvas.height;
|
|
12154
|
+
return this.captureSvgViaPreviewCanvas(renderConfig, pageIndex, canvasWidth, canvasHeight);
|
|
12088
12155
|
}
|
|
12089
12156
|
/**
|
|
12090
12157
|
* Render all pages and return SVG strings for each.
|
|
@@ -12432,7 +12499,7 @@ class PixldocsRenderer {
|
|
|
12432
12499
|
cleanup();
|
|
12433
12500
|
reject(new Error("Render timeout (30s)"));
|
|
12434
12501
|
}, 3e4);
|
|
12435
|
-
let
|
|
12502
|
+
let finished = false;
|
|
12436
12503
|
const cleanup = () => {
|
|
12437
12504
|
clearTimeout(timeout);
|
|
12438
12505
|
try {
|
|
@@ -12442,25 +12509,15 @@ class PixldocsRenderer {
|
|
|
12442
12509
|
container.remove();
|
|
12443
12510
|
};
|
|
12444
12511
|
const onReady = () => {
|
|
12445
|
-
if (
|
|
12446
|
-
|
|
12447
|
-
root.render(
|
|
12448
|
-
react.createElement(PreviewCanvas2, {
|
|
12449
|
-
config,
|
|
12450
|
-
pageIndex,
|
|
12451
|
-
zoom: pixelRatio,
|
|
12452
|
-
absoluteZoom: true,
|
|
12453
|
-
onReady
|
|
12454
|
-
})
|
|
12455
|
-
);
|
|
12456
|
-
return;
|
|
12457
|
-
}
|
|
12512
|
+
if (finished) return;
|
|
12513
|
+
finished = true;
|
|
12458
12514
|
this.waitForCanvasScene(container, config, pageIndex).then(async () => {
|
|
12459
12515
|
try {
|
|
12516
|
+
await this.waitForStableTextMetrics(container, config);
|
|
12517
|
+
await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
|
|
12460
12518
|
const fabricInstance = this.getFabricCanvasFromContainer(container);
|
|
12461
12519
|
const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
|
|
12462
12520
|
await this.waitForCanvasImages(container, expectedImageCount);
|
|
12463
|
-
await this.waitForStableTextMetrics(container, config);
|
|
12464
12521
|
const fabricCanvas = container.querySelector("canvas.upper-canvas, canvas");
|
|
12465
12522
|
const sourceCanvas = (fabricInstance == null ? void 0 : fabricInstance.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || fabricCanvas;
|
|
12466
12523
|
if (!sourceCanvas) {
|
|
@@ -12499,6 +12556,7 @@ class PixldocsRenderer {
|
|
|
12499
12556
|
pageIndex,
|
|
12500
12557
|
zoom: pixelRatio,
|
|
12501
12558
|
absoluteZoom: true,
|
|
12559
|
+
skipFontReadyWait: true,
|
|
12502
12560
|
onReady
|
|
12503
12561
|
})
|
|
12504
12562
|
);
|
|
@@ -12527,7 +12585,7 @@ class PixldocsRenderer {
|
|
|
12527
12585
|
cleanup();
|
|
12528
12586
|
reject(new Error("SVG render timeout (30s)"));
|
|
12529
12587
|
}, 3e4);
|
|
12530
|
-
let
|
|
12588
|
+
let finished = false;
|
|
12531
12589
|
const cleanup = () => {
|
|
12532
12590
|
clearTimeout(timeout);
|
|
12533
12591
|
try {
|
|
@@ -12537,22 +12595,13 @@ class PixldocsRenderer {
|
|
|
12537
12595
|
container.remove();
|
|
12538
12596
|
};
|
|
12539
12597
|
const onReady = () => {
|
|
12540
|
-
if (
|
|
12541
|
-
|
|
12542
|
-
root.render(
|
|
12543
|
-
react.createElement(PreviewCanvas2, {
|
|
12544
|
-
config,
|
|
12545
|
-
pageIndex,
|
|
12546
|
-
zoom: 1,
|
|
12547
|
-
absoluteZoom: true,
|
|
12548
|
-
onReady
|
|
12549
|
-
})
|
|
12550
|
-
);
|
|
12551
|
-
return;
|
|
12552
|
-
}
|
|
12598
|
+
if (finished) return;
|
|
12599
|
+
finished = true;
|
|
12553
12600
|
this.waitForCanvasScene(container, config, pageIndex).then(async () => {
|
|
12554
12601
|
var _a, _b;
|
|
12555
12602
|
try {
|
|
12603
|
+
await this.waitForStableTextMetrics(container, config);
|
|
12604
|
+
await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
|
|
12556
12605
|
const fabricInstance = this.getFabricCanvasFromContainer(container);
|
|
12557
12606
|
if (!fabricInstance) {
|
|
12558
12607
|
cleanup();
|
|
@@ -12561,7 +12610,6 @@ class PixldocsRenderer {
|
|
|
12561
12610
|
}
|
|
12562
12611
|
const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
|
|
12563
12612
|
await this.waitForCanvasImages(container, expectedImageCount);
|
|
12564
|
-
await this.waitForStableTextMetrics(container, config);
|
|
12565
12613
|
const prevVPT = fabricInstance.viewportTransform ? [...fabricInstance.viewportTransform] : void 0;
|
|
12566
12614
|
const prevSvgVPT = fabricInstance.svgViewportTransformation;
|
|
12567
12615
|
const prevRetina = fabricInstance.enableRetinaScaling;
|
|
@@ -12612,6 +12660,7 @@ class PixldocsRenderer {
|
|
|
12612
12660
|
zoom: 1,
|
|
12613
12661
|
// 1:1 — no UI scaling for SVG capture
|
|
12614
12662
|
absoluteZoom: true,
|
|
12663
|
+
skipFontReadyWait: true,
|
|
12615
12664
|
onReady
|
|
12616
12665
|
})
|
|
12617
12666
|
);
|