@openpresentation/opf 0.11.0 → 0.11.2

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-6FU2KICU.js → chunk-7LF37SG3.js} +7084 -5106
  5. package/dist/{chunk-FTWNWFIV.js → chunk-BVLWFMDX.js} +348 -36
  6. package/dist/{chunk-CHKJ4QHQ.js → chunk-DJRZMGKY.js} +113 -32
  7. package/dist/{chunk-EQHZ3ZO4.js → chunk-K7IWYODS.js} +3 -3
  8. package/dist/chunk-KZ6BT5UG.js +3591 -0
  9. package/dist/{chunk-GN52EE3P.js → chunk-LTSDDBNW.js} +425 -40
  10. package/dist/{chunk-RS5GZ5RW.js → chunk-SDETQV7Y.js} +161 -0
  11. package/dist/{chunk-FWA5XB45.js → chunk-SMYEM3HV.js} +45 -25
  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 +127 -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 +3510 -0
  230. package/dist/spec/reference/font-policy.schema.json +338 -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) {
@@ -201,6 +319,21 @@ function placeTextLines(lines, box, alignment = "left", rasterPadding = 0) {
201
319
  }
202
320
  return { alignment, rasterPadding, lines: placed, height, overflow };
203
321
  }
322
+ function translateTextFit(fit, dy) {
323
+ if (!dy || !fit.placement) return fit;
324
+ return {
325
+ ...fit,
326
+ placement: {
327
+ ...fit.placement,
328
+ lines: fit.placement.lines.map((line) => ({
329
+ ...line,
330
+ y: line.y + dy,
331
+ baseline: line.baseline + dy,
332
+ outline: line.outline ? { ...line.outline, y: line.outline.y + dy } : line.outline
333
+ }))
334
+ }
335
+ };
336
+ }
204
337
  var OPFCompositionError = class extends Error {
205
338
  constructor(diagnostics, explanation) {
206
339
  super("Slide content does not fit its composition.");
@@ -214,11 +347,112 @@ var OPFCompositionError = class extends Error {
214
347
  };
215
348
  var fields = ["text", "items", "bullets", "image", "video", "chart", "table", "code", "metric", "quote", "timeline"];
216
349
  var headings = /* @__PURE__ */ new Set(["title", "subtitle", "tag"]);
350
+ var COVER_LAYOUT_IDS = /* @__PURE__ */ new Set(["title", "title-subtitle"]);
217
351
  var rows = ["top", "middle", "bottom"];
218
352
  var columns = ["left", "center", "right"];
219
353
  var record2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
220
354
  var kind = (field) => field === "items" ? "list" : field === "bullets" ? "text" : field;
221
355
  var round = (value) => Math.round(value * 1e6) / 1e6 || value;
356
+ var SLIDE_IMAGE_POSITIONS = ["background", "top", "bottom", "left", "right"];
357
+ var SLIDE_IMAGE_BAND = 0.5;
358
+ var assetSource = (value) => typeof value === "string" ? value : record2(value).src;
359
+ var finite = (value, min, max) => typeof value === "number" && Number.isFinite(value) && value >= min && value <= max ? value : void 0;
360
+ var pathNumber = (value) => String(round(value));
361
+ var SLIDE_IMAGE_SHAPES = { rectangle: "rect", rounded: "roundRect", circle: "ellipse", hexagon: "hexagon" };
362
+ function slideImageShape(kind2, box, cornerRadius = 1 / 6) {
363
+ const { x, y, width: w, height: h } = box, r = x + w, b = y + h, ss = Math.min(w, h), n = pathNumber;
364
+ const rect = `M${n(x)} ${n(y)}H${n(r)}V${n(b)}H${n(x)}Z`;
365
+ if (kind2 === "rounded") {
366
+ const adj = Math.round(Math.min(0.5, Math.max(0, cornerRadius)) * 1e5), radius = ss * adj / 1e5;
367
+ 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`;
368
+ return { kind: kind2, preset: "roundRect", adjust: { adj }, path };
369
+ }
370
+ if (kind2 === "circle") {
371
+ const cy = y + h / 2;
372
+ 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` };
373
+ }
374
+ if (kind2 === "hexagon") {
375
+ const adj = 25e3, vf = 115470, a = Math.min(Math.max(adj, 0), 5e4 * w / ss);
376
+ const x1 = ss * a / 1e5, x2 = r - x1, vc = y + h / 2, dy1 = h / 2 * vf / 1e5 * Math.sin(Math.PI / 3);
377
+ 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` };
378
+ }
379
+ return { kind: "rectangle", preset: "rect", adjust: {}, path: rect };
380
+ }
381
+ function resolveSlideImage(slide, layout, presentation, width, height, path, padding, scale, diagnostics) {
382
+ const own = record2(slide.design), deck = record2(record2(presentation).design);
383
+ const local = own.slideImage !== void 0;
384
+ const configured = local ? own.slideImage : deck.slideImage;
385
+ if (!configured || typeof configured !== "object" && typeof configured !== "string") return void 0;
386
+ const treatment = typeof configured === "object" && !Array.isArray(configured) && "position" in configured ? record2(configured) : void 0;
387
+ const alignment = typeof layout.slideImageAlignment === "string" ? layout.slideImageAlignment.toLowerCase() : void 0;
388
+ const position = treatment ? treatment.position : SLIDE_IMAGE_POSITIONS.find((value2) => value2 === alignment) ?? "background";
389
+ if (!SLIDE_IMAGE_POSITIONS.includes(position)) return void 0;
390
+ const designSource = treatment ? treatment.src : configured;
391
+ const root = slide.image, sameSource = root !== void 0 && designSource !== void 0 && assetSource(root) === assetSource(designSource);
392
+ if (!local && layout.slideImage !== true && !sameSource) return void 0;
393
+ const replacesContent = root !== void 0 && (designSource === void 0 || sameSource);
394
+ const value = replacesContent ? root : designSource;
395
+ const source = assetSource(value);
396
+ if (typeof source !== "string" || !source) return void 0;
397
+ const t = treatment ?? {};
398
+ const fill = t.fill === "crop" || t.fill === "fit" ? t.fill : (own.imageFill ?? deck.imageFill) === "fit" ? "fit" : "crop";
399
+ const share = finite(t.size, 0.1, 0.9) ?? SLIDE_IMAGE_BAND;
400
+ const band = { width: round(width * share), height: round(height * share) };
401
+ 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 };
402
+ const designPath = local ? `${path}.design.slideImage` : "design.slideImage";
403
+ const kind2 = Object.hasOwn(SLIDE_IMAGE_SHAPES, t.shape) ? t.shape : "rectangle";
404
+ 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 };
405
+ const aspect = kind2 === "circle" ? 1 : finite(t.aspectRatio, Number.MIN_VALUE, 10);
406
+ if (aspect) {
407
+ const frameWidth = Math.min(box.width, box.height * aspect), frameHeight = frameWidth / aspect;
408
+ box = { x: box.x + (box.width - frameWidth) / 2, y: box.y + (box.height - frameHeight) / 2, width: frameWidth, height: frameHeight };
409
+ }
410
+ box = { x: round(box.x), y: round(box.y), width: round(box.width), height: round(box.height) };
411
+ const result = {
412
+ path: designPath,
413
+ sourcePath: replacesContent ? `${path}.image` : designPath,
414
+ value,
415
+ position,
416
+ fill,
417
+ region,
418
+ box,
419
+ replacesContent,
420
+ shape: slideImageShape(kind2, box, finite(t.cornerRadius, 0, 0.5))
421
+ };
422
+ if (typeof t.alt === "string") result.alt = t.alt;
423
+ const border = record2(t.border), borderWidth = finite(border.width, 0, 64);
424
+ if (border.color !== void 0 && borderWidth) result.border = { color: border.color, width: round(borderWidth * scale) };
425
+ const opacity = finite(t.opacity, 0, 1);
426
+ if (opacity !== void 0 && opacity < 1) result.opacity = opacity;
427
+ if (t.recolor === "grayscale") result.recolor = { type: "grayscale" };
428
+ 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 };
429
+ const overlay = record2(t.overlay), overlayOpacity = finite(overlay.opacity, 0, 1);
430
+ if (overlay.color !== void 0 && overlayOpacity !== void 0) {
431
+ const edge = ["top", "bottom", "left", "right"].includes(overlay.edge) ? overlay.edge : void 0;
432
+ 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." });
433
+ else {
434
+ const part = finite(overlay.size, 0.05, 1) ?? 0.3;
435
+ 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) };
436
+ result.overlay = { color: overlay.color, opacity: overlayOpacity, box: bandBox, shape: edge ? slideImageShape("rectangle", bandBox) : result.shape };
437
+ }
438
+ }
439
+ return result;
440
+ }
441
+ var webUrl = /^https?:\/\/\S+$/i;
442
+ function resolveSocialProfile(platform, value, records = [], owner = "organization") {
443
+ const raw = String(value).trim().replace(/\s+/gu, " ");
444
+ const display = (url2) => url2.replace(/^https:\/\//i, "");
445
+ if (webUrl.test(raw)) return { text: display(raw), href: raw, resolved: false };
446
+ const platformRecord = records.find((item) => item?.id === platform);
447
+ const base = typeof platformRecord?.baseUrl === "string" && webUrl.test(platformRecord.baseUrl) ? `${platformRecord.baseUrl.replace(/\/+$/u, "")}/{handle}` : void 0;
448
+ const pattern = (owner === "organization" ? platformRecord?.companyUrlPattern : void 0) ?? platformRecord?.profileUrlPattern ?? base;
449
+ const prefix = platformRecord?.handlePrefix ?? "";
450
+ const handle = prefix && raw.startsWith(prefix) ? raw.slice(prefix.length) : raw;
451
+ if (!handle || typeof pattern !== "string" || !pattern.includes("{handle}")) return { text: raw, resolved: false };
452
+ const url = pattern.split("{handle}").join(handle);
453
+ if (!webUrl.test(url)) return { text: raw, resolved: false };
454
+ return { text: display(url), href: encodeURI(url), resolved: true };
455
+ }
222
456
  function layoutFurniture(input, options = {}) {
223
457
  const slide = record2(input), width = options.width ?? 1280, height = options.height ?? 720, scale = Math.min(width, height) / 720;
224
458
  const settings = { ...record2(record2(options.layout).composition), ...record2(slide.composition) };
@@ -227,10 +461,14 @@ function layoutFurniture(input, options = {}) {
227
461
  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
462
  const number = options.slideNumber ?? (options.slideIndex ?? 0) + 1;
229
463
  if (!Number.isSafeInteger(number) || number < 1) throw new RangeError("Displayed slide number must be a positive safe integer.");
464
+ const presentationSlides = options.presentation?.slides, slideCount = options.slideCount ?? (Array.isArray(presentationSlides) && presentationSlides.length ? presentationSlides.length : void 0);
465
+ if (slideCount !== void 0 && (!Number.isSafeInteger(slideCount) || slideCount < 1)) throw new RangeError("Displayed slide count must be a positive safe integer.");
466
+ 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
467
  const outlines = options.textMeasurement?.outlineBounds !== void 0, parts = [], diagnostics = [];
231
468
  const sourceRoot = `slides.${options.slideIndex ?? 0}`, organizations = Array.isArray(options.presentation?.organization) ? options.presentation.organization : [options.presentation?.organization];
232
469
  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";
470
+ const organization = record2(organizations[organizationIndex]), organizationRoot = Array.isArray(options.presentation?.organization) ? `organization.${organizationIndex}` : "organization", organizationPath = `${organizationRoot}.name`;
471
+ const inlinePlatforms = record2(record2(options.presentation?.catalogs).socialPlatforms).records, platformRecords = [...Array.isArray(inlinePlatforms) ? inlinePlatforms : [], ...options.socialPlatforms ?? []];
234
472
  const fontFamily = options.fonts?.body ?? "sans-serif";
235
473
  let headerBottom = 0, footerTop = height, configured = false;
236
474
  const error = (path, message, code = "text-overflow") => diagnostics.push({ code, path, message });
@@ -244,7 +482,7 @@ function layoutFurniture(input, options = {}) {
244
482
  for (const [index, zone] of ["left", "center", "right"].entries()) {
245
483
  const content = record2(record2(value)[zone]), path = `${root}.${zone}`, x = width * (0.07 + index * 0.3), zoneWidth = width * 0.26, zoneParts = [];
246
484
  let y = 0;
247
- const add = (field, text, generated = false, sourcePath) => {
485
+ const add = (field, text, generated = false, sourcePath, extras = {}) => {
248
486
  if (text === void 0) return;
249
487
  if (typeof text !== "string") throw new TypeError(`Furniture field ${path}.${field} requires string content.`);
250
488
  const partPath = `${path}.${field}`, style = resolveTextStyle({ fontFamily, fontWeight: 400, italic: false, path: partPath }, options.textMeasurement);
@@ -274,7 +512,7 @@ function layoutFurniture(input, options = {}) {
274
512
  if (!Number.isFinite(partHeight)) throw new RangeError("Furniture exceeds finite layout coordinates.");
275
513
  const box = { x, y, width: zoneWidth, height: Math.max(scale, partHeight) }, placement = outlines ? placeTextLines(ink, box, zone, padding) : void 0;
276
514
  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 });
515
+ 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
516
  if (accepted.overflow) error(partPath, "Repeated text exceeds its zone at the selected readability floor; change the furniture or slide design.");
279
517
  y += box.height;
280
518
  };
@@ -288,13 +526,37 @@ function layoutFurniture(input, options = {}) {
288
526
  if (typeof organization.name === "string") add("organization", organization.name, true, organizationPath);
289
527
  else error(`${path}.organization`, "Generated organization name needs a named organization in the presentation.", "unresolved-content");
290
528
  }
529
+ if (content.socials === true) {
530
+ 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") }));
531
+ if (links.length) add("socials", links.map((link) => link.text).join("\n"), true, `${organizationRoot}.socials`, { links });
532
+ else error(`${path}.socials`, "Generated social profiles need a primary organization with socials.", "unresolved-content");
533
+ }
291
534
  if (content.section === true) {
292
535
  if (typeof slide.section === "string") add("section", slide.section, true, `${sourceRoot}.section`);
293
536
  else error(`${path}.section`, "Generated section needs a literal slide section.", "unresolved-content");
294
537
  }
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);
538
+ 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.`);
539
+ if (content.slideNumber === true) {
540
+ const resolved = formatSlideNumber(content.slideNumberFormat ?? DEFAULT_SLIDE_NUMBER_FORMAT, number, slideCount);
541
+ if ("error" in resolved) error(`${path}.slideNumberFormat`, resolved.error, "unresolved-content");
542
+ else add("slideNumber", resolved.text, true, void 0, { fields: resolved.fields });
543
+ }
544
+ if (content.date === true) {
545
+ const format = content.dateFormat ?? DEFAULT_FURNITURE_DATE_FORMAT;
546
+ 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");
547
+ else {
548
+ const resolved = formatFurnitureDate(options.date, format);
549
+ if ("error" in resolved) error(`${path}.dateFormat`, resolved.error, "unresolved-content");
550
+ else add("date", resolved.text, true, void 0, { fields: [{ type: "date", start: 0, end: resolved.text.length, format }] });
551
+ }
552
+ } else if (typeof content.date === "string") {
553
+ if (content.dateFormat === void 0) add("date", content.date);
554
+ else {
555
+ const resolved = formatFurnitureDate(content.date, content.dateFormat);
556
+ if ("error" in resolved) error(`${path}.${parseIsoDate(content.date) ? "dateFormat" : "date"}`, resolved.error, "unresolved-content");
557
+ else add("date", resolved.text, true, `${path}.date`);
558
+ }
559
+ }
298
560
  zones.push(zoneParts);
299
561
  }
300
562
  const tallest = Math.max(0, ...zones.map((zone) => zone.reduce((sum, part) => sum + part.box.height, 0)));
@@ -931,9 +1193,24 @@ function richTextLayouter(input, box, requestedSize, options) {
931
1193
  const text = whole.slice(a, b), normalSize = (entry.run.fontSize !== void 0 ? entry.run.fontSize * 4 / 3 : requestedSize) * ratio;
932
1194
  const script = entry.run.superscript || entry.run.subscript;
933
1195
  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;
1196
+ const measure = textWidthMeasurer(entry.style, options.textMeasurement);
1197
+ let cursor = 0;
1198
+ for (const [index, segment] of text.split(" ").entries()) {
1199
+ if (index) {
1200
+ const tabStart = a + cursor, tabWidth = measure(" ", size) * 4;
1201
+ if (!Number.isFinite(tabWidth) || tabWidth <= 0) throw new RangeError("Rich text tabs require a positive finite measured space advance.");
1202
+ const next = (Math.floor(x / tabWidth + 1e-9) + 1) * tabWidth;
1203
+ 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 });
1204
+ x = next;
1205
+ cursor++;
1206
+ }
1207
+ if (segment) {
1208
+ const width2 = measure(segment, size), segmentStart = a + cursor;
1209
+ 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 });
1210
+ x += width2;
1211
+ cursor += segment.length;
1212
+ }
1213
+ }
937
1214
  }
938
1215
  return result;
939
1216
  };
@@ -1188,6 +1465,7 @@ function composeSlide(input, options = {}) {
1188
1465
  const minSize = (composition.minFontSize ?? 16) * scale;
1189
1466
  const rasterPadding = (options.textRasterPadding ?? 1) * scale;
1190
1467
  if (!Number.isFinite(rasterPadding) || rasterPadding < 0) throw new RangeError("Text raster padding must be finite and nonnegative.");
1468
+ const alignmentFor = (field) => (field === "title" ? record2(slide.design).titleAlignment ?? options.titleAlignment : record2(slide.design).contentAlignment ?? options.contentAlignment) ?? "left";
1191
1469
  const styleFor = (field, path2) => resolveTextStyle({
1192
1470
  fontFamily: (field === "title" ? options.fonts?.heading : field === "code" ? options.fonts?.code : options.fonts?.body) ?? (field === "code" ? "monospace" : "sans-serif"),
1193
1471
  fontWeight: field === "title" ? 700 : 400,
@@ -1197,7 +1475,7 @@ function composeSlide(input, options = {}) {
1197
1475
  const fitPlacedText = (field, value, text, box, size, minimum, path2) => {
1198
1476
  const style = styleFor(field, path2), rich = field === "text" && Array.isArray(value);
1199
1477
  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";
1478
+ const alignment = alignmentFor(field);
1201
1479
  const richLayout = rich ? richTextLayouter(value, box, size, { style, textMeasurement: options.textMeasurement }) : void 0;
1202
1480
  const measure = textWidthMeasurer(style, options.textMeasurement), floor = rich ? richMinimum(value, size, minimum) : minimum, start = Math.max(size, floor);
1203
1481
  for (let trial = 0; trial <= 64; trial++) {
@@ -1207,6 +1485,7 @@ function composeSlide(input, options = {}) {
1207
1485
  const lines = richLines ? richLines.map((line) => {
1208
1486
  let outline = null;
1209
1487
  for (const fragment of line.fragments) {
1488
+ if (fragment.kind === "tab") continue;
1210
1489
  const bounds = measureTextOutline(fragment.text, fragment.fontSize, fragment.style, options.textMeasurement);
1211
1490
  if (!bounds) continue;
1212
1491
  const next = { ...bounds, x: fragment.x + bounds.x, y: fragment.baselineShift + bounds.y };
@@ -1245,25 +1524,53 @@ function composeSlide(input, options = {}) {
1245
1524
  };
1246
1525
  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
1526
  const path = `slides.${options.slideIndex ?? 0}`;
1527
+ const slideImageDiagnostics = [];
1528
+ const slideImage = resolveSlideImage(slide, layout, options.presentation, width, height, path, padding, scale, slideImageDiagnostics);
1529
+ const area = { left: 0, top: 0, right: width, bottom: height };
1530
+ if (slideImage?.position === "left") area.left = slideImage.region.width;
1531
+ else if (slideImage?.position === "right") area.right = slideImage.region.x;
1532
+ else if (slideImage?.position === "top") area.top = slideImage.region.height;
1533
+ else if (slideImage?.position === "bottom") area.bottom = slideImage.region.y;
1248
1534
  const items = [], diagnostics = [];
1249
1535
  const measuredFurniture = layoutFurniture(slide, options);
1250
1536
  const furniture = measuredFurniture.configured || measuredFurniture.diagnostics.length ? measuredFurniture : void 0;
1251
1537
  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);
1538
+ const bodyBottom = Math.min(area.bottom - padding, furniture && furniture.footerTop < height ? furniture.footerTop - gap * 0.5 : height - padding);
1539
+ const headingTop = Math.max(area.top + padding, furniture?.headerBottom ? furniture.headerBottom + gap * 0.5 : padding);
1540
+ const layoutPlaceholders = Array.isArray(layout.placeholders) ? layout.placeholders : [];
1541
+ const headingOnlyLayout = COVER_LAYOUT_IDS.has(String(layout.id ?? "")) || layoutPlaceholders.length > 0 && layoutPlaceholders.every((placeholder) => headings.has(placeholder.type ?? ""));
1542
+ const regions = Object.keys(slide).filter((key) => regionParts(key)).sort();
1543
+ const emptyPayload = (value) => value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
1544
+ const emptyHost = (host) => emptyPayload(host.blocks) && fields.every((field) => emptyPayload(host[field]));
1545
+ const hasBodyPayload = regions.some((key) => !emptyHost(record2(slide[key]))) || !emptyPayload(slide.blocks) || fields.some((field) => !emptyPayload(slide[field]));
1546
+ const isCover = !hasBodyPayload && (headingOnlyLayout || !layout.id && !layoutPlaceholders.some((placeholder) => !headings.has(placeholder.type ?? "")));
1547
+ let y = headingTop;
1548
+ const headingItems = [];
1254
1549
  for (const field of ["tag", "title", "subtitle"]) {
1255
1550
  if (!slide[field]) continue;
1256
1551
  const requested = (field === "title" ? 54 : field === "tag" ? 16 : 25) * scale;
1257
1552
  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 };
1553
+ const box = { x: area.left + padding, y, width: area.right - area.left - padding * 2, height: maxHeight };
1259
1554
  const text = fitPlacedText(field, slide[field], String(slide[field]), box, requested, minSize, `${path}.${field}`);
1260
1555
  box.height = Math.min(maxHeight, Math.max(text.lines.length * text.lineHeight, text.placement?.height ?? 0));
1261
1556
  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 });
1557
+ const item = { path: `${path}.${field}`, field, type: "text", value: slide[field], payload: { text: slide[field] }, box, text, textStyle: styleFor(field, `${path}.${field}`), composition, alignment: alignmentFor(field) };
1558
+ headingItems.push(item);
1559
+ items.push(item);
1263
1560
  y += box.height + gap * 0.5;
1264
1561
  }
1265
- if (items.length) y += gap * 0.5;
1266
- const contentBox = { x: padding, y, width: width - padding * 2, height: Math.max(scale, bodyBottom - y) };
1562
+ if (headingItems.length) {
1563
+ if (isCover) {
1564
+ const last = headingItems[headingItems.length - 1];
1565
+ const shift = Math.max(0, (bodyBottom - (last.box.y + last.box.height)) / 2);
1566
+ if (shift) for (const item of headingItems) {
1567
+ item.box.y += shift;
1568
+ if (item.text) item.text = translateTextFit(item.text, shift);
1569
+ }
1570
+ y = last.box.y + last.box.height + gap;
1571
+ } else y += gap * 0.5;
1572
+ }
1573
+ const contentBox = { x: area.left + padding, y, width: area.right - area.left - padding * 2, height: Math.max(scale, bodyBottom - y) };
1267
1574
  const collect = (host, basePath, depth = 0, ancestors = []) => {
1268
1575
  if (Array.isArray(host.blocks)) {
1269
1576
  if (depth >= MAX_COMPOSITION_DEPTH || ancestors.includes(host)) throw new RangeError(`Content groups must be acyclic and nest at most ${MAX_COMPOSITION_DEPTH} levels.`);
@@ -1281,8 +1588,7 @@ function composeSlide(input, options = {}) {
1281
1588
  }
1282
1589
  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
1590
  };
1284
- 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);
1591
+ 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
1592
  const groups = [], flows = [];
1287
1593
  const decisions = options.explain ? [] : void 0;
1288
1594
  const inheritedSettings = (parent, own = {}) => ({
@@ -1321,7 +1627,7 @@ function composeSlide(input, options = {}) {
1321
1627
  minFontSize: settings.minFontSize,
1322
1628
  path: node.path,
1323
1629
  textRasterPadding: options.textRasterPadding,
1324
- align: record2(slide.design).contentAlignment ?? options.contentAlignment
1630
+ align: alignmentFor("metric")
1325
1631
  });
1326
1632
  const measureTimeline = (node, box, settings) => layoutTimeline(node.value, acceptedBox(box), {
1327
1633
  fonts: options.fonts,
@@ -1364,29 +1670,29 @@ function composeSlide(input, options = {}) {
1364
1670
  return score;
1365
1671
  };
1366
1672
  const modeFor = (settings) => settings.mode ?? "auto";
1367
- const defaultColumns = (count, area, settings) => {
1673
+ const defaultColumns = (count, area2, settings) => {
1368
1674
  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))));
1675
+ 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
1676
  };
1371
1677
  const scoreNode = (node, box, settings, penalties) => {
1372
1678
  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");
1679
+ const own = inheritedSettings(settings, node.composition), area2 = inset(box, own);
1680
+ const cols = defaultColumns(node.children.length, area2, own);
1681
+ const boxes = gridBoxes(node.children.length, area2, cols, (own.gap ?? 1 / 30) * Math.min(box.width, box.height), own.weights ?? [], modeFor(own) === "column");
1376
1682
  return node.children.reduce((score, child, index) => score + scoreNode(child, boxes[index], own, penalties), 0);
1377
1683
  };
1378
- const arrange = (nodes, area, settings, reserved = 0, gapOverride, containerPath = path) => {
1684
+ const arrange = (nodes, area2, settings, reserved = 0, gapOverride, containerPath = path) => {
1379
1685
  const count = Math.max(nodes.length, reserved);
1380
1686
  if (!count) return;
1381
- const mode = modeFor(settings), localGap = (settings.gap ?? 1 / 30) * Math.min(area.width, area.height);
1687
+ const mode = modeFor(settings), localGap = (settings.gap ?? 1 / 30) * Math.min(area2.width, area2.height);
1382
1688
  const actualGap = gapOverride ?? (settings === rootSettings ? gap : localGap);
1383
- let cols = defaultColumns(count, area, settings);
1689
+ let cols = defaultColumns(count, area2, settings);
1384
1690
  const hasRegions = nodes.some((node) => node.region);
1385
1691
  const candidates = decisions ? [] : void 0;
1386
1692
  if (mode === "auto" && !hasRegions) {
1387
1693
  let best = Infinity;
1388
1694
  for (let candidate = 1; candidate <= Math.min(count, settings.columns ?? 6); candidate++) {
1389
- const boxes2 = gridBoxes(count, area, candidate, actualGap, settings.weights ?? [], false);
1695
+ const boxes2 = gridBoxes(count, area2, candidate, actualGap, settings.weights ?? [], false);
1390
1696
  const emptySlots = (Math.ceil(count / candidate) * candidate - count) * 2;
1391
1697
  const penalties = candidates ? { cellProportions: 0, fontReduction: 0, textOverflow: 0, tableOverflow: 0, smallCells: 0, emptySlots } : void 0;
1392
1698
  const score = nodes.reduce((sum, node, i) => sum + scoreNode(node, boxes2[i], settings, penalties), 0) + emptySlots;
@@ -1404,11 +1710,11 @@ function composeSlide(input, options = {}) {
1404
1710
  ...hasRegions ? {} : { selectedColumns: cols },
1405
1711
  candidates: candidates ?? []
1406
1712
  });
1407
- const grid = gridGeometry(count, area, cols, actualGap, settings.weights ?? [], mode === "column");
1713
+ const grid = gridGeometry(count, area2, cols, actualGap, settings.weights ?? [], mode === "column");
1408
1714
  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 });
1715
+ 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
1716
  nodes.forEach((node, index) => {
1411
- let box = node.region ? regionBox(node.region, area, actualGap) : boxes[index];
1717
+ let box = node.region ? regionBox(node.region, area2, actualGap) : boxes[index];
1412
1718
  if (node.children) {
1413
1719
  const own = inheritedSettings(settings, node.composition), inner = inset(box, own);
1414
1720
  groups.push({ path: node.path, box, contentBox: inner, composition: own });
@@ -1434,6 +1740,7 @@ function composeSlide(input, options = {}) {
1434
1740
  text,
1435
1741
  textStyle: body?.style ?? styleFor(node.field, node.path),
1436
1742
  composition: settings,
1743
+ alignment: alignmentFor(node.field),
1437
1744
  ...quoteLayout ? { quoteLayout } : {},
1438
1745
  ...codeLayout ? { codeLayout } : {},
1439
1746
  ...metricLayout ? { metricLayout } : {},
@@ -1444,6 +1751,10 @@ function composeSlide(input, options = {}) {
1444
1751
  });
1445
1752
  };
1446
1753
  const placeholders = Array.isArray(layout.placeholders) ? layout.placeholders.filter((p) => !headings.has(p.type)) : [];
1754
+ if (slideImage?.replacesContent) {
1755
+ const picture = placeholders.findIndex((p) => p.type === "picture");
1756
+ if (picture >= 0) placeholders.splice(picture, 1);
1757
+ }
1447
1758
  const rootSettings = { ...composition, mode: composition.mode ?? (layout.slideLayoutDirection === "Vertical" ? "column" : layout.slideLayoutDirection === "Horizontal" ? "row" : "auto") };
1448
1759
  arrange(pending, contentBox, rootSettings, composition.mode ? 0 : placeholders.length);
1449
1760
  for (const item of items) {
@@ -1477,7 +1788,8 @@ function composeSlide(input, options = {}) {
1477
1788
  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
1789
  } : void 0;
1479
1790
  if (failures.length) throw new OPFCompositionError(failures, explanation);
1480
- return { width, height, contentBox, items, groups, flows, diagnostics, composition, ...furniture ? { furniture } : {}, ...explanation ? { explanation } : {} };
1791
+ diagnostics.push(...slideImageDiagnostics);
1792
+ return { width, height, contentBox, items, groups, flows, diagnostics, composition, ...furniture ? { furniture } : {}, ...slideImage ? { slideImage } : {}, ...explanation ? { explanation } : {} };
1481
1793
  }
1482
1794
  function resolveCanvasDimensions(input) {
1483
1795
  const presets = {
@@ -1497,4 +1809,4 @@ function resolveCanvasDimensions(input) {
1497
1809
  return { width, height };
1498
1810
  }
1499
1811
 
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 };
1812
+ 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 };