@openpresentation/opf 0.11.0 → 0.11.1

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 (240) hide show
  1. package/dist/{catalogs-atkOF4Vy.d.ts → catalogs-DJ-B5ZyD.d.ts} +78 -8
  2. package/dist/catalogs.d.ts +2 -2
  3. package/dist/catalogs.js +1 -1
  4. package/dist/{chunk-FTWNWFIV.js → chunk-6UPHKXI5.js} +310 -34
  5. package/dist/{chunk-6FU2KICU.js → chunk-7LF37SG3.js} +7084 -5106
  6. package/dist/{chunk-EQHZ3ZO4.js → chunk-BP6WMSSS.js} +3 -3
  7. package/dist/{chunk-FWA5XB45.js → chunk-KWIOQ3QR.js} +45 -25
  8. package/dist/{chunk-GN52EE3P.js → chunk-LTSDDBNW.js} +425 -40
  9. package/dist/{chunk-CHKJ4QHQ.js → chunk-M4GTWRK7.js} +113 -32
  10. package/dist/chunk-NPYN4TZ5.js +3516 -0
  11. package/dist/{chunk-RS5GZ5RW.js → chunk-SDETQV7Y.js} +161 -0
  12. package/dist/{composition-Tv9Wm3Oh.d.ts → composition-DzLwY-Mi.d.ts} +200 -4
  13. package/dist/composition.d.ts +1 -1
  14. package/dist/composition.js +1 -1
  15. package/dist/docs.js +50 -20
  16. package/dist/examples.d.ts +1 -1
  17. package/dist/examples.js +11 -11
  18. package/dist/font-policy.d.ts +123 -0
  19. package/dist/font-policy.js +1 -0
  20. package/dist/index.d.ts +129 -10
  21. package/dist/index.js +280 -12
  22. package/dist/lint.d.ts +3 -3
  23. package/dist/lint.js +5 -5
  24. package/dist/pagination.d.ts +5 -1
  25. package/dist/pagination.js +5 -5
  26. package/dist/{presentation-Ct42Mitb.d.ts → presentation-bceTClm8.d.ts} +246 -22
  27. package/dist/repo-readme.js +1 -1
  28. package/dist/{schemas-CiVmUT2p.d.ts → schemas-X_NniU4A.d.ts} +2 -0
  29. package/dist/schemas.d.ts +1 -1
  30. package/dist/schemas.js +1 -1
  31. package/dist/spec/catalogs/audiences/academic.json +24 -0
  32. package/dist/spec/catalogs/audiences/all-hands.json +3 -1
  33. package/dist/spec/catalogs/audiences/board.json +3 -1
  34. package/dist/spec/catalogs/audiences/customer.json +25 -0
  35. package/dist/spec/catalogs/audiences/executive.json +24 -0
  36. package/dist/spec/catalogs/audiences/general-public.json +25 -0
  37. package/dist/spec/catalogs/audiences/index.json +73 -1
  38. package/dist/spec/catalogs/audiences/internal-team.json +25 -0
  39. package/dist/spec/catalogs/audiences/investor.json +25 -0
  40. package/dist/spec/catalogs/audiences/marketing.json +25 -0
  41. package/dist/spec/catalogs/audiences/media.json +24 -0
  42. package/dist/spec/catalogs/audiences/partner.json +25 -0
  43. package/dist/spec/catalogs/audiences/regulatory.json +25 -0
  44. package/dist/spec/catalogs/audiences/sales.json +25 -0
  45. package/dist/spec/catalogs/audiences/technical.json +25 -0
  46. package/dist/spec/catalogs/chart-types/100pct-bullet-bar-2x.json +5 -0
  47. package/dist/spec/catalogs/chart-types/100pct-bullet-bar-3x.json +5 -0
  48. package/dist/spec/catalogs/chart-types/100pct-bullet-bar.json +5 -0
  49. package/dist/spec/catalogs/chart-types/100pct-bullet-column-2x.json +5 -0
  50. package/dist/spec/catalogs/chart-types/100pct-bullet-column-3x.json +5 -0
  51. package/dist/spec/catalogs/chart-types/100pct-bullet-column.json +5 -0
  52. package/dist/spec/catalogs/chart-types/100pct-progress-bar.json +10 -0
  53. package/dist/spec/catalogs/chart-types/100pct-stacked-area-2x.json +10 -0
  54. package/dist/spec/catalogs/chart-types/100pct-stacked-area-3x.json +6 -1
  55. package/dist/spec/catalogs/chart-types/100pct-stacked-bar-2x.json +10 -0
  56. package/dist/spec/catalogs/chart-types/100pct-stacked-bar-3x.json +6 -1
  57. package/dist/spec/catalogs/chart-types/100pct-stacked-column-2x.json +10 -0
  58. package/dist/spec/catalogs/chart-types/100pct-stacked-column-3x.json +6 -1
  59. package/dist/spec/catalogs/chart-types/area.json +5 -0
  60. package/dist/spec/catalogs/chart-types/australia.json +10 -0
  61. package/dist/spec/catalogs/chart-types/bar.json +5 -0
  62. package/dist/spec/catalogs/chart-types/box-and-whisker-2x.json +10 -0
  63. package/dist/spec/catalogs/chart-types/box-and-whisker-3x.json +10 -0
  64. package/dist/spec/catalogs/chart-types/box-and-whisker.json +5 -0
  65. package/dist/spec/catalogs/chart-types/bullet-bar-2x.json +5 -0
  66. package/dist/spec/catalogs/chart-types/bullet-bar-3x.json +5 -0
  67. package/dist/spec/catalogs/chart-types/bullet-bar.json +5 -0
  68. package/dist/spec/catalogs/chart-types/bullet-column-2x.json +5 -0
  69. package/dist/spec/catalogs/chart-types/bullet-column-3x.json +5 -0
  70. package/dist/spec/catalogs/chart-types/bullet-column.json +5 -0
  71. package/dist/spec/catalogs/chart-types/canada.json +10 -0
  72. package/dist/spec/catalogs/chart-types/clustered-bar-2x.json +10 -0
  73. package/dist/spec/catalogs/chart-types/clustered-column.json +10 -0
  74. package/dist/spec/catalogs/chart-types/column.json +5 -0
  75. package/dist/spec/catalogs/chart-types/dot-plot-2x.json +5 -0
  76. package/dist/spec/catalogs/chart-types/dot-plot-3x.json +5 -0
  77. package/dist/spec/catalogs/chart-types/dot-plot-4x.json +5 -0
  78. package/dist/spec/catalogs/chart-types/dot-plot-5x.json +5 -0
  79. package/dist/spec/catalogs/chart-types/dot-plot-6x.json +5 -0
  80. package/dist/spec/catalogs/chart-types/dot-plot.json +5 -0
  81. package/dist/spec/catalogs/chart-types/doughnut.json +5 -0
  82. package/dist/spec/catalogs/chart-types/dumbbell.json +5 -0
  83. package/dist/spec/catalogs/chart-types/filled-radar.json +5 -0
  84. package/dist/spec/catalogs/chart-types/funnel.json +5 -0
  85. package/dist/spec/catalogs/chart-types/histogram.json +5 -0
  86. package/dist/spec/catalogs/chart-types/index.json +333 -233
  87. package/dist/spec/catalogs/chart-types/line-2x.json +10 -0
  88. package/dist/spec/catalogs/chart-types/line-3x.json +10 -0
  89. package/dist/spec/catalogs/chart-types/line-with-high-low-and-markers.json +5 -0
  90. package/dist/spec/catalogs/chart-types/line-with-high-low.json +5 -0
  91. package/dist/spec/catalogs/chart-types/line-with-markers-2x.json +10 -0
  92. package/dist/spec/catalogs/chart-types/line-with-markers-3x.json +10 -0
  93. package/dist/spec/catalogs/chart-types/line-with-markers.json +5 -0
  94. package/dist/spec/catalogs/chart-types/line.json +5 -0
  95. package/dist/spec/catalogs/chart-types/pareto.json +9 -14
  96. package/dist/spec/catalogs/chart-types/pie.json +5 -0
  97. package/dist/spec/catalogs/chart-types/radar-with-markers.json +5 -0
  98. package/dist/spec/catalogs/chart-types/radar.json +5 -0
  99. package/dist/spec/catalogs/chart-types/scatter.json +5 -0
  100. package/dist/spec/catalogs/chart-types/sparkline-2x.json +5 -0
  101. package/dist/spec/catalogs/chart-types/sparkline-3x.json +5 -0
  102. package/dist/spec/catalogs/chart-types/sparkline-4x.json +5 -0
  103. package/dist/spec/catalogs/chart-types/sparkline-5x.json +5 -0
  104. package/dist/spec/catalogs/chart-types/sparkline-6x.json +5 -0
  105. package/dist/spec/catalogs/chart-types/sparkline.json +5 -0
  106. package/dist/spec/catalogs/chart-types/stacked-area-2x.json +10 -0
  107. package/dist/spec/catalogs/chart-types/stacked-area-3x.json +6 -1
  108. package/dist/spec/catalogs/chart-types/stacked-bar-2x.json +10 -0
  109. package/dist/spec/catalogs/chart-types/stacked-bar-3x.json +6 -1
  110. package/dist/spec/catalogs/chart-types/stacked-column-2x.json +10 -0
  111. package/dist/spec/catalogs/chart-types/stacked-column-3x.json +6 -1
  112. package/dist/spec/catalogs/chart-types/stacked-line-2x.json +10 -0
  113. package/dist/spec/catalogs/chart-types/stacked-line-3x.json +6 -1
  114. package/dist/spec/catalogs/chart-types/stacked-line-with-markers-2x.json +10 -0
  115. package/dist/spec/catalogs/chart-types/stacked-line-with-markers-3x.json +6 -1
  116. package/dist/spec/catalogs/chart-types/treemap-2x.json +10 -0
  117. package/dist/spec/catalogs/chart-types/treemap-3x.json +10 -0
  118. package/dist/spec/catalogs/chart-types/treemap.json +5 -0
  119. package/dist/spec/catalogs/chart-types/united-kingdom.json +10 -0
  120. package/dist/spec/catalogs/chart-types/united-states.json +10 -0
  121. package/dist/spec/catalogs/chart-types/waterfall.json +5 -0
  122. package/dist/spec/catalogs/chart-types/world.json +6 -1
  123. package/dist/spec/catalogs/font-schemes/consolas.json +3 -0
  124. package/dist/spec/catalogs/font-schemes/courier-new.json +3 -0
  125. package/dist/spec/catalogs/languages/afrikaans.json +3 -0
  126. package/dist/spec/catalogs/languages/albanian.json +3 -0
  127. package/dist/spec/catalogs/languages/amharic.json +3 -0
  128. package/dist/spec/catalogs/languages/arabic.json +3 -0
  129. package/dist/spec/catalogs/languages/armenian.json +3 -0
  130. package/dist/spec/catalogs/languages/aymara.json +3 -0
  131. package/dist/spec/catalogs/languages/azerbaijani.json +3 -0
  132. package/dist/spec/catalogs/languages/bengali.json +3 -0
  133. package/dist/spec/catalogs/languages/berber-latin.json +3 -0
  134. package/dist/spec/catalogs/languages/bosnian-latin.json +3 -0
  135. package/dist/spec/catalogs/languages/bulgarian.json +3 -0
  136. package/dist/spec/catalogs/languages/catalan.json +3 -0
  137. package/dist/spec/catalogs/languages/cebuano.json +3 -0
  138. package/dist/spec/catalogs/languages/chinese-simplified.json +3 -0
  139. package/dist/spec/catalogs/languages/chinese-traditional.json +3 -0
  140. package/dist/spec/catalogs/languages/chittagonian.json +3 -0
  141. package/dist/spec/catalogs/languages/croatian.json +3 -0
  142. package/dist/spec/catalogs/languages/czech.json +3 -0
  143. package/dist/spec/catalogs/languages/danish.json +3 -0
  144. package/dist/spec/catalogs/languages/dutch.json +3 -0
  145. package/dist/spec/catalogs/languages/english-au.json +3 -0
  146. package/dist/spec/catalogs/languages/english-ca.json +3 -0
  147. package/dist/spec/catalogs/languages/english-gb.json +3 -0
  148. package/dist/spec/catalogs/languages/english-in.json +3 -0
  149. package/dist/spec/catalogs/languages/english-us.json +3 -0
  150. package/dist/spec/catalogs/languages/english.json +3 -0
  151. package/dist/spec/catalogs/languages/estonian.json +3 -0
  152. package/dist/spec/catalogs/languages/filipino.json +3 -0
  153. package/dist/spec/catalogs/languages/finnish.json +3 -0
  154. package/dist/spec/catalogs/languages/french.json +3 -0
  155. package/dist/spec/catalogs/languages/fulfulde.json +3 -0
  156. package/dist/spec/catalogs/languages/galician.json +3 -0
  157. package/dist/spec/catalogs/languages/georgian.json +3 -0
  158. package/dist/spec/catalogs/languages/german.json +3 -0
  159. package/dist/spec/catalogs/languages/greek.json +3 -0
  160. package/dist/spec/catalogs/languages/gujarati.json +3 -0
  161. package/dist/spec/catalogs/languages/hausa.json +3 -0
  162. package/dist/spec/catalogs/languages/hebrew.json +3 -0
  163. package/dist/spec/catalogs/languages/hindi.json +3 -0
  164. package/dist/spec/catalogs/languages/hungarian.json +3 -0
  165. package/dist/spec/catalogs/languages/igbo.json +3 -0
  166. package/dist/spec/catalogs/languages/indonesian.json +3 -0
  167. package/dist/spec/catalogs/languages/italian.json +3 -0
  168. package/dist/spec/catalogs/languages/japanese.json +3 -0
  169. package/dist/spec/catalogs/languages/kannada.json +3 -0
  170. package/dist/spec/catalogs/languages/kazakh.json +3 -0
  171. package/dist/spec/catalogs/languages/khmer.json +3 -0
  172. package/dist/spec/catalogs/languages/kinyarwanda.json +3 -0
  173. package/dist/spec/catalogs/languages/korean.json +3 -0
  174. package/dist/spec/catalogs/languages/kurmanji.json +3 -0
  175. package/dist/spec/catalogs/languages/latvian.json +3 -0
  176. package/dist/spec/catalogs/languages/lithuanian.json +3 -0
  177. package/dist/spec/catalogs/languages/macedonian.json +3 -0
  178. package/dist/spec/catalogs/languages/malagasy.json +3 -0
  179. package/dist/spec/catalogs/languages/malay.json +3 -0
  180. package/dist/spec/catalogs/languages/malayalam.json +3 -0
  181. package/dist/spec/catalogs/languages/maori.json +3 -0
  182. package/dist/spec/catalogs/languages/marathi.json +3 -0
  183. package/dist/spec/catalogs/languages/mongolian.json +3 -0
  184. package/dist/spec/catalogs/languages/nepali.json +3 -0
  185. package/dist/spec/catalogs/languages/norwegian.json +3 -0
  186. package/dist/spec/catalogs/languages/odia.json +3 -0
  187. package/dist/spec/catalogs/languages/oromo.json +3 -0
  188. package/dist/spec/catalogs/languages/pashto.json +3 -0
  189. package/dist/spec/catalogs/languages/persian.json +3 -0
  190. package/dist/spec/catalogs/languages/polish.json +3 -0
  191. package/dist/spec/catalogs/languages/portuguese.json +3 -0
  192. package/dist/spec/catalogs/languages/punjabi-gurmukhi.json +3 -0
  193. package/dist/spec/catalogs/languages/punjabi-shahmukhi.json +3 -0
  194. package/dist/spec/catalogs/languages/romanian.json +3 -0
  195. package/dist/spec/catalogs/languages/russian.json +3 -0
  196. package/dist/spec/catalogs/languages/serbian-cyrillic.json +3 -0
  197. package/dist/spec/catalogs/languages/serbian-latin.json +3 -0
  198. package/dist/spec/catalogs/languages/shona.json +3 -0
  199. package/dist/spec/catalogs/languages/slovak.json +3 -0
  200. package/dist/spec/catalogs/languages/slovenian.json +3 -0
  201. package/dist/spec/catalogs/languages/somali.json +3 -0
  202. package/dist/spec/catalogs/languages/spanish.json +3 -0
  203. package/dist/spec/catalogs/languages/swahili.json +3 -0
  204. package/dist/spec/catalogs/languages/swedish.json +3 -0
  205. package/dist/spec/catalogs/languages/tagalog.json +3 -0
  206. package/dist/spec/catalogs/languages/tajik.json +3 -0
  207. package/dist/spec/catalogs/languages/tamil.json +3 -0
  208. package/dist/spec/catalogs/languages/telugu.json +3 -0
  209. package/dist/spec/catalogs/languages/thai.json +3 -0
  210. package/dist/spec/catalogs/languages/turkish.json +3 -0
  211. package/dist/spec/catalogs/languages/ukrainian.json +3 -0
  212. package/dist/spec/catalogs/languages/urdu.json +3 -0
  213. package/dist/spec/catalogs/languages/uzbek-latin.json +3 -0
  214. package/dist/spec/catalogs/languages/vietnamese-quoc-ngu.json +3 -0
  215. package/dist/spec/catalogs/languages/xhosa.json +3 -0
  216. package/dist/spec/catalogs/languages/yoruba.json +3 -0
  217. package/dist/spec/catalogs/languages/zulu.json +3 -0
  218. package/dist/spec/catalogs/narratives/change-story.json +64 -0
  219. package/dist/spec/catalogs/narratives/data-story.json +66 -0
  220. package/dist/spec/catalogs/narratives/heros-journey.json +64 -0
  221. package/dist/spec/catalogs/narratives/index.json +81 -0
  222. package/dist/spec/catalogs/narratives/pyramid-principle.json +48 -0
  223. package/dist/spec/catalogs/narratives/situation-complication-resolution.json +56 -0
  224. package/dist/spec/catalogs/narratives/sparkline.json +56 -0
  225. package/dist/spec/catalogs/narratives/star-method.json +56 -0
  226. package/dist/spec/catalogs/narratives/vision-roadmap.json +65 -0
  227. package/dist/spec/catalogs/narratives/what-so-what-now-what.json +48 -0
  228. package/dist/spec/reference/engine-defaults.json +2 -2
  229. package/dist/spec/reference/font-policy.json +3435 -0
  230. package/dist/spec/reference/font-policy.schema.json +328 -0
  231. package/dist/spec/schemas/chart-type.schema.json +51 -11
  232. package/dist/spec/schemas/font-scheme.schema.json +91 -1
  233. package/dist/spec/schemas/language.schema.json +10 -5
  234. package/dist/spec/schemas/opf.schema.json +158 -20
  235. package/dist/spec-files.d.ts +1 -1
  236. package/dist/spec-files.js +1 -1
  237. package/dist/types.d.ts +3 -3
  238. package/dist/validator.d.ts +3 -3
  239. package/dist/validator.js +4 -4
  240. package/package.json +6 -2
@@ -35,6 +35,112 @@ function tableRowBoundaries(value) {
35
35
  return [...allowed];
36
36
  }
37
37
 
38
+ // src/furniture-fields.ts
39
+ var DEFAULT_FURNITURE_DATE_FORMAT = "M/d/yyyy";
40
+ var DEFAULT_SLIDE_NUMBER_FORMAT = "{current}";
41
+ var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
42
+ var weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
43
+ var tokenWidths = { y: [1, 2, 4], M: [1, 2, 3, 4], d: [1, 2], E: [1, 2, 3, 4] };
44
+ function parseFurnitureDateFormat(pattern) {
45
+ if (!pattern) return "dateFormat must not be empty.";
46
+ const tokens = [];
47
+ const literal = (text) => {
48
+ const last = tokens[tokens.length - 1];
49
+ if (last?.kind === "literal") last.text += text;
50
+ else tokens.push({ kind: "literal", text });
51
+ };
52
+ for (let index = 0; index < pattern.length; ) {
53
+ const character = pattern.charAt(index);
54
+ if (character === "'") {
55
+ if (pattern[index + 1] === "'") {
56
+ literal("'");
57
+ index += 2;
58
+ continue;
59
+ }
60
+ let end = index + 1, text = "";
61
+ for (; ; ) {
62
+ if (end >= pattern.length) return "dateFormat has an unterminated quoted literal.";
63
+ if (pattern[end] === "'") {
64
+ if (pattern[end + 1] === "'") {
65
+ text += "'";
66
+ end += 2;
67
+ continue;
68
+ }
69
+ break;
70
+ }
71
+ text += pattern[end];
72
+ end++;
73
+ }
74
+ literal(text);
75
+ index = end + 1;
76
+ continue;
77
+ }
78
+ if (/[A-Za-z]/.test(character)) {
79
+ let end = index;
80
+ while (pattern[end] === character) end++;
81
+ const width = end - index;
82
+ if (!tokenWidths[character]?.includes(width)) return `dateFormat token '${pattern.slice(index, end)}' is not supported; use yyyy, yy, MMMM, MMM, MM, M, dd, d, EEEE or EEE, and quote literal letters.`;
83
+ tokens.push({ kind: "field", letter: character, width });
84
+ index = end;
85
+ continue;
86
+ }
87
+ literal(character);
88
+ index++;
89
+ }
90
+ return tokens;
91
+ }
92
+ function parseIsoDate(value) {
93
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
94
+ if (!match) return null;
95
+ const year = Number(match[1]), month = Number(match[2]), day = Number(match[3]);
96
+ const date = new Date(Date.UTC(2e3, month - 1, day));
97
+ date.setUTCFullYear(year);
98
+ if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) return null;
99
+ return { year, month, day };
100
+ }
101
+ function formatFurnitureDate(iso, pattern) {
102
+ const date = parseIsoDate(iso);
103
+ if (!date) return { error: `'${iso}' is not an ISO YYYY-MM-DD calendar date.` };
104
+ const tokens = parseFurnitureDateFormat(pattern);
105
+ if (typeof tokens === "string") return { error: tokens };
106
+ const utc = new Date(Date.UTC(2e3, date.month - 1, date.day));
107
+ utc.setUTCFullYear(date.year);
108
+ const pad = (value, width) => String(value).padStart(width, "0");
109
+ let text = "";
110
+ for (const token of tokens) {
111
+ if (token.kind === "literal") {
112
+ text += token.text;
113
+ continue;
114
+ }
115
+ const { letter, width } = token;
116
+ if (letter === "y") text += width === 2 ? pad(date.year % 100, 2) : width === 4 ? pad(date.year, 4) : String(date.year);
117
+ else if (letter === "M") {
118
+ const name = months[date.month - 1] ?? "";
119
+ text += width >= 3 ? width === 4 ? name : name.slice(0, 3) : pad(date.month, width);
120
+ } else if (letter === "d") text += pad(date.day, width);
121
+ else {
122
+ const name = weekdays[utc.getUTCDay()] ?? "";
123
+ text += width === 4 ? name : name.slice(0, 3);
124
+ }
125
+ }
126
+ return { text };
127
+ }
128
+ function formatSlideNumber(format, current, total) {
129
+ if (!format.includes("{current}")) return { error: "slideNumberFormat must contain {current}." };
130
+ if (format.includes("{total}") && total === void 0) return { error: "slideNumberFormat uses {total}, which needs the rendered slide count." };
131
+ let text = "";
132
+ const fields2 = [];
133
+ for (const piece of format.split(/(\{current\}|\{total\})/)) {
134
+ if (piece === "{current}") {
135
+ const value = String(current);
136
+ fields2.push({ type: "slideNumber", start: text.length, end: text.length + value.length });
137
+ text += value;
138
+ } else if (piece === "{total}") text += String(total);
139
+ else text += piece;
140
+ }
141
+ return { text, fields: fields2 };
142
+ }
143
+
38
144
  // src/color.ts
39
145
  var schemeColorSlots = /* @__PURE__ */ new Set([
40
146
  "accent1",
@@ -147,13 +253,25 @@ function chartColorForFill(fill, preferred) {
147
253
 
148
254
  // src/composition.ts
149
255
  var MAX_COMPOSITION_DEPTH = 32;
256
+ var FALLBACK_CODE_FAMILY = "Roboto Mono";
257
+ var DEFAULT_FONT_SCHEME = "aptos";
258
+ var DEFAULT_FONT_FAMILIES = { heading: "Aptos Display", body: "Aptos" };
259
+ function resolveFontSchemeReference(reference, lookup, path = "design.fontScheme") {
260
+ const overrides = typeof reference === "object" && reference !== null && !Array.isArray(reference) ? reference : void 0;
261
+ const id = typeof reference === "string" ? reference : typeof overrides?.id === "string" ? overrides.id : void 0;
262
+ const found = id === void 0 ? void 0 : lookup(id);
263
+ const base = record2(found ?? lookup(DEFAULT_FONT_SCHEME));
264
+ const scheme = overrides ? { ...base, ...overrides } : { ...base };
265
+ if (id === void 0 || found !== void 0) return { scheme };
266
+ return { scheme, diagnostic: { code: "unresolved-font-scheme", path, id, fallback: DEFAULT_FONT_SCHEME, message: `Font scheme '${id}' is not in the inline or bundled catalogs; using the default font scheme '${DEFAULT_FONT_SCHEME}'.` } };
267
+ }
150
268
  function resolveFontFamilies(input) {
151
269
  const scheme = record2(input);
152
270
  const family = (value) => typeof value === "string" ? value : record2(value).family;
153
271
  return {
154
- heading: family(scheme.heading) ?? scheme.major ?? scheme.minor ?? "Roboto",
155
- body: family(scheme.body) ?? scheme.minor ?? scheme.major ?? "Roboto",
156
- code: family(scheme.code) ?? "Roboto Mono"
272
+ heading: family(scheme.heading) ?? scheme.major ?? scheme.minor ?? DEFAULT_FONT_FAMILIES.heading,
273
+ body: family(scheme.body) ?? scheme.minor ?? scheme.major ?? DEFAULT_FONT_FAMILIES.body,
274
+ code: family(scheme.code) ?? FALLBACK_CODE_FAMILY
157
275
  };
158
276
  }
159
277
  function resolveTextStyle(style, measurement) {
@@ -219,6 +337,106 @@ var columns = ["left", "center", "right"];
219
337
  var record2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
220
338
  var kind = (field) => field === "items" ? "list" : field === "bullets" ? "text" : field;
221
339
  var round = (value) => Math.round(value * 1e6) / 1e6 || value;
340
+ var SLIDE_IMAGE_POSITIONS = ["background", "top", "bottom", "left", "right"];
341
+ var SLIDE_IMAGE_BAND = 0.5;
342
+ var assetSource = (value) => typeof value === "string" ? value : record2(value).src;
343
+ var finite = (value, min, max) => typeof value === "number" && Number.isFinite(value) && value >= min && value <= max ? value : void 0;
344
+ var pathNumber = (value) => String(round(value));
345
+ var SLIDE_IMAGE_SHAPES = { rectangle: "rect", rounded: "roundRect", circle: "ellipse", hexagon: "hexagon" };
346
+ function slideImageShape(kind2, box, cornerRadius = 1 / 6) {
347
+ const { x, y, width: w, height: h } = box, r = x + w, b = y + h, ss = Math.min(w, h), n = pathNumber;
348
+ const rect = `M${n(x)} ${n(y)}H${n(r)}V${n(b)}H${n(x)}Z`;
349
+ if (kind2 === "rounded") {
350
+ const adj = Math.round(Math.min(0.5, Math.max(0, cornerRadius)) * 1e5), radius = ss * adj / 1e5;
351
+ const path = radius <= 0 ? rect : `M${n(x)} ${n(y + radius)}A${n(radius)} ${n(radius)} 0 0 1 ${n(x + radius)} ${n(y)}H${n(r - radius)}A${n(radius)} ${n(radius)} 0 0 1 ${n(r)} ${n(y + radius)}V${n(b - radius)}A${n(radius)} ${n(radius)} 0 0 1 ${n(r - radius)} ${n(b)}H${n(x + radius)}A${n(radius)} ${n(radius)} 0 0 1 ${n(x)} ${n(b - radius)}Z`;
352
+ return { kind: kind2, preset: "roundRect", adjust: { adj }, path };
353
+ }
354
+ if (kind2 === "circle") {
355
+ const cy = y + h / 2;
356
+ return { kind: kind2, preset: "ellipse", adjust: {}, path: `M${n(x)} ${n(cy)}A${n(w / 2)} ${n(h / 2)} 0 1 1 ${n(r)} ${n(cy)}A${n(w / 2)} ${n(h / 2)} 0 1 1 ${n(x)} ${n(cy)}Z` };
357
+ }
358
+ if (kind2 === "hexagon") {
359
+ const adj = 25e3, vf = 115470, a = Math.min(Math.max(adj, 0), 5e4 * w / ss);
360
+ const x1 = ss * a / 1e5, x2 = r - x1, vc = y + h / 2, dy1 = h / 2 * vf / 1e5 * Math.sin(Math.PI / 3);
361
+ return { kind: kind2, preset: "hexagon", adjust: { adj, vf }, path: `M${n(x)} ${n(vc)}L${n(x + x1)} ${n(vc - dy1)}L${n(x2)} ${n(vc - dy1)}L${n(r)} ${n(vc)}L${n(x2)} ${n(vc + dy1)}L${n(x + x1)} ${n(vc + dy1)}Z` };
362
+ }
363
+ return { kind: "rectangle", preset: "rect", adjust: {}, path: rect };
364
+ }
365
+ function resolveSlideImage(slide, layout, presentation, width, height, path, padding, scale, diagnostics) {
366
+ const own = record2(slide.design), deck = record2(record2(presentation).design);
367
+ const local = own.slideImage !== void 0;
368
+ const configured = local ? own.slideImage : deck.slideImage;
369
+ if (!configured || typeof configured !== "object" && typeof configured !== "string") return void 0;
370
+ const treatment = typeof configured === "object" && !Array.isArray(configured) && "position" in configured ? record2(configured) : void 0;
371
+ const alignment = typeof layout.slideImageAlignment === "string" ? layout.slideImageAlignment.toLowerCase() : void 0;
372
+ const position = treatment ? treatment.position : SLIDE_IMAGE_POSITIONS.find((value2) => value2 === alignment) ?? "background";
373
+ if (!SLIDE_IMAGE_POSITIONS.includes(position)) return void 0;
374
+ const designSource = treatment ? treatment.src : configured;
375
+ const root = slide.image, sameSource = root !== void 0 && designSource !== void 0 && assetSource(root) === assetSource(designSource);
376
+ if (!local && layout.slideImage !== true && !sameSource) return void 0;
377
+ const replacesContent = root !== void 0 && (designSource === void 0 || sameSource);
378
+ const value = replacesContent ? root : designSource;
379
+ const source = assetSource(value);
380
+ if (typeof source !== "string" || !source) return void 0;
381
+ const t = treatment ?? {};
382
+ const fill = t.fill === "crop" || t.fill === "fit" ? t.fill : (own.imageFill ?? deck.imageFill) === "fit" ? "fit" : "crop";
383
+ const share = finite(t.size, 0.1, 0.9) ?? SLIDE_IMAGE_BAND;
384
+ const band = { width: round(width * share), height: round(height * share) };
385
+ const region = position === "left" ? { x: 0, y: 0, width: band.width, height } : position === "right" ? { x: round(width - band.width), y: 0, width: band.width, height } : position === "top" ? { x: 0, y: 0, width, height: band.height } : position === "bottom" ? { x: 0, y: round(height - band.height), width, height: band.height } : { x: 0, y: 0, width, height };
386
+ const designPath = local ? `${path}.design.slideImage` : "design.slideImage";
387
+ const kind2 = Object.hasOwn(SLIDE_IMAGE_SHAPES, t.shape) ? t.shape : "rectangle";
388
+ let box = t.inset === true ? { x: region.x + padding, y: region.y + padding, width: Math.max(scale, region.width - 2 * padding), height: Math.max(scale, region.height - 2 * padding) } : { ...region };
389
+ const aspect = kind2 === "circle" ? 1 : finite(t.aspectRatio, Number.MIN_VALUE, 10);
390
+ if (aspect) {
391
+ const frameWidth = Math.min(box.width, box.height * aspect), frameHeight = frameWidth / aspect;
392
+ box = { x: box.x + (box.width - frameWidth) / 2, y: box.y + (box.height - frameHeight) / 2, width: frameWidth, height: frameHeight };
393
+ }
394
+ box = { x: round(box.x), y: round(box.y), width: round(box.width), height: round(box.height) };
395
+ const result = {
396
+ path: designPath,
397
+ sourcePath: replacesContent ? `${path}.image` : designPath,
398
+ value,
399
+ position,
400
+ fill,
401
+ region,
402
+ box,
403
+ replacesContent,
404
+ shape: slideImageShape(kind2, box, finite(t.cornerRadius, 0, 0.5))
405
+ };
406
+ if (typeof t.alt === "string") result.alt = t.alt;
407
+ const border = record2(t.border), borderWidth = finite(border.width, 0, 64);
408
+ if (border.color !== void 0 && borderWidth) result.border = { color: border.color, width: round(borderWidth * scale) };
409
+ const opacity = finite(t.opacity, 0, 1);
410
+ if (opacity !== void 0 && opacity < 1) result.opacity = opacity;
411
+ if (t.recolor === "grayscale") result.recolor = { type: "grayscale" };
412
+ else if (record2(t.recolor).dark !== void 0 && record2(t.recolor).light !== void 0) result.recolor = { type: "duotone", dark: t.recolor.dark, light: t.recolor.light };
413
+ const overlay = record2(t.overlay), overlayOpacity = finite(overlay.opacity, 0, 1);
414
+ if (overlay.color !== void 0 && overlayOpacity !== void 0) {
415
+ const edge = ["top", "bottom", "left", "right"].includes(overlay.edge) ? overlay.edge : void 0;
416
+ if (edge && kind2 !== "rectangle") diagnostics.push({ code: "unsupported-image-treatment", path: `${designPath}.overlay.edge`, message: "An edge overlay needs a rectangle frame; a band cannot follow a rounded, circular or hexagonal mask as one native shape. Remove edge or use shape rectangle." });
417
+ else {
418
+ const part = finite(overlay.size, 0.05, 1) ?? 0.3;
419
+ const bandBox = !edge ? box : edge === "top" ? { ...box, height: round(box.height * part) } : edge === "bottom" ? { ...box, y: round(box.y + box.height * (1 - part)), height: round(box.height * part) } : edge === "left" ? { ...box, width: round(box.width * part) } : { ...box, x: round(box.x + box.width * (1 - part)), width: round(box.width * part) };
420
+ result.overlay = { color: overlay.color, opacity: overlayOpacity, box: bandBox, shape: edge ? slideImageShape("rectangle", bandBox) : result.shape };
421
+ }
422
+ }
423
+ return result;
424
+ }
425
+ var webUrl = /^https?:\/\/\S+$/i;
426
+ function resolveSocialProfile(platform, value, records = [], owner = "organization") {
427
+ const raw = String(value).trim().replace(/\s+/gu, " ");
428
+ const display = (url2) => url2.replace(/^https:\/\//i, "");
429
+ if (webUrl.test(raw)) return { text: display(raw), href: raw, resolved: false };
430
+ const platformRecord = records.find((item) => item?.id === platform);
431
+ const base = typeof platformRecord?.baseUrl === "string" && webUrl.test(platformRecord.baseUrl) ? `${platformRecord.baseUrl.replace(/\/+$/u, "")}/{handle}` : void 0;
432
+ const pattern = (owner === "organization" ? platformRecord?.companyUrlPattern : void 0) ?? platformRecord?.profileUrlPattern ?? base;
433
+ const prefix = platformRecord?.handlePrefix ?? "";
434
+ const handle = prefix && raw.startsWith(prefix) ? raw.slice(prefix.length) : raw;
435
+ if (!handle || typeof pattern !== "string" || !pattern.includes("{handle}")) return { text: raw, resolved: false };
436
+ const url = pattern.split("{handle}").join(handle);
437
+ if (!webUrl.test(url)) return { text: raw, resolved: false };
438
+ return { text: display(url), href: encodeURI(url), resolved: true };
439
+ }
222
440
  function layoutFurniture(input, options = {}) {
223
441
  const slide = record2(input), width = options.width ?? 1280, height = options.height ?? 720, scale = Math.min(width, height) / 720;
224
442
  const settings = { ...record2(record2(options.layout).composition), ...record2(slide.composition) };
@@ -227,10 +445,14 @@ function layoutFurniture(input, options = {}) {
227
445
  if (![width, height, scale, minimum, padding].every(Number.isFinite) || width <= 0 || height <= 0 || minimum <= 0 || padding < 0) throw new RangeError("Furniture requires finite positive dimensions and nonnegative raster padding.");
228
446
  const number = options.slideNumber ?? (options.slideIndex ?? 0) + 1;
229
447
  if (!Number.isSafeInteger(number) || number < 1) throw new RangeError("Displayed slide number must be a positive safe integer.");
448
+ const presentationSlides = options.presentation?.slides, slideCount = options.slideCount ?? (Array.isArray(presentationSlides) && presentationSlides.length ? presentationSlides.length : void 0);
449
+ if (slideCount !== void 0 && (!Number.isSafeInteger(slideCount) || slideCount < 1)) throw new RangeError("Displayed slide count must be a positive safe integer.");
450
+ if (options.date !== void 0 && (typeof options.date !== "string" || !parseIsoDate(options.date))) throw new RangeError("The furniture date option must be an ISO YYYY-MM-DD calendar date.");
230
451
  const outlines = options.textMeasurement?.outlineBounds !== void 0, parts = [], diagnostics = [];
231
452
  const sourceRoot = `slides.${options.slideIndex ?? 0}`, organizations = Array.isArray(options.presentation?.organization) ? options.presentation.organization : [options.presentation?.organization];
232
453
  const primaryIndex = organizations.findIndex((item) => record2(item).role === "primary"), organizationIndex = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
233
- const organization = record2(organizations[organizationIndex]), organizationPath = Array.isArray(options.presentation?.organization) ? `organization.${organizationIndex}.name` : "organization.name";
454
+ const organization = record2(organizations[organizationIndex]), organizationRoot = Array.isArray(options.presentation?.organization) ? `organization.${organizationIndex}` : "organization", organizationPath = `${organizationRoot}.name`;
455
+ const inlinePlatforms = record2(record2(options.presentation?.catalogs).socialPlatforms).records, platformRecords = [...Array.isArray(inlinePlatforms) ? inlinePlatforms : [], ...options.socialPlatforms ?? []];
234
456
  const fontFamily = options.fonts?.body ?? "sans-serif";
235
457
  let headerBottom = 0, footerTop = height, configured = false;
236
458
  const error = (path, message, code = "text-overflow") => diagnostics.push({ code, path, message });
@@ -244,7 +466,7 @@ function layoutFurniture(input, options = {}) {
244
466
  for (const [index, zone] of ["left", "center", "right"].entries()) {
245
467
  const content = record2(record2(value)[zone]), path = `${root}.${zone}`, x = width * (0.07 + index * 0.3), zoneWidth = width * 0.26, zoneParts = [];
246
468
  let y = 0;
247
- const add = (field, text, generated = false, sourcePath) => {
469
+ const add = (field, text, generated = false, sourcePath, extras = {}) => {
248
470
  if (text === void 0) return;
249
471
  if (typeof text !== "string") throw new TypeError(`Furniture field ${path}.${field} requires string content.`);
250
472
  const partPath = `${path}.${field}`, style = resolveTextStyle({ fontFamily, fontWeight: 400, italic: false, path: partPath }, options.textMeasurement);
@@ -274,7 +496,7 @@ function layoutFurniture(input, options = {}) {
274
496
  if (!Number.isFinite(partHeight)) throw new RangeError("Furniture exceeds finite layout coordinates.");
275
497
  const box = { x, y, width: zoneWidth, height: Math.max(scale, partHeight) }, placement = outlines ? placeTextLines(ink, box, zone, padding) : void 0;
276
498
  const accepted = { ...fit, ...placement ? { placement } : {}, overflow: fit.overflow || !!placement?.overflow };
277
- zoneParts.push({ type: "text", kind: kind2, zone, field, path: partPath, sourcePath: sourcePath ?? (!generated ? partPath : void 0), generated, text, style, requestedFontSize: 13 * scale, minFontSize: minimum, box, alignment: zone, fit: accepted });
499
+ zoneParts.push({ type: "text", kind: kind2, zone, field, path: partPath, sourcePath: sourcePath ?? (!generated ? partPath : void 0), generated, text, style, requestedFontSize: 13 * scale, minFontSize: minimum, box, alignment: zone, fit: accepted, ...extras.fields?.length ? { fields: extras.fields } : {}, ...extras.links?.length ? { links: extras.links } : {} });
278
500
  if (accepted.overflow) error(partPath, "Repeated text exceeds its zone at the selected readability floor; change the furniture or slide design.");
279
501
  y += box.height;
280
502
  };
@@ -288,13 +510,37 @@ function layoutFurniture(input, options = {}) {
288
510
  if (typeof organization.name === "string") add("organization", organization.name, true, organizationPath);
289
511
  else error(`${path}.organization`, "Generated organization name needs a named organization in the presentation.", "unresolved-content");
290
512
  }
513
+ if (content.socials === true) {
514
+ const links = Object.entries(record2(organization.socials)).filter(([, value2]) => typeof value2 === "string" && value2.trim()).map(([platform, value2]) => ({ platform, sourcePath: `${organizationRoot}.socials.${platform}`, ...resolveSocialProfile(platform, value2, platformRecords, "organization") }));
515
+ if (links.length) add("socials", links.map((link) => link.text).join("\n"), true, `${organizationRoot}.socials`, { links });
516
+ else error(`${path}.socials`, "Generated social profiles need a primary organization with socials.", "unresolved-content");
517
+ }
291
518
  if (content.section === true) {
292
519
  if (typeof slide.section === "string") add("section", slide.section, true, `${sourceRoot}.section`);
293
520
  else error(`${path}.section`, "Generated section needs a literal slide section.", "unresolved-content");
294
521
  }
295
- if (content.slideNumber === true) add("slideNumber", String(number), true);
296
- if (content.date === true) error(`${path}.date`, "Use a literal date string for deterministic header/footer content.", "unresolved-content");
297
- else if (typeof content.date === "string") add("date", content.date);
522
+ for (const setting of ["slideNumberFormat", "dateFormat"]) if (content[setting] !== void 0 && typeof content[setting] !== "string") throw new TypeError(`Furniture setting ${path}.${setting} requires a string.`);
523
+ if (content.slideNumber === true) {
524
+ const resolved = formatSlideNumber(content.slideNumberFormat ?? DEFAULT_SLIDE_NUMBER_FORMAT, number, slideCount);
525
+ if ("error" in resolved) error(`${path}.slideNumberFormat`, resolved.error, "unresolved-content");
526
+ else add("slideNumber", resolved.text, true, void 0, { fields: resolved.fields });
527
+ }
528
+ if (content.date === true) {
529
+ const format = content.dateFormat ?? DEFAULT_FURNITURE_DATE_FORMAT;
530
+ if (options.date === void 0) error(`${path}.date`, "A current date needs a host-supplied ISO date option. For fixed content use a literal date, or an ISO date with dateFormat.", "unresolved-content");
531
+ else {
532
+ const resolved = formatFurnitureDate(options.date, format);
533
+ if ("error" in resolved) error(`${path}.dateFormat`, resolved.error, "unresolved-content");
534
+ else add("date", resolved.text, true, void 0, { fields: [{ type: "date", start: 0, end: resolved.text.length, format }] });
535
+ }
536
+ } else if (typeof content.date === "string") {
537
+ if (content.dateFormat === void 0) add("date", content.date);
538
+ else {
539
+ const resolved = formatFurnitureDate(content.date, content.dateFormat);
540
+ if ("error" in resolved) error(`${path}.${parseIsoDate(content.date) ? "dateFormat" : "date"}`, resolved.error, "unresolved-content");
541
+ else add("date", resolved.text, true, `${path}.date`);
542
+ }
543
+ }
298
544
  zones.push(zoneParts);
299
545
  }
300
546
  const tallest = Math.max(0, ...zones.map((zone) => zone.reduce((sum, part) => sum + part.box.height, 0)));
@@ -931,9 +1177,24 @@ function richTextLayouter(input, box, requestedSize, options) {
931
1177
  const text = whole.slice(a, b), normalSize = (entry.run.fontSize !== void 0 ? entry.run.fontSize * 4 / 3 : requestedSize) * ratio;
932
1178
  const script = entry.run.superscript || entry.run.subscript;
933
1179
  const size = normalSize * (script ? 0.7 : 1), baselineShift = entry.run.superscript ? -normalSize * 0.35 : entry.run.subscript ? normalSize * 0.2 : 0;
934
- const width2 = textWidthMeasurer(entry.style, options.textMeasurement)(text, size);
935
- result.push({ text, runIndex: entry.runIndex, start: a - entry.start, end: b - entry.start, x, width: width2, fontSize: size, baselineShift, style: entry.style, run: entry.run });
936
- x += width2;
1180
+ const measure = textWidthMeasurer(entry.style, options.textMeasurement);
1181
+ let cursor = 0;
1182
+ for (const [index, segment] of text.split(" ").entries()) {
1183
+ if (index) {
1184
+ const tabStart = a + cursor, tabWidth = measure(" ", size) * 4;
1185
+ if (!Number.isFinite(tabWidth) || tabWidth <= 0) throw new RangeError("Rich text tabs require a positive finite measured space advance.");
1186
+ const next = (Math.floor(x / tabWidth + 1e-9) + 1) * tabWidth;
1187
+ result.push({ kind: "tab", text: " ", runIndex: entry.runIndex, start: tabStart - entry.start, end: tabStart - entry.start + 1, x, width: next - x, fontSize: size, baselineShift, style: entry.style, run: entry.run });
1188
+ x = next;
1189
+ cursor++;
1190
+ }
1191
+ if (segment) {
1192
+ const width2 = measure(segment, size), segmentStart = a + cursor;
1193
+ result.push({ text: segment, runIndex: entry.runIndex, start: segmentStart - entry.start, end: segmentStart - entry.start + segment.length, x, width: width2, fontSize: size, baselineShift, style: entry.style, run: entry.run });
1194
+ x += width2;
1195
+ cursor += segment.length;
1196
+ }
1197
+ }
937
1198
  }
938
1199
  return result;
939
1200
  };
@@ -1188,6 +1449,7 @@ function composeSlide(input, options = {}) {
1188
1449
  const minSize = (composition.minFontSize ?? 16) * scale;
1189
1450
  const rasterPadding = (options.textRasterPadding ?? 1) * scale;
1190
1451
  if (!Number.isFinite(rasterPadding) || rasterPadding < 0) throw new RangeError("Text raster padding must be finite and nonnegative.");
1452
+ const alignmentFor = (field) => (field === "title" ? record2(slide.design).titleAlignment ?? options.titleAlignment : record2(slide.design).contentAlignment ?? options.contentAlignment) ?? "left";
1191
1453
  const styleFor = (field, path2) => resolveTextStyle({
1192
1454
  fontFamily: (field === "title" ? options.fonts?.heading : field === "code" ? options.fonts?.code : options.fonts?.body) ?? (field === "code" ? "monospace" : "sans-serif"),
1193
1455
  fontWeight: field === "title" ? 700 : 400,
@@ -1197,7 +1459,7 @@ function composeSlide(input, options = {}) {
1197
1459
  const fitPlacedText = (field, value, text, box, size, minimum, path2) => {
1198
1460
  const style = styleFor(field, path2), rich = field === "text" && Array.isArray(value);
1199
1461
  if (!options.textMeasurement?.outlineBounds) return rich ? fitRichText(value, box, size, minimum, { style, textMeasurement: options.textMeasurement }) : fitText(text, box, size, minimum, textWidthMeasurer(style, options.textMeasurement));
1200
- const alignment = (field === "title" ? record2(slide.design).titleAlignment ?? options.titleAlignment : record2(slide.design).contentAlignment ?? options.contentAlignment) ?? "left";
1462
+ const alignment = alignmentFor(field);
1201
1463
  const richLayout = rich ? richTextLayouter(value, box, size, { style, textMeasurement: options.textMeasurement }) : void 0;
1202
1464
  const measure = textWidthMeasurer(style, options.textMeasurement), floor = rich ? richMinimum(value, size, minimum) : minimum, start = Math.max(size, floor);
1203
1465
  for (let trial = 0; trial <= 64; trial++) {
@@ -1207,6 +1469,7 @@ function composeSlide(input, options = {}) {
1207
1469
  const lines = richLines ? richLines.map((line) => {
1208
1470
  let outline = null;
1209
1471
  for (const fragment of line.fragments) {
1472
+ if (fragment.kind === "tab") continue;
1210
1473
  const bounds = measureTextOutline(fragment.text, fragment.fontSize, fragment.style, options.textMeasurement);
1211
1474
  if (!bounds) continue;
1212
1475
  const next = { ...bounds, x: fragment.x + bounds.x, y: fragment.baselineShift + bounds.y };
@@ -1245,25 +1508,32 @@ function composeSlide(input, options = {}) {
1245
1508
  };
1246
1509
  const fitContent = (field, value, text, box, size, minimum, path2) => field === "text" ? fitPlacedText(field, value, text, box, size, minimum, path2) : field === "items" || field === "bullets" ? fitList(value, box, size, minimum, { style: styleFor(field, path2), textMeasurement: options.textMeasurement }) : fitText(text, box, size, minimum, widthFor(field, path2));
1247
1510
  const path = `slides.${options.slideIndex ?? 0}`;
1511
+ const slideImageDiagnostics = [];
1512
+ const slideImage = resolveSlideImage(slide, layout, options.presentation, width, height, path, padding, scale, slideImageDiagnostics);
1513
+ const area = { left: 0, top: 0, right: width, bottom: height };
1514
+ if (slideImage?.position === "left") area.left = slideImage.region.width;
1515
+ else if (slideImage?.position === "right") area.right = slideImage.region.x;
1516
+ else if (slideImage?.position === "top") area.top = slideImage.region.height;
1517
+ else if (slideImage?.position === "bottom") area.bottom = slideImage.region.y;
1248
1518
  const items = [], diagnostics = [];
1249
1519
  const measuredFurniture = layoutFurniture(slide, options);
1250
1520
  const furniture = measuredFurniture.configured || measuredFurniture.diagnostics.length ? measuredFurniture : void 0;
1251
1521
  if (furniture) diagnostics.push(...furniture.diagnostics);
1252
- const bodyBottom = Math.min(height - padding, furniture && furniture.footerTop < height ? furniture.footerTop - gap * 0.5 : height - padding);
1253
- let y = Math.max(padding, furniture?.headerBottom ? furniture.headerBottom + gap * 0.5 : padding);
1522
+ const bodyBottom = Math.min(area.bottom - padding, furniture && furniture.footerTop < height ? furniture.footerTop - gap * 0.5 : height - padding);
1523
+ let y = Math.max(area.top + padding, furniture?.headerBottom ? furniture.headerBottom + gap * 0.5 : padding);
1254
1524
  for (const field of ["tag", "title", "subtitle"]) {
1255
1525
  if (!slide[field]) continue;
1256
1526
  const requested = (field === "title" ? 54 : field === "tag" ? 16 : 25) * scale;
1257
1527
  const maxHeight = Math.max(height * (field === "title" ? 0.26 : field === "subtitle" ? 0.12 : 0.045), minSize * 1.22 + 2 * rasterPadding);
1258
- const box = { x: padding, y, width: width - padding * 2, height: maxHeight };
1528
+ const box = { x: area.left + padding, y, width: area.right - area.left - padding * 2, height: maxHeight };
1259
1529
  const text = fitPlacedText(field, slide[field], String(slide[field]), box, requested, minSize, `${path}.${field}`);
1260
1530
  box.height = Math.min(maxHeight, Math.max(text.lines.length * text.lineHeight, text.placement?.height ?? 0));
1261
1531
  if (furniture && box.y + box.height > bodyBottom + 0.01) diagnostics.push({ code: "text-overflow", path: `${path}.${field}`, message: "Repeated furniture leaves too little room for this heading. Change the header/footer or slide design." });
1262
- items.push({ path: `${path}.${field}`, field, type: "text", value: slide[field], payload: { text: slide[field] }, box, text, textStyle: styleFor(field, `${path}.${field}`), composition });
1532
+ items.push({ path: `${path}.${field}`, field, type: "text", value: slide[field], payload: { text: slide[field] }, box, text, textStyle: styleFor(field, `${path}.${field}`), composition, alignment: alignmentFor(field) });
1263
1533
  y += box.height + gap * 0.5;
1264
1534
  }
1265
1535
  if (items.length) y += gap * 0.5;
1266
- const contentBox = { x: padding, y, width: width - padding * 2, height: Math.max(scale, bodyBottom - y) };
1536
+ const contentBox = { x: area.left + padding, y, width: area.right - area.left - padding * 2, height: Math.max(scale, bodyBottom - y) };
1267
1537
  const collect = (host, basePath, depth = 0, ancestors = []) => {
1268
1538
  if (Array.isArray(host.blocks)) {
1269
1539
  if (depth >= MAX_COMPOSITION_DEPTH || ancestors.includes(host)) throw new RangeError(`Content groups must be acyclic and nest at most ${MAX_COMPOSITION_DEPTH} levels.`);
@@ -1282,7 +1552,7 @@ function composeSlide(input, options = {}) {
1282
1552
  return fields.filter((field) => host[field] !== void 0).map((field) => ({ field, type: host.type ?? kind(field), value: host[field], path: `${basePath}.${field}`, payload: { type: host.type ?? kind(field), [field]: host[field] } }));
1283
1553
  };
1284
1554
  const regions = Object.keys(slide).filter((key) => regionParts(key)).sort();
1285
- const pending = regions.length ? regions.flatMap((key) => collect(record2(slide[key]), `${path}.${key}`).map((item) => ({ ...item, region: regionParts(key) }))) : Array.isArray(slide.blocks) ? slide.blocks.flatMap((block, index) => collect(record2(block), `${path}.blocks.${index}`)) : collect(slide, path);
1555
+ const pending = regions.length ? regions.flatMap((key) => collect(record2(slide[key]), `${path}.${key}`).map((item) => ({ ...item, region: regionParts(key) }))) : Array.isArray(slide.blocks) ? slide.blocks.flatMap((block, index) => collect(record2(block), `${path}.blocks.${index}`)) : collect(slideImage?.replacesContent ? { ...slide, image: void 0 } : slide, path);
1286
1556
  const groups = [], flows = [];
1287
1557
  const decisions = options.explain ? [] : void 0;
1288
1558
  const inheritedSettings = (parent, own = {}) => ({
@@ -1321,7 +1591,7 @@ function composeSlide(input, options = {}) {
1321
1591
  minFontSize: settings.minFontSize,
1322
1592
  path: node.path,
1323
1593
  textRasterPadding: options.textRasterPadding,
1324
- align: record2(slide.design).contentAlignment ?? options.contentAlignment
1594
+ align: alignmentFor("metric")
1325
1595
  });
1326
1596
  const measureTimeline = (node, box, settings) => layoutTimeline(node.value, acceptedBox(box), {
1327
1597
  fonts: options.fonts,
@@ -1364,29 +1634,29 @@ function composeSlide(input, options = {}) {
1364
1634
  return score;
1365
1635
  };
1366
1636
  const modeFor = (settings) => settings.mode ?? "auto";
1367
- const defaultColumns = (count, area, settings) => {
1637
+ const defaultColumns = (count, area2, settings) => {
1368
1638
  const mode = modeFor(settings);
1369
- return mode === "column" ? 1 : mode === "row" ? Math.max(1, count) : settings.columns ?? Math.max(1, Math.min(count, Math.ceil(Math.sqrt(count * area.width / area.height / 1.6))));
1639
+ return mode === "column" ? 1 : mode === "row" ? Math.max(1, count) : settings.columns ?? Math.max(1, Math.min(count, Math.ceil(Math.sqrt(count * area2.width / area2.height / 1.6))));
1370
1640
  };
1371
1641
  const scoreNode = (node, box, settings, penalties) => {
1372
1642
  if (!node.children) return leafScore(node, box, settings, penalties);
1373
- const own = inheritedSettings(settings, node.composition), area = inset(box, own);
1374
- const cols = defaultColumns(node.children.length, area, own);
1375
- const boxes = gridBoxes(node.children.length, area, cols, (own.gap ?? 1 / 30) * Math.min(box.width, box.height), own.weights ?? [], modeFor(own) === "column");
1643
+ const own = inheritedSettings(settings, node.composition), area2 = inset(box, own);
1644
+ const cols = defaultColumns(node.children.length, area2, own);
1645
+ const boxes = gridBoxes(node.children.length, area2, cols, (own.gap ?? 1 / 30) * Math.min(box.width, box.height), own.weights ?? [], modeFor(own) === "column");
1376
1646
  return node.children.reduce((score, child, index) => score + scoreNode(child, boxes[index], own, penalties), 0);
1377
1647
  };
1378
- const arrange = (nodes, area, settings, reserved = 0, gapOverride, containerPath = path) => {
1648
+ const arrange = (nodes, area2, settings, reserved = 0, gapOverride, containerPath = path) => {
1379
1649
  const count = Math.max(nodes.length, reserved);
1380
1650
  if (!count) return;
1381
- const mode = modeFor(settings), localGap = (settings.gap ?? 1 / 30) * Math.min(area.width, area.height);
1651
+ const mode = modeFor(settings), localGap = (settings.gap ?? 1 / 30) * Math.min(area2.width, area2.height);
1382
1652
  const actualGap = gapOverride ?? (settings === rootSettings ? gap : localGap);
1383
- let cols = defaultColumns(count, area, settings);
1653
+ let cols = defaultColumns(count, area2, settings);
1384
1654
  const hasRegions = nodes.some((node) => node.region);
1385
1655
  const candidates = decisions ? [] : void 0;
1386
1656
  if (mode === "auto" && !hasRegions) {
1387
1657
  let best = Infinity;
1388
1658
  for (let candidate = 1; candidate <= Math.min(count, settings.columns ?? 6); candidate++) {
1389
- const boxes2 = gridBoxes(count, area, candidate, actualGap, settings.weights ?? [], false);
1659
+ const boxes2 = gridBoxes(count, area2, candidate, actualGap, settings.weights ?? [], false);
1390
1660
  const emptySlots = (Math.ceil(count / candidate) * candidate - count) * 2;
1391
1661
  const penalties = candidates ? { cellProportions: 0, fontReduction: 0, textOverflow: 0, tableOverflow: 0, smallCells: 0, emptySlots } : void 0;
1392
1662
  const score = nodes.reduce((sum, node, i) => sum + scoreNode(node, boxes2[i], settings, penalties), 0) + emptySlots;
@@ -1404,11 +1674,11 @@ function composeSlide(input, options = {}) {
1404
1674
  ...hasRegions ? {} : { selectedColumns: cols },
1405
1675
  candidates: candidates ?? []
1406
1676
  });
1407
- const grid = gridGeometry(count, area, cols, actualGap, settings.weights ?? [], mode === "column");
1677
+ const grid = gridGeometry(count, area2, cols, actualGap, settings.weights ?? [], mode === "column");
1408
1678
  const boxes = grid.boxes;
1409
- if (!nodes.some((node) => node.region)) flows.push({ path: containerPath, box: { ...area }, composition: { ...settings }, columns: grid.columns, rows: grid.rows, gap: grid.gap, itemCount: nodes.length, slotCount: count });
1679
+ if (!nodes.some((node) => node.region)) flows.push({ path: containerPath, box: { ...area2 }, composition: { ...settings }, columns: grid.columns, rows: grid.rows, gap: grid.gap, itemCount: nodes.length, slotCount: count });
1410
1680
  nodes.forEach((node, index) => {
1411
- let box = node.region ? regionBox(node.region, area, actualGap) : boxes[index];
1681
+ let box = node.region ? regionBox(node.region, area2, actualGap) : boxes[index];
1412
1682
  if (node.children) {
1413
1683
  const own = inheritedSettings(settings, node.composition), inner = inset(box, own);
1414
1684
  groups.push({ path: node.path, box, contentBox: inner, composition: own });
@@ -1434,6 +1704,7 @@ function composeSlide(input, options = {}) {
1434
1704
  text,
1435
1705
  textStyle: body?.style ?? styleFor(node.field, node.path),
1436
1706
  composition: settings,
1707
+ alignment: alignmentFor(node.field),
1437
1708
  ...quoteLayout ? { quoteLayout } : {},
1438
1709
  ...codeLayout ? { codeLayout } : {},
1439
1710
  ...metricLayout ? { metricLayout } : {},
@@ -1444,6 +1715,10 @@ function composeSlide(input, options = {}) {
1444
1715
  });
1445
1716
  };
1446
1717
  const placeholders = Array.isArray(layout.placeholders) ? layout.placeholders.filter((p) => !headings.has(p.type)) : [];
1718
+ if (slideImage?.replacesContent) {
1719
+ const picture = placeholders.findIndex((p) => p.type === "picture");
1720
+ if (picture >= 0) placeholders.splice(picture, 1);
1721
+ }
1447
1722
  const rootSettings = { ...composition, mode: composition.mode ?? (layout.slideLayoutDirection === "Vertical" ? "column" : layout.slideLayoutDirection === "Horizontal" ? "row" : "auto") };
1448
1723
  arrange(pending, contentBox, rootSettings, composition.mode ? 0 : placeholders.length);
1449
1724
  for (const item of items) {
@@ -1477,7 +1752,8 @@ function composeSlide(input, options = {}) {
1477
1752
  unmeasuredPayloads: items.filter((item) => !headings.has(item.field) && !item.quoteLayout && !item.codeLayout && !item.metricLayout && !item.timelineLayout && !["text", "items", "bullets", "table"].includes(item.field)).map((item) => item.path)
1478
1753
  } : void 0;
1479
1754
  if (failures.length) throw new OPFCompositionError(failures, explanation);
1480
- return { width, height, contentBox, items, groups, flows, diagnostics, composition, ...furniture ? { furniture } : {}, ...explanation ? { explanation } : {} };
1755
+ diagnostics.push(...slideImageDiagnostics);
1756
+ return { width, height, contentBox, items, groups, flows, diagnostics, composition, ...furniture ? { furniture } : {}, ...slideImage ? { slideImage } : {}, ...explanation ? { explanation } : {} };
1481
1757
  }
1482
1758
  function resolveCanvasDimensions(input) {
1483
1759
  const presets = {
@@ -1497,4 +1773,4 @@ function resolveCanvasDimensions(input) {
1497
1773
  return { width, height };
1498
1774
  }
1499
1775
 
1500
- export { MAX_COMPOSITION_DEPTH, OPFCompositionError, chartColorForFill, colorContrast, composeSlide, fitList, fitRichText, fitText, layoutCode, layoutFurniture, layoutMetric, layoutQuote, layoutTable, layoutTimeline, measureText, measureTextOutline, normalizeHexColor, placeTextLines, resolveCanvasDimensions, resolveColorRef, resolveFontFamilies, resolveTextStyle, tableGrid, tableRowBoundaries, textColorForFill, textWidthMeasurer, wrapText };
1776
+ export { DEFAULT_FONT_SCHEME, DEFAULT_FURNITURE_DATE_FORMAT, DEFAULT_SLIDE_NUMBER_FORMAT, MAX_COMPOSITION_DEPTH, OPFCompositionError, chartColorForFill, colorContrast, composeSlide, fitList, fitRichText, fitText, formatFurnitureDate, formatSlideNumber, layoutCode, layoutFurniture, layoutMetric, layoutQuote, layoutTable, layoutTimeline, measureText, measureTextOutline, normalizeHexColor, placeTextLines, resolveCanvasDimensions, resolveColorRef, resolveFontFamilies, resolveFontSchemeReference, resolveSocialProfile, resolveTextStyle, slideImageShape, tableGrid, tableRowBoundaries, textColorForFill, textWidthMeasurer, wrapText };