@openpresentation/opf 0.10.1 → 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 (280) hide show
  1. package/dist/{catalogs-DoVmvDr7.d.ts → catalogs-DJ-B5ZyD.d.ts} +79 -9
  2. package/dist/catalogs.d.ts +2 -2
  3. package/dist/catalogs.js +1 -1
  4. package/dist/{chunk-PQJRDNA6.js → chunk-6UPHKXI5.js} +381 -34
  5. package/dist/{chunk-TU7I3KSB.js → chunk-7LF37SG3.js} +7400 -5422
  6. package/dist/{chunk-EWJNRHXA.js → chunk-BP6WMSSS.js} +3 -3
  7. package/dist/{chunk-FHGJX5QK.js → chunk-KWIOQ3QR.js} +73 -32
  8. package/dist/{chunk-D3GQIREP.js → chunk-LTSDDBNW.js} +558 -52
  9. package/dist/{chunk-ZIL7MGZU.js → chunk-M4GTWRK7.js} +333 -102
  10. package/dist/chunk-NPYN4TZ5.js +3516 -0
  11. package/dist/{chunk-RS5GZ5RW.js → chunk-SDETQV7Y.js} +161 -0
  12. package/dist/composition-DzLwY-Mi.d.ts +887 -0
  13. package/dist/composition.d.ts +1 -676
  14. package/dist/composition.js +1 -1
  15. package/dist/docs.js +80 -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 +184 -4
  21. package/dist/index.js +473 -7
  22. package/dist/lint.d.ts +3 -3
  23. package/dist/lint.js +5 -5
  24. package/dist/pagination.d.ts +6 -2
  25. package/dist/pagination.js +5 -5
  26. package/dist/{presentation-BMTdX6O1.d.ts → presentation-bceTClm8.d.ts} +297 -30
  27. package/dist/repo-readme.js +1 -1
  28. package/dist/{schemas-BYe5y8-i.d.ts → schemas-X_NniU4A.d.ts} +8 -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/board-meeting.json +21 -21
  219. package/dist/spec/catalogs/narratives/business-narrative.json +8 -8
  220. package/dist/spec/catalogs/narratives/business-review.json +12 -12
  221. package/dist/spec/catalogs/narratives/capacity-planning.json +8 -8
  222. package/dist/spec/catalogs/narratives/challenge-resolution.json +7 -7
  223. package/dist/spec/catalogs/narratives/change-story.json +64 -0
  224. package/dist/spec/catalogs/narratives/classic-story.json +7 -7
  225. package/dist/spec/catalogs/narratives/company-intro.json +9 -9
  226. package/dist/spec/catalogs/narratives/conference-talk.json +1 -1
  227. package/dist/spec/catalogs/narratives/data-story.json +66 -0
  228. package/dist/spec/catalogs/narratives/early-startup-pitch.json +12 -12
  229. package/dist/spec/catalogs/narratives/educate.json +9 -9
  230. package/dist/spec/catalogs/narratives/employee-review.json +8 -8
  231. package/dist/spec/catalogs/narratives/failure-analysis.json +10 -10
  232. package/dist/spec/catalogs/narratives/focus.json +12 -12
  233. package/dist/spec/catalogs/narratives/golden-circle.json +1 -1
  234. package/dist/spec/catalogs/narratives/heros-journey.json +64 -0
  235. package/dist/spec/catalogs/narratives/index.json +81 -0
  236. package/dist/spec/catalogs/narratives/innovation.json +9 -9
  237. package/dist/spec/catalogs/narratives/justice.json +11 -11
  238. package/dist/spec/catalogs/narratives/marketing-strategy.json +11 -11
  239. package/dist/spec/catalogs/narratives/performance-improvement-plan.json +8 -8
  240. package/dist/spec/catalogs/narratives/performance-review.json +11 -11
  241. package/dist/spec/catalogs/narratives/persuade.json +7 -7
  242. package/dist/spec/catalogs/narratives/persuasive-sales.json +8 -8
  243. package/dist/spec/catalogs/narratives/pitch-deck.json +3 -3
  244. package/dist/spec/catalogs/narratives/problem-solution.json +2 -2
  245. package/dist/spec/catalogs/narratives/product-launch.json +10 -10
  246. package/dist/spec/catalogs/narratives/project-proposal.json +10 -10
  247. package/dist/spec/catalogs/narratives/pyramid-principle.json +48 -0
  248. package/dist/spec/catalogs/narratives/qbr.json +3 -3
  249. package/dist/spec/catalogs/narratives/rags-to-riches.json +7 -7
  250. package/dist/spec/catalogs/narratives/reveal.json +8 -8
  251. package/dist/spec/catalogs/narratives/scqa.json +1 -1
  252. package/dist/spec/catalogs/narratives/situation-complication-resolution.json +56 -0
  253. package/dist/spec/catalogs/narratives/sparkline.json +56 -0
  254. package/dist/spec/catalogs/narratives/star-method.json +56 -0
  255. package/dist/spec/catalogs/narratives/status-update.json +6 -6
  256. package/dist/spec/catalogs/narratives/strategic-advisory.json +7 -7
  257. package/dist/spec/catalogs/narratives/strategic-narrative.json +1 -1
  258. package/dist/spec/catalogs/narratives/survey-analysis.json +10 -10
  259. package/dist/spec/catalogs/narratives/survival-story.json +10 -10
  260. package/dist/spec/catalogs/narratives/transformation-arc.json +1 -1
  261. package/dist/spec/catalogs/narratives/trend-analysis.json +7 -7
  262. package/dist/spec/catalogs/narratives/underdog-victory.json +8 -8
  263. package/dist/spec/catalogs/narratives/venture-pitch.json +12 -12
  264. package/dist/spec/catalogs/narratives/vision-roadmap.json +65 -0
  265. package/dist/spec/catalogs/narratives/weekly-progress.json +10 -10
  266. package/dist/spec/catalogs/narratives/what-so-what-now-what.json +48 -0
  267. package/dist/spec/openapi.yaml +5 -0
  268. package/dist/spec/reference/engine-defaults.json +2 -2
  269. package/dist/spec/reference/font-policy.json +3435 -0
  270. package/dist/spec/reference/font-policy.schema.json +328 -0
  271. package/dist/spec/schemas/chart-type.schema.json +51 -11
  272. package/dist/spec/schemas/font-scheme.schema.json +91 -1
  273. package/dist/spec/schemas/language.schema.json +10 -5
  274. package/dist/spec/schemas/opf.schema.json +241 -32
  275. package/dist/spec-files.d.ts +1 -1
  276. package/dist/spec-files.js +1 -1
  277. package/dist/types.d.ts +3 -3
  278. package/dist/validator.d.ts +7 -4
  279. package/dist/validator.js +4 -4
  280. package/package.json +6 -2
@@ -35,7 +35,184 @@ 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
145
+ var schemeColorSlots = /* @__PURE__ */ new Set([
146
+ "accent1",
147
+ "accent2",
148
+ "accent3",
149
+ "accent4",
150
+ "accent5",
151
+ "accent6",
152
+ "dark1",
153
+ "dark2",
154
+ "light1",
155
+ "light2",
156
+ "hyperlink",
157
+ "followedHyperlink"
158
+ ]);
159
+ var schemeColorRoles = /* @__PURE__ */ new Set([
160
+ "primary",
161
+ "secondary",
162
+ "accent",
163
+ "background",
164
+ "surface",
165
+ "text",
166
+ "textSecondary"
167
+ ]);
168
+ var defaultRoleSlots = {
169
+ primary: "accent1",
170
+ secondary: "accent2",
171
+ accent: "accent3",
172
+ background: "light1",
173
+ surface: "light2",
174
+ text: "dark1",
175
+ textSecondary: "dark2"
176
+ };
177
+ function normalizeHexColor(value) {
178
+ if (typeof value !== "string") return void 0;
179
+ let hex = value.trim();
180
+ if (/^#[0-9a-f]{3}$/i.test(hex)) hex = `#${[...hex.slice(1)].map((char) => char + char).join("")}`;
181
+ if (/^#[0-9a-f]{6}([0-9a-f]{2})?$/i.test(hex)) return hex.length === 9 ? `#${hex.slice(1, 7).toUpperCase()}` : hex.toUpperCase();
182
+ return void 0;
183
+ }
184
+ function schemeSlot(colorScheme, slot, fallback) {
185
+ return normalizeHexColor(colorScheme[slot]) ?? fallback;
186
+ }
187
+ function resolveVariable(variables, id, fallback) {
188
+ const entry = variables[id];
189
+ if (typeof entry === "string") return normalizeHexColor(entry) ?? fallback;
190
+ if (entry && typeof entry === "object" && entry.type === "color") {
191
+ return normalizeHexColor(entry.value) ?? fallback;
192
+ }
193
+ return fallback;
194
+ }
195
+ function resolveColorRef(reference, options) {
196
+ const { colorScheme, roles, variables = {}, fallback } = options;
197
+ const literal = normalizeHexColor(reference);
198
+ if (literal) return literal;
199
+ if (reference.startsWith("var:")) {
200
+ const id = reference.slice("var:".length);
201
+ if (/^[a-z][a-z0-9-]*$/.test(id)) return resolveVariable(variables, id, fallback);
202
+ return fallback;
203
+ }
204
+ if (schemeColorRoles.has(reference)) {
205
+ const roleKey = reference;
206
+ const fromRoles = roles?.[roleKey];
207
+ if (fromRoles) return normalizeHexColor(fromRoles) ?? fallback;
208
+ const fromScheme = normalizeHexColor(colorScheme[reference]);
209
+ if (fromScheme) return fromScheme;
210
+ const slot = defaultRoleSlots[reference];
211
+ return slot ? schemeSlot(colorScheme, slot, fallback) : fallback;
212
+ }
213
+ if (schemeColorSlots.has(reference)) return schemeSlot(colorScheme, reference, fallback);
214
+ return fallback;
215
+ }
39
216
  function luminance(value) {
40
217
  if (typeof value !== "string") return void 0;
41
218
  let hex = value.trim();
@@ -76,13 +253,25 @@ function chartColorForFill(fill, preferred) {
76
253
 
77
254
  // src/composition.ts
78
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
+ }
79
268
  function resolveFontFamilies(input) {
80
269
  const scheme = record2(input);
81
270
  const family = (value) => typeof value === "string" ? value : record2(value).family;
82
271
  return {
83
- heading: family(scheme.heading) ?? scheme.major ?? scheme.minor ?? "Roboto",
84
- body: family(scheme.body) ?? scheme.minor ?? scheme.major ?? "Roboto",
85
- 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
86
275
  };
87
276
  }
88
277
  function resolveTextStyle(style, measurement) {
@@ -148,6 +337,106 @@ var columns = ["left", "center", "right"];
148
337
  var record2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
149
338
  var kind = (field) => field === "items" ? "list" : field === "bullets" ? "text" : field;
150
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
+ }
151
440
  function layoutFurniture(input, options = {}) {
152
441
  const slide = record2(input), width = options.width ?? 1280, height = options.height ?? 720, scale = Math.min(width, height) / 720;
153
442
  const settings = { ...record2(record2(options.layout).composition), ...record2(slide.composition) };
@@ -156,10 +445,14 @@ function layoutFurniture(input, options = {}) {
156
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.");
157
446
  const number = options.slideNumber ?? (options.slideIndex ?? 0) + 1;
158
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.");
159
451
  const outlines = options.textMeasurement?.outlineBounds !== void 0, parts = [], diagnostics = [];
160
452
  const sourceRoot = `slides.${options.slideIndex ?? 0}`, organizations = Array.isArray(options.presentation?.organization) ? options.presentation.organization : [options.presentation?.organization];
161
453
  const primaryIndex = organizations.findIndex((item) => record2(item).role === "primary"), organizationIndex = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
162
- 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 ?? []];
163
456
  const fontFamily = options.fonts?.body ?? "sans-serif";
164
457
  let headerBottom = 0, footerTop = height, configured = false;
165
458
  const error = (path, message, code = "text-overflow") => diagnostics.push({ code, path, message });
@@ -173,7 +466,7 @@ function layoutFurniture(input, options = {}) {
173
466
  for (const [index, zone] of ["left", "center", "right"].entries()) {
174
467
  const content = record2(record2(value)[zone]), path = `${root}.${zone}`, x = width * (0.07 + index * 0.3), zoneWidth = width * 0.26, zoneParts = [];
175
468
  let y = 0;
176
- const add = (field, text, generated = false, sourcePath) => {
469
+ const add = (field, text, generated = false, sourcePath, extras = {}) => {
177
470
  if (text === void 0) return;
178
471
  if (typeof text !== "string") throw new TypeError(`Furniture field ${path}.${field} requires string content.`);
179
472
  const partPath = `${path}.${field}`, style = resolveTextStyle({ fontFamily, fontWeight: 400, italic: false, path: partPath }, options.textMeasurement);
@@ -203,7 +496,7 @@ function layoutFurniture(input, options = {}) {
203
496
  if (!Number.isFinite(partHeight)) throw new RangeError("Furniture exceeds finite layout coordinates.");
204
497
  const box = { x, y, width: zoneWidth, height: Math.max(scale, partHeight) }, placement = outlines ? placeTextLines(ink, box, zone, padding) : void 0;
205
498
  const accepted = { ...fit, ...placement ? { placement } : {}, overflow: fit.overflow || !!placement?.overflow };
206
- 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 } : {} });
207
500
  if (accepted.overflow) error(partPath, "Repeated text exceeds its zone at the selected readability floor; change the furniture or slide design.");
208
501
  y += box.height;
209
502
  };
@@ -217,13 +510,37 @@ function layoutFurniture(input, options = {}) {
217
510
  if (typeof organization.name === "string") add("organization", organization.name, true, organizationPath);
218
511
  else error(`${path}.organization`, "Generated organization name needs a named organization in the presentation.", "unresolved-content");
219
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
+ }
220
518
  if (content.section === true) {
221
519
  if (typeof slide.section === "string") add("section", slide.section, true, `${sourceRoot}.section`);
222
520
  else error(`${path}.section`, "Generated section needs a literal slide section.", "unresolved-content");
223
521
  }
224
- if (content.slideNumber === true) add("slideNumber", String(number), true);
225
- if (content.date === true) error(`${path}.date`, "Use a literal date string for deterministic header/footer content.", "unresolved-content");
226
- 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
+ }
227
544
  zones.push(zoneParts);
228
545
  }
229
546
  const tallest = Math.max(0, ...zones.map((zone) => zone.reduce((sum, part) => sum + part.box.height, 0)));
@@ -860,9 +1177,24 @@ function richTextLayouter(input, box, requestedSize, options) {
860
1177
  const text = whole.slice(a, b), normalSize = (entry.run.fontSize !== void 0 ? entry.run.fontSize * 4 / 3 : requestedSize) * ratio;
861
1178
  const script = entry.run.superscript || entry.run.subscript;
862
1179
  const size = normalSize * (script ? 0.7 : 1), baselineShift = entry.run.superscript ? -normalSize * 0.35 : entry.run.subscript ? normalSize * 0.2 : 0;
863
- const width2 = textWidthMeasurer(entry.style, options.textMeasurement)(text, size);
864
- 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 });
865
- 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
+ }
866
1198
  }
867
1199
  return result;
868
1200
  };
@@ -1117,6 +1449,7 @@ function composeSlide(input, options = {}) {
1117
1449
  const minSize = (composition.minFontSize ?? 16) * scale;
1118
1450
  const rasterPadding = (options.textRasterPadding ?? 1) * scale;
1119
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";
1120
1453
  const styleFor = (field, path2) => resolveTextStyle({
1121
1454
  fontFamily: (field === "title" ? options.fonts?.heading : field === "code" ? options.fonts?.code : options.fonts?.body) ?? (field === "code" ? "monospace" : "sans-serif"),
1122
1455
  fontWeight: field === "title" ? 700 : 400,
@@ -1126,7 +1459,7 @@ function composeSlide(input, options = {}) {
1126
1459
  const fitPlacedText = (field, value, text, box, size, minimum, path2) => {
1127
1460
  const style = styleFor(field, path2), rich = field === "text" && Array.isArray(value);
1128
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));
1129
- const alignment = (field === "title" ? record2(slide.design).titleAlignment ?? options.titleAlignment : record2(slide.design).contentAlignment ?? options.contentAlignment) ?? "left";
1462
+ const alignment = alignmentFor(field);
1130
1463
  const richLayout = rich ? richTextLayouter(value, box, size, { style, textMeasurement: options.textMeasurement }) : void 0;
1131
1464
  const measure = textWidthMeasurer(style, options.textMeasurement), floor = rich ? richMinimum(value, size, minimum) : minimum, start = Math.max(size, floor);
1132
1465
  for (let trial = 0; trial <= 64; trial++) {
@@ -1136,6 +1469,7 @@ function composeSlide(input, options = {}) {
1136
1469
  const lines = richLines ? richLines.map((line) => {
1137
1470
  let outline = null;
1138
1471
  for (const fragment of line.fragments) {
1472
+ if (fragment.kind === "tab") continue;
1139
1473
  const bounds = measureTextOutline(fragment.text, fragment.fontSize, fragment.style, options.textMeasurement);
1140
1474
  if (!bounds) continue;
1141
1475
  const next = { ...bounds, x: fragment.x + bounds.x, y: fragment.baselineShift + bounds.y };
@@ -1174,25 +1508,32 @@ function composeSlide(input, options = {}) {
1174
1508
  };
1175
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));
1176
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;
1177
1518
  const items = [], diagnostics = [];
1178
1519
  const measuredFurniture = layoutFurniture(slide, options);
1179
1520
  const furniture = measuredFurniture.configured || measuredFurniture.diagnostics.length ? measuredFurniture : void 0;
1180
1521
  if (furniture) diagnostics.push(...furniture.diagnostics);
1181
- const bodyBottom = Math.min(height - padding, furniture && furniture.footerTop < height ? furniture.footerTop - gap * 0.5 : height - padding);
1182
- 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);
1183
1524
  for (const field of ["tag", "title", "subtitle"]) {
1184
1525
  if (!slide[field]) continue;
1185
1526
  const requested = (field === "title" ? 54 : field === "tag" ? 16 : 25) * scale;
1186
1527
  const maxHeight = Math.max(height * (field === "title" ? 0.26 : field === "subtitle" ? 0.12 : 0.045), minSize * 1.22 + 2 * rasterPadding);
1187
- 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 };
1188
1529
  const text = fitPlacedText(field, slide[field], String(slide[field]), box, requested, minSize, `${path}.${field}`);
1189
1530
  box.height = Math.min(maxHeight, Math.max(text.lines.length * text.lineHeight, text.placement?.height ?? 0));
1190
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." });
1191
- 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) });
1192
1533
  y += box.height + gap * 0.5;
1193
1534
  }
1194
1535
  if (items.length) y += gap * 0.5;
1195
- 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) };
1196
1537
  const collect = (host, basePath, depth = 0, ancestors = []) => {
1197
1538
  if (Array.isArray(host.blocks)) {
1198
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.`);
@@ -1211,7 +1552,7 @@ function composeSlide(input, options = {}) {
1211
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] } }));
1212
1553
  };
1213
1554
  const regions = Object.keys(slide).filter((key) => regionParts(key)).sort();
1214
- 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);
1215
1556
  const groups = [], flows = [];
1216
1557
  const decisions = options.explain ? [] : void 0;
1217
1558
  const inheritedSettings = (parent, own = {}) => ({
@@ -1250,7 +1591,7 @@ function composeSlide(input, options = {}) {
1250
1591
  minFontSize: settings.minFontSize,
1251
1592
  path: node.path,
1252
1593
  textRasterPadding: options.textRasterPadding,
1253
- align: record2(slide.design).contentAlignment ?? options.contentAlignment
1594
+ align: alignmentFor("metric")
1254
1595
  });
1255
1596
  const measureTimeline = (node, box, settings) => layoutTimeline(node.value, acceptedBox(box), {
1256
1597
  fonts: options.fonts,
@@ -1293,29 +1634,29 @@ function composeSlide(input, options = {}) {
1293
1634
  return score;
1294
1635
  };
1295
1636
  const modeFor = (settings) => settings.mode ?? "auto";
1296
- const defaultColumns = (count, area, settings) => {
1637
+ const defaultColumns = (count, area2, settings) => {
1297
1638
  const mode = modeFor(settings);
1298
- 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))));
1299
1640
  };
1300
1641
  const scoreNode = (node, box, settings, penalties) => {
1301
1642
  if (!node.children) return leafScore(node, box, settings, penalties);
1302
- const own = inheritedSettings(settings, node.composition), area = inset(box, own);
1303
- const cols = defaultColumns(node.children.length, area, own);
1304
- 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");
1305
1646
  return node.children.reduce((score, child, index) => score + scoreNode(child, boxes[index], own, penalties), 0);
1306
1647
  };
1307
- const arrange = (nodes, area, settings, reserved = 0, gapOverride, containerPath = path) => {
1648
+ const arrange = (nodes, area2, settings, reserved = 0, gapOverride, containerPath = path) => {
1308
1649
  const count = Math.max(nodes.length, reserved);
1309
1650
  if (!count) return;
1310
- 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);
1311
1652
  const actualGap = gapOverride ?? (settings === rootSettings ? gap : localGap);
1312
- let cols = defaultColumns(count, area, settings);
1653
+ let cols = defaultColumns(count, area2, settings);
1313
1654
  const hasRegions = nodes.some((node) => node.region);
1314
1655
  const candidates = decisions ? [] : void 0;
1315
1656
  if (mode === "auto" && !hasRegions) {
1316
1657
  let best = Infinity;
1317
1658
  for (let candidate = 1; candidate <= Math.min(count, settings.columns ?? 6); candidate++) {
1318
- const boxes2 = gridBoxes(count, area, candidate, actualGap, settings.weights ?? [], false);
1659
+ const boxes2 = gridBoxes(count, area2, candidate, actualGap, settings.weights ?? [], false);
1319
1660
  const emptySlots = (Math.ceil(count / candidate) * candidate - count) * 2;
1320
1661
  const penalties = candidates ? { cellProportions: 0, fontReduction: 0, textOverflow: 0, tableOverflow: 0, smallCells: 0, emptySlots } : void 0;
1321
1662
  const score = nodes.reduce((sum, node, i) => sum + scoreNode(node, boxes2[i], settings, penalties), 0) + emptySlots;
@@ -1333,11 +1674,11 @@ function composeSlide(input, options = {}) {
1333
1674
  ...hasRegions ? {} : { selectedColumns: cols },
1334
1675
  candidates: candidates ?? []
1335
1676
  });
1336
- const grid = gridGeometry(count, area, cols, actualGap, settings.weights ?? [], mode === "column");
1677
+ const grid = gridGeometry(count, area2, cols, actualGap, settings.weights ?? [], mode === "column");
1337
1678
  const boxes = grid.boxes;
1338
- 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 });
1339
1680
  nodes.forEach((node, index) => {
1340
- let box = node.region ? regionBox(node.region, area, actualGap) : boxes[index];
1681
+ let box = node.region ? regionBox(node.region, area2, actualGap) : boxes[index];
1341
1682
  if (node.children) {
1342
1683
  const own = inheritedSettings(settings, node.composition), inner = inset(box, own);
1343
1684
  groups.push({ path: node.path, box, contentBox: inner, composition: own });
@@ -1363,6 +1704,7 @@ function composeSlide(input, options = {}) {
1363
1704
  text,
1364
1705
  textStyle: body?.style ?? styleFor(node.field, node.path),
1365
1706
  composition: settings,
1707
+ alignment: alignmentFor(node.field),
1366
1708
  ...quoteLayout ? { quoteLayout } : {},
1367
1709
  ...codeLayout ? { codeLayout } : {},
1368
1710
  ...metricLayout ? { metricLayout } : {},
@@ -1373,6 +1715,10 @@ function composeSlide(input, options = {}) {
1373
1715
  });
1374
1716
  };
1375
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
+ }
1376
1722
  const rootSettings = { ...composition, mode: composition.mode ?? (layout.slideLayoutDirection === "Vertical" ? "column" : layout.slideLayoutDirection === "Horizontal" ? "row" : "auto") };
1377
1723
  arrange(pending, contentBox, rootSettings, composition.mode ? 0 : placeholders.length);
1378
1724
  for (const item of items) {
@@ -1406,7 +1752,8 @@ function composeSlide(input, options = {}) {
1406
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)
1407
1753
  } : void 0;
1408
1754
  if (failures.length) throw new OPFCompositionError(failures, explanation);
1409
- 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 } : {} };
1410
1757
  }
1411
1758
  function resolveCanvasDimensions(input) {
1412
1759
  const presets = {
@@ -1426,4 +1773,4 @@ function resolveCanvasDimensions(input) {
1426
1773
  return { width, height };
1427
1774
  }
1428
1775
 
1429
- export { MAX_COMPOSITION_DEPTH, OPFCompositionError, chartColorForFill, colorContrast, composeSlide, fitList, fitRichText, fitText, layoutCode, layoutFurniture, layoutMetric, layoutQuote, layoutTable, layoutTimeline, measureText, measureTextOutline, placeTextLines, resolveCanvasDimensions, 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 };