@pixldocs/canvas-renderer 0.5.56 → 0.5.58

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
@@ -12263,6 +12263,46 @@ async function ensureFontsForResolvedConfig(config) {
12263
12263
  });
12264
12264
  }
12265
12265
  }
12266
+ function configHasAutoShrinkText$1(config) {
12267
+ var _a;
12268
+ if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
12269
+ const walk = (nodes) => {
12270
+ for (const node of nodes || []) {
12271
+ if (!node) continue;
12272
+ if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
12273
+ if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
12274
+ }
12275
+ return false;
12276
+ };
12277
+ for (const page of config.pages) {
12278
+ if (walk(page.children || [])) return true;
12279
+ }
12280
+ return false;
12281
+ }
12282
+ async function awaitFontsForConfig(config, maxWaitMs) {
12283
+ if (typeof document === "undefined" || !document.fonts) return;
12284
+ void ensureFontsForResolvedConfig(config);
12285
+ const descriptors = collectFontDescriptorsFromConfig(config);
12286
+ if (descriptors.length === 0) return;
12287
+ const loads = Promise.all(
12288
+ descriptors.map((d) => {
12289
+ const stylePrefix = d.style === "italic" ? "italic " : "";
12290
+ const spec = `${stylePrefix}${d.weight} 16px "${d.family}"`;
12291
+ return document.fonts.load(spec).catch(() => []);
12292
+ })
12293
+ ).then(() => void 0);
12294
+ await Promise.race([
12295
+ loads,
12296
+ new Promise((resolve) => setTimeout(resolve, maxWaitMs))
12297
+ ]);
12298
+ await Promise.race([
12299
+ document.fonts.ready.catch(() => void 0).then(() => void 0),
12300
+ new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
12301
+ ]);
12302
+ await new Promise(
12303
+ (resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
12304
+ );
12305
+ }
12266
12306
  const PREVIEW_DEBUG_PREFIX = "[canvas-renderer][preview-debug]";
12267
12307
  function countUnderlinedNodes(config) {
12268
12308
  var _a;
@@ -12348,15 +12388,17 @@ function PixldocsPreview(props) {
12348
12388
  underlinedNodes: countUnderlinedNodes(resolved.config)
12349
12389
  });
12350
12390
  setResolvedConfig(resolved.config);
12351
- ensureFontsForResolvedConfig(resolved.config).then(() => {
12391
+ const hasAutoShrink = configHasAutoShrinkText$1(resolved.config);
12392
+ const waitMs = hasAutoShrink ? 4e3 : 1800;
12393
+ awaitFontsForConfig(resolved.config, waitMs).then(() => {
12352
12394
  if (!cancelled) {
12353
- console.log(PREVIEW_DEBUG_PREFIX, "resolve-mode fonts queued");
12395
+ console.log(PREVIEW_DEBUG_PREFIX, "resolve-mode fonts settled", { hasAutoShrink, waitMs });
12354
12396
  setFontsReady(true);
12355
12397
  setIsLoading(false);
12356
12398
  }
12357
12399
  }).catch((err) => {
12358
12400
  if (!cancelled) {
12359
- console.warn(PREVIEW_DEBUG_PREFIX, "resolve-mode fonts queue failed", err);
12401
+ console.warn(PREVIEW_DEBUG_PREFIX, "resolve-mode font wait failed", err);
12360
12402
  setFontsReady(true);
12361
12403
  setIsLoading(false);
12362
12404
  }
@@ -12425,16 +12467,26 @@ function PixldocsPreview(props) {
12425
12467
  setFontsReady(false);
12426
12468
  setCanvasSettled(false);
12427
12469
  setStabilizationPass(0);
12428
- ensureFontsForResolvedConfig(config).then(() => {
12429
- console.log(PREVIEW_DEBUG_PREFIX, "config-mode fonts queued", {
12470
+ let cancelled = false;
12471
+ const hasAutoShrink = configHasAutoShrinkText$1(config);
12472
+ const waitMs = hasAutoShrink ? 4e3 : 1800;
12473
+ awaitFontsForConfig(config, waitMs).then(() => {
12474
+ if (cancelled) return;
12475
+ console.log(PREVIEW_DEBUG_PREFIX, "config-mode fonts settled", {
12430
12476
  pageIndex,
12477
+ hasAutoShrink,
12478
+ waitMs,
12431
12479
  underlinedNodes: countUnderlinedNodes(config)
12432
12480
  });
12433
12481
  setFontsReady(true);
12434
12482
  }).catch((err) => {
12435
- console.warn(PREVIEW_DEBUG_PREFIX, "config-mode fonts queue failed", err);
12483
+ if (cancelled) return;
12484
+ console.warn(PREVIEW_DEBUG_PREFIX, "config-mode font wait failed", err);
12436
12485
  setFontsReady(true);
12437
12486
  });
12487
+ return () => {
12488
+ cancelled = true;
12489
+ };
12438
12490
  }, [isResolveMode, config]);
12439
12491
  const handleCanvasReady = react.useCallback(() => {
12440
12492
  if (stabilizationPass === 0) {
@@ -12471,7 +12523,7 @@ function PixldocsPreview(props) {
12471
12523
  !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..." }) })
12472
12524
  ] });
12473
12525
  }
12474
- const PACKAGE_VERSION = "0.5.54";
12526
+ const PACKAGE_VERSION = "0.5.57";
12475
12527
  let __underlineFixInstalled = false;
12476
12528
  function installUnderlineFix(fab) {
12477
12529
  var _a;
@@ -12568,6 +12620,22 @@ function installUnderlineFix(fab) {
12568
12620
  __underlineFixInstalled = true;
12569
12621
  console.log(`[canvas-renderer] underline-fix monkey patch installed (v${PACKAGE_VERSION})`);
12570
12622
  }
12623
+ function configHasAutoShrinkText(config) {
12624
+ var _a;
12625
+ if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
12626
+ const walk = (nodes) => {
12627
+ for (const node of nodes || []) {
12628
+ if (!node) continue;
12629
+ if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
12630
+ if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
12631
+ }
12632
+ return false;
12633
+ };
12634
+ for (const page of config.pages) {
12635
+ if (walk(page.children || [])) return true;
12636
+ }
12637
+ return false;
12638
+ }
12571
12639
  class PixldocsRenderer {
12572
12640
  constructor(config) {
12573
12641
  __publicField(this, "config");
@@ -12594,6 +12662,11 @@ class PixldocsRenderer {
12594
12662
  throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12595
12663
  }
12596
12664
  await ensureFontsForResolvedConfig(templateConfig);
12665
+ if (!options.skipFontReadyWait) {
12666
+ const hasAutoShrink = configHasAutoShrinkText(templateConfig);
12667
+ const defaultWait = hasAutoShrink ? 4e3 : 1800;
12668
+ await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
12669
+ }
12597
12670
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12598
12671
  setPackageApiUrl2(this.config.imageProxyUrl);
12599
12672
  const dataUrl = await this.renderPageViaPreviewCanvas(
@@ -12601,7 +12674,8 @@ class PixldocsRenderer {
12601
12674
  pageIndex,
12602
12675
  pixelRatio,
12603
12676
  format,
12604
- quality
12677
+ quality,
12678
+ { skipFontReadyWait: options.skipFontReadyWait, waitForFontsMs: options.waitForFontsMs }
12605
12679
  );
12606
12680
  return {
12607
12681
  dataUrl,
@@ -12615,9 +12689,14 @@ class PixldocsRenderer {
12615
12689
  * Render all pages and return array of results.
12616
12690
  */
12617
12691
  async renderAllPages(templateConfig, options = {}) {
12692
+ if (!options.skipFontReadyWait) {
12693
+ const hasAutoShrink = configHasAutoShrinkText(templateConfig);
12694
+ const defaultWait = hasAutoShrink ? 4e3 : 1800;
12695
+ await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
12696
+ }
12618
12697
  const results = [];
12619
12698
  for (let i = 0; i < templateConfig.pages.length; i++) {
12620
- results.push(await this.render(templateConfig, { ...options, pageIndex: i }));
12699
+ results.push(await this.render(templateConfig, { ...options, pageIndex: i, skipFontReadyWait: true }));
12621
12700
  }
12622
12701
  return results;
12623
12702
  }
@@ -12654,6 +12733,8 @@ class PixldocsRenderer {
12654
12733
  throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12655
12734
  }
12656
12735
  await ensureFontsForResolvedConfig(templateConfig);
12736
+ const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
12737
+ await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
12657
12738
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12658
12739
  setPackageApiUrl2(this.config.imageProxyUrl);
12659
12740
  const canvasWidth = templateConfig.canvas.width;
@@ -12665,6 +12746,8 @@ class PixldocsRenderer {
12665
12746
  */
12666
12747
  async renderAllPageSvgs(templateConfig) {
12667
12748
  await ensureFontsForResolvedConfig(templateConfig);
12749
+ const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
12750
+ await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
12668
12751
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12669
12752
  setPackageApiUrl2(this.config.imageProxyUrl);
12670
12753
  const results = [];
@@ -12917,6 +13000,26 @@ class PixldocsRenderer {
12917
13000
  ]);
12918
13001
  await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
12919
13002
  }
13003
+ /**
13004
+ * Block until the webfonts referenced by `config` have actually loaded
13005
+ * (or `maxWaitMs` elapses). Used by the headless capture path BEFORE
13006
+ * mounting `PreviewCanvas`, so the synchronous `createText` auto-shrink
13007
+ * loop measures against final font metrics instead of fallback ones.
13008
+ *
13009
+ * Stronger than `ensureFontsForResolvedConfig` (which is fire-and-forget)
13010
+ * — this awaits each `document.fonts.load(spec)` AND `document.fonts.ready`,
13011
+ * racing the whole thing against `maxWaitMs` so a slow CDN can't hang the
13012
+ * renderer.
13013
+ */
13014
+ async awaitFontsForConfig(config, maxWaitMs) {
13015
+ if (typeof document === "undefined" || !document.fonts) return;
13016
+ void ensureFontsForResolvedConfig(config);
13017
+ await this.waitForRelevantFonts(config, maxWaitMs);
13018
+ await Promise.race([
13019
+ document.fonts.ready.catch(() => void 0).then(() => void 0),
13020
+ new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
13021
+ ]);
13022
+ }
12920
13023
  getNormalizedGradientStops(gradient) {
12921
13024
  const stops = Array.isArray(gradient == null ? void 0 : gradient.stops) ? gradient.stops.map((stop) => ({
12922
13025
  offset: Math.max(0, Math.min(1, Number((stop == null ? void 0 : stop.offset) ?? 0))),
@@ -12990,10 +13093,19 @@ class PixldocsRenderer {
12990
13093
  } catch {
12991
13094
  }
12992
13095
  }
12993
- async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality) {
13096
+ async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality, options = {}) {
12994
13097
  const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
12995
13098
  const canvasWidth = config.canvas.width;
12996
13099
  const canvasHeight = config.canvas.height;
13100
+ const hasAutoShrink = configHasAutoShrinkText(config);
13101
+ let firstMountSettled = false;
13102
+ let lateFontSettleDetected = false;
13103
+ if (typeof document !== "undefined" && document.fonts && hasAutoShrink) {
13104
+ document.fonts.ready.then(() => {
13105
+ if (firstMountSettled) lateFontSettleDetected = true;
13106
+ }).catch(() => {
13107
+ });
13108
+ }
12997
13109
  return new Promise((resolve, reject) => {
12998
13110
  const container = document.createElement("div");
12999
13111
  container.style.cssText = `
@@ -13006,6 +13118,8 @@ class PixldocsRenderer {
13006
13118
  cleanup();
13007
13119
  reject(new Error("Render timeout (30s)"));
13008
13120
  }, 3e4);
13121
+ let root;
13122
+ let mountKey = 0;
13009
13123
  const cleanup = () => {
13010
13124
  clearTimeout(timeout);
13011
13125
  try {
@@ -13014,6 +13128,46 @@ class PixldocsRenderer {
13014
13128
  }
13015
13129
  container.remove();
13016
13130
  };
13131
+ const remountWithFreshKey = async () => {
13132
+ mountKey += 1;
13133
+ try {
13134
+ clearMeasurementCache();
13135
+ } catch {
13136
+ }
13137
+ try {
13138
+ clearFabricCharCache();
13139
+ } catch {
13140
+ }
13141
+ try {
13142
+ root.unmount();
13143
+ } catch {
13144
+ }
13145
+ root = client.createRoot(container);
13146
+ await new Promise((settle) => {
13147
+ const onReadyOnce = () => {
13148
+ this.waitForCanvasScene(container, config, pageIndex).then(async () => {
13149
+ const fabricInstance = this.getFabricCanvasFromContainer(container);
13150
+ const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
13151
+ await this.waitForCanvasImages(container, expectedImageCount);
13152
+ await this.waitForStableTextMetrics(container, config);
13153
+ await this.waitForCanvasScene(container, config, pageIndex);
13154
+ if (!fabricInstance) return settle();
13155
+ settle();
13156
+ }).catch(() => settle());
13157
+ };
13158
+ root.render(
13159
+ react.createElement(PreviewCanvas2, {
13160
+ key: `remount-${mountKey}`,
13161
+ config,
13162
+ pageIndex,
13163
+ zoom: pixelRatio,
13164
+ absoluteZoom: true,
13165
+ skipFontReadyWait: false,
13166
+ onReady: onReadyOnce
13167
+ })
13168
+ );
13169
+ });
13170
+ };
13017
13171
  const onReady = () => {
13018
13172
  this.waitForCanvasScene(container, config, pageIndex).then(async () => {
13019
13173
  try {
@@ -13022,16 +13176,23 @@ class PixldocsRenderer {
13022
13176
  await this.waitForCanvasImages(container, expectedImageCount);
13023
13177
  await this.waitForStableTextMetrics(container, config);
13024
13178
  await this.waitForCanvasScene(container, config, pageIndex);
13179
+ firstMountSettled = true;
13180
+ if (hasAutoShrink && lateFontSettleDetected) {
13181
+ console.log("[canvas-renderer][parity] late font-settle detected — remounting for auto-shrink reflow");
13182
+ await remountWithFreshKey();
13183
+ }
13025
13184
  const fabricCanvas = container.querySelector("canvas.upper-canvas, canvas");
13026
13185
  const sourceCanvas = (fabricInstance == null ? void 0 : fabricInstance.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || fabricCanvas;
13186
+ const fabricInstanceAfter = this.getFabricCanvasFromContainer(container) || fabricInstance;
13187
+ const sourceCanvasAfter = (fabricInstanceAfter == null ? void 0 : fabricInstanceAfter.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || sourceCanvas;
13027
13188
  if (!sourceCanvas) {
13028
13189
  cleanup();
13029
13190
  reject(new Error("No canvas element found after render"));
13030
13191
  return;
13031
13192
  }
13032
13193
  const exportCanvas = document.createElement("canvas");
13033
- exportCanvas.width = sourceCanvas.width;
13034
- exportCanvas.height = sourceCanvas.height;
13194
+ exportCanvas.width = sourceCanvasAfter.width;
13195
+ exportCanvas.height = sourceCanvasAfter.height;
13035
13196
  const exportCtx = exportCanvas.getContext("2d");
13036
13197
  if (!exportCtx) {
13037
13198
  cleanup();
@@ -13039,10 +13200,10 @@ class PixldocsRenderer {
13039
13200
  return;
13040
13201
  }
13041
13202
  exportCtx.save();
13042
- exportCtx.scale(sourceCanvas.width / canvasWidth, sourceCanvas.height / canvasHeight);
13203
+ exportCtx.scale(sourceCanvasAfter.width / canvasWidth, sourceCanvasAfter.height / canvasHeight);
13043
13204
  this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
13044
13205
  exportCtx.restore();
13045
- exportCtx.drawImage(sourceCanvas, 0, 0);
13206
+ exportCtx.drawImage(sourceCanvasAfter, 0, 0);
13046
13207
  const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
13047
13208
  const dataUrl = exportCanvas.toDataURL(mimeType, quality);
13048
13209
  cleanup();
@@ -13053,7 +13214,7 @@ class PixldocsRenderer {
13053
13214
  }
13054
13215
  });
13055
13216
  };
13056
- const root = client.createRoot(container);
13217
+ root = client.createRoot(container);
13057
13218
  root.render(
13058
13219
  react.createElement(PreviewCanvas2, {
13059
13220
  config,
@@ -15533,9 +15694,11 @@ exports.PixldocsPreview = PixldocsPreview;
15533
15694
  exports.PixldocsRenderer = PixldocsRenderer;
15534
15695
  exports.applyThemeToConfig = applyThemeToConfig;
15535
15696
  exports.assemblePdfFromSvgs = assemblePdfFromSvgs;
15697
+ exports.awaitFontsForConfig = awaitFontsForConfig;
15536
15698
  exports.collectFontDescriptorsFromConfig = collectFontDescriptorsFromConfig;
15537
15699
  exports.collectFontsFromConfig = collectFontsFromConfig;
15538
15700
  exports.collectImageUrls = collectImageUrls;
15701
+ exports.configHasAutoShrinkText = configHasAutoShrinkText$1;
15539
15702
  exports.embedFont = embedFont;
15540
15703
  exports.embedFontsForConfig = embedFontsForConfig;
15541
15704
  exports.embedFontsInPdf = embedFontsInPdf;