@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 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 = templateConfig.canvas.width;
12022
- const canvasHeight = templateConfig.canvas.height;
12023
- const page = templateConfig.pages[pageIndex];
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 ${templateConfig.pages.length} pages)`);
12086
+ throw new Error(`Page index ${pageIndex} not found (template has ${renderConfig.pages.length} pages)`);
12026
12087
  }
12027
- await ensureFontsForResolvedConfig(templateConfig);
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
- templateConfig,
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 page = templateConfig.pages[pageIndex];
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 ${templateConfig.pages.length} pages)`);
12147
+ throw new Error(`Page index ${pageIndex} not found (template has ${renderConfig.pages.length} pages)`);
12086
12148
  }
12087
- await ensureFontsForResolvedConfig(templateConfig);
12149
+ await ensureFontsForResolvedConfig(renderConfig);
12088
12150
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12089
12151
  setPackageApiUrl2(this.config.imageProxyUrl);
12090
- const canvasWidth = templateConfig.canvas.width;
12091
- const canvasHeight = templateConfig.canvas.height;
12092
- return this.captureSvgViaPreviewCanvas(templateConfig, pageIndex, canvasWidth, canvasHeight);
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 stabilizationPass = 0;
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 (stabilizationPass === 0) {
12451
- stabilizationPass = 1;
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 stabilizationPass = 0;
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 (stabilizationPass === 0) {
12547
- stabilizationPass = 1;
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();