@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.js CHANGED
@@ -11985,6 +11985,66 @@ function PixldocsPreview(props) {
11985
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..." }) })
11986
11986
  ] });
11987
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
+ }
11988
12048
  class PixldocsRenderer {
11989
12049
  constructor(config) {
11990
12050
  __publicField(this, "config");
@@ -11995,21 +12055,22 @@ class PixldocsRenderer {
11995
12055
  * Mounts a hidden PreviewCanvas component and captures the Fabric canvas output.
11996
12056
  */
11997
12057
  async render(templateConfig, options = {}) {
12058
+ const renderConfig = await inlineBrowserReachableImageAssets(templateConfig);
11998
12059
  const pageIndex = options.pageIndex ?? 0;
11999
12060
  const format = options.format ?? "png";
12000
12061
  const quality = options.quality ?? 0.92;
12001
12062
  const pixelRatio = options.pixelRatio ?? this.config.pixelRatio ?? 2;
12002
- const canvasWidth = templateConfig.canvas.width;
12003
- const canvasHeight = templateConfig.canvas.height;
12004
- const page = templateConfig.pages[pageIndex];
12063
+ const canvasWidth = renderConfig.canvas.width;
12064
+ const canvasHeight = renderConfig.canvas.height;
12065
+ const page = renderConfig.pages[pageIndex];
12005
12066
  if (!page) {
12006
- throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12067
+ throw new Error(`Page index ${pageIndex} not found (template has ${renderConfig.pages.length} pages)`);
12007
12068
  }
12008
- await ensureFontsForResolvedConfig(templateConfig);
12069
+ await ensureFontsForResolvedConfig(renderConfig);
12009
12070
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12010
12071
  setPackageApiUrl2(this.config.imageProxyUrl);
12011
12072
  const dataUrl = await this.renderPageViaPreviewCanvas(
12012
- templateConfig,
12073
+ renderConfig,
12013
12074
  pageIndex,
12014
12075
  pixelRatio,
12015
12076
  format,
@@ -12061,16 +12122,17 @@ class PixldocsRenderer {
12061
12122
  * This is the key building block for client-side vector PDF export.
12062
12123
  */
12063
12124
  async renderPageSvg(templateConfig, pageIndex = 0) {
12064
- const page = templateConfig.pages[pageIndex];
12125
+ const renderConfig = await inlineBrowserReachableImageAssets(templateConfig);
12126
+ const page = renderConfig.pages[pageIndex];
12065
12127
  if (!page) {
12066
- throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12128
+ throw new Error(`Page index ${pageIndex} not found (template has ${renderConfig.pages.length} pages)`);
12067
12129
  }
12068
- await ensureFontsForResolvedConfig(templateConfig);
12130
+ await ensureFontsForResolvedConfig(renderConfig);
12069
12131
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12070
12132
  setPackageApiUrl2(this.config.imageProxyUrl);
12071
- const canvasWidth = templateConfig.canvas.width;
12072
- const canvasHeight = templateConfig.canvas.height;
12073
- return this.captureSvgViaPreviewCanvas(templateConfig, pageIndex, canvasWidth, canvasHeight);
12133
+ const canvasWidth = renderConfig.canvas.width;
12134
+ const canvasHeight = renderConfig.canvas.height;
12135
+ return this.captureSvgViaPreviewCanvas(renderConfig, pageIndex, canvasWidth, canvasHeight);
12074
12136
  }
12075
12137
  /**
12076
12138
  * Render all pages and return SVG strings for each.
@@ -12418,7 +12480,7 @@ class PixldocsRenderer {
12418
12480
  cleanup();
12419
12481
  reject(new Error("Render timeout (30s)"));
12420
12482
  }, 3e4);
12421
- let stabilizationPass = 0;
12483
+ let finished = false;
12422
12484
  const cleanup = () => {
12423
12485
  clearTimeout(timeout);
12424
12486
  try {
@@ -12428,22 +12490,12 @@ class PixldocsRenderer {
12428
12490
  container.remove();
12429
12491
  };
12430
12492
  const onReady = () => {
12431
- if (stabilizationPass === 0) {
12432
- stabilizationPass = 1;
12433
- root.render(
12434
- createElement(PreviewCanvas2, {
12435
- config,
12436
- pageIndex,
12437
- zoom: pixelRatio,
12438
- absoluteZoom: true,
12439
- skipFontReadyWait: true,
12440
- onReady
12441
- })
12442
- );
12443
- return;
12444
- }
12493
+ if (finished) return;
12494
+ finished = true;
12445
12495
  this.waitForCanvasScene(container, config, pageIndex).then(async () => {
12446
12496
  try {
12497
+ await this.waitForStableTextMetrics(container, config);
12498
+ await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
12447
12499
  const fabricInstance = this.getFabricCanvasFromContainer(container);
12448
12500
  const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
12449
12501
  await this.waitForCanvasImages(container, expectedImageCount);
@@ -12514,7 +12566,7 @@ class PixldocsRenderer {
12514
12566
  cleanup();
12515
12567
  reject(new Error("SVG render timeout (30s)"));
12516
12568
  }, 3e4);
12517
- let stabilizationPass = 0;
12569
+ let finished = false;
12518
12570
  const cleanup = () => {
12519
12571
  clearTimeout(timeout);
12520
12572
  try {
@@ -12524,23 +12576,13 @@ class PixldocsRenderer {
12524
12576
  container.remove();
12525
12577
  };
12526
12578
  const onReady = () => {
12527
- if (stabilizationPass === 0) {
12528
- stabilizationPass = 1;
12529
- root.render(
12530
- createElement(PreviewCanvas2, {
12531
- config,
12532
- pageIndex,
12533
- zoom: 1,
12534
- absoluteZoom: true,
12535
- skipFontReadyWait: true,
12536
- onReady
12537
- })
12538
- );
12539
- return;
12540
- }
12579
+ if (finished) return;
12580
+ finished = true;
12541
12581
  this.waitForCanvasScene(container, config, pageIndex).then(async () => {
12542
12582
  var _a, _b;
12543
12583
  try {
12584
+ await this.waitForStableTextMetrics(container, config);
12585
+ await this.waitForCanvasScene(container, config, pageIndex, 2500, 50);
12544
12586
  const fabricInstance = this.getFabricCanvasFromContainer(container);
12545
12587
  if (!fabricInstance) {
12546
12588
  cleanup();