@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.d.ts CHANGED
@@ -231,7 +231,7 @@ export declare function normalizeFontFamily(fontStack: string): string;
231
231
  * Package version banner. Bump alongside package.json so we can confirm
232
232
  * (via browser:log) that the deployed bundle matches the expected build.
233
233
  */
234
- export declare const PACKAGE_VERSION = "0.5.54";
234
+ export declare const PACKAGE_VERSION = "0.5.57";
235
235
 
236
236
  export declare interface PageSettings {
237
237
  backgroundColor?: string;
@@ -391,6 +391,18 @@ export declare class PixldocsRenderer {
391
391
  private waitForCanvasImages;
392
392
  private waitForCanvasScene;
393
393
  private waitForRelevantFonts;
394
+ /**
395
+ * Block until the webfonts referenced by `config` have actually loaded
396
+ * (or `maxWaitMs` elapses). Used by the headless capture path BEFORE
397
+ * mounting `PreviewCanvas`, so the synchronous `createText` auto-shrink
398
+ * loop measures against final font metrics instead of fallback ones.
399
+ *
400
+ * Stronger than `ensureFontsForResolvedConfig` (which is fire-and-forget)
401
+ * — this awaits each `document.fonts.load(spec)` AND `document.fonts.ready`,
402
+ * racing the whole thing against `maxWaitMs` so a slow CDN can't hang the
403
+ * renderer.
404
+ */
405
+ private awaitFontsForConfig;
394
406
  private getNormalizedGradientStops;
395
407
  private paintPageBackground;
396
408
  private renderPageViaPreviewCanvas;
@@ -447,6 +459,21 @@ export declare interface RenderOptions {
447
459
  scale?: number;
448
460
  /** Custom pixel ratio override */
449
461
  pixelRatio?: number;
462
+ /**
463
+ * If true, skip the blocking font-ready wait before mounting the headless
464
+ * PreviewCanvas. Default: `false`. Setting this to `true` makes capture
465
+ * faster but can cause `overflowPolicy: 'auto-shrink'` text to overflow
466
+ * when the real webfont loads after auto-shrink has already measured
467
+ * against fallback metrics.
468
+ */
469
+ skipFontReadyWait?: boolean;
470
+ /**
471
+ * Maximum time (ms) to wait for `document.fonts.load()` per descriptor
472
+ * before mounting PreviewCanvas. Default: `4000` for configs that contain
473
+ * any `auto-shrink` text (correctness matters), `1800` otherwise. Only
474
+ * applies when `skipFontReadyWait` is false.
475
+ */
476
+ waitForFontsMs?: number;
450
477
  }
451
478
 
452
479
  export declare interface RenderResult {
package/dist/index.js CHANGED
@@ -12452,7 +12452,7 @@ function PixldocsPreview(props) {
12452
12452
  !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..." }) })
12453
12453
  ] });
12454
12454
  }
12455
- const PACKAGE_VERSION = "0.5.54";
12455
+ const PACKAGE_VERSION = "0.5.57";
12456
12456
  let __underlineFixInstalled = false;
12457
12457
  function installUnderlineFix(fab) {
12458
12458
  var _a;
@@ -12549,6 +12549,22 @@ function installUnderlineFix(fab) {
12549
12549
  __underlineFixInstalled = true;
12550
12550
  console.log(`[canvas-renderer] underline-fix monkey patch installed (v${PACKAGE_VERSION})`);
12551
12551
  }
12552
+ function configHasAutoShrinkText(config) {
12553
+ var _a;
12554
+ if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
12555
+ const walk = (nodes) => {
12556
+ for (const node of nodes || []) {
12557
+ if (!node) continue;
12558
+ if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
12559
+ if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
12560
+ }
12561
+ return false;
12562
+ };
12563
+ for (const page of config.pages) {
12564
+ if (walk(page.children || [])) return true;
12565
+ }
12566
+ return false;
12567
+ }
12552
12568
  class PixldocsRenderer {
12553
12569
  constructor(config) {
12554
12570
  __publicField(this, "config");
@@ -12575,6 +12591,11 @@ class PixldocsRenderer {
12575
12591
  throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12576
12592
  }
12577
12593
  await ensureFontsForResolvedConfig(templateConfig);
12594
+ if (!options.skipFontReadyWait) {
12595
+ const hasAutoShrink = configHasAutoShrinkText(templateConfig);
12596
+ const defaultWait = hasAutoShrink ? 4e3 : 1800;
12597
+ await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
12598
+ }
12578
12599
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12579
12600
  setPackageApiUrl2(this.config.imageProxyUrl);
12580
12601
  const dataUrl = await this.renderPageViaPreviewCanvas(
@@ -12582,7 +12603,8 @@ class PixldocsRenderer {
12582
12603
  pageIndex,
12583
12604
  pixelRatio,
12584
12605
  format,
12585
- quality
12606
+ quality,
12607
+ { skipFontReadyWait: options.skipFontReadyWait, waitForFontsMs: options.waitForFontsMs }
12586
12608
  );
12587
12609
  return {
12588
12610
  dataUrl,
@@ -12596,9 +12618,14 @@ class PixldocsRenderer {
12596
12618
  * Render all pages and return array of results.
12597
12619
  */
12598
12620
  async renderAllPages(templateConfig, options = {}) {
12621
+ if (!options.skipFontReadyWait) {
12622
+ const hasAutoShrink = configHasAutoShrinkText(templateConfig);
12623
+ const defaultWait = hasAutoShrink ? 4e3 : 1800;
12624
+ await this.awaitFontsForConfig(templateConfig, options.waitForFontsMs ?? defaultWait);
12625
+ }
12599
12626
  const results = [];
12600
12627
  for (let i = 0; i < templateConfig.pages.length; i++) {
12601
- results.push(await this.render(templateConfig, { ...options, pageIndex: i }));
12628
+ results.push(await this.render(templateConfig, { ...options, pageIndex: i, skipFontReadyWait: true }));
12602
12629
  }
12603
12630
  return results;
12604
12631
  }
@@ -12635,6 +12662,8 @@ class PixldocsRenderer {
12635
12662
  throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12636
12663
  }
12637
12664
  await ensureFontsForResolvedConfig(templateConfig);
12665
+ const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
12666
+ await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
12638
12667
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12639
12668
  setPackageApiUrl2(this.config.imageProxyUrl);
12640
12669
  const canvasWidth = templateConfig.canvas.width;
@@ -12646,6 +12675,8 @@ class PixldocsRenderer {
12646
12675
  */
12647
12676
  async renderAllPageSvgs(templateConfig) {
12648
12677
  await ensureFontsForResolvedConfig(templateConfig);
12678
+ const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
12679
+ await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
12649
12680
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12650
12681
  setPackageApiUrl2(this.config.imageProxyUrl);
12651
12682
  const results = [];
@@ -12898,6 +12929,26 @@ class PixldocsRenderer {
12898
12929
  ]);
12899
12930
  await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
12900
12931
  }
12932
+ /**
12933
+ * Block until the webfonts referenced by `config` have actually loaded
12934
+ * (or `maxWaitMs` elapses). Used by the headless capture path BEFORE
12935
+ * mounting `PreviewCanvas`, so the synchronous `createText` auto-shrink
12936
+ * loop measures against final font metrics instead of fallback ones.
12937
+ *
12938
+ * Stronger than `ensureFontsForResolvedConfig` (which is fire-and-forget)
12939
+ * — this awaits each `document.fonts.load(spec)` AND `document.fonts.ready`,
12940
+ * racing the whole thing against `maxWaitMs` so a slow CDN can't hang the
12941
+ * renderer.
12942
+ */
12943
+ async awaitFontsForConfig(config, maxWaitMs) {
12944
+ if (typeof document === "undefined" || !document.fonts) return;
12945
+ void ensureFontsForResolvedConfig(config);
12946
+ await this.waitForRelevantFonts(config, maxWaitMs);
12947
+ await Promise.race([
12948
+ document.fonts.ready.catch(() => void 0).then(() => void 0),
12949
+ new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
12950
+ ]);
12951
+ }
12901
12952
  getNormalizedGradientStops(gradient) {
12902
12953
  const stops = Array.isArray(gradient == null ? void 0 : gradient.stops) ? gradient.stops.map((stop) => ({
12903
12954
  offset: Math.max(0, Math.min(1, Number((stop == null ? void 0 : stop.offset) ?? 0))),
@@ -12971,10 +13022,19 @@ class PixldocsRenderer {
12971
13022
  } catch {
12972
13023
  }
12973
13024
  }
12974
- async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality) {
13025
+ async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality, options = {}) {
12975
13026
  const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
12976
13027
  const canvasWidth = config.canvas.width;
12977
13028
  const canvasHeight = config.canvas.height;
13029
+ const hasAutoShrink = configHasAutoShrinkText(config);
13030
+ let firstMountSettled = false;
13031
+ let lateFontSettleDetected = false;
13032
+ if (typeof document !== "undefined" && document.fonts && hasAutoShrink) {
13033
+ document.fonts.ready.then(() => {
13034
+ if (firstMountSettled) lateFontSettleDetected = true;
13035
+ }).catch(() => {
13036
+ });
13037
+ }
12978
13038
  return new Promise((resolve, reject) => {
12979
13039
  const container = document.createElement("div");
12980
13040
  container.style.cssText = `
@@ -12987,6 +13047,8 @@ class PixldocsRenderer {
12987
13047
  cleanup();
12988
13048
  reject(new Error("Render timeout (30s)"));
12989
13049
  }, 3e4);
13050
+ let root;
13051
+ let mountKey = 0;
12990
13052
  const cleanup = () => {
12991
13053
  clearTimeout(timeout);
12992
13054
  try {
@@ -12995,6 +13057,46 @@ class PixldocsRenderer {
12995
13057
  }
12996
13058
  container.remove();
12997
13059
  };
13060
+ const remountWithFreshKey = async () => {
13061
+ mountKey += 1;
13062
+ try {
13063
+ clearMeasurementCache();
13064
+ } catch {
13065
+ }
13066
+ try {
13067
+ clearFabricCharCache();
13068
+ } catch {
13069
+ }
13070
+ try {
13071
+ root.unmount();
13072
+ } catch {
13073
+ }
13074
+ root = createRoot(container);
13075
+ await new Promise((settle) => {
13076
+ const onReadyOnce = () => {
13077
+ this.waitForCanvasScene(container, config, pageIndex).then(async () => {
13078
+ const fabricInstance = this.getFabricCanvasFromContainer(container);
13079
+ const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
13080
+ await this.waitForCanvasImages(container, expectedImageCount);
13081
+ await this.waitForStableTextMetrics(container, config);
13082
+ await this.waitForCanvasScene(container, config, pageIndex);
13083
+ if (!fabricInstance) return settle();
13084
+ settle();
13085
+ }).catch(() => settle());
13086
+ };
13087
+ root.render(
13088
+ createElement(PreviewCanvas2, {
13089
+ key: `remount-${mountKey}`,
13090
+ config,
13091
+ pageIndex,
13092
+ zoom: pixelRatio,
13093
+ absoluteZoom: true,
13094
+ skipFontReadyWait: false,
13095
+ onReady: onReadyOnce
13096
+ })
13097
+ );
13098
+ });
13099
+ };
12998
13100
  const onReady = () => {
12999
13101
  this.waitForCanvasScene(container, config, pageIndex).then(async () => {
13000
13102
  try {
@@ -13003,16 +13105,23 @@ class PixldocsRenderer {
13003
13105
  await this.waitForCanvasImages(container, expectedImageCount);
13004
13106
  await this.waitForStableTextMetrics(container, config);
13005
13107
  await this.waitForCanvasScene(container, config, pageIndex);
13108
+ firstMountSettled = true;
13109
+ if (hasAutoShrink && lateFontSettleDetected) {
13110
+ console.log("[canvas-renderer][parity] late font-settle detected — remounting for auto-shrink reflow");
13111
+ await remountWithFreshKey();
13112
+ }
13006
13113
  const fabricCanvas = container.querySelector("canvas.upper-canvas, canvas");
13007
13114
  const sourceCanvas = (fabricInstance == null ? void 0 : fabricInstance.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || fabricCanvas;
13115
+ const fabricInstanceAfter = this.getFabricCanvasFromContainer(container) || fabricInstance;
13116
+ const sourceCanvasAfter = (fabricInstanceAfter == null ? void 0 : fabricInstanceAfter.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || sourceCanvas;
13008
13117
  if (!sourceCanvas) {
13009
13118
  cleanup();
13010
13119
  reject(new Error("No canvas element found after render"));
13011
13120
  return;
13012
13121
  }
13013
13122
  const exportCanvas = document.createElement("canvas");
13014
- exportCanvas.width = sourceCanvas.width;
13015
- exportCanvas.height = sourceCanvas.height;
13123
+ exportCanvas.width = sourceCanvasAfter.width;
13124
+ exportCanvas.height = sourceCanvasAfter.height;
13016
13125
  const exportCtx = exportCanvas.getContext("2d");
13017
13126
  if (!exportCtx) {
13018
13127
  cleanup();
@@ -13020,10 +13129,10 @@ class PixldocsRenderer {
13020
13129
  return;
13021
13130
  }
13022
13131
  exportCtx.save();
13023
- exportCtx.scale(sourceCanvas.width / canvasWidth, sourceCanvas.height / canvasHeight);
13132
+ exportCtx.scale(sourceCanvasAfter.width / canvasWidth, sourceCanvasAfter.height / canvasHeight);
13024
13133
  this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
13025
13134
  exportCtx.restore();
13026
- exportCtx.drawImage(sourceCanvas, 0, 0);
13135
+ exportCtx.drawImage(sourceCanvasAfter, 0, 0);
13027
13136
  const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
13028
13137
  const dataUrl = exportCanvas.toDataURL(mimeType, quality);
13029
13138
  cleanup();
@@ -13034,7 +13143,7 @@ class PixldocsRenderer {
13034
13143
  }
13035
13144
  });
13036
13145
  };
13037
- const root = createRoot(container);
13146
+ root = createRoot(container);
13038
13147
  root.render(
13039
13148
  createElement(PreviewCanvas2, {
13040
13149
  config,