@pixldocs/canvas-renderer 0.5.18 → 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 +84 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +84 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -12004,6 +12004,66 @@ function PixldocsPreview(props) {
|
|
|
12004
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..." }) })
|
|
12005
12005
|
] });
|
|
12006
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
|
+
}
|
|
12007
12067
|
class PixldocsRenderer {
|
|
12008
12068
|
constructor(config) {
|
|
12009
12069
|
__publicField(this, "config");
|
|
@@ -12014,21 +12074,22 @@ class PixldocsRenderer {
|
|
|
12014
12074
|
* Mounts a hidden PreviewCanvas component and captures the Fabric canvas output.
|
|
12015
12075
|
*/
|
|
12016
12076
|
async render(templateConfig, options = {}) {
|
|
12077
|
+
const renderConfig = await inlineBrowserReachableImageAssets(templateConfig);
|
|
12017
12078
|
const pageIndex = options.pageIndex ?? 0;
|
|
12018
12079
|
const format = options.format ?? "png";
|
|
12019
12080
|
const quality = options.quality ?? 0.92;
|
|
12020
12081
|
const pixelRatio = options.pixelRatio ?? this.config.pixelRatio ?? 2;
|
|
12021
|
-
const canvasWidth =
|
|
12022
|
-
const canvasHeight =
|
|
12023
|
-
const page =
|
|
12082
|
+
const canvasWidth = renderConfig.canvas.width;
|
|
12083
|
+
const canvasHeight = renderConfig.canvas.height;
|
|
12084
|
+
const page = renderConfig.pages[pageIndex];
|
|
12024
12085
|
if (!page) {
|
|
12025
|
-
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)`);
|
|
12026
12087
|
}
|
|
12027
|
-
await ensureFontsForResolvedConfig(
|
|
12088
|
+
await ensureFontsForResolvedConfig(renderConfig);
|
|
12028
12089
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
12029
12090
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
12030
12091
|
const dataUrl = await this.renderPageViaPreviewCanvas(
|
|
12031
|
-
|
|
12092
|
+
renderConfig,
|
|
12032
12093
|
pageIndex,
|
|
12033
12094
|
pixelRatio,
|
|
12034
12095
|
format,
|
|
@@ -12080,16 +12141,17 @@ class PixldocsRenderer {
|
|
|
12080
12141
|
* This is the key building block for client-side vector PDF export.
|
|
12081
12142
|
*/
|
|
12082
12143
|
async renderPageSvg(templateConfig, pageIndex = 0) {
|
|
12083
|
-
const
|
|
12144
|
+
const renderConfig = await inlineBrowserReachableImageAssets(templateConfig);
|
|
12145
|
+
const page = renderConfig.pages[pageIndex];
|
|
12084
12146
|
if (!page) {
|
|
12085
|
-
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)`);
|
|
12086
12148
|
}
|
|
12087
|
-
await ensureFontsForResolvedConfig(
|
|
12149
|
+
await ensureFontsForResolvedConfig(renderConfig);
|
|
12088
12150
|
const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
|
|
12089
12151
|
setPackageApiUrl2(this.config.imageProxyUrl);
|
|
12090
|
-
const canvasWidth =
|
|
12091
|
-
const canvasHeight =
|
|
12092
|
-
return this.captureSvgViaPreviewCanvas(
|
|
12152
|
+
const canvasWidth = renderConfig.canvas.width;
|
|
12153
|
+
const canvasHeight = renderConfig.canvas.height;
|
|
12154
|
+
return this.captureSvgViaPreviewCanvas(renderConfig, pageIndex, canvasWidth, canvasHeight);
|
|
12093
12155
|
}
|
|
12094
12156
|
/**
|
|
12095
12157
|
* Render all pages and return SVG strings for each.
|
|
@@ -12437,7 +12499,7 @@ class PixldocsRenderer {
|
|
|
12437
12499
|
cleanup();
|
|
12438
12500
|
reject(new Error("Render timeout (30s)"));
|
|
12439
12501
|
}, 3e4);
|
|
12440
|
-
let
|
|
12502
|
+
let finished = false;
|
|
12441
12503
|
const cleanup = () => {
|
|
12442
12504
|
clearTimeout(timeout);
|
|
12443
12505
|
try {
|
|
@@ -12447,22 +12509,12 @@ class PixldocsRenderer {
|
|
|
12447
12509
|
container.remove();
|
|
12448
12510
|
};
|
|
12449
12511
|
const onReady = () => {
|
|
12450
|
-
if (
|
|
12451
|
-
|
|
12452
|
-
root.render(
|
|
12453
|
-
react.createElement(PreviewCanvas2, {
|
|
12454
|
-
config,
|
|
12455
|
-
pageIndex,
|
|
12456
|
-
zoom: pixelRatio,
|
|
12457
|
-
absoluteZoom: true,
|
|
12458
|
-
skipFontReadyWait: true,
|
|
12459
|
-
onReady
|
|
12460
|
-
})
|
|
12461
|
-
);
|
|
12462
|
-
return;
|
|
12463
|
-
}
|
|
12512
|
+
if (finished) return;
|
|
12513
|
+
finished = true;
|
|
12464
12514
|
this.waitForCanvasScene(container, config, pageIndex).then(async () => {
|
|
12465
12515
|
try {
|
|
12516
|
+
await this.waitForStableTextMetrics(container, config);
|
|
12517
|
+
await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
|
|
12466
12518
|
const fabricInstance = this.getFabricCanvasFromContainer(container);
|
|
12467
12519
|
const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
|
|
12468
12520
|
await this.waitForCanvasImages(container, expectedImageCount);
|
|
@@ -12533,7 +12585,7 @@ class PixldocsRenderer {
|
|
|
12533
12585
|
cleanup();
|
|
12534
12586
|
reject(new Error("SVG render timeout (30s)"));
|
|
12535
12587
|
}, 3e4);
|
|
12536
|
-
let
|
|
12588
|
+
let finished = false;
|
|
12537
12589
|
const cleanup = () => {
|
|
12538
12590
|
clearTimeout(timeout);
|
|
12539
12591
|
try {
|
|
@@ -12543,23 +12595,13 @@ class PixldocsRenderer {
|
|
|
12543
12595
|
container.remove();
|
|
12544
12596
|
};
|
|
12545
12597
|
const onReady = () => {
|
|
12546
|
-
if (
|
|
12547
|
-
|
|
12548
|
-
root.render(
|
|
12549
|
-
react.createElement(PreviewCanvas2, {
|
|
12550
|
-
config,
|
|
12551
|
-
pageIndex,
|
|
12552
|
-
zoom: 1,
|
|
12553
|
-
absoluteZoom: true,
|
|
12554
|
-
skipFontReadyWait: true,
|
|
12555
|
-
onReady
|
|
12556
|
-
})
|
|
12557
|
-
);
|
|
12558
|
-
return;
|
|
12559
|
-
}
|
|
12598
|
+
if (finished) return;
|
|
12599
|
+
finished = true;
|
|
12560
12600
|
this.waitForCanvasScene(container, config, pageIndex).then(async () => {
|
|
12561
12601
|
var _a, _b;
|
|
12562
12602
|
try {
|
|
12603
|
+
await this.waitForStableTextMetrics(container, config);
|
|
12604
|
+
await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
|
|
12563
12605
|
const fabricInstance = this.getFabricCanvasFromContainer(container);
|
|
12564
12606
|
if (!fabricInstance) {
|
|
12565
12607
|
cleanup();
|