@openpresentation/opf 0.26.10 → 0.28.0

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.
Files changed (105) hide show
  1. package/README.md +6 -0
  2. package/dist/browser.js +20 -17
  3. package/dist/{catalog-refs-BSfGELpt.d.ts → catalog-refs-kAefsZWT.d.ts} +2 -1
  4. package/dist/{catalog-schemas-DoxA2BgY.d.ts → catalog-schemas-CC1d_h8s.d.ts} +3 -2
  5. package/dist/{chart-data-BnNqgnC-.d.ts → chart-data-Ci9s_NDN.d.ts} +2 -1
  6. package/dist/{chunk-XRMV2GMB.js → chunk-2CUCYENF.js} +758 -232
  7. package/dist/chunk-43JWUIGF.js +8 -0
  8. package/dist/{chunk-F4HFDP6I.js → chunk-5Q4IYLMW.js} +31 -15
  9. package/dist/{chunk-GIWNHRTX.js → chunk-BQUWRXTR.js} +4 -4
  10. package/dist/chunk-CR72ZJ6W.js +236 -0
  11. package/dist/{chunk-L52N5FZZ.js → chunk-F6DCEGB3.js} +6 -6
  12. package/dist/chunk-GM7WFPGG.js +10 -0
  13. package/dist/chunk-JBARNYFR.js +23 -0
  14. package/dist/{chunk-EQ6S5SEZ.js → chunk-KELC4K62.js} +1 -1
  15. package/dist/{chunk-77KL2PMM.js → chunk-KXKN3W5Z.js} +3 -3
  16. package/dist/{chunk-D25BNPTW.js → chunk-LLEXM7TQ.js} +4 -4
  17. package/dist/{chunk-3S2XVDPN.js → chunk-MTFK7QEY.js} +6 -6
  18. package/dist/{chunk-TKQ2VFV5.js → chunk-N77N4AOD.js} +1 -1
  19. package/dist/{chunk-NVT2YDVC.js → chunk-OAIAFXMS.js} +2 -2
  20. package/dist/{chunk-PHP45MO7.js → chunk-P6PKYJTS.js} +1 -1
  21. package/dist/{chunk-H7XMO36O.js → chunk-PCU4XGKV.js} +5 -5
  22. package/dist/{chunk-Y4TZCE7R.js → chunk-SEZ4EPW6.js} +2 -2
  23. package/dist/{chunk-Y25DYRME.js → chunk-TNUTRV6E.js} +1 -1
  24. package/dist/{chunk-G5WJMJV3.js → chunk-UOHWG4OS.js} +1 -1
  25. package/dist/{chunk-A54NGT2K.js → chunk-VGGPAMP7.js} +2 -2
  26. package/dist/{chunk-IVPA6NG6.js → chunk-WPKCWQ6W.js} +494 -120
  27. package/dist/{chunk-VZBFQLCA.js → chunk-XZ23FORL.js} +6 -6
  28. package/dist/chunk-ZROVYWGU.js +1 -0
  29. package/dist/{composition-DIftf6iP.d.ts → composition-C-hd_Ci3.d.ts} +134 -14
  30. package/dist/composition.d.ts +3 -3
  31. package/dist/composition.js +2 -1
  32. package/dist/conversion-XJTDWQ5W.js +24 -0
  33. package/dist/{conversion-ChmGA8Ul.d.ts → conversion-hbZeS5kD.d.ts} +8 -7
  34. package/dist/convert.d.ts +6 -5
  35. package/dist/convert.js +9 -7
  36. package/dist/data.d.ts +4 -3
  37. package/dist/data.js +3 -2
  38. package/dist/{deck-report-5vwMGcQz.d.ts → deck-report-DQMsvyBI.d.ts} +5 -4
  39. package/dist/deck.d.ts +9 -8
  40. package/dist/deck.js +13 -11
  41. package/dist/diff.d.ts +3 -2
  42. package/dist/diff.js +6 -4
  43. package/dist/docs.d.ts +2 -1
  44. package/dist/docs.js +33 -19
  45. package/dist/examples.d.ts +3 -2
  46. package/dist/examples.js +92 -50
  47. package/dist/{explain-JSuESV-Y.d.ts → explain-iZeNOUc6.d.ts} +2 -1
  48. package/dist/font-policy.d.ts +2 -1
  49. package/dist/font-policy.js +1 -0
  50. package/dist/format.d.ts +2 -1
  51. package/dist/format.js +3 -2
  52. package/dist/ids.d.ts +114 -0
  53. package/dist/ids.js +6 -0
  54. package/dist/index.d.ts +40 -16
  55. package/dist/index.js +24 -21
  56. package/dist/markdown.d.ts +8 -7
  57. package/dist/markdown.js +10 -8
  58. package/dist/node-engine.d.ts +33 -10
  59. package/dist/node-engine.js +37 -28
  60. package/dist/{pagination-D_kJe4jH.d.ts → pagination-DyAKYpXu.d.ts} +4 -3
  61. package/dist/pagination.d.ts +4 -4
  62. package/dist/pagination.js +6 -4
  63. package/dist/patch.d.ts +89 -4
  64. package/dist/patch.js +5 -3
  65. package/dist/precompiled.d.ts +2 -0
  66. package/dist/precompiled.js +7 -0
  67. package/dist/{presentation-Bwo8Ko6D.d.ts → presentation-8jLSTFqL.d.ts} +11 -11
  68. package/dist/repo-readme.js +3 -1
  69. package/dist/schema-engine-ajv.js +37 -0
  70. package/dist/schema-engine-precompiled.js +3 -0
  71. package/dist/schema.d.ts +4 -3
  72. package/dist/schema.js +5 -3
  73. package/dist/schemas.d.ts +2 -1
  74. package/dist/schemas.js +2 -1
  75. package/dist/source.d.ts +5 -4
  76. package/dist/source.js +12 -10
  77. package/dist/spec/README.md +1 -1
  78. package/dist/spec/schemas/audience.schema.json +1 -0
  79. package/dist/spec/schemas/catalog-index.schema.json +1 -0
  80. package/dist/spec/schemas/catalog-manifest.schema.json +1 -0
  81. package/dist/spec/schemas/chart-type.schema.json +1 -0
  82. package/dist/spec/schemas/color-scheme.schema.json +1 -0
  83. package/dist/spec/schemas/finding.schema.json +1 -0
  84. package/dist/spec/schemas/font-scheme.schema.json +1 -0
  85. package/dist/spec/schemas/language.schema.json +1 -0
  86. package/dist/spec/schemas/layout-preview-index.schema.json +1 -0
  87. package/dist/spec/schemas/layout.schema.json +8 -6
  88. package/dist/spec/schemas/narrative.schema.json +1 -0
  89. package/dist/spec/schemas/opf.schema.json +9 -8
  90. package/dist/spec/schemas/purpose.schema.json +1 -0
  91. package/dist/spec/schemas/social-platform.schema.json +1 -0
  92. package/dist/spec/schemas/theme.schema.json +3 -1
  93. package/dist/spec/schemas/tone.schema.json +1 -0
  94. package/dist/spec-files.d.ts +2 -1
  95. package/dist/spec-files.js +1 -0
  96. package/dist/symbol-font-encodings.d.ts +2 -1
  97. package/dist/symbol-font-encodings.js +2 -0
  98. package/dist/types.d.ts +9 -9
  99. package/dist/{validation-types-3ARvTsB0.d.ts → validation-types-mNmfcY9A.d.ts} +4 -3
  100. package/dist/validator.d.ts +8 -8
  101. package/dist/validator.js +5 -3
  102. package/dist/yaml.d.ts +8 -7
  103. package/dist/yaml.js +9 -7
  104. package/package.json +30 -9
  105. package/dist/conversion-IVKCKMCN.js +0 -21
@@ -1877,11 +1877,18 @@ function tableCellDisplayValue(cell, columnFormat2) {
1877
1877
 
1878
1878
  // src/chart-axis.ts
1879
1879
  var CHART_TEXT_PX = 20;
1880
+ var CHART_TEXT_FLOOR_PX = 18;
1881
+ var CHART_FLOOR_MIN_HEIGHT = 12 * CHART_TEXT_FLOOR_PX;
1882
+ var CHART_FLOOR_MIN_WIDTH = 16 * CHART_TEXT_FLOOR_PX;
1883
+ var CHART_MIN_TICK_SPACING_PX = 48;
1884
+ var CHART_MAX_INTERVALS = 8;
1880
1885
  function chartTextSize(geometry) {
1881
1886
  const { width, height, scale: scale2 } = geometry;
1882
1887
  const minimum = geometry.minFontSize ?? 16;
1883
1888
  if (![width, height, scale2].every(Number.isFinite) || width <= 0 || height <= 0 || scale2 <= 0) return Math.max(CHART_TEXT_PX, minimum);
1884
- return Math.max(Math.floor(Math.min(CHART_TEXT_PX, height / scale2 / 18, width / scale2 / 30)), minimum);
1889
+ const roomy = Math.floor(Math.min(CHART_TEXT_PX, height / scale2 / 18, width / scale2 / 30));
1890
+ const roomForFloor = height / scale2 >= CHART_FLOOR_MIN_HEIGHT && width / scale2 >= CHART_FLOOR_MIN_WIDTH;
1891
+ return Math.max(roomy, minimum, roomForFloor ? CHART_TEXT_FLOOR_PX : 0);
1885
1892
  }
1886
1893
  function clean(value) {
1887
1894
  const rounded = Number(value.toPrecision(12));
@@ -1949,6 +1956,10 @@ function normalizedScale(dataMin, dataMax, limit) {
1949
1956
  function maxIntervalsFor(length, labelExtent) {
1950
1957
  return Math.max(1, Math.min(10, Math.floor(length / Math.max(1, labelExtent))));
1951
1958
  }
1959
+ function chartIntervalsFor(length, labelExtent, scale2) {
1960
+ const spacing = CHART_MIN_TICK_SPACING_PX * (Number.isFinite(scale2) && scale2 > 0 ? scale2 : 1);
1961
+ return Math.min(CHART_MAX_INTERVALS, maxIntervalsFor(length, Math.max(labelExtent, spacing)));
1962
+ }
1952
1963
  var bar = (horizontal, grouping) => ({ kind: "bar", horizontal, grouping });
1953
1964
  var line = (grouping) => ({ kind: "line", horizontal: false, grouping });
1954
1965
  var area = (grouping) => ({ kind: "area", horizontal: false, grouping });
@@ -2024,7 +2035,7 @@ function chartValueAxis(chart, resolved, geometry) {
2024
2035
  const legendBand = options.legend === "top" || options.legend === "bottom" ? lineHeight + pad : 0;
2025
2036
  const titleBand = options.axisTitles.category ? lineHeight : 0;
2026
2037
  const length = spec.horizontal ? width * 0.7 - 2 * pad - fontPx : height - 2 * pad - lineHeight * 1.5 - legendBand - titleBand;
2027
- const fitted = niceScale(dataMin, dataMax, maxIntervalsFor(Math.max(1, length), spec.horizontal ? 4 * fontPx : lineHeight * 1.2), { percent });
2038
+ const fitted = niceScale(dataMin, dataMax, chartIntervalsFor(Math.max(1, length), spec.horizontal ? 4 * fontPx : lineHeight * 1.2, scale2), { percent });
2028
2039
  return { min: fitted.min, max: fitted.max, majorUnit: fitted.step, ...percent ? { percent: true } : {} };
2029
2040
  }
2030
2041
 
@@ -2057,11 +2068,28 @@ var u32be = (b, i) => (at2(b, i) << 24 | at2(b, i + 1) << 16 | at2(b, i + 2) <<
2057
2068
  var u16le = (b, i) => at2(b, i) | at2(b, i + 1) << 8;
2058
2069
  var u24le = (b, i) => at2(b, i) | at2(b, i + 1) << 8 | at2(b, i + 2) << 16;
2059
2070
  var ascii = (b, i, n) => String.fromCharCode(...b.subarray(i, i + n));
2071
+ function exifOrientation(b, start, end) {
2072
+ if (end > b.length || ascii(b, start, 4) !== "Exif" || at2(b, start + 4) !== 0) return void 0;
2073
+ const tiff = start + 6, little = ascii(b, tiff, 2) === "II";
2074
+ if (!little && ascii(b, tiff, 2) !== "MM") return void 0;
2075
+ const u16 = (index) => little ? u16le(b, index) : u16be(b, index);
2076
+ const u32 = (index) => little ? (u16le(b, index) | u16le(b, index + 2) << 16) >>> 0 : u32be(b, index);
2077
+ const directory = tiff + u32(tiff + 4);
2078
+ if (directory + 2 > end) return void 0;
2079
+ for (let entry = 0, count = u16(directory); entry < count && directory + 2 + entry * 12 + 12 <= end; entry++) {
2080
+ const at12 = directory + 2 + entry * 12;
2081
+ if (u16(at12) === 274) {
2082
+ const value = u16(at12 + 8);
2083
+ return value >= 1 && value <= 8 ? value : void 0;
2084
+ }
2085
+ }
2086
+ return void 0;
2087
+ }
2060
2088
  function rasterDimensions(b) {
2061
2089
  if (b.length >= 24 && u32be(b, 0) === 2303741511 && ascii(b, 12, 4) === "IHDR") return { width: u32be(b, 16), height: u32be(b, 20) };
2062
2090
  if (b.length >= 10 && (ascii(b, 0, 6) === "GIF87a" || ascii(b, 0, 6) === "GIF89a")) return { width: u16le(b, 6), height: u16le(b, 8) };
2063
2091
  if (b.length >= 4 && b[0] === 255 && b[1] === 216) {
2064
- let i = 2;
2092
+ let i = 2, orientation = 1;
2065
2093
  while (i + 9 < b.length) {
2066
2094
  if (at2(b, i) !== 255) {
2067
2095
  i++;
@@ -2076,7 +2104,11 @@ function rasterDimensions(b) {
2076
2104
  i += 2;
2077
2105
  continue;
2078
2106
  }
2079
- if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) return { height: u16be(b, i + 5), width: u16be(b, i + 7) };
2107
+ if (marker === 225) orientation = exifOrientation(b, i + 4, i + 2 + u16be(b, i + 2)) ?? orientation;
2108
+ if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
2109
+ const height = u16be(b, i + 5), width = u16be(b, i + 7);
2110
+ return orientation >= 5 ? { width: height, height: width } : { width, height };
2111
+ }
2080
2112
  i += 2 + u16be(b, i + 2);
2081
2113
  }
2082
2114
  return void 0;
@@ -2156,7 +2188,7 @@ var VALUES = {
2156
2188
  contentDirection: ["horizontal", "vertical"],
2157
2189
  chartPrimary: ["none", "top", "bottom", "left", "right"],
2158
2190
  imageFit: ["cover", "contain", "stretch"],
2159
- listBullet: ["character", "image"],
2191
+ listBullet: ["character", "image", "none"],
2160
2192
  mirror: "boolean"
2161
2193
  };
2162
2194
  var themeKeys = new Set(THEME_DESIGN_KEYS);
@@ -2384,6 +2416,8 @@ function parse(layout) {
2384
2416
  }
2385
2417
  };
2386
2418
  order(children);
2419
+ const stackAnchor = anchorOf(value.anchor);
2420
+ const stack = direction === "column" && children.length > 0 && children.every((node) => node.size === "auto") && stackAnchor !== void 0 && stackAnchor !== "top" ? stackAnchor : void 0;
2387
2421
  const overflowRegion = typeof value.overflowRegion === "string" ? value.overflowRegion : regions.find((entry) => entry.role === "primary" && entry.flow !== "none")?.name;
2388
2422
  return {
2389
2423
  issues,
@@ -2395,7 +2429,8 @@ function parse(layout) {
2395
2429
  regions,
2396
2430
  ...overflowRegion !== void 0 ? { overflowRegion } : {},
2397
2431
  title: names.has("title"),
2398
- subtitle: names.has("subtitle")
2432
+ subtitle: names.has("subtitle"),
2433
+ ...stack ? { stack } : {}
2399
2434
  }
2400
2435
  };
2401
2436
  }
@@ -2744,19 +2779,32 @@ function listColumnBreaks(heights, levels, count) {
2744
2779
  return solve(0, count).breaks;
2745
2780
  }
2746
2781
  function allocateRows(rows3, total, gap) {
2747
- const auto = rows3.map((row) => row.size === "auto" ? Math.max(0, Math.min(row.need, row.kind === "below" ? total : total / 2)) : 0);
2782
+ const auto = rows3.map((row) => row.size === "auto" ? Math.max(0, Math.min(row.need, row.kind === "heading" ? total / 2 : total)) : 0);
2748
2783
  const visible = rows3.filter((row, index) => row.size !== "auto" || auto[index] > 0).length;
2749
2784
  const headings2 = rows3.reduce((sum, row, index) => sum + (row.kind === "heading" ? auto[index] : 0), 0);
2750
2785
  const space = Math.max(0, total - headings2 - Math.max(0, visible - 1) * gap);
2751
2786
  const numeric = rows3.reduce((sum, row) => sum + (row.size === "auto" ? 0 : row.size), 0);
2752
2787
  const numericNeed = rows3.reduce((sum, row) => sum + (row.size === "auto" ? 0 : Math.max(0, row.need)), 0);
2753
2788
  const reserve = numeric > 0 ? Math.min(numericNeed, space / 2) : 0;
2754
- const body = rows3.reduce((sum, row, index) => sum + (row.size === "auto" && row.kind !== "heading" ? auto[index] : 0), 0);
2755
- const factor = body > space - reserve && body > 0 ? Math.max(0, space - reserve) / body : 1;
2756
- const heights = rows3.map((row, index) => row.size === "auto" ? row.kind === "heading" ? auto[index] : auto[index] * factor : 0);
2789
+ const bodyRows = rows3.flatMap((row, index) => row.size === "auto" && row.kind !== "heading" ? [index] : []);
2790
+ const fitted = shareHeights(bodyRows.map((index) => auto[index]), bodyRows.map((index) => rows3[index].floor ?? 0), Math.max(0, space - reserve));
2791
+ const heights = rows3.map((row, index) => row.size === "auto" ? row.kind === "heading" ? auto[index] : fitted[bodyRows.indexOf(index)] : 0);
2757
2792
  const rest = Math.max(0, space - heights.reduce((sum, height, index) => sum + (rows3[index].kind === "heading" ? 0 : height), 0));
2758
2793
  return rows3.map((row, index) => row.size === "auto" ? heights[index] : numeric > 0 ? rest * row.size / numeric : 0);
2759
2794
  }
2795
+ function shareHeights(needs, floors, total) {
2796
+ const space = Math.max(0, total);
2797
+ const need = needs.map((value) => Number.isFinite(value) ? Math.max(0, value) : 0);
2798
+ const floor = need.map((value, index) => Math.min(value, Math.max(0, Number.isFinite(floors[index]) ? floors[index] : 0)));
2799
+ const sum = (values) => values.reduce((a, b) => a + b, 0);
2800
+ const needed = sum(need), lowest = sum(floor);
2801
+ if (needed <= space) return need;
2802
+ if (lowest < space) {
2803
+ const give = (needed - space) / (needed - lowest);
2804
+ return need.map((value, index) => value - give * (value - floor[index]));
2805
+ }
2806
+ return lowest > 0 ? floor.map((value) => value * space / lowest) : need.map(() => 0);
2807
+ }
2760
2808
 
2761
2809
  // src/direction.ts
2762
2810
  var strongRtl = /[\u200F\u061C]|(?=\p{L})[\p{Script=Arabic}\p{Script=Hebrew}\p{Script=Syriac}\p{Script=Thaana}\p{Script=Nko}\p{Script=Adlam}\p{Script=Hanifi_Rohingya}\p{Script=Mandaic}\p{Script=Samaritan}\p{Script=Mende_Kikakui}\p{Script=Imperial_Aramaic}\p{Script=Phoenician}\p{Script=Kharoshthi}\p{Script=Old_South_Arabian}\p{Script=Old_North_Arabian}\p{Script=Avestan}\p{Script=Inscriptional_Parthian}\p{Script=Inscriptional_Pahlavi}\p{Script=Psalter_Pahlavi}\p{Script=Old_Turkic}\p{Script=Old_Hungarian}\p{Script=Nabataean}\p{Script=Palmyrene}\p{Script=Hatran}\p{Script=Manichaean}\p{Script=Sogdian}\p{Script=Old_Sogdian}\p{Script=Elymaic}\p{Script=Chorasmian}\p{Script=Yezidi}\p{Script=Cypriot}\p{Script=Lydian}\p{Script=Meroitic_Cursive}\p{Script=Meroitic_Hieroglyphs}\u{10F70}-\u{10FAF}\u{10D40}-\u{10D8F}]/u;
@@ -3343,6 +3391,93 @@ function resolveSlideDirection(presentation, slideIndex) {
3343
3391
  }
3344
3392
  }
3345
3393
 
3394
+ // src/quote-marks.ts
3395
+ var marks = (open, close) => ({ open, close });
3396
+ var ENGLISH = marks("\u201C", "\u201D");
3397
+ var GUILLEMETS = marks("\xAB", "\xBB");
3398
+ var LOW_HIGH = marks("\u201E", "\u201C");
3399
+ var LOW_RIGHT = marks("\u201E", "\u201D");
3400
+ var RIGHT_ONLY = marks("\u201D", "\u201D");
3401
+ var CORNER = marks("\u300C", "\u300D");
3402
+ var REVERSED = marks("\xBB", "\xAB");
3403
+ var QUOTE_MARKS = {
3404
+ en: ENGLISH,
3405
+ es: GUILLEMETS,
3406
+ fr: GUILLEMETS,
3407
+ de: LOW_HIGH,
3408
+ it: GUILLEMETS,
3409
+ pt: ENGLISH,
3410
+ nl: ENGLISH,
3411
+ ca: GUILLEMETS,
3412
+ gl: GUILLEMETS,
3413
+ eu: GUILLEMETS,
3414
+ sv: RIGHT_ONLY,
3415
+ fi: RIGHT_ONLY,
3416
+ da: REVERSED,
3417
+ nb: GUILLEMETS,
3418
+ nn: GUILLEMETS,
3419
+ no: GUILLEMETS,
3420
+ is: LOW_HIGH,
3421
+ pl: LOW_RIGHT,
3422
+ hu: LOW_RIGHT,
3423
+ ro: LOW_RIGHT,
3424
+ cs: LOW_HIGH,
3425
+ sk: LOW_HIGH,
3426
+ sl: LOW_HIGH,
3427
+ hr: LOW_HIGH,
3428
+ sr: LOW_HIGH,
3429
+ bg: LOW_HIGH,
3430
+ lt: LOW_HIGH,
3431
+ lv: LOW_HIGH,
3432
+ et: LOW_HIGH,
3433
+ ru: GUILLEMETS,
3434
+ uk: GUILLEMETS,
3435
+ el: GUILLEMETS,
3436
+ tr: ENGLISH,
3437
+ ga: ENGLISH,
3438
+ cy: ENGLISH,
3439
+ ja: CORNER,
3440
+ zh: ENGLISH,
3441
+ "zh-tw": CORNER,
3442
+ "zh-hk": CORNER,
3443
+ "zh-hant": CORNER,
3444
+ ko: ENGLISH,
3445
+ ar: GUILLEMETS,
3446
+ fa: GUILLEMETS,
3447
+ he: ENGLISH,
3448
+ ur: ENGLISH,
3449
+ hi: ENGLISH,
3450
+ th: ENGLISH,
3451
+ vi: ENGLISH,
3452
+ id: ENGLISH,
3453
+ ms: ENGLISH,
3454
+ "de-ch": GUILLEMETS,
3455
+ "fr-ch": GUILLEMETS,
3456
+ "pt-pt": GUILLEMETS
3457
+ };
3458
+ var ASCII_QUOTE_MARKS = marks('"', '"');
3459
+ function quoteMarksFor(tag) {
3460
+ if (tag === void 0 || tag === "") return ENGLISH;
3461
+ const lower = tag.toLowerCase().replaceAll("_", "-"), primary = lower.split("-")[0];
3462
+ return QUOTE_MARKS[lower] ?? QUOTE_MARKS[primary] ?? ASCII_QUOTE_MARKS;
3463
+ }
3464
+ function quoteLanguage(presentation, slideIndex) {
3465
+ if (presentation === null || typeof presentation !== "object" || presentation.language === void 0) return void 0;
3466
+ try {
3467
+ const resolved = resolveScriptFonts(presentation, Number.isInteger(slideIndex) ? { slideIndex } : {});
3468
+ return resolved.languageSource === "document" ? resolved.bcp47 : void 0;
3469
+ } catch {
3470
+ return void 0;
3471
+ }
3472
+ }
3473
+ var PAIRS = new Set(Object.values(QUOTE_MARKS).concat(ASCII_QUOTE_MARKS).map((entry) => entry.open + entry.close));
3474
+ function isWrappedInQuoteMarks(text2) {
3475
+ return text2.length >= 2 && PAIRS.has(text2[0] + text2.at(-1));
3476
+ }
3477
+ function unwrapQuoteMarks(text2) {
3478
+ return isWrappedInQuoteMarks(text2) ? text2.slice(1, -1) : text2;
3479
+ }
3480
+
3346
3481
  // src/numbering.ts
3347
3482
  var NUMBERING_STYLES = ["arabic", "roman-upper", "roman-lower", "alpha-upper", "alpha-lower"];
3348
3483
  var NUMBERING_SUFFIXES = ["period", "paren", "paren-both"];
@@ -3458,6 +3593,54 @@ function numberingFindings(payload) {
3458
3593
  return { errors, warnings };
3459
3594
  }
3460
3595
 
3596
+ // src/legible-color.ts
3597
+ var channels = (hex) => [1, 3, 5].map((offset) => Number.parseInt(hex.slice(offset, offset + 2), 16));
3598
+ var toHex = (values) => `#${values.map((value) => Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, "0")).join("").toUpperCase()}`;
3599
+ var linear = (value) => {
3600
+ const n = value / 255;
3601
+ return n <= 0.04045 ? n / 12.92 : ((n + 0.055) / 1.055) ** 2.4;
3602
+ };
3603
+ var luminance = (hex) => {
3604
+ const [r, g, b] = channels(hex);
3605
+ return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);
3606
+ };
3607
+ var contrast = (a, b) => {
3608
+ const x = luminance(a), y = luminance(b);
3609
+ return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05);
3610
+ };
3611
+ var normalize = (value) => {
3612
+ if (typeof value !== "string") return void 0;
3613
+ const text2 = value.trim();
3614
+ if (/^#[0-9a-f]{3}$/i.test(text2)) return `#${[...text2.slice(1)].map((c) => c + c).join("")}`.toUpperCase();
3615
+ return /^#[0-9a-f]{6}([0-9a-f]{2})?$/i.test(text2) ? text2.slice(0, 7).toUpperCase() : void 0;
3616
+ };
3617
+ function toHsl(hex) {
3618
+ const [r, g, b] = channels(hex).map((value) => value / 255);
3619
+ const max = Math.max(r, g, b), min = Math.min(r, g, b), l = (max + min) / 2, d = max - min;
3620
+ if (d === 0) return [0, 0, l];
3621
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
3622
+ const h = max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
3623
+ return [h * 60, s, l];
3624
+ }
3625
+ function fromHsl(h, s, l) {
3626
+ const hue = (h % 360 + 360) % 360, c = (1 - Math.abs(2 * l - 1)) * s, x = c * (1 - Math.abs(hue / 60 % 2 - 1)), m = l - c / 2;
3627
+ const [r, g, b] = hue < 60 ? [c, x, 0] : hue < 120 ? [x, c, 0] : hue < 180 ? [0, c, x] : hue < 240 ? [0, x, c] : hue < 300 ? [x, 0, c] : [c, 0, x];
3628
+ return toHex([(r + m) * 255, (g + m) * 255, (b + m) * 255]);
3629
+ }
3630
+ function legible(foreground, background, minimum) {
3631
+ if (contrast(foreground, background) >= minimum) return foreground;
3632
+ const [h, s, l] = toHsl(foreground), preferred = luminance(background) < 0.5 ? 1 : -1;
3633
+ for (const direction of [preferred, -preferred]) {
3634
+ for (let step = 1; step <= 100; step++) {
3635
+ const lightness = l + direction * step / 100;
3636
+ if (lightness < 0 || lightness > 1) break;
3637
+ const candidate = fromHsl(h, s, lightness);
3638
+ if (contrast(candidate, background) >= minimum) return candidate;
3639
+ }
3640
+ }
3641
+ return contrast("#FFFFFF", background) >= contrast("#000000", background) ? "#FFFFFF" : "#000000";
3642
+ }
3643
+
3461
3644
  // src/color.ts
3462
3645
  var schemeColorSlots = /* @__PURE__ */ new Set([
3463
3646
  "accent1",
@@ -3539,7 +3722,7 @@ function resolveColorRef(reference, options) {
3539
3722
  var DARK_BACKGROUND_LUMINANCE = 0.179;
3540
3723
  function isDarkColor(color) {
3541
3724
  const hex = normalizeHexColor(color);
3542
- const value = hex ? luminance(hex) : void 0;
3725
+ const value = hex ? luminance2(hex) : void 0;
3543
3726
  return value !== void 0 && value < DARK_BACKGROUND_LUMINANCE;
3544
3727
  }
3545
3728
  function defaultSlideBackground(colorScheme) {
@@ -3697,7 +3880,7 @@ function readableSchemeColor(fill, roles) {
3697
3880
  const ink = { slot: "tx1", hex: normalizeHexColor(roles.text) ?? roles.text };
3698
3881
  return (colorContrast(light.hex, fill) ?? 0) >= (colorContrast(ink.hex, fill) ?? 0) ? light : ink;
3699
3882
  }
3700
- function luminance(value) {
3883
+ function luminance2(value) {
3701
3884
  if (typeof value !== "string") return void 0;
3702
3885
  let hex = value.trim();
3703
3886
  if (/^#[0-9a-f]{3}$/i.test(hex)) hex = `#${[...hex.slice(1)].map((c) => c + c).join("")}`;
@@ -3710,11 +3893,11 @@ function luminance(value) {
3710
3893
  }, 0);
3711
3894
  }
3712
3895
  function colorContrast(foreground, background) {
3713
- const a = luminance(foreground), b = luminance(background);
3896
+ const a = luminance2(foreground), b = luminance2(background);
3714
3897
  return a === void 0 || b === void 0 ? void 0 : (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
3715
3898
  }
3716
3899
  function textColorForFill(fill, preferred) {
3717
- const background = luminance(fill), contrast2 = colorContrast(preferred, fill);
3900
+ const background = luminance2(fill), contrast2 = colorContrast(preferred, fill);
3718
3901
  if (background === void 0 || contrast2 === void 0 || contrast2 >= 4.5) return preferred;
3719
3902
  return (background + 0.05) / 0.05 >= 1.05 / (background + 0.05) ? "#000000" : "#FFFFFF";
3720
3903
  }
@@ -3738,7 +3921,7 @@ var CHART_SERIES_MIN_LIGHTNESS_STEP = 11;
3738
3921
  var CHART_SERIES_MIN_DIFFERENCE = 12;
3739
3922
  var D65 = [0.95047, 1, 1.08883];
3740
3923
  function opaqueChannels(value) {
3741
- if (luminance(value) === void 0) return void 0;
3924
+ if (luminance2(value) === void 0) return void 0;
3742
3925
  let hex = value.trim();
3743
3926
  if (hex.length === 4) hex = `#${[...hex.slice(1)].map((c) => c + c).join("")}`;
3744
3927
  return [1, 3, 5].map((offset) => Number.parseInt(hex.slice(offset, offset + 2), 16));
@@ -3787,8 +3970,13 @@ function labDifference(a, b) {
3787
3970
  var CHART_SERIES_TIER_SIZE = 6;
3788
3971
  var tierOf = (index) => Math.floor(index / CHART_SERIES_TIER_SIZE);
3789
3972
  var CHART_SERIES_SHORTFALL_WEIGHT = 8;
3790
- function chartPaletteForFill(fill, palette) {
3973
+ var CHART_LEAD_MIN_CONTRAST = 4.5;
3974
+ function chartPaletteForFill(fill, palette, options = {}) {
3791
3975
  const opaqueFill = colorContrast(fill, fill) !== void 0;
3976
+ if (options.legibleLead && opaqueFill && isDarkColor(fill) && palette.length > 0) {
3977
+ const lead = normalizeHexColor(palette[0]);
3978
+ if (lead) palette = [legible(lead, normalizeHexColor(fill), CHART_LEAD_MIN_CONTRAST), ...palette.slice(1)];
3979
+ }
3792
3980
  const preferredLab = palette.map((color) => {
3793
3981
  const channels2 = opaqueChannels(color);
3794
3982
  return channels2 ? labOf(channels2) : void 0;
@@ -3846,6 +4034,9 @@ function chartSeriesPalette(colorScheme = {}, colors, context = {}) {
3846
4034
  });
3847
4035
  return palette;
3848
4036
  }
4037
+ function chartLeadFromScheme(colors, colorScheme = {}, context = {}) {
4038
+ return resolveChartColors(colors, colorScheme, context)[0] === void 0;
4039
+ }
3849
4040
  function resolveChartColors(colors, colorScheme = {}, context = {}) {
3850
4041
  if (!Array.isArray(colors)) return [];
3851
4042
  return colors.map((entry) => {
@@ -3882,7 +4073,7 @@ function surfaceAltColor(surface, text2) {
3882
4073
  if (!readable(band)) break;
3883
4074
  if (step(band)) return band;
3884
4075
  }
3885
- const away = (luminance(ink) ?? 0) > (luminance(fill) ?? 0) ? [0, 0, 0] : [255, 255, 255];
4076
+ const away = (luminance2(ink) ?? 0) > (luminance2(fill) ?? 0) ? [0, 0, 0] : [255, 255, 255];
3886
4077
  for (let amount = SURFACE_ALT_MIX; amount <= 1 + 1e-9; amount += 0.02) {
3887
4078
  const band = mixChannels(from, away, Math.min(1, amount));
3888
4079
  if (step(band)) return band;
@@ -3890,54 +4081,6 @@ function surfaceAltColor(surface, text2) {
3890
4081
  return mixChannels(from, toText, SURFACE_ALT_MIX);
3891
4082
  }
3892
4083
 
3893
- // src/legible-color.ts
3894
- var channels = (hex) => [1, 3, 5].map((offset) => Number.parseInt(hex.slice(offset, offset + 2), 16));
3895
- var toHex = (values) => `#${values.map((value) => Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, "0")).join("").toUpperCase()}`;
3896
- var linear = (value) => {
3897
- const n = value / 255;
3898
- return n <= 0.04045 ? n / 12.92 : ((n + 0.055) / 1.055) ** 2.4;
3899
- };
3900
- var luminance2 = (hex) => {
3901
- const [r, g, b] = channels(hex);
3902
- return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);
3903
- };
3904
- var contrast = (a, b) => {
3905
- const x = luminance2(a), y = luminance2(b);
3906
- return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05);
3907
- };
3908
- var normalize = (value) => {
3909
- if (typeof value !== "string") return void 0;
3910
- const text2 = value.trim();
3911
- if (/^#[0-9a-f]{3}$/i.test(text2)) return `#${[...text2.slice(1)].map((c) => c + c).join("")}`.toUpperCase();
3912
- return /^#[0-9a-f]{6}([0-9a-f]{2})?$/i.test(text2) ? text2.slice(0, 7).toUpperCase() : void 0;
3913
- };
3914
- function toHsl(hex) {
3915
- const [r, g, b] = channels(hex).map((value) => value / 255);
3916
- const max = Math.max(r, g, b), min = Math.min(r, g, b), l = (max + min) / 2, d = max - min;
3917
- if (d === 0) return [0, 0, l];
3918
- const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
3919
- const h = max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
3920
- return [h * 60, s, l];
3921
- }
3922
- function fromHsl(h, s, l) {
3923
- const hue = (h % 360 + 360) % 360, c = (1 - Math.abs(2 * l - 1)) * s, x = c * (1 - Math.abs(hue / 60 % 2 - 1)), m = l - c / 2;
3924
- const [r, g, b] = hue < 60 ? [c, x, 0] : hue < 120 ? [x, c, 0] : hue < 180 ? [0, c, x] : hue < 240 ? [0, x, c] : hue < 300 ? [x, 0, c] : [c, 0, x];
3925
- return toHex([(r + m) * 255, (g + m) * 255, (b + m) * 255]);
3926
- }
3927
- function legible(foreground, background, minimum) {
3928
- if (contrast(foreground, background) >= minimum) return foreground;
3929
- const [h, s, l] = toHsl(foreground), preferred = luminance2(background) < 0.5 ? 1 : -1;
3930
- for (const direction of [preferred, -preferred]) {
3931
- for (let step = 1; step <= 100; step++) {
3932
- const lightness = l + direction * step / 100;
3933
- if (lightness < 0 || lightness > 1) break;
3934
- const candidate = fromHsl(h, s, lightness);
3935
- if (contrast(candidate, background) >= minimum) return candidate;
3936
- }
3937
- }
3938
- return contrast("#FFFFFF", background) >= contrast("#000000", background) ? "#FFFFFF" : "#000000";
3939
- }
3940
-
3941
4084
  // src/code-syntax.ts
3942
4085
  var CODE_HIGHLIGHT_MAX_LENGTH = 2e5;
3943
4086
  var CODE_SYNTAX_MIN_CONTRAST = 4.5;
@@ -4637,6 +4780,12 @@ function metricEffectiveSentiment(trend, sentiment) {
4637
4780
  }
4638
4781
  var METRIC_TREND_SHAPES = Object.freeze({ up: "upArrow", down: "downArrow", flat: "rightArrow" });
4639
4782
  var METRIC_TREND_MIN_CONTRAST = 4.5;
4783
+ var METRIC_VALUE_MIN_CONTRAST = 4.5;
4784
+ function metricValueColor(color, background) {
4785
+ const foreground = normalize(color), surface = normalize(background);
4786
+ if (!foreground || !surface || !isDarkColor(surface)) return color;
4787
+ return legible(foreground, surface, METRIC_VALUE_MIN_CONTRAST);
4788
+ }
4640
4789
  var SEMANTIC = {
4641
4790
  positive: { light: "#15803D", dark: "#4ADE80" },
4642
4791
  negative: { light: "#B91C1C", dark: "#F87171" }
@@ -4644,9 +4793,9 @@ var SEMANTIC = {
4644
4793
  function metricTrendColor(trend, options) {
4645
4794
  const background = normalize(options.background) ?? "#FFFFFF";
4646
4795
  const sentiment = metricEffectiveSentiment(trend, options.sentiment);
4647
- if (sentiment === "neutral") return legible(normalize(options.neutral) ?? (luminance2(background) < 0.5 ? "#CBD5E1" : "#475569"), background, METRIC_TREND_MIN_CONTRAST);
4796
+ if (sentiment === "neutral") return legible(normalize(options.neutral) ?? (luminance(background) < 0.5 ? "#CBD5E1" : "#475569"), background, METRIC_TREND_MIN_CONTRAST);
4648
4797
  const pair = SEMANTIC[sentiment];
4649
- return legible(luminance2(background) < 0.5 ? pair.dark : pair.light, background, METRIC_TREND_MIN_CONTRAST);
4798
+ return legible(luminance(background) < 0.5 ? pair.dark : pair.light, background, METRIC_TREND_MIN_CONTRAST);
4650
4799
  }
4651
4800
  function metricTrendPoints(shape, box) {
4652
4801
  const { x: l, y: t, width: w, height: h } = box, r = l + w, b = t + h, hc = l + w / 2, vc = t + h / 2, ss = Math.min(w, h);
@@ -5315,7 +5464,7 @@ function fitImage(frame, fit, aspect, focus = { x: 0.5, y: 0.5 }) {
5315
5464
  right: round2((image.x + width - frame.x - frame.width) / width),
5316
5465
  bottom: round2((image.y + height - frame.y - frame.height) / height)
5317
5466
  };
5318
- for (const key of ["left", "top", "right", "bottom"]) if (Object.is(crop[key], -0)) crop[key] = 0;
5467
+ for (const key of ["left", "top", "right", "bottom"]) if (Math.abs(crop[key]) < 1e-9) crop[key] = 0;
5319
5468
  return { image: roundBox(image), crop };
5320
5469
  }
5321
5470
  function imageBackground(value) {
@@ -5350,17 +5499,29 @@ function composeOverlay(value, frame, shape, path, mirrorSide, diagnostics) {
5350
5499
  const box = !edge ? frame : edge === "top" ? { ...frame, height: round2(frame.height * part) } : edge === "bottom" ? { ...frame, y: round2(frame.y + frame.height * (1 - part)), height: round2(frame.height * part) } : edge === "left" ? { ...frame, width: round2(frame.width * part) } : { ...frame, x: round2(frame.x + frame.width * (1 - part)), width: round2(frame.width * part) };
5351
5500
  return { path, color: overlay.color, opacity, ...edge ? { edge } : {}, box, shape: edge ? imageShape("rectangle", box) : shape };
5352
5501
  }
5502
+ function anchoredOffset(spare, anchor) {
5503
+ return Math.max(0, anchor === "middle" ? spare / 2 : anchor === "bottom" ? spare : 0);
5504
+ }
5505
+ function containedBox(cell, aspect, anchor) {
5506
+ const width = Math.min(cell.width, cell.height * aspect), height = width / aspect;
5507
+ return { x: cell.x + (cell.width - width) / 2, y: cell.y + anchoredOffset(cell.height - height, anchor), width, height };
5508
+ }
5353
5509
  function composeImage(host, hostPath, region, frameArea, context) {
5354
5510
  const kindName = Object.hasOwn(IMAGE_SHAPES, host.shape) ? host.shape : "rectangle";
5355
5511
  const { scale: scale2 } = context;
5512
+ const fit = IMAGE_FITS.includes(host.fit) ? host.fit : context.fit ?? "cover";
5513
+ const pictureAspect = intrinsicImageAspect(host.image, context.assets);
5356
5514
  let box = { ...frameArea };
5357
5515
  const aspect = kindName === "circle" ? 1 : finite(host.aspectRatio, Number.MIN_VALUE, 10);
5358
5516
  if (aspect) {
5359
5517
  const frameWidth = Math.min(box.width, box.height * aspect), frameHeight = frameWidth / aspect;
5360
5518
  box = { x: box.x + (box.width - frameWidth) / 2, y: box.y + (box.height - frameHeight) / 2, width: frameWidth, height: frameHeight };
5519
+ } else if (context.anchor && fit === "contain" && pictureAspect) {
5520
+ const frameWidth = Math.min(box.width, box.height * pictureAspect);
5521
+ box = { x: box.x + (box.width - frameWidth) / 2, y: box.y, width: frameWidth, height: frameWidth / pictureAspect };
5361
5522
  }
5523
+ if (context.anchor && fit === "contain" && !aspect && box.height < frameArea.height) box.y = frameArea.y + anchoredOffset(frameArea.height - box.height, context.anchor);
5362
5524
  box = roundBox(box);
5363
- const fit = IMAGE_FITS.includes(host.fit) ? host.fit : context.fit ?? "cover";
5364
5525
  const focus = imageFocus(host.focus);
5365
5526
  const shape = imageShape(kindName, box, finite(host.cornerRadius, 0, 0.5));
5366
5527
  const result = { region: roundBox(region), box, fit, focus, shape };
@@ -5373,7 +5534,6 @@ function composeImage(host, hostPath, region, frameArea, context) {
5373
5534
  const overlay = composeOverlay(host.overlay, box, shape, `${hostPath}.overlay`, context.mirrorSide, context.diagnostics);
5374
5535
  if (overlay) result.overlay = overlay;
5375
5536
  if (context.placement) result.placement = context.placement;
5376
- const pictureAspect = intrinsicImageAspect(host.image, context.assets);
5377
5537
  if (pictureAspect) result.picture = fitImage(box, fit, pictureAspect, focus);
5378
5538
  return result;
5379
5539
  }
@@ -5597,7 +5757,72 @@ function layoutFurniture(input, options = {}) {
5597
5757
  return { algorithm: "furniture-flow-v2", configured, textMeasurement: options.textMeasurement ? "provided" : "estimated", textOutlines: outlines ? "provided" : "unavailable", parts, headerBottom, footerTop, diagnostics, overflow: diagnostics.length > 0 };
5598
5758
  }
5599
5759
  var textSegments = new Intl.Segmenter("und", { granularity: "grapheme" });
5760
+ var PROSE_NOMINAL_SIZE = 28;
5761
+ var LIST_NOMINAL_SIZE = 32;
5762
+ var LEAD_NOMINAL_SIZE = 32;
5763
+ var TABLE_NOMINAL_SIZE = 25;
5764
+ var QUOTE_NOMINAL_SIZE = 40;
5765
+ var CODE_NOMINAL_SIZE = 22;
5766
+ var LEGACY_CODE_NOMINAL_SIZE = 18;
5767
+ var QUOTE_SOURCE_SEPARATOR = " \u2013 ";
5768
+ var PROSE_MEASURE_CHARS = 70;
5769
+ var AVERAGE_CHARACTER_EM = 0.5;
5600
5770
  var WRAP_TOLERANCE = 0.01;
5771
+ var SENTENCE_END = /[.!?\u2026\u3002\uff01\uff1f]["'\u201d\u2019)\]\u00bb]*$/u;
5772
+ var CLAUSE_END = /[,;:\u2013\u2014\u060c\u061b\uff0c\u3001\uff1b\uff1a]["'\u201d\u2019)\]\u00bb]*$/u;
5773
+ var BALANCE_MAX_WORDS = 48;
5774
+ function balancedBreaks(text2, lines, widthOf, boxWidth) {
5775
+ const forced = /* @__PURE__ */ new Set();
5776
+ const paragraph = (group2) => {
5777
+ const count = group2.length;
5778
+ if (count < 2) return;
5779
+ const from = group2[0].start, to = group2.at(-1).end;
5780
+ const words = [...text2.slice(from, to).matchAll(/(?:[^\s]|\u00a0|\u202f|\ufeff)+/gu)].map((match) => ({ start: from + match.index, end: from + match.index + match[0].length, text: match[0] }));
5781
+ const total = words.length;
5782
+ if (total < count || total > BALANCE_MAX_WORDS) return;
5783
+ const cache2 = /* @__PURE__ */ new Map(), span = (first, last) => {
5784
+ const key = first * (BALANCE_MAX_WORDS + 1) + last;
5785
+ let value = cache2.get(key);
5786
+ if (value === void 0) {
5787
+ value = widthOf(words[first].start, words[last - 1].end);
5788
+ cache2.set(key, value);
5789
+ }
5790
+ return value;
5791
+ };
5792
+ const target = span(0, total) / count;
5793
+ if (!(target > 0)) return;
5794
+ const credit = (last) => last >= total ? 0 : SENTENCE_END.test(words[last - 1].text) ? 0.02 : CLAUSE_END.test(words[last - 1].text) ? 0.01 : 0;
5795
+ const best = Array.from({ length: count + 1 }, () => Array(total + 1).fill(Infinity)), from_ = Array.from({ length: count + 1 }, () => Array(total + 1).fill(-1));
5796
+ best[0][0] = 0;
5797
+ for (let line2 = 1; line2 <= count; line2++) for (let last = line2; last <= total - (count - line2); last++) for (let first = line2 - 1; first < last; first++) {
5798
+ const before = best[line2 - 1][first];
5799
+ if (before === Infinity) continue;
5800
+ const width = span(first, last);
5801
+ if (width > boxWidth + WRAP_TOLERANCE) continue;
5802
+ const cost = before + ((width - target) / target) ** 2 - credit(last);
5803
+ if (cost < best[line2][last]) {
5804
+ best[line2][last] = cost;
5805
+ from_[line2][last] = first;
5806
+ }
5807
+ }
5808
+ if (best[count][total] === Infinity) return;
5809
+ for (let line2 = count, last = total; line2 > 1; line2--) {
5810
+ const first = from_[line2][last];
5811
+ forced.add(words[first].start);
5812
+ last = first;
5813
+ }
5814
+ };
5815
+ let group = [];
5816
+ for (const line2 of lines) {
5817
+ group.push(line2);
5818
+ if (line2.hard) {
5819
+ paragraph(group);
5820
+ group = [];
5821
+ }
5822
+ }
5823
+ paragraph(group);
5824
+ return forced.size ? forced : void 0;
5825
+ }
5601
5826
  var HANGING_SPACE = /[^\S\r\n\u00a0\u202f\ufeff]/u;
5602
5827
  var SCRIPT_WIDTH_FACTORS = [
5603
5828
  [/\p{Script=Tamil}/u, 1.53],
@@ -5621,11 +5846,11 @@ function measureText(text2, fontSize) {
5621
5846
  function wrapText(text2, width, fontSize, measure = measureText) {
5622
5847
  return fitSourceText(text2, { width, height: Number.MAX_VALUE }, fontSize, fontSize, 1, measure, true, false).lines;
5623
5848
  }
5624
- function fitText(text2, box, requestedSize = 25, minFontSize = 16, measure = measureText, direction) {
5849
+ function fitText(text2, box, requestedSize = PROSE_NOMINAL_SIZE, minFontSize = 16, measure = measureText, direction, balance = false) {
5625
5850
  if (![box.width, box.height, requestedSize, minFontSize].every(Number.isFinite) || box.width <= 0 || box.height <= 0 || requestedSize <= 0 || minFontSize <= 0) {
5626
5851
  throw new RangeError("Text dimensions and font sizes must be finite and positive.");
5627
5852
  }
5628
- return fitSourceText(text2, box, Math.max(requestedSize, minFontSize), minFontSize, 1, measure, true, true, direction);
5853
+ return fitSourceText(text2, box, Math.max(requestedSize, minFontSize), minFontSize, 1, measure, true, true, direction, balance);
5629
5854
  }
5630
5855
  var FONT_SIZE_GRID_PER_PX = 75;
5631
5856
  var FONT_GRID_EPSILON = 1e-6;
@@ -5650,11 +5875,11 @@ function fitAtSizes(layout, requested, minimum, step = 1, snap = true) {
5650
5875
  }
5651
5876
  var PHOTO_RATIO = 3;
5652
5877
  var PHOTO_GAP = 0.75;
5653
- function quoteBodyRuns(runs) {
5654
- if (!runs.length) return ['""'];
5878
+ function quoteBodyRuns(runs, marks2) {
5879
+ if (!runs.length) return [marks2.open + marks2.close];
5655
5880
  const last = runs.length - 1;
5656
5881
  return runs.map((run, index) => {
5657
- const before = index === 0 ? '"' : "", after = index === last ? '"' : "";
5882
+ const before = index === 0 ? marks2.open : "", after = index === last ? marks2.close : "";
5658
5883
  return typeof run === "string" ? before + run + after : { ...run, text: before + run.text + after };
5659
5884
  });
5660
5885
  }
@@ -5689,23 +5914,24 @@ function layoutQuote(value, box, options = {}) {
5689
5914
  for (const field of ["attribution", "role", "source"]) {
5690
5915
  const text2 = quote[field];
5691
5916
  if (!text2) continue;
5692
- if (footer) footer += field === "role" ? "\n" : " - ";
5917
+ if (footer) footer += field === "role" ? "\n" : QUOTE_SOURCE_SEPARATOR;
5693
5918
  footerSources.push(source(`${path}.${field}`, text2, footer.length));
5694
5919
  footer += text2;
5695
5920
  }
5696
5921
  const hasPhoto = photoValue !== void 0, hasFooter = footer !== "", hasBlock = hasPhoto || hasFooter;
5697
5922
  const photoPath = `${path}.photo`, rtl = options.direction === "rtl";
5698
5923
  const bodyPath = shorthand ? path : `${path}.text`;
5924
+ const marks2 = quoteMarksFor(options.language);
5699
5925
  const body = add(
5700
5926
  "body",
5701
- `"${quoteText}"`,
5702
- [source(bodyPath, quoteText, 1)],
5927
+ `${marks2.open}${quoteText}${marks2.close}`,
5928
+ [source(bodyPath, quoteText, marks2.open.length)],
5703
5929
  { x: box.x + 18, y: box.y + 18, width: box.width - 36, height: box.height - (hasBlock ? 94 : 36) },
5704
- 28,
5930
+ QUOTE_NOMINAL_SIZE,
5705
5931
  options.fontFamilies?.accent ?? options.fontFamilies?.heading ?? "sans-serif",
5706
5932
  600,
5707
5933
  bodyPath,
5708
- richBody ? quoteBodyRuns(richBody) : void 0
5934
+ richBody ? quoteBodyRuns(richBody, marks2) : void 0
5709
5935
  );
5710
5936
  const attribution = hasFooter ? add(
5711
5937
  "footer",
@@ -5827,7 +6053,7 @@ function layoutMetric(value, box, options = {}) {
5827
6053
  const sourceValue = metric[role];
5828
6054
  if (sourceValue === void 0) continue;
5829
6055
  const text2 = String(sourceValue), path = scalar ? sourcePath : `${sourcePath}.${role}`;
5830
- const nominal = role === "value" ? Math.min(76 * scale2, box.height * 0.28) : (role === "description" ? 20 : role === "trend" ? 18 : 23) * scale2;
6056
+ const nominal = (role === "value" ? 76 : role === "description" ? 20 : role === "trend" ? 18 : 23) * scale2;
5831
6057
  const requestedStyle = { fontFamily: (role === "value" ? options.fontFamilies?.heading : options.fontFamilies?.body) ?? "sans-serif", fontWeight: role === "value" ? 800 : role === "description" ? 400 : 500, italic: false, path };
5832
6058
  const part = {
5833
6059
  role,
@@ -5979,6 +6205,8 @@ function layoutMetric(value, box, options = {}) {
5979
6205
  return { bounds, algorithm: "metric-flow-v1", alignment, textMeasurement: options.textMeasurement ? "provided" : "estimated", arrangement: selected.arrangement, ...metric.sentiment === void 0 ? {} : { sentiment: metric.sentiment }, attempts, parts, diagnostics, overflow: diagnostics.length > 0 };
5980
6206
  }
5981
6207
  var TIMELINE_STATUS_VALUES = ["done", "current", "planned"];
6208
+ var TIMELINE_NARROW_EM = 8;
6209
+ var TIMELINE_NARROW_REDUCTIONS = 4;
5982
6210
  var TIMELINE_RING_RATIO = 1.6;
5983
6211
  function layoutTimeline(value, box, options = {}) {
5984
6212
  const shorthand = Array.isArray(value), timeline = shorthand ? { events: value } : value;
@@ -6042,87 +6270,96 @@ function layoutTimeline(value, box, options = {}) {
6042
6270
  let reductionLimit = 0;
6043
6271
  for (const part of source) reductionLimit = Math.max(reductionLimit, (part.requestedFontSize - minimum) / scale2);
6044
6272
  const reductions = Math.min(24, Math.ceil(reductionLimit));
6045
- for (let reduction = 0; reduction <= reductions; reduction++) {
6046
- for (const arrangement of ["alternating", "vertical"]) {
6047
- attempts++;
6048
- const parts = [], markers = [], diagnostics = [];
6049
- let score2 = 0;
6050
- const report = (part, reason, message, excess = 1) => {
6051
- diagnostics.push({ code: "text-overflow", reason, path: part.path, message });
6052
- score2 += Math.max(1, excess);
6053
- };
6054
- const place = (part, x, y2, width, alignment) => {
6055
- const measured = measure(part, gridFontSize(part.requestedFontSize - reduction * scale2, minimum), Math.max(scale2, width), alignment);
6056
- const area2 = { x, y: y2, width: Math.max(scale2, width), height: Math.max(scale2, measured.height) }, placement = outlines ? placeTextLines(measured.ink, area2, alignment, padding, measured.fit.directions) : void 0;
6057
- const fit = { ...measured.fit, ...placement ? { placement } : {}, overflow: measured.fit.overflow || !!placement?.overflow };
6058
- const accepted = { ...part, box: area2, alignment, fit };
6059
- parts.push(accepted);
6060
- if (fit.overflow) report(accepted, "text-fit", "Timeline field exceeds its readable width; increase its space or split the timeline.");
6061
- if (breaksInsideWord(part.text, fit)) report(accepted, "text-fit", "Timeline field would split a word; use a wider vertical arrangement or paginate events.");
6062
- if (area2.x < box.x - 0.01 || area2.y < box.y - 0.01 || area2.x + area2.width > box.x + box.width + 0.01 || area2.y + area2.height > box.y + box.height + 0.01) report(accepted, "part-outside-cell", "Timeline field extends outside its cell; increase the cell or paginate events.", Math.max(area2.x + area2.width - box.x - box.width, area2.y + area2.height - box.y - box.height));
6063
- return accepted;
6064
- };
6065
- let y = box.y;
6066
- for (const part of metadata) {
6067
- const placed = place(part, box.x, y, box.width, "center");
6068
- y += placed.box.height + 8 * scale2;
6069
- }
6070
- const available = box.y + box.height - y, count = events.length, radius = Math.min(9 * scale2, box.width / Math.max(2, count) / 5, Math.max(scale2, available) * 0.04);
6071
- const statusOf = (index) => timeline.events[index].status, ringRadius = radius * TIMELINE_RING_RATIO, strokeWidth = Math.min(2 * scale2, radius / 2);
6072
- const hasRing = timeline.events.some((event) => event.status === "current");
6073
- const marker = (index, x, y2) => {
6074
- const status = statusOf(index);
6075
- return { path: eventPath(index), eventIndex: index, x, y: y2, radius, ...status ? { status } : {}, ...status === "current" ? { ring: { radius: ringRadius } } : {}, ...status === "current" || status === "planned" ? { strokeWidth } : {} };
6076
- };
6077
- if (arrangement === "alternating") {
6078
- const width = box.width / Math.max(2, count), step = (box.width - width) / Math.max(1, count - 1), start = count === 1 ? box.x + box.width / 2 : box.x + width / 2;
6079
- const fieldGap = 4 * scale2, clearance = 24 * scale2;
6080
- const heights = events.map((fields2) => fields2.reduce((height, part) => height + measure(part, gridFontSize(part.requestedFontSize - reduction * scale2, minimum), width, "center").height, 0) + Math.max(0, fields2.length - 1) * fieldGap);
6081
- const markerExtent = hasRing ? ringRadius + strokeWidth / 2 : radius;
6082
- const upper = Math.max(markerExtent, ...heights.filter((_, index) => index % 2 === 0).map((height) => height + clearance));
6083
- const lower = Math.max(markerExtent, ...heights.filter((_, index) => index % 2 === 1).map((height) => height + clearance));
6084
- const lineY = y + Math.max(0, (available - upper - lower) / 2) + upper;
6085
- events.forEach((fields2, index) => {
6086
- const x = start + (rtl ? count - 1 - index : index) * step, top = index % 2 === 0 ? lineY - clearance - heights[index] : lineY + clearance, bottom = index % 2 === 0 ? lineY - clearance : box.y + box.height;
6087
- markers.push(marker(index, x, lineY));
6088
- let cursor = top;
6089
- for (const [fieldIndex, part] of fields2.entries()) {
6090
- const placed = place(part, x - width / 2, cursor, width, "center");
6091
- cursor += placed.box.height;
6092
- if (cursor > bottom + 0.01) report(placed, "event-space", "Timeline event labels exceed their side of the connector; change the arrangement or paginate events.", cursor - bottom);
6093
- if (fieldIndex < fields2.length - 1) cursor += fieldGap;
6094
- }
6095
- });
6096
- } else {
6097
- const outlined = timeline.events.some((event) => event.status === "planned");
6098
- const lead = hasRing ? ringRadius + strokeWidth / 2 : radius + (outlined ? strokeWidth / 2 : 0), gap = 2 * lead + 12 * scale2;
6099
- const x = rtl ? box.x + box.width - lead : box.x + lead, textX = rtl ? box.x : box.x + gap, width = box.width - gap;
6100
- events.forEach((fields2, index) => {
6101
- const top = y;
6102
- let first2;
6103
- for (const [fieldIndex, part] of fields2.entries()) {
6104
- const placed = place(part, textX, y, width, "left");
6105
- first2 ??= placed;
6106
- y += placed.box.height;
6107
- if (fieldIndex < fields2.length - 1) y += 4 * scale2;
6108
- }
6109
- markers.push(marker(index, x, top + Math.min(first2?.box.height ?? 2 * radius, 2 * radius) / 2));
6110
- y += 16 * scale2;
6111
- });
6112
- }
6113
- for (const marker2 of markers) {
6114
- const extent = (marker2.ring?.radius ?? marker2.radius) + (marker2.strokeWidth ?? 0) / 2;
6115
- if (marker2.x - extent < box.x - 0.01 || marker2.y - extent < box.y - 0.01 || marker2.x + extent > box.x + box.width + 0.01 || marker2.y + extent > box.y + box.height + 0.01) {
6116
- diagnostics.push({ code: "text-overflow", reason: "event-space", path: marker2.path, message: "Timeline marker has no usable space; increase the cell or paginate events." });
6117
- score2 += 1;
6273
+ const labelSize = source.find((part) => part.role === "what")?.requestedFontSize ?? gridFontSize(22 * scale2, minimum);
6274
+ const narrow = timeline.events.length > 1 && box.width / Math.max(2, timeline.events.length) < TIMELINE_NARROW_EM * labelSize;
6275
+ const tryArrangement = (arrangement, reduction) => {
6276
+ attempts++;
6277
+ const parts = [], markers = [], diagnostics = [];
6278
+ let score2 = 0;
6279
+ const report = (part, reason, message, excess = 1) => {
6280
+ diagnostics.push({ code: "text-overflow", reason, path: part.path, message });
6281
+ score2 += Math.max(1, excess);
6282
+ };
6283
+ const place = (part, x, y2, width, alignment) => {
6284
+ const measured = measure(part, gridFontSize(part.requestedFontSize - reduction * scale2, minimum), Math.max(scale2, width), alignment);
6285
+ const area2 = { x, y: y2, width: Math.max(scale2, width), height: Math.max(scale2, measured.height) }, placement = outlines ? placeTextLines(measured.ink, area2, alignment, padding, measured.fit.directions) : void 0;
6286
+ const fit = { ...measured.fit, ...placement ? { placement } : {}, overflow: measured.fit.overflow || !!placement?.overflow };
6287
+ const accepted = { ...part, box: area2, alignment, fit };
6288
+ parts.push(accepted);
6289
+ if (fit.overflow) report(accepted, "text-fit", "Timeline field exceeds its readable width; increase its space or split the timeline.");
6290
+ if (breaksInsideWord(part.text, fit)) report(accepted, "text-fit", "Timeline field would split a word; use a wider vertical arrangement or paginate events.");
6291
+ if (area2.x < box.x - 0.01 || area2.y < box.y - 0.01 || area2.x + area2.width > box.x + box.width + 0.01 || area2.y + area2.height > box.y + box.height + 0.01) report(accepted, "part-outside-cell", "Timeline field extends outside its cell; increase the cell or paginate events.", Math.max(area2.x + area2.width - box.x - box.width, area2.y + area2.height - box.y - box.height));
6292
+ return accepted;
6293
+ };
6294
+ let y = box.y;
6295
+ for (const part of metadata) {
6296
+ const placed = place(part, box.x, y, box.width, "center");
6297
+ y += placed.box.height + 8 * scale2;
6298
+ }
6299
+ const available = box.y + box.height - y, count = events.length, radius = Math.min(9 * scale2, box.width / Math.max(2, count) / 5, Math.max(scale2, available) * 0.04);
6300
+ const statusOf = (index) => timeline.events[index].status, ringRadius = radius * TIMELINE_RING_RATIO, strokeWidth = Math.min(2 * scale2, radius / 2);
6301
+ const hasRing = timeline.events.some((event) => event.status === "current");
6302
+ const marker = (index, x, y2) => {
6303
+ const status = statusOf(index);
6304
+ return { path: eventPath(index), eventIndex: index, x, y: y2, radius, ...status ? { status } : {}, ...status === "current" ? { ring: { radius: ringRadius } } : {}, ...status === "current" || status === "planned" ? { strokeWidth } : {} };
6305
+ };
6306
+ if (arrangement === "alternating") {
6307
+ const width = box.width / Math.max(2, count), step = (box.width - width) / Math.max(1, count - 1), start = count === 1 ? box.x + box.width / 2 : box.x + width / 2;
6308
+ const fieldGap = 4 * scale2, clearance = 24 * scale2;
6309
+ const heights = events.map((fields2) => fields2.reduce((height, part) => height + measure(part, gridFontSize(part.requestedFontSize - reduction * scale2, minimum), width, "center").height, 0) + Math.max(0, fields2.length - 1) * fieldGap);
6310
+ const markerExtent = hasRing ? ringRadius + strokeWidth / 2 : radius;
6311
+ const upper = Math.max(markerExtent, ...heights.filter((_, index) => index % 2 === 0).map((height) => height + clearance));
6312
+ const lower = Math.max(markerExtent, ...heights.filter((_, index) => index % 2 === 1).map((height) => height + clearance));
6313
+ const lineY = y + Math.max(0, (available - upper - lower) / 2) + upper;
6314
+ events.forEach((fields2, index) => {
6315
+ const x = start + (rtl ? count - 1 - index : index) * step, top = index % 2 === 0 ? lineY - clearance - heights[index] : lineY + clearance, bottom = index % 2 === 0 ? lineY - clearance : box.y + box.height;
6316
+ markers.push(marker(index, x, lineY));
6317
+ let cursor = top;
6318
+ for (const [fieldIndex, part] of fields2.entries()) {
6319
+ const placed = place(part, x - width / 2, cursor, width, "center");
6320
+ cursor += placed.box.height;
6321
+ if (cursor > bottom + 0.01) report(placed, "event-space", "Timeline event labels exceed their side of the connector; change the arrangement or paginate events.", cursor - bottom);
6322
+ if (fieldIndex < fields2.length - 1) cursor += fieldGap;
6118
6323
  }
6324
+ });
6325
+ } else {
6326
+ const outlined = timeline.events.some((event) => event.status === "planned");
6327
+ const lead = hasRing ? ringRadius + strokeWidth / 2 : radius + (outlined ? strokeWidth / 2 : 0), gap = 2 * lead + 12 * scale2;
6328
+ const x = rtl ? box.x + box.width - lead : box.x + lead, textX = rtl ? box.x : box.x + gap, width = box.width - gap;
6329
+ events.forEach((fields2, index) => {
6330
+ const top = y;
6331
+ let first2;
6332
+ for (const [fieldIndex, part] of fields2.entries()) {
6333
+ const placed = place(part, textX, y, width, "left");
6334
+ first2 ??= placed;
6335
+ y += placed.box.height;
6336
+ if (fieldIndex < fields2.length - 1) y += 4 * scale2;
6337
+ }
6338
+ markers.push(marker(index, x, top + Math.min(first2?.box.height ?? 2 * radius, 2 * radius) / 2));
6339
+ y += 16 * scale2;
6340
+ });
6341
+ }
6342
+ for (const marker2 of markers) {
6343
+ const extent = (marker2.ring?.radius ?? marker2.radius) + (marker2.strokeWidth ?? 0) / 2;
6344
+ if (marker2.x - extent < box.x - 0.01 || marker2.y - extent < box.y - 0.01 || marker2.x + extent > box.x + box.width + 0.01 || marker2.y + extent > box.y + box.height + 0.01) {
6345
+ diagnostics.push({ code: "text-overflow", reason: "event-space", path: marker2.path, message: "Timeline marker has no usable space; increase the cell or paginate events." });
6346
+ score2 += 1;
6347
+ }
6348
+ }
6349
+ const first = markers[0], last = markers.at(-1), connector = rtl ? { x1: Math.min(first.x, last.x), y1: first.y, x2: Math.max(first.x, last.x), y2: last.y } : { x1: first.x, y1: first.y, x2: last.x, y2: last.y };
6350
+ const candidate = { arrangement, parts, markers, connector, diagnostics, score: score2 };
6351
+ if (!selected || score2 < selected.score) selected = candidate;
6352
+ return !diagnostics.length;
6353
+ };
6354
+ let settled = false;
6355
+ if (narrow) for (let reduction = 0; reduction <= Math.min(reductions, TIMELINE_NARROW_REDUCTIONS) && !settled; reduction++) settled = tryArrangement("vertical", reduction);
6356
+ for (let reduction = 0; reduction <= reductions && !settled; reduction++) {
6357
+ for (const arrangement of ["alternating", "vertical"]) {
6358
+ if (tryArrangement(arrangement, reduction)) {
6359
+ settled = true;
6360
+ break;
6119
6361
  }
6120
- const first = markers[0], last = markers.at(-1), connector = rtl ? { x1: Math.min(first.x, last.x), y1: first.y, x2: Math.max(first.x, last.x), y2: last.y } : { x1: first.x, y1: first.y, x2: last.x, y2: last.y };
6121
- const candidate = { arrangement, parts, markers, connector, diagnostics, score: score2 };
6122
- if (!selected || score2 < selected.score) selected = candidate;
6123
- if (!diagnostics.length) break;
6124
6362
  }
6125
- if (!selected.diagnostics.length) break;
6126
6363
  }
6127
6364
  const { score, ...result } = selected;
6128
6365
  if (result.diagnostics.length && options.overflow === "error") throw new OPFCompositionError(result.diagnostics);
@@ -6131,9 +6368,9 @@ function layoutTimeline(value, box, options = {}) {
6131
6368
  function fitCodeText(text2, box, size, minimum, step, measure, direction) {
6132
6369
  return fitSourceText(text2, box, size, minimum, step, measure, false, true, direction);
6133
6370
  }
6134
- function fitSourceText(text2, box, size, minimum, step, measure, prose = false, snap = true, direction) {
6371
+ function fitSourceText(text2, box, size, minimum, step, measure, prose = false, snap = true, direction, balance = false) {
6135
6372
  const directionAt = direction === "rtl" ? paragraphDirectionAt(text2, "rtl") : void 0;
6136
- const layout = (fontSize) => {
6373
+ const layout = (fontSize, forced) => {
6137
6374
  const tabWidth = measure(" ", fontSize) * 4;
6138
6375
  if (!Number.isFinite(tabWidth) || text2.includes(" ") && tabWidth <= 0) throw new RangeError("Text tabs require a positive finite measured space advance.");
6139
6376
  const measureLine = (start2, end2, record8 = false) => {
@@ -6174,6 +6411,7 @@ function fitSourceText(text2, box, size, minimum, step, measure, prose = false,
6174
6411
  push(a, b, "hard");
6175
6412
  continue;
6176
6413
  }
6414
+ if (forced?.has(a) && end > start && /\S/u.test(text2.slice(start, end))) push(a, a, "soft");
6177
6415
  if (width(start, b) <= box.width + WRAP_TOLERANCE) {
6178
6416
  end = b;
6179
6417
  continue;
@@ -6211,7 +6449,22 @@ function fitSourceText(text2, box, size, minimum, step, measure, prose = false,
6211
6449
  overflow: sourceLines.length * lineHeight > box.height + 0.01 || sourceLines.some((line2) => line2.width > box.width + 0.01)
6212
6450
  };
6213
6451
  };
6214
- return fitAtSizes(layout, size, minimum, step, snap);
6452
+ const balanced = (fontSize) => {
6453
+ const greedy = layout(fontSize);
6454
+ if (!balance || greedy.overflow || greedy.sourceLines.length < 2) return greedy;
6455
+ const widthOf = (start, end) => {
6456
+ let x = 0;
6457
+ for (const [index, segment2] of text2.slice(start, end).split(" ").entries()) {
6458
+ if (index) x = (Math.floor(x / (measure(" ", fontSize) * 4) + 1e-9) + 1) * measure(" ", fontSize) * 4;
6459
+ if (segment2) x += measure(segment2, fontSize);
6460
+ }
6461
+ return x;
6462
+ };
6463
+ const forced = balancedBreaks(text2, greedy.sourceLines.map((line2) => ({ start: line2.start, end: line2.end, hard: line2.boundary !== "soft" })), widthOf, box.width);
6464
+ const result = forced ? layout(fontSize, forced) : greedy;
6465
+ return result.sourceLines.length === greedy.sourceLines.length && !result.overflow ? result : greedy;
6466
+ };
6467
+ return fitAtSizes(balance ? balanced : layout, size, minimum, step, snap);
6215
6468
  }
6216
6469
  function layoutCode(value, box, options = {}) {
6217
6470
  const shorthand = typeof value === "string", code = shorthand ? { source: value } : value;
@@ -6234,7 +6487,7 @@ function layoutCode(value, box, options = {}) {
6234
6487
  sources: generated ? [] : [{ path: partPath, start: 0, end: text2.length }],
6235
6488
  generated,
6236
6489
  box: { ...inner },
6237
- requestedFontSize: gridFontSize((role === "body" ? 18 : 14) * scale2, minimum),
6490
+ requestedFontSize: gridFontSize((role === "body" ? CODE_NOMINAL_SIZE : 14) * scale2, minimum),
6238
6491
  minFontSize: minimum,
6239
6492
  requestedStyle,
6240
6493
  style: resolveTextStyle({ ...requestedStyle }, options.textMeasurement)
@@ -6247,7 +6500,16 @@ function layoutCode(value, box, options = {}) {
6247
6500
  if (!parts.length) add("language", "code", path, true);
6248
6501
  const body = add("body", code.source, shorthand ? path : `${path}.source`), headers = parts.filter((part) => part !== body);
6249
6502
  const usable2 = (area2) => [area2.x, area2.y, area2.width, area2.height].every(Number.isFinite) && area2.width > 0 && area2.height > 0;
6250
- const fit = (part, area2, size = part.requestedFontSize, floor = minimum) => usable2(area2) ? fitCodeText(part.text, area2, size, floor, scale2, textWidthMeasurer(part.style, options.textMeasurement)) : void 0;
6503
+ const measureCode = textWidthMeasurer(body.style, options.textMeasurement);
6504
+ const fit = (part, area2, size = part.requestedFontSize, floor = minimum) => {
6505
+ if (!usable2(area2)) return void 0;
6506
+ const wanted = fitCodeText(part.text, area2, size, floor, scale2, textWidthMeasurer(part.style, options.textMeasurement));
6507
+ const wraps = (value2) => value2.sourceLines.filter((line2) => line2.boundary === "soft").length;
6508
+ const legacy = gridFontSize(LEGACY_CODE_NOMINAL_SIZE * scale2, minimum);
6509
+ if (part.role !== "body" || part.requestedFontSize <= legacy || !wraps(wanted)) return wanted;
6510
+ const earlier = fitCodeText(part.text, area2, legacy, floor, scale2, measureCode);
6511
+ return !earlier.overflow && wraps(earlier) < wraps(wanted) ? earlier : wanted;
6512
+ };
6251
6513
  if (usable2(inner)) {
6252
6514
  const combinations = headers.reduce((choices, part) => choices.flatMap((choice) => [.../* @__PURE__ */ new Set([part.requestedFontSize, minimum])].map((size) => [...choice, { part, size }])), [[]]);
6253
6515
  let selected;
@@ -6270,6 +6532,7 @@ function layoutCode(value, box, options = {}) {
6270
6532
  part.box = box2;
6271
6533
  part.fit = fit2;
6272
6534
  }
6535
+ if (selected && !selected.overflow && body.fit) body.box = { ...body.box, height: Math.min(body.box.height, body.fit.lines.length * body.fit.lineHeight) };
6273
6536
  }
6274
6537
  const report = (reason, part, roles, message) => diagnostics.push({ code: "text-overflow", reason, path: part.path, parts: roles, message });
6275
6538
  for (const part of parts) {
@@ -6287,9 +6550,11 @@ function layoutCode(value, box, options = {}) {
6287
6550
  }
6288
6551
  }
6289
6552
  if (diagnostics.length && options.overflow === "error") throw new OPFCompositionError(diagnostics);
6290
- return { algorithm: "code-flow-v1", textMeasurement: options.textMeasurement ? "provided" : "estimated", parts, diagnostics, overflow: diagnostics.length > 0 };
6553
+ const bottom = body.fit && !body.fit.overflow ? body.box.y + body.box.height + 18 : box.y + box.height;
6554
+ const bounds = { x: box.x, y: box.y, width: box.width, height: Math.min(box.height, Math.max(bottom - box.y, 0)) };
6555
+ return { bounds, algorithm: "code-flow-v1", textMeasurement: options.textMeasurement ? "provided" : "estimated", parts, diagnostics, overflow: diagnostics.length > 0 };
6291
6556
  }
6292
- function fitRichText(input, box, requestedSize = 25, minFontSize = 16, options = { style: { fontFamily: "sans-serif", fontWeight: 400 } }) {
6557
+ function fitRichText(input, box, requestedSize = PROSE_NOMINAL_SIZE, minFontSize = 16, options = { style: { fontFamily: "sans-serif", fontWeight: 400 } }) {
6293
6558
  if (![box.width, box.height, requestedSize, minFontSize].every((value) => Number.isFinite(value) && value > 0)) throw new RangeError("Rich text dimensions and font sizes must be finite and positive.");
6294
6559
  const layout = richTextLayouter(input, box, requestedSize, options, minFontSize);
6295
6560
  return fitAtSizes(layout, requestedSize, richMinimum(input, requestedSize, minFontSize));
@@ -6306,7 +6571,7 @@ function richMinimum(input, requested, minimum) {
6306
6571
  return visible ? floor : minimum;
6307
6572
  }
6308
6573
  function richTextLayouter(input, box, requestedSize, options, minimum = 0) {
6309
- let offset = 0;
6574
+ let offset = 0, lastWidth = () => 0, lastRanges = [];
6310
6575
  const source = input.map((value, runIndex) => {
6311
6576
  const run = typeof value === "string" ? { text: value } : value;
6312
6577
  if (typeof run?.text !== "string" || run.fontSize !== void 0 && (!Number.isFinite(run.fontSize) || run.fontSize <= 0)) throw new RangeError("Rich text runs need text and a positive finite font size.");
@@ -6320,7 +6585,7 @@ function richTextLayouter(input, box, requestedSize, options, minimum = 0) {
6320
6585
  const whole = source.map((entry) => entry.run.text).join("");
6321
6586
  const paint = (raw) => raw >= minimum - 1e-6 ? gridFontSize(raw, minimum) : snapFontSizeDown(raw);
6322
6587
  const directionAt = options.direction === "rtl" ? paragraphDirectionAt(whole, "rtl") : void 0;
6323
- const layout = (rawFontSize) => {
6588
+ const build = (rawFontSize, forced) => {
6324
6589
  const fontSize = paint(rawFontSize), ratio = rawFontSize / requestedSize;
6325
6590
  const fragments = (start, end) => {
6326
6591
  let x = 0;
@@ -6375,6 +6640,7 @@ function richTextLayouter(input, box, requestedSize, options, minimum = 0) {
6375
6640
  lineEnd = end;
6376
6641
  continue;
6377
6642
  }
6643
+ if (forced?.has(start) && lineEnd > lineStart && /\S/u.test(whole.slice(lineStart, lineEnd))) push(start);
6378
6644
  if (width(lineStart, end) <= box.width + WRAP_TOLERANCE) {
6379
6645
  lineEnd = end;
6380
6646
  continue;
@@ -6418,7 +6684,7 @@ function richTextLayouter(input, box, requestedSize, options, minimum = 0) {
6418
6684
  y += height;
6419
6685
  }
6420
6686
  }
6421
- return {
6687
+ const fit = {
6422
6688
  lines: ranges.map((range) => whole.slice(range.start, range.end)),
6423
6689
  fontSize,
6424
6690
  lineHeight: Math.max(fontSize * 1.22, ...richLines.map((line2) => line2.height)),
@@ -6427,12 +6693,22 @@ function richTextLayouter(input, box, requestedSize, options, minimum = 0) {
6427
6693
  overflow: y > box.height + 0.01 || richLines.some((line2) => line2.width > box.width + 0.01),
6428
6694
  ...directionAt ? { directions: ranges.map((range) => directionAt(range.start)) } : {}
6429
6695
  };
6696
+ lastWidth = width;
6697
+ lastRanges = ranges;
6698
+ return fit;
6699
+ };
6700
+ const layout = (rawFontSize) => {
6701
+ const greedy = build(rawFontSize);
6702
+ if (!options.balance || greedy.overflow || greedy.richLines.length < 2) return greedy;
6703
+ const forced = balancedBreaks(whole, lastRanges.map((range) => ({ start: range.start, end: range.end, hard: /^[\r\n]/.test(whole.slice(range.end, range.end + 1)) || range.end >= whole.length })), lastWidth, box.width);
6704
+ const result = forced ? build(rawFontSize, forced) : greedy;
6705
+ return result.richLines.length === greedy.richLines.length && !result.overflow ? result : greedy;
6430
6706
  };
6431
6707
  return layout;
6432
6708
  }
6433
6709
  var PICTURE_BULLET_SCALE = 0.65;
6434
6710
  function contentNominalSize(field) {
6435
- return field === "items" || field === "bullets" ? 32 : 25;
6711
+ return field === "items" || field === "bullets" ? LIST_NOMINAL_SIZE : field === "table" ? TABLE_NOMINAL_SIZE : PROSE_NOMINAL_SIZE;
6436
6712
  }
6437
6713
  function fitList(input, box, requestedSize = contentNominalSize("items"), minFontSize = 16, options = { style: { fontFamily: "sans-serif", fontWeight: 400 } }) {
6438
6714
  if (!Array.isArray(input) || ![box.width, box.height, requestedSize, minFontSize].every((value) => Number.isFinite(value) && value > 0)) throw new RangeError("List content, dimensions and font sizes must be valid.");
@@ -6457,10 +6733,11 @@ function fitList(input, box, requestedSize = contentNominalSize("items"), minFon
6457
6733
  const entries = [], lines = [], directions = [];
6458
6734
  const markerStyles = numbers ? source.map((item) => resolveTextStyle(markerStyleFor(item), options.textMeasurement)) : [];
6459
6735
  const markerWidths = numbers ? numbers.map((number, index) => textWidthMeasurer(markerStyles[index], options.textMeasurement)(number.text, fontSize)) : [];
6736
+ const none = options.bullet === "none" && !numbers;
6460
6737
  const hanging = numbers ? Math.max(fontSize * 1.1, Math.max(0, ...markerWidths) + fontSize * 0.3) : fontSize * 1.1;
6461
6738
  for (const item of source) {
6462
6739
  const rtl = item.direction === "rtl";
6463
- const indent = hanging, offset = Math.min(box.width, item.level * indent), x = rtl ? box.x : box.x + offset + indent, width = Math.max(1, rtl ? box.width - offset - indent : box.x + box.width - x);
6740
+ const indent = none ? 0 : hanging, offset = Math.min(box.width, item.level * hanging), x = rtl ? box.x : box.x + offset + indent, width = Math.max(1, rtl ? box.width - offset - indent : box.x + box.width - x);
6464
6741
  if (offset + indent >= box.width) overflow = true;
6465
6742
  const textBox = { x, y, width, height: box.height };
6466
6743
  const style = { ...options.style, path: item.textPath };
@@ -6481,7 +6758,7 @@ function fitList(input, box, requestedSize = contentNominalSize("items"), minFon
6481
6758
  }
6482
6759
  overflow ||= text2.overflow || !!description?.overflow;
6483
6760
  const number = numbers?.[item.index];
6484
- const marker = { text: number ? number.text : ["\u2022", "\u25E6", "\u25AA"][item.level % 3], x: rtl ? box.x + box.width - offset : box.x + offset, y: textBox.y + (text2.richLines[0]?.baseline ?? fontSize), fontSize, style: number ? markerStyles[item.index] : resolveTextStyle(style, options.textMeasurement), indent, ...number ? { number, width: markerWidths[item.index] } : {}, ...rtl ? { anchor: "end" } : {} }, side = fontSize * PICTURE_BULLET_SCALE;
6761
+ const marker = { text: number ? number.text : none ? "" : ["\u2022", "\u25E6", "\u25AA"][item.level % 3], x: rtl ? box.x + box.width - offset : box.x + offset, y: textBox.y + (text2.richLines[0]?.baseline ?? fontSize), fontSize, style: number ? markerStyles[item.index] : resolveTextStyle(style, options.textMeasurement), indent, ...number ? { number, width: markerWidths[item.index] } : {}, ...none ? { none: true } : {}, ...rtl ? { anchor: "end" } : {} }, side = fontSize * PICTURE_BULLET_SCALE;
6485
6762
  entries.push({
6486
6763
  index: item.index,
6487
6764
  level: item.level,
@@ -6495,7 +6772,7 @@ function fitList(input, box, requestedSize = contentNominalSize("items"), minFon
6495
6772
  descriptionBox,
6496
6773
  marker,
6497
6774
  ...item.direction ? { direction: item.direction } : {},
6498
- ...options.bulletImage && !number ? { bulletImage: options.bulletImage, bulletBox: { x: rtl ? marker.x - side : marker.x, y: marker.y - side, width: side, height: side } } : {}
6775
+ ...options.bulletImage && !number && !none ? { bulletImage: options.bulletImage, bulletBox: { x: rtl ? marker.x - side : marker.x, y: marker.y - side, width: side, height: side } } : {}
6499
6776
  });
6500
6777
  if (item.index < source.length - 1) y += fontSize * 0.28;
6501
6778
  }
@@ -6537,15 +6814,16 @@ function layoutTable(value, box, options = {}) {
6537
6814
  const shown = tableCellDisplayValue(cell.input, formats[cell.column]);
6538
6815
  if (shown !== cell.input) cell.value = typeof shown === "object" && shown !== null && !Array.isArray(shown) ? shown.value : shown;
6539
6816
  }
6540
- const columnCount = grid.columnCount, cellWidth = box.width / columnCount;
6817
+ const columnCount = grid.columnCount;
6541
6818
  const cells = grid.rows.flat().map((cell) => {
6542
6819
  const padding = { top: 8, right: 10, bottom: 4, left: 10, ...cell.style.padding };
6543
- const width = cellWidth * cell.colSpan - (padding.left + padding.right) * scale2;
6820
+ const width = 0;
6544
6821
  const style = { fontFamily: options.fontFamily ?? "sans-serif", fontWeight: cell.header || cell.style.bold ? 700 : 400, italic: false, path: cell.valuePath };
6545
6822
  const numericDefault = numeric.has(cell) || (cell.header || Array.isArray(cell.value)) && cell.colSpan === 1 && numericColumns.has(cell.column);
6546
6823
  const alignment = cell.style.align ?? options.alignment ?? (numericDefault ? options.direction === "rtl" ? "left" : "right" : "left");
6547
6824
  return { ...cell, padding, width, textStyle: style, alignment };
6548
6825
  });
6826
+ const columnWidths2 = Array(columnCount).fill(box.width / columnCount);
6549
6827
  const cellMinimum = (cell) => Array.isArray(cell.value) ? richMinimum(cell.value, requested, minimum) : minimum;
6550
6828
  let trial = requested;
6551
6829
  const cellSize = (cell) => {
@@ -6556,6 +6834,59 @@ function layoutTable(value, box, options = {}) {
6556
6834
  const textBox = { width: Math.max(scale2, cell.width), height: Math.max(scale2, height) }, size = cellSize(cell);
6557
6835
  return Array.isArray(cell.value) ? richTextLayouter(cell.value, textBox, requested, { style: cell.textStyle, ...options.codeFontFamily ? { codeFontFamily: options.codeFontFamily } : {}, textMeasurement: options.textMeasurement, uniformLineHeight: true, direction: options.direction }, minimum)(size) : fitText(flatten(cell.value), textBox, size, size, textWidthMeasurer(resolveTextStyle(cell.textStyle, options.textMeasurement), options.textMeasurement), options.direction);
6558
6836
  };
6837
+ const horizontalPadding = (cell) => (cell.padding.left + cell.padding.right) * scale2;
6838
+ const measureText2 = (cell) => textWidthMeasurer(resolveTextStyle(cell.textStyle, options.textMeasurement), options.textMeasurement);
6839
+ const wordSplit = /[^\S\u00a0\u202f\ufeff]+/u;
6840
+ const demand = (cell) => {
6841
+ const measure = measureText2(cell), size = cellSize(cell);
6842
+ let widest = 0, longest = 0;
6843
+ for (const line2 of flatten(cell.value).split(/\r\n|\r|\n/)) {
6844
+ longest = Math.max(longest, measure(line2.replace(/\t/g, " "), size));
6845
+ for (const word of line2.split(wordSplit)) {
6846
+ if (!word) continue;
6847
+ widest = Math.max(widest, UNSPACED_SCRIPT.test(word) ? Math.max(...[...word].map((character) => measure(character, size))) : measure(word, size));
6848
+ }
6849
+ }
6850
+ const padding = horizontalPadding(cell), slack = 0.5 * scale2;
6851
+ return { min: widest > 0 ? widest + padding + slack : padding, max: Math.max(longest + padding + slack, padding) };
6852
+ };
6853
+ const sizeColumns = () => {
6854
+ const min = Array(columnCount).fill(0), max = Array(columnCount).fill(0), demands = cells.map(demand);
6855
+ cells.forEach((cell, index) => {
6856
+ if (cell.colSpan === 1) {
6857
+ min[cell.column] = Math.max(min[cell.column], demands[index].min);
6858
+ max[cell.column] = Math.max(max[cell.column], demands[index].max);
6859
+ }
6860
+ });
6861
+ cells.forEach((cell, index) => {
6862
+ if (cell.colSpan > 1) for (const [need, target] of [[demands[index].min, min], [demands[index].max, max]]) {
6863
+ const columns2 = Array.from({ length: cell.colSpan }, (_, offset) => cell.column + offset), have = columns2.reduce((sum2, column) => sum2 + target[column], 0);
6864
+ if (need > have) for (const column of columns2) target[column] += (need - have) / cell.colSpan;
6865
+ }
6866
+ });
6867
+ const total = box.width, sum = (values) => values.reduce((a, b) => a + b, 0);
6868
+ for (let column = 0; column < columnCount; column++) max[column] = Math.max(max[column], min[column], 1);
6869
+ const minTotal = sum(min), maxTotal = sum(max);
6870
+ let widths;
6871
+ if (maxTotal <= total) widths = max.map((value2) => value2 + (total - maxTotal) * value2 / maxTotal);
6872
+ else if (minTotal <= total) {
6873
+ let low = 0, high = total;
6874
+ for (let step = 0; step < 60; step++) {
6875
+ const level = (low + high) / 2;
6876
+ if (sum(min.map((value2, column) => Math.min(max[column], Math.max(value2, level)))) > total) high = level;
6877
+ else low = level;
6878
+ }
6879
+ widths = min.map((value2, column) => Math.min(max[column], Math.max(value2, low)));
6880
+ const rest = total - sum(widths), open = widths.map((value2, column) => value2 > min[column] + 1e-9 && value2 < max[column] - 1e-9);
6881
+ const target = open.indexOf(true);
6882
+ if (target >= 0) widths[target] += rest;
6883
+ else widths[widths.indexOf(Math.max(...widths))] += rest;
6884
+ } else widths = min.map((value2) => value2 * total / Math.max(minTotal, Number.EPSILON));
6885
+ columnWidths2.splice(0, columnCount, ...widths);
6886
+ for (const cell of cells) cell.width = sum(columnWidths2.slice(cell.column, cell.column + cell.colSpan)) - horizontalPadding(cell);
6887
+ return min;
6888
+ };
6889
+ const splitsWords = () => cells.some((cell) => demand(cell).min - 0.5 * scale2 > cell.width + horizontalPadding(cell) + 0.01);
6559
6890
  const textHeight = (fit) => "height" in fit ? fit.height : fit.lines.length * fit.lineHeight;
6560
6891
  const required = (cell, natural) => {
6561
6892
  const floor = cellMinimum(cell), size = gridFontSize(natural ? Math.max(trial, floor) : floor, floor);
@@ -6584,7 +6915,8 @@ function layoutTable(value, box, options = {}) {
6584
6915
  let overflow = false;
6585
6916
  const rows3 = heights.map((height, r) => ({ box: { x: box.x, y: ys[r], width: box.width, height }, cells: [] }));
6586
6917
  for (const cell of cells) {
6587
- const cellBox = { x: box.x + (options.direction === "rtl" ? columnCount - cell.column - cell.colSpan : cell.column) * cellWidth, y: ys[cell.row], width: cell.colSpan * cellWidth, height: ys[cell.row + cell.rowSpan] - ys[cell.row] };
6918
+ const before = options.direction === "rtl" ? columnWidths2.slice(cell.column + cell.colSpan) : columnWidths2.slice(0, cell.column);
6919
+ const cellBox = { x: box.x + before.reduce((a, b) => a + b, 0), y: ys[cell.row], width: columnWidths2.slice(cell.column, cell.column + cell.colSpan).reduce((a, b) => a + b, 0), height: ys[cell.row + cell.rowSpan] - ys[cell.row] };
6588
6920
  const available = cellBox.height - (cell.padding.top + cell.padding.bottom) * scale2;
6589
6921
  const fit = fitCell(cell, available), height = textHeight(fit);
6590
6922
  const offset = cell.style.verticalAlign === "bottom" ? Math.max(0, available - height) : cell.style.verticalAlign === "middle" ? Math.max(0, (available - height) / 2) : 0;
@@ -6592,13 +6924,14 @@ function layoutTable(value, box, options = {}) {
6592
6924
  overflow ||= cell.width <= 0 || available <= 0 || fit.overflow;
6593
6925
  rows3[cell.row].cells.push({ value: cell.value, input: cell.input, path: cell.valuePath, sourcePath: cell.path, style: cell.style, alignment: cell.alignment, row: cell.row, column: cell.column, rowSpan: cell.rowSpan, colSpan: cell.colSpan, header: cell.header, rich: Array.isArray(cell.value), box: cellBox, textBox, textStyle: resolveTextStyle(cell.textStyle, options.textMeasurement), fit, ...fit.directions ? { direction: fit.directions[0] } : {} });
6594
6926
  }
6595
- return { rows: rows3, columnCount, height: sum(heights), overflow };
6927
+ return { rows: rows3, columnCount, height: sum(heights), overflow, columnWidths: [...columnWidths2], splitsWords: false };
6596
6928
  };
6597
6929
  for (let step = 0; ; step++) {
6598
6930
  const raw = requested - step * scale2, last = step === 64 || raw <= minimum;
6599
6931
  trial = last ? snapFontSizeUp(minimum) : gridFontSize(raw, minimum);
6600
- const layout = place();
6601
- if (last || !layout.overflow) return layout;
6932
+ sizeColumns();
6933
+ const layout = place(), splits = splitsWords();
6934
+ if (last || !layout.overflow && !splits) return { ...layout, splitsWords: splits };
6602
6935
  }
6603
6936
  }
6604
6937
  function tableOverflows(value, box, scale2, settings, options, path) {
@@ -6706,14 +7039,15 @@ function composeSlide(input, options = {}) {
6706
7039
  const minSize = snapFontSizeUp((composition.minFontSize ?? 16) * scale2);
6707
7040
  const rasterPadding = (options.textRasterPadding ?? 1) * scale2;
6708
7041
  if (!Number.isFinite(rasterPadding) || rasterPadding < 0) throw new RangeError("Text raster padding must be finite and nonnegative.");
6709
- let coverGroup = false;
6710
- const alignmentFor = (field) => (field === "title" || (field === "tag" || coverGroup && field === "subtitle") && hints.sources.contentAlignment !== "slide" ? hints.titleAlignment : hints.contentAlignment) ?? (field === "metric" ? "center" : "left");
7042
+ let subtitleInGroup = true;
7043
+ const alignmentFor = (field) => (field === "title" || (field === "tag" || subtitleInGroup && field === "subtitle") && hints.sources.contentAlignment !== "slide" ? hints.titleAlignment : hints.contentAlignment) ?? (field === "metric" ? "center" : "left");
6711
7044
  const styleFor = (field, path2) => resolveTextStyle({
6712
7045
  fontFamily: (field === "title" ? options.fontFamilies?.heading : field === "code" ? options.fontFamilies?.code : field === "tag" ? options.fontFamilies?.accent ?? options.fontFamilies?.body : options.fontFamilies?.body) ?? (field === "code" ? "monospace" : "sans-serif"),
6713
7046
  fontWeight: field === "title" ? 700 : 400,
6714
7047
  path: path2
6715
7048
  }, options.textMeasurement);
6716
7049
  const listBullet = hints.listBullet !== void 0 ? { value: hints.listBullet, path: hints.paths.listBullet } : void 0;
7050
+ const noListBullet = listBullet?.value === "none";
6717
7051
  const bulletImage = listBullet?.value === "image" ? (() => {
6718
7052
  const resolved = resolveLogo(options.presentation, slide, { shape: "icon", onDark: options.darkBackground, slideIndex: options.slideIndex });
6719
7053
  return resolved ? { source: resolved.source, path: resolved.path } : void 0;
@@ -6724,13 +7058,14 @@ function composeSlide(input, options = {}) {
6724
7058
  const richOptions = (style) => ({ style, textMeasurement: options.textMeasurement, ...options.fontFamilies?.code ? { codeFontFamily: options.fontFamilies.code } : {}, ...citationMarker ? { citationMarker } : {}, ...rtl ? { direction: "rtl" } : {} });
6725
7059
  const fitPlacedText = (field, value, text2, box, size, minimum, path2, explicitAlignment) => {
6726
7060
  const style = styleFor(field, path2), rich = (field === "text" || headings.has(field)) && Array.isArray(value);
6727
- if (!options.textMeasurement?.outlineBounds) return rich ? fitRichText(value, box, size, minimum, richOptions(style)) : fitText(text2, box, size, minimum, textWidthMeasurer(style, options.textMeasurement), textDirection);
7061
+ const balance = field === "title" || field === "subtitle", textOptions = balance ? { ...richOptions(style), balance } : richOptions(style);
7062
+ if (!options.textMeasurement?.outlineBounds) return rich ? fitRichText(value, box, size, minimum, textOptions) : fitText(text2, box, size, minimum, textWidthMeasurer(style, options.textMeasurement), textDirection, balance);
6728
7063
  const alignment = explicitAlignment ?? alignmentFor(field);
6729
- const richLayout = rich ? richTextLayouter(value, box, size, richOptions(style), minimum) : void 0;
7064
+ const richLayout = rich ? richTextLayouter(value, box, size, textOptions, minimum) : void 0;
6730
7065
  const measure = textWidthMeasurer(style, options.textMeasurement), floor = snapFontSizeUp(rich ? richMinimum(value, size, minimum) : minimum), start = Math.max(size, floor);
6731
7066
  for (let trial = 0; trial <= 64; trial++) {
6732
7067
  const raw = start - trial * scale2, last = trial === 64 || raw <= floor, fontSize = last ? floor : gridFontSize(raw, floor);
6733
- const fit = richLayout ? richLayout(fontSize) : fitText(text2, box, fontSize, fontSize, measure, textDirection);
7068
+ const fit = richLayout ? richLayout(fontSize) : fitText(text2, box, fontSize, fontSize, measure, textDirection, balance);
6734
7069
  const richLines = "richLines" in fit ? fit.richLines : void 0;
6735
7070
  const lines = richLines ? richLines.map((line2) => {
6736
7071
  let outline = null;
@@ -6772,7 +7107,7 @@ function composeSlide(input, options = {}) {
6772
7107
  }
6773
7108
  throw new Error("Text placement did not evaluate its bounded floor trial.");
6774
7109
  };
6775
- const fitContent = (field, value, text2, box, size, minimum, path2, numbering) => field === "text" ? fitPlacedText(field, value, text2, box, size, minimum, path2) : field === "items" || field === "bullets" ? fitList(value, box, size, minimum, { ...richOptions(styleFor(field, path2)), ...numbering !== void 0 ? { numbering } : bulletImage ? { bulletImage } : {} }) : fitText(text2, box, size, minimum, widthFor(field, path2), field === "code" ? void 0 : textDirection);
7110
+ const fitContent = (field, value, text2, box, size, minimum, path2, numbering) => field === "text" ? fitPlacedText(field, value, text2, box, size, minimum, path2) : field === "items" || field === "bullets" ? fitList(value, box, size, minimum, { ...richOptions(styleFor(field, path2)), ...numbering !== void 0 ? { numbering } : bulletImage ? { bulletImage } : noListBullet ? { bullet: "none" } : {} }) : fitText(text2, box, size, minimum, widthFor(field, path2), field === "code" ? void 0 : textDirection);
6776
7111
  const path = `slides.${options.slideIndex ?? 0}`;
6777
7112
  const annotationFit = (value, box, requestedSize, minFontSize, fitPath, alignment) => fitPlacedText("text", value, annotationText(value), box, requestedSize, minFontSize, fitPath, alignment);
6778
7113
  const annotationOptions = { scale: scale2, minFontSize: minSize, fit: annotationFit, textStyle: (fitPath) => styleFor("text", fitPath) };
@@ -6781,7 +7116,7 @@ function composeSlide(input, options = {}) {
6781
7116
  const backgroundImage = resolveBackgroundImage(slide, options.presentation, options.themeBackground, width, height, path, mirrorSide, imageDiagnostics);
6782
7117
  const regions = Object.keys(slide).filter((key) => regionParts(key)).sort();
6783
7118
  const assets = record7(options.presentation).assets;
6784
- const imageContext = (placement) => ({ fit: hints.imageFit, scale: scale2, mirrorSide, diagnostics: imageDiagnostics, assets, ...placement ? { placement } : {} });
7119
+ const imageContext = (placement, anchor) => ({ fit: hints.imageFit ?? (!placement && autoComposition() ? "contain" : void 0), scale: scale2, mirrorSide, diagnostics: imageDiagnostics, assets, ...placement ? { placement } : {}, ...anchor ? { anchor } : {} });
6785
7120
  const area2 = { left: 0, top: 0, right: width, bottom: height };
6786
7121
  const topImages = regions.length ? [] : Array.isArray(slide.blocks) ? slide.blocks.flatMap((block, index) => !Array.isArray(record7(block).blocks) && record7(block).image !== void 0 ? [{ host: record7(block), hostPath: `${path}.blocks.${index}`, block: index }] : []) : slide.image !== void 0 ? [{ host: { type: "image", image: slide.image }, hostPath: path }] : [];
6787
7122
  const placed = [], usedEdges = /* @__PURE__ */ new Set();
@@ -6823,8 +7158,9 @@ function composeSlide(input, options = {}) {
6823
7158
  const flowBlocks = Array.isArray(slide.blocks) ? slide.blocks.filter((_, index) => !placedBlocks.has(index)) : slide.blocks;
6824
7159
  const hasBodyPayload = regions.some((key) => !emptyHost(record7(slide[key]))) || !emptyPayload(flowBlocks) || fields.some((field) => !(placedRoot && field === "image") && !emptyPayload(slide[field]));
6825
7160
  const useTemplate = template !== void 0 && !regions.length;
7161
+ const autoComposition = () => !hasLayoutRecord && !regions.length;
6826
7162
  const isCover = !hasBodyPayload && (useTemplate || !hasLayoutRecord);
6827
- coverGroup = isCover;
7163
+ subtitleInGroup = !(useTemplate && template?.subtitle === true);
6828
7164
  let logo;
6829
7165
  if (isCover && options[LAYOUT_MASTER] !== true) {
6830
7166
  const resolved = resolveLogo(options.presentation, slide, { shape: "full", onDark: options.darkBackground, slideIndex: options.slideIndex });
@@ -6839,7 +7175,7 @@ function composeSlide(input, options = {}) {
6839
7175
  const headingItems = [];
6840
7176
  for (const field of useTemplate ? [] : ["tag", "title", "subtitle"]) {
6841
7177
  if (!slide[field] || Array.isArray(slide[field]) && !annotationText(slide[field])) continue;
6842
- const requested = (field === "title" ? 54 : field === "tag" ? 16 : 25) * scale2;
7178
+ const requested = (field === "title" ? 54 : field === "tag" ? 16 : PROSE_NOMINAL_SIZE) * scale2;
6843
7179
  const maxHeight = Math.max(height * (field === "title" ? 0.26 : field === "subtitle" ? 0.12 : 0.045), minSize * 1.22 + 2 * rasterPadding);
6844
7180
  const box = { x: area2.left + padding, y, width: area2.right - area2.left - padding * 2, height: maxHeight };
6845
7181
  const text2 = fitPlacedText(field, slide[field], annotationText(slide[field]), box, requested, minSize, `${path}.${field}`);
@@ -6916,6 +7252,8 @@ function composeSlide(input, options = {}) {
6916
7252
  const regionsInReadingOrder = visualReadingOrder(regions.map((key) => ({ key, box: regionBox(regionParts(key), contentBox, gap) }))).map((entry) => entry.key);
6917
7253
  const pending = regions.length ? regionsInReadingOrder.flatMap((key) => collect(record7(slide[key]), `${path}.${key}`).map((item) => ({ ...item, region: regionParts(key) }))) : Array.isArray(slide.blocks) ? slide.blocks.flatMap((block, index) => placedBlocks.has(index) ? [] : collect(record7(block), `${path}.blocks.${index}`)) : collect(placedRoot ? { ...slide, image: void 0 } : slide, path);
6918
7254
  const groups = [], flows = [];
7255
+ const peerSets = [];
7256
+ const range = (from, to) => Array.from({ length: to - from }, (_, index) => from + index);
6919
7257
  const decisions = options.explain ? [] : void 0;
6920
7258
  const inheritedSettings = (parent, own3 = {}) => ({
6921
7259
  minFontSize: parent.minFontSize,
@@ -6932,6 +7270,11 @@ function composeSlide(input, options = {}) {
6932
7270
  const frame = acceptedBox(box), padding2 = Math.min(12 * scale2, frame.width / 4, frame.height / 4);
6933
7271
  return acceptedBox({ x: frame.x + padding2, y: frame.y + padding2, width: frame.width - 2 * padding2, height: frame.height - 2 * padding2 });
6934
7272
  };
7273
+ let languageTag = null;
7274
+ const deckLanguage = () => {
7275
+ if (languageTag === null) languageTag = quoteLanguage(options.presentation, options.slideIndex);
7276
+ return languageTag;
7277
+ };
6935
7278
  const measureQuote = (node, box, settings) => layoutQuote(node.value, acceptedBox(box), {
6936
7279
  fontFamilies: options.fontFamilies,
6937
7280
  textMeasurement: options.textMeasurement,
@@ -6939,7 +7282,8 @@ function composeSlide(input, options = {}) {
6939
7282
  minFontSize: settings.minFontSize,
6940
7283
  path: node.path,
6941
7284
  direction: textDirection,
6942
- ...citationMarker ? { citationMarker } : {}
7285
+ ...citationMarker ? { citationMarker } : {},
7286
+ ...deckLanguage() !== void 0 ? { language: deckLanguage() } : {}
6943
7287
  });
6944
7288
  const measureCode = (node, box, settings) => layoutCode(node.value, acceptedBox(box), {
6945
7289
  fontFamilies: options.fontFamilies,
@@ -6968,14 +7312,21 @@ function composeSlide(input, options = {}) {
6968
7312
  direction: textDirection
6969
7313
  });
6970
7314
  const captionOf = (node, box) => node.caption !== void 0 && node.captionPath ? layoutCaption(node.caption, box, node.captionPath, annotationOptions) : void 0;
7315
+ const wholePictureAspect = (node) => {
7316
+ const host = node.host;
7317
+ if (node.field !== "image" || !host || !autoComposition() || host.aspectRatio !== void 0 || host.shape === "circle") return void 0;
7318
+ const fit = IMAGE_FITS.includes(host.fit) ? host.fit : hints.imageFit ?? "contain";
7319
+ return fit === "contain" ? intrinsicImageAspect(host.image, assets) : void 0;
7320
+ };
6971
7321
  const leafScore = (node, box, settings, penalties) => {
6972
7322
  box = payloadBox(box);
6973
7323
  const captioned = captionOf(node, box);
6974
7324
  if (captioned) box = captioned.mediaBox;
6975
7325
  const text2 = contentText(node.field, node.value);
6976
- let score = Math.abs(Math.log(box.width / box.height / 1.6)) + (captioned?.overflow ? 1e3 : 0);
7326
+ const proportions = Math.abs(Math.log(box.width / box.height / (wholePictureAspect(node) ?? 1.6)));
7327
+ let score = proportions + (captioned?.overflow ? 1e3 : 0);
6977
7328
  if (penalties) {
6978
- penalties.cellProportions += Math.abs(Math.log(box.width / box.height / 1.6));
7329
+ penalties.cellProportions += proportions;
6979
7330
  if (captioned?.overflow) penalties.textOverflow += 1e3;
6980
7331
  }
6981
7332
  if (node.field === "quote" || node.field === "code" || node.field === "metric" || node.field === "timeline") {
@@ -6987,8 +7338,9 @@ function composeSlide(input, options = {}) {
6987
7338
  penalties.textOverflow += internal.overflow ? 1e3 : 0;
6988
7339
  }
6989
7340
  } else if (text2) {
6990
- const fit = fitContent(node.field, node.value, text2, box, contentNominalSize(node.field) * scale2, snapFontSizeUp((settings.minFontSize ?? 16) * scale2), node.path, node.payload.numbering);
6991
- const reduction = Math.max(0, snapFontSizeDown(contentNominalSize(node.field) * scale2) - fit.fontSize) / scale2;
7341
+ const prose = proseMeasure(node, box.width);
7342
+ const fit = fitContent(node.field, node.value, text2, { ...box, width: prose.width }, prose.size, snapFontSizeUp((settings.minFontSize ?? 16) * scale2), node.path, node.payload.numbering);
7343
+ const reduction = Math.max(0, snapFontSizeDown(prose.size) - fit.fontSize) / scale2;
6992
7344
  score += reduction + (fit.overflow ? 1e3 : 0);
6993
7345
  if (penalties) {
6994
7346
  penalties.fontReduction += reduction;
@@ -7017,6 +7369,74 @@ function composeSlide(input, options = {}) {
7017
7369
  const boxes = gridBoxes(slotCount, area3, cols, (own3.gap ?? 1 / 30) * Math.min(box.width, box.height), own3.weights ?? [], modeFor(own3) === "column");
7018
7370
  return node.children.reduce((score, child, index) => score + scoreNode(child, boxes[index], own3, penalties), 0);
7019
7371
  };
7372
+ const contentSized = /* @__PURE__ */ new WeakSet();
7373
+ const stackedBoxes = (nodes, area3, requestedGap, settings, cards = hasCards, anchor = "top") => {
7374
+ const between = Math.max(0, Math.min(requestedGap, area3.width / 2, area3.height / (nodes.length * 2)));
7375
+ const space = Math.max(0, area3.height - between * (nodes.length - 1)), share = space / nodes.length;
7376
+ const elastic = nodes.map((node) => !measuredNode(node)), stretch = elastic.filter(Boolean).length;
7377
+ const needs = nodes.map((node, index) => elastic[index] ? share : naturalHeight(node, area3.width, cards, void 0, settings));
7378
+ const floors = nodes.map((node, index) => elastic[index] ? share : naturalHeight(node, area3.width, cards, void 0, settings, true));
7379
+ let heights = shareHeights(needs, floors, space);
7380
+ const textual = nodes.every((node) => !node.children && node.caption === void 0 && (node.field === "text" || node.field === "items" || node.field === "bullets"));
7381
+ if (textual && nodes.length > 1 && needs.reduce((sum, size) => sum + size, 0) > space + 1e-6) {
7382
+ const floor = snapFontSizeUp((settings.minFontSize ?? 16) * scale2), start = Math.max(...nodes.map((node) => proseMeasure(node, area3.width).size));
7383
+ for (let size = start; ; size -= scale2) {
7384
+ const trial = Math.max(floor, snapFontSizeDown(size));
7385
+ const natural = nodes.map((node) => naturalHeight(node, area3.width, cards, void 0, settings, false, Math.min(trial, proseMeasure(node, area3.width).size)));
7386
+ if (natural.reduce((sum, height2) => sum + height2, 0) <= space + 1e-6) {
7387
+ heights = natural;
7388
+ break;
7389
+ }
7390
+ if (trial <= floor) break;
7391
+ }
7392
+ }
7393
+ if (stretch && heights.some((size, index) => elastic[index] && size < share - 1e-9)) {
7394
+ const measured = nodes.flatMap((_, index) => elastic[index] ? [] : [index]);
7395
+ const rest = shareHeights(measured.map((index) => needs[index]), measured.map((index) => floors[index]), space - stretch * share);
7396
+ heights = nodes.map((_, index) => elastic[index] ? share : rest[measured.indexOf(index)]);
7397
+ }
7398
+ const spare = space - heights.reduce((sum, size) => sum + size, 0);
7399
+ if (stretch && spare > 0) heights = heights.map((size, index) => elastic[index] ? size + spare / stretch : size);
7400
+ const used = heights.reduce((sum, size) => sum + size, 0) + between * (nodes.length - 1);
7401
+ nodes.forEach((node, index) => {
7402
+ if (!node.children && !elastic[index] && heights[index] >= needs[index] - 1e-6) contentSized.add(node);
7403
+ });
7404
+ let y2 = area3.y + Math.max(0, anchor === "middle" ? (area3.height - used) / 2 : anchor === "bottom" ? area3.height - used : 0);
7405
+ return heights.map((size) => {
7406
+ const box = { x: area3.x, y: y2, width: area3.width, height: size };
7407
+ y2 += size + between;
7408
+ return box;
7409
+ });
7410
+ };
7411
+ const WEIGHTED_DEMAND_RATIO = 2.5, WEIGHTED_SHARES = [0.55, 0.6, 0.65, 0.7];
7412
+ const autoRowWeights = (nodes, count, cols, area3, rowGap, settings) => {
7413
+ if (!autoComposition() || settings.weights?.length || count < 2 || count > 3 || cols !== count || nodes.length !== count) return void 0;
7414
+ const equal = gridGeometry(count, area3, cols, rowGap, [], false).boxes;
7415
+ const wholeWidths = nodes.map((node, index) => {
7416
+ const aspect = node.caption === void 0 ? wholePictureAspect(node) : void 0;
7417
+ return aspect === void 0 ? void 0 : Math.min(equal[index].width, equal[index].height * aspect);
7418
+ });
7419
+ const media = wholeWidths.flatMap((width2, index) => width2 !== void 0 && width2 < equal[index].width - scale2 ? [index] : []);
7420
+ const others = nodes.flatMap((_, index) => wholeWidths[index] === void 0 ? [index] : []);
7421
+ if (media.length && others.length) {
7422
+ const freed = media.reduce((sum, index) => sum + equal[index].width - wholeWidths[index], 0);
7423
+ return nodes.map((_, index) => media.includes(index) ? wholeWidths[index] : equal[index].width + freed / others.length);
7424
+ }
7425
+ const prose = (node) => !node.children && node.caption === void 0 && (node.field === "text" || node.field === "items" || node.field === "bullets");
7426
+ if (count !== 2 || !nodes.every(prose)) return void 0;
7427
+ const need = (node, width2) => naturalHeight(node, width2, hasCards, void 0, settings);
7428
+ const heights = nodes.map((node, index) => need(node, equal[index].width));
7429
+ const long = heights[0] >= heights[1] ? 0 : 1, short = 1 - long;
7430
+ if (heights[long] < WEIGHTED_DEMAND_RATIO * heights[short]) return void 0;
7431
+ const total = equal[0].width + equal[1].width, line2 = contentNominalSize(nodes[short].field) * scale2 * 1.3;
7432
+ let best;
7433
+ for (const share of WEIGHTED_SHARES) {
7434
+ const longHeight = need(nodes[long], total * share), shortHeight = need(nodes[short], total * (1 - share));
7435
+ if (shortHeight > heights[short] + line2 || longHeight > heights[long] - line2 * 0.5) continue;
7436
+ if (!best || longHeight < best.height - 1e-6) best = { share, height: longHeight };
7437
+ }
7438
+ return best ? long === 0 ? [best.share, 1 - best.share] : [1 - best.share, best.share] : void 0;
7439
+ };
7020
7440
  const arrange = (nodes, area3, settings, reserved2 = 0, gapOverride, containerPath = path, recorded = true) => {
7021
7441
  const count = Math.max(nodes.length, reserved2);
7022
7442
  if (!count) return;
@@ -7024,7 +7444,12 @@ function composeSlide(input, options = {}) {
7024
7444
  const actualGap = gapOverride ?? (settings === rootSettings ? gap : localGap);
7025
7445
  let cols = defaultColumns(count, area3, settings);
7026
7446
  const hasRegions = nodes.some((node) => node.region);
7447
+ if (!hasRegions && (mode === "auto" || nodes.length === 1)) for (const node of nodes) {
7448
+ node.auto = true;
7449
+ node.lone = nodes.length === 1 && reserved2 === 0;
7450
+ }
7027
7451
  const candidates = decisions && recorded ? [] : void 0;
7452
+ const stacks = !hasRegions && count === nodes.length && count > 1 && !(mode === "column" && settings.weights?.length);
7028
7453
  if (mode === "auto" && !hasRegions) {
7029
7454
  let best = Infinity;
7030
7455
  for (let candidate = 1; candidate <= Math.min(count, settings.columns ?? 6); candidate++) {
@@ -7046,14 +7471,24 @@ function composeSlide(input, options = {}) {
7046
7471
  ...hasRegions ? {} : { selectedColumns: cols },
7047
7472
  candidates: candidates ?? []
7048
7473
  });
7049
- const grid = gridGeometry(count, area3, cols, actualGap, settings.weights ?? [], mode === "column");
7474
+ const grid = gridGeometry(count, area3, cols, actualGap, (mode === "auto" && !hasRegions ? autoRowWeights(nodes, count, cols, area3, actualGap, settings) : void 0) ?? settings.weights ?? [], mode === "column");
7475
+ if (cols === 1 && stacks) {
7476
+ grid.boxes = stackedBoxes(nodes, area3, actualGap, settings);
7477
+ grid.rows = grid.boxes.map((box) => ({ offset: box.y - area3.y, size: box.height }));
7478
+ }
7050
7479
  const mirrored = (box) => rtl ? { ...box, x: area3.x + area3.width - (box.x - area3.x) - box.width } : box;
7051
7480
  const boxes = grid.boxes.map(mirrored);
7052
7481
  if (recorded && !hasRegions) flows.push({ path: containerPath, box: { ...area3 }, composition: { ...settings }, columns: rtl ? grid.columns.map((track) => ({ offset: area3.width - track.offset - track.size, size: track.size })) : grid.columns, rows: grid.rows, gap: grid.gap, itemCount: nodes.length, slotCount: count });
7482
+ const rowItems = /* @__PURE__ */ new Map(), stack = [];
7053
7483
  nodes.forEach((node, index) => {
7054
7484
  const box = node.region ? mirrored(regionBox(node.region, area3, actualGap)) : boxes[index];
7485
+ const first = items.length;
7055
7486
  placeNode(node, box, settings);
7487
+ if (!hasRegions && cols > 1 && !node.children) rowItems.set(Math.floor(index / cols), [...rowItems.get(Math.floor(index / cols)) ?? [], ...range(first, items.length)]);
7488
+ else if (!hasRegions && cols === 1 && stacks && !node.children) stack.push(...range(first, items.length));
7056
7489
  });
7490
+ for (const set2 of rowItems.values()) peerSets.push(set2);
7491
+ if (stack.length > 1) peerSets.push(stack);
7057
7492
  };
7058
7493
  let regionTag, templateCell = false;
7059
7494
  const composeChart = (node, box, settings) => {
@@ -7061,7 +7496,15 @@ function composeSlide(input, options = {}) {
7061
7496
  const valueAxis = chartValueAxis(node.value, resolved, { width: box.width, height: box.height, scale: scale2, minFontSize: settings.minFontSize });
7062
7497
  return valueAxis ? { valueAxis } : void 0;
7063
7498
  };
7064
- const placeNode = (node, box, settings, cards = hasCards, listColumns, metricReferenceHeight) => {
7499
+ const VIDEO_ASPECT = 16 / 9, CHART_MAX_ASPECT_RATIO = 3 / 4;
7500
+ const settleCaption = (caption, frame, anchor) => {
7501
+ if (caption.position === "above" || anchor !== "top") return;
7502
+ const target = round2(frame.y + frame.height + caption.fontSize * 0.4), dy = target - caption.box.y;
7503
+ if (dy >= -0.01) return;
7504
+ caption.box = { ...caption.box, y: target };
7505
+ caption.fit = translateTextFit(caption.fit, dy);
7506
+ };
7507
+ const placeNode = (node, box, settings, cards = hasCards, listColumns, metricReferenceHeight, anchor = "top") => {
7065
7508
  if (node.children) {
7066
7509
  const own3 = inheritedSettings(settings, node.composition), inner = inset(box, own3);
7067
7510
  if (!node.synthetic) groups.push({ path: node.path, box, contentBox: inner, composition: own3 });
@@ -7083,8 +7526,24 @@ function composeSlide(input, options = {}) {
7083
7526
  const timelineLayout = node.field === "timeline" ? measureTimeline(node, box, settings) : void 0;
7084
7527
  const internal = quoteLayout ?? codeLayout ?? metricLayout ?? timelineLayout, body = internal?.parts.find((part) => part.role === "body" || part.role === "value");
7085
7528
  const columns2 = textValue !== void 0 && !internal ? listColumnsFor(node, box, settings, listColumns) : void 0;
7086
- const text2 = internal ? body?.fit : columns2 ? columns2.length > 1 ? mergeListColumns(columns2) : columns2[0].text : textValue !== void 0 ? fitContent(node.field, node.value, textValue, box, contentNominalSize(node.field) * scale2, snapFontSizeUp((settings.minFontSize ?? 16) * scale2), node.path, node.payload.numbering) : void 0;
7087
- const image = node.field === "image" ? composeImage(node.host ?? {}, node.hostPath ?? node.path, box, box, imageContext()) : void 0;
7529
+ const floorSize = snapFontSizeUp((settings.minFontSize ?? 16) * scale2);
7530
+ const prose = textValue !== void 0 && !internal ? proseMeasure(node, box.width) : void 0, wholeBox = box;
7531
+ if (prose && prose.width < box.width - 0.5) {
7532
+ const side = alignmentFor(node.field), atEnd = rtl ? side === "left" : side === "right";
7533
+ box = { ...box, x: side === "center" ? box.x + (box.width - prose.width) / 2 : atEnd ? box.x + box.width - prose.width : box.x, width: prose.width };
7534
+ }
7535
+ let text2 = internal ? body?.fit : columns2 ? columns2.length > 1 ? mergeListColumns(columns2) : columns2[0].text : textValue !== void 0 ? fitContent(node.field, node.value, textValue, box, prose?.size ?? contentNominalSize(node.field) * scale2, floorSize, node.path, node.payload.numbering) : void 0;
7536
+ if (prose && box !== wholeBox && text2 && textValue !== void 0) {
7537
+ const wide = fitContent(node.field, node.value, textValue, wholeBox, prose.size, floorSize, node.path, node.payload.numbering);
7538
+ if (wide.fontSize > text2.fontSize + 1e-9 || text2.overflow && !wide.overflow) {
7539
+ text2 = wide;
7540
+ box = wholeBox;
7541
+ }
7542
+ }
7543
+ if (node.field === "video") box = roundBox(containedBox(box, VIDEO_ASPECT, anchor));
7544
+ else if (node.field === "chart" && !templateCell && modeFor(settings) === "auto" && box.height > box.width * CHART_MAX_ASPECT_RATIO) box = roundBox({ ...box, height: box.width * CHART_MAX_ASPECT_RATIO });
7545
+ const image = node.field === "image" ? composeImage(node.host ?? {}, node.hostPath ?? node.path, box, box, imageContext(void 0, anchor)) : void 0;
7546
+ if (caption) settleCaption(caption, image?.box ?? box, anchor);
7088
7547
  const chart = node.field === "chart" ? composeChart(node, box, settings) : void 0;
7089
7548
  items.push({
7090
7549
  path: node.path,
@@ -7092,7 +7551,7 @@ function composeSlide(input, options = {}) {
7092
7551
  type: node.type,
7093
7552
  value: node.value,
7094
7553
  payload: node.payload,
7095
- box: internal ? acceptedBox(box) : box,
7554
+ box: codeLayout && !codeLayout.overflow ? { ...acceptedBox(box), height: Math.min(acceptedBox(box).height, round2(codeLayout.bounds.height)) } : internal ? acceptedBox(box) : box,
7096
7555
  ...image ? { image } : {},
7097
7556
  ...chart ? { chart } : {},
7098
7557
  ...frameBox ? { frameBox } : {},
@@ -7109,7 +7568,7 @@ function composeSlide(input, options = {}) {
7109
7568
  ...regionTag !== void 0 ? { region: regionTag } : {},
7110
7569
  ...columns2 && columns2.length > 1 ? { listColumns: columns2 } : {}
7111
7570
  });
7112
- if (box.width < 100 * scale2 || !templateCell && box.height < 60 * scale2) diagnostics.push({ code: "small-cell", path: node.path, message: "Content cell is too small for comfortable reading; use fewer blocks or a different composition." });
7571
+ if (box.width < 100 * scale2 || !templateCell && !contentSized.has(node) && box.height < 60 * scale2) diagnostics.push({ code: "small-cell", path: node.path, message: "Content cell is too small for comfortable reading; use fewer blocks or a different composition." });
7113
7572
  };
7114
7573
  const listColumnsFor = (node, box, settings, regionMode) => {
7115
7574
  if (node.field !== "items" && node.field !== "bullets") return void 0;
@@ -7120,7 +7579,7 @@ function composeSlide(input, options = {}) {
7120
7579
  if (mode === void 0 || mode === 1) return void 0;
7121
7580
  const start = contentNominalSize(node.field) * scale2, floor = snapFontSizeUp((settings.minFontSize ?? 16) * scale2);
7122
7581
  const numbering = node.payload.numbering;
7123
- const listOptions = { ...richOptions(styleFor(node.field, node.path)), ...numbering !== void 0 ? { numbering } : bulletImage ? { bulletImage } : {} };
7582
+ const listOptions = { ...richOptions(styleFor(node.field, node.path)), ...numbering !== void 0 ? { numbering } : bulletImage ? { bulletImage } : noListBullet ? { bullet: "none" } : {} };
7124
7583
  const columnGap = (count2) => Math.min(gap, box.width / (count2 * 2));
7125
7584
  const columnBoxes = (count2) => {
7126
7585
  const between = columnGap(count2), columnWidth = (box.width - between * (count2 - 1)) / count2;
@@ -7156,7 +7615,7 @@ function composeSlide(input, options = {}) {
7156
7615
  const size = Math.min(...shrunk.map((column) => column.text.fontSize));
7157
7616
  return shrunk.map((column) => ({ ...column, text: renumber(fitList(chunk(column.start, column.end), column.box, size, size, listOptions), column.start) }));
7158
7617
  };
7159
- const LONG_LINES = 6, LONG_CODE_LINES = 12, startSize = 25 * scale2;
7618
+ const LONG_LINES = 6, LONG_CODE_LINES = 12, startSize = PROSE_NOMINAL_SIZE * scale2;
7160
7619
  const estimatedLines = (value, cellWidth, size) => flatten(value).split("\n").reduce((sum, paragraph) => sum + Math.max(1, wrapText(paragraph, Math.max(1, cellWidth), size).length), 0);
7161
7620
  const isLong = (node, cellWidth) => {
7162
7621
  if (node.children) return node.children.some((child) => isLong(child, cellWidth));
@@ -7180,22 +7639,37 @@ function composeSlide(input, options = {}) {
7180
7639
  if (node.field === "code") return String(typeof node.value === "string" ? node.value : record7(node.value).source ?? "").split("\n").length > LONG_CODE_LINES;
7181
7640
  return false;
7182
7641
  };
7183
- const naturalHeight = (node, cellWidth, cards, metricReferenceHeight, naturalSettings = composition) => {
7642
+ const proseMeasure = (node, cellWidth) => {
7643
+ const role = contentNominalSize(node.field) * scale2;
7644
+ if (node.field !== "text" || node.children || !node.auto) return { size: role, width: cellWidth };
7645
+ const linesAt = (width2, size) => estimatedLines(node.value, width2, size);
7646
+ const measureAt = (size) => Math.min(cellWidth, PROSE_MEASURE_CHARS * AVERAGE_CHARACTER_EM * size);
7647
+ const lead = LEAD_NOMINAL_SIZE * scale2;
7648
+ if (node.lone && lead > role && linesAt(measureAt(lead), lead) <= 2) return { size: lead, width: linesAt(cellWidth, lead) <= 1 ? cellWidth : measureAt(lead) };
7649
+ return { size: role, width: linesAt(cellWidth, role) <= 1 ? cellWidth : measureAt(role) };
7650
+ };
7651
+ const MEASURED_FIELDS = /* @__PURE__ */ new Set(["text", "items", "bullets", "quote", "metric", "code"]);
7652
+ const measuredNode = (node) => node.children ? node.children.length > 0 && node.children.every(measuredNode) : MEASURED_FIELDS.has(node.field);
7653
+ const naturalHeight = (node, cellWidth, cards, metricReferenceHeight, naturalSettings = composition, atFloor = false, sizeOverride) => {
7184
7654
  const pad = cards ? 2 * Math.min(12 * scale2, cellWidth / 4) : 0, inner = Math.max(scale2, cellWidth - pad);
7185
- if (node.children) return node.children.reduce((sum, child) => sum + naturalHeight(child, cellWidth, false), 0) + gap * Math.max(0, node.children.length - 1) + pad;
7655
+ if (node.children) return node.children.reduce((sum, child) => sum + naturalHeight(child, cellWidth, false, void 0, naturalSettings, atFloor), 0) + gap * Math.max(0, node.children.length - 1) + pad;
7656
+ const floorSize = snapFontSizeUp((naturalSettings.minFontSize ?? 16) * scale2);
7186
7657
  let content;
7187
7658
  if (node.field === "text" || node.field === "items" || node.field === "bullets") {
7188
- const size = contentNominalSize(node.field) * scale2;
7189
- const fit = fitContent(node.field, node.value, contentText(node.field, node.value) ?? "", { x: 0, y: 0, width: inner, height: 1e6 }, size, size, node.path, node.payload.numbering);
7190
- content = "height" in fit && typeof fit.height === "number" ? fit.height : fit.lines.length * fit.lineHeight;
7659
+ const prose = proseMeasure(node, inner), size = sizeOverride ?? (atFloor ? floorSize : prose.size);
7660
+ const fit = fitContent(node.field, node.value, contentText(node.field, node.value) ?? "", { x: 0, y: 0, width: atFloor || sizeOverride !== void 0 ? inner : prose.width, height: 1e6 }, size, size, node.path, node.payload.numbering);
7661
+ content = Math.max("height" in fit && typeof fit.height === "number" ? fit.height : fit.lines.length * fit.lineHeight, fit.placement?.height ?? 0);
7191
7662
  } else if (node.field === "quote") content = measureQuote(node, { x: 0, y: 0, width: inner, height: 1e6 }, naturalSettings).bounds.height + 18;
7192
- else if (node.field === "code") content = String(typeof node.value === "string" ? node.value : record7(node.value).source ?? "").split("\n").length * 18 * 1.4 * scale2 + 24 * scale2;
7193
- else if (node.field === "metric") content = measureMetric(node, { x: 0, y: 0, width: inner, height: metricReferenceHeight === void 0 ? 1e6 : Math.max(scale2, metricReferenceHeight - pad) }, naturalSettings).bounds.height;
7663
+ else if (node.field === "code") {
7664
+ const code = measureCode(node, { x: 0, y: 0, width: inner, height: 1e6 }, naturalSettings), source = code.parts.find((part) => part.role === "body");
7665
+ content = source?.fit ? source.box.y + source.fit.lines.length * source.fit.lineHeight + 18 : 1e6;
7666
+ } else if (node.field === "metric") content = measureMetric(node, { x: 0, y: 0, width: inner, height: metricReferenceHeight === void 0 ? 1e6 : Math.max(scale2, metricReferenceHeight - pad) }, naturalSettings).bounds.height;
7194
7667
  else if (node.field === "timeline") content = 170 * scale2;
7195
7668
  else if (node.field === "table") {
7196
7669
  const value = record7(node.value);
7197
7670
  content = ((Array.isArray(value.rows) ? value.rows.length : 4) + 1) * 36 * scale2;
7198
7671
  } else content = inner / 1.6;
7672
+ if (atFloor && !(node.field === "text" || node.field === "items" || node.field === "bullets")) content *= Math.min(1, floorSize / startSize);
7199
7673
  return content + (node.caption !== void 0 ? 32 * scale2 : 0) + pad;
7200
7674
  };
7201
7675
  const templateRegions = [], headingAreas = [];
@@ -7210,6 +7684,13 @@ function composeSlide(input, options = {}) {
7210
7684
  return parts.length === 1 ? parts[0] : { field: "blocks", type: "group", value: parts.map((part) => part.payload), path: bound.path, payload: {}, children: parts, composition: { mode: "column" }, synthetic: true };
7211
7685
  };
7212
7686
  const contents = new Map(binding.regions.map((entry) => [entry.name, [...entry.blocks, ...entry.overflow].map(unitOf)]));
7687
+ for (const region of plan2.regions) {
7688
+ const units = contents.get(region.name) ?? [];
7689
+ if (region.flow === "auto") for (const unit of units) {
7690
+ unit.auto = true;
7691
+ unit.lone = units.length === 1;
7692
+ }
7693
+ }
7213
7694
  const BELOW = "(below)";
7214
7695
  const belowUnits = binding.below.map(unitOf);
7215
7696
  const belowRegion = { name: BELOW, accepts: [], role: "supporting", flow: "auto", max: belowUnits.length, bleed: false, listColumns: 1, anchor: "top", empty: "collapse" };
@@ -7248,7 +7729,7 @@ function composeSlide(input, options = {}) {
7248
7729
  const list = [];
7249
7730
  let top = y2;
7250
7731
  for (const field of fields2) {
7251
- const requested = (field === "title" ? 54 : field === "tag" ? 16 : 25) * scale2;
7732
+ const requested = (field === "title" ? 54 : field === "tag" ? 16 : PROSE_NOMINAL_SIZE) * scale2;
7252
7733
  const maxHeight = Math.max(height * (field === "title" ? 0.26 : field === "subtitle" ? 0.12 : 0.045), minSize * 1.22 + 2 * rasterPadding);
7253
7734
  const box = { x, y: top, width: areaWidth, height: maxHeight };
7254
7735
  const text2 = fitPlacedText(field, slide[field], annotationText(slide[field]), box, requested, minSize, `${path}.${field}`);
@@ -7269,33 +7750,35 @@ function composeSlide(input, options = {}) {
7269
7750
  const cellWidth = (cell.width - gap2 * (shape.columns - 1)) / shape.columns - (cards ? 2 * Math.min(12 * scale2, cell.width / 4) : 0);
7270
7751
  return units.some((unit) => isLong(unit, cellWidth)) ? "column" : "grid";
7271
7752
  };
7272
- const regionNeed = (name, areaWidth) => {
7753
+ const regionNeed = (name, areaWidth, atFloor = false) => {
7273
7754
  const units = unitsOf(name), region = name === BELOW ? belowRegion : regionOf.get(name);
7274
- if (!units.length) return region.empty === "keep" ? 2 * startSize * 1.22 + (hasCards && !region.bleed ? 2 * Math.min(12 * scale2, areaWidth / 4) : 0) : 0;
7755
+ if (!units.length) return region.empty === "keep" && !atFloor ? 2 * (region.flow === "auto" ? LEAD_NOMINAL_SIZE * scale2 : startSize) * 1.22 + (hasCards && !region.bleed ? 2 * Math.min(12 * scale2, areaWidth / 4) : 0) : 0;
7275
7756
  const shape = flowShape(arrangementOf(region, units, { width: areaWidth, height: areaWidth / 4 }), units.length, true);
7276
7757
  const cellWidth = (areaWidth - gap2 * (shape.columns - 1)) / shape.columns, cards = hasCards && !region.bleed;
7277
7758
  let total = 0, index = 0;
7278
7759
  for (const count of shape.perRow) {
7279
7760
  let row = 0;
7280
- for (let k = 0; k < count; k++) row = Math.max(row, naturalHeight(units[index++], cellWidth, cards));
7761
+ for (let k = 0; k < count; k++) row = Math.max(row, naturalHeight(units[index++], cellWidth, cards, void 0, composition, atFloor));
7281
7762
  total += row;
7282
7763
  }
7283
7764
  return total + gap2 * Math.max(0, shape.rows - 1);
7284
7765
  };
7285
7766
  const areaNeed = (name, rect) => name === "title" ? groupHeight(headingItemsAt(titleFields, 0, 0, spanWidth(rect))) : name === "subtitle" ? groupHeight(headingItemsAt(subtitleFields, 0, 0, spanWidth(rect))) : regionNeed(name, spanWidth(rect));
7286
7767
  const demands = rowSizes.map((size, row) => {
7287
- let need = 0, heading = false, below = false;
7768
+ let need = 0, floor = 0, heading = false, below = false;
7288
7769
  for (const [name, rect] of rects) {
7289
- if (size === "auto" ? rect.row !== row || rect.rowSpan !== 1 : row < rect.row || row >= rect.row + rect.rowSpan) continue;
7770
+ const autoSpan = size === "auto" && rect.rowSpan > 1 && rect.row === row && rowSizes.slice(rect.row, rect.row + rect.rowSpan).every((entry) => entry === "auto");
7771
+ if (size === "auto" ? rect.row !== row || rect.rowSpan !== 1 && !autoSpan : row < rect.row || row >= rect.row + rect.rowSpan) continue;
7290
7772
  if (size !== "auto" && rect.rowSpan > 1 && rowSizes.slice(rect.row, rect.row + rect.rowSpan).some((entry) => entry === "auto")) continue;
7291
- const value = areaNeed(name, rect) / rect.rowSpan;
7773
+ const value = areaNeed(name, rect) / (autoSpan ? 1 : rect.rowSpan);
7774
+ if (size === "auto" && name !== "title" && name !== "subtitle") floor = Math.max(floor, regionNeed(name, spanWidth(rect), true));
7292
7775
  if (value > need) {
7293
7776
  need = value;
7294
7777
  heading = name === "title" || name === "subtitle";
7295
7778
  below = name === BELOW;
7296
7779
  }
7297
7780
  }
7298
- return { size, need, kind: heading ? "heading" : below ? "below" : "body" };
7781
+ return { size, need, ...size === "auto" && !heading ? { floor } : {}, kind: heading ? "heading" : below ? "below" : "body" };
7299
7782
  });
7300
7783
  const heights = allocateRows(demands, frame.height, gap2);
7301
7784
  const rowY = [];
@@ -7303,9 +7786,11 @@ function composeSlide(input, options = {}) {
7303
7786
  rowY.push(size > 0 || !heights.slice(0, row).some((above) => above > 0) ? y2 : y2 - gap2);
7304
7787
  return size > 0 ? y2 + size + gap2 : y2;
7305
7788
  }, 0);
7789
+ const stackUsed = heights.reduce((sum, size) => sum + size, 0) + gap2 * Math.max(0, heights.filter((size) => size > 0).length - 1);
7790
+ const stackShift = plan2.stack === void 0 ? 0 : Math.max(0, frame.height - stackUsed) * (plan2.stack === "middle" ? 0.5 : 1);
7306
7791
  const logical = (rect) => {
7307
- const last = rect.row + rect.rowSpan - 1, y2 = frame.y + rowY[rect.row];
7308
- return { x: frame.x + columnX[rect.column], y: y2, width: spanWidth(rect), height: Math.max(0, frame.y + rowY[last] + heights[last] - y2) };
7792
+ const last = rect.row + rect.rowSpan - 1, y2 = frame.y + stackShift + rowY[rect.row];
7793
+ return { x: frame.x + columnX[rect.column], y: y2, width: spanWidth(rect), height: Math.max(0, frame.y + stackShift + rowY[last] + heights[last] - y2) };
7309
7794
  };
7310
7795
  const flip = hints.mirror === true !== rtl;
7311
7796
  const physical = (cell) => flip ? { ...cell, x: frame.x + frame.width - (cell.x - frame.x) - cell.width } : cell;
@@ -7362,18 +7847,8 @@ function composeSlide(input, options = {}) {
7362
7847
  const arrangement = arrangementOf(region, units, cell);
7363
7848
  let boxes = flowCells(flowShape(arrangement, units.length, cell.width >= cell.height), cell, gap2).cells;
7364
7849
  const referenceHeights = boxes.map((box) => box.height);
7365
- if (arrangement === "column" && region.anchor !== "top" && units.every((unit) => !unit.children && (unit.field === "text" || unit.field === "items" || unit.field === "bullets" || !unit.caption && (unit.field === "quote" || unit.field === "metric")))) {
7366
- const naturals = units.map((unit, index) => naturalHeight(unit, cell.width, cards, referenceHeights[index], settings) + 2 * rasterPadding + scale2);
7367
- const used = naturals.reduce((a, b) => a + b, 0) + gap2 * (units.length - 1);
7368
- if (used < cell.height) {
7369
- let y2 = cell.y + (region.anchor === "middle" ? (cell.height - used) / 2 : cell.height - used);
7370
- boxes = naturals.map((size) => {
7371
- const next = { x: cell.x, y: y2, width: cell.width, height: size };
7372
- y2 += size + gap2;
7373
- return next;
7374
- });
7375
- }
7376
- }
7850
+ const stacked = arrangement === "column" && !(units.length === 1 && region.anchor === "top");
7851
+ if (stacked) boxes = stackedBoxes(units, cell, gap2, settings, cards, region.anchor);
7377
7852
  if ((arrangement === "grid" || arrangement === "row") && region.anchor !== "top") {
7378
7853
  const naturals = boxes.map((box, index) => {
7379
7854
  const unit = units[index];
@@ -7388,22 +7863,44 @@ function composeSlide(input, options = {}) {
7388
7863
  });
7389
7864
  }
7390
7865
  if (rtl) boxes = boxes.map((box) => ({ ...box, x: cell.x + cell.width - (box.x - cell.x) - box.width }));
7866
+ const shape = flowShape(arrangement, units.length, cell.width >= cell.height), rowItems = shape.perRow.map(() => []);
7867
+ const rowOf = (index) => {
7868
+ let row = 0;
7869
+ for (let seen = shape.perRow[0] ?? 0; index >= seen; seen += shape.perRow[++row] ?? Infinity) ;
7870
+ return row;
7871
+ };
7872
+ const stack = [];
7391
7873
  units.forEach((unit, index) => {
7392
- placeNode(unit, boxes[index], settings, cards, region.listColumns, region.anchor === "top" ? void 0 : referenceHeights[index]);
7874
+ const first = items.length;
7875
+ placeNode(unit, boxes[index], settings, cards, region.listColumns, region.anchor === "top" || stacked ? void 0 : referenceHeights[index], region.anchor);
7876
+ if (!unit.children) {
7877
+ rowItems[rowOf(index)]?.push(...range(first, items.length));
7878
+ leafItems.push(...range(first, items.length));
7879
+ stack.push(...range(first, items.length));
7880
+ }
7393
7881
  });
7882
+ if (arrangement !== "column") peerSets.push(...rowItems.filter((_, row) => (shape.perRow[row] ?? 0) > 1));
7883
+ else if (stacked && stack.length > 1) peerSets.push(stack);
7394
7884
  return arrangement;
7395
7885
  } finally {
7396
7886
  regionTag = void 0;
7397
7887
  templateCell = false;
7398
7888
  }
7399
7889
  };
7890
+ const bands = /* @__PURE__ */ new Map();
7891
+ let leafItems = [];
7400
7892
  for (const region of plan2.regions) {
7401
7893
  const units = unitsOf(region.name), bound = binding.regions.find((entry) => entry.name === region.name);
7402
7894
  const rect = rects.get(region.name);
7403
7895
  let cell = physical(logical(rect ?? ownRects.get(region.name)));
7404
7896
  const areaBox = region.bleed && rect ? roundBox(cell) : void 0;
7405
7897
  if (region.bleed && rect) cell = bleed(cell);
7898
+ leafItems = [];
7406
7899
  const arrangement = units.length && rect ? placeRegion(region, units, cell) : void 0;
7900
+ if (arrangement && rect) {
7901
+ const band = `${rect.row}:${rect.rowSpan}:${region.role}`;
7902
+ bands.set(band, [...bands.get(band) ?? [], leafItems]);
7903
+ }
7407
7904
  templateRegions.push({
7408
7905
  name: region.name,
7409
7906
  box: roundBox(cell),
@@ -7420,8 +7917,36 @@ function composeSlide(input, options = {}) {
7420
7917
  ...areaBox ? { areaBox } : {}
7421
7918
  });
7422
7919
  }
7920
+ for (const members of bands.values()) if (members.length > 1) peerSets.push(members.flat());
7423
7921
  if (belowUnits.length) placeRegion(belowRegion, belowUnits, physical(logical(rects.get(BELOW))));
7424
7922
  };
7923
+ const sharePeerSizes = () => {
7924
+ const parent = /* @__PURE__ */ new Map();
7925
+ const find = (index) => {
7926
+ let root = index;
7927
+ while (parent.has(root) && parent.get(root) !== root) root = parent.get(root);
7928
+ parent.set(index, root);
7929
+ return root;
7930
+ };
7931
+ for (const set2 of peerSets) for (const index of set2) parent.set(find(index), find(set2[0]));
7932
+ const kindOf = (item) => !item.text || item.quoteLayout || item.codeLayout || item.metricLayout || item.timelineLayout || item.listColumns ? void 0 : item.field === "text" ? "text" : item.field === "items" || item.field === "bullets" ? "list" : void 0;
7933
+ const sets = /* @__PURE__ */ new Map();
7934
+ for (const index of parent.keys()) {
7935
+ const item = items[index], kind2 = item && kindOf(item);
7936
+ if (kind2) sets.set(`${find(index)}:${kind2}`, [...sets.get(`${find(index)}:${kind2}`) ?? [], index]);
7937
+ }
7938
+ for (const members of sets.values()) {
7939
+ if (members.length < 2) continue;
7940
+ const size = Math.min(...members.map((index) => items[index].text.fontSize));
7941
+ for (const index of members) {
7942
+ const item = items[index];
7943
+ if (item.text.fontSize <= size + 1e-9) continue;
7944
+ const text2 = contentText(item.field, item.value) ?? "";
7945
+ const refit = fitContent(item.field, item.value, text2, item.box, size, snapFontSizeUp((item.composition.minFontSize ?? 16) * scale2), item.path, item.payload.numbering);
7946
+ if (!refit.overflow && Math.abs(refit.fontSize - size) < 1e-9) item.text = refit;
7947
+ }
7948
+ }
7949
+ };
7425
7950
  const ownMode = record7(slide.composition).mode;
7426
7951
  const direction = hints.contentDirection;
7427
7952
  const directionMode = direction === "vertical" ? "column" : direction === "horizontal" ? "row" : "auto";
@@ -7437,6 +7962,7 @@ function composeSlide(input, options = {}) {
7437
7962
  const support = { field: "blocks", type: "group", value: rest.map((node) => node.payload), path, payload: {}, children: rest, composition: {}, synthetic: true };
7438
7963
  arrange(firstChart ? [primary, support] : [support, primary], contentBox, rootSettings, 0);
7439
7964
  } else arrange(pending, contentBox, rootSettings, 0);
7965
+ sharePeerSizes();
7440
7966
  if (listBullet?.value === "image" && !bulletImage && items.some((item) => (item.field === "items" || item.field === "bullets") && item.payload.numbering === void 0)) {
7441
7967
  diagnostics.push({ code: "unresolved-content", path: listBullet.path, message: "Picture bullets (listBullet: image) need an organization logo (the primary organization, or the one design.logo names); the marker glyph is drawn instead." });
7442
7968
  }
@@ -7499,4 +8025,4 @@ function resolveCanvasDimensions(input) {
7499
8025
  return { width, height };
7500
8026
  }
7501
8027
 
7502
- export { AUTO_LAYOUT, CAPTIONABLE_FIELDS, CAPTION_FONT_RATIO, CAPTION_MAX_RATIO, CATALOG_GROUP_PATTERN, CATALOG_REFERENCE_PATTERN, CHART_HIGHLIGHT_MUTED_MIN_CONTRAST, CHART_HIGHLIGHT_MUTED_MIX, CHART_SERIES_MIN_DIFFERENCE, CHART_SERIES_MIN_LIGHTNESS_STEP, CHART_SERIES_TIER_SIZE, CHART_SERIES_TINT_MIX, CHART_TEXT_PX, CHART_TYPES, CITATION_MARKER_RAISE, CITATION_MARKER_SCALE, CODE_HIGHLIGHT_LANGUAGES, CODE_HIGHLIGHT_MAX_LENGTH, CODE_PANEL_BACKGROUND, CODE_PANEL_FOREGROUND, CODE_SYNTAX_MIN_CONTRAST, DARK_BACKGROUND_LUMINANCE, DEFAULT_CHART_LABEL_SEPARATOR, DEFAULT_FURNITURE_DATE_FORMAT, DEFAULT_VARIABLE_DATE_FORMAT, DESIGN_HINT_KEYS, ENGINE_DEFAULT_CHART_TYPES, ENGINE_DEFAULT_COLOR_SCHEME, ENGINE_DEFAULT_FONT_SCHEME, ENGINE_DEFAULT_THEME, FONT_SIZE_GRID_PER_PX, FOOTNOTE_MAX_RATIO, FURNITURE_GAP, FURNITURE_IMAGE_SHARE, FURNITURE_ZONE_GUTTER, HEADING_AREAS, LANGUAGES, LAYOUT_BODY_KINDS, LOGO_SHAPES, MAX_COMPOSITION_DEPTH, MAX_NUMBERING_LEVELS, MAX_NUMBERING_VALUE, MAX_REGION_BLOCKS, MAX_REGION_DEPTH, MAX_REGION_ENTRIES, MAX_ROMAN_VALUE, MAX_TEMPLATE_TRACKS, METRIC_TREND_MIN_CONTRAST, METRIC_TREND_SHAPES, NEUTRAL_SHARES, NUMBERING_STYLES, NUMBERING_SUFFIXES, OPFCatalogsOptionError, OPFCompositionError, OPFLayoutTemplateError, OPFUnresolvedReferenceError, OPFVariableError, PATTERN_PRESETS, PATTERN_PRESET_ALIASES, PATTERN_TILE_SIZE, PICTURE_BULLET_SCALE, PROMOTED_REGION_KEY, REGION_ANCHORS, REGION_FLOWS, REGION_KINDS, REGION_ROLES, RESERVED_REGION_NAMES, SLIDE_SCOPED_BUILTINS, SOCIAL_PLATFORMS, SURFACE_ALT_MIN_CONTRAST, SURFACE_ALT_MIX, THEME_DESIGN_KEYS, TIMELINE_OUTLINE_MIN_CONTRAST, TIMELINE_STATUSES, TIMELINE_TEXT_MIN_CONTRAST, VARIABLE_KINDS, allocateRows, annotationText, applyLuminance, bindRegions, blockKind, captionSettings, catalogGroupDeclared, catalogGroupSource, catalogKinds, catalogRecords, catalogReferenceSites, chartColorForFill, chartHighlightColors, chartHighlightMarks, chartLabelText, chartNumber, chartNumberFixesByCell, chartOptionSupport, chartOptionTarget, chartPaletteForFill, chartSeriesPalette, chartTextSize, chartValueAxis, checkCatalogsOption, citationMarkerText, clrMapAttributes, codeHighlightBands, codeHighlightColors, codeHighlightLines, codeHighlightSlice, codeLineNumbers, codeLineRuns, codeSyntaxPalette, codeSyntaxPaletteForRoles, codeSyntaxPaletteForScheme, coerceVariableValue, collectCitations, colorContrast, colorMapFor, composeLayoutAreas, composeSlide, datasetDiagnostics, defaultSlideBackground, dottedPath, embeddedCopy, fitImage, fitList, fitRichText, fitText, formatChartLabelNumber, formatChartLabelPercent, formatDataNumber, formatFurnitureDate, formatListNumber, formatVariableNumber, fromExcelNumberFormat, gridFlowShape, hasContentVariables, imageBackground, imageShape, inlineChartData, inlineDatasets, inlineTableData, instantiateForValidation, intrinsicImageAspect, intrinsicImageSize, isColorVariant, isDarkColor, isDatasetRef, isLayoutTemplate, isRecord, isTemplate, isXYChartType, layoutCaption, layoutCode, layoutContent, layoutEntries, layoutFootnotes, layoutFurniture, layoutMetric, layoutQuote, layoutRegion, layoutStructure, layoutTable, layoutTemplate, layoutTemplateIssues, layoutTimeline, layoutWatermark, listBuiltinVariables, listColumnBreaks, listNumbers, listVariables, maxIntervalsFor, measureText, measureTextOutline, metricTrendColor, metricTrendMark, metricTrendPoints, niceScale, normalizeColorVariant, normalizeHexColor, normalizeLanguageFamily, numberFormatError, numberingAtLevel, numberingFindings, numberingStyleDraws, paragraphDirection, paragraphDirectionAt, parseReference, pathFor, patternBitmap, patternRuns, physicalAlignment, placeTextLines, placedImageBlocks, pointerPath, promotedRegionKeys, provenanceOf, readableSchemeColor, readingRows, recordReferenceSites, referencesSlide, regionAccepts, regionRoleRank, registeredCatalog, resolveCanvasDimensions, resolveChartColors, resolveChartData, resolveChartOptions, resolveCodeLanguage, resolveColorRef, resolveColorRoles, resolveDesignHints, resolveDesignRecords, resolveFontFamilies, resolveFontScheme, resolveLogo, resolveNumbering, resolvePatternPreset, resolveReference, resolveScriptFonts, resolveSlideDirection, resolveSlideVariables, resolveSocialProfile, resolveTableData, resolveTextStyle, resolveVariables, rootNodes, scriptFontRole, sliceNumberedItems, slideCitations, slideTokenSpans, snapFontSizeDown, snapFontSizeUp, stackCategoryValues, suggestChartNumberFix, surfaceAltColor, tableCellDisplayValue, tableGrid, tableRowBoundaries, textColorForFill, textWidthMeasurer, timelineMarkerShapes, timelineTextColor, toExcelNumberFormat, tokenizeCode, unresolvedReference, unusedDatasets, usesSlideBuiltin, variableDeclarations, visitContentPayloads, visualReadingOrder, walkCitationRuns, wrapText };
8028
+ export { ASCII_QUOTE_MARKS, AUTO_LAYOUT, CAPTIONABLE_FIELDS, CAPTION_FONT_RATIO, CAPTION_MAX_RATIO, CATALOG_GROUP_PATTERN, CATALOG_REFERENCE_PATTERN, CHART_HIGHLIGHT_MUTED_MIN_CONTRAST, CHART_HIGHLIGHT_MUTED_MIX, CHART_LEAD_MIN_CONTRAST, CHART_MAX_INTERVALS, CHART_MIN_TICK_SPACING_PX, CHART_SERIES_MIN_DIFFERENCE, CHART_SERIES_MIN_LIGHTNESS_STEP, CHART_SERIES_TIER_SIZE, CHART_SERIES_TINT_MIX, CHART_TEXT_FLOOR_PX, CHART_TEXT_PX, CHART_TYPES, CITATION_MARKER_RAISE, CITATION_MARKER_SCALE, CODE_HIGHLIGHT_LANGUAGES, CODE_HIGHLIGHT_MAX_LENGTH, CODE_NOMINAL_SIZE, CODE_PANEL_BACKGROUND, CODE_PANEL_FOREGROUND, CODE_SYNTAX_MIN_CONTRAST, DARK_BACKGROUND_LUMINANCE, DEFAULT_CHART_LABEL_SEPARATOR, DEFAULT_FURNITURE_DATE_FORMAT, DEFAULT_VARIABLE_DATE_FORMAT, DESIGN_HINT_KEYS, ENGINE_DEFAULT_CHART_TYPES, ENGINE_DEFAULT_COLOR_SCHEME, ENGINE_DEFAULT_FONT_SCHEME, ENGINE_DEFAULT_THEME, FONT_SIZE_GRID_PER_PX, FOOTNOTE_MAX_RATIO, FURNITURE_GAP, FURNITURE_IMAGE_SHARE, FURNITURE_ZONE_GUTTER, HEADING_AREAS, LANGUAGES, LAYOUT_BODY_KINDS, LEAD_NOMINAL_SIZE, LIST_NOMINAL_SIZE, LOGO_SHAPES, MAX_COMPOSITION_DEPTH, MAX_NUMBERING_LEVELS, MAX_NUMBERING_VALUE, MAX_REGION_BLOCKS, MAX_REGION_DEPTH, MAX_REGION_ENTRIES, MAX_ROMAN_VALUE, MAX_TEMPLATE_TRACKS, METRIC_TREND_MIN_CONTRAST, METRIC_TREND_SHAPES, METRIC_VALUE_MIN_CONTRAST, NEUTRAL_SHARES, NUMBERING_STYLES, NUMBERING_SUFFIXES, OPFCatalogsOptionError, OPFCompositionError, OPFLayoutTemplateError, OPFUnresolvedReferenceError, OPFVariableError, PATTERN_PRESETS, PATTERN_PRESET_ALIASES, PATTERN_TILE_SIZE, PICTURE_BULLET_SCALE, PROMOTED_REGION_KEY, PROSE_NOMINAL_SIZE, QUOTE_MARKS, QUOTE_NOMINAL_SIZE, QUOTE_SOURCE_SEPARATOR, REGION_ANCHORS, REGION_FLOWS, REGION_KINDS, REGION_ROLES, RESERVED_REGION_NAMES, SLIDE_SCOPED_BUILTINS, SOCIAL_PLATFORMS, SURFACE_ALT_MIN_CONTRAST, SURFACE_ALT_MIX, TABLE_NOMINAL_SIZE, THEME_DESIGN_KEYS, TIMELINE_OUTLINE_MIN_CONTRAST, TIMELINE_STATUSES, TIMELINE_TEXT_MIN_CONTRAST, VARIABLE_KINDS, allocateRows, annotationText, applyLuminance, bindRegions, blockKind, captionSettings, catalogGroupDeclared, catalogGroupSource, catalogKinds, catalogRecords, catalogReferenceSites, chartColorForFill, chartHighlightColors, chartHighlightMarks, chartIntervalsFor, chartLabelText, chartLeadFromScheme, chartNumber, chartNumberFixesByCell, chartOptionSupport, chartOptionTarget, chartPaletteForFill, chartSeriesPalette, chartTextSize, chartValueAxis, checkCatalogsOption, citationMarkerText, clrMapAttributes, codeHighlightBands, codeHighlightColors, codeHighlightLines, codeHighlightSlice, codeLineNumbers, codeLineRuns, codeSyntaxPalette, codeSyntaxPaletteForRoles, codeSyntaxPaletteForScheme, coerceVariableValue, collectCitations, colorContrast, colorMapFor, composeLayoutAreas, composeSlide, datasetDiagnostics, defaultSlideBackground, dottedPath, embeddedCopy, fitImage, fitList, fitRichText, fitText, formatChartLabelNumber, formatChartLabelPercent, formatDataNumber, formatFurnitureDate, formatListNumber, formatVariableNumber, fromExcelNumberFormat, gridFlowShape, hasContentVariables, imageBackground, imageShape, inlineChartData, inlineDatasets, inlineTableData, instantiateForValidation, intrinsicImageAspect, intrinsicImageSize, isColorVariant, isDarkColor, isDatasetRef, isLayoutTemplate, isRecord, isTemplate, isWrappedInQuoteMarks, isXYChartType, layoutCaption, layoutCode, layoutContent, layoutEntries, layoutFootnotes, layoutFurniture, layoutMetric, layoutQuote, layoutRegion, layoutStructure, layoutTable, layoutTemplate, layoutTemplateIssues, layoutTimeline, layoutWatermark, listBuiltinVariables, listColumnBreaks, listNumbers, listVariables, maxIntervalsFor, measureText, measureTextOutline, metricTrendColor, metricTrendMark, metricTrendPoints, metricValueColor, niceScale, normalizeColorVariant, normalizeHexColor, normalizeLanguageFamily, numberFormatError, numberingAtLevel, numberingFindings, numberingStyleDraws, paragraphDirection, paragraphDirectionAt, parseReference, pathFor, patternBitmap, patternRuns, physicalAlignment, placeTextLines, placedImageBlocks, pointerPath, promotedRegionKeys, provenanceOf, quoteLanguage, quoteMarksFor, readableSchemeColor, readingRows, recordReferenceSites, referencesSlide, regionAccepts, regionRoleRank, registeredCatalog, resolveCanvasDimensions, resolveChartColors, resolveChartData, resolveChartOptions, resolveCodeLanguage, resolveColorRef, resolveColorRoles, resolveDesignHints, resolveDesignRecords, resolveFontFamilies, resolveFontScheme, resolveLogo, resolveNumbering, resolvePatternPreset, resolveReference, resolveScriptFonts, resolveSlideDirection, resolveSlideVariables, resolveSocialProfile, resolveTableData, resolveTextStyle, resolveVariables, rootNodes, scriptFontRole, shareHeights, sliceNumberedItems, slideCitations, slideTokenSpans, snapFontSizeDown, snapFontSizeUp, stackCategoryValues, suggestChartNumberFix, surfaceAltColor, tableCellDisplayValue, tableGrid, tableRowBoundaries, textColorForFill, textWidthMeasurer, timelineMarkerShapes, timelineTextColor, toExcelNumberFormat, tokenizeCode, unresolvedReference, unusedDatasets, unwrapQuoteMarks, usesSlideBuiltin, variableDeclarations, visitContentPayloads, visualReadingOrder, walkCitationRuns, wrapText };