@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.cjs CHANGED
@@ -5213,6 +5213,17 @@ function buildRoundedTrianglePath(w, h, rTop, rBR, rBL) {
5213
5213
  ];
5214
5214
  return parts.join(" ");
5215
5215
  }
5216
+ const roundDiag = (value) => {
5217
+ if (typeof value !== "number") return value;
5218
+ return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
5219
+ };
5220
+ const stringifyDiag = (payload) => {
5221
+ try {
5222
+ return JSON.stringify(payload, (_key, value) => roundDiag(value));
5223
+ } catch {
5224
+ return String(payload);
5225
+ }
5226
+ };
5216
5227
  function buildRoundedRectPath(w, h, tl, tr, br, bl) {
5217
5228
  return buildRoundedRectPath$1(w, h, getRoundedRectRadii(w, h, { rxTL: tl, rxTR: tr, rxBR: br, rxBL: bl }));
5218
5229
  }
@@ -5323,11 +5334,11 @@ function createShape(element) {
5323
5334
  }
5324
5335
  }
5325
5336
  function createText(element) {
5326
- var _a;
5337
+ var _a, _b;
5327
5338
  const overflowPolicy = element.overflowPolicy || "grow-and-push";
5328
5339
  let text = element.text || "Text";
5329
5340
  let fontSize = element.fontSize || 16;
5330
- element.minFontSize || 8;
5341
+ const minFontSize = element.minFontSize || 8;
5331
5342
  const maxLines = element.maxLines || 3;
5332
5343
  const baseWidth = element.width && element.width > 0 ? element.width : 200;
5333
5344
  const baseHeight = element.height;
@@ -5339,6 +5350,7 @@ function createText(element) {
5339
5350
  const startFontSize = fontSize;
5340
5351
  let breakReason = "min-font-size-reached";
5341
5352
  let lastIter = null;
5353
+ const iterationSamples = [];
5342
5354
  while (fontSize > 1) {
5343
5355
  const testTextbox = new fabric__namespace.Textbox(text, {
5344
5356
  width: fixedWidth,
@@ -5356,7 +5368,7 @@ function createText(element) {
5356
5368
  const hasNoImplicitWrap = renderedLineCount <= explicitLineCount;
5357
5369
  const fitsHeight = !baseHeight || textHeight <= baseHeight;
5358
5370
  const widthMetrics = getTextboxWidthFitMetrics(testTextbox, fixedWidth);
5359
- const { fitsWidth } = widthMetrics;
5371
+ const { maxLineWidth, widthDidGrow, fitsWidth } = widthMetrics;
5360
5372
  if (debugAutoShrink) {
5361
5373
  lastIter = {
5362
5374
  fontSize,
@@ -5367,8 +5379,12 @@ function createText(element) {
5367
5379
  fixedWidth,
5368
5380
  hasNoImplicitWrap,
5369
5381
  fitsHeight,
5370
- fitsWidth
5382
+ fitsWidth,
5383
+ textLines: (testTextbox.textLines || []).map((line) => Array.isArray(line) ? line.join("") : String(line ?? ""))
5371
5384
  };
5385
+ if (iterationSamples.length < 6 || fontSize <= minFontSize + 2) {
5386
+ iterationSamples.push(lastIter);
5387
+ }
5372
5388
  }
5373
5389
  if (hasNoImplicitWrap && fitsHeight && fitsWidth) {
5374
5390
  breakReason = "fits";
@@ -5377,10 +5393,11 @@ function createText(element) {
5377
5393
  fontSize--;
5378
5394
  }
5379
5395
  if (debugAutoShrink) {
5380
- console.log("[auto-shrink][diag]", {
5396
+ console.log("[auto-shrink][diag] " + stringifyDiag({
5381
5397
  id: element.id,
5382
5398
  name: element.name,
5383
- text,
5399
+ text: text.slice(0, 180),
5400
+ textLength: text.length,
5384
5401
  fontFamily: element.fontFamily,
5385
5402
  fontWeight: element.fontWeight,
5386
5403
  elementWidth: element.width,
@@ -5392,10 +5409,11 @@ function createText(element) {
5392
5409
  startFontSize,
5393
5410
  finalFontSize: fontSize,
5394
5411
  breakReason,
5412
+ iterations: iterationSamples,
5395
5413
  lastIter,
5396
5414
  fontCheckRegular: typeof document !== "undefined" && document.fonts ? document.fonts.check(`16px "${element.fontFamily || "Open Sans"}"`) : null,
5397
5415
  fontCheckBold: typeof document !== "undefined" && document.fonts ? document.fonts.check(`bold 16px "${element.fontFamily || "Open Sans"}"`) : null
5398
- });
5416
+ }));
5399
5417
  }
5400
5418
  }
5401
5419
  if (overflowPolicy === "max-lines-ellipsis") {
@@ -5470,6 +5488,22 @@ function createText(element) {
5470
5488
  textbox.setCoords();
5471
5489
  }
5472
5490
  textbox.dirty = true;
5491
+ if (overflowPolicy === "auto-shrink" && typeof window !== "undefined" && window.__pixldocsDebugAutoShrink === true) {
5492
+ console.log("[auto-shrink][final-textbox] " + stringifyDiag({
5493
+ id: element.id,
5494
+ name: element.name,
5495
+ text: text.slice(0, 180),
5496
+ targetWidth,
5497
+ targetScaleX,
5498
+ targetScaleY,
5499
+ finalFontSize: fontSize,
5500
+ textboxWidth: textbox.width,
5501
+ textboxHeight: textbox.height,
5502
+ lineCount: ((_b = textbox.textLines) == null ? void 0 : _b.length) || 0,
5503
+ lines: (textbox.textLines || []).map((line) => Array.isArray(line) ? line.join("") : String(line ?? "")),
5504
+ widthMetrics: getTextboxWidthFitMetrics(textbox, targetWidth)
5505
+ }));
5506
+ }
5473
5507
  applyTextBackground(textbox, extractTextBgConfig(element));
5474
5508
  const shadow = buildTextShadow(element);
5475
5509
  if (shadow) textbox.set("shadow", shadow);
@@ -11630,6 +11664,232 @@ function applyContentBoundsPagination(config) {
11630
11664
  if (!mutated) return config;
11631
11665
  return { ...config, pages: resultPages };
11632
11666
  }
11667
+ function normalizeFontFamily(fontStack) {
11668
+ const first = fontStack.split(",")[0].trim();
11669
+ return first.replace(/^['"]|['"]$/g, "");
11670
+ }
11671
+ const loadedFonts = /* @__PURE__ */ new Set();
11672
+ const loadingPromises = /* @__PURE__ */ new Map();
11673
+ function withTimeout(promise, timeoutMs = 4e3) {
11674
+ let timeoutId;
11675
+ return Promise.race([
11676
+ promise,
11677
+ new Promise((resolve) => {
11678
+ timeoutId = setTimeout(resolve, timeoutMs);
11679
+ })
11680
+ ]).finally(() => {
11681
+ if (timeoutId) clearTimeout(timeoutId);
11682
+ });
11683
+ }
11684
+ async function loadGoogleFontCSS(rawFontFamily) {
11685
+ if (!rawFontFamily || typeof document === "undefined") return;
11686
+ const fontFamily = normalizeFontFamily(rawFontFamily);
11687
+ if (!fontFamily) return;
11688
+ if (loadedFonts.has(fontFamily)) return;
11689
+ const existing = loadingPromises.get(fontFamily);
11690
+ if (existing) return existing;
11691
+ const promise = (async () => {
11692
+ try {
11693
+ const encoded = encodeURIComponent(fontFamily);
11694
+ const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
11695
+ const link = document.createElement("link");
11696
+ link.rel = "stylesheet";
11697
+ link.href = url;
11698
+ link.crossOrigin = "anonymous";
11699
+ await new Promise((resolve, reject) => {
11700
+ link.onload = () => resolve();
11701
+ link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
11702
+ document.head.appendChild(link);
11703
+ });
11704
+ loadedFonts.add(fontFamily);
11705
+ } catch (e) {
11706
+ console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
11707
+ }
11708
+ })();
11709
+ loadingPromises.set(fontFamily, promise);
11710
+ await promise;
11711
+ loadingPromises.delete(fontFamily);
11712
+ }
11713
+ function collectFontsFromConfig(config) {
11714
+ var _a;
11715
+ const fonts = /* @__PURE__ */ new Set();
11716
+ fonts.add("Open Sans");
11717
+ fonts.add("Hind");
11718
+ function walk(nodes) {
11719
+ var _a2;
11720
+ if (!nodes) return;
11721
+ for (const node of nodes) {
11722
+ if (node.fontFamily) fonts.add(normalizeFontFamily(node.fontFamily));
11723
+ if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) fonts.add(normalizeFontFamily(node.smartProps.fontFamily));
11724
+ if (node.styles && Array.isArray(node.styles)) {
11725
+ for (const lineStyle of node.styles) {
11726
+ if (lineStyle && typeof lineStyle === "object") {
11727
+ for (const charStyle of Object.values(lineStyle)) {
11728
+ if (charStyle == null ? void 0 : charStyle.fontFamily) fonts.add(normalizeFontFamily(charStyle.fontFamily));
11729
+ }
11730
+ }
11731
+ }
11732
+ }
11733
+ if (node.children) walk(node.children);
11734
+ }
11735
+ }
11736
+ for (const page of config.pages || []) {
11737
+ walk(page.children || []);
11738
+ }
11739
+ if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
11740
+ for (const def of Object.values(config.themeConfig.variables)) {
11741
+ if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
11742
+ if (def.label && /font/i.test(def.label)) {
11743
+ fonts.add(normalizeFontFamily(def.value));
11744
+ }
11745
+ }
11746
+ }
11747
+ }
11748
+ return fonts;
11749
+ }
11750
+ function collectFontDescriptorsFromConfig(config) {
11751
+ var _a;
11752
+ const seen = /* @__PURE__ */ new Set();
11753
+ const descriptors = [];
11754
+ function add(family, weight, style) {
11755
+ const f = normalizeFontFamily(family);
11756
+ if (!f) return;
11757
+ const w = weight ?? 400;
11758
+ const s = style ?? "normal";
11759
+ const key = `${f}|${w}|${s}`;
11760
+ if (seen.has(key)) return;
11761
+ seen.add(key);
11762
+ descriptors.push({ family: f, weight: w, style: s });
11763
+ }
11764
+ function walk(nodes) {
11765
+ var _a2;
11766
+ if (!nodes) return;
11767
+ for (const node of nodes) {
11768
+ if (node.fontFamily) {
11769
+ add(node.fontFamily, node.fontWeight, node.fontStyle);
11770
+ if (node.type === "text") {
11771
+ for (const w of [300, 400, 500, 600, 700]) {
11772
+ add(node.fontFamily, w, node.fontStyle);
11773
+ }
11774
+ }
11775
+ }
11776
+ if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) {
11777
+ add(node.smartProps.fontFamily, node.smartProps.fontWeight, node.smartProps.fontStyle);
11778
+ }
11779
+ if (node.styles) {
11780
+ const styleEntries = Array.isArray(node.styles) ? node.styles : Object.values(node.styles);
11781
+ for (const lineStyle of styleEntries) {
11782
+ if (lineStyle && typeof lineStyle === "object") {
11783
+ for (const charStyle of Object.values(lineStyle)) {
11784
+ if (charStyle == null ? void 0 : charStyle.fontFamily) {
11785
+ add(charStyle.fontFamily, charStyle.fontWeight, charStyle.fontStyle);
11786
+ }
11787
+ }
11788
+ }
11789
+ }
11790
+ }
11791
+ if (node.children) walk(node.children);
11792
+ }
11793
+ }
11794
+ add("Open Sans", 400, "normal");
11795
+ add("Hind", 400, "normal");
11796
+ add("Hind", 700, "normal");
11797
+ for (const page of config.pages || []) {
11798
+ walk(page.children || []);
11799
+ }
11800
+ if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
11801
+ for (const def of Object.values(config.themeConfig.variables)) {
11802
+ if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
11803
+ if (def.label && /font/i.test(def.label)) {
11804
+ add(def.value);
11805
+ }
11806
+ }
11807
+ }
11808
+ }
11809
+ return descriptors;
11810
+ }
11811
+ async function ensureFontsForResolvedConfig(config) {
11812
+ if (typeof document === "undefined") return;
11813
+ const descriptors = collectFontDescriptorsFromConfig(config);
11814
+ const families = new Set(descriptors.map((d) => d.family));
11815
+ await withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 3500);
11816
+ if (document.fonts) {
11817
+ descriptors.forEach((d) => {
11818
+ const stylePrefix = d.style === "italic" ? "italic " : "";
11819
+ const weightStr = String(d.weight);
11820
+ const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
11821
+ document.fonts.load(spec).catch(() => {
11822
+ });
11823
+ });
11824
+ }
11825
+ }
11826
+ function configHasAutoShrinkText$1(config) {
11827
+ var _a;
11828
+ if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
11829
+ const walk = (nodes) => {
11830
+ for (const node of nodes || []) {
11831
+ if (!node) continue;
11832
+ if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
11833
+ if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
11834
+ }
11835
+ return false;
11836
+ };
11837
+ for (const page of config.pages) {
11838
+ if (walk(page.children || [])) return true;
11839
+ }
11840
+ return false;
11841
+ }
11842
+ async function awaitFontsForConfig(config, maxWaitMs) {
11843
+ if (typeof document === "undefined" || !document.fonts) return;
11844
+ await ensureFontsForResolvedConfig(config);
11845
+ const descriptors = collectFontDescriptorsFromConfig(config);
11846
+ if (descriptors.length === 0) return;
11847
+ const loads = Promise.all(
11848
+ descriptors.map((d) => {
11849
+ const stylePrefix = d.style === "italic" ? "italic " : "";
11850
+ const spec = `${stylePrefix}${d.weight} 16px "${d.family}"`;
11851
+ return document.fonts.load(spec).catch(() => []);
11852
+ })
11853
+ ).then(() => void 0);
11854
+ await Promise.race([
11855
+ loads,
11856
+ new Promise((resolve) => setTimeout(resolve, maxWaitMs))
11857
+ ]);
11858
+ await Promise.race([
11859
+ document.fonts.ready.catch(() => void 0).then(() => void 0),
11860
+ new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
11861
+ ]);
11862
+ const checkSpecs = [];
11863
+ for (const d of descriptors) {
11864
+ const stylePrefix = d.style === "italic" ? "italic " : "";
11865
+ checkSpecs.push(`${stylePrefix}${d.weight} 16px "${d.family}"`);
11866
+ }
11867
+ const startedAt = Date.now();
11868
+ const pollBudget = Math.min(maxWaitMs, 2500);
11869
+ const allReady = () => {
11870
+ for (const spec of checkSpecs) {
11871
+ try {
11872
+ if (!document.fonts.check(spec)) return false;
11873
+ } catch {
11874
+ }
11875
+ }
11876
+ return true;
11877
+ };
11878
+ while (!allReady() && Date.now() - startedAt < pollBudget) {
11879
+ await new Promise((resolve) => setTimeout(resolve, 50));
11880
+ }
11881
+ await new Promise(
11882
+ (resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
11883
+ );
11884
+ }
11885
+ async function awaitFontsBeforeTextReflow(config) {
11886
+ if (typeof document === "undefined" || !configHasAutoShrinkText$1(config)) return;
11887
+ try {
11888
+ await awaitFontsForConfig(config, 4e3);
11889
+ } catch (error) {
11890
+ console.warn("[@pixldocs/canvas-renderer] Font wait before text reflow failed:", error);
11891
+ }
11892
+ }
11633
11893
  function repeatablePageToSection(page) {
11634
11894
  return {
11635
11895
  id: page.id,
@@ -11823,6 +12083,7 @@ async function resolveTemplateData(options) {
11823
12083
  }
11824
12084
  }
11825
12085
  const repeatablePagesInput = deriveRepeatablePagesFromTemplate(config, inlineFormSchema, mergedFormData);
12086
+ await awaitFontsBeforeTextReflow(config);
11826
12087
  const resolvedConfig = applyFormDataToConfig(
11827
12088
  config,
11828
12089
  mappings,
@@ -11967,6 +12228,7 @@ async function resolveFromForm(options) {
11967
12228
  };
11968
12229
  collectFormats(formConfig.sections);
11969
12230
  }
12231
+ await awaitFontsBeforeTextReflow(templateConfig);
11970
12232
  let resolvedConfig = applyFormDataToConfig(
11971
12233
  templateConfig,
11972
12234
  mappings,
@@ -12145,224 +12407,6 @@ function paintRepeatableSections(config, repeatableSections) {
12145
12407
  }
12146
12408
  }
12147
12409
  }
12148
- function normalizeFontFamily(fontStack) {
12149
- const first = fontStack.split(",")[0].trim();
12150
- return first.replace(/^['"]|['"]$/g, "");
12151
- }
12152
- const loadedFonts = /* @__PURE__ */ new Set();
12153
- const loadingPromises = /* @__PURE__ */ new Map();
12154
- function withTimeout(promise, timeoutMs = 4e3) {
12155
- let timeoutId;
12156
- return Promise.race([
12157
- promise,
12158
- new Promise((resolve) => {
12159
- timeoutId = setTimeout(resolve, timeoutMs);
12160
- })
12161
- ]).finally(() => {
12162
- if (timeoutId) clearTimeout(timeoutId);
12163
- });
12164
- }
12165
- async function loadGoogleFontCSS(rawFontFamily) {
12166
- if (!rawFontFamily || typeof document === "undefined") return;
12167
- const fontFamily = normalizeFontFamily(rawFontFamily);
12168
- if (!fontFamily) return;
12169
- if (loadedFonts.has(fontFamily)) return;
12170
- const existing = loadingPromises.get(fontFamily);
12171
- if (existing) return existing;
12172
- const promise = (async () => {
12173
- try {
12174
- const encoded = encodeURIComponent(fontFamily);
12175
- const url = `https://fonts.googleapis.com/css?family=${encoded}:300,400,500,600,700&display=swap`;
12176
- const link = document.createElement("link");
12177
- link.rel = "stylesheet";
12178
- link.href = url;
12179
- link.crossOrigin = "anonymous";
12180
- await new Promise((resolve, reject) => {
12181
- link.onload = () => resolve();
12182
- link.onerror = () => reject(new Error(`Failed to load font: ${fontFamily}`));
12183
- document.head.appendChild(link);
12184
- });
12185
- loadedFonts.add(fontFamily);
12186
- } catch (e) {
12187
- console.warn(`[@pixldocs/canvas-renderer] Font load failed: ${fontFamily}`, e);
12188
- }
12189
- })();
12190
- loadingPromises.set(fontFamily, promise);
12191
- await promise;
12192
- loadingPromises.delete(fontFamily);
12193
- }
12194
- function collectFontsFromConfig(config) {
12195
- var _a;
12196
- const fonts = /* @__PURE__ */ new Set();
12197
- fonts.add("Open Sans");
12198
- fonts.add("Hind");
12199
- function walk(nodes) {
12200
- var _a2;
12201
- if (!nodes) return;
12202
- for (const node of nodes) {
12203
- if (node.fontFamily) fonts.add(normalizeFontFamily(node.fontFamily));
12204
- if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) fonts.add(normalizeFontFamily(node.smartProps.fontFamily));
12205
- if (node.styles && Array.isArray(node.styles)) {
12206
- for (const lineStyle of node.styles) {
12207
- if (lineStyle && typeof lineStyle === "object") {
12208
- for (const charStyle of Object.values(lineStyle)) {
12209
- if (charStyle == null ? void 0 : charStyle.fontFamily) fonts.add(normalizeFontFamily(charStyle.fontFamily));
12210
- }
12211
- }
12212
- }
12213
- }
12214
- if (node.children) walk(node.children);
12215
- }
12216
- }
12217
- for (const page of config.pages || []) {
12218
- walk(page.children || []);
12219
- }
12220
- if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
12221
- for (const def of Object.values(config.themeConfig.variables)) {
12222
- if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
12223
- if (def.label && /font/i.test(def.label)) {
12224
- fonts.add(normalizeFontFamily(def.value));
12225
- }
12226
- }
12227
- }
12228
- }
12229
- return fonts;
12230
- }
12231
- function collectFontDescriptorsFromConfig(config) {
12232
- var _a;
12233
- const seen = /* @__PURE__ */ new Set();
12234
- const descriptors = [];
12235
- function add(family, weight, style) {
12236
- const f = normalizeFontFamily(family);
12237
- if (!f) return;
12238
- const w = weight ?? 400;
12239
- const s = style ?? "normal";
12240
- const key = `${f}|${w}|${s}`;
12241
- if (seen.has(key)) return;
12242
- seen.add(key);
12243
- descriptors.push({ family: f, weight: w, style: s });
12244
- }
12245
- function walk(nodes) {
12246
- var _a2;
12247
- if (!nodes) return;
12248
- for (const node of nodes) {
12249
- if (node.fontFamily) {
12250
- add(node.fontFamily, node.fontWeight, node.fontStyle);
12251
- if (node.type === "text") {
12252
- for (const w of [300, 400, 500, 600, 700]) {
12253
- add(node.fontFamily, w, node.fontStyle);
12254
- }
12255
- }
12256
- }
12257
- if ((_a2 = node.smartProps) == null ? void 0 : _a2.fontFamily) {
12258
- add(node.smartProps.fontFamily, node.smartProps.fontWeight, node.smartProps.fontStyle);
12259
- }
12260
- if (node.styles) {
12261
- const styleEntries = Array.isArray(node.styles) ? node.styles : Object.values(node.styles);
12262
- for (const lineStyle of styleEntries) {
12263
- if (lineStyle && typeof lineStyle === "object") {
12264
- for (const charStyle of Object.values(lineStyle)) {
12265
- if (charStyle == null ? void 0 : charStyle.fontFamily) {
12266
- add(charStyle.fontFamily, charStyle.fontWeight, charStyle.fontStyle);
12267
- }
12268
- }
12269
- }
12270
- }
12271
- }
12272
- if (node.children) walk(node.children);
12273
- }
12274
- }
12275
- add("Open Sans", 400, "normal");
12276
- add("Hind", 400, "normal");
12277
- add("Hind", 700, "normal");
12278
- for (const page of config.pages || []) {
12279
- walk(page.children || []);
12280
- }
12281
- if ((_a = config.themeConfig) == null ? void 0 : _a.variables) {
12282
- for (const def of Object.values(config.themeConfig.variables)) {
12283
- if (def.value && typeof def.value === "string" && !def.value.startsWith("#") && !def.value.startsWith("rgb")) {
12284
- if (def.label && /font/i.test(def.label)) {
12285
- add(def.value);
12286
- }
12287
- }
12288
- }
12289
- }
12290
- return descriptors;
12291
- }
12292
- async function ensureFontsForResolvedConfig(config) {
12293
- if (typeof document === "undefined") return;
12294
- const descriptors = collectFontDescriptorsFromConfig(config);
12295
- const families = new Set(descriptors.map((d) => d.family));
12296
- await withTimeout(Promise.all([...families].map((f) => loadGoogleFontCSS(f))), 3500);
12297
- if (document.fonts) {
12298
- descriptors.forEach((d) => {
12299
- const stylePrefix = d.style === "italic" ? "italic " : "";
12300
- const weightStr = String(d.weight);
12301
- const spec = `${stylePrefix}${weightStr} 16px "${d.family}"`;
12302
- document.fonts.load(spec).catch(() => {
12303
- });
12304
- });
12305
- }
12306
- }
12307
- function configHasAutoShrinkText$1(config) {
12308
- var _a;
12309
- if (!((_a = config == null ? void 0 : config.pages) == null ? void 0 : _a.length)) return false;
12310
- const walk = (nodes) => {
12311
- for (const node of nodes || []) {
12312
- if (!node) continue;
12313
- if (node.type === "text" && node.overflowPolicy === "auto-shrink") return true;
12314
- if (Array.isArray(node.children) && node.children.length && walk(node.children)) return true;
12315
- }
12316
- return false;
12317
- };
12318
- for (const page of config.pages) {
12319
- if (walk(page.children || [])) return true;
12320
- }
12321
- return false;
12322
- }
12323
- async function awaitFontsForConfig(config, maxWaitMs) {
12324
- if (typeof document === "undefined" || !document.fonts) return;
12325
- await ensureFontsForResolvedConfig(config);
12326
- const descriptors = collectFontDescriptorsFromConfig(config);
12327
- if (descriptors.length === 0) return;
12328
- const loads = Promise.all(
12329
- descriptors.map((d) => {
12330
- const stylePrefix = d.style === "italic" ? "italic " : "";
12331
- const spec = `${stylePrefix}${d.weight} 16px "${d.family}"`;
12332
- return document.fonts.load(spec).catch(() => []);
12333
- })
12334
- ).then(() => void 0);
12335
- await Promise.race([
12336
- loads,
12337
- new Promise((resolve) => setTimeout(resolve, maxWaitMs))
12338
- ]);
12339
- await Promise.race([
12340
- document.fonts.ready.catch(() => void 0).then(() => void 0),
12341
- new Promise((r) => setTimeout(r, Math.min(500, maxWaitMs)))
12342
- ]);
12343
- const checkSpecs = [];
12344
- for (const d of descriptors) {
12345
- const stylePrefix = d.style === "italic" ? "italic " : "";
12346
- checkSpecs.push(`${stylePrefix}${d.weight} 16px "${d.family}"`);
12347
- }
12348
- const startedAt = Date.now();
12349
- const pollBudget = Math.min(maxWaitMs, 2500);
12350
- const allReady = () => {
12351
- for (const spec of checkSpecs) {
12352
- try {
12353
- if (!document.fonts.check(spec)) return false;
12354
- } catch {
12355
- }
12356
- }
12357
- return true;
12358
- };
12359
- while (!allReady() && Date.now() - startedAt < pollBudget) {
12360
- await new Promise((resolve) => setTimeout(resolve, 50));
12361
- }
12362
- await new Promise(
12363
- (resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
12364
- );
12365
- }
12366
12410
  const PREVIEW_DEBUG_PREFIX = "[canvas-renderer][preview-debug]";
12367
12411
  function countUnderlinedNodes(config) {
12368
12412
  var _a;
@@ -12553,7 +12597,18 @@ function PixldocsPreview(props) {
12553
12597
  !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..." }) })
12554
12598
  ] });
12555
12599
  }
12556
- const PACKAGE_VERSION = "0.5.76";
12600
+ const PACKAGE_VERSION = "0.5.78";
12601
+ const roundParityValue = (value) => {
12602
+ if (typeof value !== "number") return value;
12603
+ return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
12604
+ };
12605
+ function logJsonLine(tag, payload) {
12606
+ try {
12607
+ console.log(`${tag} ${JSON.stringify(payload, (_key, value) => roundParityValue(value))}`);
12608
+ } catch {
12609
+ console.log(tag, payload);
12610
+ }
12611
+ }
12557
12612
  let __underlineFixInstalled = false;
12558
12613
  function installUnderlineFix(fab) {
12559
12614
  var _a;
@@ -13429,6 +13484,42 @@ class PixldocsRenderer {
13429
13484
  }
13430
13485
  return null;
13431
13486
  }
13487
+ logFabricTextParitySnapshot(stage, fabricInstance) {
13488
+ var _a;
13489
+ if (typeof window === "undefined" || window.__pixldocsDebugAutoShrink !== true) return;
13490
+ const sample = [];
13491
+ const visit = (obj, groupPath = "") => {
13492
+ var _a2;
13493
+ if (!obj) return;
13494
+ if (obj instanceof fabric__namespace.Textbox) {
13495
+ const lineWidths = getCanvasMeasuredTextboxLineWidths(obj);
13496
+ sample.push({
13497
+ id: getObjectId(obj),
13498
+ groupPath,
13499
+ text: String(obj.text ?? "").slice(0, 180),
13500
+ left: obj.left,
13501
+ top: obj.top,
13502
+ width: obj.width,
13503
+ height: obj.height,
13504
+ scaleX: obj.scaleX,
13505
+ scaleY: obj.scaleY,
13506
+ fontSize: obj.fontSize,
13507
+ fontFamily: obj.fontFamily,
13508
+ fontWeight: obj.fontWeight,
13509
+ lineCount: ((_a2 = obj.textLines) == null ? void 0 : _a2.length) || 0,
13510
+ lines: (obj.textLines || []).map((line) => Array.isArray(line) ? line.join("") : String(line ?? "")),
13511
+ lineWidths,
13512
+ maxLineWidth: lineWidths.length ? Math.max(...lineWidths) : 0
13513
+ });
13514
+ }
13515
+ if (obj instanceof fabric__namespace.Group && typeof obj.getObjects === "function") {
13516
+ const nextPath = [groupPath, getObjectId(obj) || obj.type || "group"].filter(Boolean).join("/");
13517
+ obj.getObjects().forEach((child) => visit(child, nextPath));
13518
+ }
13519
+ };
13520
+ (_a = fabricInstance == null ? void 0 : fabricInstance.getObjects) == null ? void 0 : _a.call(fabricInstance).forEach((obj) => visit(obj));
13521
+ logJsonLine("[canvas-renderer][fabric-text-parity]", { stage, textboxes: sample.length, sample });
13522
+ }
13432
13523
  async waitForStableTextMetrics(container, config) {
13433
13524
  var _a, _b, _c;
13434
13525
  if (typeof document !== "undefined") {
@@ -13437,6 +13528,7 @@ class PixldocsRenderer {
13437
13528
  }
13438
13529
  const fabricInstance = this.getFabricCanvasFromContainer(container);
13439
13530
  if (!(fabricInstance == null ? void 0 : fabricInstance.getObjects)) return;
13531
+ this.logFabricTextParitySnapshot("before-stable-text-metrics", fabricInstance);
13440
13532
  const waitForPaint = () => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r())));
13441
13533
  const primeCharBounds = (obj) => {
13442
13534
  if (obj instanceof fabric__namespace.Textbox) {
@@ -13463,6 +13555,7 @@ class PixldocsRenderer {
13463
13555
  await waitForPaint();
13464
13556
  (_c = fabricInstance.renderAll) == null ? void 0 : _c.call(fabricInstance);
13465
13557
  await waitForPaint();
13558
+ this.logFabricTextParitySnapshot("after-stable-text-metrics", fabricInstance);
13466
13559
  }
13467
13560
  }
13468
13561
  const FONT_WEIGHT_LABELS = {
@@ -14284,6 +14377,16 @@ const pdfFonts = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProp
14284
14377
  resolveFontWeight,
14285
14378
  rewriteSvgFontsForJsPDF
14286
14379
  }, Symbol.toStringTag, { value: "Module" }));
14380
+ function logParityJson(tag, stage, payload) {
14381
+ try {
14382
+ console.log(`${tag} ${stage} ${JSON.stringify(payload, (_key, value) => {
14383
+ if (typeof value !== "number") return value;
14384
+ return Number.isFinite(value) ? Number(value.toFixed(3)) : value;
14385
+ })}`);
14386
+ } catch {
14387
+ console.log(`${tag} ${stage}`, payload);
14388
+ }
14389
+ }
14287
14390
  function dumpSvgTextDiagnostics(svgStr, pageIndex, tag, stage, maxItems = 30) {
14288
14391
  try {
14289
14392
  if (typeof DOMParser === "undefined") return;
@@ -14306,7 +14409,7 @@ function dumpSvgTextDiagnostics(svgStr, pageIndex, tag, stage, maxItems = 30) {
14306
14409
  svgViewBox,
14307
14410
  textCount: texts.length
14308
14411
  };
14309
- console.log(`${tag} ${stage} page=${pageIndex} summary`, summary);
14412
+ logParityJson(tag, stage, { kind: "summary", ...summary });
14310
14413
  const sample = texts.slice(0, maxItems).map((t, idx) => {
14311
14414
  var _a, _b;
14312
14415
  const tspans = Array.from(t.querySelectorAll("tspan"));
@@ -14338,7 +14441,7 @@ function dumpSvgTextDiagnostics(svgStr, pageIndex, tag, stage, maxItems = 30) {
14338
14441
  tspanSample: tspanInfo
14339
14442
  };
14340
14443
  });
14341
- console.log(`${tag} ${stage} page=${pageIndex} text-sample (first ${sample.length}/${texts.length})`, sample);
14444
+ logParityJson(tag, stage, { kind: "text-sample", page: pageIndex, count: sample.length, total: texts.length, sample });
14342
14445
  } catch (err) {
14343
14446
  console.warn(`${tag} ${stage} page=${pageIndex} dump threw`, err);
14344
14447
  }