@pixldocs/canvas-renderer 0.5.15 → 0.5.17

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
@@ -91,8 +91,7 @@ export declare function embedFontsForConfig(pdf: jsPDF, config: any, fontBaseUrl
91
91
  export declare function embedFontsInPdf(pdf: jsPDF, fontFamilies: Set<string>, fontBaseUrl: string): Promise<Set<string>>;
92
92
 
93
93
  /**
94
- * Ensure all fonts required by a fully-resolved TemplateConfig are loaded
95
- * and available to Fabric/Canvas before rendering.
94
+ * Start loading all fonts required by a fully-resolved TemplateConfig.
96
95
  *
97
96
  * This is the **single API** consumers (and the renderer internally) should
98
97
  * call to guarantee font parity with EC2 `/render-from-form`.
@@ -101,8 +100,8 @@ export declare function embedFontsInPdf(pdf: jsPDF, fontFamilies: Set<string>, f
101
100
  * 1. Walks ALL text nodes (including clones/repeatables) collecting
102
101
  * fontFamily + fontWeight + fontStyle.
103
102
  * 2. Loads each unique family via Google Fonts CSS v1 (idempotent).
104
- * 3. Explicitly loads each weight+style combo via `document.fonts.load()`.
105
- * 4. Awaits `document.fonts.ready` so Fabric never paints with fallback faces.
103
+ * 3. Kicks off each weight+style combo via `document.fonts.load()` without
104
+ * blocking render completion; late font load reflow handles final metrics.
106
105
  *
107
106
  * Idempotent — safe to call multiple times for the same config.
108
107
  */
@@ -370,6 +369,8 @@ export declare class PixldocsRenderer {
370
369
  renderAllById(templateId: string, formData?: Record<string, any>, options?: Omit<RenderOptions, 'pageIndex'>): Promise<RenderResult[]>;
371
370
  private getExpectedImageCount;
372
371
  private waitForCanvasImages;
372
+ private waitForCanvasScene;
373
+ private waitForRelevantFonts;
373
374
  private getNormalizedGradientStops;
374
375
  private paintPageBackground;
375
376
  private renderPageViaPreviewCanvas;
package/dist/index.js CHANGED
@@ -2684,12 +2684,10 @@ const waitForFontsReady = async () => {
2684
2684
  if (!document.fonts) return;
2685
2685
  await withFontTimeout(document.fonts.ready, 2500);
2686
2686
  };
2687
- const DEFAULT_FONT_CHECK_TIMEOUT_MS = 3500;
2688
- const FONT_CHECK_POLL_MS = 60;
2689
2687
  const waitUntilFontsAvailable = async (fontFamilies, options) => {
2690
2688
  if (!document.fonts || fontFamilies.length === 0) return;
2691
- const timeoutMs = (options == null ? void 0 : options.timeoutMs) ?? DEFAULT_FONT_CHECK_TIMEOUT_MS;
2692
- const pollMs = (options == null ? void 0 : options.pollIntervalMs) ?? FONT_CHECK_POLL_MS;
2689
+ const timeoutMs = options == null ? void 0 : options.timeoutMs;
2690
+ const pollMs = options == null ? void 0 : options.pollIntervalMs;
2693
2691
  const deadline = Date.now() + timeoutMs;
2694
2692
  const check = () => fontFamilies.every(
2695
2693
  (f) => document.fonts.check(`16px "${f}"`) && document.fonts.check(`bold 16px "${f}"`)
@@ -11688,18 +11686,16 @@ function normalizeFontFamily(fontStack) {
11688
11686
  }
11689
11687
  const loadedFonts = /* @__PURE__ */ new Set();
11690
11688
  const loadingPromises = /* @__PURE__ */ new Map();
11691
- async function withTimeout(promise, timeoutMs = 4e3) {
11689
+ function withTimeout(promise, timeoutMs = 4e3) {
11692
11690
  let timeoutId;
11693
- try {
11694
- return await Promise.race([
11695
- promise,
11696
- new Promise((resolve) => {
11697
- timeoutId = setTimeout(resolve, timeoutMs);
11698
- })
11699
- ]);
11700
- } finally {
11691
+ return Promise.race([
11692
+ promise,
11693
+ new Promise((resolve) => {
11694
+ timeoutId = setTimeout(resolve, timeoutMs);
11695
+ })
11696
+ ]).finally(() => {
11701
11697
  if (timeoutId) clearTimeout(timeoutId);
11702
- }
11698
+ });
11703
11699
  }
11704
11700
  async function loadGoogleFontCSS(rawFontFamily) {
11705
11701
  if (!rawFontFamily || typeof document === "undefined") return;
@@ -11721,10 +11717,6 @@ async function loadGoogleFontCSS(rawFontFamily) {
11721
11717
  link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
11722
11718
  document.head.appendChild(link);
11723
11719
  });
11724
- if (document.fonts) {
11725
- await withTimeout(document.fonts.load(`16px "${fontFamily}"`), 2500);
11726
- await withTimeout(document.fonts.ready, 2500);
11727
- }
11728
11720
  loadedFonts.add(fontFamily);
11729
11721
  } catch (e) {
11730
11722
  console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
@@ -11836,17 +11828,15 @@ async function ensureFontsForResolvedConfig(config) {
11836
11828
  if (typeof document === "undefined") return;
11837
11829
  const descriptors = collectFontDescriptorsFromConfig(config);
11838
11830
  const families = new Set(descriptors.map((d) => d.family));
11839
- await withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 8e3);
11831
+ void withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 2500);
11840
11832
  if (document.fonts) {
11841
- const loadPromises = descriptors.map((d) => {
11833
+ descriptors.forEach((d) => {
11842
11834
  const stylePrefix = d.style === "italic" ? "italic " : "";
11843
11835
  const weightStr = String(d.weight);
11844
11836
  const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
11845
- return document.fonts.load(spec).catch(() => {
11837
+ document.fonts.load(spec).catch(() => {
11846
11838
  });
11847
11839
  });
11848
- await withTimeout(Promise.all(loadPromises), 8e3);
11849
- await withTimeout(document.fonts.ready, 2500);
11850
11840
  }
11851
11841
  }
11852
11842
  function PixldocsPreview(props) {
@@ -12291,6 +12281,49 @@ class PixldocsRenderer {
12291
12281
  setTimeout(check, 0);
12292
12282
  });
12293
12283
  }
12284
+ waitForCanvasScene(container, config, pageIndex, maxWaitMs = 8e3, pollMs = 50) {
12285
+ return new Promise((resolve) => {
12286
+ var _a, _b;
12287
+ const start = Date.now();
12288
+ const pageHasContent = (((_b = (_a = config.pages[pageIndex]) == null ? void 0 : _a.children) == null ? void 0 : _b.length) ?? 0) > 0;
12289
+ const settle = () => requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
12290
+ const check = () => {
12291
+ const fabricCanvas = this.getFabricCanvasFromContainer(container);
12292
+ const lowerCanvas = (fabricCanvas == null ? void 0 : fabricCanvas.lowerCanvasEl) || container.querySelector("canvas.lower-canvas, canvas");
12293
+ const objectCount = typeof (fabricCanvas == null ? void 0 : fabricCanvas.getObjects) === "function" ? fabricCanvas.getObjects().length : 0;
12294
+ const ready = !!lowerCanvas && (!pageHasContent || objectCount > 0);
12295
+ if (ready) {
12296
+ console.log(`[canvas-renderer][scene-wait] ready after ${Date.now() - start}ms (objects=${objectCount})`);
12297
+ settle();
12298
+ return;
12299
+ }
12300
+ if (Date.now() - start >= maxWaitMs) {
12301
+ console.warn(`[canvas-renderer][scene-wait-timeout] elapsed=${Date.now() - start}ms objects=${objectCount} pageHasContent=${pageHasContent}`);
12302
+ settle();
12303
+ return;
12304
+ }
12305
+ setTimeout(check, pollMs);
12306
+ };
12307
+ setTimeout(check, 0);
12308
+ });
12309
+ }
12310
+ async waitForRelevantFonts(config, maxWaitMs = 1800) {
12311
+ if (typeof document === "undefined" || !document.fonts) return;
12312
+ const descriptors = collectFontDescriptorsFromConfig(config);
12313
+ if (descriptors.length === 0) return;
12314
+ const loads = Promise.all(
12315
+ descriptors.map((descriptor) => {
12316
+ const stylePrefix = descriptor.style === "italic" ? "italic " : "";
12317
+ const spec = `${stylePrefix}${descriptor.weight} 16px "${descriptor.family}"`;
12318
+ return document.fonts.load(spec).catch(() => []);
12319
+ })
12320
+ ).then(() => void 0);
12321
+ await Promise.race([
12322
+ loads,
12323
+ new Promise((resolve) => setTimeout(resolve, maxWaitMs))
12324
+ ]);
12325
+ await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())));
12326
+ }
12294
12327
  getNormalizedGradientStops(gradient) {
12295
12328
  const stops = Array.isArray(gradient == null ? void 0 : gradient.stops) ? gradient.stops.map((stop) => ({
12296
12329
  offset: Math.max(0, Math.min(1, Number((stop == null ? void 0 : stop.offset) ?? 0))),
@@ -12403,10 +12436,11 @@ class PixldocsRenderer {
12403
12436
  );
12404
12437
  return;
12405
12438
  }
12406
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
12407
- this.waitForCanvasImages(container, expectedImageCount).then(async () => {
12439
+ this.waitForCanvasScene(container, config, pageIndex).then(async () => {
12408
12440
  try {
12409
12441
  const fabricInstance = this.getFabricCanvasFromContainer(container);
12442
+ const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
12443
+ await this.waitForCanvasImages(container, expectedImageCount);
12410
12444
  await this.waitForStableTextMetrics(container, config);
12411
12445
  const fabricCanvas = container.querySelector("canvas.upper-canvas, canvas");
12412
12446
  const sourceCanvas = (fabricInstance == null ? void 0 : fabricInstance.lowerCanvasEl) || container.querySelector("canvas.lower-canvas") || fabricCanvas;
@@ -12497,8 +12531,7 @@ class PixldocsRenderer {
12497
12531
  );
12498
12532
  return;
12499
12533
  }
12500
- const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
12501
- this.waitForCanvasImages(container, expectedImageCount).then(async () => {
12534
+ this.waitForCanvasScene(container, config, pageIndex).then(async () => {
12502
12535
  var _a, _b;
12503
12536
  try {
12504
12537
  const fabricInstance = this.getFabricCanvasFromContainer(container);
@@ -12507,6 +12540,8 @@ class PixldocsRenderer {
12507
12540
  reject(new Error("No Fabric canvas instance found for SVG capture"));
12508
12541
  return;
12509
12542
  }
12543
+ const expectedImageCount = this.getExpectedImageCount(config, pageIndex);
12544
+ await this.waitForCanvasImages(container, expectedImageCount);
12510
12545
  await this.waitForStableTextMetrics(container, config);
12511
12546
  const prevVPT = fabricInstance.viewportTransform ? [...fabricInstance.viewportTransform] : void 0;
12512
12547
  const prevSvgVPT = fabricInstance.svgViewportTransformation;
@@ -12630,14 +12665,9 @@ class PixldocsRenderer {
12630
12665
  return null;
12631
12666
  }
12632
12667
  async waitForStableTextMetrics(container, config) {
12633
- var _a;
12634
12668
  if (typeof document !== "undefined") {
12635
- await ensureFontsForResolvedConfig(config);
12636
- await ((_a = document.fonts) == null ? void 0 : _a.ready);
12637
- const fontFamilies = [...collectFontsFromConfig(config)].filter(Boolean);
12638
- if (fontFamilies.length > 0) {
12639
- await waitUntilFontsAvailable(fontFamilies, { timeoutMs: 4e3, pollIntervalMs: 50 });
12640
- }
12669
+ void ensureFontsForResolvedConfig(config);
12670
+ await this.waitForRelevantFonts(config);
12641
12671
  }
12642
12672
  const fabricInstance = this.getFabricCanvasFromContainer(container);
12643
12673
  if (!(fabricInstance == null ? void 0 : fabricInstance.getObjects)) return;
@@ -12647,9 +12677,9 @@ class PixldocsRenderer {
12647
12677
  clearMeasurementCache();
12648
12678
  };
12649
12679
  const reflowTextboxes = () => {
12650
- var _a2, _b, _c;
12680
+ var _a, _b, _c;
12651
12681
  const walk = (obj) => {
12652
- var _a3, _b2, _c2, _d;
12682
+ var _a2, _b2, _c2, _d;
12653
12683
  if (!obj) return;
12654
12684
  const children = Array.isArray(obj._objects) ? obj._objects : Array.isArray(obj.objects) ? obj.objects : [];
12655
12685
  if (children.length) children.forEach(walk);
@@ -12661,8 +12691,8 @@ class PixldocsRenderer {
12661
12691
  scaleY: obj.scaleY
12662
12692
  };
12663
12693
  const resetTextboxLayoutInternals = () => {
12664
- var _a4;
12665
- (_a4 = obj._clearCache) == null ? void 0 : _a4.call(obj);
12694
+ var _a3;
12695
+ (_a3 = obj._clearCache) == null ? void 0 : _a3.call(obj);
12666
12696
  obj.__charBounds = [];
12667
12697
  obj.__lineWidths = [];
12668
12698
  obj.__lineHeights = [];
@@ -12676,7 +12706,7 @@ class PixldocsRenderer {
12676
12706
  resetTextboxLayoutInternals();
12677
12707
  obj.initDimensions();
12678
12708
  if (saved.width != null) {
12679
- (_a3 = obj.set) == null ? void 0 : _a3.call(obj, {
12709
+ (_a2 = obj.set) == null ? void 0 : _a2.call(obj, {
12680
12710
  width: saved.width,
12681
12711
  scaleX: saved.scaleX,
12682
12712
  scaleY: saved.scaleY
@@ -12695,7 +12725,7 @@ class PixldocsRenderer {
12695
12725
  }
12696
12726
  };
12697
12727
  fabricInstance.getObjects().forEach(walk);
12698
- (_a2 = fabricInstance.calcOffset) == null ? void 0 : _a2.call(fabricInstance);
12728
+ (_a = fabricInstance.calcOffset) == null ? void 0 : _a.call(fabricInstance);
12699
12729
  (_b = fabricInstance.renderAll) == null ? void 0 : _b.call(fabricInstance);
12700
12730
  (_c = fabricInstance.requestRenderAll) == null ? void 0 : _c.call(fabricInstance);
12701
12731
  };