@pixldocs/canvas-renderer 0.5.76 → 0.5.78

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.js CHANGED
@@ -5194,6 +5194,17 @@ function buildRoundedTrianglePath(w, h, rTop, rBR, rBL) {
5194
5194
  ];
5195
5195
  return parts.join(" ");
5196
5196
  }
5197
+ const roundDiag = (value) => {
5198
+ if (typeof value !== "number") return value;
5199
+ return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
5200
+ };
5201
+ const stringifyDiag = (payload) => {
5202
+ try {
5203
+ return JSON.stringify(payload, (_key, value) => roundDiag(value));
5204
+ } catch {
5205
+ return String(payload);
5206
+ }
5207
+ };
5197
5208
  function buildRoundedRectPath(w, h, tl, tr, br, bl) {
5198
5209
  return buildRoundedRectPath$1(w, h, getRoundedRectRadii(w, h, { rxTL: tl, rxTR: tr, rxBR: br, rxBL: bl }));
5199
5210
  }
@@ -5304,11 +5315,11 @@ function createShape(element) {
5304
5315
  }
5305
5316
  }
5306
5317
  function createText(element) {
5307
- var _a;
5318
+ var _a, _b;
5308
5319
  const overflowPolicy = element.overflowPolicy || "grow-and-push";
5309
5320
  let text = element.text || "Text";
5310
5321
  let fontSize = element.fontSize || 16;
5311
- element.minFontSize || 8;
5322
+ const minFontSize = element.minFontSize || 8;
5312
5323
  const maxLines = element.maxLines || 3;
5313
5324
  const baseWidth = element.width && element.width > 0 ? element.width : 200;
5314
5325
  const baseHeight = element.height;
@@ -5320,6 +5331,7 @@ function createText(element) {
5320
5331
  const startFontSize = fontSize;
5321
5332
  let breakReason = "min-font-size-reached";
5322
5333
  let lastIter = null;
5334
+ const iterationSamples = [];
5323
5335
  while (fontSize > 1) {
5324
5336
  const testTextbox = new fabric.Textbox(text, {
5325
5337
  width: fixedWidth,
@@ -5337,7 +5349,7 @@ function createText(element) {
5337
5349
  const hasNoImplicitWrap = renderedLineCount <= explicitLineCount;
5338
5350
  const fitsHeight = !baseHeight || textHeight <= baseHeight;
5339
5351
  const widthMetrics = getTextboxWidthFitMetrics(testTextbox, fixedWidth);
5340
- const { fitsWidth } = widthMetrics;
5352
+ const { maxLineWidth, widthDidGrow, fitsWidth } = widthMetrics;
5341
5353
  if (debugAutoShrink) {
5342
5354
  lastIter = {
5343
5355
  fontSize,
@@ -5348,8 +5360,12 @@ function createText(element) {
5348
5360
  fixedWidth,
5349
5361
  hasNoImplicitWrap,
5350
5362
  fitsHeight,
5351
- fitsWidth
5363
+ fitsWidth,
5364
+ textLines: (testTextbox.textLines || []).map((line) => Array.isArray(line) ? line.join("") : String(line ?? ""))
5352
5365
  };
5366
+ if (iterationSamples.length < 6 || fontSize <= minFontSize + 2) {
5367
+ iterationSamples.push(lastIter);
5368
+ }
5353
5369
  }
5354
5370
  if (hasNoImplicitWrap && fitsHeight && fitsWidth) {
5355
5371
  breakReason = "fits";
@@ -5358,10 +5374,11 @@ function createText(element) {
5358
5374
  fontSize--;
5359
5375
  }
5360
5376
  if (debugAutoShrink) {
5361
- console.log("[auto-shrink][diag]", {
5377
+ console.log("[auto-shrink][diag] " + stringifyDiag({
5362
5378
  id: element.id,
5363
5379
  name: element.name,
5364
- text,
5380
+ text: text.slice(0, 180),
5381
+ textLength: text.length,
5365
5382
  fontFamily: element.fontFamily,
5366
5383
  fontWeight: element.fontWeight,
5367
5384
  elementWidth: element.width,
@@ -5373,10 +5390,11 @@ function createText(element) {
5373
5390
  startFontSize,
5374
5391
  finalFontSize: fontSize,
5375
5392
  breakReason,
5393
+ iterations: iterationSamples,
5376
5394
  lastIter,
5377
5395
  fontCheckRegular: typeof document !== "undefined" && document.fonts ? document.fonts.check(`16px "${element.fontFamily || "Open Sans"}"`) : null,
5378
5396
  fontCheckBold: typeof document !== "undefined" && document.fonts ? document.fonts.check(`bold 16px "${element.fontFamily || "Open Sans"}"`) : null
5379
- });
5397
+ }));
5380
5398
  }
5381
5399
  }
5382
5400
  if (overflowPolicy === "max-lines-ellipsis") {
@@ -5451,6 +5469,22 @@ function createText(element) {
5451
5469
  textbox.setCoords();
5452
5470
  }
5453
5471
  textbox.dirty = true;
5472
+ if (overflowPolicy === "auto-shrink" && typeof window !== "undefined" && window.__pixldocsDebugAutoShrink === true) {
5473
+ console.log("[auto-shrink][final-textbox] " + stringifyDiag({
5474
+ id: element.id,
5475
+ name: element.name,
5476
+ text: text.slice(0, 180),
5477
+ targetWidth,
5478
+ targetScaleX,
5479
+ targetScaleY,
5480
+ finalFontSize: fontSize,
5481
+ textboxWidth: textbox.width,
5482
+ textboxHeight: textbox.height,
5483
+ lineCount: ((_b = textbox.textLines) == null ? void 0 : _b.length) || 0,
5484
+ lines: (textbox.textLines || []).map((line) => Array.isArray(line) ? line.join("") : String(line ?? "")),
5485
+ widthMetrics: getTextboxWidthFitMetrics(textbox, targetWidth)
5486
+ }));
5487
+ }
5454
5488
  applyTextBackground(textbox, extractTextBgConfig(element));
5455
5489
  const shadow = buildTextShadow(element);
5456
5490
  if (shadow) textbox.set("shadow", shadow);
@@ -11611,6 +11645,232 @@ function applyContentBoundsPagination(config) {
11611
11645
  if (!mutated) return config;
11612
11646
  return { ...config, pages: resultPages };
11613
11647
  }
11648
+ function normalizeFontFamily(fontStack) {
11649
+ const first = fontStack.split(",")[0].trim();
11650
+ return first.replace(/^['"]|['"]$/g, "");
11651
+ }
11652
+ const loadedFonts = /* @__PURE__ */ new Set();
11653
+ const loadingPromises = /* @__PURE__ */ new Map();
11654
+ function withTimeout(promise, timeoutMs = 4e3) {
11655
+ let timeoutId;
11656
+ return Promise.race([
11657
+ promise,
11658
+ new Promise((resolve) => {
11659
+ timeoutId = setTimeout(resolve, timeoutMs);
11660
+ })
11661
+ ]).finally(() => {
11662
+ if (timeoutId) clearTimeout(timeoutId);
11663
+ });
11664
+ }
11665
+ async function loadGoogleFontCSS(rawFontFamily) {
11666
+ if (!rawFontFamily || typeof document === "undefined") return;
11667
+ const fontFamily = normalizeFontFamily(rawFontFamily);
11668
+ if (!fontFamily) return;
11669
+ if (loadedFonts.has(fontFamily)) return;
11670
+ const existing = loadingPromises.get(fontFamily);
11671
+ if (existing) return existing;
11672
+ const promise = (async () => {
11673
+ try {
11674
+ const encoded = encodeURIComponent(fontFamily);
11675
+ const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
11676
+ const link = document.createElement("link");
11677
+ link.rel = "stylesheet";
11678
+ link.href = url;
11679
+ link.crossOrigin = "anonymous";
11680
+ await new Promise((resolve, reject) => {
11681
+ link.onload = () => resolve();
11682
+ link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
11683
+ document.head.appendChild(link);
11684
+ });
11685
+ loadedFonts.add(fontFamily);
11686
+ } catch (e) {
11687
+ console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
11688
+ }
11689
+ })();
11690
+ loadingPromises.set(fontFamily, promise);
11691
+ await promise;
11692
+ loadingPromises.delete(fontFamily);
11693
+ }
11694
+ function collectFontsFromConfig(config) {
11695
+ var _a;
11696
+ const fonts = /* @__PURE__ */ new Set();
11697
+ fonts.add("Open Sans");
11698
+ fonts.add("Hind");
11699
+ function walk(nodes) {
11700
+ var _a2;
11701
+ if (!nodes) return;
11702
+ for (const node of nodes) {
11703
+ if (node.fontFamily) fonts.add(normalizeFontFamily(node.fontFamily));
11704
+ if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) fonts.add(normalizeFontFamily(node.smartProps.fontFamily));
11705
+ if (node.styles && Array.isArray(node.styles)) {
11706
+ for (const lineStyle of node.styles) {
11707
+ if (lineStyle && typeof lineStyle === "object") {
11708
+ for (const charStyle of Object.values(lineStyle)) {
11709
+ if (charStyle == null ? void 0 : charStyle.fontFamily) fonts.add(normalizeFontFamily(charStyle.fontFamily));
11710
+ }
11711
+ }
11712
+ }
11713
+ }
11714
+ if (node.children) walk(node.children);
11715
+ }
11716
+ }
11717
+ for (const page of config.pages || []) {
11718
+ walk(page.children || []);
11719
+ }
11720
+ if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
11721
+ for (const def of Object.values(config.themeConfig.variables)) {
11722
+ if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
11723
+ if (def.label && /font/i.test(def.label)) {
11724
+ fonts.add(normalizeFontFamily(def.value));
11725
+ }
11726
+ }
11727
+ }
11728
+ }
11729
+ return fonts;
11730
+ }
11731
+ function collectFontDescriptorsFromConfig(config) {
11732
+ var _a;
11733
+ const seen = /* @__PURE__ */ new Set();
11734
+ const descriptors = [];
11735
+ function add(family, weight, style) {
11736
+ const f = normalizeFontFamily(family);
11737
+ if (!f) return;
11738
+ const w = weight ?? 400;
11739
+ const s = style ?? "normal";
11740
+ const key = `${f}|${w}|${s}`;
11741
+ if (seen.has(key)) return;
11742
+ seen.add(key);
11743
+ descriptors.push({ family: f, weight: w, style: s });
11744
+ }
11745
+ function walk(nodes) {
11746
+ var _a2;
11747
+ if (!nodes) return;
11748
+ for (const node of nodes) {
11749
+ if (node.fontFamily) {
11750
+ add(node.fontFamily, node.fontWeight, node.fontStyle);
11751
+ if (node.type === "text") {
11752
+ for (const w of [300, 400, 500, 600, 700]) {
11753
+ add(node.fontFamily, w, node.fontStyle);
11754
+ }
11755
+ }
11756
+ }
11757
+ if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) {
11758
+ add(node.smartProps.fontFamily, node.smartProps.fontWeight, node.smartProps.fontStyle);
11759
+ }
11760
+ if (node.styles) {
11761
+ const styleEntries = Array.isArray(node.styles) ? node.styles : Object.values(node.styles);
11762
+ for (const lineStyle of styleEntries) {
11763
+ if (lineStyle && typeof lineStyle === "object") {
11764
+ for (const charStyle of Object.values(lineStyle)) {
11765
+ if (charStyle == null ? void 0 : charStyle.fontFamily) {
11766
+ add(charStyle.fontFamily, charStyle.fontWeight, charStyle.fontStyle);
11767
+ }
11768
+ }
11769
+ }
11770
+ }
11771
+ }
11772
+ if (node.children) walk(node.children);
11773
+ }
11774
+ }
11775
+ add("Open Sans", 400, "normal");
11776
+ add("Hind", 400, "normal");
11777
+ add("Hind", 700, "normal");
11778
+ for (const page of config.pages || []) {
11779
+ walk(page.children || []);
11780
+ }
11781
+ if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
11782
+ for (const def of Object.values(config.themeConfig.variables)) {
11783
+ if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
11784
+ if (def.label && /font/i.test(def.label)) {
11785
+ add(def.value);
11786
+ }
11787
+ }
11788
+ }
11789
+ }
11790
+ return descriptors;
11791
+ }
11792
+ async function ensureFontsForResolvedConfig(config) {
11793
+ if (typeof document === "undefined") return;
11794
+ const descriptors = collectFontDescriptorsFromConfig(config);
11795
+ const families = new Set(descriptors.map((d) => d.family));
11796
+ await withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 3500);
11797
+ if (document.fonts) {
11798
+ descriptors.forEach((d) => {
11799
+ const stylePrefix = d.style === "italic" ? "italic " : "";
11800
+ const weightStr = String(d.weight);
11801
+ const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
11802
+ document.fonts.load(spec).catch(() => {
11803
+ });
11804
+ });
11805
+ }
11806
+ }
11807
+ function configHasAutoShrinkText$1(config) {
11808
+ var _a;
11809
+ if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
11810
+ const walk = (nodes) => {
11811
+ for (const node of nodes || []) {
11812
+ if (!node) continue;
11813
+ if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
11814
+ if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
11815
+ }
11816
+ return false;
11817
+ };
11818
+ for (const page of config.pages) {
11819
+ if (walk(page.children || [])) return true;
11820
+ }
11821
+ return false;
11822
+ }
11823
+ async function awaitFontsForConfig(config, maxWaitMs) {
11824
+ if (typeof document === "undefined" || !document.fonts) return;
11825
+ await ensureFontsForResolvedConfig(config);
11826
+ const descriptors = collectFontDescriptorsFromConfig(config);
11827
+ if (descriptors.length === 0) return;
11828
+ const loads = Promise.all(
11829
+ descriptors.map((d) => {
11830
+ const stylePrefix = d.style === "italic" ? "italic " : "";
11831
+ const spec = `${stylePrefix}${d.weight} 16px "${d.family}"`;
11832
+ return document.fonts.load(spec).catch(() => []);
11833
+ })
11834
+ ).then(() => void 0);
11835
+ await Promise.race([
11836
+ loads,
11837
+ new Promise((resolve) => setTimeout(resolve, maxWaitMs))
11838
+ ]);
11839
+ await Promise.race([
11840
+ document.fonts.ready.catch(() => void 0).then(() => void 0),
11841
+ new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
11842
+ ]);
11843
+ const checkSpecs = [];
11844
+ for (const d of descriptors) {
11845
+ const stylePrefix = d.style === "italic" ? "italic " : "";
11846
+ checkSpecs.push(`${stylePrefix}${d.weight} 16px "${d.family}"`);
11847
+ }
11848
+ const startedAt = Date.now();
11849
+ const pollBudget = Math.min(maxWaitMs, 2500);
11850
+ const allReady = () => {
11851
+ for (const spec of checkSpecs) {
11852
+ try {
11853
+ if (!document.fonts.check(spec)) return false;
11854
+ } catch {
11855
+ }
11856
+ }
11857
+ return true;
11858
+ };
11859
+ while (!allReady() && Date.now() - startedAt < pollBudget) {
11860
+ await new Promise((resolve) => setTimeout(resolve, 50));
11861
+ }
11862
+ await new Promise(
11863
+ (resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
11864
+ );
11865
+ }
11866
+ async function awaitFontsBeforeTextReflow(config) {
11867
+ if (typeof document === "undefined" || !configHasAutoShrinkText$1(config)) return;
11868
+ try {
11869
+ await awaitFontsForConfig(config, 4e3);
11870
+ } catch (error) {
11871
+ console.warn("[@pixldocs/canvas-renderer] Font wait before text reflow failed:", error);
11872
+ }
11873
+ }
11614
11874
  function repeatablePageToSection(page) {
11615
11875
  return {
11616
11876
  id: page.id,
@@ -11804,6 +12064,7 @@ async function resolveTemplateData(options) {
11804
12064
  }
11805
12065
  }
11806
12066
  const repeatablePagesInput = deriveRepeatablePagesFromTemplate(config, inlineFormSchema, mergedFormData);
12067
+ await awaitFontsBeforeTextReflow(config);
11807
12068
  const resolvedConfig = applyFormDataToConfig(
11808
12069
  config,
11809
12070
  mappings,
@@ -11948,6 +12209,7 @@ async function resolveFromForm(options) {
11948
12209
  };
11949
12210
  collectFormats(formConfig.sections);
11950
12211
  }
12212
+ await awaitFontsBeforeTextReflow(templateConfig);
11951
12213
  let resolvedConfig = applyFormDataToConfig(
11952
12214
  templateConfig,
11953
12215
  mappings,
@@ -12126,224 +12388,6 @@ function paintRepeatableSections(config, repeatableSections) {
12126
12388
  }
12127
12389
  }
12128
12390
  }
12129
- function normalizeFontFamily(fontStack) {
12130
- const first = fontStack.split(",")[0].trim();
12131
- return first.replace(/^['"]|['"]$/g, "");
12132
- }
12133
- const loadedFonts = /* @__PURE__ */ new Set();
12134
- const loadingPromises = /* @__PURE__ */ new Map();
12135
- function withTimeout(promise, timeoutMs = 4e3) {
12136
- let timeoutId;
12137
- return Promise.race([
12138
- promise,
12139
- new Promise((resolve) => {
12140
- timeoutId = setTimeout(resolve, timeoutMs);
12141
- })
12142
- ]).finally(() => {
12143
- if (timeoutId) clearTimeout(timeoutId);
12144
- });
12145
- }
12146
- async function loadGoogleFontCSS(rawFontFamily) {
12147
- if (!rawFontFamily || typeof document === "undefined") return;
12148
- const fontFamily = normalizeFontFamily(rawFontFamily);
12149
- if (!fontFamily) return;
12150
- if (loadedFonts.has(fontFamily)) return;
12151
- const existing = loadingPromises.get(fontFamily);
12152
- if (existing) return existing;
12153
- const promise = (async () => {
12154
- try {
12155
- const encoded = encodeURIComponent(fontFamily);
12156
- const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
12157
- const link = document.createElement("link");
12158
- link.rel = "stylesheet";
12159
- link.href = url;
12160
- link.crossOrigin = "anonymous";
12161
- await new Promise((resolve, reject) => {
12162
- link.onload = () => resolve();
12163
- link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
12164
- document.head.appendChild(link);
12165
- });
12166
- loadedFonts.add(fontFamily);
12167
- } catch (e) {
12168
- console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
12169
- }
12170
- })();
12171
- loadingPromises.set(fontFamily, promise);
12172
- await promise;
12173
- loadingPromises.delete(fontFamily);
12174
- }
12175
- function collectFontsFromConfig(config) {
12176
- var _a;
12177
- const fonts = /* @__PURE__ */ new Set();
12178
- fonts.add("Open Sans");
12179
- fonts.add("Hind");
12180
- function walk(nodes) {
12181
- var _a2;
12182
- if (!nodes) return;
12183
- for (const node of nodes) {
12184
- if (node.fontFamily) fonts.add(normalizeFontFamily(node.fontFamily));
12185
- if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) fonts.add(normalizeFontFamily(node.smartProps.fontFamily));
12186
- if (node.styles && Array.isArray(node.styles)) {
12187
- for (const lineStyle of node.styles) {
12188
- if (lineStyle && typeof lineStyle === "object") {
12189
- for (const charStyle of Object.values(lineStyle)) {
12190
- if (charStyle == null ? void 0 : charStyle.fontFamily) fonts.add(normalizeFontFamily(charStyle.fontFamily));
12191
- }
12192
- }
12193
- }
12194
- }
12195
- if (node.children) walk(node.children);
12196
- }
12197
- }
12198
- for (const page of config.pages || []) {
12199
- walk(page.children || []);
12200
- }
12201
- if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
12202
- for (const def of Object.values(config.themeConfig.variables)) {
12203
- if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
12204
- if (def.label && /font/i.test(def.label)) {
12205
- fonts.add(normalizeFontFamily(def.value));
12206
- }
12207
- }
12208
- }
12209
- }
12210
- return fonts;
12211
- }
12212
- function collectFontDescriptorsFromConfig(config) {
12213
- var _a;
12214
- const seen = /* @__PURE__ */ new Set();
12215
- const descriptors = [];
12216
- function add(family, weight, style) {
12217
- const f = normalizeFontFamily(family);
12218
- if (!f) return;
12219
- const w = weight ?? 400;
12220
- const s = style ?? "normal";
12221
- const key = `${f}|${w}|${s}`;
12222
- if (seen.has(key)) return;
12223
- seen.add(key);
12224
- descriptors.push({ family: f, weight: w, style: s });
12225
- }
12226
- function walk(nodes) {
12227
- var _a2;
12228
- if (!nodes) return;
12229
- for (const node of nodes) {
12230
- if (node.fontFamily) {
12231
- add(node.fontFamily, node.fontWeight, node.fontStyle);
12232
- if (node.type === "text") {
12233
- for (const w of [300, 400, 500, 600, 700]) {
12234
- add(node.fontFamily, w, node.fontStyle);
12235
- }
12236
- }
12237
- }
12238
- if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) {
12239
- add(node.smartProps.fontFamily, node.smartProps.fontWeight, node.smartProps.fontStyle);
12240
- }
12241
- if (node.styles) {
12242
- const styleEntries = Array.isArray(node.styles) ? node.styles : Object.values(node.styles);
12243
- for (const lineStyle of styleEntries) {
12244
- if (lineStyle && typeof lineStyle === "object") {
12245
- for (const charStyle of Object.values(lineStyle)) {
12246
- if (charStyle == null ? void 0 : charStyle.fontFamily) {
12247
- add(charStyle.fontFamily, charStyle.fontWeight, charStyle.fontStyle);
12248
- }
12249
- }
12250
- }
12251
- }
12252
- }
12253
- if (node.children) walk(node.children);
12254
- }
12255
- }
12256
- add("Open Sans", 400, "normal");
12257
- add("Hind", 400, "normal");
12258
- add("Hind", 700, "normal");
12259
- for (const page of config.pages || []) {
12260
- walk(page.children || []);
12261
- }
12262
- if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
12263
- for (const def of Object.values(config.themeConfig.variables)) {
12264
- if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
12265
- if (def.label && /font/i.test(def.label)) {
12266
- add(def.value);
12267
- }
12268
- }
12269
- }
12270
- }
12271
- return descriptors;
12272
- }
12273
- async function ensureFontsForResolvedConfig(config) {
12274
- if (typeof document === "undefined") return;
12275
- const descriptors = collectFontDescriptorsFromConfig(config);
12276
- const families = new Set(descriptors.map((d) => d.family));
12277
- await withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 3500);
12278
- if (document.fonts) {
12279
- descriptors.forEach((d) => {
12280
- const stylePrefix = d.style === "italic" ? "italic " : "";
12281
- const weightStr = String(d.weight);
12282
- const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
12283
- document.fonts.load(spec).catch(() => {
12284
- });
12285
- });
12286
- }
12287
- }
12288
- function configHasAutoShrinkText$1(config) {
12289
- var _a;
12290
- if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
12291
- const walk = (nodes) => {
12292
- for (const node of nodes || []) {
12293
- if (!node) continue;
12294
- if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
12295
- if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
12296
- }
12297
- return false;
12298
- };
12299
- for (const page of config.pages) {
12300
- if (walk(page.children || [])) return true;
12301
- }
12302
- return false;
12303
- }
12304
- async function awaitFontsForConfig(config, maxWaitMs) {
12305
- if (typeof document === "undefined" || !document.fonts) return;
12306
- await ensureFontsForResolvedConfig(config);
12307
- const descriptors = collectFontDescriptorsFromConfig(config);
12308
- if (descriptors.length === 0) return;
12309
- const loads = Promise.all(
12310
- descriptors.map((d) => {
12311
- const stylePrefix = d.style === "italic" ? "italic " : "";
12312
- const spec = `${stylePrefix}${d.weight} 16px "${d.family}"`;
12313
- return document.fonts.load(spec).catch(() => []);
12314
- })
12315
- ).then(() => void 0);
12316
- await Promise.race([
12317
- loads,
12318
- new Promise((resolve) => setTimeout(resolve, maxWaitMs))
12319
- ]);
12320
- await Promise.race([
12321
- document.fonts.ready.catch(() => void 0).then(() => void 0),
12322
- new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
12323
- ]);
12324
- const checkSpecs = [];
12325
- for (const d of descriptors) {
12326
- const stylePrefix = d.style === "italic" ? "italic " : "";
12327
- checkSpecs.push(`${stylePrefix}${d.weight} 16px "${d.family}"`);
12328
- }
12329
- const startedAt = Date.now();
12330
- const pollBudget = Math.min(maxWaitMs, 2500);
12331
- const allReady = () => {
12332
- for (const spec of checkSpecs) {
12333
- try {
12334
- if (!document.fonts.check(spec)) return false;
12335
- } catch {
12336
- }
12337
- }
12338
- return true;
12339
- };
12340
- while (!allReady() && Date.now() - startedAt < pollBudget) {
12341
- await new Promise((resolve) => setTimeout(resolve, 50));
12342
- }
12343
- await new Promise(
12344
- (resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
12345
- );
12346
- }
12347
12391
  const PREVIEW_DEBUG_PREFIX = "[canvas-renderer][preview-debug]";
12348
12392
  function countUnderlinedNodes(config) {
12349
12393
  var _a;
@@ -12534,7 +12578,18 @@ function PixldocsPreview(props) {
12534
12578
  !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..." }) })
12535
12579
  ] });
12536
12580
  }
12537
- const PACKAGE_VERSION = "0.5.76";
12581
+ const PACKAGE_VERSION = "0.5.78";
12582
+ const roundParityValue = (value) => {
12583
+ if (typeof value !== "number") return value;
12584
+ return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
12585
+ };
12586
+ function logJsonLine(tag, payload) {
12587
+ try {
12588
+ console.log(`${tag} ${JSON.stringify(payload, (_key, value) => roundParityValue(value))}`);
12589
+ } catch {
12590
+ console.log(tag, payload);
12591
+ }
12592
+ }
12538
12593
  let __underlineFixInstalled = false;
12539
12594
  function installUnderlineFix(fab) {
12540
12595
  var _a;
@@ -13410,6 +13465,42 @@ class PixldocsRenderer {
13410
13465
  }
13411
13466
  return null;
13412
13467
  }
13468
+ logFabricTextParitySnapshot(stage, fabricInstance) {
13469
+ var _a;
13470
+ if (typeof window === "undefined" || window.__pixldocsDebugAutoShrink !== true) return;
13471
+ const sample = [];
13472
+ const visit = (obj, groupPath = "") => {
13473
+ var _a2;
13474
+ if (!obj) return;
13475
+ if (obj instanceof fabric.Textbox) {
13476
+ const lineWidths = getCanvasMeasuredTextboxLineWidths(obj);
13477
+ sample.push({
13478
+ id: getObjectId(obj),
13479
+ groupPath,
13480
+ text: String(obj.text ?? "").slice(0, 180),
13481
+ left: obj.left,
13482
+ top: obj.top,
13483
+ width: obj.width,
13484
+ height: obj.height,
13485
+ scaleX: obj.scaleX,
13486
+ scaleY: obj.scaleY,
13487
+ fontSize: obj.fontSize,
13488
+ fontFamily: obj.fontFamily,
13489
+ fontWeight: obj.fontWeight,
13490
+ lineCount: ((_a2 = obj.textLines) == null ? void 0 : _a2.length) || 0,
13491
+ lines: (obj.textLines || []).map((line) => Array.isArray(line) ? line.join("") : String(line ?? "")),
13492
+ lineWidths,
13493
+ maxLineWidth: lineWidths.length ? Math.max(...lineWidths) : 0
13494
+ });
13495
+ }
13496
+ if (obj instanceof fabric.Group && typeof obj.getObjects === "function") {
13497
+ const nextPath = [groupPath, getObjectId(obj) || obj.type || "group"].filter(Boolean).join("/");
13498
+ obj.getObjects().forEach((child) => visit(child, nextPath));
13499
+ }
13500
+ };
13501
+ (_a = fabricInstance == null ? void 0 : fabricInstance.getObjects) == null ? void 0 : _a.call(fabricInstance).forEach((obj) => visit(obj));
13502
+ logJsonLine("[canvas-renderer][fabric-text-parity]", { stage, textboxes: sample.length, sample });
13503
+ }
13413
13504
  async waitForStableTextMetrics(container, config) {
13414
13505
  var _a, _b, _c;
13415
13506
  if (typeof document !== "undefined") {
@@ -13418,6 +13509,7 @@ class PixldocsRenderer {
13418
13509
  }
13419
13510
  const fabricInstance = this.getFabricCanvasFromContainer(container);
13420
13511
  if (!(fabricInstance == null ? void 0 : fabricInstance.getObjects)) return;
13512
+ this.logFabricTextParitySnapshot("before-stable-text-metrics", fabricInstance);
13421
13513
  const waitForPaint = () => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r())));
13422
13514
  const primeCharBounds = (obj) => {
13423
13515
  if (obj instanceof fabric.Textbox) {
@@ -13444,6 +13536,7 @@ class PixldocsRenderer {
13444
13536
  await waitForPaint();
13445
13537
  (_c = fabricInstance.renderAll) == null ? void 0 : _c.call(fabricInstance);
13446
13538
  await waitForPaint();
13539
+ this.logFabricTextParitySnapshot("after-stable-text-metrics", fabricInstance);
13447
13540
  }
13448
13541
  }
13449
13542
  const FONT_WEIGHT_LABELS = {
@@ -14265,6 +14358,16 @@ const pdfFonts = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProp
14265
14358
  resolveFontWeight,
14266
14359
  rewriteSvgFontsForJsPDF
14267
14360
  }, Symbol.toStringTag, { value: "Module" }));
14361
+ function logParityJson(tag, stage, payload) {
14362
+ try {
14363
+ console.log(`${tag} ${stage} ${JSON.stringify(payload, (_key, value) => {
14364
+ if (typeof value !== "number") return value;
14365
+ return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
14366
+ })}`);
14367
+ } catch {
14368
+ console.log(`${tag} ${stage}`, payload);
14369
+ }
14370
+ }
14268
14371
  function dumpSvgTextDiagnostics(svgStr, pageIndex, tag, stage, maxItems = 30) {
14269
14372
  try {
14270
14373
  if (typeof DOMParser === "undefined") return;
@@ -14287,7 +14390,7 @@ function dumpSvgTextDiagnostics(svgStr, pageIndex, tag, stage, maxItems = 30) {
14287
14390
  svgViewBox,
14288
14391
  textCount: texts.length
14289
14392
  };
14290
- console.log(`${tag} ${stage} page=${pageIndex} summary`, summary);
14393
+ logParityJson(tag, stage, { kind: "summary", ...summary });
14291
14394
  const sample = texts.slice(0, maxItems).map((t, idx) => {
14292
14395
  var _a, _b;
14293
14396
  const tspans = Array.from(t.querySelectorAll("tspan"));
@@ -14319,7 +14422,7 @@ function dumpSvgTextDiagnostics(svgStr, pageIndex, tag, stage, maxItems = 30) {
14319
14422
  tspanSample: tspanInfo
14320
14423
  };
14321
14424
  });
14322
- console.log(`${tag} ${stage} page=${pageIndex} text-sample (first ${sample.length}/${texts.length})`, sample);
14425
+ logParityJson(tag, stage, { kind: "text-sample", page: pageIndex, count: sample.length, total: texts.length, sample });
14323
14426
  } catch (err) {
14324
14427
  console.warn(`${tag} ${stage} page=${pageIndex} dump threw`, err);
14325
14428
  }