@pixldocs/canvas-renderer 0.5.56 → 0.5.57

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
@@ -12471,7 +12471,7 @@ function PixldocsPreview(props) {
12471
12471
  !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
12472
  ] });
12473
12473
  }
12474
- const PACKAGE_VERSION = "0.5.54";
12474
+ const PACKAGE_VERSION = "0.5.57";
12475
12475
  let __underlineFixInstalled = false;
12476
12476
  function installUnderlineFix(fab) {
12477
12477
  var _a;
@@ -12568,6 +12568,22 @@ function installUnderlineFix(fab) {
12568
12568
  __underlineFixInstalled = true;
12569
12569
  console.log(`[canvas-renderer] underline-fix monkey patch installed (v${PACKAGE_VERSION})`);
12570
12570
  }
12571
+ function configHasAutoShrinkText(config) {
12572
+ var _a;
12573
+ if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
12574
+ const walk = (nodes) => {
12575
+ for (const node of nodes || []) {
12576
+ if (!node) continue;
12577
+ if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
12578
+ if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
12579
+ }
12580
+ return false;
12581
+ };
12582
+ for (const page of config.pages) {
12583
+ if (walk(page.children || [])) return true;
12584
+ }
12585
+ return false;
12586
+ }
12571
12587
  class PixldocsRenderer {
12572
12588
  constructor(config) {
12573
12589
  __publicField(this, "config");
@@ -12594,6 +12610,11 @@ class PixldocsRenderer {
12594
12610
  throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12595
12611
  }
12596
12612
  await ensureFontsForResolvedConfig(templateConfig);
12613
+ if (!options.skipFontReadyWait) {
12614
+ const hasAutoShrink = configHasAutoShrinkText(templateConfig);
12615
+ const defaultWait = hasAutoShrink ? 4e3 : 1800;
12616
+ await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
12617
+ }
12597
12618
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12598
12619
  setPackageApiUrl2(this.config.imageProxyUrl);
12599
12620
  const dataUrl = await this.renderPageViaPreviewCanvas(
@@ -12601,7 +12622,8 @@ class PixldocsRenderer {
12601
12622
  pageIndex,
12602
12623
  pixelRatio,
12603
12624
  format,
12604
- quality
12625
+ quality,
12626
+ { skipFontReadyWait: options.skipFontReadyWait, waitForFontsMs: options.waitForFontsMs }
12605
12627
  );
12606
12628
  return {
12607
12629
  dataUrl,
@@ -12615,9 +12637,14 @@ class PixldocsRenderer {
12615
12637
  * Render all pages and return array of results.
12616
12638
  */
12617
12639
  async renderAllPages(templateConfig, options = {}) {
12640
+ if (!options.skipFontReadyWait) {
12641
+ const hasAutoShrink = configHasAutoShrinkText(templateConfig);
12642
+ const defaultWait = hasAutoShrink ? 4e3 : 1800;
12643
+ await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
12644
+ }
12618
12645
  const results = [];
12619
12646
  for (let i = 0; i < templateConfig.pages.length; i++) {
12620
- results.push(await this.render(templateConfig, { ...options, pageIndex: i }));
12647
+ results.push(await this.render(templateConfig, { ...options, pageIndex: i, skipFontReadyWait: true }));
12621
12648
  }
12622
12649
  return results;
12623
12650
  }
@@ -12654,6 +12681,8 @@ class PixldocsRenderer {
12654
12681
  throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12655
12682
  }
12656
12683
  await ensureFontsForResolvedConfig(templateConfig);
12684
+ const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
12685
+ await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
12657
12686
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12658
12687
  setPackageApiUrl2(this.config.imageProxyUrl);
12659
12688
  const canvasWidth = templateConfig.canvas.width;
@@ -12665,6 +12694,8 @@ class PixldocsRenderer {
12665
12694
  */
12666
12695
  async renderAllPageSvgs(templateConfig) {
12667
12696
  await ensureFontsForResolvedConfig(templateConfig);
12697
+ const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
12698
+ await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
12668
12699
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12669
12700
  setPackageApiUrl2(this.config.imageProxyUrl);
12670
12701
  const results = [];
@@ -12917,6 +12948,26 @@ class PixldocsRenderer {
12917
12948
  ]);
12918
12949
  await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
12919
12950
  }
12951
+ /**
12952
+ * Block until the webfonts referenced by `config` have actually loaded
12953
+ * (or `maxWaitMs` elapses). Used by the headless capture path BEFORE
12954
+ * mounting `PreviewCanvas`, so the synchronous `createText` auto-shrink
12955
+ * loop measures against final font metrics instead of fallback ones.
12956
+ *
12957
+ * Stronger than `ensureFontsForResolvedConfig` (which is fire-and-forget)
12958
+ * — this awaits each `document.fonts.load(spec)` AND `document.fonts.ready`,
12959
+ * racing the whole thing against `maxWaitMs` so a slow CDN can't hang the
12960
+ * renderer.
12961
+ */
12962
+ async awaitFontsForConfig(config, maxWaitMs) {
12963
+ if (typeof document === "undefined" || !document.fonts) return;
12964
+ void ensureFontsForResolvedConfig(config);
12965
+ await this.waitForRelevantFonts(config, maxWaitMs);
12966
+ await Promise.race([
12967
+ document.fonts.ready.catch(() => void 0).then(() => void 0),
12968
+ new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
12969
+ ]);
12970
+ }
12920
12971
  getNormalizedGradientStops(gradient) {
12921
12972
  const stops = Array.isArray(gradient == null ? void 0 : gradient.stops) ? gradient.stops.map((stop) => ({
12922
12973
  offset: Math.max(0, Math.min(1, Number((stop == null ? void 0 : stop.offset) ?? 0))),
@@ -12990,10 +13041,19 @@ class PixldocsRenderer {
12990
13041
  } catch {
12991
13042
  }
12992
13043
  }
12993
- async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality) {
13044
+ async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality, options = {}) {
12994
13045
  const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
12995
13046
  const canvasWidth = config.canvas.width;
12996
13047
  const canvasHeight = config.canvas.height;
13048
+ const hasAutoShrink = configHasAutoShrinkText(config);
13049
+ let firstMountSettled = false;
13050
+ let lateFontSettleDetected = false;
13051
+ if (typeof document !== "undefined" && document.fonts && hasAutoShrink) {
13052
+ document.fonts.ready.then(() => {
13053
+ if (firstMountSettled) lateFontSettleDetected = true;
13054
+ }).catch(() => {
13055
+ });
13056
+ }
12997
13057
  return new Promise((resolve, reject) => {
12998
13058
  const container = document.createElement("div");
12999
13059
  container.style.cssText = `
@@ -13006,6 +13066,8 @@ class PixldocsRenderer {
13006
13066
  cleanup();
13007
13067
  reject(new Error("Render timeout (30s)"));
13008
13068
  }, 3e4);
13069
+ let root;
13070
+ let mountKey = 0;
13009
13071
  const cleanup = () => {
13010
13072
  clearTimeout(timeout);
13011
13073
  try {
@@ -13014,6 +13076,46 @@ class PixldocsRenderer {
13014
13076
  }
13015
13077
  container.remove();
13016
13078
  };
13079
+ const remountWithFreshKey = async () => {
13080
+ mountKey += 1;
13081
+ try {
13082
+ clearMeasurementCache();
13083
+ } catch {
13084
+ }
13085
+ try {
13086
+ clearFabricCharCache();
13087
+ } catch {
13088
+ }
13089
+ try {
13090
+ root.unmount();
13091
+ } catch {
13092
+ }
13093
+ root = client.createRoot(container);
13094
+ await new Promise((settle) => {
13095
+ const onReadyOnce = () => {
13096
+ this.waitForCanvasScene(container, config, pageIndex).then(async () => {
13097
+ const fabricInstance = this.getFabricCanvasFromContainer(container);
13098
+ const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
13099
+ await this.waitForCanvasImages(container, expectedImageCount);
13100
+ await this.waitForStableTextMetrics(container, config);
13101
+ await this.waitForCanvasScene(container, config, pageIndex);
13102
+ if (!fabricInstance) return settle();
13103
+ settle();
13104
+ }).catch(() => settle());
13105
+ };
13106
+ root.render(
13107
+ react.createElement(PreviewCanvas2, {
13108
+ key: `remount-${mountKey}`,
13109
+ config,
13110
+ pageIndex,
13111
+ zoom: pixelRatio,
13112
+ absoluteZoom: true,
13113
+ skipFontReadyWait: false,
13114
+ onReady: onReadyOnce
13115
+ })
13116
+ );
13117
+ });
13118
+ };
13017
13119
  const onReady = () => {
13018
13120
  this.waitForCanvasScene(container, config, pageIndex).then(async () => {
13019
13121
  try {
@@ -13022,16 +13124,23 @@ class PixldocsRenderer {
13022
13124
  await this.waitForCanvasImages(container, expectedImageCount);
13023
13125
  await this.waitForStableTextMetrics(container, config);
13024
13126
  await this.waitForCanvasScene(container, config, pageIndex);
13127
+ firstMountSettled = true;
13128
+ if (hasAutoShrink && lateFontSettleDetected) {
13129
+ console.log("[canvas-renderer][parity] late font-settle detected — remounting for auto-shrink reflow");
13130
+ await remountWithFreshKey();
13131
+ }
13025
13132
  const fabricCanvas = container.querySelector("canvas.upper-canvas, canvas");
13026
13133
  const sourceCanvas = (fabricInstance == null ? void 0 : fabricInstance.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || fabricCanvas;
13134
+ const fabricInstanceAfter = this.getFabricCanvasFromContainer(container) || fabricInstance;
13135
+ const sourceCanvasAfter = (fabricInstanceAfter == null ? void 0 : fabricInstanceAfter.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || sourceCanvas;
13027
13136
  if (!sourceCanvas) {
13028
13137
  cleanup();
13029
13138
  reject(new Error("No canvas element found after render"));
13030
13139
  return;
13031
13140
  }
13032
13141
  const exportCanvas = document.createElement("canvas");
13033
- exportCanvas.width = sourceCanvas.width;
13034
- exportCanvas.height = sourceCanvas.height;
13142
+ exportCanvas.width = sourceCanvasAfter.width;
13143
+ exportCanvas.height = sourceCanvasAfter.height;
13035
13144
  const exportCtx = exportCanvas.getContext("2d");
13036
13145
  if (!exportCtx) {
13037
13146
  cleanup();
@@ -13039,10 +13148,10 @@ class PixldocsRenderer {
13039
13148
  return;
13040
13149
  }
13041
13150
  exportCtx.save();
13042
- exportCtx.scale(sourceCanvas.width / canvasWidth, sourceCanvas.height / canvasHeight);
13151
+ exportCtx.scale(sourceCanvasAfter.width / canvasWidth, sourceCanvasAfter.height / canvasHeight);
13043
13152
  this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
13044
13153
  exportCtx.restore();
13045
- exportCtx.drawImage(sourceCanvas, 0, 0);
13154
+ exportCtx.drawImage(sourceCanvasAfter, 0, 0);
13046
13155
  const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
13047
13156
  const dataUrl = exportCanvas.toDataURL(mimeType, quality);
13048
13157
  cleanup();
@@ -13053,7 +13162,7 @@ class PixldocsRenderer {
13053
13162
  }
13054
13163
  });
13055
13164
  };
13056
- const root = client.createRoot(container);
13165
+ root = client.createRoot(container);
13057
13166
  root.render(
13058
13167
  react.createElement(PreviewCanvas2, {
13059
13168
  config,