@pixldocs/canvas-renderer 0.5.55 → 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
@@ -5289,6 +5289,10 @@ function createText(element) {
5289
5289
  const splitByGrapheme = overflowPolicy === "auto-shrink" ? false : element.splitByGrapheme ?? element.wordWrap === "break-word";
5290
5290
  if (overflowPolicy === "auto-shrink") {
5291
5291
  const explicitLineCount = Math.max(1, text.split("\n").length);
5292
+ const debugAutoShrink = typeof window !== "undefined" && window.__pixldocsDebugAutoShrink === true;
5293
+ const startFontSize = fontSize;
5294
+ let breakReason = "min-font-size-reached";
5295
+ let lastIter = null;
5292
5296
  while (fontSize > 1) {
5293
5297
  const testTextbox = new fabric__namespace.Textbox(text, {
5294
5298
  width: fixedWidth,
@@ -5310,11 +5314,47 @@ function createText(element) {
5310
5314
  const lineWidths = testTextbox.__lineWidths;
5311
5315
  const maxLineWidth = lineWidths && lineWidths.length > 0 ? Math.max(...lineWidths) : 0;
5312
5316
  const fitsWidth = !widthDidGrow && maxLineWidth <= fixedWidth + 1;
5317
+ if (debugAutoShrink) {
5318
+ lastIter = {
5319
+ fontSize,
5320
+ renderedLineCount,
5321
+ explicitLineCount,
5322
+ textHeight,
5323
+ maxLineWidth,
5324
+ fixedWidth,
5325
+ widthDidGrow,
5326
+ hasNoImplicitWrap,
5327
+ fitsHeight,
5328
+ fitsWidth
5329
+ };
5330
+ }
5313
5331
  if (hasNoImplicitWrap && fitsHeight && fitsWidth) {
5332
+ breakReason = "fits";
5314
5333
  break;
5315
5334
  }
5316
5335
  fontSize--;
5317
5336
  }
5337
+ if (debugAutoShrink) {
5338
+ console.log("[auto-shrink][diag]", {
5339
+ id: element.id,
5340
+ name: element.name,
5341
+ text,
5342
+ fontFamily: element.fontFamily,
5343
+ fontWeight: element.fontWeight,
5344
+ elementWidth: element.width,
5345
+ elementHeight: element.height,
5346
+ scaleX: element.scaleX ?? 1,
5347
+ scaleY: element.scaleY ?? 1,
5348
+ fixedWidth,
5349
+ baseHeight,
5350
+ startFontSize,
5351
+ finalFontSize: fontSize,
5352
+ breakReason,
5353
+ lastIter,
5354
+ fontCheckRegular: typeof document !== "undefined" && document.fonts ? document.fonts.check(`16px "${element.fontFamily || "Open Sans"}"`) : null,
5355
+ fontCheckBold: typeof document !== "undefined" && document.fonts ? document.fonts.check(`bold 16px "${element.fontFamily || "Open Sans"}"`) : null
5356
+ });
5357
+ }
5318
5358
  }
5319
5359
  if (overflowPolicy === "max-lines-ellipsis") {
5320
5360
  const originalText = element.text || "Text";
@@ -12431,7 +12471,7 @@ function PixldocsPreview(props) {
12431
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..." }) })
12432
12472
  ] });
12433
12473
  }
12434
- const PACKAGE_VERSION = "0.5.54";
12474
+ const PACKAGE_VERSION = "0.5.57";
12435
12475
  let __underlineFixInstalled = false;
12436
12476
  function installUnderlineFix(fab) {
12437
12477
  var _a;
@@ -12528,6 +12568,22 @@ function installUnderlineFix(fab) {
12528
12568
  __underlineFixInstalled = true;
12529
12569
  console.log(`[canvas-renderer] underline-fix monkey patch installed (v${PACKAGE_VERSION})`);
12530
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
+ }
12531
12587
  class PixldocsRenderer {
12532
12588
  constructor(config) {
12533
12589
  __publicField(this, "config");
@@ -12554,6 +12610,11 @@ class PixldocsRenderer {
12554
12610
  throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12555
12611
  }
12556
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
+ }
12557
12618
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12558
12619
  setPackageApiUrl2(this.config.imageProxyUrl);
12559
12620
  const dataUrl = await this.renderPageViaPreviewCanvas(
@@ -12561,7 +12622,8 @@ class PixldocsRenderer {
12561
12622
  pageIndex,
12562
12623
  pixelRatio,
12563
12624
  format,
12564
- quality
12625
+ quality,
12626
+ { skipFontReadyWait: options.skipFontReadyWait, waitForFontsMs: options.waitForFontsMs }
12565
12627
  );
12566
12628
  return {
12567
12629
  dataUrl,
@@ -12575,9 +12637,14 @@ class PixldocsRenderer {
12575
12637
  * Render all pages and return array of results.
12576
12638
  */
12577
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
+ }
12578
12645
  const results = [];
12579
12646
  for (let i = 0; i < templateConfig.pages.length; i++) {
12580
- results.push(await this.render(templateConfig, { ...options, pageIndex: i }));
12647
+ results.push(await this.render(templateConfig, { ...options, pageIndex: i, skipFontReadyWait: true }));
12581
12648
  }
12582
12649
  return results;
12583
12650
  }
@@ -12614,6 +12681,8 @@ class PixldocsRenderer {
12614
12681
  throw new Error(`Page index ${pageIndex} not found (template has ${templateConfig.pages.length} pages)`);
12615
12682
  }
12616
12683
  await ensureFontsForResolvedConfig(templateConfig);
12684
+ const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
12685
+ await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
12617
12686
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12618
12687
  setPackageApiUrl2(this.config.imageProxyUrl);
12619
12688
  const canvasWidth = templateConfig.canvas.width;
@@ -12625,6 +12694,8 @@ class PixldocsRenderer {
12625
12694
  */
12626
12695
  async renderAllPageSvgs(templateConfig) {
12627
12696
  await ensureFontsForResolvedConfig(templateConfig);
12697
+ const hasAutoShrinkSvg = configHasAutoShrinkText(templateConfig);
12698
+ await this.awaitFontsForConfig(templateConfig, hasAutoShrinkSvg ? 4e3 : 1800);
12628
12699
  const { setPackageApiUrl: setPackageApiUrl2 } = await Promise.resolve().then(() => appApi);
12629
12700
  setPackageApiUrl2(this.config.imageProxyUrl);
12630
12701
  const results = [];
@@ -12877,6 +12948,26 @@ class PixldocsRenderer {
12877
12948
  ]);
12878
12949
  await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
12879
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
+ }
12880
12971
  getNormalizedGradientStops(gradient) {
12881
12972
  const stops = Array.isArray(gradient == null ? void 0 : gradient.stops) ? gradient.stops.map((stop) => ({
12882
12973
  offset: Math.max(0, Math.min(1, Number((stop == null ? void 0 : stop.offset) ?? 0))),
@@ -12950,10 +13041,19 @@ class PixldocsRenderer {
12950
13041
  } catch {
12951
13042
  }
12952
13043
  }
12953
- async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality) {
13044
+ async renderPageViaPreviewCanvas(config, pageIndex, pixelRatio, format, quality, options = {}) {
12954
13045
  const { PreviewCanvas: PreviewCanvas2 } = await Promise.resolve().then(() => PreviewCanvas$1);
12955
13046
  const canvasWidth = config.canvas.width;
12956
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
+ }
12957
13057
  return new Promise((resolve, reject) => {
12958
13058
  const container = document.createElement("div");
12959
13059
  container.style.cssText = `
@@ -12966,6 +13066,8 @@ class PixldocsRenderer {
12966
13066
  cleanup();
12967
13067
  reject(new Error("Render timeout (30s)"));
12968
13068
  }, 3e4);
13069
+ let root;
13070
+ let mountKey = 0;
12969
13071
  const cleanup = () => {
12970
13072
  clearTimeout(timeout);
12971
13073
  try {
@@ -12974,6 +13076,46 @@ class PixldocsRenderer {
12974
13076
  }
12975
13077
  container.remove();
12976
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
+ };
12977
13119
  const onReady = () => {
12978
13120
  this.waitForCanvasScene(container, config, pageIndex).then(async () => {
12979
13121
  try {
@@ -12982,16 +13124,23 @@ class PixldocsRenderer {
12982
13124
  await this.waitForCanvasImages(container, expectedImageCount);
12983
13125
  await this.waitForStableTextMetrics(container, config);
12984
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
+ }
12985
13132
  const fabricCanvas = container.querySelector("canvas.upper-canvas, canvas");
12986
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;
12987
13136
  if (!sourceCanvas) {
12988
13137
  cleanup();
12989
13138
  reject(new Error("No canvas element found after render"));
12990
13139
  return;
12991
13140
  }
12992
13141
  const exportCanvas = document.createElement("canvas");
12993
- exportCanvas.width = sourceCanvas.width;
12994
- exportCanvas.height = sourceCanvas.height;
13142
+ exportCanvas.width = sourceCanvasAfter.width;
13143
+ exportCanvas.height = sourceCanvasAfter.height;
12995
13144
  const exportCtx = exportCanvas.getContext("2d");
12996
13145
  if (!exportCtx) {
12997
13146
  cleanup();
@@ -12999,10 +13148,10 @@ class PixldocsRenderer {
12999
13148
  return;
13000
13149
  }
13001
13150
  exportCtx.save();
13002
- exportCtx.scale(sourceCanvas.width / canvasWidth, sourceCanvas.height / canvasHeight);
13151
+ exportCtx.scale(sourceCanvasAfter.width / canvasWidth, sourceCanvasAfter.height / canvasHeight);
13003
13152
  this.paintPageBackground(exportCtx, config.pages[pageIndex], canvasWidth, canvasHeight);
13004
13153
  exportCtx.restore();
13005
- exportCtx.drawImage(sourceCanvas, 0, 0);
13154
+ exportCtx.drawImage(sourceCanvasAfter, 0, 0);
13006
13155
  const mimeType = format === "jpeg" ? "image/jpeg" : format === "webp" ? "image/webp" : "image/png";
13007
13156
  const dataUrl = exportCanvas.toDataURL(mimeType, quality);
13008
13157
  cleanup();
@@ -13013,7 +13162,7 @@ class PixldocsRenderer {
13013
13162
  }
13014
13163
  });
13015
13164
  };
13016
- const root = client.createRoot(container);
13165
+ root = client.createRoot(container);
13017
13166
  root.render(
13018
13167
  react.createElement(PreviewCanvas2, {
13019
13168
  config,
@@ -15480,6 +15629,11 @@ async function warmTemplateFromForm(options) {
15480
15629
  if (signal == null ? void 0 : signal.aborted) return;
15481
15630
  await warmResolvedTemplateForPreview(resolved.config, { signal, imageProxyUrl });
15482
15631
  }
15632
+ function setAutoShrinkDebug(enabled) {
15633
+ if (typeof window !== "undefined") {
15634
+ window.__pixldocsDebugAutoShrink = !!enabled;
15635
+ }
15636
+ }
15483
15637
  exports.FONT_FALLBACK_DEVANAGARI = FONT_FALLBACK_DEVANAGARI;
15484
15638
  exports.FONT_FALLBACK_SYMBOLS = FONT_FALLBACK_SYMBOLS;
15485
15639
  exports.FONT_FILES = FONT_FILES;
@@ -15507,6 +15661,7 @@ exports.resolveFontWeight = resolveFontWeight;
15507
15661
  exports.resolveFromForm = resolveFromForm;
15508
15662
  exports.resolveTemplateData = resolveTemplateData;
15509
15663
  exports.rewriteSvgFontsForJsPDF = rewriteSvgFontsForJsPDF;
15664
+ exports.setAutoShrinkDebug = setAutoShrinkDebug;
15510
15665
  exports.setBundledAssetPrefixes = setBundledAssetPrefixes;
15511
15666
  exports.warmResolvedTemplateForPreview = warmResolvedTemplateForPreview;
15512
15667
  exports.warmTemplateFromForm = warmTemplateFromForm;