@openpresentation/opf 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (360) hide show
  1. package/README.md +28 -38
  2. package/dist/catalog-refs-_anZs-Qz.d.ts +128 -0
  3. package/dist/catalog-schemas-BZCjgAxu.d.ts +12 -0
  4. package/dist/catalog.d.ts +91 -0
  5. package/dist/{chunk-XOZ4KMSR.js → catalog.js} +17018 -8816
  6. package/dist/{chart-data-B_3z9VTb.d.ts → chart-data-BnNqgnC-.d.ts} +1 -1
  7. package/dist/{chunk-BFLVSVA7.js → chunk-3XDRQYWZ.js} +1 -1
  8. package/dist/{chunk-6OXY2CP3.js → chunk-5PDN7BN6.js} +503 -239
  9. package/dist/{chunk-CSQHPVGL.js → chunk-5TO6I3GB.js} +1 -1
  10. package/dist/{chunk-4LDGKLUC.js → chunk-5UEUAW57.js} +4 -2
  11. package/dist/{chunk-5ZU6JAPP.js → chunk-6OR326EA.js} +1 -1
  12. package/dist/{chunk-XIZUK2XM.js → chunk-BSYQDWT4.js} +15 -3
  13. package/dist/{chunk-UPQQD7MA.js → chunk-FQSIIADJ.js} +15 -6
  14. package/dist/{chunk-D3TY4TT6.js → chunk-HXHOZ7AH.js} +1407 -154
  15. package/dist/chunk-K4DQQIT4.js +319 -0
  16. package/dist/chunk-KINQFU6X.js +51 -0
  17. package/dist/{chunk-4VJDTF55.js → chunk-NAWQR7MN.js} +1 -1
  18. package/dist/{chunk-JDJBDYSR.js → chunk-PX5AWL7Y.js} +333 -857
  19. package/dist/{chunk-Q2TCMN56.js → chunk-SMLMI6IZ.js} +5 -2
  20. package/dist/chunk-SPFV27O5.js +210 -0
  21. package/dist/chunk-TRDXRRQK.js +17 -0
  22. package/dist/{chunk-Z65B2B77.js → chunk-UM24JFUE.js} +2 -2
  23. package/dist/{chunk-LS2FMPI5.js → chunk-WEDGVC5W.js} +969 -886
  24. package/dist/{composition-_WEwBe2a.d.ts → composition-D4ADF5c-.d.ts} +319 -115
  25. package/dist/composition.d.ts +3 -2
  26. package/dist/composition.js +2 -2
  27. package/dist/convert.d.ts +14 -17
  28. package/dist/convert.js +36 -39
  29. package/dist/data.d.ts +28 -11
  30. package/dist/data.js +3 -3
  31. package/dist/deck.d.ts +77 -0
  32. package/dist/deck.js +12 -0
  33. package/dist/diff.d.ts +1 -1
  34. package/dist/diff.js +7 -7
  35. package/dist/docs.js +33 -33
  36. package/dist/examples.d.ts +1 -1
  37. package/dist/examples.js +51761 -4783
  38. package/dist/{finding-C_por275.d.ts → finding-CT_f_vbD.d.ts} +2 -2
  39. package/dist/format.js +2 -2
  40. package/dist/index.d.ts +227 -77
  41. package/dist/index.js +343 -258
  42. package/dist/json-BFSdESOE.d.ts +19 -0
  43. package/dist/markdown.d.ts +20 -9
  44. package/dist/markdown.js +9 -9
  45. package/dist/{pagination-BZpXLvFL.d.ts → pagination-DzZPFheM.d.ts} +31 -31
  46. package/dist/pagination.d.ts +4 -5
  47. package/dist/pagination.js +6 -6
  48. package/dist/patch.d.ts +1 -1
  49. package/dist/patch.js +6 -6
  50. package/dist/{presentation-C5eHkevA.d.ts → presentation-COrFuj2g.d.ts} +398 -558
  51. package/dist/repo-readme.js +1 -1
  52. package/dist/schemas.d.ts +298 -1
  53. package/dist/schemas.js +1 -1
  54. package/dist/spec/README.md +1 -1
  55. package/dist/spec/catalogs/font-schemes/angsana-new.json +1 -1
  56. package/dist/spec/catalogs/font-schemes/aparajita.json +2 -2
  57. package/dist/spec/catalogs/font-schemes/arabic-typesetting.json +1 -1
  58. package/dist/spec/catalogs/font-schemes/batang.json +1 -1
  59. package/dist/spec/catalogs/font-schemes/daunpenh.json +1 -1
  60. package/dist/spec/catalogs/font-schemes/david.json +1 -1
  61. package/dist/spec/catalogs/font-schemes/dilleniaupc.json +1 -1
  62. package/dist/spec/catalogs/font-schemes/fangsong.json +1 -1
  63. package/dist/spec/catalogs/font-schemes/gautami.json +1 -1
  64. package/dist/spec/catalogs/font-schemes/gisha.json +2 -2
  65. package/dist/spec/catalogs/font-schemes/gungsuh.json +1 -1
  66. package/dist/spec/catalogs/font-schemes/index.json +1 -1
  67. package/dist/spec/catalogs/font-schemes/kalinga.json +1 -1
  68. package/dist/spec/catalogs/font-schemes/kartika.json +1 -1
  69. package/dist/spec/catalogs/font-schemes/khmer-ui.json +1 -1
  70. package/dist/spec/catalogs/font-schemes/latha.json +1 -1
  71. package/dist/spec/catalogs/font-schemes/malgun-gothic.json +1 -1
  72. package/dist/spec/catalogs/font-schemes/mangal.json +3 -3
  73. package/dist/spec/catalogs/font-schemes/meiryo.json +1 -1
  74. package/dist/spec/catalogs/font-schemes/microsoft-jhenghei.json +1 -1
  75. package/dist/spec/catalogs/font-schemes/microsoft-yahei.json +4 -4
  76. package/dist/spec/catalogs/font-schemes/mingliu.json +1 -1
  77. package/dist/spec/catalogs/font-schemes/miriam.json +1 -1
  78. package/dist/spec/catalogs/font-schemes/ms-mincho.json +1 -1
  79. package/dist/spec/catalogs/font-schemes/nirmala-ui.json +1 -1
  80. package/dist/spec/catalogs/font-schemes/noto-naksh-arabic.json +1 -1
  81. package/dist/spec/catalogs/font-schemes/noto-nastaliq-urdu.json +2 -2
  82. package/dist/spec/catalogs/font-schemes/noto-sans-arabic.json +3 -3
  83. package/dist/spec/catalogs/font-schemes/noto-sans-armenian.json +1 -1
  84. package/dist/spec/catalogs/font-schemes/noto-sans-bengali.json +2 -2
  85. package/dist/spec/catalogs/font-schemes/noto-sans-devangari.json +2 -2
  86. package/dist/spec/catalogs/font-schemes/noto-sans-ethiopic.json +1 -1
  87. package/dist/spec/catalogs/font-schemes/noto-sans-georgian.json +1 -1
  88. package/dist/spec/catalogs/font-schemes/noto-sans-gujarati.json +1 -1
  89. package/dist/spec/catalogs/font-schemes/noto-sans-gurmukhi.json +1 -1
  90. package/dist/spec/catalogs/font-schemes/noto-sans-hebrew.json +1 -1
  91. package/dist/spec/catalogs/font-schemes/noto-sans-jp.json +1 -1
  92. package/dist/spec/catalogs/font-schemes/noto-sans-kannada.json +1 -1
  93. package/dist/spec/catalogs/font-schemes/noto-sans-khmer.json +1 -1
  94. package/dist/spec/catalogs/font-schemes/noto-sans-kr.json +1 -1
  95. package/dist/spec/catalogs/font-schemes/noto-sans-malayalam.json +1 -1
  96. package/dist/spec/catalogs/font-schemes/noto-sans-mongolian.json +1 -1
  97. package/dist/spec/catalogs/font-schemes/noto-sans-oriya.json +1 -1
  98. package/dist/spec/catalogs/font-schemes/noto-sans-sc.json +4 -4
  99. package/dist/spec/catalogs/font-schemes/noto-sans-tamil.json +1 -1
  100. package/dist/spec/catalogs/font-schemes/noto-sans-tc.json +1 -1
  101. package/dist/spec/catalogs/font-schemes/noto-sans-telugu.json +1 -1
  102. package/dist/spec/catalogs/font-schemes/noto-sans-thai.json +1 -1
  103. package/dist/spec/catalogs/font-schemes/noto-sans.json +4 -4
  104. package/dist/spec/catalogs/font-schemes/nyala.json +1 -1
  105. package/dist/spec/catalogs/font-schemes/pmingliu.json +1 -1
  106. package/dist/spec/catalogs/font-schemes/raavi.json +1 -1
  107. package/dist/spec/catalogs/font-schemes/sakkal-majalla.json +3 -3
  108. package/dist/spec/catalogs/font-schemes/shonar-bangla.json +2 -2
  109. package/dist/spec/catalogs/font-schemes/shruti.json +1 -1
  110. package/dist/spec/catalogs/font-schemes/simsun.json +1 -1
  111. package/dist/spec/catalogs/font-schemes/sylfaen.json +2 -2
  112. package/dist/spec/catalogs/font-schemes/traditional-arabic.json +3 -3
  113. package/dist/spec/catalogs/font-schemes/tunga.json +1 -1
  114. package/dist/spec/catalogs/font-schemes/vrinda.json +1 -1
  115. package/dist/spec/catalogs/font-schemes/yu-gothic.json +1 -1
  116. package/dist/spec/catalogs/layouts/business-model-canvas.json +66 -1
  117. package/dist/spec/catalogs/layouts/capability-map.json +53 -6
  118. package/dist/spec/catalogs/layouts/chart-1x-title-center.json +30 -0
  119. package/dist/spec/catalogs/layouts/chart-2x-title-center.json +36 -0
  120. package/dist/spec/catalogs/layouts/chart-2x-vertical-title-left.json +37 -0
  121. package/dist/spec/catalogs/layouts/chart-2x.json +32 -8
  122. package/dist/spec/catalogs/layouts/chart-3x-bottom-vertical-title-left.json +52 -0
  123. package/dist/spec/catalogs/layouts/chart-3x-left-title-center.json +51 -0
  124. package/dist/spec/catalogs/layouts/chart-3x-left.json +46 -0
  125. package/dist/spec/catalogs/layouts/chart-3x-right-title-center.json +51 -0
  126. package/dist/spec/catalogs/layouts/chart-3x-right.json +46 -0
  127. package/dist/spec/catalogs/layouts/chart-3x-title-center.json +39 -0
  128. package/dist/spec/catalogs/layouts/chart-3x-top-vertical-title-left.json +52 -0
  129. package/dist/spec/catalogs/layouts/chart-3x.json +48 -12
  130. package/dist/spec/catalogs/layouts/competitive-landscape.json +1 -4
  131. package/dist/spec/catalogs/layouts/customer-journey.json +1 -4
  132. package/dist/spec/catalogs/layouts/dashboard.json +37 -5
  133. package/dist/spec/catalogs/layouts/decision-tree.json +1 -4
  134. package/dist/spec/catalogs/layouts/ecosystem-map.json +1 -4
  135. package/dist/spec/catalogs/layouts/extract/canonical.json +0 -9
  136. package/dist/spec/catalogs/layouts/geographic-map.json +1 -4
  137. package/dist/spec/catalogs/layouts/icon-grid.json +1 -4
  138. package/dist/spec/catalogs/layouts/image-1x-crop-title-center.json +32 -0
  139. package/dist/spec/catalogs/layouts/image-1x-crop-title-left.json +32 -0
  140. package/dist/spec/catalogs/layouts/image-1x-crop.json +27 -0
  141. package/dist/spec/catalogs/layouts/image-1x-fit-title-center.json +32 -0
  142. package/dist/spec/catalogs/layouts/image-1x.json +3 -0
  143. package/dist/spec/catalogs/layouts/image-2x-crop-title-center.json +38 -0
  144. package/dist/spec/catalogs/layouts/image-2x-crop-vertical-title-left.json +39 -0
  145. package/dist/spec/catalogs/layouts/image-2x-crop-vertical.json +34 -0
  146. package/dist/spec/catalogs/layouts/image-2x-crop.json +33 -0
  147. package/dist/spec/catalogs/layouts/image-2x-fit-title-center.json +38 -0
  148. package/dist/spec/catalogs/layouts/image-2x-fit-vertical-title-left.json +39 -0
  149. package/dist/spec/catalogs/layouts/image-2x-fit-vertical.json +34 -0
  150. package/dist/spec/catalogs/layouts/image-2x.json +35 -8
  151. package/dist/spec/catalogs/layouts/image-3x-crop-title-center.json +41 -0
  152. package/dist/spec/catalogs/layouts/image-3x-crop-vertical-title-left.json +42 -0
  153. package/dist/spec/catalogs/layouts/image-3x-crop-vertical.json +37 -0
  154. package/dist/spec/catalogs/layouts/image-3x-crop.json +36 -0
  155. package/dist/spec/catalogs/layouts/image-3x-fit-title-center.json +41 -0
  156. package/dist/spec/catalogs/layouts/image-3x-fit-vertical-title-left.json +42 -0
  157. package/dist/spec/catalogs/layouts/image-3x-fit-vertical.json +37 -0
  158. package/dist/spec/catalogs/layouts/image-3x-fit.json +36 -0
  159. package/dist/spec/catalogs/layouts/image-3x.json +51 -12
  160. package/dist/spec/catalogs/layouts/image-focus.json +3 -6
  161. package/dist/spec/catalogs/layouts/image-only-1x-crop-title-center.json +31 -0
  162. package/dist/spec/catalogs/layouts/image-only-1x-fit-title-center.json +31 -0
  163. package/dist/spec/catalogs/layouts/image-only-2x-crop-title-center.json +37 -0
  164. package/dist/spec/catalogs/layouts/image-only-2x-crop-vertical-title-left.json +38 -0
  165. package/dist/spec/catalogs/layouts/image-only-2x-crop-vertical.json +33 -0
  166. package/dist/spec/catalogs/layouts/image-only-2x-fit-title-center.json +37 -0
  167. package/dist/spec/catalogs/layouts/image-only-2x-fit-vertical-title-left.json +38 -0
  168. package/dist/spec/catalogs/layouts/image-only-2x-fit-vertical.json +33 -0
  169. package/dist/spec/catalogs/layouts/image-only-3x-crop-title-center.json +40 -0
  170. package/dist/spec/catalogs/layouts/image-only-3x-crop-vertical-title-left.json +41 -0
  171. package/dist/spec/catalogs/layouts/image-only-3x-crop-vertical.json +36 -0
  172. package/dist/spec/catalogs/layouts/image-only-3x-crop.json +35 -0
  173. package/dist/spec/catalogs/layouts/image-only-3x-fit-title-center.json +40 -0
  174. package/dist/spec/catalogs/layouts/image-only-3x-fit-vertical-title-left.json +41 -0
  175. package/dist/spec/catalogs/layouts/image-only-3x-fit-vertical.json +36 -0
  176. package/dist/spec/catalogs/layouts/image-only-3x-fit.json +35 -0
  177. package/dist/spec/catalogs/layouts/index.json +1041 -151
  178. package/dist/spec/catalogs/layouts/kpi-scorecard.json +48 -3
  179. package/dist/spec/catalogs/layouts/list-1x-box-title-center.json +32 -0
  180. package/dist/spec/catalogs/layouts/list-1x-box-vertical-title-center.json +33 -0
  181. package/dist/spec/catalogs/layouts/list-1x-box-vertical-title-left.json +33 -0
  182. package/dist/spec/catalogs/layouts/list-1x-box-vertical.json +28 -0
  183. package/dist/spec/catalogs/layouts/list-1x-box.json +27 -0
  184. package/dist/spec/catalogs/layouts/list-1x-itemimage-title-center.json +32 -0
  185. package/dist/spec/catalogs/layouts/list-1x-itemimage-vertical-title-center.json +33 -0
  186. package/dist/spec/catalogs/layouts/list-1x-title-center.json +31 -0
  187. package/dist/spec/catalogs/layouts/list-1x-vertical-title-center.json +32 -0
  188. package/dist/spec/catalogs/layouts/list-2x-box-title-center.json +38 -0
  189. package/dist/spec/catalogs/layouts/list-2x-box-vertical-title-center.json +39 -0
  190. package/dist/spec/catalogs/layouts/list-2x-box-vertical-title-left.json +39 -0
  191. package/dist/spec/catalogs/layouts/list-2x-box-vertical.json +34 -0
  192. package/dist/spec/catalogs/layouts/list-2x-box.json +33 -0
  193. package/dist/spec/catalogs/layouts/list-2x-itemimage-title-center.json +38 -0
  194. package/dist/spec/catalogs/layouts/list-2x-itemimage-vertical-title-center.json +39 -0
  195. package/dist/spec/catalogs/layouts/list-2x-itemimage-vertical-title-left.json +39 -0
  196. package/dist/spec/catalogs/layouts/list-2x-itemimage-vertical.json +34 -0
  197. package/dist/spec/catalogs/layouts/list-2x-title-center.json +37 -0
  198. package/dist/spec/catalogs/layouts/list-2x-vertical-title-center.json +38 -0
  199. package/dist/spec/catalogs/layouts/list-2x-vertical-title-left.json +38 -0
  200. package/dist/spec/catalogs/layouts/list-2x-vertical.json +33 -0
  201. package/dist/spec/catalogs/layouts/list-3x-box-title-center.json +41 -0
  202. package/dist/spec/catalogs/layouts/list-3x-box-vertical-title-center.json +42 -0
  203. package/dist/spec/catalogs/layouts/list-3x-box-vertical-title-left.json +42 -0
  204. package/dist/spec/catalogs/layouts/list-3x-box-vertical.json +37 -0
  205. package/dist/spec/catalogs/layouts/list-3x-box.json +36 -0
  206. package/dist/spec/catalogs/layouts/list-3x-itemimage-title-center.json +41 -0
  207. package/dist/spec/catalogs/layouts/list-3x-itemimage-vertical-title-center.json +42 -0
  208. package/dist/spec/catalogs/layouts/list-3x-itemimage-vertical-title-left.json +42 -0
  209. package/dist/spec/catalogs/layouts/list-3x-itemimage-vertical.json +37 -0
  210. package/dist/spec/catalogs/layouts/list-3x-itemimage.json +36 -0
  211. package/dist/spec/catalogs/layouts/list-3x-title-center.json +40 -0
  212. package/dist/spec/catalogs/layouts/list-3x-vertical-title-center.json +41 -0
  213. package/dist/spec/catalogs/layouts/list-3x-vertical-title-left.json +41 -0
  214. package/dist/spec/catalogs/layouts/list-3x-vertical.json +36 -0
  215. package/dist/spec/catalogs/layouts/list-4x-box-title-center.json +45 -0
  216. package/dist/spec/catalogs/layouts/list-4x-box-vertical-title-center.json +45 -0
  217. package/dist/spec/catalogs/layouts/list-4x-box-vertical-title-left.json +45 -0
  218. package/dist/spec/catalogs/layouts/list-4x-box-vertical.json +40 -0
  219. package/dist/spec/catalogs/layouts/list-4x-box.json +40 -0
  220. package/dist/spec/catalogs/layouts/list-4x-itemimage-title-center.json +45 -0
  221. package/dist/spec/catalogs/layouts/list-4x-itemimage-vertical-title-center.json +45 -0
  222. package/dist/spec/catalogs/layouts/list-4x-itemimage-vertical-title-left.json +45 -0
  223. package/dist/spec/catalogs/layouts/list-4x-itemimage-vertical.json +40 -0
  224. package/dist/spec/catalogs/layouts/list-4x-title-center.json +44 -0
  225. package/dist/spec/catalogs/layouts/list-4x-vertical-title-center.json +44 -0
  226. package/dist/spec/catalogs/layouts/list-4x-vertical-title-left.json +44 -0
  227. package/dist/spec/catalogs/layouts/list-4x-vertical.json +39 -0
  228. package/dist/spec/catalogs/layouts/list-5x-box-title-center.json +48 -0
  229. package/dist/spec/catalogs/layouts/list-5x-box-vertical-title-center.json +50 -0
  230. package/dist/spec/catalogs/layouts/list-5x-box-vertical-title-left.json +50 -0
  231. package/dist/spec/catalogs/layouts/list-5x-box-vertical.json +45 -0
  232. package/dist/spec/catalogs/layouts/list-5x-box.json +43 -0
  233. package/dist/spec/catalogs/layouts/list-5x-itemimage-title-center.json +48 -0
  234. package/dist/spec/catalogs/layouts/list-5x-itemimage-vertical-title-center.json +48 -0
  235. package/dist/spec/catalogs/layouts/list-5x-itemimage-vertical-title-left.json +48 -0
  236. package/dist/spec/catalogs/layouts/list-5x-itemimage-vertical.json +43 -0
  237. package/dist/spec/catalogs/layouts/list-5x-title-center.json +47 -0
  238. package/dist/spec/catalogs/layouts/list-5x-vertical-title-center.json +47 -0
  239. package/dist/spec/catalogs/layouts/list-5x-vertical-title-left.json +47 -0
  240. package/dist/spec/catalogs/layouts/list-5x-vertical.json +42 -0
  241. package/dist/spec/catalogs/layouts/list-6x-box-title-center.json +51 -0
  242. package/dist/spec/catalogs/layouts/list-6x-box-vertical-title-center.json +53 -0
  243. package/dist/spec/catalogs/layouts/list-6x-box-vertical-title-left.json +53 -0
  244. package/dist/spec/catalogs/layouts/list-6x-box-vertical.json +48 -0
  245. package/dist/spec/catalogs/layouts/list-6x-box.json +46 -0
  246. package/dist/spec/catalogs/layouts/list-6x-itemimage-title-center.json +51 -0
  247. package/dist/spec/catalogs/layouts/list-6x-itemimage-vertical-title-center.json +51 -0
  248. package/dist/spec/catalogs/layouts/list-6x-itemimage-vertical-title-left.json +51 -0
  249. package/dist/spec/catalogs/layouts/list-6x-itemimage-vertical.json +46 -0
  250. package/dist/spec/catalogs/layouts/list-6x-title-center.json +50 -0
  251. package/dist/spec/catalogs/layouts/list-6x-vertical-title-center.json +50 -0
  252. package/dist/spec/catalogs/layouts/list-6x-vertical-title-left.json +50 -0
  253. package/dist/spec/catalogs/layouts/list-6x-vertical.json +45 -0
  254. package/dist/spec/catalogs/layouts/logic-tree.json +69 -0
  255. package/dist/spec/catalogs/layouts/market-size.json +26 -2
  256. package/dist/spec/catalogs/layouts/network-dependency-map.json +1 -4
  257. package/dist/spec/catalogs/layouts/number-1x-box-title-center.json +30 -0
  258. package/dist/spec/catalogs/layouts/number-1x-box.json +25 -0
  259. package/dist/spec/catalogs/layouts/number-1x-title-center.json +29 -0
  260. package/dist/spec/catalogs/layouts/number-2x-box-title-center.json +36 -0
  261. package/dist/spec/catalogs/layouts/number-2x-box.json +31 -0
  262. package/dist/spec/catalogs/layouts/number-2x-title-center.json +35 -0
  263. package/dist/spec/catalogs/layouts/number-3x-box-title-center.json +39 -0
  264. package/dist/spec/catalogs/layouts/number-3x-box.json +34 -0
  265. package/dist/spec/catalogs/layouts/number-3x-title-center.json +38 -0
  266. package/dist/spec/catalogs/layouts/number-4x-box-title-center.json +43 -0
  267. package/dist/spec/catalogs/layouts/number-4x-box.json +38 -0
  268. package/dist/spec/catalogs/layouts/number-4x-title-center.json +42 -0
  269. package/dist/spec/catalogs/layouts/number-5x-box-title-center.json +46 -0
  270. package/dist/spec/catalogs/layouts/number-5x-box.json +41 -0
  271. package/dist/spec/catalogs/layouts/number-5x-title-center.json +45 -0
  272. package/dist/spec/catalogs/layouts/number-6x-box-title-center.json +49 -0
  273. package/dist/spec/catalogs/layouts/number-6x-box.json +44 -0
  274. package/dist/spec/catalogs/layouts/number-6x-title-center.json +48 -0
  275. package/dist/spec/catalogs/layouts/org-chart.json +53 -6
  276. package/dist/spec/catalogs/layouts/persona-grid.json +1 -4
  277. package/dist/spec/catalogs/layouts/photo-grid.json +1 -4
  278. package/dist/spec/catalogs/layouts/product-feature.json +1 -4
  279. package/dist/spec/catalogs/layouts/screenshot-walkthrough.json +1 -4
  280. package/dist/spec/catalogs/layouts/swimlane-process.json +1 -4
  281. package/dist/spec/catalogs/layouts/team-bio.json +1 -4
  282. package/dist/spec/catalogs/layouts/text-1x-box-title-center.json +31 -0
  283. package/dist/spec/catalogs/layouts/text-1x-box-title-left.json +31 -0
  284. package/dist/spec/catalogs/layouts/text-1x-center-box-title-center.json +31 -0
  285. package/dist/spec/catalogs/layouts/text-1x-center-box-title-left.json +31 -0
  286. package/dist/spec/catalogs/layouts/text-1x-center-box.json +26 -0
  287. package/dist/spec/catalogs/layouts/text-1x-center-title-center.json +30 -0
  288. package/dist/spec/catalogs/layouts/text-1x-center-title-left.json +30 -0
  289. package/dist/spec/catalogs/layouts/text-1x-center.json +25 -0
  290. package/dist/spec/catalogs/layouts/text-1x-left-box.json +26 -0
  291. package/dist/spec/catalogs/layouts/text-1x-title-center.json +30 -0
  292. package/dist/spec/catalogs/layouts/text-2x-box-title-center.json +37 -0
  293. package/dist/spec/catalogs/layouts/text-2x-box-vertical-title-left.json +37 -0
  294. package/dist/spec/catalogs/layouts/text-2x-center-box-title-center.json +37 -0
  295. package/dist/spec/catalogs/layouts/text-2x-center-box-vertical-title-left.json +37 -0
  296. package/dist/spec/catalogs/layouts/text-2x-center-box.json +32 -0
  297. package/dist/spec/catalogs/layouts/text-2x-center-title-center.json +36 -0
  298. package/dist/spec/catalogs/layouts/text-2x-center-vertical-title-left.json +36 -0
  299. package/dist/spec/catalogs/layouts/text-2x-center.json +31 -0
  300. package/dist/spec/catalogs/layouts/text-2x-left-box.json +32 -0
  301. package/dist/spec/catalogs/layouts/text-2x-title-center.json +36 -0
  302. package/dist/spec/catalogs/layouts/text-2x-vertical-title-left.json +36 -0
  303. package/dist/spec/catalogs/layouts/text-3x-box-title-center.json +40 -0
  304. package/dist/spec/catalogs/layouts/text-3x-box-vertical-title-left.json +41 -0
  305. package/dist/spec/catalogs/layouts/text-3x-center-box-title-center.json +40 -0
  306. package/dist/spec/catalogs/layouts/text-3x-center-box-vertical-title-left.json +41 -0
  307. package/dist/spec/catalogs/layouts/text-3x-center-box.json +35 -0
  308. package/dist/spec/catalogs/layouts/text-3x-center-title-center.json +39 -0
  309. package/dist/spec/catalogs/layouts/text-3x-center-vertical-title-left.json +40 -0
  310. package/dist/spec/catalogs/layouts/text-3x-center.json +34 -0
  311. package/dist/spec/catalogs/layouts/text-3x-left-box.json +35 -0
  312. package/dist/spec/catalogs/layouts/text-3x-left.json +34 -0
  313. package/dist/spec/catalogs/layouts/text-3x-title-center.json +39 -0
  314. package/dist/spec/catalogs/layouts/text-3x-vertical-title-left.json +40 -0
  315. package/dist/spec/catalogs/layouts/title-center-box.json +27 -0
  316. package/dist/spec/catalogs/layouts/title-center-image-bottom.json +34 -0
  317. package/dist/spec/catalogs/layouts/title-center-image-top.json +34 -0
  318. package/dist/spec/catalogs/layouts/title-center.json +26 -0
  319. package/dist/spec/catalogs/layouts/title-left-image-bottom.json +34 -0
  320. package/dist/spec/catalogs/layouts/title-left-image-left.json +34 -0
  321. package/dist/spec/catalogs/layouts/title-left-image-right.json +34 -0
  322. package/dist/spec/catalogs/layouts/title-left-image-top.json +34 -0
  323. package/dist/spec/catalogs/manifest.json +18 -18
  324. package/dist/spec/catalogs/narratives/heros-journey.json +1 -1
  325. package/dist/spec/catalogs/narratives/index.json +1 -1
  326. package/dist/spec/openapi.yaml +3 -3
  327. package/dist/spec/reference/engine-defaults.json +22 -30
  328. package/dist/spec/reference/engine-vocabularies.json +1577 -0
  329. package/dist/spec/schemas/audience.schema.json +5 -26
  330. package/dist/spec/schemas/catalog-manifest.schema.json +3 -3
  331. package/dist/spec/schemas/chart-type.schema.json +2 -35
  332. package/dist/spec/schemas/color-scheme.schema.json +3 -24
  333. package/dist/spec/schemas/finding.schema.json +1 -1
  334. package/dist/spec/schemas/font-scheme.schema.json +6 -37
  335. package/dist/spec/schemas/language.schema.json +7 -28
  336. package/dist/spec/schemas/layout.schema.json +227 -55
  337. package/dist/spec/schemas/narrative.schema.json +4 -24
  338. package/dist/spec/schemas/opf.schema.json +410 -374
  339. package/dist/spec/schemas/purpose.schema.json +5 -26
  340. package/dist/spec/schemas/social-platform.schema.json +2 -23
  341. package/dist/spec/schemas/theme.schema.json +9 -37
  342. package/dist/spec/schemas/tone.schema.json +4 -25
  343. package/dist/spec-files.d.ts +1 -1
  344. package/dist/spec-files.js +1 -1
  345. package/dist/types.d.ts +1103 -4
  346. package/dist/{validation-types-zh03LeZy.d.ts → validation-types-HxIgJfKx.d.ts} +7 -7
  347. package/dist/validator.d.ts +15 -12
  348. package/dist/validator.js +5 -5
  349. package/dist/yaml.d.ts +22 -7
  350. package/dist/yaml.js +9 -321
  351. package/package.json +9 -9
  352. package/dist/catalogs-D-yyQWCQ.d.ts +0 -1343
  353. package/dist/catalogs.d.ts +0 -2
  354. package/dist/catalogs.js +0 -2
  355. package/dist/chunk-PIQXLBBJ.js +0 -445
  356. package/dist/chunk-S5W34SIJ.js +0 -1
  357. package/dist/previews.d.ts +0 -40
  358. package/dist/previews.js +0 -1
  359. package/dist/schemas-DDpqAgyS.d.ts +0 -284
  360. package/dist/spec/catalogs/layouts/image-bleed.json +0 -20
@@ -1,6 +1,7 @@
1
- import { promotedRegionKeys, VARIABLE_KINDS, resolveChartData, chartPaletteForFill, tableGrid, timelineTextColor, visitContentPayloads, readingRows, visualReadingOrder, intrinsicImageSize, textWidthMeasurer, normalizeHexColor, resolveColorRef, resolveFontSchemeReference, DEFAULT_FONT_SCHEME, resolveColorRoles, resolveFontFamilies, resolveCanvasDimensions, defaultSlideBackground, isRecord, hasContentVariables, isTemplate, instantiateForValidation, unusedDatasets, MAX_COMPOSITION_DEPTH, datasetDiagnostics, pathFor, collectCitations, numberingFindings, walkCitationRuns, resolveTableData, bareIdPattern, resolveChartOptions, chartOptionTarget, codeHighlightLines, chartNumberFixesByCell, OPFCompositionError, composeSlide, resolveScriptFonts } from './chunk-6OXY2CP3.js';
2
- import { catalogs, catalogKinds, catalogEntries, catalogSchemaNames, themes } from './chunk-XOZ4KMSR.js';
3
- import { schemaEntries, schemas } from './chunk-LS2FMPI5.js';
1
+ import { promotedRegionKeys, VARIABLE_KINDS, resolveChartData, chartPaletteForFill, imageBackground, tableGrid, timelineTextColor, visitContentPayloads, readingRows, visualReadingOrder, intrinsicImageSize, textWidthMeasurer, normalizeHexColor, resolveColorRef, resolveDesignRecords, resolveColorRoles, resolveFontFamilies, resolveCanvasDimensions, defaultSlideBackground, isRecord, hasContentVariables, isTemplate, instantiateForValidation, unusedDatasets, MAX_COMPOSITION_DEPTH, datasetDiagnostics, pathFor, collectCitations, numberingFindings, walkCitationRuns, resolveTableData, resolveChartOptions, chartOptionTarget, codeHighlightLines, chartNumberFixesByCell, MAX_PLACEHOLDER_GROUP_DEPTH, OPFCompositionError, composeSlide, resolveScriptFonts } from './chunk-5PDN7BN6.js';
2
+ import { catalogSchemaNames } from './chunk-TRDXRRQK.js';
3
+ import { resolveReference, checkCatalogsOption, provenanceOf, unresolvedReference, OPFUnresolvedReferenceError, catalogReferenceSites, parseReference, catalogGroupDeclared, pointerPath, catalogRecords, catalogKinds, catalogGroupSource, registeredCatalog } from './chunk-SPFV27O5.js';
4
+ import { schemaEntries, schemas } from './chunk-WEDGVC5W.js';
4
5
  import Ajv2020 from 'ajv/dist/2020.js';
5
6
  import addFormats from 'ajv-formats';
6
7
  import { parseTree, printParseErrorCode, getNodePath } from 'jsonc-parser';
@@ -71,450 +72,6 @@ function unusedReferenceWarnings(value) {
71
72
  return value.references.flatMap((reference, index) => isRecord(reference) && typeof reference.id === "string" && unused.includes(reference.id) ? [issue(`/references/${index}`, `reference '${reference.id}' is never cited; cite it with a run's cite field or remove it`, { code: "unused-reference", id: reference.id })] : []);
72
73
  }
73
74
 
74
- // src/generated/catalog-ids.ts
75
- var catalogIds = {
76
- audiences: [
77
- "board",
78
- "engineering-team",
79
- "all-hands",
80
- "candidates",
81
- "executive",
82
- "investor",
83
- "technical",
84
- "sales",
85
- "marketing",
86
- "academic",
87
- "internal-team",
88
- "customer",
89
- "general-public",
90
- "media",
91
- "partner",
92
- "regulatory"
93
- ],
94
- purposes: [
95
- "inform",
96
- "decide",
97
- "align",
98
- "persuade",
99
- "educate",
100
- "report",
101
- "pitch",
102
- "sell",
103
- "plan"
104
- ],
105
- tones: [
106
- "formal",
107
- "casual",
108
- "inspirational",
109
- "technical",
110
- "persuasive",
111
- "authoritative",
112
- "conversational"
113
- ],
114
- themes: [
115
- "minimal",
116
- "classic",
117
- "dark",
118
- "bold"
119
- ],
120
- layouts: [
121
- "blank",
122
- "chart-1x",
123
- "chart-2x",
124
- "chart-3x",
125
- "code-1x",
126
- "image-1x",
127
- "image-2x",
128
- "image-3x",
129
- "image-bleed",
130
- "list-1x",
131
- "list-2x",
132
- "list-3x",
133
- "list-4x",
134
- "list-5x",
135
- "list-6x",
136
- "media-1x",
137
- "number-1x",
138
- "number-2x",
139
- "number-3x",
140
- "number-4x",
141
- "number-5x",
142
- "number-6x",
143
- "quote-1x",
144
- "table-1x",
145
- "text-1x",
146
- "text-2x",
147
- "text-3x",
148
- "timeline-1x",
149
- "title",
150
- "title-subtitle",
151
- "title-slide",
152
- "two-column",
153
- "image-focus",
154
- "data-visualization",
155
- "quote-highlight",
156
- "section-divider",
157
- "agenda",
158
- "thank-you-cta",
159
- "bullet-list",
160
- "three-column",
161
- "icon-grid",
162
- "faq",
163
- "stats-metrics",
164
- "comparison",
165
- "swot-analysis",
166
- "matrix-quadrant",
167
- "funnel-pyramid",
168
- "timeline",
169
- "process-flow",
170
- "photo-grid",
171
- "before-after",
172
- "testimonial-grid",
173
- "team-bio",
174
- "pricing-table",
175
- "roadmap",
176
- "product-feature",
177
- "executive-summary",
178
- "key-takeaway",
179
- "chapter-recap",
180
- "qa-discussion",
181
- "single-message-statement",
182
- "one-column-explainer",
183
- "case-study",
184
- "pros-cons",
185
- "feature-comparison-matrix",
186
- "venn-overlap",
187
- "decision-tree",
188
- "kpi-scorecard",
189
- "dashboard",
190
- "waterfall-bridge",
191
- "benchmark-target-table",
192
- "heatmap-cohort",
193
- "risk-heatmap",
194
- "forecast-scenario",
195
- "swimlane-process",
196
- "gantt-project-plan",
197
- "circular-lifecycle",
198
- "customer-journey",
199
- "org-chart",
200
- "capability-map",
201
- "logic-tree",
202
- "hub-and-spoke",
203
- "architecture-stack",
204
- "ecosystem-map",
205
- "network-dependency-map",
206
- "wheel-radial",
207
- "geographic-map",
208
- "screenshot-walkthrough",
209
- "market-size",
210
- "persona-grid",
211
- "competitive-landscape",
212
- "business-model-canvas",
213
- "customer-logos",
214
- "executive-decision",
215
- "status-rag",
216
- "raci-matrix",
217
- "action-plan",
218
- "assumptions-dependencies",
219
- "appendix-index",
220
- "detailed-data-table"
221
- ],
222
- chartTypes: [
223
- "100pct-stacked-area",
224
- "100pct-stacked-bar",
225
- "100pct-stacked-column",
226
- "area",
227
- "bar",
228
- "box-and-whisker",
229
- "column",
230
- "combo",
231
- "doughnut",
232
- "filled-radar",
233
- "funnel",
234
- "histogram",
235
- "line",
236
- "line-with-markers",
237
- "pareto",
238
- "pie",
239
- "radar",
240
- "radar-with-markers",
241
- "scatter",
242
- "stacked-area",
243
- "stacked-bar",
244
- "stacked-column",
245
- "stacked-line",
246
- "stacked-line-with-markers",
247
- "treemap",
248
- "waterfall",
249
- "world"
250
- ],
251
- narratives: [
252
- "problem-solution",
253
- "scqa",
254
- "pyramid-principle",
255
- "situation-complication-resolution",
256
- "what-so-what-now-what",
257
- "strategic-narrative",
258
- "golden-circle",
259
- "conference-talk",
260
- "transformation-arc",
261
- "pitch-deck",
262
- "early-startup-pitch",
263
- "venture-pitch",
264
- "persuasive-sales",
265
- "sparkline",
266
- "company-intro",
267
- "marketing-strategy",
268
- "product-launch",
269
- "strategic-advisory",
270
- "challenge-resolution",
271
- "project-proposal",
272
- "vision-roadmap",
273
- "failure-analysis",
274
- "star-method",
275
- "classic-story",
276
- "heros-journey",
277
- "business-narrative",
278
- "change-story",
279
- "rags-to-riches",
280
- "underdog-victory",
281
- "survival-story",
282
- "educate",
283
- "persuade",
284
- "reveal",
285
- "justice",
286
- "innovation",
287
- "focus",
288
- "qbr",
289
- "business-review",
290
- "board-meeting",
291
- "weekly-progress",
292
- "status-update",
293
- "performance-review",
294
- "survey-analysis",
295
- "trend-analysis",
296
- "data-story",
297
- "employee-review",
298
- "performance-improvement-plan",
299
- "capacity-planning"
300
- ],
301
- socialPlatforms: [
302
- "linkedin",
303
- "x",
304
- "github",
305
- "youtube",
306
- "instagram",
307
- "facebook",
308
- "tiktok",
309
- "threads",
310
- "mastodon",
311
- "bluesky"
312
- ],
313
- languages: [
314
- "afrikaans",
315
- "albanian",
316
- "amharic",
317
- "arabic",
318
- "armenian",
319
- "aymara",
320
- "azerbaijani",
321
- "bengali",
322
- "berber-latin",
323
- "bosnian-latin",
324
- "bulgarian",
325
- "catalan",
326
- "cebuano",
327
- "chinese-simplified",
328
- "chinese-traditional",
329
- "chittagonian",
330
- "croatian",
331
- "czech",
332
- "danish",
333
- "dutch",
334
- "english",
335
- "english-au",
336
- "english-ca",
337
- "english-gb",
338
- "english-in",
339
- "english-us",
340
- "estonian",
341
- "filipino",
342
- "finnish",
343
- "french",
344
- "fulfulde",
345
- "galician",
346
- "georgian",
347
- "german",
348
- "greek",
349
- "gujarati",
350
- "hausa",
351
- "hebrew",
352
- "hindi",
353
- "hungarian",
354
- "igbo",
355
- "indonesian",
356
- "italian",
357
- "japanese",
358
- "kannada",
359
- "kazakh",
360
- "khmer",
361
- "kinyarwanda",
362
- "korean",
363
- "kurmanji",
364
- "latvian",
365
- "lithuanian",
366
- "macedonian",
367
- "malagasy",
368
- "malay",
369
- "malayalam",
370
- "maori",
371
- "marathi",
372
- "mongolian",
373
- "nepali",
374
- "norwegian",
375
- "odia",
376
- "oromo",
377
- "pashto",
378
- "persian",
379
- "polish",
380
- "portuguese",
381
- "punjabi-gurmukhi",
382
- "punjabi-shahmukhi",
383
- "romanian",
384
- "russian",
385
- "serbian-cyrillic",
386
- "serbian-latin",
387
- "shona",
388
- "slovak",
389
- "slovenian",
390
- "somali",
391
- "spanish",
392
- "swahili",
393
- "swedish",
394
- "tagalog",
395
- "tajik",
396
- "tamil",
397
- "telugu",
398
- "thai",
399
- "turkish",
400
- "ukrainian",
401
- "urdu",
402
- "uzbek-latin",
403
- "vietnamese-quoc-ngu",
404
- "xhosa",
405
- "yoruba",
406
- "zulu"
407
- ],
408
- colorSchemes: [
409
- "black-and-white",
410
- "bold-red",
411
- "boost",
412
- "burnt-orange",
413
- "cool-horizon",
414
- "corporate-blue",
415
- "deep-purple",
416
- "forest-green",
417
- "golden-yellow",
418
- "luxury",
419
- "pastel-red",
420
- "slate-gray",
421
- "steel-blue",
422
- "vibes"
423
- ],
424
- fontSchemes: [
425
- "consolas",
426
- "courier-new",
427
- "arial",
428
- "calibri",
429
- "impact",
430
- "lucida-sans",
431
- "segoe-ui",
432
- "segoe-ui-light",
433
- "tahoma",
434
- "trebuchet-ms",
435
- "verdana",
436
- "bookman",
437
- "century-schoolbook",
438
- "constantia",
439
- "garamond",
440
- "rockwell",
441
- "times-new-roman",
442
- "aptos",
443
- "grandview",
444
- "seaford",
445
- "skeena",
446
- "tenorite",
447
- "georgia",
448
- "microsoft-yahei",
449
- "simsun",
450
- "fangsong",
451
- "yu-gothic",
452
- "pmingliu",
453
- "mingliu",
454
- "ms-mincho",
455
- "malgun-gothic",
456
- "batang",
457
- "gungsuh",
458
- "mangal",
459
- "nirmala-ui",
460
- "latha",
461
- "david",
462
- "miriam",
463
- "gisha",
464
- "vrinda",
465
- "shruti",
466
- "tunga",
467
- "kartika",
468
- "kalinga",
469
- "raavi",
470
- "gautami",
471
- "arabic-typesetting",
472
- "microsoft-jhenghei",
473
- "meiryo",
474
- "angsana-new",
475
- "aparajita",
476
- "dilleniaupc",
477
- "sakkal-majalla",
478
- "shonar-bangla",
479
- "sylfaen",
480
- "traditional-arabic",
481
- "montserrat",
482
- "open-sans",
483
- "poppins",
484
- "pt-serif",
485
- "raleway",
486
- "roboto",
487
- "noto-sans-arabic",
488
- "noto-sans-armenian",
489
- "noto-sans-bengali",
490
- "noto-sans-devangari",
491
- "noto-sans-georgian",
492
- "noto-sans-gujarati",
493
- "noto-sans-gurmukhi",
494
- "noto-sans-hebrew",
495
- "noto-sans-jp",
496
- "noto-sans-kannada",
497
- "noto-sans-kr",
498
- "noto-sans-malayalam",
499
- "noto-sans-mongolian",
500
- "noto-sans-oriya",
501
- "noto-sans-sc",
502
- "noto-sans-tamil",
503
- "noto-sans-tc",
504
- "noto-sans-telugu",
505
- "noto-sans",
506
- "noto-sans-thai",
507
- "noto-nastaliq-urdu",
508
- "noto-naksh-arabic",
509
- "noto-sans-ethiopic",
510
- "noto-sans-khmer",
511
- "nyala",
512
- "daunpenh",
513
- "khmer-ui"
514
- ]
515
- };
516
- var deprecatedCatalogIds = {};
517
-
518
75
  // src/validation-definitions.ts
519
76
  var schemaLocations;
520
77
  var issueLocations = /* @__PURE__ */ new WeakMap();
@@ -618,6 +175,7 @@ var contentKindSpecs = {
618
175
  }
619
176
  };
620
177
  var captionableKinds = /* @__PURE__ */ new Set(["image", "chart", "table", "video"]);
178
+ var imageOptionFields = ["fit", "focus", "aspectRatio", "shape", "cornerRadius", "border", "opacity", "recolor", "overlay", "placement"];
621
179
  var columnSpans = {
622
180
  left: [0],
623
181
  center: [1],
@@ -733,15 +291,31 @@ function validateContentPayload(value, path, options = {}) {
733
291
  const payloadFields = presentFields(value, rootPayloadFields);
734
292
  const hasBlocks = hasOwn(value, "blocks");
735
293
  const captionIssue = (reason, params = {}) => semanticIssue(pathFor(path, "caption"), `caption is only valid on an image, chart, table or video payload; ${reason}`, { code: "caption-unsupported-payload", ...params });
294
+ const imageOptionIssues = (reason, params = {}) => {
295
+ for (const field of imageOptionFields) if (hasOwn(value, field)) issues.push(semanticIssue(pathFor(path, field), `${field} is only valid on an image payload; ${reason}`, { code: "image-option-unsupported-payload", field, ...params }));
296
+ };
297
+ if (hasOwn(value, "placement") && !options.topLevel && !hasBlocks && explicitType !== "group" && inferredKinds(value).includes("image")) {
298
+ issues.push(semanticIssue(pathFor(path, "placement"), "placement is only valid on a top-level block (slides.N.blocks.I); a block inside a group or a promoted region cannot bleed to a slide edge", { code: "image-placement-invalid" }));
299
+ }
736
300
  if (hasBlocks || explicitType === "group") {
737
301
  if (hasOwn(value, "caption")) issues.push(captionIssue("a group cannot carry one"));
302
+ imageOptionIssues("a group cannot carry them");
738
303
  if (explicitType !== void 0 && explicitType !== "group") issues.push(semanticIssue(path, "a group must use type 'group' or omit type"));
739
304
  const incompatible2 = payloadFields.filter((field) => field !== "blocks" && field !== "type");
740
305
  if (incompatible2.length) issues.push(semanticIssue(path, "blocks cannot be mixed with leaf payload fields", { fields: incompatible2 }));
741
306
  if (!Array.isArray(value.blocks) || value.blocks.length === 0 && !options.slideRoot) issues.push(semanticIssue(pathFor(path, "blocks"), "a group requires at least one block"));
742
307
  if (Array.isArray(value.blocks)) value.blocks.forEach((block, index) => {
743
- if (isRecord(block)) issues.push(...validateContentPayload(block, `${pathFor(path, "blocks")}/${index}`));
308
+ if (isRecord(block)) issues.push(...validateContentPayload(block, `${pathFor(path, "blocks")}/${index}`, { topLevel: options.slideRoot === true }));
744
309
  });
310
+ if (options.slideRoot && Array.isArray(value.blocks)) {
311
+ const edges = /* @__PURE__ */ new Map();
312
+ value.blocks.forEach((block, index) => {
313
+ const edge = isRecord(block) && isRecord(block.placement) ? block.placement.edge : void 0;
314
+ if (edge === void 0) return;
315
+ if (edges.has(edge)) issues.push(semanticIssue(`${pathFor(path, "blocks")}/${index}/placement/edge`, `blocks ${edges.get(edge)} and ${index} are both placed on the ${String(edge)} edge; a slide edge takes at most one placed image`, { code: "image-placement-invalid", edge, blocks: [edges.get(edge), index] }));
316
+ else edges.set(edge, index);
317
+ });
318
+ }
745
319
  return issues;
746
320
  }
747
321
  if (hasOwn(value, "composition") && !options.slideRoot) issues.push(semanticIssue(pathFor(path, "composition"), "composition is only valid on a group containing blocks"));
@@ -774,6 +348,7 @@ function validateContentPayload(value, path, options = {}) {
774
348
  }
775
349
  const spec = contentKindSpecs[resolvedKind];
776
350
  if (hasOwn(value, "caption") && !captionableKinds.has(resolvedKind)) issues.push(captionIssue(`this payload is '${resolvedKind}'`, { type: resolvedKind }));
351
+ if (resolvedKind !== "image") imageOptionIssues(`this payload is '${resolvedKind}'`, { type: resolvedKind });
777
352
  const allowedFields = /* @__PURE__ */ new Set([
778
353
  "type",
779
354
  ...spec.fields
@@ -937,77 +512,6 @@ function presentationSemanticIssues(value) {
937
512
  issues.push(...annotationIssues(value));
938
513
  return issues;
939
514
  }
940
- function inlineCatalogEntry(context, kind) {
941
- const catalogsField = context.document.catalogs;
942
- if (!isRecord(catalogsField)) {
943
- return void 0;
944
- }
945
- const entry = catalogsField[kind];
946
- return isRecord(entry) ? entry : void 0;
947
- }
948
- function unknownIdWarning(kind, value, path, context) {
949
- if (typeof value !== "string" || !bareIdPattern.test(value)) {
950
- return void 0;
951
- }
952
- if (context) {
953
- const entry = inlineCatalogEntry(context, kind);
954
- if (entry) {
955
- const inline = Array.isArray(entry.records) ? entry.records.find((record) => isRecord(record) && record.id === value) : void 0;
956
- if (isRecord(inline)) {
957
- const replacedBy = isRecord(inline.deprecation) ? inline.deprecation.replacedBy : void 0;
958
- return typeof replacedBy === "string" ? deprecatedIdWarning(kind, value, replacedBy, path) : void 0;
959
- }
960
- if (typeof entry.source === "string" || Array.isArray(entry.source) && entry.source.length > 0) {
961
- return void 0;
962
- }
963
- }
964
- }
965
- if (catalogIds[kind].includes(value)) {
966
- const replacedBy = deprecatedCatalogIds[`${kind}/${value}`];
967
- return replacedBy === void 0 ? void 0 : deprecatedIdWarning(kind, value, replacedBy, path);
968
- }
969
- return semanticIssue(path, `unknown ${kind} catalog id '${value}'`, { kind, id: value });
970
- }
971
- function deprecatedIdWarning(kind, id, replacedBy, path) {
972
- return semanticIssue(path, `deprecated ${kind} catalog id '${id}'; use '${replacedBy}'`, { kind, id, replacedBy });
973
- }
974
- function referenceObjectWarning(kind, value, path, context) {
975
- if (typeof value === "string") {
976
- return unknownIdWarning(kind, value, path, context);
977
- }
978
- if (isRecord(value)) {
979
- return unknownIdWarning(kind, value.id, pathFor(path, "id"), context);
980
- }
981
- return void 0;
982
- }
983
- function pushIfDefined(issues, issue2) {
984
- if (issue2) {
985
- issues.push(issue2);
986
- }
987
- }
988
- function designReferenceWarnings(design, path, context) {
989
- if (!isRecord(design)) {
990
- return [];
991
- }
992
- const issues = [];
993
- pushIfDefined(issues, referenceObjectWarning("themes", design.theme, pathFor(path, "theme"), context));
994
- pushIfDefined(issues, referenceObjectWarning("colorSchemes", design.colorScheme, pathFor(path, "colorScheme"), context));
995
- pushIfDefined(issues, referenceObjectWarning("fontSchemes", design.fontScheme, pathFor(path, "fontScheme"), context));
996
- if (isRecord(design.theme)) {
997
- const themePath = pathFor(path, "theme");
998
- pushIfDefined(issues, referenceObjectWarning("colorSchemes", design.theme.colorScheme, pathFor(themePath, "colorScheme"), context));
999
- pushIfDefined(issues, referenceObjectWarning("fontSchemes", design.theme.fontScheme, pathFor(themePath, "fontScheme"), context));
1000
- }
1001
- return issues;
1002
- }
1003
- function themeOf(reference, context) {
1004
- const id = typeof reference === "string" ? reference : isRecord(reference) ? reference.id : void 0;
1005
- const entry = inlineCatalogEntry(context, "themes");
1006
- const inline = typeof id === "string" && Array.isArray(entry?.records) ? entry.records.find((record) => isRecord(record) && record.id === id) : void 0;
1007
- const base = isRecord(inline) ? inline : typeof id === "string" ? themes.find((record) => isRecord(record) && record.id === id) : void 0;
1008
- if (isRecord(reference)) return { ...base ?? {}, ...reference };
1009
- return base;
1010
- }
1011
515
  function canvasSize(dimensions) {
1012
516
  try {
1013
517
  const { width, height } = resolveCanvasDimensions(dimensions);
@@ -1016,15 +520,16 @@ function canvasSize(dimensions) {
1016
520
  return "invalid";
1017
521
  }
1018
522
  }
1019
- function slideThemeDimensionsWarnings(document, slide, slidePath, context) {
523
+ function slideThemeDimensionsWarnings(document, slide, index, catalogs) {
524
+ const slidePath = `/slides/${index}`;
1020
525
  const slideDesign = isRecord(slide.design) ? slide.design : void 0;
1021
- if (!slideDesign || slideDesign.theme === void 0) return [];
1022
- const slideTheme = themeOf(slideDesign.theme, context);
1023
- if (!slideTheme || slideTheme.dimensions === void 0) return [];
526
+ if (!slideDesign || typeof slideDesign.theme !== "string") return [];
527
+ const options = { catalogs: catalogs ?? [] };
528
+ const slideTheme = resolveDesignRecords(document, index, options);
529
+ if (slideTheme.diagnostics.some((diagnostic) => diagnostic.kind === "themes") || slideTheme.theme.dimensions === void 0) return [];
1024
530
  const deckDesign = isRecord(document.design) ? document.design : {};
1025
- const deckTheme = themeOf(deckDesign.theme ?? "minimal", context);
1026
- const deckDimensions = deckDesign.dimensions ?? deckTheme?.dimensions;
1027
- const slideSize = canvasSize(slideTheme.dimensions);
531
+ const deckDimensions = deckDesign.dimensions ?? resolveDesignRecords(document, void 0, options).theme.dimensions;
532
+ const slideSize = canvasSize(slideTheme.theme.dimensions);
1028
533
  const deckSize = canvasSize(deckDimensions);
1029
534
  if (slideSize === deckSize) return [];
1030
535
  const explicit = deckDesign.dimensions !== void 0;
@@ -1034,20 +539,19 @@ function slideThemeDimensionsWarnings(document, slide, slidePath, context) {
1034
539
  { code: "slide-theme-dimensions", slideDimensions: slideSize, deckDimensions: deckSize }
1035
540
  )];
1036
541
  }
1037
- function chartTypeWarnings(payload, path, context) {
542
+ function chartTypeWarnings(payload, path) {
1038
543
  if (!isRecord(payload)) {
1039
544
  return [];
1040
545
  }
1041
546
  const issues = [];
1042
547
  if (isRecord(payload.chart)) {
1043
- pushIfDefined(issues, unknownIdWarning("chartTypes", payload.chart.type, `${pathFor(path, "chart")}/type`, context));
1044
548
  for (const diagnostic of resolveChartOptions(payload.chart, chartOptionTarget(payload.chart.type)).diagnostics) {
1045
549
  issues.push(semanticIssue(diagnostic.option.split(".").reduce(pathFor, pathFor(path, "chart")), diagnostic.message, { code: diagnostic.code, option: diagnostic.option, reason: diagnostic.reason }));
1046
550
  }
1047
551
  }
1048
552
  if (Array.isArray(payload.blocks)) {
1049
553
  payload.blocks.forEach((block, index) => {
1050
- issues.push(...chartTypeWarnings(block, `${pathFor(path, "blocks")}/${index}`, context));
554
+ issues.push(...chartTypeWarnings(block, `${pathFor(path, "blocks")}/${index}`));
1051
555
  });
1052
556
  }
1053
557
  return issues;
@@ -1216,34 +720,19 @@ function variableReferenceWarnings(value) {
1216
720
  });
1217
721
  return issues;
1218
722
  }
1219
- function presentationReferenceWarnings(value) {
723
+ function presentationReferenceWarnings(value, catalogs) {
1220
724
  if (!isRecord(value) || !Array.isArray(value.slides)) {
1221
725
  return [];
1222
726
  }
1223
- const context = { document: value };
1224
727
  const issues = [];
1225
- if (typeof value.narrative === "string") {
1226
- pushIfDefined(issues, unknownIdWarning("narratives", value.narrative, "/narrative", context));
1227
- }
1228
- if (Array.isArray(value.audience)) {
1229
- value.audience.forEach((entry, index) => {
1230
- pushIfDefined(issues, referenceObjectWarning("audiences", entry, `/audience/${index}`, context));
1231
- });
1232
- } else if (isRecord(value.audience)) {
1233
- pushIfDefined(issues, referenceObjectWarning("audiences", value.audience, "/audience", context));
1234
- } else {
1235
- pushIfDefined(issues, unknownIdWarning("audiences", value.audience, "/audience", context));
1236
- }
1237
- issues.push(...designReferenceWarnings(value.design, "/design", context));
1238
728
  issues.push(...variableReferenceWarnings(value));
1239
729
  value.slides.forEach((slide, index) => {
1240
730
  if (!isRecord(slide)) {
1241
731
  return;
1242
732
  }
1243
733
  const slidePath = `/slides/${index}`;
1244
- issues.push(...designReferenceWarnings(slide.design, pathFor(slidePath, "design"), context));
1245
- issues.push(...slideThemeDimensionsWarnings(value, slide, slidePath, context));
1246
- issues.push(...chartTypeWarnings(slide, slidePath, context));
734
+ issues.push(...slideThemeDimensionsWarnings(value, slide, index, catalogs));
735
+ issues.push(...chartTypeWarnings(slide, slidePath));
1247
736
  const numberingWarnings = (payload, path) => {
1248
737
  for (const finding2 of numberingFindings(payload).warnings) issues.push(semanticIssue(pathFor(path, finding2.key), finding2.message, { ...finding2.params, code: "numbering-start-ignored" }));
1249
738
  };
@@ -1261,34 +750,12 @@ function presentationReferenceWarnings(value) {
1261
750
  visitContentPayloads(slide, slidePath, highlightWarnings);
1262
751
  for (const key of Object.keys(slide)) {
1263
752
  if (promotedRegionKeySet.has(key)) {
1264
- issues.push(...chartTypeWarnings(slide[key], pathFor(slidePath, key), context));
753
+ issues.push(...chartTypeWarnings(slide[key], pathFor(slidePath, key)));
1265
754
  }
1266
755
  }
1267
756
  });
1268
757
  return issues;
1269
758
  }
1270
- var catalogCrossLinkFields = {
1271
- audience: { recommendedNarratives: "narratives", recommendedTones: "tones" },
1272
- purpose: { recommendedNarratives: "narratives", recommendedTones: "tones" },
1273
- tone: { recommendedNarratives: "narratives" }
1274
- };
1275
- function catalogCrossLinkWarnings(schemaName, value) {
1276
- const fields2 = catalogCrossLinkFields[schemaName];
1277
- if (!fields2 || !isRecord(value)) {
1278
- return [];
1279
- }
1280
- const issues = [];
1281
- for (const [field, kind] of Object.entries(fields2)) {
1282
- const links = value[field];
1283
- if (!Array.isArray(links)) {
1284
- continue;
1285
- }
1286
- links.forEach((link, index) => {
1287
- pushIfDefined(issues, unknownIdWarning(kind, link, `${pathFor("/", field)}/${index}`));
1288
- });
1289
- }
1290
- return issues;
1291
- }
1292
759
  function validateLanguageSemantics(value) {
1293
760
  if (!isRecord(value)) {
1294
761
  return [];
@@ -1397,6 +864,33 @@ function dataUnionErrors(errors, value) {
1397
864
  return { ...error, message: field === "fields" ? "'fields' applies only to a dataset-backed table; add 'dataset' or remove 'fields'" : datasetTableMessage(field) };
1398
865
  });
1399
866
  }
867
+ var placeholderHeadings = /* @__PURE__ */ new Set(["title", "subtitle", "tag"]);
868
+ function placeholderGroupIssues(value) {
869
+ const issues = [];
870
+ if (!isRecord(value) || !Array.isArray(value.placeholders)) return issues;
871
+ const walk = (entries, path, depth) => entries.forEach((entry, index) => {
872
+ if (!isRecord(entry)) return;
873
+ const at2 = `${path}/${index}`;
874
+ if (entry.type === "group") {
875
+ if (depth >= MAX_PLACEHOLDER_GROUP_DEPTH) issues.push(semanticIssue(at2, `placeholder groups nest at most ${MAX_PLACEHOLDER_GROUP_DEPTH} levels inside a layout record; this group would be level ${depth + 1}`, { code: "layout-placeholder-group", depth: depth + 1, maxDepth: MAX_PLACEHOLDER_GROUP_DEPTH }));
876
+ else if (Array.isArray(entry.placeholders)) walk(entry.placeholders, `${at2}/placeholders`, depth + 1);
877
+ return;
878
+ }
879
+ if (depth === 0) return;
880
+ if (typeof entry.type === "string" && placeholderHeadings.has(entry.type)) issues.push(semanticIssue(`${at2}/type`, `a placeholder group holds body regions only; a '${entry.type}' placeholder belongs at the record's top level`, { code: "layout-placeholder-group" }));
881
+ if (hasOwn(entry, "placement")) issues.push(semanticIssue(`${at2}/placement`, "placement is only valid on a top-level image placeholder; a region inside a placeholder group cannot bleed to a slide edge", { code: "image-placement-invalid" }));
882
+ });
883
+ walk(value.placeholders, "/placeholders", 0);
884
+ return issues;
885
+ }
886
+ function withPlaceholderGroupIssues(errors, value) {
887
+ const issues = placeholderGroupIssues(value);
888
+ const entries = issues.map((issue2) => issue2.path.replace(/\/(type|placement)$/, ""));
889
+ const covered = (error) => entries.some((entry) => error.path === entry || error.path.startsWith(`${entry}/`));
890
+ const kept = errors.filter((error) => !covered(error));
891
+ const reported = [...kept.filter((error) => error.keyword !== "if"), ...issues].map((error) => error.path);
892
+ return [...kept.filter((error) => error.keyword !== "if" || !reported.some((path) => path === error.path || path.startsWith(`${error.path}/`))), ...issues];
893
+ }
1400
894
  function validateAgainstSchema(value, schemaOrKind = "presentation", options = {}) {
1401
895
  const resolved = resolveValidator(schemaOrKind);
1402
896
  if (resolved.schemaName === "presentation") {
@@ -1428,20 +922,20 @@ function validateAgainstSchema(value, schemaOrKind = "presentation", options = {
1428
922
  }
1429
923
  const valid = resolved.validate(subject) === true;
1430
924
  const ajvErrors = valid ? [] : typedVariableErrors(resolved.validate.errors ?? [], subject);
1431
- const errors = (resolved.schemaName === "presentation" ? dataUnionErrors(ajvErrors, subject) : ajvErrors).map(toIssue);
925
+ const mapped = (resolved.schemaName === "presentation" ? dataUnionErrors(ajvErrors, subject) : ajvErrors).map(toIssue);
926
+ const errors = resolved.schemaName === "layout" ? withPlaceholderGroupIssues(mapped, subject) : mapped;
1432
927
  const warnings = [];
1433
928
  if (resolved.schemaName === "presentation") {
1434
929
  errors.push(...presentationSemanticIssues(subject));
1435
930
  errors.push(...variableIssues.errors);
1436
931
  const data = dataIssues(subject);
1437
932
  errors.push(...data.errors);
1438
- warnings.push(...presentationReferenceWarnings(subject));
933
+ warnings.push(...presentationReferenceWarnings(subject, options.catalogs));
1439
934
  warnings.push(...data.warnings);
1440
935
  warnings.push(...variableIssues.warnings);
1441
936
  } else if (resolved.schemaName === "language") {
1442
937
  errors.push(...validateLanguageSemantics(value));
1443
938
  }
1444
- warnings.push(...catalogCrossLinkWarnings(resolved.schemaName, value));
1445
939
  return {
1446
940
  valid: errors.length === 0,
1447
941
  errors,
@@ -1455,24 +949,13 @@ function validateAgainstSchema(value, schemaOrKind = "presentation", options = {
1455
949
 
1456
950
  // src/narrative-plan.ts
1457
951
  var isRec = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1458
- function resolveNarrative(document, loaded = []) {
952
+ function resolveNarrative(document, options = {}) {
1459
953
  if (!isRec(document) || typeof document.narrative !== "string") return void 0;
1460
- const id = document.narrative;
1461
- if (/^(?:https?:|pkg:)/i.test(id)) return void 0;
1462
- const entry = isRec(document.catalogs) && isRec(document.catalogs.narratives) ? document.catalogs.narratives : {};
1463
- const sources = [
1464
- ["document", Array.isArray(entry.records) ? entry.records : []],
1465
- ["loaded", loaded],
1466
- ["built-in", catalogs.narratives]
1467
- ];
1468
- for (const [origin, records] of sources) {
1469
- const record = records.find((candidate) => isRec(candidate) && candidate.id === id);
1470
- if (isRec(record)) {
1471
- const beats = Array.isArray(record.beats) ? record.beats.flatMap((beat) => isRec(beat) && typeof beat.id === "string" ? [beat.id] : []) : [];
1472
- return { record, origin, beats };
1473
- }
1474
- }
1475
- return void 0;
954
+ const found = resolveReference(document, "narratives", document.narrative, { catalogs: options.catalogs ?? [] });
955
+ if (!found) return void 0;
956
+ const record = found.record;
957
+ const beats = Array.isArray(record.beats) ? record.beats.flatMap((beat) => isRec(beat) && typeof beat.id === "string" ? [beat.id] : []) : [];
958
+ return { record, origin: found.origin, beats };
1476
959
  }
1477
960
  function slideBeatReferences(document) {
1478
961
  if (!isRec(document) || !Array.isArray(document.slides)) return [];
@@ -1590,19 +1073,13 @@ function simulateVision(hex, model) {
1590
1073
  const m = CVD_MATRICES[model];
1591
1074
  return toHex(m.map((row) => delinear(row[0] * (rgb[0] ?? 0) + row[1] * (rgb[1] ?? 0) + row[2] * (rgb[2] ?? 0))));
1592
1075
  }
1593
- function createLookup(document, extra) {
1594
- return (kind, id) => {
1595
- const inline = rec(rec(document.catalogs)[kind]).records;
1596
- const found = (Array.isArray(inline) ? inline : []).find((r) => r?.id === id) ?? (extra?.[kind] ?? []).find((r) => rec(r).id === id) ?? catalogs[kind]?.find((r) => rec(r).id === id);
1597
- return found === void 0 ? void 0 : rec(found);
1598
- };
1599
- }
1600
1076
  var PAGE = "#FFFFFF";
1601
1077
  var colorFrom = (scheme, slot, fallback) => typeof slot === "string" ? slot.startsWith("#") ? normalizeHexColor(slot) ?? fallback : normalizeHexColor(scheme[slot]) ?? fallback : fallback;
1602
1078
  function backdropOf(definition, scheme, text, variables) {
1603
1079
  const resolve2 = (value, fallback) => backgroundColorRef(value, scheme, variables, fallback);
1604
1080
  const canvas = defaultSlideBackground(scheme);
1605
1081
  if (!definition) return { kind: "solid", color: canvas };
1082
+ if (imageBackground(definition)) return { kind: "image", opacity: imageBackground(definition)?.opacity ?? 1, base: PAGE };
1606
1083
  if (typeof definition === "string") return { kind: "solid", color: colorFrom(scheme, definition, canvas) };
1607
1084
  const bg = rec(definition), opacity = typeof bg.opacity === "number" ? Math.max(0, Math.min(1, bg.opacity)) : 1;
1608
1085
  switch (bg.type) {
@@ -1653,6 +1130,7 @@ function decisionColor(definition, scheme, variables = {}) {
1653
1130
  if (!definition) return void 0;
1654
1131
  const canvas = defaultSlideBackground(scheme);
1655
1132
  const reference = (value) => backgroundColorRef(value, scheme, variables, PAGE);
1133
+ if (imageBackground(definition)) return void 0;
1656
1134
  if (typeof definition === "string") return colorFrom(scheme, definition, canvas);
1657
1135
  const bg = rec(definition);
1658
1136
  switch (bg.type) {
@@ -1671,15 +1149,15 @@ function resolveDesign(document, index, context) {
1671
1149
  const { options, diagnostics, resolved } = context;
1672
1150
  const { theme, colorScheme, fontScheme, fontSchemePath } = resolved;
1673
1151
  const fontPath = `/${fontSchemePath.split(".").join("/")}`;
1674
- const fontDiagnostic = diagnostics.find((diagnostic) => diagnostic.code === "unresolved-font-scheme");
1152
+ const fontDiagnostic = diagnostics.find((diagnostic) => diagnostic.kind === "fontSchemes");
1675
1153
  const dimensions = { width: options.width, height: options.height };
1676
1154
  const variables = rec(document.variables);
1677
1155
  const roles = resolveColorRoles(colorScheme, { background: decisionColor(own2.background ?? deck.background ?? theme.background, colorScheme, variables) });
1678
1156
  const dark = roles.dark;
1679
1157
  const text = roles.text;
1680
1158
  const backdrop = backdropOf(own2.background ?? deck.background ?? theme.background, colorScheme, text, variables);
1681
- const definition = rec(own2.background ?? deck.background ?? theme.background);
1682
- const backgroundImage = definition.type === "image" && own2.background !== void 0 ? { source: rec(definition.image).src, fit: rec(definition.image).fit ?? "cover", path: `/slides/${index}/design/background/image` } : definition.type === "image" && deck.background !== void 0 ? { source: rec(definition.image).src, fit: rec(definition.image).fit ?? "cover", path: "/design/background/image" } : void 0;
1159
+ const picture = imageBackground(own2.background ?? deck.background ?? theme.background);
1160
+ const backgroundImage = picture && own2.background !== void 0 ? { source: picture.src, fit: picture.fit, path: `/slides/${index}/design/background` } : picture && deck.background !== void 0 ? { source: picture.src, fit: picture.fit, path: "/design/background" } : void 0;
1683
1161
  return {
1684
1162
  theme,
1685
1163
  colorScheme,
@@ -1700,7 +1178,7 @@ function resolveDesign(document, index, context) {
1700
1178
  variables,
1701
1179
  ...backgroundImage ? { backgroundImage } : {},
1702
1180
  fontSchemePath: fontPath,
1703
- ...fontDiagnostic ? { unresolvedFontScheme: fontDiagnostic.id } : {}
1181
+ ...fontDiagnostic ? { unresolvedFontScheme: fontDiagnostic.reference } : {}
1704
1182
  };
1705
1183
  }
1706
1184
  function resolveTextColor(reference, design, fallback) {
@@ -1896,9 +1374,6 @@ function assetRefs(document, slide, slidePath, includeDeck) {
1896
1374
  if (rec(payload.node.quote).photo !== void 0) out.push({ path: at2("quote", "photo"), value: rec(payload.node.quote).photo, kind: "quote-photo" });
1897
1375
  }
1898
1376
  const designRefs = (design, base) => {
1899
- const slideImage = design.slideImage;
1900
- if (slideImage && (typeof slideImage === "string" || typeof slideImage.src === "string"))
1901
- out.push({ path: `${base}/slideImage`, value: slideImage, kind: "slide-image" });
1902
1377
  const logo = design.logo;
1903
1378
  if (typeof logo === "string" || logo && typeof logo.src === "string") out.push({ path: `${base}/logo`, value: logo, kind: "logo" });
1904
1379
  else if (logo && typeof logo === "object") {
@@ -1940,10 +1415,10 @@ var isLarge = (sizePx, bold) => {
1940
1415
  };
1941
1416
  function slideBackdrop(context, box) {
1942
1417
  const { design, composition } = context;
1943
- const image = composition?.slideImage;
1944
- if (image?.position === "background") {
1945
- const overlay = image.overlay && image.overlay.box.width >= composition.width * 0.99 && image.overlay.box.height >= composition.height * 0.99 ? { color: resolveTextColor(image.overlay.color, design, "#000000") ?? "#000000", opacity: image.overlay.opacity } : void 0;
1946
- return backdropColors({ kind: "image", opacity: image.opacity ?? 1, base: design.backdrop.kind === "solid" ? design.backdrop.color : "#FFFFFF" }, box, design.dimensions, { overlay });
1418
+ const image = composition?.backgroundImage;
1419
+ if (image) {
1420
+ const overlay = image.overlay && !image.overlay.edge ? { color: resolveTextColor(image.overlay.color, design, "#000000") ?? "#000000", opacity: image.overlay.opacity } : void 0;
1421
+ return backdropColors({ kind: "image", opacity: image.opacity ?? 1, base: "#FFFFFF" }, box, design.dimensions, { overlay });
1947
1422
  }
1948
1423
  return backdropColors(design.backdrop, box, design.dimensions);
1949
1424
  }
@@ -2055,7 +1530,7 @@ var onImageRule = rule(
2055
1530
  standard: "WCAG 2.2 SC 1.4.3 Contrast (Minimum), level AA",
2056
1531
  thresholds: ["contrastNormal", "contrastLarge"],
2057
1532
  cost: "composition",
2058
- approximations: "Core never reads picture pixels. The picture is bounded by a grey ramp from black to white, composited through the image opacity and a full-frame design.slideImage.overlay; the text passes only when every step of that ramp passes. An edge-banded overlay is not counted."
1533
+ approximations: "Core never reads picture pixels. The picture is bounded by a grey ramp from black to white, composited through the image opacity and a full-frame design.background overlay; the text passes only when every step of that ramp passes. An edge-banded overlay is not counted."
2059
1534
  }
2060
1535
  );
2061
1536
  var contrastRules = [
@@ -2078,7 +1553,7 @@ var contrastRules = [
2078
1553
  path: sample.path,
2079
1554
  slide,
2080
1555
  message: `${sample.label} sits on a background picture. Contrast cannot be guaranteed from the document (against the lightest or darkest possible pixels it is as low as ${worst.ratio.toFixed(1)}:1; ${needed}:1 is needed at this size).`,
2081
- help: "Add a full-frame overlay to design.slideImage (overlay colour and an opacity high enough that text passes on any picture), use a solid or gradient background behind text, or place text on a card.",
1556
+ help: "Add a full-frame overlay to the image background (design.background.overlay: a colour and an opacity high enough that text passes on any picture), use a solid or gradient background behind text, or place text on a card.",
2082
1557
  measured: { ratio: round(worst.ratio), needed, textColor: sample.color }
2083
1558
  });
2084
1559
  }
@@ -2126,7 +1601,7 @@ var altRule = rule(
2126
1601
  'People using a screen reader get nothing for a picture without alternative text. A picture that is purely decorative should say so with an empty alt (alt: ""), which is an explicit, reviewed choice instead of an omission.',
2127
1602
  {
2128
1603
  standard: "WCAG 2.2 SC 1.1.1 Non-text Content, level A",
2129
- approximations: "Checks the alt field of images, video, the slide image, logos (design.logo and each LogoSet variant, organization.logo), header/footer images, quote photos and speaker photos, following asset: references to the assets registry. Whether the text describes the picture well is not judged here (see opf/poor-alt-text). Charts carry `chart.alt` and are checked by opf/chart-text-alternative. Background images and watermarks are decorative by definition and are not checked."
1604
+ approximations: "Checks the alt field of images (image blocks, Slide.image and region images, placed or not), video, logos (design.logo and each LogoSet variant, organization.logo), header/footer images, quote photos and speaker photos, following asset: references to the assets registry. Whether the text describes the picture well is not judged here (see opf/poor-alt-text). Charts carry `chart.alt` and are checked by opf/chart-text-alternative. Picture backgrounds are decorative unless they carry their own alt, and watermarks are decorative, so neither is checked."
2130
1605
  }
2131
1606
  );
2132
1607
  var poorAltRule = rule(
@@ -2140,7 +1615,7 @@ var poorAltRule = rule(
2140
1615
  var GENERIC_ALT = /^(image|picture|photo|photograph|graphic|img|icon|figure|screenshot|untitled|alt|alt text|logo)$/i;
2141
1616
  var FILE_ALT = /(\.(png|jpe?g|gif|svg|webp|bmp|tiff?|heic|avif)$)|^(img|dsc|image|screenshot|screen shot|photo|pic)[ _-]?\d+/i;
2142
1617
  var GENERIC_CHART_ALT = /^((a|an|the)\s+)?([a-z-]+\s+){0,2}(chart|graph|plot|diagram|figure)$/i;
2143
- var kindLabel = (kind) => ({ chart: "Chart", image: "Image", video: "Video", "slide-image": "Slide image", logo: "Logo", furniture: "Header/footer image", speaker: "Speaker photo", "quote-photo": "Quote photo", organization: "Organization logo" })[kind] ?? "Picture";
1618
+ var kindLabel = (kind) => ({ chart: "Chart", image: "Image", video: "Video", logo: "Logo", furniture: "Header/footer image", speaker: "Speaker photo", "quote-photo": "Quote photo", organization: "Organization logo" })[kind] ?? "Picture";
2144
1619
  function altFixes(path, value) {
2145
1620
  const decorative = typeof value === "string" ? { id: "mark-decorative", title: "Mark as decorative (empty alt)", kind: "patch", safe: false, patch: [{ op: "replace", path, value: { src: value, alt: "" } }] } : { id: "mark-decorative", title: "Mark as decorative (empty alt)", kind: "patch", safe: false, patch: [{ op: "add", path: `${path}/alt`, value: "" }] };
2146
1621
  return [{ id: "focus-alt", title: "Write alt text", kind: "focus", safe: true, focus: { path, field: "alt", value } }, decorative];
@@ -2471,7 +1946,7 @@ var languageRule = rule(
2471
1946
  "info",
2472
1947
  "The presentation does not declare its language.",
2473
1948
  "Screen readers and text-to-speech choose pronunciation and hyphenation from the declared language; spell checkers and translation tools use it too.",
2474
- { standard: "WCAG 2.2 SC 3.1.1 Language of Page, level A", approximations: "Only the presentation-level `language` is checked, not the language of individual runs (OPF has no per-run language)." }
1949
+ { standard: "WCAG 2.2 SC 3.1.1 Language of Page, level A", approximations: "Only the presentation-level `language` is checked, not the language of individual runs (`TextRun.lang`; opf/language-tag checks how those tags are written)." }
2475
1950
  );
2476
1951
  var languageRules = [
2477
1952
  {
@@ -2481,7 +1956,7 @@ var languageRules = [
2481
1956
  context.report(languageRule, {
2482
1957
  path: "",
2483
1958
  message: "The presentation has no language set.",
2484
- help: 'Set `language` to a language id from the languages catalog or a BCP 47 tag, for example "en-US".',
1959
+ help: 'Set `language` to a BCP 47 tag, for example "en-US".',
2485
1960
  fixes: [{ id: "focus-language", title: "Set the language", kind: "focus", safe: true, focus: { path: "/language", field: "language" } }]
2486
1961
  });
2487
1962
  }
@@ -2497,6 +1972,105 @@ var accessibilityRules = [
2497
1972
  ...languageRules
2498
1973
  ];
2499
1974
 
1975
+ // src/language-tag.ts
1976
+ var REGION_ALIASES = { UK: "GB" };
1977
+ var privateRegion = /^(?:AA|Q[M-Z]|X[A-Z]|ZZ)$/;
1978
+ var regionNames;
1979
+ var assignedRegion = (region) => {
1980
+ if (privateRegion.test(region)) return true;
1981
+ regionNames ??= new Intl.DisplayNames(["en"], { type: "region", fallback: "code" });
1982
+ try {
1983
+ return regionNames.of(region) !== region;
1984
+ } catch {
1985
+ return false;
1986
+ }
1987
+ };
1988
+ var canonical = (tag) => {
1989
+ try {
1990
+ return Intl.getCanonicalLocales(tag)[0];
1991
+ } catch {
1992
+ return void 0;
1993
+ }
1994
+ };
1995
+ function regionSubtag(tag) {
1996
+ const subtags = tag.split("-");
1997
+ for (let index = 1; index < subtags.length; index++) {
1998
+ const subtag = subtags[index];
1999
+ if (/^(?:[a-z]{2}|\d{3})$/i.test(subtag)) return { region: subtag.toUpperCase(), index };
2000
+ if (!/^[a-z]{3,4}$/i.test(subtag)) return void 0;
2001
+ }
2002
+ return void 0;
2003
+ }
2004
+ function languageTagProblem(tag) {
2005
+ if (/^[ix]-/i.test(tag)) return void 0;
2006
+ const parsed = canonical(tag);
2007
+ if (parsed === void 0) return { message: `'${tag}' is not a well-formed BCP 47 language tag (subtags are letters and digits joined by hyphens, such as 'en-US' or 'zh-Hans').`, caseOnly: false };
2008
+ const found = regionSubtag(tag);
2009
+ if (found) {
2010
+ const alias = REGION_ALIASES[found.region];
2011
+ if (alias || !assignedRegion(found.region)) {
2012
+ const subtags = tag.split("-");
2013
+ subtags[found.index] = alias ?? found.region;
2014
+ const suggestion = alias ? canonical(subtags.join("-")) : void 0;
2015
+ return {
2016
+ message: `'${tag}' names the region '${found.region}', which is not an assigned region${suggestion ? `; '${suggestion}' is the tag for ${new Intl.DisplayNames(["en"], { type: "region" }).of(alias)}` : ""}.`,
2017
+ ...suggestion ? { suggestion } : {},
2018
+ caseOnly: false
2019
+ };
2020
+ }
2021
+ }
2022
+ if (parsed === tag) return void 0;
2023
+ if (parsed.toLowerCase() === tag.toLowerCase())
2024
+ return { message: `'${tag}' is not in canonical case; the canonical form is '${parsed}' (language lower case, script title case, region upper case).`, suggestion: parsed, caseOnly: true };
2025
+ const replacement = found && regionSubtag(parsed)?.region;
2026
+ if (found && replacement && replacement !== found.region) {
2027
+ const subtags = tag.split("-");
2028
+ subtags[found.index] = replacement;
2029
+ return { message: `'${tag}' names the region '${found.region}', which is deprecated; use '${replacement}'.`, suggestion: subtags.join("-"), caseOnly: false };
2030
+ }
2031
+ return void 0;
2032
+ }
2033
+
2034
+ // src/rules-language.ts
2035
+ var languageTagRule = rule(
2036
+ "language-tag",
2037
+ "content",
2038
+ "warning",
2039
+ "A language tag is malformed, uses an unassigned region or is not in canonical case.",
2040
+ "Engines read the deck language and each run's `lang` as BCP 47 tags to choose script fonts, text direction, proofing language and the PowerPoint `lang` attribute. A tag such as `en-a`, or a region that does not exist (`en-UK`; the United Kingdom is `GB`), is guessed at or dropped, and a wrong-case tag (`EN-us`) is not what a locale lookup expects.",
2041
+ {
2042
+ standard: "BCP 47 (RFC 5646, section 2.1) and the platform locale data (Intl.getCanonicalLocales, Intl.DisplayNames)",
2043
+ approximations: "Checks the root `language` (a tag string, or the `bcp47` of an inline Language object) and `lang` on the TextRun objects of titles, subtitles, tags, text, lists, tables, quotes, metrics and timelines. A region is assigned when the runtime's CLDR data names it (ISO 3166-1 codes and UN M.49 areas such as 419); private-use regions (`AA`, `QM`-`QZ`, `XA`-`XZ`) and `ZZ` are accepted, as are private-use (`x-...`) tags. Only the spelling of a tag is checked, not whether the language subtag is assigned or whether the language suits the text. The schema rejects tags with characters other than letters, digits and hyphens (`en_US` is a schema error, with no suggestion), and `en-UK` as the deck language is already an error of the schema rule, so those are not reported twice."
2044
+ }
2045
+ );
2046
+ var report = (context, tag, path, slide) => {
2047
+ if (typeof tag !== "string") return;
2048
+ const problem = languageTagProblem(tag);
2049
+ if (!problem) return;
2050
+ context.report(languageTagRule, {
2051
+ path,
2052
+ slide,
2053
+ message: problem.message,
2054
+ help: problem.suggestion ? `Write '${problem.suggestion}'.` : 'Write a well-formed BCP 47 tag with an assigned region, such as "en-US", "pt-BR" or "es-419".',
2055
+ ...problem.suggestion ? { fixes: [{ id: "use-canonical-tag", title: `Use '${problem.suggestion}'`, kind: "patch", safe: problem.caseOnly, patch: [{ op: "replace", path, value: problem.suggestion }] }] } : {}
2056
+ });
2057
+ };
2058
+ var isEnUk = (tag) => typeof tag === "string" && tag.toLowerCase() === "en-uk";
2059
+ var languageTagRules = [
2060
+ {
2061
+ info: languageTagRule,
2062
+ run(context) {
2063
+ const language = context.document.language;
2064
+ if (typeof language === "string") {
2065
+ if (!isEnUk(language)) report(context, language, "/language");
2066
+ } else if (language !== void 0 && !isEnUk(rec(language).bcp47)) report(context, rec(language).bcp47, "/language/bcp47");
2067
+ for (const slide of context.slides)
2068
+ for (const tv of slide.texts)
2069
+ if (typeof tv.value !== "string") for (const run of runsOf(tv)) report(context, run.style.lang, `${run.path}/lang`, slide);
2070
+ }
2071
+ }
2072
+ ];
2073
+
2500
2074
  // src/rules-layout-content.ts
2501
2075
  var PX_TO_PT2 = 0.75;
2502
2076
  var round2 = (value, places = 1) => Math.round(value * 10 ** places) / 10 ** places;
@@ -2641,7 +2215,7 @@ var resolutionRule = rule(
2641
2215
  {
2642
2216
  cost: "composition",
2643
2217
  thresholds: ["minImagePpi"],
2644
- approximations: "Only embedded data: images (and asset: references to them) have readable pixel sizes; URLs and files are never fetched, so they are not checked. The displayed size is the composed box (cropped images are measured as the cover scale, fitted ones as the contain scale). Effective ppi is the image pixels per inch of the 96 px/inch reference slide. SVG is vector and exempt."
2218
+ approximations: "Only embedded data: images (and asset: references to them) have readable pixel sizes; URLs and files are never fetched, so they are not checked. The displayed size is the composed frame (cover and stretch are measured as the cover scale, contain as the contain scale). Effective ppi is the image pixels per inch of the 96 px/inch reference slide. SVG is vector and exempt."
2645
2219
  }
2646
2220
  );
2647
2221
  var resolutionRules = [
@@ -2669,12 +2243,10 @@ var resolutionRules = [
2669
2243
  for (const slide of context.slides) {
2670
2244
  const composition = slide.composition;
2671
2245
  if (composition) {
2672
- const fill = composition.design.imageFill;
2673
- for (const item of composition.items) if (item.field === "image") check(pointerOfDotted(item.path), item.value, item.box, fill === "crop", slide);
2674
- if (composition.slideImage) check(pointerOfDotted(composition.slideImage.sourcePath), composition.slideImage.value, composition.slideImage.box, composition.slideImage.fill === "crop", slide);
2246
+ for (const item of composition.items) if (item.field === "image") check(pointerOfDotted(item.path), item.value, item.image?.box ?? item.box, item.image?.fit !== "contain", slide);
2675
2247
  }
2676
2248
  const background = slide.design.backgroundImage;
2677
- if (background && background.fit !== "tile") check(background.path, rec({ src: background.source }), slide.design.dimensions, background.fit === "cover", background.path.startsWith("/slides/") ? slide : void 0);
2249
+ if (background && background.fit !== "tile") check(background.path, rec({ src: background.source }), slide.design.dimensions, background.fit !== "contain", background.path.startsWith("/slides/") ? slide : void 0);
2678
2250
  }
2679
2251
  }
2680
2252
  }
@@ -2708,7 +2280,7 @@ var unusedBeatRule = rule(
2708
2280
  "A beat of the narrative has no slide that references it.",
2709
2281
  "The narrative is the plan and the slides are the product. When some slides name a beat (slides[].beat) and a beat of the plan has none, the deck skips a step of the story or the plan is out of date.",
2710
2282
  {
2711
- approximations: "Resolved offline like every catalog reference: the inline catalogs.narratives.records of the document, then records passed in the `catalogs` option, then the bundled catalog. A narrative given as a URL or a pkg: reference, or an id no local source defines, is not checked. A deck in which no slide references any beat is not checked either, because it has not linked its slides to the plan. A slide that lists several beats covers each of them."
2283
+ approximations: "Resolved like every content reference: the document's catalogs groups, then the registered catalogs. A narrative that resolves nowhere is not checked. A deck in which no slide references any beat is not checked either, because it has not linked its slides to the plan. A slide that lists several beats covers each of them."
2712
2284
  }
2713
2285
  );
2714
2286
  var PLACEHOLDERS = [
@@ -2758,7 +2330,7 @@ var contentRules = [
2758
2330
  {
2759
2331
  info: unusedBeatRule,
2760
2332
  run(context) {
2761
- const narrative = resolveNarrative(context.document, context.options.catalogs?.narratives);
2333
+ const narrative = resolveNarrative(context.document, context.options);
2762
2334
  if (!narrative) return;
2763
2335
  const id = String(context.document.narrative);
2764
2336
  for (const { beat, index } of unreferencedBeats(context.document, narrative))
@@ -2778,8 +2350,8 @@ var contentRules = [
2778
2350
  const has = (value) => value !== void 0 && value !== null && !(typeof value === "string" && value.trim() === "") && !(Array.isArray(value) && value.length === 0);
2779
2351
  const hasText = slide.texts.some((tv) => plainText(tv.value).trim() !== "");
2780
2352
  const hasObject = slidePayloads(slide.slide, slide.path).some((p) => ["image", "video", "chart", "table", "code", "metric", "quote", "timeline"].some((field) => has(p.node[field])));
2781
- const hasSlideImage = has(rec(slide.slide.design).slideImage);
2782
- if (hasText || hasObject || hasSlideImage) continue;
2353
+ const hasBackgroundImage = imageBackground(rec(slide.slide.design).background) !== void 0;
2354
+ if (hasText || hasObject || hasBackgroundImage) continue;
2783
2355
  context.report(emptySlideRule, { path: slide.path, slide, message: `Slide ${slide.index + 1} has no title and no content.`, help: "Add content, delete the slide, or give it the blank layout if it is intentionally empty." });
2784
2356
  }
2785
2357
  }
@@ -2840,6 +2412,9 @@ var formatRules = [
2840
2412
  rule("cite-unknown-reference", "format", "error", "A citation names a reference that does not exist.", "A cited id with no entry in `references` has nothing to show in the footnote area.", { standard: SCHEMA }),
2841
2413
  rule("cite-unsupported-location", "format", "error", "A citation or footnote sits where no engine draws it.", "`cite` and `footnote` apply to runs in text, bullets and list items; table cells, captions, and reference and footnote texts have no marker.", { standard: SCHEMA }),
2842
2414
  rule("caption-unsupported-payload", "format", "error", "A caption is on a payload that cannot carry one.", "A caption belongs to exactly one image, chart, table or video; a group, text or code block has no place to draw it.", { standard: SCHEMA }),
2415
+ rule("image-option-unsupported-payload", "format", "error", "An image option is on a payload that is not an image.", "`fit`, `focus`, the image treatments (`shape`, `cornerRadius`, `border`, `opacity`, `recolor`, `overlay`, `aspectRatio`) and `placement` describe how a picture is drawn; a group, text or chart block has no picture for them to apply to.", { standard: SCHEMA }),
2416
+ rule("image-placement-invalid", "format", "error", "An image placement cannot be honoured.", "A placed image bleeds to a slide edge and the rest of the slide composes beside it. Only a top-level block (slides.N.blocks.I) can do that, and a slide edge holds one placed image; a block in a group or a promoted region, or a second block on the same edge, has nowhere to go.", { standard: SCHEMA }),
2417
+ rule("layout-placeholder-group", "format", "error", "A layout record's placeholder group breaks a nesting rule.", "A placeholder group arranges body regions inside one cell of its layout record. Groups nest at most three levels inside a record, and a group holds body regions and groups only: the headings (title, subtitle, tag) stay at the record's top level, where the engine places them. Composition cannot use a record that breaks either rule.", { standard: SCHEMA }),
2843
2418
  rule("dataset-unknown", "format", "error", "A chart or table names a dataset that does not exist.", "The data cannot be drawn without the dataset it refers to.", { standard: SCHEMA }),
2844
2419
  rule("dataset-field-unknown", "format", "error", "A table asks a dataset for a column it does not have.", "The column cannot be shown when the dataset has no column of that name.", { standard: SCHEMA }),
2845
2420
  rule("data-column-duplicate", "format", "error", "A dataset or table has two columns with the same name.", "Series, mappings and fields address columns by name, so a repeated name makes them ambiguous.", { standard: SCHEMA }),
@@ -2876,23 +2451,30 @@ var referenceRules = [
2876
2451
  "catalog-record",
2877
2452
  "references",
2878
2453
  "error",
2879
- "A supplied or inline catalog record is invalid, or repeats an id.",
2880
- "A record that fails its catalog schema cannot be resolved, and a repeated id makes the override ambiguous. Invalid records are left out of the lookup, so the references to them are reported too.",
2454
+ "An embedded or registered catalog record is invalid.",
2455
+ "A record that fails its companion schema cannot be resolved. Invalid records are left out of the lookup, so the references to them are reported too.",
2881
2456
  { standard: "spec/schemas/<kind>.schema.json" }
2882
2457
  ),
2883
2458
  rule(
2884
- "catalog-reference",
2459
+ "unresolved-reference",
2885
2460
  "references",
2886
2461
  "warning",
2887
- "A catalog id is not in the available context.",
2888
- "A theme, layout, font scheme or other catalog id that no record defines falls back to the engine default. Custom ids are legitimate when the host supplies their records, so this is advisory.",
2889
- { approximations: "Resolved against the document's inline records, the records the host passed in `catalogs`, then the bundled catalogs. External sources and `pkg:` records are never fetched. Free-form audience and purpose text, and BCP 47 language tags, are not catalog references." }
2462
+ "A content reference resolves nowhere.",
2463
+ "A layout, theme, colour scheme, font scheme, narrative, audience, purpose or tone reference that neither the document embeds nor a registered catalog defines falls back: a slide composes automatically, a design uses the engine default. A strict export fails instead. A slide with no layout is automatic composition and is never reported.",
2464
+ { standard: SCHEMA, approximations: "Resolved like every engine resolves it: catalogs.custom, then the records embedded under catalogs.default, then the catalog registered for its source (the first registered catalog when `default` is omitted); `name:id` in catalogs.<name>, then the catalog registered for its source. Nothing is fetched. Free-form audience and purpose text is not a reference." }
2890
2465
  ),
2891
- rule("deprecated-catalog-id", "references", "warning", "A catalog id is deprecated.", "A deprecated record still resolves, so nothing breaks, but the replacement is the one that will be maintained.", { standard: "spec/catalogs `deprecation.replacedBy`" }),
2892
- rule("catalog-source", "references", "info", "An external catalog source was not fetched.", "A catalog `source` is a URL or package the checker never loads, so ids defined there are not verified."),
2466
+ rule(
2467
+ "catalog-record-not-in-source",
2468
+ "references",
2469
+ "warning",
2470
+ "A record embedded under a catalog group is not in that catalog.",
2471
+ "A record under catalogs.default or a named group says it came from the catalog its source names. When the catalog the host registered for that source has no record of that kind and id, the record is the document's own and belongs in catalogs.custom, where an update from the catalog never looks for it. Rendering is unchanged: the embedded record still wins.",
2472
+ { standard: SCHEMA, approximations: "Checked only against a catalog the host registered for the group's source (the first registered catalog for an omitted default.source); with none registered for it the check is silent. A record the catalog has with different content is an update difference (updateFromCatalog), not this finding. catalogs.custom is never checked." }
2473
+ ),
2474
+ rule("undeclared-catalog", "format", "error", "A reference names a catalog group the document does not declare.", "The prefix of a `name:id` reference names the group of `catalogs` it resolves in. A prefix with no group can never resolve, whatever the host registers, so it is a format error: engines reject the document at their format check instead of drawing a fallback.", { standard: SCHEMA }),
2893
2475
  rule("unused-reference", "references", "warning", "A reference is never cited.", "A listed reference that no run cites is not drawn anywhere.", { standard: SCHEMA }),
2894
2476
  rule("unused-dataset", "references", "warning", "A dataset is never used.", "A dataset no chart or table references is not drawn anywhere.", { standard: SCHEMA }),
2895
- rule("unknown-beat", "references", "warning", "A slide names a beat its narrative does not define.", "`slides[].beat` links a slide to a step of the narrative plan. A beat id the narrative does not have links to nothing; nothing is drawn from a beat.", { standard: SCHEMA, approximations: "The narrative resolves offline: the inline catalogs.narratives.records, then the `catalogs` option, then the bundled catalog. A narrative no local source defines is not checked." }),
2477
+ rule("unknown-beat", "references", "warning", "A slide names a beat its narrative does not define.", "`slides[].beat` links a slide to a step of the narrative plan. A beat id the narrative does not have links to nothing; nothing is drawn from a beat.", { standard: SCHEMA, approximations: "The narrative resolves like every content reference: the document's catalogs groups, then the registered catalogs. A narrative that resolves nowhere is not checked." }),
2896
2478
  rule("slide-theme-dimensions", "references", "warning", "A slide theme sets a slide size that differs from the deck.", "A PPTX has one slide size. The size a slide-level theme carries is never used: design.dimensions of the deck (or of its theme) wins in every engine, and without them the exporter stops with mixed slide sizes.", { standard: SCHEMA }),
2897
2479
  rule("variable-unknown", "references", "warning", "A `{{token}}` names a variable that is not declared.", "An undeclared token is shown literally.", { standard: SCHEMA }),
2898
2480
  rule("variable-unknown-value", "references", "warning", "A value was supplied for a variable that is not declared.", "The value is never used.", { standard: SCHEMA }),
@@ -2927,7 +2509,7 @@ var dataRules = [
2927
2509
  rule("duration-outside-narrative", "content", "warning", "The target duration is outside the range the narrative suits.", "A narrative records the talk lengths it suits. A deck whose duration is outside that range probably needs a different narrative or a different length; nothing is drawn or exported differently.", { standard: SCHEMA }),
2928
2510
  rule("narrative-duration-range", "content", "warning", "An inline narrative duration range has min greater than max.", "A range with its bounds swapped matches no duration, so the duration check cannot help.", { standard: "spec/schemas/narrative.schema.json" })
2929
2511
  ];
2930
- var implementations = [...accessibilityRules, ...layoutContentRules];
2512
+ var implementations = [...accessibilityRules, ...layoutContentRules, ...languageTagRules];
2931
2513
  var ruleImplementations = implementations;
2932
2514
  var infos = [
2933
2515
  ...formatRules,
@@ -2972,12 +2554,7 @@ function at(value, path) {
2972
2554
  );
2973
2555
  }
2974
2556
  function checkDocumentOptions(options) {
2975
- if (options.catalogs !== void 0 && (!object(options.catalogs) || Object.entries(options.catalogs).some(
2976
- ([kind, records]) => !catalogKinds.includes(kind) || !Array.isArray(records)
2977
- )))
2978
- throw new TypeError(
2979
- "Validate catalogs must map known catalog kinds to arrays of loaded records."
2980
- );
2557
+ checkCatalogsOption(options.catalogs, "validate");
2981
2558
  if (options.contracts !== void 0 && !Array.isArray(options.contracts))
2982
2559
  throw new TypeError("Validate contracts must be an array.");
2983
2560
  for (const contract of options.contracts ?? []) {
@@ -3094,36 +2671,6 @@ function active(cursor, value) {
3094
2671
  const selected = candidates.sort((a, b) => score(b.schema, value) - score(a.schema, value))[0];
3095
2672
  return selected ? { ...selected, baseSchemas: [...selected.baseSchemas ?? [], resolved] } : resolved;
3096
2673
  }
3097
- function catalogIn(schema) {
3098
- const text = String(schema.description ?? "");
3099
- if (/\bfont-scheme id\b/.test(text)) return "fontSchemes";
3100
- return catalogKinds.find(
3101
- (kind) => text.includes(`catalogs.${kind}`) || new RegExp(`\\b${kind}['\u2019]? catalog`).test(text)
3102
- );
3103
- }
3104
- var languageTagSyntax = new RegExp(
3105
- "^(?:(?:[a-z]{2,3}(?:-[a-z]{3}){0,3}|[a-z]{4}|[a-z]{5,8})(?:-[a-z]{4})?(?:-(?:[a-z]{2}|[0-9]{3}))?(?:-(?:[a-z0-9]{5,8}|[0-9][a-z0-9]{3}))*(?:-[0-9a-wy-z](?:-[a-z0-9]{2,8})+)*(?:-x(?:-[a-z0-9]{1,8})+)?|x(?:-[a-z0-9]{1,8})+)$",
3106
- "i"
3107
- );
3108
- var irregularLanguageTags = /* @__PURE__ */ new Set([
3109
- "en-gb-oed",
3110
- "i-ami",
3111
- "i-bnn",
3112
- "i-default",
3113
- "i-enochian",
3114
- "i-hak",
3115
- "i-klingon",
3116
- "i-lux",
3117
- "i-mingo",
3118
- "i-navajo",
3119
- "i-pwn",
3120
- "i-tao",
3121
- "i-tay",
3122
- "i-tsu",
3123
- "sgn-be-fr",
3124
- "sgn-be-nl",
3125
- "sgn-ch-de"
3126
- ]);
3127
2674
  function childCursor(cursors, key) {
3128
2675
  for (const keyword of [
3129
2676
  "properties",
@@ -3164,7 +2711,7 @@ function fields(document) {
3164
2711
  const entry = stack.pop();
3165
2712
  if (!entry) break;
3166
2713
  if (entry.ancestors.includes(entry.value)) continue;
3167
- const resolved = resolve(entry.cursor), selected = active(entry.cursor, entry.value), description = String(resolved.schema.description ?? "");
2714
+ const resolved = resolve(entry.cursor), selected = active(entry.cursor, entry.value);
3168
2715
  const assetReference = [resolved, selected].some(
3169
2716
  (cursor) => cursor.root.$id === schemas.presentation.$id && (cursor.path === "/$defs/Asset" || cursor.path === "/$defs/Asset/oneOf/0" || cursor.path === "/$defs/Watermark/properties/src") || String(cursor.schema.description ?? "").includes("asset:") || Array.isArray(cursor.schema.examples) && cursor.schema.examples.some(
3170
2717
  (example) => typeof example === "string" && example.startsWith("asset:")
@@ -3174,8 +2721,6 @@ function fields(document) {
3174
2721
  path: pointer3(entry.path),
3175
2722
  value: entry.value,
3176
2723
  cursor: resolved,
3177
- kind: catalogIn(resolved.schema),
3178
- description,
3179
2724
  assetReference
3180
2725
  });
3181
2726
  const ancestors = [...entry.ancestors, entry.value];
@@ -3209,74 +2754,39 @@ function fields(document) {
3209
2754
  }
3210
2755
  return result;
3211
2756
  }
3212
- function catalogContext(document, options, findings) {
3213
- const result = /* @__PURE__ */ new Map();
3214
- for (const entry of catalogEntries) {
3215
- const kind = entry.kind, map = /* @__PURE__ */ new Map();
3216
- result.set(kind, map);
3217
- for (const record of entry.records)
3218
- map.set(record.id, {
3219
- value: record.id,
3220
- label: String(
3221
- record.name ?? record.id
3222
- ),
3223
- origin: "built-in",
3224
- definition: `spec/${entry.dir}/${entry.index.records.find((item) => item.id === record.id)?.file ?? `${record.id}.json`}`
3225
- });
3226
- const local = object(document) && object(document.catalogs) && object(document.catalogs[kind]) ? document.catalogs[kind] : {};
3227
- if (local.source !== void 0)
3228
- findings.push(
3229
- finding("opf/catalog-source", {
3230
- path: `/catalogs/${kind}/source`,
3231
- message: "External catalog source was not fetched.",
3232
- help: "This run checks built-in, supplied and inline records only. Load external catalogs in the host and pass their records explicitly to verify that context."
3233
- })
3234
- );
3235
- for (const group of [
3236
- {
3237
- records: options.catalogs?.[kind] ?? [],
3238
- origin: "loaded",
3239
- scope: "context",
3240
- path: `/catalogs/${kind}`
3241
- },
3242
- {
3243
- records: Array.isArray(local.records) ? local.records : [],
3244
- origin: "document",
3245
- scope: "document",
3246
- path: `/catalogs/${kind}/records`
2757
+ var recordKey = (group, kind, id) => `${group}/${kind}/${id}`;
2758
+ function recordIssues(kind, id, value) {
2759
+ const schemaName = catalogSchemaNames[kind];
2760
+ return validateAgainstSchema(object(value) ? { $schema: schemas[schemaName].$id, id, ...value } : value, kind);
2761
+ }
2762
+ function embeddedRecordFindings(document, options, findings) {
2763
+ const invalid = /* @__PURE__ */ new Set();
2764
+ if (!object(document) || !object(document.catalogs)) return invalid;
2765
+ for (const [group, value] of Object.entries(document.catalogs)) {
2766
+ if (!object(value)) continue;
2767
+ for (const kind of catalogKinds)
2768
+ for (const [id, record] of Object.entries(object(value[kind]) ? value[kind] : {})) {
2769
+ const path = pointer3(["catalogs", group, kind, id]);
2770
+ if (object(record) && (Object.hasOwn(record, "$schema") || Object.hasOwn(record, "id"))) continue;
2771
+ const validation = recordIssues(kind, id, record);
2772
+ for (const issue2 of validation.errors) findings.push(issueFinding(issue2, "opf/catalog-record", catalogSchemaNames[kind], path, "document"));
2773
+ if (!validation.valid) invalid.add(recordKey(group, kind, id));
2774
+ if (group === "custom") continue;
2775
+ const source = catalogGroupSource(document, group, options);
2776
+ const registered = registeredCatalog(source, options);
2777
+ if (!registered || Object.hasOwn(registered[kind] ?? {}, id)) continue;
2778
+ findings.push(
2779
+ finding("opf/catalog-record-not-in-source", {
2780
+ path,
2781
+ message: `Record ${JSON.stringify(id)} is embedded under catalogs.${group}.${kind}, but the catalog registered for ${source} has no ${kind} record ${JSON.stringify(id)}; move it to catalogs.custom.`,
2782
+ help: `Move the record to catalogs.custom (and drop the ${group === "default" ? "" : `${group}:`}prefix from its references), or use the id the catalog publishes. The embedded record still renders.`,
2783
+ definition: `${schemas.presentation.$id}#/$defs/Catalogs`,
2784
+ lookup: ["opf", "catalog", kind]
2785
+ })
2786
+ );
3247
2787
  }
3248
- ]) {
3249
- const seen = /* @__PURE__ */ new Set();
3250
- group.records.forEach((value, index) => {
3251
- const path = `${group.path}/${index}`, schemaName = catalogSchemaNames[kind], record = object(value) ? { $schema: schemas[schemaName].$id, ...value } : value;
3252
- const validation = validateAgainstSchema(record, kind);
3253
- for (const issue2 of validation.errors)
3254
- findings.push(issueFinding(issue2, "opf/catalog-record", schemaName, path, group.scope));
3255
- if (!object(value) || typeof value.id !== "string") return;
3256
- if (seen.has(value.id))
3257
- findings.push(
3258
- finding("opf/catalog-record", {
3259
- scope: group.scope,
3260
- path: `${path}/id`,
3261
- message: `Duplicate ${kind} record id ${JSON.stringify(value.id)} in this catalog.`,
3262
- help: "Give distinct records distinct IDs, or combine the intended override explicitly. Preserve their authored content."
3263
- })
3264
- );
3265
- seen.add(value.id);
3266
- if (!validation.valid) {
3267
- map.delete(value.id);
3268
- return;
3269
- }
3270
- map.set(value.id, {
3271
- value: value.id,
3272
- label: typeof value.name === "string" ? value.name : value.id,
3273
- origin: group.origin,
3274
- definition: `${group.scope}#${path}`
3275
- });
3276
- });
3277
- }
3278
2788
  }
3279
- return result;
2789
+ return invalid;
3280
2790
  }
3281
2791
  function distance(a, b) {
3282
2792
  a = a.slice(0, 128);
@@ -3411,14 +2921,59 @@ function variableFindings(document, engine, schemaValid) {
3411
2921
  walk(document, [], 0);
3412
2922
  return findings;
3413
2923
  }
3414
- function hasExternalSource(document, kind) {
3415
- if (!object(document) || !object(document.catalogs)) return false;
3416
- const entry = document.catalogs[kind];
3417
- return object(entry) && (typeof entry.source === "string" || Array.isArray(entry.source) && entry.source.length > 0);
2924
+ function undeclaredCatalogFindings(document) {
2925
+ const findings = [];
2926
+ const reported = /* @__PURE__ */ new Set();
2927
+ for (const site of catalogReferenceSites(document)) {
2928
+ const parsed = parseReference(site.reference);
2929
+ if (parsed?.group === void 0 || catalogGroupDeclared(document, parsed.group)) continue;
2930
+ const path = pointerPath(site.path);
2931
+ if (reported.has(path)) continue;
2932
+ reported.add(path);
2933
+ findings.push(
2934
+ finding("opf/undeclared-catalog", {
2935
+ path,
2936
+ message: `Reference ${JSON.stringify(site.reference)} names catalog group ${JSON.stringify(parsed.group)}, which the document does not declare, so it can never resolve.`,
2937
+ help: `Declare catalogs.${parsed.group} with the catalog's source (and embed the record), or write the reference without the prefix.`,
2938
+ definition: `${schemas.presentation.$id}#/$defs/CatalogReference`,
2939
+ lookup: ["opf", "schema", "presentation", "/$defs/CatalogReference"]
2940
+ })
2941
+ );
2942
+ }
2943
+ return findings;
2944
+ }
2945
+ function contentReferenceFindings(document, options, findings) {
2946
+ const invalid = embeddedRecordFindings(document, options, findings);
2947
+ const registeredChecked = /* @__PURE__ */ new Set();
2948
+ for (const site of catalogReferenceSites(document)) {
2949
+ const path = pointerPath(site.path);
2950
+ const parsed = parseReference(site.reference);
2951
+ if (parsed?.group !== void 0 && !catalogGroupDeclared(document, parsed.group)) continue;
2952
+ const found = resolveReference(document, site.kind, site.reference, { ...options, ...site.group !== void 0 ? { group: site.group } : {} });
2953
+ if (found?.origin === "host" && !registeredChecked.has(recordKey(found.group, site.kind, found.id))) {
2954
+ registeredChecked.add(recordKey(found.group, site.kind, found.id));
2955
+ for (const issue2 of recordIssues(site.kind, found.id, found.record).errors)
2956
+ findings.push(issueFinding(issue2, "opf/catalog-record", catalogSchemaNames[site.kind], pointer3(["catalogs", found.group, site.kind, found.id]), "context"));
2957
+ }
2958
+ if (found && !(found.origin === "document" && invalid.has(recordKey(found.group, site.kind, found.id)))) continue;
2959
+ const diagnostic = unresolvedReference(document, site.kind, site.reference, path, options);
2960
+ const suggestions = catalogRecords(document, site.kind, options).map((entry) => ({ value: entry.reference, label: typeof entry.record.name === "string" ? entry.record.name : entry.reference, origin: entry.origin === "host" ? "registered" : "document", definition: entry.origin === "document" ? `document#${pointer3(["catalogs", entry.group, site.kind, entry.id])}` : `${entry.source ?? "context"}#/${site.kind}/${entry.id}` })).sort((a, b) => distance(site.reference, a.value) - distance(site.reference, b.value) || (a.value < b.value ? -1 : a.value > b.value ? 1 : 0)).slice(0, 5);
2961
+ findings.push(
2962
+ finding("opf/unresolved-reference", {
2963
+ path,
2964
+ message: diagnostic.message,
2965
+ help: site.kind === "layouts" ? "Embed the layout record (embed(), or opf embed), register the catalog that defines it, choose one of the suggested layouts, or remove the reference to compose the slide automatically." : "Embed the record (embed(), or opf embed), register the catalog that defines it, or choose one of the suggested records.",
2966
+ definition: `${schemas.presentation.$id}#/$defs/Catalogs`,
2967
+ lookup: ["opf", "catalog", site.kind],
2968
+ suggestions
2969
+ })
2970
+ );
2971
+ }
3418
2972
  }
3419
2973
  function referenceFindings(document, engine, options) {
3420
2974
  const findings = [];
3421
- const catalogs2 = catalogContext(document, options, findings), seen = /* @__PURE__ */ new Set();
2975
+ const catalogs = { catalogs: options.catalogs ?? [] };
2976
+ contentReferenceFindings(document, catalogs, findings);
3422
2977
  const assetValues = object(document) && object(document.assets) ? document.assets : {};
3423
2978
  for (const field of fields(document)) {
3424
2979
  if (typeof field.value === "string" && field.value.startsWith("asset:") && field.assetReference) {
@@ -3442,30 +2997,6 @@ function referenceFindings(document, engine, options) {
3442
2997
  })
3443
2998
  );
3444
2999
  }
3445
- if (!field.path || !field.kind) continue;
3446
- const { kind } = field;
3447
- if (kind === "languages" && field.path === "/language" && typeof field.value === "string" && (languageTagSyntax.test(field.value) || irregularLanguageTags.has(field.value.toLowerCase())))
3448
- continue;
3449
- if (kind !== "layouts" && /free-form/i.test(field.description)) continue;
3450
- const value = object(field.value) ? field.value.id : field.value, path = object(field.value) ? `${field.path}/id` : field.path;
3451
- if (typeof value !== "string" || !value || /^(?:https?:|pkg:)/i.test(value) || catalogs2.get(kind)?.has(value) || seen.has(path))
3452
- continue;
3453
- seen.add(path);
3454
- if (!options.catalogs?.[kind]?.length && hasExternalSource(document, kind)) continue;
3455
- const suggestions = [...catalogs2.get(kind)?.values() ?? []].sort(
3456
- (a, b) => distance(value, String(a.value)) - distance(value, String(b.value)) || (String(a.value) < String(b.value) ? -1 : String(a.value) > String(b.value) ? 1 : 0)
3457
- ).slice(0, 5);
3458
- const definition = String(field.cursor.root.$id) + `#${field.cursor.path}`;
3459
- findings.push(
3460
- finding("opf/catalog-reference", {
3461
- path,
3462
- message: `Unknown ${kind} catalog id ${JSON.stringify(value)} in the available local context.`,
3463
- help: kind === "layouts" ? "Choose an available layout or supply the intended custom record. Engine-defined layouts may be valid; preview with the target renderer before changing content." : "Choose an available record or supply the intended custom catalog context. External references and native rendering are not verified by this check.",
3464
- definition,
3465
- lookup: ["opf", "catalog", kind],
3466
- suggestions
3467
- })
3468
- );
3469
3000
  }
3470
3001
  const reportedCycles = /* @__PURE__ */ new Set();
3471
3002
  for (const id of Object.keys(assetValues)) {
@@ -3492,33 +3023,7 @@ function referenceFindings(document, engine, options) {
3492
3023
  current = typeof source === "string" && source.startsWith("asset:") ? source.slice(6) : void 0;
3493
3024
  }
3494
3025
  }
3495
- for (const issue2 of engine.warnings) {
3496
- if (typeof issue2.params.code === "string") continue;
3497
- const kind = issue2.params.kind, id = issue2.params.id, replacedBy = issue2.params.replacedBy;
3498
- if (kind === void 0) continue;
3499
- if (typeof replacedBy === "string" && typeof id === "string") {
3500
- const target = catalogs2.get(kind)?.get(replacedBy);
3501
- findings.push({
3502
- ...issueFinding(issue2, "opf/deprecated-catalog-id"),
3503
- message: `Deprecated ${kind} catalog id ${JSON.stringify(id)}; use ${JSON.stringify(replacedBy)} instead.`,
3504
- help: "The deprecated id still resolves to its original record, so nothing breaks. Switch to the replacement when you next edit this reference.",
3505
- lookup: ["opf", "catalog", kind],
3506
- ...target ? { suggestions: [target] } : {}
3507
- });
3508
- continue;
3509
- }
3510
- if (seen.has(issue2.path) || typeof id === "string" && catalogs2.get(kind)?.has(id))
3511
- continue;
3512
- const dir = catalogEntries.find((entry) => entry.kind === kind)?.dir;
3513
- findings.push({
3514
- ...issueFinding(issue2, "opf/catalog-reference"),
3515
- message: `Unknown ${kind} catalog id ${JSON.stringify(id)} in the available local context.`,
3516
- help: "Inspect the referenced catalog and supply the intended record if this is a custom context.",
3517
- ...dir ? { definition: `spec/${dir}/index.json` } : {},
3518
- lookup: ["opf", "catalog", kind]
3519
- });
3520
- }
3521
- findings.push(...narrativeFindings(document, options));
3026
+ findings.push(...narrativeFindings(document, catalogs));
3522
3027
  for (const issue2 of unusedReferenceWarnings(document))
3523
3028
  findings.push(
3524
3029
  finding("opf/unused-reference", {
@@ -3545,16 +3050,16 @@ function referenceFindings(document, engine, options) {
3545
3050
  function narrativeFindings(document, options) {
3546
3051
  const findings = [];
3547
3052
  if (!object(document)) return findings;
3548
- const narrative = resolveNarrative(document, options.catalogs?.narratives);
3053
+ const narrative = resolveNarrative(document, options);
3549
3054
  if (narrative) {
3550
3055
  const id = String(document.narrative);
3551
- const beatSuggestions = (beat) => narrative.beats.slice().sort((a, b) => distance(beat, a) - distance(beat, b) || (a < b ? -1 : a > b ? 1 : 0)).slice(0, 5).map((value) => ({ value, label: value, origin: narrative.origin === "built-in" ? "built-in" : narrative.origin, definition: `narratives/${id}#/beats` }));
3056
+ const beatSuggestions = (beat) => narrative.beats.slice().sort((a, b) => distance(beat, a) - distance(beat, b) || (a < b ? -1 : a > b ? 1 : 0)).slice(0, 5).map((value) => ({ value, label: value, origin: narrative.origin, definition: `narratives/${id}#/beats` }));
3552
3057
  for (const reference of unknownBeatReferences(document, narrative))
3553
3058
  findings.push(
3554
3059
  finding("opf/unknown-beat", {
3555
3060
  path: reference.path,
3556
3061
  message: `Slide ${reference.slide + 1} names beat ${JSON.stringify(reference.beat)}, which narrative ${JSON.stringify(id)} does not define.`,
3557
- help: `Use one of the narrative's beat ids (${narrative.beats.join(", ")}), add the beat to the narrative record (an inline record in catalogs.narratives.records), or remove the beat link. Nothing is drawn from a beat.`,
3062
+ help: `Use one of the narrative's beat ids (${narrative.beats.join(", ")}), add the beat to the narrative record (a record the document embeds or defines in catalogs.custom.narratives), or remove the beat link. Nothing is drawn from a beat.`,
3558
3063
  definition: schemas.presentation.$id + "#/$defs/Slide/properties/beat",
3559
3064
  lookup: ["opf", "catalog", "narratives"],
3560
3065
  suggestions: beatSuggestions(reference.beat)
@@ -3572,20 +3077,19 @@ function narrativeFindings(document, options) {
3572
3077
  })
3573
3078
  );
3574
3079
  }
3575
- const inlineNarratives = object(document.catalogs) && object(document.catalogs.narratives) ? document.catalogs.narratives.records : void 0;
3576
- if (Array.isArray(inlineNarratives))
3577
- inlineNarratives.forEach((record, index) => {
3080
+ for (const [group, value] of Object.entries(object(document.catalogs) ? document.catalogs : {}))
3081
+ for (const [id, record] of Object.entries(object(value) && object(value.narratives) ? value.narratives : {})) {
3578
3082
  if (durationRangeInverted(record))
3579
3083
  findings.push(
3580
3084
  finding("opf/narrative-duration-range", {
3581
- path: `/catalogs/narratives/records/${index}/duration`,
3085
+ path: pointer3(["catalogs", group, "narratives", id, "duration"]),
3582
3086
  message: "The narrative duration range has min greater than max.",
3583
3087
  help: "Swap the bounds so min is the shortest and max the longest talk length, in minutes.",
3584
3088
  definition: schemas.narrative.$id + "#/properties/duration",
3585
3089
  lookup: ["opf", "schema", "narrative", "/properties/duration"]
3586
3090
  })
3587
3091
  );
3588
- });
3092
+ }
3589
3093
  return findings;
3590
3094
  }
3591
3095
  function contractFindings(document, contracts) {
@@ -3742,52 +3246,27 @@ function parseSource(source) {
3742
3246
  }
3743
3247
 
3744
3248
  // src/slide-context.ts
3745
- var DEFAULT_THEME = "minimal";
3746
- var DEFAULT_COLOR_SCHEME = "cool-horizon";
3747
3249
  var rec2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
3748
3250
  function resolveSlideContext(presentation, index, options = {}) {
3749
3251
  const deck = rec2(presentation);
3750
3252
  const slides = Array.isArray(deck.slides) ? deck.slides : [];
3751
3253
  if (!Number.isInteger(index) || index < 0 || index >= slides.length) throw new RangeError(`Slide index ${String(index)} is outside the presentation's ${slides.length} slides.`);
3752
3254
  const slide = rec2(slides[index]);
3753
- const diagnostics = [];
3754
- const lookup = (kind, id) => {
3755
- const inline = rec2(rec2(deck.catalogs)[kind]).records;
3756
- const found = (Array.isArray(inline) ? inline : []).find((record) => record?.id === id) ?? (options.catalogs?.[kind] ?? []).find((record) => rec2(record).id === id) ?? catalogs[kind].find((record) => rec2(record).id === id);
3757
- return found === void 0 ? void 0 : rec2(found);
3758
- };
3255
+ const catalogs = { catalogs: checkCatalogsOption(options.catalogs, "resolveSlideContext") };
3256
+ const records = resolveDesignRecords(presentation, index, catalogs);
3257
+ const diagnostics = [...records.diagnostics];
3258
+ const { theme, colorScheme, fontScheme } = records;
3759
3259
  const design = { ...rec2(deck.design), ...rec2(slide.design) };
3760
- const own2 = rec2(slide.design), shared = rec2(deck.design);
3761
- const resolveReference = (kind, reference, path, code, fallbackId) => {
3762
- const object2 = typeof reference === "object" && reference !== null && !Array.isArray(reference) ? rec2(reference) : {};
3763
- const id = typeof reference === "string" ? reference : typeof object2.id === "string" ? object2.id : void 0;
3764
- if (id === void 0 && typeof reference === "object" && reference !== null && !Array.isArray(reference)) return { ...lookup(kind, fallbackId), ...object2 };
3765
- const found = id === void 0 ? void 0 : lookup(kind, id);
3766
- const inline = Object.keys(object2).some((key) => key !== "id");
3767
- if (id !== void 0 && found === void 0 && !inline) {
3768
- diagnostics.push({
3769
- code,
3770
- path,
3771
- id,
3772
- fallback: fallbackId,
3773
- message: `${kind === "themes" ? "Theme" : "Colour scheme"} '${id}' is not in the inline, host or bundled catalogs; using '${fallbackId}'.`
3774
- });
3775
- return { ...lookup(kind, fallbackId), ...object2 };
3776
- }
3777
- return { ...found, ...object2 };
3778
- };
3779
- const themePath = own2.theme !== void 0 ? `slides.${index}.design.theme` : "design.theme";
3780
- const theme = resolveReference("themes", design.theme ?? DEFAULT_THEME, themePath, "unresolved-theme", DEFAULT_THEME);
3781
- const colorSchemePath = own2.colorScheme !== void 0 ? `slides.${index}.design.colorScheme` : shared.colorScheme !== void 0 ? "design.colorScheme" : themePath;
3782
- const colorScheme = resolveReference("colorSchemes", design.colorScheme ?? theme.colorScheme ?? DEFAULT_COLOR_SCHEME, colorSchemePath, "unresolved-color-scheme", DEFAULT_COLOR_SCHEME);
3783
- const fontSchemePath = own2.fontScheme !== void 0 ? `slides.${index}.design.fontScheme` : shared.fontScheme !== void 0 ? "design.fontScheme" : themePath;
3784
- const font = resolveFontSchemeReference(design.fontScheme ?? theme.fontScheme ?? DEFAULT_FONT_SCHEME, (id) => lookup("fontSchemes", id), fontSchemePath);
3785
- if (font.diagnostic) diagnostics.push(font.diagnostic);
3786
3260
  let layout;
3261
+ let layoutProvenance;
3787
3262
  if (typeof slide.layout === "string") {
3788
- layout = lookup("layouts", slide.layout);
3789
- if (!layout) diagnostics.push({ code: "unresolved-layout", path: `slides.${index}.layout`, id: slide.layout, message: `Layout '${slide.layout}' is not in the inline or bundled catalogs.` });
3263
+ const found = resolveReference(presentation, "layouts", slide.layout, catalogs);
3264
+ if (found) {
3265
+ layout = { ...found.record };
3266
+ layoutProvenance = provenanceOf(found);
3267
+ } else diagnostics.push(unresolvedReference(presentation, "layouts", slide.layout, `slides.${index}.layout`, catalogs));
3790
3268
  }
3269
+ if (options.strictReferences && diagnostics.length) throw new OPFUnresolvedReferenceError(diagnostics);
3791
3270
  const composeOptions = {
3792
3271
  ...resolveCanvasDimensions(design.dimensions ?? theme.dimensions),
3793
3272
  ...layout ? { layout } : {},
@@ -3795,13 +3274,13 @@ function resolveSlideContext(presentation, index, options = {}) {
3795
3274
  slideIndex: index,
3796
3275
  slideNumber: options.slideNumber ?? index + 1,
3797
3276
  slideCount: options.slideCount ?? slides.length,
3798
- fontFamilies: resolveFontFamilies(font.scheme),
3277
+ fontFamilies: resolveFontFamilies(fontScheme),
3799
3278
  darkBackground: resolveColorRoles(colorScheme, { background: decisionColor(design.background ?? theme.background, colorScheme, rec2(deck.variables)) }).dark,
3800
- socialPlatforms: catalogs.socialPlatforms,
3279
+ ...theme.background !== void 0 ? { themeBackground: theme.background } : {},
3801
3280
  ...options.fonts?.textMeasurement ? { textMeasurement: options.fonts.textMeasurement } : {},
3802
3281
  ...options.date !== void 0 ? { date: options.date } : {}
3803
3282
  };
3804
- return { options: composeOptions, diagnostics, resolved: { theme, colorScheme, fontScheme: font.scheme, fontSchemePath } };
3283
+ return { options: composeOptions, diagnostics, resolved: { theme, colorScheme, fontScheme, fontSchemePath: records.fontSchemePath, ...layout ? { layout } : {}, provenance: { ...layoutProvenance ? { layout: layoutProvenance } : {}, ...records.provenance } } };
3805
3284
  }
3806
3285
 
3807
3286
  // src/rule-slides.ts
@@ -3897,12 +3376,12 @@ var OPFValidationError = class extends Error {
3897
3376
  report;
3898
3377
  /** The error-severity findings of the report. */
3899
3378
  findings;
3900
- constructor(report) {
3901
- const errors = report.findings.filter((entry) => entry.severity === "error");
3379
+ constructor(report2) {
3380
+ const errors = report2.findings.filter((entry) => entry.severity === "error");
3902
3381
  const first = errors[0];
3903
3382
  super(first ? `OPF validation failed at ${first.path || "/"}: ${first.message}` : "OPF validation failed");
3904
3383
  this.name = "OPFValidationError";
3905
- this.report = report;
3384
+ this.report = report2;
3906
3385
  this.findings = errors;
3907
3386
  }
3908
3387
  };
@@ -4023,7 +3502,7 @@ function syntaxReport(parsed, run) {
4023
3502
  return { valid: tally.error === 0, schemaValid: null, findings, counts: tally, checks };
4024
3503
  }
4025
3504
  function validateDocument(document, run, parsed) {
4026
- const engine = validateAgainstSchema(document, "presentation", { template: run.options.template, values: run.options.values });
3505
+ const engine = validateAgainstSchema(document, "presentation", { template: run.options.template, values: run.options.values, ...run.options.catalogs ? { catalogs: run.options.catalogs } : {} });
4027
3506
  const schemaValid = engine.errors.length === 0;
4028
3507
  const slides = isObject(document) && Array.isArray(document.slides) ? document.slides : [];
4029
3508
  const auditWanted = auditInfos.some((info) => run.enabled(info));
@@ -4032,6 +3511,7 @@ function validateDocument(document, run, parsed) {
4032
3511
  const findings = [];
4033
3512
  findings.push(...engineFindings(document, engine, (ruleId) => enabledRule(findValidationRule(ruleId))));
4034
3513
  if (enabledRule(findValidationRule("opf/variable-unfilled"))) findings.push(...variableFindings(document, engine, schemaValid));
3514
+ if (enabledRule(findValidationRule("opf/undeclared-catalog"))) findings.push(...undeclaredCatalogFindings(document));
4035
3515
  if (parsed) {
4036
3516
  findings.push(...parsed.duplicates.filter((entry) => enabledRule(findValidationRule(entry.ruleId))));
4037
3517
  }
@@ -4048,13 +3528,11 @@ function validateDocument(document, run, parsed) {
4048
3528
  const auditRan = /* @__PURE__ */ new Set();
4049
3529
  if (schemaValid && auditWanted) {
4050
3530
  const doc = document;
4051
- const lookup = createLookup(doc, run.options.catalogs);
4052
3531
  const context = {
4053
3532
  document: doc,
4054
3533
  slides: buildSlides(doc, run.options),
4055
3534
  thresholds: run.thresholds,
4056
3535
  options: run.options,
4057
- lookup,
4058
3536
  chartPalette: run.chartPalette,
4059
3537
  report(info, input) {
4060
3538
  const severity = run.severity(info) ?? input.severity ?? info.severity;
@@ -4136,16 +3614,14 @@ function validate(input, options = {}) {
4136
3614
  }
4137
3615
  function assertValid(value, options = {}) {
4138
3616
  if (typeof value === "string") throw new TypeError("assertValid takes a parsed presentation. Use validate(text) for JSON text.");
4139
- const report = validate(value, options);
4140
- if (!report.valid) throw new OPFValidationError(report);
3617
+ const report2 = validate(value, options);
3618
+ if (!report2.valid) throw new OPFValidationError(report2);
4141
3619
  }
4142
3620
  function validateCatalogRecord(kind, value) {
4143
- if (!catalogKinds.includes(kind)) throw new TypeError(`Unknown catalog kind ${JSON.stringify(kind)}. Kinds: ${catalogKinds.join(", ")}.`);
3621
+ const kinds = Object.keys(catalogSchemaNames);
3622
+ if (!kinds.includes(kind)) throw new TypeError(`Unknown catalog kind ${JSON.stringify(kind)}. Kinds: ${kinds.join(", ")}.`);
4144
3623
  const engine = validateAgainstSchema(value, kind);
4145
- const findings = [
4146
- ...engine.errors.map((issue2) => issueFinding(issue2, issueRuleId(issue2), engine.schemaName)),
4147
- ...engine.warnings.map((issue2) => issueFinding(issue2, "opf/catalog-reference", engine.schemaName, "", "document", "warning"))
4148
- ];
3624
+ const findings = engine.errors.map((issue2) => issueFinding(issue2, issueRuleId(issue2), engine.schemaName));
4149
3625
  const tally = counts(findings);
4150
3626
  const checks = {
4151
3627
  syntax: "not-applicable",
@@ -4160,8 +3636,8 @@ function validateCatalogRecord(kind, value) {
4160
3636
  return { valid: tally.error === 0, schemaValid: engine.valid, findings, counts: tally, checks };
4161
3637
  }
4162
3638
  function assertValidCatalogRecord(kind, value) {
4163
- const report = validateCatalogRecord(kind, value);
4164
- if (!report.valid) throw new OPFValidationError(report);
3639
+ const report2 = validateCatalogRecord(kind, value);
3640
+ if (!report2.valid) throw new OPFValidationError(report2);
4165
3641
  }
4166
3642
 
4167
- export { DEFAULT_CHART_PALETTE, DEFAULT_VALIDATION_THRESHOLDS, OPFValidationError, assertValid, assertValidCatalogRecord, findValidationRule, resolveSlideContext, validate, validateCatalogRecord, validationCategories, validationRules };
3643
+ export { DEFAULT_CHART_PALETTE, DEFAULT_VALIDATION_THRESHOLDS, OPFValidationError, assertValid, assertValidCatalogRecord, findValidationRule, parseSource, resolveSlideContext, validate, validateCatalogRecord, validationCategories, validationRules };