@openpresentation/opf 0.13.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (611) hide show
  1. package/README.md +40 -49
  2. package/dist/catalog-refs-BSfGELpt.d.ts +128 -0
  3. package/dist/catalog-schemas-CinPh5WA.d.ts +12 -0
  4. package/dist/catalog.d.ts +91 -0
  5. package/dist/{chunk-E34DJ5LL.js → catalog.js} +16949 -17628
  6. package/dist/{chart-data-Cdr6rRVV.d.ts → chart-data-BnNqgnC-.d.ts} +17 -6
  7. package/dist/{chunk-7QBCWNWL.js → chunk-2CJPC4HI.js} +1807 -1904
  8. package/dist/{chunk-II5EYKZU.js → chunk-374R35TJ.js} +2 -92
  9. package/dist/chunk-3JG4YXWF.js +1842 -0
  10. package/dist/chunk-3RKB4RDJ.js +192 -0
  11. package/dist/{chunk-IS2CPNVJ.js → chunk-5FTUIDUH.js} +26 -26
  12. package/dist/{chunk-GJKJL4YY.js → chunk-HXHOZ7AH.js} +1379 -504
  13. package/dist/{chunk-MPM7NRG7.js → chunk-O744SXXA.js} +2014 -885
  14. package/dist/chunk-QDI75P2M.js +3515 -0
  15. package/dist/chunk-SPFV27O5.js +210 -0
  16. package/dist/chunk-TRDXRRQK.js +17 -0
  17. package/dist/chunk-TWQJLI6E.js +601 -0
  18. package/dist/{chunk-ATGDDJPX.js → chunk-U4J75TRG.js} +3 -3
  19. package/dist/{chunk-RAYB46AB.js → chunk-UBFJ2QHH.js} +34 -30
  20. package/dist/chunk-VGAOJEY7.js +94 -0
  21. package/dist/chunk-ZPU46TFA.js +83 -0
  22. package/dist/{composition-D7we69Tr.d.ts → composition-D2tghM82.d.ts} +947 -111
  23. package/dist/composition.d.ts +3 -2
  24. package/dist/composition.js +2 -2
  25. package/dist/convert.d.ts +18 -19
  26. package/dist/convert.js +61 -50
  27. package/dist/data.d.ts +4 -4
  28. package/dist/data.js +3 -3
  29. package/dist/diff.d.ts +4 -4
  30. package/dist/diff.js +7 -605
  31. package/dist/docs.js +56 -44
  32. package/dist/examples.d.ts +1 -1
  33. package/dist/examples.js +49451 -6259
  34. package/dist/finding-CT_f_vbD.d.ts +212 -0
  35. package/dist/format.d.ts +4 -4
  36. package/dist/format.js +2 -83
  37. package/dist/index.d.ts +492 -309
  38. package/dist/index.js +767 -1097
  39. package/dist/json-BFSdESOE.d.ts +19 -0
  40. package/dist/markdown.d.ts +40 -27
  41. package/dist/markdown.js +9 -1941
  42. package/dist/pagination-wedbe7eq.d.ts +119 -0
  43. package/dist/pagination.d.ts +4 -61
  44. package/dist/pagination.js +6 -6
  45. package/dist/patch.d.ts +12 -23
  46. package/dist/patch.js +6 -6
  47. package/dist/{presentation-mG-IYErQ.d.ts → presentation-CDKQVkyI.d.ts} +702 -1026
  48. package/dist/repo-readme.js +1 -1
  49. package/dist/schemas.d.ts +284 -1
  50. package/dist/schemas.js +1 -1
  51. package/dist/spec/README.md +11 -6
  52. package/dist/spec/catalogs/audiences/index.json +2 -50
  53. package/dist/spec/catalogs/chart-types/100pct-stacked-area.json +21 -0
  54. package/dist/spec/catalogs/chart-types/100pct-stacked-bar.json +22 -0
  55. package/dist/spec/catalogs/chart-types/100pct-stacked-column.json +22 -0
  56. package/dist/spec/catalogs/chart-types/area.json +2 -60
  57. package/dist/spec/catalogs/chart-types/bar.json +2 -48
  58. package/dist/spec/catalogs/chart-types/box-and-whisker.json +2 -132
  59. package/dist/spec/catalogs/chart-types/column.json +2 -52
  60. package/dist/spec/catalogs/chart-types/combo.json +37 -0
  61. package/dist/spec/catalogs/chart-types/doughnut.json +2 -40
  62. package/dist/spec/catalogs/chart-types/filled-radar.json +2 -52
  63. package/dist/spec/catalogs/chart-types/funnel.json +2 -46
  64. package/dist/spec/catalogs/chart-types/histogram.json +2 -149
  65. package/dist/spec/catalogs/chart-types/index.json +50 -520
  66. package/dist/spec/catalogs/chart-types/line-with-markers.json +2 -60
  67. package/dist/spec/catalogs/chart-types/line.json +2 -60
  68. package/dist/spec/catalogs/chart-types/pareto.json +2 -50
  69. package/dist/spec/catalogs/chart-types/pie.json +2 -40
  70. package/dist/spec/catalogs/chart-types/radar-with-markers.json +2 -52
  71. package/dist/spec/catalogs/chart-types/radar.json +2 -52
  72. package/dist/spec/catalogs/chart-types/scatter.json +2 -73
  73. package/dist/spec/catalogs/chart-types/stacked-area.json +21 -0
  74. package/dist/spec/catalogs/chart-types/stacked-bar.json +22 -0
  75. package/dist/spec/catalogs/chart-types/stacked-column.json +22 -0
  76. package/dist/spec/catalogs/chart-types/stacked-line-with-markers.json +22 -0
  77. package/dist/spec/catalogs/chart-types/stacked-line.json +21 -0
  78. package/dist/spec/catalogs/chart-types/treemap.json +2 -48
  79. package/dist/spec/catalogs/chart-types/waterfall.json +2 -52
  80. package/dist/spec/catalogs/chart-types/world.json +2 -64
  81. package/dist/spec/catalogs/font-schemes/angsana-new.json +2 -2
  82. package/dist/spec/catalogs/font-schemes/aparajita.json +3 -3
  83. package/dist/spec/catalogs/font-schemes/aptos.json +1 -1
  84. package/dist/spec/catalogs/font-schemes/arabic-typesetting.json +2 -2
  85. package/dist/spec/catalogs/font-schemes/arial.json +1 -1
  86. package/dist/spec/catalogs/font-schemes/batang.json +2 -2
  87. package/dist/spec/catalogs/font-schemes/bookman.json +1 -1
  88. package/dist/spec/catalogs/font-schemes/calibri.json +1 -1
  89. package/dist/spec/catalogs/font-schemes/century-schoolbook.json +1 -1
  90. package/dist/spec/catalogs/font-schemes/consolas.json +2 -4
  91. package/dist/spec/catalogs/font-schemes/constantia.json +1 -1
  92. package/dist/spec/catalogs/font-schemes/courier-new.json +2 -4
  93. package/dist/spec/catalogs/font-schemes/daunpenh.json +2 -2
  94. package/dist/spec/catalogs/font-schemes/david.json +2 -2
  95. package/dist/spec/catalogs/font-schemes/dilleniaupc.json +2 -2
  96. package/dist/spec/catalogs/font-schemes/fangsong.json +2 -2
  97. package/dist/spec/catalogs/font-schemes/garamond.json +1 -1
  98. package/dist/spec/catalogs/font-schemes/gautami.json +2 -2
  99. package/dist/spec/catalogs/font-schemes/georgia.json +1 -1
  100. package/dist/spec/catalogs/font-schemes/gisha.json +3 -3
  101. package/dist/spec/catalogs/font-schemes/grandview.json +1 -1
  102. package/dist/spec/catalogs/font-schemes/gungsuh.json +2 -2
  103. package/dist/spec/catalogs/font-schemes/impact.json +1 -1
  104. package/dist/spec/catalogs/font-schemes/index.json +1 -1
  105. package/dist/spec/catalogs/font-schemes/kalinga.json +2 -2
  106. package/dist/spec/catalogs/font-schemes/kartika.json +2 -2
  107. package/dist/spec/catalogs/font-schemes/khmer-ui.json +2 -2
  108. package/dist/spec/catalogs/font-schemes/latha.json +2 -2
  109. package/dist/spec/catalogs/font-schemes/lucida-sans.json +1 -1
  110. package/dist/spec/catalogs/font-schemes/malgun-gothic.json +2 -2
  111. package/dist/spec/catalogs/font-schemes/mangal.json +4 -5
  112. package/dist/spec/catalogs/font-schemes/meiryo.json +2 -2
  113. package/dist/spec/catalogs/font-schemes/microsoft-jhenghei.json +2 -2
  114. package/dist/spec/catalogs/font-schemes/microsoft-yahei.json +5 -5
  115. package/dist/spec/catalogs/font-schemes/mingliu.json +2 -2
  116. package/dist/spec/catalogs/font-schemes/miriam.json +2 -2
  117. package/dist/spec/catalogs/font-schemes/montserrat.json +1 -1
  118. package/dist/spec/catalogs/font-schemes/ms-mincho.json +2 -2
  119. package/dist/spec/catalogs/font-schemes/nirmala-ui.json +2 -2
  120. package/dist/spec/catalogs/font-schemes/noto-naksh-arabic.json +2 -2
  121. package/dist/spec/catalogs/font-schemes/noto-nastaliq-urdu.json +3 -3
  122. package/dist/spec/catalogs/font-schemes/noto-sans-arabic.json +4 -4
  123. package/dist/spec/catalogs/font-schemes/noto-sans-armenian.json +2 -2
  124. package/dist/spec/catalogs/font-schemes/noto-sans-bengali.json +3 -3
  125. package/dist/spec/catalogs/font-schemes/noto-sans-devangari.json +3 -3
  126. package/dist/spec/catalogs/font-schemes/noto-sans-ethiopic.json +2 -2
  127. package/dist/spec/catalogs/font-schemes/noto-sans-georgian.json +2 -2
  128. package/dist/spec/catalogs/font-schemes/noto-sans-gujarati.json +2 -2
  129. package/dist/spec/catalogs/font-schemes/noto-sans-gurmukhi.json +2 -2
  130. package/dist/spec/catalogs/font-schemes/noto-sans-hebrew.json +2 -2
  131. package/dist/spec/catalogs/font-schemes/noto-sans-jp.json +2 -2
  132. package/dist/spec/catalogs/font-schemes/noto-sans-kannada.json +2 -2
  133. package/dist/spec/catalogs/font-schemes/noto-sans-khmer.json +2 -2
  134. package/dist/spec/catalogs/font-schemes/noto-sans-kr.json +2 -2
  135. package/dist/spec/catalogs/font-schemes/noto-sans-malayalam.json +2 -2
  136. package/dist/spec/catalogs/font-schemes/noto-sans-mongolian.json +2 -2
  137. package/dist/spec/catalogs/font-schemes/noto-sans-oriya.json +2 -2
  138. package/dist/spec/catalogs/font-schemes/noto-sans-sc.json +5 -5
  139. package/dist/spec/catalogs/font-schemes/noto-sans-tamil.json +2 -2
  140. package/dist/spec/catalogs/font-schemes/noto-sans-tc.json +2 -2
  141. package/dist/spec/catalogs/font-schemes/noto-sans-telugu.json +2 -2
  142. package/dist/spec/catalogs/font-schemes/noto-sans-thai.json +2 -2
  143. package/dist/spec/catalogs/font-schemes/noto-sans.json +5 -5
  144. package/dist/spec/catalogs/font-schemes/nyala.json +2 -2
  145. package/dist/spec/catalogs/font-schemes/open-sans.json +1 -1
  146. package/dist/spec/catalogs/font-schemes/pmingliu.json +2 -2
  147. package/dist/spec/catalogs/font-schemes/poppins.json +1 -1
  148. package/dist/spec/catalogs/font-schemes/pt-serif.json +1 -1
  149. package/dist/spec/catalogs/font-schemes/raavi.json +2 -2
  150. package/dist/spec/catalogs/font-schemes/raleway.json +1 -1
  151. package/dist/spec/catalogs/font-schemes/roboto.json +1 -1
  152. package/dist/spec/catalogs/font-schemes/rockwell.json +1 -1
  153. package/dist/spec/catalogs/font-schemes/sakkal-majalla.json +4 -4
  154. package/dist/spec/catalogs/font-schemes/seaford.json +1 -1
  155. package/dist/spec/catalogs/font-schemes/segoe-ui-light.json +1 -1
  156. package/dist/spec/catalogs/font-schemes/segoe-ui.json +1 -1
  157. package/dist/spec/catalogs/font-schemes/shonar-bangla.json +3 -3
  158. package/dist/spec/catalogs/font-schemes/shruti.json +2 -2
  159. package/dist/spec/catalogs/font-schemes/simsun.json +2 -2
  160. package/dist/spec/catalogs/font-schemes/skeena.json +1 -1
  161. package/dist/spec/catalogs/font-schemes/sylfaen.json +3 -3
  162. package/dist/spec/catalogs/font-schemes/tahoma.json +1 -1
  163. package/dist/spec/catalogs/font-schemes/tenorite.json +1 -1
  164. package/dist/spec/catalogs/font-schemes/times-new-roman.json +1 -1
  165. package/dist/spec/catalogs/font-schemes/traditional-arabic.json +4 -4
  166. package/dist/spec/catalogs/font-schemes/trebuchet-ms.json +1 -1
  167. package/dist/spec/catalogs/font-schemes/tunga.json +2 -2
  168. package/dist/spec/catalogs/font-schemes/verdana.json +1 -1
  169. package/dist/spec/catalogs/font-schemes/vrinda.json +2 -2
  170. package/dist/spec/catalogs/font-schemes/yu-gothic.json +2 -2
  171. package/dist/spec/catalogs/layouts/action-plan.json +9 -15
  172. package/dist/spec/catalogs/layouts/agenda.json +8 -15
  173. package/dist/spec/catalogs/layouts/appendix-index.json +7 -15
  174. package/dist/spec/catalogs/layouts/architecture-stack.json +9 -15
  175. package/dist/spec/catalogs/layouts/assumptions-dependencies.json +9 -15
  176. package/dist/spec/catalogs/layouts/before-after.json +9 -15
  177. package/dist/spec/catalogs/layouts/benchmark-target-table.json +9 -15
  178. package/dist/spec/catalogs/layouts/blank.json +11 -1
  179. package/dist/spec/catalogs/layouts/bullet-list.json +9 -15
  180. package/dist/spec/catalogs/layouts/business-model-canvas.json +9 -15
  181. package/dist/spec/catalogs/layouts/capability-map.json +11 -16
  182. package/dist/spec/catalogs/layouts/case-study.json +9 -15
  183. package/dist/spec/catalogs/layouts/chapter-recap.json +8 -15
  184. package/dist/spec/catalogs/layouts/chart-1x-title-center.json +30 -0
  185. package/dist/spec/catalogs/layouts/chart-1x.json +15 -0
  186. package/dist/spec/catalogs/layouts/chart-2x-title-center.json +36 -0
  187. package/dist/spec/catalogs/layouts/chart-2x-vertical-title-left.json +37 -0
  188. package/dist/spec/catalogs/layouts/chart-2x.json +15 -0
  189. package/dist/spec/catalogs/layouts/chart-3x-bottom-vertical-title-left.json +46 -0
  190. package/dist/spec/catalogs/layouts/chart-3x-left-title-center.json +45 -0
  191. package/dist/spec/catalogs/layouts/chart-3x-left.json +40 -0
  192. package/dist/spec/catalogs/layouts/chart-3x-right-title-center.json +45 -0
  193. package/dist/spec/catalogs/layouts/chart-3x-right.json +40 -0
  194. package/dist/spec/catalogs/layouts/chart-3x-title-center.json +39 -0
  195. package/dist/spec/catalogs/layouts/chart-3x-top-vertical-title-left.json +46 -0
  196. package/dist/spec/catalogs/layouts/chart-3x.json +15 -0
  197. package/dist/spec/catalogs/layouts/circular-lifecycle.json +9 -15
  198. package/dist/spec/catalogs/layouts/code-1x.json +10 -0
  199. package/dist/spec/catalogs/layouts/comparison.json +9 -15
  200. package/dist/spec/catalogs/layouts/competitive-landscape.json +11 -16
  201. package/dist/spec/catalogs/layouts/customer-journey.json +11 -16
  202. package/dist/spec/catalogs/layouts/customer-logos.json +9 -15
  203. package/dist/spec/catalogs/layouts/dashboard.json +9 -15
  204. package/dist/spec/catalogs/layouts/data-visualization.json +9 -15
  205. package/dist/spec/catalogs/layouts/decision-tree.json +11 -16
  206. package/dist/spec/catalogs/layouts/detailed-data-table.json +9 -15
  207. package/dist/spec/catalogs/layouts/ecosystem-map.json +11 -16
  208. package/dist/spec/catalogs/layouts/executive-decision.json +9 -15
  209. package/dist/spec/catalogs/layouts/executive-summary.json +9 -15
  210. package/dist/spec/catalogs/layouts/extract/canonical.json +7 -16
  211. package/dist/spec/catalogs/layouts/faq.json +9 -15
  212. package/dist/spec/catalogs/layouts/feature-comparison-matrix.json +9 -15
  213. package/dist/spec/catalogs/layouts/forecast-scenario.json +9 -15
  214. package/dist/spec/catalogs/layouts/funnel-pyramid.json +9 -15
  215. package/dist/spec/catalogs/layouts/gantt-project-plan.json +9 -15
  216. package/dist/spec/catalogs/layouts/geographic-map.json +10 -15
  217. package/dist/spec/catalogs/layouts/heatmap-cohort.json +9 -15
  218. package/dist/spec/catalogs/layouts/hub-and-spoke.json +9 -15
  219. package/dist/spec/catalogs/layouts/icon-grid.json +11 -16
  220. package/dist/spec/catalogs/layouts/image-1x-crop-title-center.json +32 -0
  221. package/dist/spec/catalogs/layouts/image-1x-crop-title-left.json +32 -0
  222. package/dist/spec/catalogs/layouts/image-1x-crop.json +27 -0
  223. package/dist/spec/catalogs/layouts/image-1x-fit-title-center.json +32 -0
  224. package/dist/spec/catalogs/layouts/image-1x.json +14 -1
  225. package/dist/spec/catalogs/layouts/image-2x-crop-title-center.json +38 -0
  226. package/dist/spec/catalogs/layouts/image-2x-crop-vertical-title-left.json +39 -0
  227. package/dist/spec/catalogs/layouts/image-2x-crop-vertical.json +34 -0
  228. package/dist/spec/catalogs/layouts/image-2x-crop.json +33 -0
  229. package/dist/spec/catalogs/layouts/image-2x-fit-title-center.json +38 -0
  230. package/dist/spec/catalogs/layouts/image-2x-fit-vertical-title-left.json +39 -0
  231. package/dist/spec/catalogs/layouts/image-2x-fit-vertical.json +34 -0
  232. package/dist/spec/catalogs/layouts/image-2x.json +15 -2
  233. package/dist/spec/catalogs/layouts/image-3x-crop-title-center.json +41 -0
  234. package/dist/spec/catalogs/layouts/image-3x-crop-vertical-title-left.json +42 -0
  235. package/dist/spec/catalogs/layouts/image-3x-crop-vertical.json +37 -0
  236. package/dist/spec/catalogs/layouts/image-3x-crop.json +36 -0
  237. package/dist/spec/catalogs/layouts/image-3x-fit-title-center.json +41 -0
  238. package/dist/spec/catalogs/layouts/image-3x-fit-vertical-title-left.json +42 -0
  239. package/dist/spec/catalogs/layouts/image-3x-fit-vertical.json +37 -0
  240. package/dist/spec/catalogs/layouts/image-3x-fit.json +36 -0
  241. package/dist/spec/catalogs/layouts/image-3x.json +16 -3
  242. package/dist/spec/catalogs/layouts/image-focus.json +13 -18
  243. package/dist/spec/catalogs/layouts/image-only-1x-crop-title-center.json +31 -0
  244. package/dist/spec/catalogs/layouts/image-only-1x-fit-title-center.json +31 -0
  245. package/dist/spec/catalogs/layouts/image-only-2x-crop-title-center.json +37 -0
  246. package/dist/spec/catalogs/layouts/image-only-2x-crop-vertical-title-left.json +38 -0
  247. package/dist/spec/catalogs/layouts/image-only-2x-crop-vertical.json +33 -0
  248. package/dist/spec/catalogs/layouts/image-only-2x-fit-title-center.json +37 -0
  249. package/dist/spec/catalogs/layouts/image-only-2x-fit-vertical-title-left.json +38 -0
  250. package/dist/spec/catalogs/layouts/image-only-2x-fit-vertical.json +33 -0
  251. package/dist/spec/catalogs/layouts/image-only-3x-crop-title-center.json +40 -0
  252. package/dist/spec/catalogs/layouts/image-only-3x-crop-vertical-title-left.json +41 -0
  253. package/dist/spec/catalogs/layouts/image-only-3x-crop-vertical.json +36 -0
  254. package/dist/spec/catalogs/layouts/image-only-3x-crop.json +35 -0
  255. package/dist/spec/catalogs/layouts/image-only-3x-fit-title-center.json +40 -0
  256. package/dist/spec/catalogs/layouts/image-only-3x-fit-vertical-title-left.json +41 -0
  257. package/dist/spec/catalogs/layouts/image-only-3x-fit-vertical.json +36 -0
  258. package/dist/spec/catalogs/layouts/image-only-3x-fit.json +35 -0
  259. package/dist/spec/catalogs/layouts/index.json +1041 -151
  260. package/dist/spec/catalogs/layouts/key-takeaway.json +8 -15
  261. package/dist/spec/catalogs/layouts/kpi-scorecard.json +9 -15
  262. package/dist/spec/catalogs/layouts/list-1x-box-title-center.json +32 -0
  263. package/dist/spec/catalogs/layouts/list-1x-box-vertical-title-center.json +33 -0
  264. package/dist/spec/catalogs/layouts/list-1x-box-vertical-title-left.json +33 -0
  265. package/dist/spec/catalogs/layouts/list-1x-box-vertical.json +28 -0
  266. package/dist/spec/catalogs/layouts/list-1x-box.json +27 -0
  267. package/dist/spec/catalogs/layouts/list-1x-itemimage-title-center.json +32 -0
  268. package/dist/spec/catalogs/layouts/list-1x-itemimage-vertical-title-center.json +33 -0
  269. package/dist/spec/catalogs/layouts/list-1x-title-center.json +31 -0
  270. package/dist/spec/catalogs/layouts/list-1x-vertical-title-center.json +32 -0
  271. package/dist/spec/catalogs/layouts/list-1x.json +17 -1
  272. package/dist/spec/catalogs/layouts/list-2x-box-title-center.json +38 -0
  273. package/dist/spec/catalogs/layouts/list-2x-box-vertical-title-center.json +39 -0
  274. package/dist/spec/catalogs/layouts/list-2x-box-vertical-title-left.json +39 -0
  275. package/dist/spec/catalogs/layouts/list-2x-box-vertical.json +34 -0
  276. package/dist/spec/catalogs/layouts/list-2x-box.json +33 -0
  277. package/dist/spec/catalogs/layouts/list-2x-itemimage-title-center.json +38 -0
  278. package/dist/spec/catalogs/layouts/list-2x-itemimage-vertical-title-center.json +39 -0
  279. package/dist/spec/catalogs/layouts/list-2x-itemimage-vertical-title-left.json +39 -0
  280. package/dist/spec/catalogs/layouts/list-2x-itemimage-vertical.json +34 -0
  281. package/dist/spec/catalogs/layouts/list-2x-title-center.json +37 -0
  282. package/dist/spec/catalogs/layouts/list-2x-vertical-title-center.json +38 -0
  283. package/dist/spec/catalogs/layouts/list-2x-vertical-title-left.json +38 -0
  284. package/dist/spec/catalogs/layouts/list-2x-vertical.json +33 -0
  285. package/dist/spec/catalogs/layouts/list-2x.json +16 -0
  286. package/dist/spec/catalogs/layouts/list-3x-box-title-center.json +41 -0
  287. package/dist/spec/catalogs/layouts/list-3x-box-vertical-title-center.json +42 -0
  288. package/dist/spec/catalogs/layouts/list-3x-box-vertical-title-left.json +42 -0
  289. package/dist/spec/catalogs/layouts/list-3x-box-vertical.json +37 -0
  290. package/dist/spec/catalogs/layouts/list-3x-box.json +36 -0
  291. package/dist/spec/catalogs/layouts/list-3x-itemimage-title-center.json +41 -0
  292. package/dist/spec/catalogs/layouts/list-3x-itemimage-vertical-title-center.json +42 -0
  293. package/dist/spec/catalogs/layouts/list-3x-itemimage-vertical-title-left.json +42 -0
  294. package/dist/spec/catalogs/layouts/list-3x-itemimage-vertical.json +37 -0
  295. package/dist/spec/catalogs/layouts/list-3x-itemimage.json +36 -0
  296. package/dist/spec/catalogs/layouts/list-3x-title-center.json +40 -0
  297. package/dist/spec/catalogs/layouts/list-3x-vertical-title-center.json +41 -0
  298. package/dist/spec/catalogs/layouts/list-3x-vertical-title-left.json +41 -0
  299. package/dist/spec/catalogs/layouts/list-3x-vertical.json +36 -0
  300. package/dist/spec/catalogs/layouts/list-3x.json +16 -0
  301. package/dist/spec/catalogs/layouts/list-4x-box-title-center.json +45 -0
  302. package/dist/spec/catalogs/layouts/list-4x-box-vertical-title-center.json +45 -0
  303. package/dist/spec/catalogs/layouts/list-4x-box-vertical-title-left.json +45 -0
  304. package/dist/spec/catalogs/layouts/list-4x-box-vertical.json +40 -0
  305. package/dist/spec/catalogs/layouts/list-4x-box.json +40 -0
  306. package/dist/spec/catalogs/layouts/list-4x-itemimage-title-center.json +45 -0
  307. package/dist/spec/catalogs/layouts/list-4x-itemimage-vertical-title-center.json +45 -0
  308. package/dist/spec/catalogs/layouts/list-4x-itemimage-vertical-title-left.json +45 -0
  309. package/dist/spec/catalogs/layouts/list-4x-itemimage-vertical.json +40 -0
  310. package/dist/spec/catalogs/layouts/list-4x-title-center.json +44 -0
  311. package/dist/spec/catalogs/layouts/list-4x-vertical-title-center.json +44 -0
  312. package/dist/spec/catalogs/layouts/list-4x-vertical-title-left.json +44 -0
  313. package/dist/spec/catalogs/layouts/list-4x-vertical.json +39 -0
  314. package/dist/spec/catalogs/layouts/list-4x.json +16 -0
  315. package/dist/spec/catalogs/layouts/list-5x-box-title-center.json +48 -0
  316. package/dist/spec/catalogs/layouts/list-5x-box-vertical-title-center.json +50 -0
  317. package/dist/spec/catalogs/layouts/list-5x-box-vertical-title-left.json +50 -0
  318. package/dist/spec/catalogs/layouts/list-5x-box-vertical.json +45 -0
  319. package/dist/spec/catalogs/layouts/list-5x-box.json +43 -0
  320. package/dist/spec/catalogs/layouts/list-5x-itemimage-title-center.json +48 -0
  321. package/dist/spec/catalogs/layouts/list-5x-itemimage-vertical-title-center.json +48 -0
  322. package/dist/spec/catalogs/layouts/list-5x-itemimage-vertical-title-left.json +48 -0
  323. package/dist/spec/catalogs/layouts/list-5x-itemimage-vertical.json +43 -0
  324. package/dist/spec/catalogs/layouts/list-5x-title-center.json +47 -0
  325. package/dist/spec/catalogs/layouts/list-5x-vertical-title-center.json +47 -0
  326. package/dist/spec/catalogs/layouts/list-5x-vertical-title-left.json +47 -0
  327. package/dist/spec/catalogs/layouts/list-5x-vertical.json +42 -0
  328. package/dist/spec/catalogs/layouts/list-5x.json +16 -0
  329. package/dist/spec/catalogs/layouts/list-6x-box-title-center.json +51 -0
  330. package/dist/spec/catalogs/layouts/list-6x-box-vertical-title-center.json +53 -0
  331. package/dist/spec/catalogs/layouts/list-6x-box-vertical-title-left.json +53 -0
  332. package/dist/spec/catalogs/layouts/list-6x-box-vertical.json +48 -0
  333. package/dist/spec/catalogs/layouts/list-6x-box.json +46 -0
  334. package/dist/spec/catalogs/layouts/list-6x-itemimage-title-center.json +51 -0
  335. package/dist/spec/catalogs/layouts/list-6x-itemimage-vertical-title-center.json +51 -0
  336. package/dist/spec/catalogs/layouts/list-6x-itemimage-vertical-title-left.json +51 -0
  337. package/dist/spec/catalogs/layouts/list-6x-itemimage-vertical.json +46 -0
  338. package/dist/spec/catalogs/layouts/list-6x-title-center.json +50 -0
  339. package/dist/spec/catalogs/layouts/list-6x-vertical-title-center.json +50 -0
  340. package/dist/spec/catalogs/layouts/list-6x-vertical-title-left.json +50 -0
  341. package/dist/spec/catalogs/layouts/list-6x-vertical.json +45 -0
  342. package/dist/spec/catalogs/layouts/list-6x.json +16 -0
  343. package/dist/spec/catalogs/layouts/logic-tree.json +9 -15
  344. package/dist/spec/catalogs/layouts/market-size.json +9 -15
  345. package/dist/spec/catalogs/layouts/matrix-quadrant.json +9 -15
  346. package/dist/spec/catalogs/layouts/media-1x.json +11 -1
  347. package/dist/spec/catalogs/layouts/network-dependency-map.json +10 -15
  348. package/dist/spec/catalogs/layouts/number-1x-box-title-center.json +30 -0
  349. package/dist/spec/catalogs/layouts/number-1x-box.json +25 -0
  350. package/dist/spec/catalogs/layouts/number-1x-title-center.json +29 -0
  351. package/dist/spec/catalogs/layouts/number-1x.json +16 -1
  352. package/dist/spec/catalogs/layouts/number-2x-box-title-center.json +36 -0
  353. package/dist/spec/catalogs/layouts/number-2x-box.json +31 -0
  354. package/dist/spec/catalogs/layouts/number-2x-title-center.json +35 -0
  355. package/dist/spec/catalogs/layouts/number-2x.json +15 -0
  356. package/dist/spec/catalogs/layouts/number-3x-box-title-center.json +39 -0
  357. package/dist/spec/catalogs/layouts/number-3x-box.json +34 -0
  358. package/dist/spec/catalogs/layouts/number-3x-title-center.json +38 -0
  359. package/dist/spec/catalogs/layouts/number-3x.json +15 -0
  360. package/dist/spec/catalogs/layouts/number-4x-box-title-center.json +43 -0
  361. package/dist/spec/catalogs/layouts/number-4x-box.json +38 -0
  362. package/dist/spec/catalogs/layouts/number-4x-title-center.json +42 -0
  363. package/dist/spec/catalogs/layouts/number-4x.json +15 -0
  364. package/dist/spec/catalogs/layouts/number-5x-box-title-center.json +46 -0
  365. package/dist/spec/catalogs/layouts/number-5x-box.json +41 -0
  366. package/dist/spec/catalogs/layouts/number-5x-title-center.json +45 -0
  367. package/dist/spec/catalogs/layouts/number-5x.json +15 -0
  368. package/dist/spec/catalogs/layouts/number-6x-box-title-center.json +49 -0
  369. package/dist/spec/catalogs/layouts/number-6x-box.json +44 -0
  370. package/dist/spec/catalogs/layouts/number-6x-title-center.json +48 -0
  371. package/dist/spec/catalogs/layouts/number-6x.json +15 -0
  372. package/dist/spec/catalogs/layouts/one-column-explainer.json +9 -15
  373. package/dist/spec/catalogs/layouts/org-chart.json +9 -15
  374. package/dist/spec/catalogs/layouts/persona-grid.json +11 -16
  375. package/dist/spec/catalogs/layouts/photo-grid.json +14 -19
  376. package/dist/spec/catalogs/layouts/pricing-table.json +9 -15
  377. package/dist/spec/catalogs/layouts/process-flow.json +9 -15
  378. package/dist/spec/catalogs/layouts/product-feature.json +11 -16
  379. package/dist/spec/catalogs/layouts/pros-cons.json +9 -15
  380. package/dist/spec/catalogs/layouts/qa-discussion.json +7 -15
  381. package/dist/spec/catalogs/layouts/quote-1x.json +11 -1
  382. package/dist/spec/catalogs/layouts/quote-highlight.json +9 -15
  383. package/dist/spec/catalogs/layouts/raci-matrix.json +9 -15
  384. package/dist/spec/catalogs/layouts/risk-heatmap.json +9 -15
  385. package/dist/spec/catalogs/layouts/roadmap.json +9 -15
  386. package/dist/spec/catalogs/layouts/screenshot-walkthrough.json +11 -16
  387. package/dist/spec/catalogs/layouts/section-divider.json +8 -15
  388. package/dist/spec/catalogs/layouts/single-message-statement.json +8 -15
  389. package/dist/spec/catalogs/layouts/stats-metrics.json +9 -15
  390. package/dist/spec/catalogs/layouts/status-rag.json +9 -15
  391. package/dist/spec/catalogs/layouts/swimlane-process.json +11 -16
  392. package/dist/spec/catalogs/layouts/swot-analysis.json +9 -15
  393. package/dist/spec/catalogs/layouts/table-1x.json +10 -0
  394. package/dist/spec/catalogs/layouts/team-bio.json +11 -16
  395. package/dist/spec/catalogs/layouts/testimonial-grid.json +9 -15
  396. package/dist/spec/catalogs/layouts/text-1x-box-title-center.json +31 -0
  397. package/dist/spec/catalogs/layouts/text-1x-box-title-left.json +31 -0
  398. package/dist/spec/catalogs/layouts/text-1x-center-box-title-center.json +31 -0
  399. package/dist/spec/catalogs/layouts/text-1x-center-box-title-left.json +31 -0
  400. package/dist/spec/catalogs/layouts/text-1x-center-box.json +26 -0
  401. package/dist/spec/catalogs/layouts/text-1x-center-title-center.json +30 -0
  402. package/dist/spec/catalogs/layouts/text-1x-center-title-left.json +30 -0
  403. package/dist/spec/catalogs/layouts/text-1x-center.json +25 -0
  404. package/dist/spec/catalogs/layouts/text-1x-left-box.json +26 -0
  405. package/dist/spec/catalogs/layouts/text-1x-title-center.json +30 -0
  406. package/dist/spec/catalogs/layouts/text-1x.json +11 -1
  407. package/dist/spec/catalogs/layouts/text-2x-box-title-center.json +37 -0
  408. package/dist/spec/catalogs/layouts/text-2x-box-vertical-title-left.json +37 -0
  409. package/dist/spec/catalogs/layouts/text-2x-center-box-title-center.json +37 -0
  410. package/dist/spec/catalogs/layouts/text-2x-center-box-vertical-title-left.json +37 -0
  411. package/dist/spec/catalogs/layouts/text-2x-center-box.json +32 -0
  412. package/dist/spec/catalogs/layouts/text-2x-center-title-center.json +36 -0
  413. package/dist/spec/catalogs/layouts/text-2x-center-vertical-title-left.json +36 -0
  414. package/dist/spec/catalogs/layouts/text-2x-center.json +31 -0
  415. package/dist/spec/catalogs/layouts/text-2x-left-box.json +32 -0
  416. package/dist/spec/catalogs/layouts/text-2x-title-center.json +36 -0
  417. package/dist/spec/catalogs/layouts/text-2x-vertical-title-left.json +36 -0
  418. package/dist/spec/catalogs/layouts/text-2x.json +10 -0
  419. package/dist/spec/catalogs/layouts/text-3x-box-title-center.json +40 -0
  420. package/dist/spec/catalogs/layouts/text-3x-box-vertical-title-left.json +41 -0
  421. package/dist/spec/catalogs/layouts/text-3x-center-box-title-center.json +40 -0
  422. package/dist/spec/catalogs/layouts/text-3x-center-box-vertical-title-left.json +41 -0
  423. package/dist/spec/catalogs/layouts/text-3x-center-box.json +35 -0
  424. package/dist/spec/catalogs/layouts/text-3x-center-title-center.json +39 -0
  425. package/dist/spec/catalogs/layouts/text-3x-center-vertical-title-left.json +40 -0
  426. package/dist/spec/catalogs/layouts/text-3x-center.json +34 -0
  427. package/dist/spec/catalogs/layouts/text-3x-left-box.json +35 -0
  428. package/dist/spec/catalogs/layouts/text-3x-left.json +34 -0
  429. package/dist/spec/catalogs/layouts/text-3x-title-center.json +39 -0
  430. package/dist/spec/catalogs/layouts/text-3x-vertical-title-left.json +40 -0
  431. package/dist/spec/catalogs/layouts/text-3x.json +10 -0
  432. package/dist/spec/catalogs/layouts/thank-you-cta.json +8 -15
  433. package/dist/spec/catalogs/layouts/three-column.json +9 -15
  434. package/dist/spec/catalogs/layouts/timeline-1x.json +11 -1
  435. package/dist/spec/catalogs/layouts/timeline.json +9 -15
  436. package/dist/spec/catalogs/layouts/title-center-box.json +27 -0
  437. package/dist/spec/catalogs/layouts/title-center-image-bottom.json +34 -0
  438. package/dist/spec/catalogs/layouts/title-center-image-top.json +34 -0
  439. package/dist/spec/catalogs/layouts/title-center.json +26 -0
  440. package/dist/spec/catalogs/layouts/title-left-image-bottom.json +34 -0
  441. package/dist/spec/catalogs/layouts/title-left-image-left.json +34 -0
  442. package/dist/spec/catalogs/layouts/title-left-image-right.json +34 -0
  443. package/dist/spec/catalogs/layouts/title-left-image-top.json +34 -0
  444. package/dist/spec/catalogs/layouts/title-slide.json +8 -15
  445. package/dist/spec/catalogs/layouts/title-subtitle.json +11 -1
  446. package/dist/spec/catalogs/layouts/title.json +11 -1
  447. package/dist/spec/catalogs/layouts/two-column.json +9 -15
  448. package/dist/spec/catalogs/layouts/venn-overlap.json +9 -15
  449. package/dist/spec/catalogs/layouts/waterfall-bridge.json +9 -15
  450. package/dist/spec/catalogs/layouts/wheel-radial.json +9 -15
  451. package/dist/spec/catalogs/manifest.json +22 -22
  452. package/dist/spec/catalogs/narratives/board-meeting.json +45 -45
  453. package/dist/spec/catalogs/narratives/business-narrative.json +19 -19
  454. package/dist/spec/catalogs/narratives/business-review.json +27 -27
  455. package/dist/spec/catalogs/narratives/capacity-planning.json +19 -19
  456. package/dist/spec/catalogs/narratives/challenge-resolution.json +17 -17
  457. package/dist/spec/catalogs/narratives/change-story.json +13 -13
  458. package/dist/spec/catalogs/narratives/classic-story.json +17 -17
  459. package/dist/spec/catalogs/narratives/company-intro.json +21 -21
  460. package/dist/spec/catalogs/narratives/conference-talk.json +4 -4
  461. package/dist/spec/catalogs/narratives/data-story.json +13 -14
  462. package/dist/spec/catalogs/narratives/early-startup-pitch.json +27 -27
  463. package/dist/spec/catalogs/narratives/educate.json +21 -21
  464. package/dist/spec/catalogs/narratives/employee-review.json +19 -19
  465. package/dist/spec/catalogs/narratives/failure-analysis.json +23 -23
  466. package/dist/spec/catalogs/narratives/focus.json +27 -27
  467. package/dist/spec/catalogs/narratives/golden-circle.json +4 -4
  468. package/dist/spec/catalogs/narratives/heros-journey.json +13 -13
  469. package/dist/spec/catalogs/narratives/index.json +145 -145
  470. package/dist/spec/catalogs/narratives/innovation.json +21 -21
  471. package/dist/spec/catalogs/narratives/justice.json +25 -25
  472. package/dist/spec/catalogs/narratives/marketing-strategy.json +25 -25
  473. package/dist/spec/catalogs/narratives/performance-improvement-plan.json +19 -19
  474. package/dist/spec/catalogs/narratives/performance-review.json +25 -25
  475. package/dist/spec/catalogs/narratives/persuade.json +17 -17
  476. package/dist/spec/catalogs/narratives/persuasive-sales.json +19 -19
  477. package/dist/spec/catalogs/narratives/pitch-deck.json +6 -6
  478. package/dist/spec/catalogs/narratives/problem-solution.json +5 -5
  479. package/dist/spec/catalogs/narratives/product-launch.json +23 -23
  480. package/dist/spec/catalogs/narratives/project-proposal.json +23 -23
  481. package/dist/spec/catalogs/narratives/pyramid-principle.json +9 -9
  482. package/dist/spec/catalogs/narratives/qbr.json +8 -8
  483. package/dist/spec/catalogs/narratives/rags-to-riches.json +17 -17
  484. package/dist/spec/catalogs/narratives/reveal.json +19 -19
  485. package/dist/spec/catalogs/narratives/scqa.json +4 -4
  486. package/dist/spec/catalogs/narratives/situation-complication-resolution.json +11 -11
  487. package/dist/spec/catalogs/narratives/sparkline.json +11 -11
  488. package/dist/spec/catalogs/narratives/star-method.json +11 -11
  489. package/dist/spec/catalogs/narratives/status-update.json +15 -15
  490. package/dist/spec/catalogs/narratives/strategic-advisory.json +17 -17
  491. package/dist/spec/catalogs/narratives/strategic-narrative.json +4 -4
  492. package/dist/spec/catalogs/narratives/survey-analysis.json +23 -23
  493. package/dist/spec/catalogs/narratives/survival-story.json +23 -23
  494. package/dist/spec/catalogs/narratives/transformation-arc.json +4 -4
  495. package/dist/spec/catalogs/narratives/trend-analysis.json +17 -17
  496. package/dist/spec/catalogs/narratives/underdog-victory.json +19 -19
  497. package/dist/spec/catalogs/narratives/venture-pitch.json +27 -27
  498. package/dist/spec/catalogs/narratives/vision-roadmap.json +13 -13
  499. package/dist/spec/catalogs/narratives/weekly-progress.json +23 -23
  500. package/dist/spec/catalogs/narratives/what-so-what-now-what.json +9 -9
  501. package/dist/spec/openapi.yaml +4 -7
  502. package/dist/spec/reference/engine-defaults.json +22 -30
  503. package/dist/spec/reference/engine-vocabularies.json +1577 -0
  504. package/dist/spec/schemas/audience.schema.json +6 -27
  505. package/dist/spec/schemas/catalog-index.schema.json +2 -2
  506. package/dist/spec/schemas/catalog-manifest.schema.json +3 -3
  507. package/dist/spec/schemas/chart-type.schema.json +17 -321
  508. package/dist/spec/schemas/color-scheme.schema.json +23 -36
  509. package/dist/spec/schemas/finding.schema.json +191 -0
  510. package/dist/spec/schemas/font-scheme.schema.json +17 -75
  511. package/dist/spec/schemas/language.schema.json +7 -28
  512. package/dist/spec/schemas/layout.schema.json +130 -184
  513. package/dist/spec/schemas/narrative.schema.json +20 -48
  514. package/dist/spec/schemas/opf.schema.json +678 -701
  515. package/dist/spec/schemas/purpose.schema.json +5 -26
  516. package/dist/spec/schemas/social-platform.schema.json +2 -48
  517. package/dist/spec/schemas/theme.schema.json +13 -38
  518. package/dist/spec/schemas/tone.schema.json +4 -25
  519. package/dist/spec-files.d.ts +1 -1
  520. package/dist/spec-files.js +1 -1
  521. package/dist/symbol-font-encodings.js +18448 -1
  522. package/dist/types.d.ts +1125 -4
  523. package/dist/validation-types-BOcY9GTs.d.ts +276 -0
  524. package/dist/validator.d.ts +63 -4
  525. package/dist/validator.js +5 -5
  526. package/dist/yaml.d.ts +102 -0
  527. package/dist/yaml.js +322 -0
  528. package/package.json +9 -17
  529. package/dist/audit.d.ts +0 -35
  530. package/dist/audit.js +0 -7
  531. package/dist/catalogs-ywCamd8C.d.ts +0 -1465
  532. package/dist/catalogs.d.ts +0 -2
  533. package/dist/catalogs.js +0 -2
  534. package/dist/chunk-7N4VMAT4.js +0 -1694
  535. package/dist/chunk-FYLJVF7X.js +0 -18448
  536. package/dist/chunk-INU7ZNSB.js +0 -670
  537. package/dist/chunk-IYCJUKWD.js +0 -951
  538. package/dist/chunk-PIQXLBBJ.js +0 -445
  539. package/dist/chunk-S5W34SIJ.js +0 -1
  540. package/dist/lint-B7azdiuE.d.ts +0 -197
  541. package/dist/lint.d.ts +0 -7
  542. package/dist/lint.js +0 -6
  543. package/dist/previews.d.ts +0 -40
  544. package/dist/previews.js +0 -1
  545. package/dist/schemas-BbPonsTz.d.ts +0 -270
  546. package/dist/spec/catalogs/audiences/customers.json +0 -27
  547. package/dist/spec/catalogs/audiences/executives.json +0 -27
  548. package/dist/spec/catalogs/audiences/investors.json +0 -27
  549. package/dist/spec/catalogs/audiences/marketing-team.json +0 -27
  550. package/dist/spec/catalogs/audiences/regulators.json +0 -27
  551. package/dist/spec/catalogs/audiences/sales-team.json +0 -27
  552. package/dist/spec/catalogs/chart-types/100pct-bullet-bar-2x.json +0 -91
  553. package/dist/spec/catalogs/chart-types/100pct-bullet-bar-3x.json +0 -103
  554. package/dist/spec/catalogs/chart-types/100pct-bullet-bar.json +0 -79
  555. package/dist/spec/catalogs/chart-types/100pct-bullet-column-2x.json +0 -91
  556. package/dist/spec/catalogs/chart-types/100pct-bullet-column-3x.json +0 -103
  557. package/dist/spec/catalogs/chart-types/100pct-bullet-column.json +0 -79
  558. package/dist/spec/catalogs/chart-types/100pct-progress-bar.json +0 -105
  559. package/dist/spec/catalogs/chart-types/100pct-stacked-area-2x.json +0 -106
  560. package/dist/spec/catalogs/chart-types/100pct-stacked-area-3x.json +0 -123
  561. package/dist/spec/catalogs/chart-types/100pct-stacked-bar-2x.json +0 -89
  562. package/dist/spec/catalogs/chart-types/100pct-stacked-bar-3x.json +0 -100
  563. package/dist/spec/catalogs/chart-types/100pct-stacked-column-2x.json +0 -95
  564. package/dist/spec/catalogs/chart-types/100pct-stacked-column-3x.json +0 -108
  565. package/dist/spec/catalogs/chart-types/australia.json +0 -76
  566. package/dist/spec/catalogs/chart-types/box-and-whisker-2x.json +0 -136
  567. package/dist/spec/catalogs/chart-types/box-and-whisker-3x.json +0 -168
  568. package/dist/spec/catalogs/chart-types/bullet-bar-2x.json +0 -89
  569. package/dist/spec/catalogs/chart-types/bullet-bar-3x.json +0 -101
  570. package/dist/spec/catalogs/chart-types/bullet-bar.json +0 -77
  571. package/dist/spec/catalogs/chart-types/bullet-column-2x.json +0 -89
  572. package/dist/spec/catalogs/chart-types/bullet-column-3x.json +0 -101
  573. package/dist/spec/catalogs/chart-types/bullet-column.json +0 -77
  574. package/dist/spec/catalogs/chart-types/canada.json +0 -86
  575. package/dist/spec/catalogs/chart-types/clustered-bar-2x.json +0 -89
  576. package/dist/spec/catalogs/chart-types/clustered-column.json +0 -95
  577. package/dist/spec/catalogs/chart-types/dot-plot-2x.json +0 -99
  578. package/dist/spec/catalogs/chart-types/dot-plot-3x.json +0 -124
  579. package/dist/spec/catalogs/chart-types/dot-plot-4x.json +0 -149
  580. package/dist/spec/catalogs/chart-types/dot-plot-5x.json +0 -174
  581. package/dist/spec/catalogs/chart-types/dot-plot-6x.json +0 -199
  582. package/dist/spec/catalogs/chart-types/dot-plot.json +0 -74
  583. package/dist/spec/catalogs/chart-types/dumbbell.json +0 -120
  584. package/dist/spec/catalogs/chart-types/line-2x.json +0 -105
  585. package/dist/spec/catalogs/chart-types/line-3x.json +0 -127
  586. package/dist/spec/catalogs/chart-types/line-with-high-low-and-markers.json +0 -144
  587. package/dist/spec/catalogs/chart-types/line-with-high-low.json +0 -144
  588. package/dist/spec/catalogs/chart-types/line-with-markers-2x.json +0 -106
  589. package/dist/spec/catalogs/chart-types/line-with-markers-3x.json +0 -128
  590. package/dist/spec/catalogs/chart-types/sparkline-2x.json +0 -138
  591. package/dist/spec/catalogs/chart-types/sparkline-3x.json +0 -174
  592. package/dist/spec/catalogs/chart-types/sparkline-4x.json +0 -210
  593. package/dist/spec/catalogs/chart-types/sparkline-5x.json +0 -246
  594. package/dist/spec/catalogs/chart-types/sparkline-6x.json +0 -282
  595. package/dist/spec/catalogs/chart-types/sparkline.json +0 -102
  596. package/dist/spec/catalogs/chart-types/stacked-area-2x.json +0 -106
  597. package/dist/spec/catalogs/chart-types/stacked-area-3x.json +0 -123
  598. package/dist/spec/catalogs/chart-types/stacked-bar-2x.json +0 -89
  599. package/dist/spec/catalogs/chart-types/stacked-bar-3x.json +0 -100
  600. package/dist/spec/catalogs/chart-types/stacked-column-2x.json +0 -95
  601. package/dist/spec/catalogs/chart-types/stacked-column-3x.json +0 -108
  602. package/dist/spec/catalogs/chart-types/stacked-line-2x.json +0 -106
  603. package/dist/spec/catalogs/chart-types/stacked-line-3x.json +0 -123
  604. package/dist/spec/catalogs/chart-types/stacked-line-with-markers-2x.json +0 -107
  605. package/dist/spec/catalogs/chart-types/stacked-line-with-markers-3x.json +0 -124
  606. package/dist/spec/catalogs/chart-types/treemap-2x.json +0 -101
  607. package/dist/spec/catalogs/chart-types/treemap-3x.json +0 -122
  608. package/dist/spec/catalogs/chart-types/united-kingdom.json +0 -68
  609. package/dist/spec/catalogs/chart-types/united-states.json +0 -82
  610. package/dist/spec/catalogs/layouts/image-bleed.json +0 -10
  611. package/dist/validator-CIylxoIm.d.ts +0 -172
@@ -0,0 +1,3515 @@
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, OPFCompositionError, composeSlide, resolveScriptFonts } from './chunk-O744SXXA.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-2CJPC4HI.js';
5
+ import Ajv2020 from 'ajv/dist/2020.js';
6
+ import addFormats from 'ajv-formats';
7
+ import { parseTree, printParseErrorCode, getNodePath } from 'jsonc-parser';
8
+
9
+ // src/annotation-validation.ts
10
+ var issue = (path, message, params) => ({ path, message, keyword: "opf", schemaPath: "#/x-opf-semantics", params });
11
+ var pointer = (dotted) => `/${dotted.split(".").map((part) => part.replaceAll("~", "~0").replaceAll("/", "~1")).join("/")}`;
12
+ function markedRuns(value) {
13
+ if (!Array.isArray(value)) return [];
14
+ return value.flatMap((run, index) => isRecord(run) && (run.cite !== void 0 || run.footnote !== void 0) ? [index] : []);
15
+ }
16
+ function annotationIssues(value) {
17
+ if (!isRecord(value) || !Array.isArray(value.slides)) return [];
18
+ const issues = [];
19
+ const ids = /* @__PURE__ */ new Set();
20
+ if (Array.isArray(value.references)) value.references.forEach((reference, index) => {
21
+ if (!isRecord(reference) || typeof reference.id !== "string") return;
22
+ if (ids.has(reference.id)) issues.push(issue(`/references/${index}/id`, `reference id '${reference.id}' is used by an earlier references entry; ids must be unique`, { code: "reference-id-duplicate", id: reference.id }));
23
+ ids.add(reference.id);
24
+ for (const runIndex of markedRuns(reference.text)) issues.push(issue(`/references/${index}/text/${runIndex}`, "reference text cannot cite or carry a footnote", { code: "cite-unsupported-location" }));
25
+ });
26
+ const unsupported = (runs, path, where) => {
27
+ for (const runIndex of markedRuns(runs)) issues.push(issue(`${path}/${runIndex}`, `cite and footnote are not supported in ${where}; markers are drawn only in the slide title, subtitle and tag, text, bullets, list items and quote text`, { code: "cite-unsupported-location" }));
28
+ };
29
+ const cell = (entry, path) => {
30
+ if (Array.isArray(entry)) unsupported(entry, path, "table cells");
31
+ else if (isRecord(entry) && Array.isArray(entry.value)) unsupported(entry.value, pathFor(path, "value"), "table cells");
32
+ };
33
+ const payload = (node, path) => {
34
+ if (isRecord(node.table)) {
35
+ const table = node.table, tablePath = pathFor(path, "table");
36
+ if (Array.isArray(table.columns)) table.columns.forEach((column, index) => {
37
+ cell(column, `${pathFor(tablePath, "columns")}/${index}`);
38
+ });
39
+ if (Array.isArray(table.rows)) table.rows.forEach((row, rowIndex) => {
40
+ if (Array.isArray(row)) row.forEach((entry, cellIndex) => {
41
+ cell(entry, `${pathFor(tablePath, "rows")}/${rowIndex}/${cellIndex}`);
42
+ });
43
+ });
44
+ }
45
+ if (node.caption !== void 0) {
46
+ const caption = node.caption, captionPath = pathFor(path, "caption");
47
+ if (Array.isArray(caption)) unsupported(caption, captionPath, "captions");
48
+ else if (isRecord(caption) && Array.isArray(caption.text)) unsupported(caption.text, pathFor(captionPath, "text"), "captions");
49
+ }
50
+ };
51
+ value.slides.forEach((slide, index) => {
52
+ if (!isRecord(slide)) return;
53
+ const slidePath = `/slides/${index}`;
54
+ payload(slide, slidePath);
55
+ visitContentPayloads(slide, slidePath, payload);
56
+ walkCitationRuns(slide, `slides.${index}`, (entry) => {
57
+ const runPath = pointer(entry.path);
58
+ const cite = entry.run.cite;
59
+ if (typeof cite === "string") {
60
+ if (!ids.has(cite)) issues.push(issue(`${runPath}/cite`, `unknown reference '${cite}'; add it to the top-level references list`, { code: "cite-unknown-reference", id: cite }));
61
+ } else if (Array.isArray(cite)) cite.forEach((id, position) => {
62
+ if (typeof id === "string" && !ids.has(id)) issues.push(issue(`${runPath}/cite/${position}`, `unknown reference '${id}'; add it to the top-level references list`, { code: "cite-unknown-reference", id }));
63
+ });
64
+ if (Array.isArray(entry.footnote)) unsupported(entry.footnote, `${runPath}/footnote`, "footnote text");
65
+ });
66
+ });
67
+ return issues;
68
+ }
69
+ function unusedReferenceWarnings(value) {
70
+ if (!isRecord(value) || !Array.isArray(value.references) || !Array.isArray(value.slides)) return [];
71
+ const { unused } = collectCitations(value);
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 })] : []);
73
+ }
74
+
75
+ // src/validation-definitions.ts
76
+ var schemaLocations;
77
+ var issueLocations = /* @__PURE__ */ new WeakMap();
78
+ function rememberValidationDefinition(issue2, parentSchema, keyword) {
79
+ if (!parentSchema || typeof parentSchema !== "object") return;
80
+ if (!schemaLocations) {
81
+ schemaLocations = /* @__PURE__ */ new WeakMap();
82
+ for (const entry of schemaEntries) {
83
+ const stack = [
84
+ { value: entry.schema, pointer: "" }
85
+ ];
86
+ while (stack.length) {
87
+ const current = stack.pop();
88
+ if (!current?.value || typeof current.value !== "object")
89
+ continue;
90
+ schemaLocations.set(current.value, {
91
+ schemaName: entry.name,
92
+ pointer: current.pointer,
93
+ uri: `${entry.schema.$id}#${current.pointer}`
94
+ });
95
+ for (const [key, value] of Object.entries(current.value)) {
96
+ const segment = key.replaceAll("~", "~0").replaceAll("/", "~1");
97
+ stack.push({ value, pointer: `${current.pointer}/${segment}` });
98
+ }
99
+ }
100
+ }
101
+ }
102
+ const parent = schemaLocations.get(parentSchema);
103
+ if (!parent) return;
104
+ const suffix = `/${keyword.replaceAll("~", "~0").replaceAll("/", "~1")}`;
105
+ issueLocations.set(issue2, {
106
+ ...parent,
107
+ pointer: parent.pointer + suffix,
108
+ uri: parent.uri + suffix
109
+ });
110
+ }
111
+ function validationDefinition(issue2) {
112
+ return issueLocations.get(issue2);
113
+ }
114
+
115
+ // src/schema-check.ts
116
+ var schemaNameByCatalogKind = catalogSchemaNames;
117
+ var ajv;
118
+ var promotedRegionKeySet = new Set(promotedRegionKeys);
119
+ var rootPayloadFields = [
120
+ "type",
121
+ "text",
122
+ "items",
123
+ "bullets",
124
+ "image",
125
+ "video",
126
+ "chart",
127
+ "table",
128
+ "code",
129
+ "metric",
130
+ "quote",
131
+ "timeline",
132
+ "blocks"
133
+ ];
134
+ var contentKindSpecs = {
135
+ text: {
136
+ fields: ["text", "bullets"],
137
+ required: [],
138
+ requireAny: ["text", "bullets"]
139
+ },
140
+ list: {
141
+ fields: ["items"],
142
+ required: ["items"]
143
+ },
144
+ image: {
145
+ fields: ["image"],
146
+ required: ["image"]
147
+ },
148
+ chart: {
149
+ fields: ["chart"],
150
+ required: ["chart"]
151
+ },
152
+ table: {
153
+ fields: ["table"],
154
+ required: ["table"]
155
+ },
156
+ video: {
157
+ fields: ["video"],
158
+ required: ["video"]
159
+ },
160
+ code: {
161
+ fields: ["code"],
162
+ required: ["code"]
163
+ },
164
+ metric: {
165
+ fields: ["metric"],
166
+ required: ["metric"]
167
+ },
168
+ quote: {
169
+ fields: ["quote"],
170
+ required: ["quote"]
171
+ },
172
+ timeline: {
173
+ fields: ["timeline"],
174
+ required: ["timeline"]
175
+ }
176
+ };
177
+ var captionableKinds = /* @__PURE__ */ new Set(["image", "chart", "table", "video"]);
178
+ var imageOptionFields = ["fit", "focus", "aspectRatio", "shape", "cornerRadius", "border", "opacity", "recolor", "overlay", "placement"];
179
+ var columnSpans = {
180
+ left: [0],
181
+ center: [1],
182
+ right: [2],
183
+ "left+center": [0, 1],
184
+ "center+right": [1, 2],
185
+ "left+center+right": [0, 1, 2]
186
+ };
187
+ var rowSpans = {
188
+ top: [0],
189
+ middle: [1],
190
+ bottom: [2],
191
+ "top+middle": [0, 1],
192
+ "middle+bottom": [1, 2],
193
+ "top+middle+bottom": [0, 1, 2]
194
+ };
195
+ function getAjv() {
196
+ if (ajv) {
197
+ return ajv;
198
+ }
199
+ const instance = new Ajv2020({
200
+ allErrors: true,
201
+ verbose: true,
202
+ strict: false,
203
+ allowUnionTypes: true
204
+ });
205
+ addFormats(instance);
206
+ for (const schema of Object.values(schemas)) {
207
+ instance.addSchema(schema);
208
+ }
209
+ ajv = instance;
210
+ return instance;
211
+ }
212
+ function isSchemaName(value) {
213
+ return typeof value === "string" && value in schemas;
214
+ }
215
+ function isCatalogKind(value) {
216
+ return typeof value === "string" && value in schemaNameByCatalogKind;
217
+ }
218
+ function resolveValidator(schemaOrKind) {
219
+ const instance = getAjv();
220
+ if (isSchemaName(schemaOrKind)) {
221
+ const schema = schemas[schemaOrKind];
222
+ const validator = instance.getSchema(schema.$id) ?? instance.compile(schema);
223
+ return { validate: validator, schemaName: schemaOrKind };
224
+ }
225
+ if (isCatalogKind(schemaOrKind)) {
226
+ const schemaName = schemaNameByCatalogKind[schemaOrKind];
227
+ const schema = schemas[schemaName];
228
+ const validator = instance.getSchema(schema.$id) ?? instance.compile(schema);
229
+ return { validate: validator, schemaName, catalogKind: schemaOrKind };
230
+ }
231
+ throw new TypeError(`Unknown schema or catalog kind ${JSON.stringify(schemaOrKind)}.`);
232
+ }
233
+ var slideDimensionsSchemaPath = /\/allOf\/1\/not$/;
234
+ var designInstancePath = /\/design$/;
235
+ function toIssue(error) {
236
+ const slideDimensions = error.keyword === "not" && slideDimensionsSchemaPath.test(error.schemaPath) && designInstancePath.test(error.instancePath);
237
+ const issue2 = {
238
+ path: error.instancePath || "/",
239
+ message: slideDimensions ? "a slide's design cannot set 'dimensions': a PPTX has one slide size, so set design.dimensions on the deck (or its theme)" : error.message ?? "failed validation",
240
+ keyword: error.keyword,
241
+ schemaPath: error.schemaPath,
242
+ params: error.params
243
+ };
244
+ rememberValidationDefinition(issue2, error.parentSchema, error.keyword);
245
+ return issue2;
246
+ }
247
+ function semanticIssue(path, message, params = {}) {
248
+ return {
249
+ path,
250
+ message,
251
+ keyword: "opf",
252
+ schemaPath: "#/x-opf-semantics",
253
+ params
254
+ };
255
+ }
256
+ function hasOwn(value, key) {
257
+ return Object.hasOwn(value, key);
258
+ }
259
+ function isEnUkTag(value) {
260
+ return typeof value === "string" && value.toLowerCase() === "en-uk";
261
+ }
262
+ function presentFields(value, fields2) {
263
+ return fields2.filter((field) => hasOwn(value, field));
264
+ }
265
+ function isContentKind(value) {
266
+ return typeof value === "string" && value in contentKindSpecs;
267
+ }
268
+ function inferredKinds(value) {
269
+ const kinds = [];
270
+ if (presentFields(value, contentKindSpecs.text.fields).length > 0) kinds.push("text");
271
+ if (hasOwn(value, "items")) kinds.push("list");
272
+ if (hasOwn(value, "image")) kinds.push("image");
273
+ if (hasOwn(value, "video")) kinds.push("video");
274
+ if (hasOwn(value, "chart")) kinds.push("chart");
275
+ if (hasOwn(value, "table")) kinds.push("table");
276
+ if (hasOwn(value, "code")) kinds.push("code");
277
+ if (hasOwn(value, "metric")) kinds.push("metric");
278
+ if (hasOwn(value, "quote")) kinds.push("quote");
279
+ if (hasOwn(value, "timeline")) kinds.push("timeline");
280
+ return kinds;
281
+ }
282
+ function isImplicitBlocksComposition(value, inferred, options) {
283
+ if (options.slideRoot !== true || hasOwn(value, "blocks")) {
284
+ return false;
285
+ }
286
+ return inferred.length > 1;
287
+ }
288
+ function validateContentPayload(value, path, options = {}) {
289
+ const issues = [];
290
+ const explicitType = value.type;
291
+ const payloadFields = presentFields(value, rootPayloadFields);
292
+ const hasBlocks = hasOwn(value, "blocks");
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
+ }
300
+ if (hasBlocks || explicitType === "group") {
301
+ if (hasOwn(value, "caption")) issues.push(captionIssue("a group cannot carry one"));
302
+ imageOptionIssues("a group cannot carry them");
303
+ if (explicitType !== void 0 && explicitType !== "group") issues.push(semanticIssue(path, "a group must use type 'group' or omit type"));
304
+ const incompatible2 = payloadFields.filter((field) => field !== "blocks" && field !== "type");
305
+ if (incompatible2.length) issues.push(semanticIssue(path, "blocks cannot be mixed with leaf payload fields", { fields: incompatible2 }));
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"));
307
+ if (Array.isArray(value.blocks)) value.blocks.forEach((block, index) => {
308
+ if (isRecord(block)) issues.push(...validateContentPayload(block, `${pathFor(path, "blocks")}/${index}`, { topLevel: options.slideRoot === true }));
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
+ }
319
+ return issues;
320
+ }
321
+ if (hasOwn(value, "composition") && !options.slideRoot) issues.push(semanticIssue(pathFor(path, "composition"), "composition is only valid on a group containing blocks"));
322
+ if (explicitType !== void 0 && !isContentKind(explicitType)) {
323
+ return issues;
324
+ }
325
+ const kind = isContentKind(explicitType) ? explicitType : void 0;
326
+ const inferred = kind ? [kind] : inferredKinds(value);
327
+ if (!kind && inferred.length === 0) {
328
+ if (payloadFields.length > 0 || path !== "/") {
329
+ issues.push(semanticIssue(path, "content payload must include concrete content fields", {
330
+ fields: payloadFields
331
+ }));
332
+ }
333
+ return issues;
334
+ }
335
+ if (!kind && inferred.length > 1) {
336
+ if (isImplicitBlocksComposition(value, inferred, options)) {
337
+ if (hasOwn(value, "caption")) issues.push(captionIssue("a slide root with several payloads cannot carry one; put the caption on a block", { inferredTypes: inferred }));
338
+ return issues;
339
+ }
340
+ issues.push(semanticIssue(path, "content payload mixes fields from incompatible content types", {
341
+ inferredTypes: inferred
342
+ }));
343
+ return issues;
344
+ }
345
+ const resolvedKind = inferred[0];
346
+ if (!resolvedKind) {
347
+ return issues;
348
+ }
349
+ const spec = contentKindSpecs[resolvedKind];
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 });
352
+ const allowedFields = /* @__PURE__ */ new Set([
353
+ "type",
354
+ ...spec.fields
355
+ ]);
356
+ for (const required of spec.required) {
357
+ if (!hasOwn(value, required)) {
358
+ issues.push(semanticIssue(path, `content payload type '${resolvedKind}' requires '${required}'`, {
359
+ type: resolvedKind,
360
+ required
361
+ }));
362
+ }
363
+ }
364
+ if (spec.requireAny && presentFields(value, spec.requireAny).length === 0) {
365
+ issues.push(semanticIssue(path, `content payload type '${resolvedKind}' requires one of: ${spec.requireAny.join(", ")}`, {
366
+ type: resolvedKind,
367
+ requiredAny: spec.requireAny
368
+ }));
369
+ }
370
+ const incompatible = payloadFields.filter((field) => !allowedFields.has(field));
371
+ if (incompatible.length > 0) {
372
+ issues.push(semanticIssue(path, `content payload type '${resolvedKind}' cannot include incompatible fields: ${incompatible.join(", ")}`, {
373
+ type: resolvedKind,
374
+ incompatible
375
+ }));
376
+ }
377
+ return issues;
378
+ }
379
+ function slideRegion(key) {
380
+ if (key.includes(":")) {
381
+ const [rowPart, columnPart] = key.split(":");
382
+ if (!rowPart || !columnPart) return void 0;
383
+ const rows = rowSpans[rowPart];
384
+ const columns = columnSpans[columnPart];
385
+ return rows && columns ? { rows, columns } : void 0;
386
+ }
387
+ if (key in columnSpans) {
388
+ const columns = columnSpans[key];
389
+ return columns ? { rows: [0, 1, 2], columns } : void 0;
390
+ }
391
+ if (key in rowSpans) {
392
+ const rows = rowSpans[key];
393
+ return rows ? { rows, columns: [0, 1, 2] } : void 0;
394
+ }
395
+ return void 0;
396
+ }
397
+ function intersects(left, right) {
398
+ return left.some((value) => right.includes(value));
399
+ }
400
+ function regionsOverlap(left, right) {
401
+ return intersects(left.rows, right.rows) && intersects(left.columns, right.columns);
402
+ }
403
+ function validateSlideRegions(slide, slidePath) {
404
+ const issues = [];
405
+ const regionKeys = Object.keys(slide).filter((key) => promotedRegionKeySet.has(key));
406
+ const rootFields = presentFields(slide, rootPayloadFields);
407
+ if (regionKeys.length > 0 && rootFields.length > 0) {
408
+ issues.push(semanticIssue(slidePath, "root content payload fields cannot be mixed with promoted region keys", {
409
+ rootFields,
410
+ regionKeys
411
+ }));
412
+ }
413
+ if (regionKeys.length === 0 && rootFields.length > 0) {
414
+ issues.push(...validateContentPayload(slide, slidePath, { slideRoot: true }));
415
+ }
416
+ for (const key of regionKeys) {
417
+ const value = slide[key];
418
+ if (isRecord(value)) {
419
+ issues.push(...validateContentPayload(value, pathFor(slidePath, key)));
420
+ }
421
+ }
422
+ for (let leftIndex = 0; leftIndex < regionKeys.length; leftIndex += 1) {
423
+ const leftKey = regionKeys[leftIndex];
424
+ if (!leftKey) continue;
425
+ const leftRegion = slideRegion(leftKey);
426
+ if (!leftRegion) continue;
427
+ for (let rightIndex = leftIndex + 1; rightIndex < regionKeys.length; rightIndex += 1) {
428
+ const rightKey = regionKeys[rightIndex];
429
+ if (!rightKey) continue;
430
+ const rightRegion = slideRegion(rightKey);
431
+ if (!rightRegion) continue;
432
+ if (regionsOverlap(leftRegion, rightRegion)) {
433
+ issues.push(semanticIssue(slidePath, `promoted region keys '${leftKey}' and '${rightKey}' overlap`, {
434
+ regionKeys: [leftKey, rightKey]
435
+ }));
436
+ }
437
+ }
438
+ }
439
+ return issues;
440
+ }
441
+ function deckMetadataIssues(value) {
442
+ const issues = [];
443
+ const entries = (key) => {
444
+ const field = value[key];
445
+ if (Array.isArray(field)) return field.flatMap((entry, index) => isRecord(entry) ? [{ entry, path: `/${key}/${index}` }] : []);
446
+ return isRecord(field) ? [{ entry: field, path: `/${key}` }] : [];
447
+ };
448
+ const organizationIds = /* @__PURE__ */ new Set();
449
+ for (const key of ["organization", "speaker"]) {
450
+ const seen = /* @__PURE__ */ new Set();
451
+ for (const { entry, path } of entries(key)) {
452
+ if (typeof entry.id !== "string") continue;
453
+ if (seen.has(entry.id)) issues.push(semanticIssue(`${path}/id`, `${key} ids must be unique within a presentation`, { id: entry.id }));
454
+ seen.add(entry.id);
455
+ if (key === "organization") organizationIds.add(entry.id);
456
+ }
457
+ }
458
+ for (const { entry, path } of entries("speaker")) {
459
+ if (typeof entry.organizationId === "string" && !organizationIds.has(entry.organizationId)) {
460
+ issues.push(semanticIssue(`${path}/organizationId`, `organizationId '${entry.organizationId}' names no organization in the presentation; use the id of an entry in organization`, { id: entry.organizationId }));
461
+ }
462
+ }
463
+ return issues;
464
+ }
465
+ function presentationSemanticIssues(value) {
466
+ if (!isRecord(value) || !Array.isArray(value.slides)) {
467
+ return [];
468
+ }
469
+ const issues = [];
470
+ if (isEnUkTag(value.language)) {
471
+ issues.push(semanticIssue("/language", "Use 'en-GB' for UK English; 'en-UK' is not a valid BCP-47 region tag", {
472
+ replacement: "en-GB"
473
+ }));
474
+ } else if (isRecord(value.language) && isEnUkTag(value.language.bcp47)) {
475
+ issues.push(semanticIssue("/language/bcp47", "Use 'en-GB' for UK English; 'en-UK' is not a valid BCP-47 region tag", {
476
+ replacement: "en-GB"
477
+ }));
478
+ }
479
+ issues.push(...deckMetadataIssues(value));
480
+ if (isRecord(value.variables) && hasOwn(value.variables, "speakers")) {
481
+ issues.push(semanticIssue("/variables/speakers", "'speakers' is reserved for the built-in list of speaker names; choose another variable id", { id: "speakers" }));
482
+ }
483
+ const documentIds = /* @__PURE__ */ new Map();
484
+ value.slides.forEach((slide, index) => {
485
+ if (isRecord(slide)) {
486
+ const slidePath = `/slides/${index}`;
487
+ if (typeof slide.id === "string") {
488
+ const seen = documentIds.get(slide.id);
489
+ if (seen === "slide") issues.push(semanticIssue(`${slidePath}/id`, "slide ids must be unique within a presentation", { id: slide.id }));
490
+ else if (seen === "payload") issues.push(semanticIssue(`${slidePath}/id`, "slide id duplicates a content payload id; ids must be unique among slide and payload ids within a presentation", { id: slide.id }));
491
+ else documentIds.set(slide.id, "slide");
492
+ }
493
+ visitContentPayloads(slide, slidePath, (payload, path) => {
494
+ if (typeof payload.id === "string") {
495
+ if (documentIds.has(payload.id)) issues.push(semanticIssue(`${path}/id`, "content payload ids must be unique among slide and payload ids within a presentation", { id: payload.id }));
496
+ else documentIds.set(payload.id, "payload");
497
+ }
498
+ });
499
+ issues.push(...validateSlideRegions(slide, slidePath));
500
+ const payloadTableIssues = (payload, path) => {
501
+ if (isRecord(payload.table)) for (const issue2 of tableGrid(payload.table).issues) issues.push(semanticIssue(pathFor(path, "table") + issue2.path.slice(5).replaceAll(".", "/"), issue2.message));
502
+ };
503
+ payloadTableIssues(slide, slidePath);
504
+ visitContentPayloads(slide, slidePath, payloadTableIssues);
505
+ const payloadNumberingIssues = (payload, path) => {
506
+ for (const finding2 of numberingFindings(payload).errors) issues.push(semanticIssue(pathFor(path, finding2.key), finding2.message, finding2.params));
507
+ };
508
+ payloadNumberingIssues(slide, slidePath);
509
+ visitContentPayloads(slide, slidePath, payloadNumberingIssues);
510
+ }
511
+ });
512
+ issues.push(...annotationIssues(value));
513
+ return issues;
514
+ }
515
+ function canvasSize(dimensions) {
516
+ try {
517
+ const { width, height } = resolveCanvasDimensions(dimensions);
518
+ return `${Math.round(width * 100) / 100}x${Math.round(height * 100) / 100} px`;
519
+ } catch {
520
+ return "invalid";
521
+ }
522
+ }
523
+ function slideThemeDimensionsWarnings(document, slide, index, catalogs) {
524
+ const slidePath = `/slides/${index}`;
525
+ const slideDesign = isRecord(slide.design) ? slide.design : void 0;
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 [];
530
+ const deckDesign = isRecord(document.design) ? document.design : {};
531
+ const deckDimensions = deckDesign.dimensions ?? resolveDesignRecords(document, void 0, options).theme.dimensions;
532
+ const slideSize = canvasSize(slideTheme.theme.dimensions);
533
+ const deckSize = canvasSize(deckDimensions);
534
+ if (slideSize === deckSize) return [];
535
+ const explicit = deckDesign.dimensions !== void 0;
536
+ return [semanticIssue(
537
+ pathFor(pathFor(slidePath, "design"), "theme"),
538
+ explicit ? `this slide's theme sets a slide size (${slideSize}) that differs from the deck's design.dimensions (${deckSize}); a PPTX has one slide size, so the deck's size is used` : `this slide's theme sets a slide size (${slideSize}) that differs from the deck's (${deckSize}); a PPTX has one slide size, so set design.dimensions on the deck`,
539
+ { code: "slide-theme-dimensions", slideDimensions: slideSize, deckDimensions: deckSize }
540
+ )];
541
+ }
542
+ function chartTypeWarnings(payload, path) {
543
+ if (!isRecord(payload)) {
544
+ return [];
545
+ }
546
+ const issues = [];
547
+ if (isRecord(payload.chart)) {
548
+ for (const diagnostic of resolveChartOptions(payload.chart, chartOptionTarget(payload.chart.type)).diagnostics) {
549
+ issues.push(semanticIssue(diagnostic.option.split(".").reduce(pathFor, pathFor(path, "chart")), diagnostic.message, { code: diagnostic.code, option: diagnostic.option, reason: diagnostic.reason }));
550
+ }
551
+ }
552
+ if (Array.isArray(payload.blocks)) {
553
+ payload.blocks.forEach((block, index) => {
554
+ issues.push(...chartTypeWarnings(block, `${pathFor(path, "blocks")}/${index}`));
555
+ });
556
+ }
557
+ return issues;
558
+ }
559
+ function dataIssues(value) {
560
+ const out = { errors: [], warnings: [] };
561
+ if (!isRecord(value) || !Array.isArray(value.slides)) return out;
562
+ const seen = /* @__PURE__ */ new Set();
563
+ const add = (diagnostics) => {
564
+ for (const diagnostic of diagnostics) {
565
+ const key = `${diagnostic.code}|${diagnostic.path}`;
566
+ if (seen.has(key)) continue;
567
+ seen.add(key);
568
+ (diagnostic.severity === "error" ? out.errors : out.warnings).push(semanticIssue(diagnostic.path, diagnostic.message, { code: diagnostic.code }));
569
+ }
570
+ };
571
+ add(datasetDiagnostics(value));
572
+ const payload = (node, path) => {
573
+ if (isRecord(node.chart)) add(resolveChartData(node.chart, value, { path: pathFor(path, "chart") }).diagnostics);
574
+ if (isRecord(node.table)) add(resolveTableData(node.table, value, { path: pathFor(path, "table") }).diagnostics);
575
+ };
576
+ value.slides.forEach((slide, index) => {
577
+ if (!isRecord(slide)) return;
578
+ const slidePath = `/slides/${index}`;
579
+ payload(slide, slidePath);
580
+ visitContentPayloads(slide, slidePath, payload);
581
+ });
582
+ return out;
583
+ }
584
+ var cellBorderEdges = ["top", "right", "bottom", "left"];
585
+ var variableIdPattern = /^[a-z][a-z0-9-]*$/;
586
+ var hexColorPattern = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
587
+ var schemeColorNames = /* @__PURE__ */ new Set([
588
+ "accent1",
589
+ "accent2",
590
+ "accent3",
591
+ "accent4",
592
+ "accent5",
593
+ "accent6",
594
+ "dark1",
595
+ "dark2",
596
+ "light1",
597
+ "light2",
598
+ "hyperlink",
599
+ "followedHyperlink",
600
+ "primary",
601
+ "secondary",
602
+ "accent",
603
+ "background",
604
+ "surface",
605
+ "text",
606
+ "textSecondary"
607
+ ]);
608
+ function variableReferenceWarnings(value) {
609
+ if (!Array.isArray(value.slides)) {
610
+ return [];
611
+ }
612
+ const variables = isRecord(value.variables) ? value.variables : {};
613
+ const issues = [];
614
+ const colorRef = (entry, path) => {
615
+ if (typeof entry !== "string" || !entry.startsWith("var:")) return;
616
+ const id = entry.slice("var:".length);
617
+ if (!variableIdPattern.test(id) || hasOwn(variables, id)) return;
618
+ issues.push(semanticIssue(path, `unknown variable '${id}'; declare it in the top-level variables map`, { id, code: "variable-reference-unknown" }));
619
+ };
620
+ const runColor = (entry, path) => {
621
+ if (typeof entry !== "string") return;
622
+ if (hexColorPattern.test(entry) || schemeColorNames.has(entry)) return;
623
+ if (entry.startsWith("var:") && variableIdPattern.test(entry.slice("var:".length))) {
624
+ colorRef(entry, path);
625
+ return;
626
+ }
627
+ issues.push(semanticIssue(
628
+ path,
629
+ `run color '${entry}' is not a hex color, a color-scheme name, or a 'var:' reference; renderers fall back to the theme color`,
630
+ { color: entry, code: "run-color-unrecognized" }
631
+ ));
632
+ };
633
+ const richText = (entry, path) => {
634
+ if (!Array.isArray(entry)) return;
635
+ entry.forEach((run, index) => {
636
+ if (isRecord(run)) runColor(run.color, `${path}/${index}/color`);
637
+ });
638
+ };
639
+ const cellStyle = (style, path) => {
640
+ if (!isRecord(style)) return;
641
+ colorRef(style.fill, pathFor(path, "fill"));
642
+ colorRef(style.color, pathFor(path, "color"));
643
+ if (!isRecord(style.borders)) return;
644
+ const bordersPath = pathFor(path, "borders");
645
+ for (const edge of cellBorderEdges) {
646
+ const border = style.borders[edge];
647
+ if (isRecord(border)) colorRef(border.color, `${pathFor(bordersPath, edge)}/color`);
648
+ }
649
+ };
650
+ const tableCell = (cell, path) => {
651
+ if (Array.isArray(cell)) {
652
+ richText(cell, path);
653
+ return;
654
+ }
655
+ if (!isRecord(cell)) return;
656
+ richText(cell.value, pathFor(path, "value"));
657
+ cellStyle(cell.style, pathFor(path, "style"));
658
+ };
659
+ const table = (node, path) => {
660
+ if (!isRecord(node)) return;
661
+ if (Array.isArray(node.columns)) {
662
+ const columnsPath = pathFor(path, "columns");
663
+ node.columns.forEach((column, index) => {
664
+ tableCell(column, `${columnsPath}/${index}`);
665
+ });
666
+ }
667
+ if (Array.isArray(node.rows)) {
668
+ const rowsPath = pathFor(path, "rows");
669
+ node.rows.forEach((row, rowIndex) => {
670
+ if (!Array.isArray(row)) return;
671
+ row.forEach((cell, cellIndex) => {
672
+ tableCell(cell, `${rowsPath}/${rowIndex}/${cellIndex}`);
673
+ });
674
+ });
675
+ }
676
+ };
677
+ const textEntries = (entries, path) => {
678
+ if (!Array.isArray(entries)) return;
679
+ entries.forEach((entry, index) => {
680
+ const entryPath = `${path}/${index}`;
681
+ if (Array.isArray(entry)) {
682
+ richText(entry, entryPath);
683
+ return;
684
+ }
685
+ if (!isRecord(entry)) return;
686
+ richText(entry.text, pathFor(entryPath, "text"));
687
+ richText(entry.description, pathFor(entryPath, "description"));
688
+ });
689
+ };
690
+ const backgroundColors = (background, path) => {
691
+ if (!isRecord(background)) return;
692
+ colorRef(background.color, pathFor(path, "color"));
693
+ const stops = isRecord(background.gradient) ? background.gradient.stops : void 0;
694
+ if (Array.isArray(stops)) stops.forEach((stop, index) => {
695
+ if (isRecord(stop)) colorRef(stop.color, `${path}/gradient/stops/${index}/color`);
696
+ });
697
+ if (isRecord(background.pattern)) {
698
+ colorRef(background.pattern.foregroundColor, `${path}/pattern/foregroundColor`);
699
+ colorRef(background.pattern.backgroundColor, `${path}/pattern/backgroundColor`);
700
+ }
701
+ };
702
+ const designColors = (design, path) => {
703
+ if (!isRecord(design)) return;
704
+ backgroundColors(design.background, pathFor(path, "background"));
705
+ if (isRecord(design.theme)) backgroundColors(design.theme.background, `${path}/theme/background`);
706
+ };
707
+ designColors(value.design, "/design");
708
+ const payload = (node, path) => {
709
+ richText(node.text, pathFor(path, "text"));
710
+ textEntries(node.items, pathFor(path, "items"));
711
+ textEntries(node.bullets, pathFor(path, "bullets"));
712
+ table(node.table, pathFor(path, "table"));
713
+ };
714
+ value.slides.forEach((slide, index) => {
715
+ if (!isRecord(slide)) return;
716
+ const slidePath = `/slides/${index}`;
717
+ designColors(slide.design, pathFor(slidePath, "design"));
718
+ payload(slide, slidePath);
719
+ visitContentPayloads(slide, slidePath, payload);
720
+ });
721
+ return issues;
722
+ }
723
+ function presentationReferenceWarnings(value, catalogs) {
724
+ if (!isRecord(value) || !Array.isArray(value.slides)) {
725
+ return [];
726
+ }
727
+ const issues = [];
728
+ issues.push(...variableReferenceWarnings(value));
729
+ value.slides.forEach((slide, index) => {
730
+ if (!isRecord(slide)) {
731
+ return;
732
+ }
733
+ const slidePath = `/slides/${index}`;
734
+ issues.push(...slideThemeDimensionsWarnings(value, slide, index, catalogs));
735
+ issues.push(...chartTypeWarnings(slide, slidePath));
736
+ const numberingWarnings = (payload, path) => {
737
+ for (const finding2 of numberingFindings(payload).warnings) issues.push(semanticIssue(pathFor(path, finding2.key), finding2.message, { ...finding2.params, code: "numbering-start-ignored" }));
738
+ };
739
+ numberingWarnings(slide, slidePath);
740
+ visitContentPayloads(slide, slidePath, numberingWarnings);
741
+ const highlightWarnings = (payload, path) => {
742
+ const code = payload.code;
743
+ if (!isRecord(code) || !Array.isArray(code.highlight) || typeof code.source !== "string") return;
744
+ for (const finding2 of codeHighlightLines(code.highlight, code.source).issues) {
745
+ if (finding2.code === "code-highlight-invalid") continue;
746
+ issues.push(semanticIssue(pathFor(pathFor(pathFor(path, "code"), "highlight"), String(finding2.index)), finding2.message, { code: finding2.code }));
747
+ }
748
+ };
749
+ highlightWarnings(slide, slidePath);
750
+ visitContentPayloads(slide, slidePath, highlightWarnings);
751
+ for (const key of Object.keys(slide)) {
752
+ if (promotedRegionKeySet.has(key)) {
753
+ issues.push(...chartTypeWarnings(slide[key], pathFor(slidePath, key)));
754
+ }
755
+ }
756
+ });
757
+ return issues;
758
+ }
759
+ function validateLanguageSemantics(value) {
760
+ if (!isRecord(value)) {
761
+ return [];
762
+ }
763
+ if (isEnUkTag(value.bcp47)) {
764
+ return [semanticIssue("/bcp47", "Use 'en-GB' for UK English; 'en-UK' is not a valid BCP-47 region tag", {
765
+ replacement: "en-GB"
766
+ })];
767
+ }
768
+ return [];
769
+ }
770
+ function contentDepthIssues(value) {
771
+ if (!isRecord(value) || !Array.isArray(value.slides)) return [];
772
+ const stack = [];
773
+ value.slides.forEach((slide, index) => {
774
+ if (!isRecord(slide)) return;
775
+ if (Array.isArray(slide.blocks)) slide.blocks.forEach((block, i) => {
776
+ stack.push({ value: block, path: `/slides/${index}/blocks/${i}`, depth: 0, ancestors: [] });
777
+ });
778
+ for (const key of promotedRegionKeys) if (hasOwn(slide, key)) stack.push({ value: slide[key], path: `/slides/${index}/${key}`, depth: 0, ancestors: [] });
779
+ });
780
+ while (stack.length) {
781
+ const entry = stack.pop();
782
+ if (!isRecord(entry.value) || !Array.isArray(entry.value.blocks)) continue;
783
+ if (entry.ancestors.includes(entry.value) || entry.depth >= MAX_COMPOSITION_DEPTH) return [semanticIssue(entry.path, `content groups must be acyclic and nest at most ${MAX_COMPOSITION_DEPTH} levels`)];
784
+ const ancestors = [...entry.ancestors, entry.value];
785
+ entry.value.blocks.forEach((block, i) => {
786
+ stack.push({ value: block, path: `${entry.path}/blocks/${i}`, depth: entry.depth + 1, ancestors });
787
+ });
788
+ }
789
+ return [];
790
+ }
791
+ var variableTypeList = VARIABLE_KINDS.join(", ");
792
+ function variableIssue(id, suffix, keyword, message, params) {
793
+ return {
794
+ instancePath: `/variables/${id.replaceAll("~", "~0").replaceAll("/", "~1")}${suffix}`,
795
+ schemaPath: "#/$defs/Variable/oneOf",
796
+ keyword,
797
+ params,
798
+ message
799
+ };
800
+ }
801
+ function typedVariableErrors(errors, value) {
802
+ if (!isRecord(value) || !isRecord(value.variables)) return errors;
803
+ if (!errors.some((error) => error.instancePath.startsWith("/variables/"))) return errors;
804
+ const variables = value.variables;
805
+ const kept = errors.filter((error) => !error.instancePath.startsWith("/variables/"));
806
+ const schemaId = schemas.presentation.$id;
807
+ for (const [id, entry] of Object.entries(variables)) {
808
+ if (typeof entry === "string") {
809
+ if (!hexColorPattern.test(entry)) kept.push(variableIssue(id, "", "type", "must be a hex color string or a variable object", {}));
810
+ continue;
811
+ }
812
+ if (!isRecord(entry)) {
813
+ kept.push(variableIssue(id, "", "type", "must be a hex color string or a variable object", {}));
814
+ continue;
815
+ }
816
+ const kind = entry.type;
817
+ if (typeof kind !== "string" || !VARIABLE_KINDS.includes(kind)) {
818
+ kept.push(variableIssue(id, "/type", "enum", `must be one of: ${variableTypeList} (a hex string is shorthand for a color variable)`, { allowedValues: [...VARIABLE_KINDS] }));
819
+ continue;
820
+ }
821
+ const definition = `${kind.charAt(0).toUpperCase()}${kind.slice(1)}Variable`;
822
+ const check = getAjv().getSchema(`${schemaId}#/$defs/${definition}`);
823
+ if (!check || check(entry) === true) continue;
824
+ for (const error of check.errors ?? []) {
825
+ kept.push({ ...error, instancePath: `/variables/${id.replaceAll("~", "~0").replaceAll("/", "~1")}${error.instancePath}`, schemaPath: `#/$defs/${definition}${error.schemaPath.replace(/^#/, "")}` });
826
+ }
827
+ }
828
+ return kept;
829
+ }
830
+ function pointerValue(root, pointer4) {
831
+ let current = root;
832
+ for (const token of pointer4.split("/").slice(1)) {
833
+ const key = token.replaceAll("~1", "/").replaceAll("~0", "~");
834
+ if (Array.isArray(current)) current = current[Number(key)];
835
+ else if (isRecord(current) && hasOwn(current, key)) current = current[key];
836
+ else return void 0;
837
+ }
838
+ return current;
839
+ }
840
+ var datasetTableMessage = (field) => `'${field}' is not allowed on a dataset-backed table: it takes its headers, rows and column formats from the dataset`;
841
+ function dataUnionErrors(errors, value) {
842
+ let kept = errors;
843
+ const schemaId = schemas.presentation.$id;
844
+ for (const union of errors) {
845
+ if (union.keyword !== "oneOf") continue;
846
+ const chartData = /\/chart\/data$/.test(union.instancePath) && /\/properties\/data\/oneOf$/.test(union.schemaPath);
847
+ const header = /\/table\/columns\/\d+$/.test(union.instancePath) && /\/properties\/columns\/items\/oneOf$/.test(union.schemaPath);
848
+ if (!chartData && !header) continue;
849
+ const target = pointerValue(value, union.instancePath);
850
+ if (!isRecord(target)) continue;
851
+ const branch = chartData ? hasOwn(target, "dataset") ? "DatasetRef" : hasOwn(target, "rows") ? "ChartData" : void 0 : hasOwn(target, "value") ? "StyledTableCell" : hasOwn(target, "name") ? "DataColumn" : void 0;
852
+ const check = branch ? getAjv().getSchema(`${schemaId}#/$defs/${branch}`) : void 0;
853
+ if (!check || check(target) === true) continue;
854
+ const branchErrors = (check.errors ?? []).map((error) => ({ ...error, instancePath: `${union.instancePath}${error.instancePath}`, schemaPath: `#/$defs/${branch}${error.schemaPath.replace(/^#/, "")}` }));
855
+ const prefix = `${union.instancePath}/`;
856
+ kept = [...kept.filter((error) => error.instancePath !== union.instancePath && !error.instancePath.startsWith(prefix)), ...branchErrors];
857
+ }
858
+ const exclusions = kept.filter((error) => error.keyword === "false schema" && /\/table\/(rows|columns|fields)$/.test(error.instancePath) && /\/(then|else)\/properties\/(rows|columns|fields)\//.test(`${error.schemaPath}/`));
859
+ if (!exclusions.length) return kept;
860
+ const tables = new Set(exclusions.map((error) => error.instancePath.replace(/\/[^/]+$/, "")));
861
+ return kept.filter((error) => !(error.keyword === "if" && tables.has(error.instancePath))).map((error) => {
862
+ if (!exclusions.includes(error)) return error;
863
+ const field = error.instancePath.slice(error.instancePath.lastIndexOf("/") + 1);
864
+ return { ...error, message: field === "fields" ? "'fields' applies only to a dataset-backed table; add 'dataset' or remove 'fields'" : datasetTableMessage(field) };
865
+ });
866
+ }
867
+ function validateAgainstSchema(value, schemaOrKind = "presentation", options = {}) {
868
+ const resolved = resolveValidator(schemaOrKind);
869
+ if (resolved.schemaName === "presentation") {
870
+ const errors2 = contentDepthIssues(value);
871
+ if (errors2.length) return { valid: false, errors: errors2, warnings: [], unfilled: [], schemaName: resolved.schemaName };
872
+ }
873
+ const variableIssues = { errors: [], warnings: [] };
874
+ const unfilled = [];
875
+ let subject = value;
876
+ let template;
877
+ let unfilledVariables;
878
+ if (resolved.schemaName === "presentation" && isRecord(value) && (hasContentVariables(value) || options.values !== void 0 || options.template !== void 0)) {
879
+ template = options.template ?? isTemplate(value);
880
+ const view = instantiateForValidation(value, options.values ?? {}, template);
881
+ subject = view.presentation;
882
+ unfilledVariables = view.unfilled;
883
+ for (const entry of view.diagnostics) {
884
+ if (entry.code === "variable-example-used" || entry.code === "variable-rich-flattened" || entry.code === "variable-unfilled") continue;
885
+ const issue2 = semanticIssue(entry.path, entry.message, { id: entry.id, code: entry.code });
886
+ (entry.severity === "error" ? variableIssues.errors : variableIssues.warnings).push(issue2);
887
+ }
888
+ if (!template) {
889
+ const declared = isRecord(value.variables) ? value.variables : {};
890
+ for (const id of view.unfilled) {
891
+ if (isRecord(declared[id]) && declared[id].value !== void 0) continue;
892
+ unfilled.push(semanticIssue(`/variables/${id}`, `required variable '${id}' has no value; give it a value, fill it before use, or mark the document as a template ("template": true)`, { id, code: "variable-unfilled" }));
893
+ }
894
+ }
895
+ }
896
+ const valid = resolved.validate(subject) === true;
897
+ const ajvErrors = valid ? [] : typedVariableErrors(resolved.validate.errors ?? [], subject);
898
+ const errors = (resolved.schemaName === "presentation" ? dataUnionErrors(ajvErrors, subject) : ajvErrors).map(toIssue);
899
+ const warnings = [];
900
+ if (resolved.schemaName === "presentation") {
901
+ errors.push(...presentationSemanticIssues(subject));
902
+ errors.push(...variableIssues.errors);
903
+ const data = dataIssues(subject);
904
+ errors.push(...data.errors);
905
+ warnings.push(...presentationReferenceWarnings(subject, options.catalogs));
906
+ warnings.push(...data.warnings);
907
+ warnings.push(...variableIssues.warnings);
908
+ } else if (resolved.schemaName === "language") {
909
+ errors.push(...validateLanguageSemantics(value));
910
+ }
911
+ return {
912
+ valid: errors.length === 0,
913
+ errors,
914
+ warnings,
915
+ unfilled,
916
+ schemaName: resolved.schemaName,
917
+ catalogKind: resolved.catalogKind,
918
+ ...template !== void 0 ? { template, unfilledVariables } : {}
919
+ };
920
+ }
921
+
922
+ // src/narrative-plan.ts
923
+ var isRec = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
924
+ function resolveNarrative(document, options = {}) {
925
+ if (!isRec(document) || typeof document.narrative !== "string") return void 0;
926
+ const found = resolveReference(document, "narratives", document.narrative, { catalogs: options.catalogs ?? [] });
927
+ if (!found) return void 0;
928
+ const record = found.record;
929
+ const beats = Array.isArray(record.beats) ? record.beats.flatMap((beat) => isRec(beat) && typeof beat.id === "string" ? [beat.id] : []) : [];
930
+ return { record, origin: found.origin, beats };
931
+ }
932
+ function slideBeatReferences(document) {
933
+ if (!isRec(document) || !Array.isArray(document.slides)) return [];
934
+ const references = [];
935
+ document.slides.forEach((slide, index) => {
936
+ if (!isRec(slide)) return;
937
+ const path = `/slides/${index}/beat`;
938
+ if (typeof slide.beat === "string") references.push({ path, slide: index, beat: slide.beat });
939
+ else if (Array.isArray(slide.beat))
940
+ slide.beat.forEach((beat, at2) => {
941
+ if (typeof beat === "string") references.push({ path: `${path}/${at2}`, slide: index, beat });
942
+ });
943
+ });
944
+ return references;
945
+ }
946
+ function unknownBeatReferences(document, narrative) {
947
+ const known = new Set(narrative.beats);
948
+ return slideBeatReferences(document).filter((reference) => !known.has(reference.beat));
949
+ }
950
+ function unreferencedBeats(document, narrative) {
951
+ const referenced = new Set(slideBeatReferences(document).map((reference) => reference.beat));
952
+ if (referenced.size === 0) return [];
953
+ return narrative.beats.flatMap((beat, index) => referenced.has(beat) ? [] : [{ beat, index }]);
954
+ }
955
+ function durationRange(record) {
956
+ if (!isRec(record) || !isRec(record.duration)) return void 0;
957
+ const bound = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
958
+ const min = bound(record.duration.min), max = bound(record.duration.max);
959
+ return min === void 0 && max === void 0 ? void 0 : { ...min !== void 0 ? { min } : {}, ...max !== void 0 ? { max } : {} };
960
+ }
961
+ function durationRangeInverted(record) {
962
+ const range = durationRange(record);
963
+ return range?.min !== void 0 && range.max !== void 0 && range.min > range.max;
964
+ }
965
+ function durationOutsideNarrative(document, narrative) {
966
+ if (!isRec(document) || typeof document.duration !== "number" || !Number.isFinite(document.duration)) return void 0;
967
+ const range = durationRange(narrative.record);
968
+ if (!range || durationRangeInverted(narrative.record)) return void 0;
969
+ const duration = document.duration;
970
+ if (range.min !== void 0 && duration < range.min || range.max !== void 0 && duration > range.max) return { duration, range };
971
+ return void 0;
972
+ }
973
+ function describeDurationRange(range) {
974
+ if (range.min !== void 0 && range.max !== void 0) return `${range.min}-${range.max} minutes`;
975
+ return range.min !== void 0 ? `at least ${range.min} minutes` : `at most ${range.max} minutes`;
976
+ }
977
+
978
+ // src/rule-context.ts
979
+ var rule = (id, category, severity, summary, rationale, extra = {}) => ({ id: `opf/${id}`, name: id, category, severity, cost: extra.cost ?? "structure", summary, rationale, ...extra });
980
+
981
+ // src/rule-design.ts
982
+ var rec = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
983
+ var toRgb = (hex) => {
984
+ const n = normalizeHexColor(hex) ?? "#000000";
985
+ return [1, 3, 5].map((at2) => Number.parseInt(n.slice(at2, at2 + 2), 16));
986
+ };
987
+ var toHex = (rgb) => `#${rgb.map((c) => Math.max(0, Math.min(255, Math.round(c))).toString(16).padStart(2, "0")).join("").toUpperCase()}`;
988
+ var linear = (c) => {
989
+ const n = c / 255;
990
+ return n <= 0.04045 ? n / 12.92 : ((n + 0.055) / 1.055) ** 2.4;
991
+ };
992
+ var delinear = (c) => {
993
+ const v = Math.max(0, Math.min(1, c));
994
+ return 255 * (v <= 31308e-7 ? v * 12.92 : 1.055 * v ** (1 / 2.4) - 0.055);
995
+ };
996
+ function blend(fg, bg, alpha) {
997
+ const a = Math.max(0, Math.min(1, alpha)), f = toRgb(fg), b = toRgb(bg);
998
+ return toHex(f.map((c, i) => c * a + (b[i] ?? 0) * (1 - a)));
999
+ }
1000
+ var relativeLuminance = (hex) => {
1001
+ const [r, g, b] = toRgb(hex);
1002
+ return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);
1003
+ };
1004
+ function contrastRatio(a, b) {
1005
+ const x = relativeLuminance(a), y = relativeLuminance(b);
1006
+ return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05);
1007
+ }
1008
+ function lab(hex) {
1009
+ const [r, g, b] = toRgb(hex).map(linear);
1010
+ const x = (0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / 0.95047, y = 0.2126729 * r + 0.7151522 * g + 0.072175 * b, z = (0.0193339 * r + 0.119192 * g + 0.9503041 * b) / 1.08883;
1011
+ const f = (t) => t > 216 / 24389 ? Math.cbrt(t) : (24389 / 27 * t + 16) / 116;
1012
+ const fx = f(x), fy = f(y), fz = f(z);
1013
+ return [116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz)];
1014
+ }
1015
+ function colorDifference(a, b) {
1016
+ const [l1, a1, b1] = lab(a), [l2, a2, b2] = lab(b);
1017
+ return Math.hypot(l1 - l2, a1 - a2, b1 - b2);
1018
+ }
1019
+ var CVD_MATRICES = {
1020
+ // Machado, Oliveira and Fernandes (2009), severity 1.0, applied to linear sRGB.
1021
+ protanopia: [
1022
+ [0.152286, 1.052583, -0.204868],
1023
+ [0.114503, 0.786281, 0.099216],
1024
+ [-3882e-6, -0.048116, 1.051998]
1025
+ ],
1026
+ deuteranopia: [
1027
+ [0.367322, 0.860646, -0.227968],
1028
+ [0.280085, 0.672501, 0.047413],
1029
+ [-0.01182, 0.04294, 0.968881]
1030
+ ],
1031
+ tritanopia: [
1032
+ [1.255528, -0.076749, -0.178779],
1033
+ [-0.078411, 0.930809, 0.147602],
1034
+ [4733e-6, 0.691367, 0.3039]
1035
+ ]
1036
+ };
1037
+ var visionModels = ["protanopia", "deuteranopia", "tritanopia", "greyscale"];
1038
+ function simulateVision(hex, model) {
1039
+ const rgb = toRgb(hex).map(linear);
1040
+ if (model === "greyscale") {
1041
+ const y = 0.2126 * (rgb[0] ?? 0) + 0.7152 * (rgb[1] ?? 0) + 0.0722 * (rgb[2] ?? 0);
1042
+ const v = delinear(y);
1043
+ return toHex([v, v, v]);
1044
+ }
1045
+ const m = CVD_MATRICES[model];
1046
+ return toHex(m.map((row) => delinear(row[0] * (rgb[0] ?? 0) + row[1] * (rgb[1] ?? 0) + row[2] * (rgb[2] ?? 0))));
1047
+ }
1048
+ var PAGE = "#FFFFFF";
1049
+ var colorFrom = (scheme, slot, fallback) => typeof slot === "string" ? slot.startsWith("#") ? normalizeHexColor(slot) ?? fallback : normalizeHexColor(scheme[slot]) ?? fallback : fallback;
1050
+ function backdropOf(definition, scheme, text, variables) {
1051
+ const resolve2 = (value, fallback) => backgroundColorRef(value, scheme, variables, fallback);
1052
+ const canvas = defaultSlideBackground(scheme);
1053
+ if (!definition) return { kind: "solid", color: canvas };
1054
+ if (imageBackground(definition)) return { kind: "image", opacity: imageBackground(definition)?.opacity ?? 1, base: PAGE };
1055
+ if (typeof definition === "string") return { kind: "solid", color: colorFrom(scheme, definition, canvas) };
1056
+ const bg = rec(definition), opacity = typeof bg.opacity === "number" ? Math.max(0, Math.min(1, bg.opacity)) : 1;
1057
+ switch (bg.type) {
1058
+ case "theme":
1059
+ return { kind: "solid", color: blend(colorFrom(scheme, bg.slot, canvas), PAGE, opacity) };
1060
+ case "solid":
1061
+ return { kind: "solid", color: blend(resolve2(bg.color, PAGE), PAGE, opacity) };
1062
+ case "gradient": {
1063
+ const stops = Array.isArray(rec(bg.gradient).stops) ? rec(bg.gradient).stops : [];
1064
+ return {
1065
+ kind: "gradient",
1066
+ angle: Number(rec(bg.gradient).angle ?? 0),
1067
+ stops: stops.map((stop, index) => ({
1068
+ color: blend(resolve2(stop.color, canvas), PAGE, opacity),
1069
+ position: typeof stop.position === "number" ? stop.position : index / Math.max(1, stops.length - 1)
1070
+ }))
1071
+ };
1072
+ }
1073
+ case "pattern": {
1074
+ const pattern = rec(bg.pattern);
1075
+ return {
1076
+ kind: "pattern",
1077
+ colors: [
1078
+ blend(resolve2(pattern.backgroundColor, PAGE), PAGE, opacity),
1079
+ blend(resolve2(pattern.foregroundColor, text), PAGE, opacity)
1080
+ ]
1081
+ };
1082
+ }
1083
+ case "image":
1084
+ return { kind: "image", opacity, base: PAGE };
1085
+ default:
1086
+ return { kind: "solid", color: canvas };
1087
+ }
1088
+ }
1089
+ function backgroundColorRef(value, scheme, variables, fallback) {
1090
+ if (typeof value !== "string") return fallback;
1091
+ const literal = normalizeHexColor(value);
1092
+ if (literal) return literal;
1093
+ const roles = resolveColorRoles(scheme);
1094
+ return resolveColorRef(value.trim(), {
1095
+ colorScheme: scheme,
1096
+ roles: { primary: roles.primary, secondary: roles.secondary, accent: roles.accent },
1097
+ variables,
1098
+ fallback
1099
+ });
1100
+ }
1101
+ function decisionColor(definition, scheme, variables = {}) {
1102
+ if (!definition) return void 0;
1103
+ const canvas = defaultSlideBackground(scheme);
1104
+ const reference = (value) => backgroundColorRef(value, scheme, variables, PAGE);
1105
+ if (imageBackground(definition)) return void 0;
1106
+ if (typeof definition === "string") return colorFrom(scheme, definition, canvas);
1107
+ const bg = rec(definition);
1108
+ switch (bg.type) {
1109
+ case "theme":
1110
+ return colorFrom(scheme, bg.slot, canvas);
1111
+ case "solid":
1112
+ return reference(bg.color);
1113
+ case "pattern":
1114
+ return reference(rec(bg.pattern).backgroundColor);
1115
+ default:
1116
+ return void 0;
1117
+ }
1118
+ }
1119
+ function resolveDesign(document, index, context) {
1120
+ const deck = rec(document.design), own2 = rec(rec(rec(document.slides)[index]).design);
1121
+ const { options, diagnostics, resolved } = context;
1122
+ const { theme, colorScheme, fontScheme, fontSchemePath } = resolved;
1123
+ const fontPath = `/${fontSchemePath.split(".").join("/")}`;
1124
+ const fontDiagnostic = diagnostics.find((diagnostic) => diagnostic.kind === "fontSchemes");
1125
+ const dimensions = { width: options.width, height: options.height };
1126
+ const variables = rec(document.variables);
1127
+ const roles = resolveColorRoles(colorScheme, { background: decisionColor(own2.background ?? deck.background ?? theme.background, colorScheme, variables) });
1128
+ const dark = roles.dark;
1129
+ const text = roles.text;
1130
+ const backdrop = backdropOf(own2.background ?? deck.background ?? theme.background, colorScheme, text, variables);
1131
+ const picture = imageBackground(own2.background ?? deck.background ?? theme.background);
1132
+ 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;
1133
+ return {
1134
+ theme,
1135
+ colorScheme,
1136
+ fontScheme,
1137
+ fonts: options.fontFamilies,
1138
+ dimensions,
1139
+ backdrop,
1140
+ dark,
1141
+ colors: {
1142
+ background: roles.background,
1143
+ surface: roles.surface,
1144
+ text,
1145
+ mutedText: roles.textSecondary,
1146
+ primary: roles.primary,
1147
+ secondary: roles.secondary,
1148
+ accent: roles.accent
1149
+ },
1150
+ variables,
1151
+ ...backgroundImage ? { backgroundImage } : {},
1152
+ fontSchemePath: fontPath,
1153
+ ...fontDiagnostic ? { unresolvedFontScheme: fontDiagnostic.reference } : {}
1154
+ };
1155
+ }
1156
+ function resolveTextColor(reference, design, fallback) {
1157
+ if (typeof reference !== "string" || !reference.trim()) return void 0;
1158
+ const literal = normalizeHexColor(reference);
1159
+ if (literal) return literal;
1160
+ return resolveColorRef(reference.trim(), {
1161
+ colorScheme: design.colorScheme,
1162
+ roles: {
1163
+ primary: design.colors.primary,
1164
+ secondary: design.colors.secondary,
1165
+ accent: design.colors.accent,
1166
+ background: design.colors.background,
1167
+ surface: design.colors.surface,
1168
+ text: design.colors.text,
1169
+ textSecondary: design.colors.mutedText
1170
+ },
1171
+ variables: design.variables,
1172
+ fallback
1173
+ });
1174
+ }
1175
+ var GREY_RAMP = [0, 32, 64, 96, 128, 160, 192, 224, 255].map((v) => toHex([v, v, v]));
1176
+ function backdropColors(backdrop, box, size, layer) {
1177
+ switch (backdrop.kind) {
1178
+ case "solid":
1179
+ return { colors: [backdrop.color], unknown: false };
1180
+ case "pattern":
1181
+ return { colors: [...backdrop.colors], unknown: false };
1182
+ case "gradient": {
1183
+ const stops = [...backdrop.stops].sort((a, b) => a.position - b.position);
1184
+ if (!stops.length) return { colors: [PAGE], unknown: false };
1185
+ const angle = backdrop.angle * Math.PI / 180, c = Math.cos(angle), s = Math.sin(angle), region = box ?? { x: 0, y: 0, width: size.width, height: size.height };
1186
+ const colors = /* @__PURE__ */ new Set();
1187
+ for (let i = 0; i <= 4; i++)
1188
+ for (let j = 0; j <= 4; j++) {
1189
+ const px = (region.x + region.width * i / 4) / size.width, py = (region.y + region.height * j / 4) / size.height, t = Math.max(0, Math.min(1, 0.5 + (px - 0.5) * c + (py - 0.5) * s));
1190
+ const after = stops.findIndex((stop) => stop.position >= t);
1191
+ if (after <= 0) colors.add(stops[Math.max(0, after)]?.color ?? PAGE);
1192
+ else {
1193
+ const lo = stops[after - 1], hi = stops[after], span = hi.position - lo.position || 1;
1194
+ colors.add(blend(hi.color, lo.color, (t - lo.position) / span));
1195
+ }
1196
+ }
1197
+ return { colors: [...colors], unknown: false };
1198
+ }
1199
+ case "image": {
1200
+ const opacity = layer?.opacity ?? backdrop.opacity;
1201
+ const colors = GREY_RAMP.map((grey) => {
1202
+ let color = blend(grey, backdrop.base, opacity);
1203
+ if (layer?.overlay) color = blend(layer.overlay.color, color, layer.overlay.opacity);
1204
+ return color;
1205
+ });
1206
+ return { colors, unknown: true };
1207
+ }
1208
+ }
1209
+ }
1210
+ function worstContrast(text, sample) {
1211
+ let worst = { ratio: Infinity, against: sample.colors[0] ?? PAGE };
1212
+ for (const color of sample.colors) {
1213
+ const ratio = contrastRatio(text, color);
1214
+ if (ratio < worst.ratio) worst = { ratio, against: color };
1215
+ }
1216
+ return worst;
1217
+ }
1218
+ function readableColor(candidates, sample, minimum) {
1219
+ for (const candidate of [...candidates, "#000000", "#FFFFFF"])
1220
+ if (worstContrast(candidate, sample).ratio >= minimum) return candidate;
1221
+ return void 0;
1222
+ }
1223
+ var DEFAULT_CHART_PALETTE = [
1224
+ "#2874A6",
1225
+ "#1B4F72",
1226
+ "#5499C7",
1227
+ "#7BDBB2",
1228
+ "#3AC67A",
1229
+ "#24A89E",
1230
+ "#F59E0B",
1231
+ "#EF4444",
1232
+ "#8B5CF6",
1233
+ "#14B8A6",
1234
+ "#0F172A",
1235
+ "#64748B"
1236
+ ];
1237
+
1238
+ // src/rule-content.ts
1239
+ var pointer2 = (...parts2) => parts2.length ? "/" + parts2.map((part) => String(part).replaceAll("~", "~0").replaceAll("/", "~1")).join("/") : "";
1240
+ var pointerOfDotted = (path) => "/" + path.split(".").map((part) => part.replaceAll("~", "~0").replaceAll("/", "~1")).join("/");
1241
+ function slidePayloads(slide, slidePath) {
1242
+ const out = [{ path: slidePath, node: slide, root: true }];
1243
+ visitContentPayloads(slide, slidePath, (node, path) => out.push({ path, node, root: false }));
1244
+ return out;
1245
+ }
1246
+ var isText = (value) => typeof value === "string" || Array.isArray(value);
1247
+ function textValues(slide, slidePath) {
1248
+ const out = [];
1249
+ for (const field of ["title", "subtitle", "tag"])
1250
+ if (isText(slide[field])) out.push({ path: pointer2(...splitPointer(slidePath), field), value: slide[field], role: field, payload: slidePath, field });
1251
+ for (const payload of slidePayloads(slide, slidePath)) {
1252
+ const node = payload.node, at2 = (...tail) => pointer2(...splitPointer(payload.path), ...tail);
1253
+ const push = (path, value, role, field) => {
1254
+ if (isText(value)) out.push({ path, value, role, payload: payload.path, field });
1255
+ };
1256
+ push(at2("text"), node.text, "body", "text");
1257
+ for (const field of ["items", "bullets"])
1258
+ if (Array.isArray(node[field]))
1259
+ node[field].forEach((entry, index) => {
1260
+ if (isText(entry)) push(at2(field, index), entry, "list", field);
1261
+ else if (entry && typeof entry === "object") {
1262
+ push(at2(field, index, "text"), entry.text, "list", field);
1263
+ push(at2(field, index, "description"), entry.description, "list-description", field);
1264
+ }
1265
+ });
1266
+ const table = rec(node.table);
1267
+ if (Array.isArray(table.columns))
1268
+ table.columns.forEach((cell, c) => {
1269
+ const styled = rec(cell);
1270
+ if (!Object.hasOwn(styled, "value") && typeof styled.name === "string") push(at2("table", "columns", c, "name"), styled.name, "table-header", "table");
1271
+ else {
1272
+ const value = Object.hasOwn(styled, "value") ? styled.value : cell;
1273
+ push(at2("table", "columns", c, ...Object.hasOwn(styled, "value") ? ["value"] : []), value, "table-header", "table");
1274
+ }
1275
+ });
1276
+ if (Array.isArray(table.rows))
1277
+ table.rows.forEach((row, r) => {
1278
+ if (!Array.isArray(row)) return;
1279
+ row.forEach((cell, c) => {
1280
+ const styled = rec(cell);
1281
+ const value = Object.hasOwn(styled, "value") ? styled.value : cell;
1282
+ push(at2("table", "rows", r, c, ...Object.hasOwn(styled, "value") ? ["value"] : []), value, "table", "table");
1283
+ });
1284
+ });
1285
+ const quote = node.quote;
1286
+ if (typeof quote === "string") push(at2("quote"), quote, "quote", "quote");
1287
+ else if (quote && typeof quote === "object") {
1288
+ push(at2("quote", "text"), quote.text, "quote", "quote");
1289
+ push(at2("quote", "attribution"), quote.attribution, "quote-footer", "quote");
1290
+ push(at2("quote", "role"), quote.role, "quote-footer", "quote");
1291
+ push(at2("quote", "source"), quote.source, "quote-footer", "quote");
1292
+ }
1293
+ const metric = node.metric;
1294
+ if (typeof metric === "string") push(at2("metric"), metric, "metric", "metric");
1295
+ else if (metric && typeof metric === "object")
1296
+ for (const key of ["value", "label", "unit", "delta"]) {
1297
+ const value = metric[key];
1298
+ if (typeof value === "string") push(at2("metric", key), value, "metric", "metric");
1299
+ }
1300
+ const timeline = node.timeline, events = Array.isArray(timeline) ? timeline : Array.isArray(rec(timeline).events) ? rec(timeline).events : [];
1301
+ if (typeof rec(timeline).name === "string") push(at2("timeline", "name"), rec(timeline).name, "timeline", "timeline");
1302
+ events.forEach((event, index) => {
1303
+ const base = Array.isArray(timeline) ? ["timeline", index] : ["timeline", "events", index];
1304
+ push(at2(...base, "when"), event?.when, "timeline", "timeline");
1305
+ push(at2(...base, "what"), event?.what, "timeline", "timeline");
1306
+ });
1307
+ }
1308
+ return out;
1309
+ }
1310
+ function splitPointer(path) {
1311
+ return path === "" ? [] : path.slice(1).split("/").map((part) => part.replaceAll("~1", "/").replaceAll("~0", "~"));
1312
+ }
1313
+ var plainText = (value) => typeof value === "string" ? value : value.map((run) => typeof run === "string" ? run : typeof rec(run).text === "string" ? rec(run).text : "").join("");
1314
+ function runsOf(tv) {
1315
+ if (typeof tv.value === "string") return [{ path: tv.path, text: tv.value, style: {}, index: -1, valuePath: tv.path }];
1316
+ return tv.value.map((run, index) => ({
1317
+ path: pointer2(...splitPointer(tv.path), index),
1318
+ text: typeof run === "string" ? run : typeof rec(run).text === "string" ? rec(run).text : "",
1319
+ style: typeof run === "string" ? {} : rec(run),
1320
+ index,
1321
+ valuePath: tv.path
1322
+ }));
1323
+ }
1324
+ function altOf(value, assets) {
1325
+ const own2 = typeof value === "object" && value !== null ? value.alt : void 0;
1326
+ if (typeof own2 === "string") return own2;
1327
+ let source = typeof value === "string" ? value : rec(value).src;
1328
+ for (let hop = 0; typeof source === "string" && source.startsWith("asset:") && hop < 8; hop++) {
1329
+ const entry = rec(assets)[source.slice(6)];
1330
+ if (typeof rec(entry).alt === "string") return rec(entry).alt;
1331
+ source = typeof entry === "string" ? entry : rec(entry).src;
1332
+ }
1333
+ return void 0;
1334
+ }
1335
+ var sourceOfAsset = (value) => {
1336
+ const source = typeof value === "string" ? value : rec(value).src;
1337
+ return typeof source === "string" ? source : void 0;
1338
+ };
1339
+ var LOGO_VARIANTS = ["default", "light", "dark", "stacked", "stackedLight", "stackedDark", "icon", "iconLight", "iconDark", "wordmark", "wordmarkLight", "wordmarkDark"];
1340
+ function assetRefs(document, slide, slidePath, includeDeck) {
1341
+ const out = [];
1342
+ for (const payload of slidePayloads(slide, slidePath)) {
1343
+ const at2 = (...tail) => pointer2(...splitPointer(payload.path), ...tail);
1344
+ if (payload.node.image !== void 0) out.push({ path: at2("image"), value: payload.node.image, kind: "image" });
1345
+ if (payload.node.video !== void 0) out.push({ path: at2("video"), value: payload.node.video, kind: "video" });
1346
+ if (rec(payload.node.quote).photo !== void 0) out.push({ path: at2("quote", "photo"), value: rec(payload.node.quote).photo, kind: "quote-photo" });
1347
+ }
1348
+ const designRefs = (design, base) => {
1349
+ const logo = design.logo;
1350
+ if (typeof logo === "string" || logo && typeof logo.src === "string") out.push({ path: `${base}/logo`, value: logo, kind: "logo" });
1351
+ else if (logo && typeof logo === "object") {
1352
+ for (const variant of LOGO_VARIANTS)
1353
+ if (logo[variant] !== void 0) out.push({ path: `${base}/logo/${variant}`, value: logo[variant], kind: "logo" });
1354
+ }
1355
+ for (const kind of ["header", "footer"])
1356
+ for (const zone of ["left", "center", "right"]) {
1357
+ const item = rec(rec(design[kind])[zone]);
1358
+ if (item.image !== void 0) out.push({ path: `${base}/${kind}/${zone}/image`, value: item.image, kind: "furniture" });
1359
+ }
1360
+ };
1361
+ designRefs(rec(slide.design), `${slidePath}/design`);
1362
+ if (includeDeck) {
1363
+ designRefs(rec(document.design), "/design");
1364
+ const organizations = Array.isArray(document.organization) ? document.organization : document.organization ? [document.organization] : [];
1365
+ organizations.forEach((organization, index) => {
1366
+ if (organization?.logo !== void 0)
1367
+ out.push({
1368
+ path: Array.isArray(document.organization) ? `/organization/${index}/logo` : "/organization/logo",
1369
+ value: organization.logo,
1370
+ kind: "organization"
1371
+ });
1372
+ });
1373
+ const speakers = Array.isArray(document.speaker) ? document.speaker : document.speaker ? [document.speaker] : [];
1374
+ speakers.forEach((speaker, index) => {
1375
+ if (speaker?.photo !== void 0)
1376
+ out.push({ path: Array.isArray(document.speaker) ? `/speaker/${index}/photo` : "/speaker/photo", value: speaker.photo, kind: "speaker" });
1377
+ });
1378
+ }
1379
+ return out;
1380
+ }
1381
+
1382
+ // src/rules-accessibility.ts
1383
+ var PX_TO_PT = 0.75;
1384
+ var isLarge = (sizePx, bold) => {
1385
+ const pt = sizePx * PX_TO_PT;
1386
+ return pt >= 18 || bold && pt >= 14;
1387
+ };
1388
+ function slideBackdrop(context, box) {
1389
+ const { design, composition } = context;
1390
+ const image = composition?.backgroundImage;
1391
+ if (image) {
1392
+ const overlay = image.overlay && !image.overlay.edge ? { color: resolveTextColor(image.overlay.color, design, "#000000") ?? "#000000", opacity: image.overlay.opacity } : void 0;
1393
+ return backdropColors({ kind: "image", opacity: image.opacity ?? 1, base: "#FFFFFF" }, box, design.dimensions, { overlay });
1394
+ }
1395
+ return backdropColors(design.backdrop, box, design.dimensions);
1396
+ }
1397
+ function inkBox(item, measurement) {
1398
+ const fit = item.text;
1399
+ if (!fit || !Array.isArray(fit.lines) || !fit.lines.length || !item.textStyle || item.quoteLayout || item.metricLayout || item.timelineLayout) return item.box;
1400
+ try {
1401
+ const measure = textWidthMeasurer(item.textStyle, measurement);
1402
+ const width = Math.min(item.box.width, Math.max(...fit.lines.map((line) => measure(line, fit.fontSize))));
1403
+ const height = Math.min(item.box.height, fit.lines.length * fit.lineHeight);
1404
+ const x = item.alignment === "center" ? item.box.x + (item.box.width - width) / 2 : item.alignment === "right" ? item.box.x + item.box.width - width : item.box.x;
1405
+ return { x, y: item.box.y, width, height };
1406
+ } catch {
1407
+ return item.box;
1408
+ }
1409
+ }
1410
+ function collectSamples(context) {
1411
+ const measurement = context.measurement;
1412
+ const { design, composition } = context;
1413
+ if (!composition) return [];
1414
+ const samples = [];
1415
+ const surface = { colors: [design.colors.surface], unknown: false };
1416
+ const scale = Math.min(design.dimensions.width, design.dimensions.height) / 720;
1417
+ for (const item of composition.items) {
1418
+ if (["chart", "image", "video", "code"].includes(item.field) || item.type === "chart" || item.type === "image" || item.type === "video" || item.type === "code") continue;
1419
+ const backdrop = item.frameBox ? surface : slideBackdrop(context, inkBox(item, measurement));
1420
+ const itemPath = pointerOfDotted(item.path);
1421
+ const heading = item.field === "title" || item.field === "subtitle" || item.field === "tag";
1422
+ const baseSize = (item.text?.fontSize ?? 25 * scale) / scale;
1423
+ const bold = item.field === "title";
1424
+ const label = item.field === "title" ? "Title" : item.field === "subtitle" ? "Subtitle" : item.field === "tag" ? "Tag" : "Text";
1425
+ if (item.field === "table") {
1426
+ const grid = tableGrid(item.value, item.path);
1427
+ for (const row of grid.rows)
1428
+ for (const cell of row) {
1429
+ const fillRef = resolveTextColor(cell.style.fill, design, "");
1430
+ const fill = fillRef || (cell.header ? design.colors.primary : design.colors.surface);
1431
+ const cellBackdrop = { colors: [fill], unknown: false };
1432
+ const cellPath = pointerOfDotted(cell.valuePath);
1433
+ const stylePath = pointerOfDotted(cell.path) + "/style/color";
1434
+ if (cell.style.color != null) {
1435
+ const color = resolveTextColor(cell.style.color, design, design.colors.text);
1436
+ if (color) samples.push({ path: stylePath, color, sizePx: 15, bold: cell.header, backdrop: cellBackdrop, source: "override", colorPath: stylePath, label: "Table cell text" });
1437
+ }
1438
+ if (Array.isArray(cell.value))
1439
+ cell.value.forEach((run, index) => {
1440
+ const colorRef = rec(run).color;
1441
+ if (typeof colorRef !== "string") return;
1442
+ const color = resolveTextColor(colorRef, design, design.colors.text);
1443
+ if (color) samples.push({ path: `${cellPath}/${index}/color`, color, sizePx: rec(run).fontSize ? Number(rec(run).fontSize) / PX_TO_PT : 15, bold: Boolean(rec(run).bold) || cell.header, backdrop: cellBackdrop, source: "override", colorPath: `${cellPath}/${index}/color`, label: "Table cell text" });
1444
+ });
1445
+ }
1446
+ continue;
1447
+ }
1448
+ const parts2 = [];
1449
+ const layoutOf = item.quoteLayout ?? item.metricLayout ?? item.timelineLayout;
1450
+ if (layoutOf)
1451
+ for (const part of layoutOf.parts) {
1452
+ if (!part.fit || part.visible === false) continue;
1453
+ const role = String(part.role ?? "");
1454
+ parts2.push({
1455
+ path: typeof part.path === "string" && part.path.startsWith("slides.") ? pointerOfDotted(part.path) : itemPath,
1456
+ sizePx: part.fit.fontSize / scale,
1457
+ color: role === "footer" ? design.colors.mutedText : role === "value" && item.metricLayout ? design.colors.primary : item.timelineLayout ? timelineTextColor(part, { background: design.colors.background, primary: design.colors.primary, text: design.colors.text, mutedText: design.colors.mutedText }) : design.colors.text,
1458
+ label: item.field === "quote" ? role === "footer" ? "Quote attribution" : "Quote" : item.field === "metric" ? role === "value" ? "Metric value" : "Metric label" : "Timeline text"
1459
+ });
1460
+ }
1461
+ else parts2.push({ path: itemPath, sizePx: baseSize, color: design.colors.text, label: heading || item.field === "text" ? label : item.field === "items" || item.field === "bullets" ? "List text" : label });
1462
+ for (const part of parts2) samples.push({ path: part.path, color: part.color, sizePx: part.sizePx, bold, backdrop, source: "default", label: part.label });
1463
+ const owners = textValues({ [item.field]: item.value }, "");
1464
+ for (const owner of owners) {
1465
+ if (typeof owner.value === "string") continue;
1466
+ const relative = splitPointer(owner.path).slice(1);
1467
+ const base = pointer2(...splitPointer(itemPath), ...relative);
1468
+ for (const run of runsOf({ ...owner, path: base })) {
1469
+ if (typeof run.style.color !== "string") continue;
1470
+ const color = resolveTextColor(run.style.color, design, design.colors.text);
1471
+ if (!color) continue;
1472
+ const size = typeof run.style.fontSize === "number" ? run.style.fontSize / PX_TO_PT : baseSize;
1473
+ samples.push({ path: `${run.path}/color`, color, sizePx: size, bold: Boolean(run.style.bold) || bold, backdrop, source: "override", colorPath: `${run.path}/color`, label: "Coloured text" });
1474
+ }
1475
+ }
1476
+ }
1477
+ for (const part of composition.furniture?.parts ?? [])
1478
+ if (part.type === "text")
1479
+ samples.push({ path: pointerOfDotted(part.sourcePath ?? part.path), color: design.colors.mutedText, sizePx: (part.fit?.fontSize ?? 13 * scale) / scale, bold: false, backdrop: slideBackdrop(context, part.box), source: "default", label: part.kind === "header" ? "Header text" : "Footer text" });
1480
+ return samples;
1481
+ }
1482
+ var contrastRule = rule(
1483
+ "text-contrast",
1484
+ "accessibility",
1485
+ "warning",
1486
+ "Text colour has too little contrast against the background it sits on.",
1487
+ "Low-contrast text is hard or impossible to read for people with low vision, colour-vision differences, glare on a projector or a poor display. WCAG sets 4.5:1 for normal text and 3:1 for large text.",
1488
+ {
1489
+ standard: "WCAG 2.2 SC 1.4.3 Contrast (Minimum), level AA",
1490
+ thresholds: ["contrastNormal", "contrastLarge"],
1491
+ cost: "composition",
1492
+ approximations: "Computed on sRGB colours with the WCAG relative-luminance formula, against the background the preview draws: a solid or theme colour, the card surface, a table cell fill, every colour a gradient takes under the text box (sampled on a 5x5 grid, angle respected), or both colours of a pattern. Anti-aliasing, text shadows and font weight are not modelled. Text colour is the preview's (the scheme's text role or dark1 on a light background and light1 on a dark one, chosen from the background luminance, where a gradient or picture background uses the scheme's default background colour), so a default can fail on a dark gradient. A link with no colour of its own is measured in the scheme's hyperlink colour."
1493
+ }
1494
+ );
1495
+ var onImageRule = rule(
1496
+ "text-on-image",
1497
+ "accessibility",
1498
+ "info",
1499
+ "Text sits on a background picture whose pixels cannot be measured.",
1500
+ "Contrast over a photograph depends on the photograph. Without a full-frame overlay that guarantees readability for every possible image, the result cannot be certified from the document.",
1501
+ {
1502
+ standard: "WCAG 2.2 SC 1.4.3 Contrast (Minimum), level AA",
1503
+ thresholds: ["contrastNormal", "contrastLarge"],
1504
+ cost: "composition",
1505
+ 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."
1506
+ }
1507
+ );
1508
+ var contrastRules = [
1509
+ {
1510
+ info: contrastRule,
1511
+ also: [onImageRule],
1512
+ run(context) {
1513
+ const { thresholds } = context;
1514
+ for (const slide of context.slides) {
1515
+ const aggregated = /* @__PURE__ */ new Map();
1516
+ const imageReported = { done: false };
1517
+ for (const sample of collectSamples(slide)) {
1518
+ const large = isLarge(sample.sizePx, sample.bold), needed = large ? thresholds.contrastLarge : thresholds.contrastNormal;
1519
+ const worst = worstContrast(sample.color, sample.backdrop);
1520
+ if (worst.ratio >= needed) continue;
1521
+ if (sample.backdrop.unknown) {
1522
+ if (!imageReported.done) {
1523
+ imageReported.done = true;
1524
+ context.report(onImageRule, {
1525
+ path: sample.path,
1526
+ slide,
1527
+ 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).`,
1528
+ 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.",
1529
+ measured: { ratio: round(worst.ratio), needed, textColor: sample.color }
1530
+ });
1531
+ }
1532
+ continue;
1533
+ }
1534
+ const key = `${sample.source === "default" ? "default" : sample.path}|${sample.color}|${worst.against}|${large}`;
1535
+ const known = aggregated.get(key);
1536
+ if (known && sample.source === "default") {
1537
+ known.extra++;
1538
+ continue;
1539
+ }
1540
+ const fixes = [];
1541
+ if (sample.colorPath) {
1542
+ const preferred = readableColor([slide.design.colors.text], sample.backdrop, needed);
1543
+ if (preferred !== void 0) {
1544
+ const value = preferred.toUpperCase() === slide.design.colors.text.toUpperCase() ? "text" : preferred;
1545
+ fixes.push({ id: "use-readable-color", title: value === "text" ? "Use the slide text colour" : `Use ${preferred}`, kind: "patch", safe: true, patch: [{ op: "replace", path: sample.colorPath, value }] });
1546
+ }
1547
+ }
1548
+ const diagnostic = context.report(contrastRule, {
1549
+ path: sample.path,
1550
+ slide,
1551
+ message: `${sample.label} colour ${sample.color} on ${worst.against} has a contrast ratio of ${worst.ratio.toFixed(2)}:1; ${needed}:1 is needed for ${large ? "large" : "normal"} text.`,
1552
+ help: sample.source === "override" ? "Pick a colour with enough contrast against the background, or remove this colour override so the theme chooses a readable text colour." : "The theme text colour is unreadable on this background. Change the slide or deck background, the colour scheme (dark1/light1), or give the text an explicit readable colour.",
1553
+ measured: { ratio: round(worst.ratio), needed, large, sizePt: round(sample.sizePx * PX_TO_PT), textColor: sample.color, backgroundColor: worst.against },
1554
+ fixes
1555
+ });
1556
+ if (sample.source === "default") aggregated.set(key, { diagnostic, extra: 0 });
1557
+ }
1558
+ for (const { diagnostic, extra } of aggregated.values())
1559
+ if (extra) {
1560
+ diagnostic.message += ` ${extra} more text element${extra === 1 ? "" : "s"} on this slide ${extra === 1 ? "has" : "have"} the same problem.`;
1561
+ if (diagnostic.measured) diagnostic.measured.alsoAffected = extra;
1562
+ }
1563
+ }
1564
+ }
1565
+ }
1566
+ ];
1567
+ var round = (value) => Math.round(value * 100) / 100;
1568
+ var altRule = rule(
1569
+ "missing-alt-text",
1570
+ "accessibility",
1571
+ "warning",
1572
+ "A picture has no alt text and is not marked decorative.",
1573
+ '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.',
1574
+ {
1575
+ standard: "WCAG 2.2 SC 1.1.1 Non-text Content, level A",
1576
+ 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."
1577
+ }
1578
+ );
1579
+ var poorAltRule = rule(
1580
+ "poor-alt-text",
1581
+ "accessibility",
1582
+ "info",
1583
+ "Alt text is a file name, a URL, a generic word or very long.",
1584
+ 'Alt text such as "image", "IMG_2041.png" or a 400-character paragraph does not do the job of describing a picture: it is read out and adds noise without information.',
1585
+ { standard: "WCAG 2.2 SC 1.1.1 Non-text Content, level A", approximations: 'Pattern checks only: file extensions and camera-style names, a bare generic word, a URL, a leading "image of", and more than 250 characters. Chart alt text (chart.alt) is checked too: a bare chart word, a URL, a leading "chart of" or more than 250 characters. It cannot tell whether a plausible sentence is accurate.' }
1586
+ );
1587
+ var GENERIC_ALT = /^(image|picture|photo|photograph|graphic|img|icon|figure|screenshot|untitled|alt|alt text|logo)$/i;
1588
+ var FILE_ALT = /(\.(png|jpe?g|gif|svg|webp|bmp|tiff?|heic|avif)$)|^(img|dsc|image|screenshot|screen shot|photo|pic)[ _-]?\d+/i;
1589
+ var GENERIC_CHART_ALT = /^((a|an|the)\s+)?([a-z-]+\s+){0,2}(chart|graph|plot|diagram|figure)$/i;
1590
+ 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";
1591
+ function altFixes(path, value) {
1592
+ 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: "" }] };
1593
+ return [{ id: "focus-alt", title: "Write alt text", kind: "focus", safe: true, focus: { path, field: "alt", value } }, decorative];
1594
+ }
1595
+ var altRules = [
1596
+ {
1597
+ info: altRule,
1598
+ run(context) {
1599
+ const seen = /* @__PURE__ */ new Set();
1600
+ context.slides.forEach((slide, index) => {
1601
+ for (const ref of assetRefs(context.document, slide.slide, slide.path, index === 0)) {
1602
+ if (seen.has(ref.path)) continue;
1603
+ seen.add(ref.path);
1604
+ let alt = altOf(ref.value, context.document.assets);
1605
+ if (ref.kind === "video" && !alt) alt = typeof rec(ref.value).title === "string" ? rec(ref.value).title : void 0;
1606
+ if (alt !== void 0 && alt.trim() !== "") continue;
1607
+ if (alt === "") continue;
1608
+ const hasText = alt !== void 0;
1609
+ const organization = ref.kind === "organization" || ref.kind === "logo" ? rec(Array.isArray(context.document.organization) ? context.document.organization[0] : context.document.organization).name : void 0;
1610
+ context.report(altRule, {
1611
+ path: ref.path,
1612
+ slide: ref.path.startsWith("/slides/") ? slide : void 0,
1613
+ message: `${kindLabel(ref.kind)} has ${hasText ? "blank" : "no"} alt text.`,
1614
+ help: `Describe what the ${ref.kind === "logo" || ref.kind === "organization" ? "logo shows" : "picture shows or why it is here"}${typeof organization === "string" ? ` (for a logo, the organization name, ${organization}, is usually right)` : ""}, or set alt to "" if it is purely decorative.`,
1615
+ fixes: altFixes(ref.path, ref.value)
1616
+ });
1617
+ }
1618
+ });
1619
+ }
1620
+ },
1621
+ {
1622
+ info: poorAltRule,
1623
+ run(context) {
1624
+ const seen = /* @__PURE__ */ new Set();
1625
+ context.slides.forEach((slide, index) => {
1626
+ for (const ref of assetRefs(context.document, slide.slide, slide.path, index === 0)) {
1627
+ if (seen.has(ref.path)) continue;
1628
+ seen.add(ref.path);
1629
+ const alt = altOf(ref.value, context.document.assets)?.trim();
1630
+ if (!alt) continue;
1631
+ const source = sourceOfAsset(ref.value);
1632
+ const problem = GENERIC_ALT.test(alt) ? "is a generic word" : FILE_ALT.test(alt) ? "looks like a file name" : /^(https?:\/\/|data:|www\.)/i.test(alt) || source && alt === source ? "is a URL" : /^(image|picture|photo|graphic) of\b/i.test(alt) ? 'starts with "image of", which a screen reader already announces' : alt.length > 250 ? `is ${alt.length} characters long; aim for a sentence or two` : void 0;
1633
+ if (!problem) continue;
1634
+ context.report(poorAltRule, {
1635
+ path: `${ref.path}${typeof ref.value === "object" ? "/alt" : ""}`,
1636
+ slide: ref.path.startsWith("/slides/") ? slide : void 0,
1637
+ message: `${kindLabel(ref.kind)} alt text ${JSON.stringify(alt.length > 60 ? `${alt.slice(0, 57)}...` : alt)} ${problem}.`,
1638
+ help: 'Write what a person who cannot see the picture needs to know, in plain words and without a leading "image of".',
1639
+ fixes: [{ id: "focus-alt", title: "Edit alt text", kind: "focus", safe: true, focus: { path: ref.path, field: "alt", value: ref.value } }]
1640
+ });
1641
+ }
1642
+ for (const payload of slidePayloads(slide.slide, slide.path)) {
1643
+ const chart = rec(payload.node.chart);
1644
+ const alt = typeof chart.alt === "string" ? chart.alt.trim() : "";
1645
+ if (!alt) continue;
1646
+ const problem = GENERIC_ALT.test(alt) || GENERIC_CHART_ALT.test(alt) ? "is a generic word, not what the data shows" : /^(https?:\/\/|data:|www\.)/i.test(alt) ? "is a URL" : /^(image|picture|photo|graphic|chart|graph) of\b/i.test(alt) ? 'starts with "chart of" or "image of", which a screen reader already announces' : alt.length > 250 ? `is ${alt.length} characters long; aim for a sentence or two` : void 0;
1647
+ if (!problem) continue;
1648
+ const path = `${payload.path}/chart/alt`;
1649
+ if (seen.has(path)) continue;
1650
+ seen.add(path);
1651
+ context.report(poorAltRule, {
1652
+ path,
1653
+ slide,
1654
+ message: `Chart alt text ${JSON.stringify(alt.length > 60 ? `${alt.slice(0, 57)}...` : alt)} ${problem}.`,
1655
+ help: "State what the chart shows: its point and the key numbers, in a sentence or two.",
1656
+ fixes: [{ id: "focus-alt", title: "Edit alt text", kind: "focus", safe: true, focus: { path: `${payload.path}/chart`, field: "alt", value: chart.alt } }]
1657
+ });
1658
+ }
1659
+ });
1660
+ }
1661
+ }
1662
+ ];
1663
+ var missingTitleRule = rule(
1664
+ "missing-slide-title",
1665
+ "accessibility",
1666
+ "warning",
1667
+ "A slide has no title.",
1668
+ "Slide titles are how people using a screen reader, an outline view or keyboard navigation find and tell slides apart; PowerPoint's own accessibility checker reports a missing title too.",
1669
+ { standard: 'WCAG 2.2 SC 2.4.2 Page Titled and SC 2.4.6 Headings and Labels, level AA (PowerPoint: "Missing slide title")', approximations: "Only the slide-level title field counts. Text that merely looks like a heading inside a block does not." }
1670
+ );
1671
+ var duplicateTitleRule = rule(
1672
+ "duplicate-slide-title",
1673
+ "accessibility",
1674
+ "info",
1675
+ "Two slides have the same title.",
1676
+ "Identical titles make slides indistinguishable in an outline or a screen reader's slide list.",
1677
+ { approximations: 'Titles are compared case-insensitively with whitespace collapsed. Slides that continue one another are not exempt; give a continuation a distinct title such as "(continued)".' }
1678
+ );
1679
+ var titleRules = [
1680
+ {
1681
+ info: missingTitleRule,
1682
+ run(context) {
1683
+ for (const slide of context.slides) {
1684
+ const title = slide.slide.title;
1685
+ if ((typeof title === "string" || Array.isArray(title)) && plainText(title).trim() !== "") continue;
1686
+ context.report(missingTitleRule, {
1687
+ path: slide.path,
1688
+ slide,
1689
+ message: `Slide ${slide.index + 1} has no title.`,
1690
+ help: "Give the slide a short, specific title. If the design has no room for a visible title, keep it short; the title also names the slide for assistive technology.",
1691
+ fixes: [{ id: "focus-title", title: "Write a title", kind: "focus", safe: true, focus: { path: `${slide.path}/title`, field: "title" } }]
1692
+ });
1693
+ }
1694
+ }
1695
+ },
1696
+ {
1697
+ info: duplicateTitleRule,
1698
+ run(context) {
1699
+ const first = /* @__PURE__ */ new Map();
1700
+ for (const slide of context.slides) {
1701
+ const title = typeof slide.slide.title === "string" || Array.isArray(slide.slide.title) ? plainText(slide.slide.title).trim().replace(/\s+/g, " ").toLowerCase() : "";
1702
+ if (!title) continue;
1703
+ const earlier = first.get(title);
1704
+ if (earlier === void 0) first.set(title, slide.index);
1705
+ else
1706
+ context.report(duplicateTitleRule, {
1707
+ path: `${slide.path}/title`,
1708
+ slide,
1709
+ message: `Slide ${slide.index + 1} has the same title as slide ${earlier + 1}: ${JSON.stringify(plainText(slide.slide.title).trim())}.`,
1710
+ help: "Make the titles distinct so each slide can be told apart in an outline or by assistive technology.",
1711
+ fixes: [{ id: "focus-title", title: "Edit the title", kind: "focus", safe: true, focus: { path: `${slide.path}/title`, field: "title" } }]
1712
+ });
1713
+ }
1714
+ }
1715
+ }
1716
+ ];
1717
+ var readingOrderRule = rule(
1718
+ "reading-order",
1719
+ "accessibility",
1720
+ "warning",
1721
+ "The order content is read differs from the order it appears on the slide.",
1722
+ "Screen readers, keyboard focus and PowerPoint's selection pane follow the composed order. When it differs from the visual order (top to bottom, then start to end of the reading direction), the slide is read out of sequence.",
1723
+ {
1724
+ standard: 'WCAG 2.2 SC 1.3.2 Meaningful Sequence, level A (PowerPoint: "Check reading order")',
1725
+ cost: "composition",
1726
+ approximations: "Compares the composed content order with a visual order recomputed from the composed boxes: items whose vertical centres fall in the same row are ordered along the reading direction (a right-to-left deck is checked by rows only), rows from top to bottom. Headings are expected first. Free-form overlap is not analysed."
1727
+ }
1728
+ );
1729
+ var readingOrderRules = [
1730
+ {
1731
+ info: readingOrderRule,
1732
+ run(context) {
1733
+ for (const slide of context.slides) {
1734
+ const composition = slide.composition;
1735
+ if (!composition) continue;
1736
+ const body = composition.items.filter((item) => !["title", "subtitle", "tag"].includes(item.field));
1737
+ const title = composition.items.find((item) => item.field === "title");
1738
+ if (title && body.some((item) => item.box.y + item.box.height <= title.box.y + 1)) {
1739
+ const above = body.find((item) => item.box.y + item.box.height <= title.box.y + 1);
1740
+ context.report(readingOrderRule, {
1741
+ path: pointerOfDotted(above.path),
1742
+ slide,
1743
+ message: `Content ${JSON.stringify(shortField(above.path))} is drawn above the title but is read after it.`,
1744
+ help: "Move the title above the content or change the layout so the title is the first thing on the slide."
1745
+ });
1746
+ }
1747
+ if (body.length < 2) continue;
1748
+ const visual = visualOrder(body, slide.rtl);
1749
+ const composed = body.map((item) => item.path);
1750
+ const index = composed.findIndex((path, i) => path !== visual[i]);
1751
+ if (index < 0) continue;
1752
+ const name = (path) => shortField(path);
1753
+ context.report(readingOrderRule, {
1754
+ path: pointerOfDotted(composed[index]),
1755
+ slide,
1756
+ message: `Reading order differs from visual order: ${JSON.stringify(name(composed[index]))} is read at ${ordinal(index)} but appears at ${ordinal(visual.indexOf(composed[index]))}. Composed order: ${composed.map(name).join(", ")}; visual order: ${visual.map(name).join(", ")}.`,
1757
+ help: "Reorder the blocks (or rename promoted regions) so the composed order follows the visual order, or use blocks with an explicit composition instead of region keys.",
1758
+ measured: { firstMismatch: index, composed: composed.length }
1759
+ });
1760
+ }
1761
+ }
1762
+ }
1763
+ ];
1764
+ function visualOrder(items, rtl) {
1765
+ if (rtl) return readingRows(items).flatMap((row) => row.map((item) => item.path));
1766
+ return visualReadingOrder(items).map((item) => item.path);
1767
+ }
1768
+ var shortField = (path) => path.replace(/^slides\.\d+\./, "");
1769
+ var ordinal = (index) => `position ${index + 1}`;
1770
+ var linkRule = rule(
1771
+ "link-text",
1772
+ "accessibility",
1773
+ "warning",
1774
+ "Link text does not say where the link goes.",
1775
+ `People who scan links out of context (a screen reader's links list, a link tab order) hear only the text. "click here", "read more" or a long raw URL tells them nothing about the destination.`,
1776
+ { standard: 'WCAG 2.2 SC 2.4.4 Link Purpose (In Context), level A; PowerPoint: "Hyperlink text is not meaningful"', approximations: "Matches a short list of generic phrases in English (after lower-casing and removing punctuation), blank link text, and raw URLs longer than 40 characters. Adjacent runs sharing one link are read as one link." }
1777
+ );
1778
+ var GENERIC_LINKS = /* @__PURE__ */ new Set(["click here", "click", "here", "link", "this link", "this", "read more", "more", "learn more", "see more", "more info", "more information", "details", "download", "url", "website", "web site", "page", "this page", "visit", "go"]);
1779
+ var linkRules = [
1780
+ {
1781
+ info: linkRule,
1782
+ run(context) {
1783
+ for (const slide of context.slides)
1784
+ for (const tv of slide.texts) {
1785
+ if (typeof tv.value === "string") continue;
1786
+ const runs = runsOf(tv);
1787
+ for (let i = 0; i < runs.length; i++) {
1788
+ const run = runs[i];
1789
+ const link = run.style.link;
1790
+ if (typeof link !== "string") continue;
1791
+ let text = run.text;
1792
+ let last = i;
1793
+ while (runs[last + 1] && runs[last + 1].style.link === link) text += runs[++last].text;
1794
+ const normalized = text.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, "").replace(/\s+/g, " ").trim();
1795
+ const problem = !text.trim() ? "is blank" : GENERIC_LINKS.has(normalized) ? "is generic" : /^(https?:\/\/|www\.)\S{30,}$/i.test(text.trim()) ? "is a long raw URL" : void 0;
1796
+ i = last;
1797
+ if (!problem) continue;
1798
+ context.report(linkRule, {
1799
+ path: run.path,
1800
+ slide,
1801
+ message: `Link text ${JSON.stringify(text.trim().slice(0, 50))} ${problem}; the destination is ${link.length > 60 ? `${link.slice(0, 57)}...` : link}.`,
1802
+ help: 'Use text that names the destination or action, for example "Q3 financial results (PDF)", instead of "click here".',
1803
+ fixes: [{ id: "focus-link", title: "Edit the text", kind: "focus", safe: true, focus: { path: run.valuePath, field: "link", value: link } }]
1804
+ });
1805
+ }
1806
+ }
1807
+ }
1808
+ }
1809
+ ];
1810
+ var chartColorRule = rule(
1811
+ "chart-color-only",
1812
+ "accessibility",
1813
+ "info",
1814
+ "Chart series may be indistinguishable without colour vision.",
1815
+ "OPF charts have no data labels or patterns, so series are told apart by colour alone (and legend order). Colours that look alike to someone with colour-vision deficiency, or when printed in greyscale, make series impossible to tell apart.",
1816
+ {
1817
+ standard: "WCAG 2.2 SC 1.4.1 Use of Color, level A",
1818
+ thresholds: ["minSeriesColorDifference"],
1819
+ approximations: "Uses the engine's series palette (ValidateOptions.chartPalette; default the opf-render/opf-pptx palette, adjusted for the card surface like the preview does) in series order: series i takes colour i, pie/doughnut/treemap/funnel slices take colours per category. Pairs are compared by CIE76 distance after simulating protanopia, deuteranopia, tritanopia (Machado 2009, severity 1) and greyscale. Single-series charts and chart types without a series legend are skipped."
1820
+ }
1821
+ );
1822
+ var chartAltRule = rule(
1823
+ "chart-text-alternative",
1824
+ "accessibility",
1825
+ "info",
1826
+ "A chart has no text alternative, or is marked decorative.",
1827
+ "A chart conveys a message; people who cannot see it need the message and ideally the numbers in text. The chart's alt field is that text alternative (the preview exposes it as the chart's accessible name and the PowerPoint export writes it as the frame's alternative text); a sentence or table beside the chart also serves. An empty alt marks a chart decorative, which is reported as info so the choice is reviewed: a chart rarely carries no message.",
1828
+ { standard: "WCAG 2.2 SC 1.1.1 Non-text Content, level A", approximations: `A chart passes when chart.alt has text. Without alt, it passes when the slide has any other text, list, table, quote or metric content besides title and tag, or a subtitle. It does not judge whether alt or that text states the chart's point (see opf/poor-alt-text for generic alt text). alt: "" is reported as a decorative chart, whatever else is on the slide.` }
1829
+ );
1830
+ var chartSkip = /(histogram|box|pareto|waterfall|world|map)/;
1831
+ var perCategory = /(pie|doughnut|treemap|funnel)/;
1832
+ var chartRules = [
1833
+ {
1834
+ info: chartColorRule,
1835
+ run(context) {
1836
+ for (const slide of context.slides)
1837
+ for (const payload of slidePayloads(slide.slide, slide.path)) {
1838
+ const chart = rec(payload.node.chart);
1839
+ const resolved = resolveChartData(chart, context.document);
1840
+ if (!resolved.ok) continue;
1841
+ const data = { columns: resolved.columns.filter((_, index) => !(resolved.hasX && index === 1)), rows: resolved.rows };
1842
+ const type = String(chart.type ?? "").toLowerCase();
1843
+ if (chartSkip.test(type)) continue;
1844
+ const slices = perCategory.test(type);
1845
+ const count = slices ? data.rows.length : data.columns.length - 1;
1846
+ if (count < 2) continue;
1847
+ const surface = slide.design.colors.surface;
1848
+ const palette = chartPaletteForFill(surface, context.chartPalette);
1849
+ if (!palette.length) continue;
1850
+ const used = Array.from({ length: Math.min(count, 24) }, (_, i) => palette[i % palette.length]);
1851
+ const pairs = [];
1852
+ for (let a = 0; a < used.length; a++)
1853
+ for (let b = a + 1; b < used.length; b++) {
1854
+ let worst = { model: "normal vision", difference: colorDifference(used[a], used[b]) };
1855
+ for (const model of visionModels) {
1856
+ const difference = colorDifference(simulateVision(used[a], model), simulateVision(used[b], model));
1857
+ if (difference < worst.difference) worst = { model, difference };
1858
+ }
1859
+ if (worst.difference < context.thresholds.minSeriesColorDifference) pairs.push({ a, b, ...worst });
1860
+ }
1861
+ const cycles = count > palette.length;
1862
+ if (!pairs.length && !cycles) continue;
1863
+ pairs.sort((x, y) => x.difference - y.difference);
1864
+ const names2 = slices ? data.rows.map((row) => String(row?.[0] ?? "")) : data.columns.slice(1).map(String);
1865
+ const describe = (p) => `${JSON.stringify(names2[p.a] ?? `#${p.a + 1}`)} and ${JSON.stringify(names2[p.b] ?? `#${p.b + 1}`)} (${p.model}, difference ${p.difference.toFixed(1)})`;
1866
+ context.report(chartColorRule, {
1867
+ path: `${payload.path === slide.path ? slide.path : payload.path}/chart`,
1868
+ slide,
1869
+ message: `${slices ? "Slices" : "Series"} may be hard to tell apart without colour: ${pairs.slice(0, 3).map(describe).join("; ")}${cycles ? `${pairs.length ? "; " : ""}${count} ${slices ? "slices" : "series"} exceed the ${palette.length}-colour palette, so colours repeat` : ""}.`,
1870
+ help: "Show fewer series per chart (split into two charts), state the key values in the slide text or a table next to the chart, or choose a chart type whose categories sit on an axis instead of in a legend.",
1871
+ measured: { series: count, confusablePairs: pairs.length, minDifference: pairs[0] ? round(pairs[0].difference) : null }
1872
+ });
1873
+ }
1874
+ }
1875
+ },
1876
+ {
1877
+ info: chartAltRule,
1878
+ run(context) {
1879
+ for (const slide of context.slides) {
1880
+ const payloads = slidePayloads(slide.slide, slide.path).filter((p) => !p.node.blocks && !p.node.composition || p.root);
1881
+ const charts = payloads.filter((p) => p.node.chart !== void 0);
1882
+ if (!charts.length) continue;
1883
+ const textual = (p) => ["text", "items", "bullets", "table", "quote", "metric", "timeline"].some((field) => p.node[field] !== void 0 && p.node[field] !== "" && !(Array.isArray(p.node[field]) && p.node[field].length === 0));
1884
+ const textBeside = payloads.some(textual) || (typeof slide.slide.subtitle === "string" || Array.isArray(slide.slide.subtitle)) && plainText(slide.slide.subtitle).trim() !== "";
1885
+ const where = (chart) => charts.length > 1 ? `chart ${charts.indexOf(chart) + 1} on slide ${slide.index + 1}` : `chart on slide ${slide.index + 1}`;
1886
+ const focusAlt = (chart) => ({ id: "focus-alt", title: "Write alt text", kind: "focus", safe: true, focus: { path: `${chart.path}/chart`, field: "alt", value: rec(chart.node.chart).alt } });
1887
+ let reported = false;
1888
+ for (const chart of charts) {
1889
+ const alt = rec(chart.node.chart).alt;
1890
+ if (typeof alt === "string" && alt.trim() !== "") continue;
1891
+ if (alt === "") {
1892
+ context.report(chartAltRule, {
1893
+ path: `${chart.path}/chart/alt`,
1894
+ slide,
1895
+ message: `The ${where(chart)} is marked decorative (empty alt), so assistive technology skips it.`,
1896
+ help: "Keep the empty alt only if the chart adds nothing the slide text does not already say. Otherwise describe what it shows: its point and the key numbers.",
1897
+ fixes: [focusAlt(chart)]
1898
+ });
1899
+ continue;
1900
+ }
1901
+ if (textBeside || reported) continue;
1902
+ reported = true;
1903
+ context.report(chartAltRule, {
1904
+ path: `${chart.path}/chart`,
1905
+ slide,
1906
+ message: `The ${where(chart)} has no alt text and no text beside it that states what it shows.`,
1907
+ help: "Set chart.alt to a sentence with the chart's point and key numbers, or add a subtitle, text block or table with them, so the message does not depend on seeing the chart.",
1908
+ fixes: [focusAlt(chart), { id: "focus-subtitle", title: "Write a subtitle", kind: "focus", safe: true, focus: { path: `${slide.path}/subtitle`, field: "text" } }]
1909
+ });
1910
+ }
1911
+ }
1912
+ }
1913
+ }
1914
+ ];
1915
+ var languageRule = rule(
1916
+ "missing-language",
1917
+ "accessibility",
1918
+ "info",
1919
+ "The presentation does not declare its language.",
1920
+ "Screen readers and text-to-speech choose pronunciation and hyphenation from the declared language; spell checkers and translation tools use it too.",
1921
+ { 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)." }
1922
+ );
1923
+ var languageRules = [
1924
+ {
1925
+ info: languageRule,
1926
+ run(context) {
1927
+ if (context.document.language !== void 0) return;
1928
+ context.report(languageRule, {
1929
+ path: "",
1930
+ message: "The presentation has no language set.",
1931
+ help: 'Set `language` to a BCP 47 tag, for example "en-US".',
1932
+ fixes: [{ id: "focus-language", title: "Set the language", kind: "focus", safe: true, focus: { path: "/language", field: "language" } }]
1933
+ });
1934
+ }
1935
+ }
1936
+ ];
1937
+ var accessibilityRules = [
1938
+ ...contrastRules,
1939
+ ...altRules,
1940
+ ...titleRules,
1941
+ ...readingOrderRules,
1942
+ ...linkRules,
1943
+ ...chartRules,
1944
+ ...languageRules
1945
+ ];
1946
+
1947
+ // src/rules-layout-content.ts
1948
+ var PX_TO_PT2 = 0.75;
1949
+ var round2 = (value, places = 1) => Math.round(value * 10 ** places) / 10 ** places;
1950
+ var overflowRule = rule(
1951
+ "text-overflow",
1952
+ "layout",
1953
+ "warning",
1954
+ "Text or a table does not fit its space at the smallest allowed size.",
1955
+ "Core composition shrinks text to the readable minimum and then reports what still does not fit. Overflowing text is clipped or runs over other content in the preview and in PowerPoint.",
1956
+ {
1957
+ cost: "composition",
1958
+ approximations: "Uses composeSlide at the deck's slide size with the fonts of the resolved font scheme. Without a host-supplied text measurement (ValidateOptions.fonts.textMeasurement) widths are core's portable estimate, which can differ from the real font by a few percent; pass the renderer's measurement for font-exact results. A composition that sets overflow: \"error\" is reported as a warning here, so that `valid` never depends on font metrics; rendering and export still refuse such a slide when it does not fit."
1959
+ }
1960
+ );
1961
+ var unresolvedRule = rule(
1962
+ "unresolved-content",
1963
+ "layout",
1964
+ "warning",
1965
+ "Content cannot be drawn as authored.",
1966
+ "composeSlide reports content it cannot place or an effect it does not support (for example a picture bullet without a logo, a date field without a date, or an unsupported image treatment). The preview and the export fall back.",
1967
+ { cost: "composition" }
1968
+ );
1969
+ var layoutFailedRule = rule(
1970
+ "layout-failed",
1971
+ "layout",
1972
+ "warning",
1973
+ "The slide layout could not be computed.",
1974
+ "Composition threw for a slide that passed schema validation, so geometry-based rules (contrast, overflow, reading order, resolution) were skipped for it.",
1975
+ { cost: "composition" }
1976
+ );
1977
+ var minFontRule = rule(
1978
+ "min-font-size",
1979
+ "layout",
1980
+ "warning",
1981
+ "Text is drawn smaller than the readable minimum.",
1982
+ "Small type is unreadable from the back of a room and on a phone. The engine's own default floor is 12 pt (16 px); anything much below that was lowered on purpose or by a composition that could not fit the text.",
1983
+ {
1984
+ cost: "composition",
1985
+ standard: "Common presentation guidance (12 pt minimum for body text; 18 pt or more is easier to read from a distance).",
1986
+ thresholds: ["minFontSizePt"],
1987
+ approximations: "Sizes are those composeSlide fitted, expressed on the 13.33 x 7.5 in reference slide (96 px per inch, so 1 px is 0.75 pt; a smaller canvas scales type down with it) and explicit run fontSize values in points. Per payload, the smallest part (a metric label or a quote attribution) is reported. Table cell text (default 11.25 pt), code and header/footer furniture are not checked."
1988
+ }
1989
+ );
1990
+ var layoutRules = [
1991
+ {
1992
+ info: overflowRule,
1993
+ also: [unresolvedRule, layoutFailedRule, minFontRule],
1994
+ run(context) {
1995
+ const deckLevel = /* @__PURE__ */ new Set();
1996
+ for (const slide of context.slides) {
1997
+ if (slide.layoutError)
1998
+ context.report(layoutFailedRule, { path: slide.path, slide, message: `Layout failed: ${slide.layoutError}`, help: "Fix the composition settings or content structure named in the message; geometry-based rules were skipped for this slide." });
1999
+ for (const { diagnostic } of slide.layoutDiagnostics) {
2000
+ const path = pointerOfDotted(diagnostic.path);
2001
+ if (!path.startsWith("/slides/")) {
2002
+ const key = `${diagnostic.code}|${path}|${diagnostic.message}`;
2003
+ if (deckLevel.has(key)) continue;
2004
+ deckLevel.add(key);
2005
+ }
2006
+ if (diagnostic.code === "text-overflow")
2007
+ context.report(overflowRule, { path, slide, message: diagnostic.message, help: "Shorten the text, give it more space (fewer blocks, a different composition) or split the slide (paginate); raising the readable minimum makes it worse." });
2008
+ else if (diagnostic.code === "small-cell") continue;
2009
+ else context.report(unresolvedRule, { path, slide: path.startsWith("/slides/") ? slide : void 0, message: diagnostic.message, help: "Supply what the message asks for, or remove the setting that cannot be honoured." });
2010
+ }
2011
+ for (const tv of slide.texts)
2012
+ for (const run of runsOf(tv))
2013
+ if (typeof run.style.fontSize === "number" && run.style.fontSize < context.thresholds.minFontSizePt)
2014
+ context.report(minFontRule, {
2015
+ path: `${run.path}/fontSize`,
2016
+ slide,
2017
+ message: `Text run is set to ${run.style.fontSize} pt, below the ${context.thresholds.minFontSizePt} pt minimum.`,
2018
+ help: "Remove the size override so the text uses the fitted size, or raise it.",
2019
+ measured: { sizePt: run.style.fontSize, minimumPt: context.thresholds.minFontSizePt },
2020
+ fixes: [{ id: "focus-font-size", title: "Edit the size", kind: "focus", safe: true, focus: { path: `${run.path}/fontSize`, field: "fontSize" } }]
2021
+ });
2022
+ const composition = slide.composition;
2023
+ if (!composition) continue;
2024
+ const scale = Math.min(composition.width, composition.height) / 720;
2025
+ const limitPx = context.thresholds.minFontSizePt / PX_TO_PT2;
2026
+ const small = (path, sizes, label) => {
2027
+ const known = sizes.filter((size) => size !== void 0);
2028
+ const sizePx = known.length ? Math.min(...known) / scale : void 0;
2029
+ if (sizePx === void 0 || sizePx >= limitPx - 1e-6) return;
2030
+ context.report(minFontRule, {
2031
+ path,
2032
+ slide,
2033
+ message: `${label} is drawn at ${round2(sizePx * PX_TO_PT2)} pt${scale === 1 ? "" : " (reference slide size)"}, below the ${context.thresholds.minFontSizePt} pt minimum.`,
2034
+ help: "Shorten the text or give it more room so it can be drawn larger; check composition.minFontSize and run fontSize settings that force small type.",
2035
+ measured: { sizePt: round2(sizePx * PX_TO_PT2, 2), minimumPt: context.thresholds.minFontSizePt }
2036
+ });
2037
+ };
2038
+ for (const item of composition.items) {
2039
+ if (item.field === "code" || item.field === "table" || item.type === "code" || item.type === "table") continue;
2040
+ const layout = item.quoteLayout ?? item.metricLayout ?? item.timelineLayout;
2041
+ if (layout) small(pointerOfDotted(item.path), layout.parts.map((part) => part.visible === false ? void 0 : part.fit?.fontSize), "The smallest text");
2042
+ else small(pointerOfDotted(item.path), [item.text?.fontSize], item.field === "title" ? "Title" : "Text");
2043
+ }
2044
+ }
2045
+ }
2046
+ }
2047
+ ];
2048
+ var outsideRule = rule(
2049
+ "font-outside-scheme",
2050
+ "layout",
2051
+ "warning",
2052
+ "Text uses a font family that is not in the deck's font scheme.",
2053
+ "The font scheme is the deck's font choice. A run that names another family will not follow a font-scheme change, may be missing on the viewer's machine, and breaks the deck's typographic consistency.",
2054
+ { approximations: "Compares the run's fontFamily (case-insensitively) with the heading, body, code and accent families of the slide's resolved font scheme and the scheme's major/minor fonts. Families that the host substitutes are still different names here." }
2055
+ );
2056
+ var fontRules = [
2057
+ {
2058
+ info: outsideRule,
2059
+ run(context) {
2060
+ for (const slide of context.slides) {
2061
+ const allowed = new Set(
2062
+ [slide.design.fonts.heading, slide.design.fonts.body, slide.design.fonts.code, slide.design.fonts.accent, slide.design.fontScheme.major, slide.design.fontScheme.minor].filter((family) => typeof family === "string").map((family) => family.toLowerCase())
2063
+ );
2064
+ const reported = /* @__PURE__ */ new Set();
2065
+ for (const tv of slide.texts)
2066
+ for (const run of runsOf(tv)) {
2067
+ const family = run.style.fontFamily;
2068
+ if (typeof family !== "string" || !family.trim() || allowed.has(family.trim().toLowerCase()) || reported.has(family.toLowerCase())) continue;
2069
+ reported.add(family.toLowerCase());
2070
+ context.report(outsideRule, {
2071
+ path: `${run.path}/fontFamily`,
2072
+ slide,
2073
+ message: `Text uses ${JSON.stringify(family)}, which is not in the font scheme (${[.../* @__PURE__ */ new Set([slide.design.fonts.heading, slide.design.fonts.body])].join(" / ")}).`,
2074
+ help: "Remove the fontFamily override to use the scheme font, or change the deck's font scheme so the family is part of it.",
2075
+ fixes: [{ id: "remove-font-family", title: "Use the scheme font", kind: "patch", safe: false, patch: [{ op: "remove", path: `${run.path}/fontFamily` }] }]
2076
+ });
2077
+ }
2078
+ }
2079
+ }
2080
+ }
2081
+ ];
2082
+ var resolutionRule = rule(
2083
+ "image-resolution",
2084
+ "layout",
2085
+ "warning",
2086
+ "An image has too few pixels for the size it is shown at.",
2087
+ "An image stretched beyond its pixel size looks blurry or blocky on a projector or a high-density screen.",
2088
+ {
2089
+ cost: "composition",
2090
+ thresholds: ["minImagePpi"],
2091
+ 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."
2092
+ }
2093
+ );
2094
+ var resolutionRules = [
2095
+ {
2096
+ info: resolutionRule,
2097
+ run(context) {
2098
+ const assets = context.document.assets;
2099
+ const seen = /* @__PURE__ */ new Set();
2100
+ const check = (path, value, box, cover, slide) => {
2101
+ if (seen.has(path)) return;
2102
+ seen.add(path);
2103
+ const size = intrinsicImageSize(value, assets);
2104
+ if (!size || size.svg || box.width <= 0 || box.height <= 0) return;
2105
+ const scale = cover ? Math.max(box.width / size.width, box.height / size.height) : Math.min(box.width / size.width, box.height / size.height);
2106
+ const ppi = 96 / scale;
2107
+ if (ppi >= context.thresholds.minImagePpi) return;
2108
+ context.report(resolutionRule, {
2109
+ path,
2110
+ slide,
2111
+ message: `The ${size.width}x${size.height} px image is shown ${round2(box.width / 96)} x ${round2(box.height / 96)} in, about ${round2(ppi, 0)} pixels per inch; ${context.thresholds.minImagePpi} are needed to look sharp.`,
2112
+ help: "Use a larger source image, or show it smaller. Re-exporting a small image at a higher size does not add detail.",
2113
+ measured: { widthPx: size.width, heightPx: size.height, ppi: round2(ppi, 0), minimumPpi: context.thresholds.minImagePpi }
2114
+ });
2115
+ };
2116
+ for (const slide of context.slides) {
2117
+ const composition = slide.composition;
2118
+ if (composition) {
2119
+ 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);
2120
+ }
2121
+ const background = slide.design.backgroundImage;
2122
+ 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);
2123
+ }
2124
+ }
2125
+ }
2126
+ ];
2127
+ var placeholderRule = rule(
2128
+ "placeholder-text",
2129
+ "content",
2130
+ "warning",
2131
+ "Placeholder text was left in the deck.",
2132
+ '"Lorem ipsum", "Click to add title", "TBD" and bracketed prompts are scaffolding. Shipping them reads as unfinished work.',
2133
+ { approximations: 'Case-insensitive patterns for lorem ipsum, template prompts ("Click to add...", "Your title here", "[Insert ...]", a bare "Title" or "Text"), and the markers TBD, TBC, TODO, FIXME and XXX in capitals. A deliberate use of the words is flagged too; disable the rule for that slide with ignorePaths.' }
2134
+ );
2135
+ var emptyTextRule = rule(
2136
+ "empty-text",
2137
+ "content",
2138
+ "info",
2139
+ "A text field is present but empty.",
2140
+ "An empty title, text block or list item draws nothing, and leaves a hole in the outline and for assistive technology."
2141
+ );
2142
+ var emptySlideRule = rule(
2143
+ "empty-slide",
2144
+ "content",
2145
+ "warning",
2146
+ "A slide has no content at all.",
2147
+ "A slide with no title, no content and no picture is almost always an accident of editing. (A deliberately blank slide can use the blank layout.)"
2148
+ );
2149
+ var unusedBeatRule = rule(
2150
+ "unused-beat",
2151
+ "content",
2152
+ "info",
2153
+ "A beat of the narrative has no slide that references it.",
2154
+ "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.",
2155
+ {
2156
+ 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."
2157
+ }
2158
+ );
2159
+ var PLACEHOLDERS = [
2160
+ { pattern: /lorem ipsum|dolor sit amet|consectetur adipiscing/i, label: "lorem ipsum" },
2161
+ { pattern: /\b(click|tap) to (add|edit|enter)\b|\b(add|enter|insert|type) (a |your |the )?(title|subtitle|text|heading|caption|content|name|description)( here)?\s*$/i, label: "a template prompt" },
2162
+ { pattern: /\b(your|the) (title|subtitle|name|company|text|logo|content) (goes )?here\b|\b(title|subtitle|text|heading|content|description) (goes )?here\b/i, label: "a template prompt" },
2163
+ { pattern: /\[(insert|add|your|enter|name|title|date|company|tbd|tbc|todo|placeholder)[^\]]{0,40}\]/i, label: "a bracketed prompt" },
2164
+ { pattern: /\b(TBD|TBC|TODO|FIXME)\b|\bXXX+\b|\?\?\?/, label: "a TBD/TODO marker" },
2165
+ { pattern: /^\s*(title|subtitle|heading|text|slide title|new slide|untitled( slide| presentation)?|presentation title|sample text|placeholder)\s*$/i, label: "a template default" }
2166
+ ];
2167
+ var contentRules = [
2168
+ {
2169
+ info: placeholderRule,
2170
+ run(context) {
2171
+ for (const slide of context.slides)
2172
+ for (const tv of slide.texts) {
2173
+ const text = plainText(tv.value);
2174
+ const hit = PLACEHOLDERS.find(({ pattern }) => pattern.test(text));
2175
+ if (!hit) continue;
2176
+ context.report(placeholderRule, {
2177
+ path: tv.path,
2178
+ slide,
2179
+ message: `Text looks like ${hit.label}: ${JSON.stringify(text.length > 60 ? `${text.slice(0, 57)}...` : text)}.`,
2180
+ help: "Replace it with the real content, or delete the element.",
2181
+ fixes: [{ id: "focus-text", title: "Edit the text", kind: "focus", safe: true, focus: { path: tv.path, field: "text" } }]
2182
+ });
2183
+ }
2184
+ }
2185
+ },
2186
+ {
2187
+ info: emptyTextRule,
2188
+ run(context) {
2189
+ for (const slide of context.slides)
2190
+ for (const tv of slide.texts) {
2191
+ if (["table", "table-header", "list-description"].includes(tv.role)) continue;
2192
+ if (plainText(tv.value).trim() !== "") continue;
2193
+ context.report(emptyTextRule, {
2194
+ path: tv.path,
2195
+ slide,
2196
+ message: `The ${tv.role === "list" ? "list item" : tv.role === "body" ? "text" : tv.role} is empty.`,
2197
+ help: "Write the text, or remove the field so the slide does not carry an empty element.",
2198
+ fixes: [{ id: "focus-text", title: "Edit the text", kind: "focus", safe: true, focus: { path: tv.path, field: "text" } }]
2199
+ });
2200
+ }
2201
+ }
2202
+ },
2203
+ {
2204
+ info: unusedBeatRule,
2205
+ run(context) {
2206
+ const narrative = resolveNarrative(context.document, context.options);
2207
+ if (!narrative) return;
2208
+ const id = String(context.document.narrative);
2209
+ for (const { beat, index } of unreferencedBeats(context.document, narrative))
2210
+ context.report(unusedBeatRule, {
2211
+ path: "/narrative",
2212
+ message: `Beat ${JSON.stringify(beat)} (${index + 1} of ${narrative.beats.length}) of narrative ${JSON.stringify(id)} has no slide that references it.`,
2213
+ help: `Add a slide with "beat": ${JSON.stringify(beat)}, or list the beat on the slide that covers it. If the deck deliberately skips the beat, ignore this finding.`,
2214
+ measured: { beat, position: index + 1, beats: narrative.beats.length }
2215
+ });
2216
+ }
2217
+ },
2218
+ {
2219
+ info: emptySlideRule,
2220
+ run(context) {
2221
+ for (const slide of context.slides) {
2222
+ if (slide.slide.layout === "blank") continue;
2223
+ const has = (value) => value !== void 0 && value !== null && !(typeof value === "string" && value.trim() === "") && !(Array.isArray(value) && value.length === 0);
2224
+ const hasText = slide.texts.some((tv) => plainText(tv.value).trim() !== "");
2225
+ const hasObject = slidePayloads(slide.slide, slide.path).some((p) => ["image", "video", "chart", "table", "code", "metric", "quote", "timeline"].some((field) => has(p.node[field])));
2226
+ const hasBackgroundImage = imageBackground(rec(slide.slide.design).background) !== void 0;
2227
+ if (hasText || hasObject || hasBackgroundImage) continue;
2228
+ 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." });
2229
+ }
2230
+ }
2231
+ }
2232
+ ];
2233
+ var layoutContentRules = [
2234
+ ...layoutRules,
2235
+ ...fontRules,
2236
+ ...resolutionRules,
2237
+ ...contentRules
2238
+ ];
2239
+
2240
+ // src/validation-types.ts
2241
+ var validationCategories = Object.freeze([
2242
+ "format",
2243
+ "references",
2244
+ "policy",
2245
+ "accessibility",
2246
+ "layout",
2247
+ "content"
2248
+ ]);
2249
+
2250
+ // src/validation-rules.ts
2251
+ var DEFAULT_VALIDATION_THRESHOLDS = Object.freeze({
2252
+ contrastNormal: 4.5,
2253
+ contrastLarge: 3,
2254
+ minFontSizePt: 11,
2255
+ minImagePpi: 96,
2256
+ minSeriesColorDifference: 10
2257
+ });
2258
+ var SCHEMA = "spec/schemas/opf.schema.json (JSON Schema 2020-12) and the semantic rules of OPF";
2259
+ var formatRules = [
2260
+ rule(
2261
+ "json-syntax",
2262
+ "format",
2263
+ "error",
2264
+ "The text is not strict JSON.",
2265
+ "Every OPF tool reads JSON. A misplaced comma, quote, comment or trailing comma makes the file unreadable, and the range says where.",
2266
+ { cost: "syntax", standard: "RFC 8259 (strict JSON: no comments, no trailing commas)", approximations: "Only text input is checked. A string is always read as JSON; a text that looks like YAML or Markdown gets a hint to use fromYaml or fromMarkdown first." }
2267
+ ),
2268
+ rule(
2269
+ "duplicate-key",
2270
+ "format",
2271
+ "error",
2272
+ "A JSON object repeats a key.",
2273
+ "JSON.parse keeps only the last value of a repeated key, so the earlier one disappears without a trace. The check compares escaped and literal spellings of the same key.",
2274
+ { cost: "syntax", standard: "RFC 8259 section 4 (names within an object should be unique)" }
2275
+ ),
2276
+ rule(
2277
+ "schema",
2278
+ "format",
2279
+ "error",
2280
+ "The document breaks the OPF schema or a structural rule the schema cannot express.",
2281
+ "The schema is the contract every engine, exporter and editor relies on. A field of the wrong type, a missing required field, an unknown property or content fields that cannot be mixed make the document ambiguous or undrawable.",
2282
+ { standard: SCHEMA, approximations: "All alternatives of a union are reported, with the failing schema location in `validation`. A required variable with no value is reported by `opf/variable-unfilled`, in the same category." }
2283
+ ),
2284
+ rule("reference-id-duplicate", "format", "error", "Two references share an id.", "A citation names a reference by id; a repeated id makes it ambiguous which entry is cited.", { standard: SCHEMA }),
2285
+ 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 }),
2286
+ 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 }),
2287
+ 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 }),
2288
+ 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 }),
2289
+ 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 }),
2290
+ 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 }),
2291
+ 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 }),
2292
+ 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 }),
2293
+ rule("chart-mapping-unknown-column", "format", "error", "A chart mapping names a column the data does not have.", "The series cannot be built from a column that is not there.", { standard: SCHEMA }),
2294
+ rule("number-format-invalid", "format", "error", "A number format cannot be read.", "A format code that no engine can parse is drawn as the plain number or fails to export.", { standard: "Excel number format codes, as far as OPF supports them" }),
2295
+ rule("variable-invalid-value", "format", "error", "A variable value does not match its declared type.", "A number variable given text, or a date given a non-date, cannot be filled into the deck.", { standard: SCHEMA }),
2296
+ rule("variable-unknown-builtin", "format", "error", "A token names a built-in variable that does not exist.", "The `deck.`, `speaker.` and `organization.` names (and `speakers`) are reserved for built-in variables; a name under them that is not a built-in can never be filled, so the token would show literally.", { standard: SCHEMA }),
2297
+ rule("chart-highlight-unknown-name", "format", "error", "A chart highlight names a series or category the chart does not have.", "`chart.highlight` emphasises marks by name; a name that matches no series or category in the chart data highlights nothing, so the chart is not drawn as authored.", { standard: SCHEMA }),
2298
+ rule("variable-format", "format", "error", "A variable cannot be formatted.", "A number or date format that does not apply to the value leaves the token unreadable.", { standard: SCHEMA }),
2299
+ rule(
2300
+ "variable-unfilled",
2301
+ "format",
2302
+ "error",
2303
+ "A required template variable has no value.",
2304
+ "A variable a deck declares as required, with no value, is template scaffolding: `{{name}}` tokens show up literally and colour variables fall back to a default. A normal deck must be filled, so this is an error and the deck is not valid; a template (`template: true`) is incomplete on purpose, so it is a warning.",
2305
+ {
2306
+ approximations: "Required variables are those the variable machinery reports as unfilled for the document (declared, no value, not `required: false`, not given by the `values` option). For a document that declares no content variables, `{{id}}` / `{{id|format}}` tokens (`{{` escapes) left in any string outside `variables`, `extensions`, `catalogs` and `assets` are reported as warnings. `var:` colour references with no declaration are reported as `opf/variable-reference-unknown`. Fill a template with `opf fill` or `resolveVariables` to clear the findings."
2307
+ }
2308
+ ),
2309
+ rule("run-color-unrecognized", "format", "warning", "A text run colour is none of the documented forms.", "Run colours are open strings so imported decks stay valid, but a value that is not a hex colour, a colour-scheme name or a `var:` reference falls back to the theme colour.", { standard: SCHEMA }),
2310
+ rule("numbering-start-ignored", "format", "warning", "A list entry sets `start` where nothing is numbered.", "A start value only restarts an auto-number; without `numbering` on the payload it has no effect.", { standard: SCHEMA })
2311
+ ];
2312
+ var referenceRules = [
2313
+ rule(
2314
+ "asset-reference",
2315
+ "references",
2316
+ "error",
2317
+ "An `asset:` reference names an asset that is not in the registry.",
2318
+ "An image, logo or video that points at a missing asset cannot be drawn or exported. The check reads the document's own registry only; no file or URL is fetched.",
2319
+ { standard: SCHEMA }
2320
+ ),
2321
+ rule("asset-cycle", "references", "error", "Asset references form a cycle.", "An asset whose source points back at itself never resolves to a file.", { standard: SCHEMA }),
2322
+ rule(
2323
+ "catalog-record",
2324
+ "references",
2325
+ "error",
2326
+ "An embedded or registered catalog record is invalid.",
2327
+ "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.",
2328
+ { standard: "spec/schemas/<kind>.schema.json" }
2329
+ ),
2330
+ rule(
2331
+ "unresolved-reference",
2332
+ "references",
2333
+ "warning",
2334
+ "A content reference resolves nowhere.",
2335
+ "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.",
2336
+ { 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." }
2337
+ ),
2338
+ rule(
2339
+ "catalog-record-not-in-source",
2340
+ "references",
2341
+ "warning",
2342
+ "A record embedded under a catalog group is not in that catalog.",
2343
+ "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.",
2344
+ { 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." }
2345
+ ),
2346
+ 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 }),
2347
+ rule("unused-reference", "references", "warning", "A reference is never cited.", "A listed reference that no run cites is not drawn anywhere.", { standard: SCHEMA }),
2348
+ rule("unused-dataset", "references", "warning", "A dataset is never used.", "A dataset no chart or table references is not drawn anywhere.", { standard: SCHEMA }),
2349
+ 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." }),
2350
+ 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 }),
2351
+ rule("variable-unknown", "references", "warning", "A `{{token}}` names a variable that is not declared.", "An undeclared token is shown literally.", { standard: SCHEMA }),
2352
+ rule("variable-unknown-value", "references", "warning", "A value was supplied for a variable that is not declared.", "The value is never used.", { standard: SCHEMA }),
2353
+ rule("variable-unused", "references", "warning", "A variable is declared but never used.", "A declaration nothing refers to has no effect.", { standard: SCHEMA }),
2354
+ rule("variable-reference-unknown", "references", "warning", "A `var:` colour names a variable that is not declared.", "An undeclared colour variable falls back to a default colour.", { standard: SCHEMA })
2355
+ ];
2356
+ var policyRules = [
2357
+ rule(
2358
+ "contract",
2359
+ "policy",
2360
+ "error",
2361
+ "A field is outside the values a host policy allows.",
2362
+ "A host that needs brand layouts, fonts or other fixed choices states them as contracts. The default severity is error; a contract can set its own. Document metadata never installs policy.",
2363
+ { approximations: "Only the `contracts` option supplies policy, and it checks existing fields: a contract does not require an omitted field or insert a default." }
2364
+ )
2365
+ ];
2366
+ var dataRules = [
2367
+ rule("chart-option-adapted", "layout", "warning", "A chart option cannot be shown by the chart type.", "An axis title, legend or data label option the chart type does not support is adapted by every engine, so the chart is not drawn exactly as authored.", { standard: SCHEMA }),
2368
+ rule(
2369
+ "chart-value-not-numeric",
2370
+ "content",
2371
+ "warning",
2372
+ "A chart value is text, not a number.",
2373
+ 'A chart plots numbers. A text cell such as "12%" is plotted as a gap. When every text value of a column is written in one display style the finding carries the fix that stores the numbers and gives the column the matching format.',
2374
+ { standard: SCHEMA }
2375
+ ),
2376
+ rule("chart-mapping-adapted", "content", "warning", "A chart mapping entry is ignored.", "A mapping that names a column the chart type cannot use is left out of the chart.", { standard: SCHEMA }),
2377
+ rule("chart-highlight-adapted", "content", "warning", "A chart highlight is adapted by the chart type.", "A highlight the chart type cannot show as authored (a category highlight on a chart without categories, for example) is adapted by every engine, so the emphasis is not exactly what was written.", { standard: SCHEMA }),
2378
+ rule("code-highlight-out-of-range", "content", "warning", "A code highlight marks a line the code does not have.", "Marked lines count from 1 by line break in code.source; an entry past the last line marks nothing.", { standard: SCHEMA }),
2379
+ rule("code-highlight-range-reversed", "content", "warning", "A code highlight range ends before it starts.", "A range written end before start marks nothing; write the smaller line number first.", { standard: SCHEMA }),
2380
+ rule("variable-builtin-missing", "content", "warning", "A built-in variable has no value in this document.", "A `{{deck.*}}`, `{{speaker.*}}` or `{{organization.*}}` token whose field the document does not set resolves to nothing, so the text shows a gap.", { standard: SCHEMA }),
2381
+ 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 }),
2382
+ 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" })
2383
+ ];
2384
+ var implementations = [...accessibilityRules, ...layoutContentRules];
2385
+ var ruleImplementations = implementations;
2386
+ var infos = [
2387
+ ...formatRules,
2388
+ ...referenceRules,
2389
+ ...policyRules,
2390
+ ...dataRules,
2391
+ ...implementations.flatMap((entry) => [entry.info, ...entry.also ?? []])
2392
+ ];
2393
+ var validationRules = Object.freeze(
2394
+ infos.map((info, index) => ({ info, index })).sort((a, b) => validationCategories.indexOf(a.info.category) - validationCategories.indexOf(b.info.category) || a.index - b.index).map(({ info }) => Object.freeze({ ...info }))
2395
+ );
2396
+ var byName = /* @__PURE__ */ new Map();
2397
+ for (const info of validationRules) {
2398
+ if (byName.has(info.id) || byName.has(info.name)) throw new Error(`Duplicate validation rule ${info.id}.`);
2399
+ byName.set(info.id, info);
2400
+ byName.set(info.name, info);
2401
+ }
2402
+ function findValidationRule(name) {
2403
+ return byName.get(name);
2404
+ }
2405
+ function ruleInfo(id) {
2406
+ const found = byName.get(id);
2407
+ if (!found) throw new Error(`Validation rule ${JSON.stringify(id)} is not registered.`);
2408
+ return found;
2409
+ }
2410
+
2411
+ // src/check-document.ts
2412
+ var own = (value, key) => Object.hasOwn(value, key);
2413
+ var object = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2414
+ var primitive = (value) => value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
2415
+ var pointer3 = (parts2) => parts2.length ? "/" + parts2.map((part) => String(part).replaceAll("~", "~0").replaceAll("/", "~1")).join("/") : "";
2416
+ function parts(path) {
2417
+ if (path === "") return [];
2418
+ if (!path.startsWith("/") || /~(?![01])/.test(path))
2419
+ throw new TypeError(`Invalid contract JSON Pointer: ${path}`);
2420
+ return path.slice(1).split("/").map((part) => part.replaceAll("~1", "/").replaceAll("~0", "~"));
2421
+ }
2422
+ function at(value, path) {
2423
+ return parts(path).reduce(
2424
+ (current, key) => object(current) || Array.isArray(current) ? own(current, key) ? current[key] : void 0 : void 0,
2425
+ value
2426
+ );
2427
+ }
2428
+ function checkDocumentOptions(options) {
2429
+ checkCatalogsOption(options.catalogs, "validate");
2430
+ if (options.contracts !== void 0 && !Array.isArray(options.contracts))
2431
+ throw new TypeError("Validate contracts must be an array.");
2432
+ for (const contract of options.contracts ?? []) {
2433
+ if (!object(contract) || Object.keys(contract).some(
2434
+ (key) => ![
2435
+ "path",
2436
+ "allowedValues",
2437
+ "severity",
2438
+ "message",
2439
+ "documentation"
2440
+ ].includes(key)
2441
+ ) || typeof contract.path !== "string" || !Array.isArray(contract.allowedValues) || !contract.allowedValues.every(primitive) || contract.severity !== void 0 && (typeof contract.severity !== "string" || !["error", "warning", "info"].includes(contract.severity)) || contract.message !== void 0 && typeof contract.message !== "string" || contract.documentation !== void 0 && typeof contract.documentation !== "string")
2442
+ throw new TypeError(
2443
+ "Invalid contract: provide a path and primitive allowedValues, with optional severity, message and documentation."
2444
+ );
2445
+ parts(contract.path);
2446
+ }
2447
+ }
2448
+ function finding(ruleId, data) {
2449
+ const info = ruleInfo(ruleId);
2450
+ const { severity, scope, ...rest } = data;
2451
+ return { ruleId, severity: severity ?? info.severity, category: info.category, scope: scope ?? "document", ...rest };
2452
+ }
2453
+ var issueRuleId = (issue2) => typeof issue2.params.code === "string" && issue2.keyword === "opf" ? `opf/${issue2.params.code}` : "opf/schema";
2454
+ function issueFinding(issue2, ruleId = issueRuleId(issue2), schemaName = "presentation", prefix = "", scope = "document", severity) {
2455
+ const path = prefix + (issue2.path === "/" ? "" : issue2.path) + (issue2.keyword === "additionalProperties" && typeof issue2.params.additionalProperty === "string" ? pointer3([issue2.params.additionalProperty]) : ""), entry = schemaEntries.find((entry2) => entry2.name === schemaName);
2456
+ if (!entry) throw new TypeError(`Unknown schema ${schemaName}`);
2457
+ const precise = validationDefinition(issue2);
2458
+ const definition = precise?.uri ?? (issue2.keyword === "opf" ? "packages/javascript/src/schema-check.ts" : issue2.schemaPath.startsWith("#") ? entry.schema.$id + issue2.schemaPath : issue2.schemaPath);
2459
+ const lookup = [
2460
+ "opf",
2461
+ "schema",
2462
+ precise?.schemaName ?? schemaName,
2463
+ precise?.pointer ?? (issue2.keyword !== "opf" && issue2.schemaPath.startsWith("#") ? issue2.schemaPath.slice(1) : "")
2464
+ ];
2465
+ let help = "Inspect this schema constraint and preserve unrelated content. Errors inside oneOf/anyOf branches may describe alternatives; retain the intended form.";
2466
+ if (issue2.keyword === "required")
2467
+ help = `Add the required ${JSON.stringify(issue2.params.missingProperty)} field in this object, using the intended schema form.`;
2468
+ else if (issue2.keyword === "additionalProperties")
2469
+ help = `Review ${JSON.stringify(issue2.params.additionalProperty)} against this object's schema. Preserve custom metadata in document.extensions where appropriate; do not delete authored content to clear this error.`;
2470
+ else if (issue2.keyword === "enum")
2471
+ help = "Choose one of this schema branch\u2019s allowed values; inspect the other alternatives if the field uses another form.";
2472
+ else if (issue2.keyword === "opf")
2473
+ help = "Resolve the reported structural conflict while preserving every content field. Preview the result after editing.";
2474
+ const allowed = Array.isArray(issue2.params.allowedValues) ? issue2.params.allowedValues.filter(primitive) : [];
2475
+ return finding(ruleId, {
2476
+ ...severity ? { severity } : {},
2477
+ path,
2478
+ scope,
2479
+ message: issue2.message,
2480
+ help,
2481
+ definition,
2482
+ lookup,
2483
+ validation: issue2,
2484
+ ...allowed.length ? {
2485
+ suggestions: allowed.map((value) => ({
2486
+ value,
2487
+ label: String(value),
2488
+ origin: "schema",
2489
+ definition
2490
+ }))
2491
+ } : {}
2492
+ });
2493
+ }
2494
+ function resolve(cursor, seen = /* @__PURE__ */ new Set()) {
2495
+ const ref = cursor.schema.$ref;
2496
+ if (typeof ref !== "string") return cursor;
2497
+ const id = String(cursor.root.$id ?? "") + ref;
2498
+ if (seen.has(id)) return { ...cursor, schema: {} };
2499
+ seen.add(id);
2500
+ const [base, fragment = ""] = ref.split("#"), root = base ? schemaEntries.find((entry) => entry.schema.$id === base)?.schema : cursor.root;
2501
+ if (!root) return { ...cursor, schema: {} };
2502
+ const target = at(root, fragment);
2503
+ if (!object(target)) return { ...cursor, schema: {} };
2504
+ const resolved = resolve(
2505
+ { schema: target, root, path: fragment },
2506
+ seen
2507
+ );
2508
+ const { $ref, ...rest } = cursor.schema;
2509
+ return { ...resolved, schema: { ...resolved.schema, ...rest } };
2510
+ }
2511
+ function score(schema, value) {
2512
+ const type = schema.type;
2513
+ if (typeof type === "string" && (type === "array" ? !Array.isArray(value) : type === "object" ? !object(value) : type === "null" ? value !== null : type === "integer" ? !Number.isInteger(value) : typeof value !== type))
2514
+ return -1e4;
2515
+ if (own(schema, "const")) return schema.const === value ? 100 : -1e4;
2516
+ let result = 1;
2517
+ if (object(value) && object(schema.properties)) {
2518
+ for (const [key, child] of Object.entries(schema.properties))
2519
+ if (own(value, key) && object(child))
2520
+ result += own(child, "const") ? child.const === value[key] ? 100 : -1e3 : 1;
2521
+ }
2522
+ if (Array.isArray(schema.required)) {
2523
+ for (const key of schema.required)
2524
+ if (typeof key === "string" && (!object(value) || !own(value, key)))
2525
+ result -= 2;
2526
+ }
2527
+ return result;
2528
+ }
2529
+ function active(cursor, value) {
2530
+ const resolved = resolve(cursor), alternatives = resolved.schema.oneOf ?? resolved.schema.anyOf;
2531
+ if (!Array.isArray(alternatives)) return resolved;
2532
+ const keyword = resolved.schema.oneOf ? "oneOf" : "anyOf";
2533
+ const candidates = alternatives.filter(object).map(
2534
+ (schema, index) => active(
2535
+ {
2536
+ schema,
2537
+ root: resolved.root,
2538
+ path: `${resolved.path}/${keyword}/${index}`
2539
+ },
2540
+ value
2541
+ )
2542
+ );
2543
+ const selected = candidates.sort((a, b) => score(b.schema, value) - score(a.schema, value))[0];
2544
+ return selected ? { ...selected, baseSchemas: [...selected.baseSchemas ?? [], resolved] } : resolved;
2545
+ }
2546
+ function childCursor(cursors, key) {
2547
+ for (const keyword of [
2548
+ "properties",
2549
+ "patternProperties",
2550
+ "additionalProperties"
2551
+ ]) {
2552
+ for (const cursor of cursors) {
2553
+ const declarations = cursor.schema[keyword];
2554
+ if (!object(declarations)) continue;
2555
+ const name = keyword === "properties" ? key : keyword === "patternProperties" ? Object.keys(declarations).find(
2556
+ (pattern) => new RegExp(pattern).test(key)
2557
+ ) : void 0;
2558
+ const schema = keyword === "additionalProperties" ? declarations : name === void 0 ? void 0 : declarations[name];
2559
+ if (object(schema))
2560
+ return {
2561
+ schema,
2562
+ root: cursor.root,
2563
+ path: `${cursor.path}/${keyword}${name === void 0 ? "" : pointer3([name])}`
2564
+ };
2565
+ }
2566
+ }
2567
+ return void 0;
2568
+ }
2569
+ function fields(document) {
2570
+ const result = [], stack = [
2571
+ {
2572
+ value: document,
2573
+ path: [],
2574
+ cursor: {
2575
+ schema: schemas.presentation,
2576
+ root: schemas.presentation,
2577
+ path: ""
2578
+ },
2579
+ ancestors: []
2580
+ }
2581
+ ];
2582
+ while (stack.length) {
2583
+ const entry = stack.pop();
2584
+ if (!entry) break;
2585
+ if (entry.ancestors.includes(entry.value)) continue;
2586
+ const resolved = resolve(entry.cursor), selected = active(entry.cursor, entry.value);
2587
+ const assetReference = [resolved, selected].some(
2588
+ (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(
2589
+ (example) => typeof example === "string" && example.startsWith("asset:")
2590
+ )
2591
+ );
2592
+ result.push({
2593
+ path: pointer3(entry.path),
2594
+ value: entry.value,
2595
+ cursor: resolved,
2596
+ assetReference
2597
+ });
2598
+ const ancestors = [...entry.ancestors, entry.value];
2599
+ const cursors = [selected, ...selected.baseSchemas ?? [], resolved];
2600
+ const arrayCursor = cursors.find((cursor) => object(cursor.schema.items));
2601
+ if (Array.isArray(entry.value) && arrayCursor)
2602
+ entry.value.forEach((value, index) => {
2603
+ stack.push({
2604
+ value,
2605
+ path: [...entry.path, index],
2606
+ cursor: {
2607
+ schema: arrayCursor.schema.items,
2608
+ root: arrayCursor.root,
2609
+ path: `${arrayCursor.path}/items`
2610
+ },
2611
+ ancestors
2612
+ });
2613
+ });
2614
+ else if (object(entry.value))
2615
+ for (const [key, value] of Object.entries(entry.value)) {
2616
+ if (entry.path.length === 0 && key === "catalogs") continue;
2617
+ const cursor = childCursor(cursors, key);
2618
+ if (!cursor) continue;
2619
+ stack.push({
2620
+ value,
2621
+ path: [...entry.path, key],
2622
+ cursor,
2623
+ ancestors
2624
+ });
2625
+ }
2626
+ }
2627
+ return result;
2628
+ }
2629
+ var recordKey = (group, kind, id) => `${group}/${kind}/${id}`;
2630
+ function recordIssues(kind, id, value) {
2631
+ const schemaName = catalogSchemaNames[kind];
2632
+ return validateAgainstSchema(object(value) ? { $schema: schemas[schemaName].$id, id, ...value } : value, kind);
2633
+ }
2634
+ function embeddedRecordFindings(document, options, findings) {
2635
+ const invalid = /* @__PURE__ */ new Set();
2636
+ if (!object(document) || !object(document.catalogs)) return invalid;
2637
+ for (const [group, value] of Object.entries(document.catalogs)) {
2638
+ if (!object(value)) continue;
2639
+ for (const kind of catalogKinds)
2640
+ for (const [id, record] of Object.entries(object(value[kind]) ? value[kind] : {})) {
2641
+ const path = pointer3(["catalogs", group, kind, id]);
2642
+ if (object(record) && (Object.hasOwn(record, "$schema") || Object.hasOwn(record, "id"))) continue;
2643
+ const validation = recordIssues(kind, id, record);
2644
+ for (const issue2 of validation.errors) findings.push(issueFinding(issue2, "opf/catalog-record", catalogSchemaNames[kind], path, "document"));
2645
+ if (!validation.valid) invalid.add(recordKey(group, kind, id));
2646
+ if (group === "custom") continue;
2647
+ const source = catalogGroupSource(document, group, options);
2648
+ const registered = registeredCatalog(source, options);
2649
+ if (!registered || Object.hasOwn(registered[kind] ?? {}, id)) continue;
2650
+ findings.push(
2651
+ finding("opf/catalog-record-not-in-source", {
2652
+ path,
2653
+ 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.`,
2654
+ 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.`,
2655
+ definition: `${schemas.presentation.$id}#/$defs/Catalogs`,
2656
+ lookup: ["opf", "catalog", kind]
2657
+ })
2658
+ );
2659
+ }
2660
+ }
2661
+ return invalid;
2662
+ }
2663
+ function distance(a, b) {
2664
+ a = a.slice(0, 128);
2665
+ b = b.slice(0, 128);
2666
+ let row = Array.from({ length: b.length + 1 }, (_, i) => i);
2667
+ for (let i = 0; i < a.length; i++) {
2668
+ const next = [i + 1];
2669
+ for (let j = 0; j < b.length; j++)
2670
+ next.push(
2671
+ Math.min(
2672
+ (next[j] ?? Infinity) + 1,
2673
+ (row[j + 1] ?? Infinity) + 1,
2674
+ (row[j] ?? Infinity) + (a[i] === b[j] ? 0 : 1)
2675
+ )
2676
+ );
2677
+ row = next;
2678
+ }
2679
+ return row[b.length] ?? Math.max(a.length, b.length);
2680
+ }
2681
+ function chartNumberFixPaths(document) {
2682
+ const byCell = /* @__PURE__ */ new Map();
2683
+ if (!isRecord(document) || !Array.isArray(document.slides)) return byCell;
2684
+ const payload = (node, path) => {
2685
+ if (isRecord(node.chart))
2686
+ for (const [cell, fix] of chartNumberFixesByCell(node.chart, document, { path: pathFor(path, "chart") })) byCell.set(cell, fix);
2687
+ };
2688
+ document.slides.forEach((slide, index) => {
2689
+ if (!isRecord(slide)) return;
2690
+ payload(slide, `/slides/${index}`);
2691
+ visitContentPayloads(slide, `/slides/${index}`, payload);
2692
+ });
2693
+ return byCell;
2694
+ }
2695
+ var HELP = {
2696
+ "chart-value-not-numeric": 'Write chart values as numbers (or strict decimal strings such as "12.5" or "1e6"); put currency, percent and units in the column format ({ "name": "Revenue", "format": "$#,##0" }). The value is plotted as a gap.',
2697
+ "chart-mapping-adapted": "The mapping entry is ignored. Remove it, or name a different column.",
2698
+ "chart-highlight-adapted": "The highlight is adapted by every engine. Name a series or category the chart type can emphasise, or remove the entry.",
2699
+ "slide-theme-dimensions": "A PPTX has one slide size. Set design.dimensions on the deck, or give every slide the same theme dimensions.",
2700
+ "code-highlight-out-of-range": "Marked lines count from 1 by line break in code.source. The entry marks nothing past the last line; change it to a line the code has, or remove it.",
2701
+ "code-highlight-range-reversed": "Write the range with the smaller line number first, or remove it. A reversed range marks nothing.",
2702
+ "chart-option-adapted": "Remove the option, or choose a chart type that can show it; every engine draws the adapted result.",
2703
+ "variable-unknown": "Declare the variable in the top-level variables map, or write '\\{{' for literal braces.",
2704
+ "variable-unknown-value": "Remove the supplied value, or declare the variable it belongs to.",
2705
+ "variable-unused": "Reference the variable as {{id}} or var:id where it belongs, or remove its declaration.",
2706
+ "variable-reference-unknown": "Declare the variable in the top-level variables map, or use another colour.",
2707
+ "run-color-unrecognized": "Use a #RRGGBB hex colour, a colour-scheme name or a var: reference to a declared colour variable.",
2708
+ "numbering-start-ignored": "Add a 'numbering' field to the payload, or remove 'start'."
2709
+ };
2710
+ function engineFindings(document, engine, enabled) {
2711
+ const findings = [];
2712
+ for (const issue2 of engine.errors) {
2713
+ const ruleId = issueRuleId(issue2);
2714
+ if (enabled(ruleId)) findings.push(issueFinding(issue2, ruleId));
2715
+ }
2716
+ let numberFixes;
2717
+ for (const issue2 of engine.warnings) {
2718
+ const code = issue2.params.code;
2719
+ if (typeof code !== "string") continue;
2720
+ const ruleId = `opf/${code}`;
2721
+ if (!enabled(ruleId)) continue;
2722
+ if (code === "chart-value-not-numeric") {
2723
+ numberFixes ??= chartNumberFixPaths(document);
2724
+ const fix = numberFixes.get(issue2.path);
2725
+ if (fix) {
2726
+ findings.push({
2727
+ ...issueFinding(issue2, ruleId, "presentation", "", "document", "warning"),
2728
+ help: `Every text value of column ${JSON.stringify(fix.name)} is written in one display style. Store the numbers and give the column the format ${JSON.stringify(fix.format)}, which shows the same text: apply fixes[0] (core suggestChartNumberFix). Until then the value is plotted as a gap.`,
2729
+ fixes: [{ id: "store-chart-numbers", title: `Store ${JSON.stringify(fix.name)} as numbers with the format ${JSON.stringify(fix.format)}`, kind: "patch", safe: false, patch: fix.patches }]
2730
+ });
2731
+ continue;
2732
+ }
2733
+ }
2734
+ const found = issueFinding(issue2, ruleId, "presentation", "", "document", "warning");
2735
+ const help = HELP[code];
2736
+ if (help) found.help = help;
2737
+ findings.push(found);
2738
+ }
2739
+ return findings;
2740
+ }
2741
+ var VARIABLE_TOKEN = /\\\{\{|\{\{\s*([a-z][a-z0-9-]*)\s*(?:\|[^{}]*)?\}\}/g;
2742
+ function variableFindings(document, engine, schemaValid) {
2743
+ const findings = [];
2744
+ if (engine.template === true) {
2745
+ for (const id of engine.unfilledVariables ?? [])
2746
+ findings.push(
2747
+ finding("opf/variable-unfilled", {
2748
+ path: pointer3(["variables", id]),
2749
+ message: `Template variable ${JSON.stringify(id)} has no value.`,
2750
+ help: "This document is a template: fill it with its values (opf fill, or resolveVariables) before using it as a deck.",
2751
+ severity: "warning"
2752
+ })
2753
+ );
2754
+ return findings;
2755
+ }
2756
+ for (const issue2 of engine.unfilled)
2757
+ findings.push(
2758
+ finding("opf/variable-unfilled", {
2759
+ path: issue2.path,
2760
+ message: issue2.message,
2761
+ help: 'Give the variable a value, fill the deck before use (opf fill, or resolveVariables), or mark the document as a template ("template": true).'
2762
+ })
2763
+ );
2764
+ if (engine.template !== void 0 || !schemaValid) return findings;
2765
+ const walk = (value, path, depth) => {
2766
+ if (depth > 64) return;
2767
+ if (typeof value === "string") {
2768
+ if (value.startsWith("data:") || !value.includes("{{")) return;
2769
+ for (const match of value.matchAll(VARIABLE_TOKEN))
2770
+ if (match[1])
2771
+ findings.push(
2772
+ finding("opf/variable-unfilled", {
2773
+ path: pointer3(path),
2774
+ message: `Template variable ${JSON.stringify(match[0])} is still in the text.`,
2775
+ help: "Declare the variable and fill it, or replace the token with the real value.",
2776
+ severity: "warning"
2777
+ })
2778
+ );
2779
+ return;
2780
+ }
2781
+ if (Array.isArray(value)) {
2782
+ value.forEach((entry, index) => {
2783
+ walk(entry, [...path, String(index)], depth + 1);
2784
+ });
2785
+ return;
2786
+ }
2787
+ if (value && typeof value === "object")
2788
+ for (const [key, entry] of Object.entries(value)) {
2789
+ if (path.length === 0 && ["variables", "extensions", "catalogs", "assets"].includes(key)) continue;
2790
+ walk(entry, [...path, key], depth + 1);
2791
+ }
2792
+ };
2793
+ walk(document, [], 0);
2794
+ return findings;
2795
+ }
2796
+ function undeclaredCatalogFindings(document) {
2797
+ const findings = [];
2798
+ const reported = /* @__PURE__ */ new Set();
2799
+ for (const site of catalogReferenceSites(document)) {
2800
+ const parsed = parseReference(site.reference);
2801
+ if (parsed?.group === void 0 || catalogGroupDeclared(document, parsed.group)) continue;
2802
+ const path = pointerPath(site.path);
2803
+ if (reported.has(path)) continue;
2804
+ reported.add(path);
2805
+ findings.push(
2806
+ finding("opf/undeclared-catalog", {
2807
+ path,
2808
+ message: `Reference ${JSON.stringify(site.reference)} names catalog group ${JSON.stringify(parsed.group)}, which the document does not declare, so it can never resolve.`,
2809
+ help: `Declare catalogs.${parsed.group} with the catalog's source (and embed the record), or write the reference without the prefix.`,
2810
+ definition: `${schemas.presentation.$id}#/$defs/CatalogReference`,
2811
+ lookup: ["opf", "schema", "presentation", "/$defs/CatalogReference"]
2812
+ })
2813
+ );
2814
+ }
2815
+ return findings;
2816
+ }
2817
+ function contentReferenceFindings(document, options, findings) {
2818
+ const invalid = embeddedRecordFindings(document, options, findings);
2819
+ const registeredChecked = /* @__PURE__ */ new Set();
2820
+ for (const site of catalogReferenceSites(document)) {
2821
+ const path = pointerPath(site.path);
2822
+ const parsed = parseReference(site.reference);
2823
+ if (parsed?.group !== void 0 && !catalogGroupDeclared(document, parsed.group)) continue;
2824
+ const found = resolveReference(document, site.kind, site.reference, { ...options, ...site.group !== void 0 ? { group: site.group } : {} });
2825
+ if (found?.origin === "host" && !registeredChecked.has(recordKey(found.group, site.kind, found.id))) {
2826
+ registeredChecked.add(recordKey(found.group, site.kind, found.id));
2827
+ for (const issue2 of recordIssues(site.kind, found.id, found.record).errors)
2828
+ findings.push(issueFinding(issue2, "opf/catalog-record", catalogSchemaNames[site.kind], pointer3(["catalogs", found.group, site.kind, found.id]), "context"));
2829
+ }
2830
+ if (found && !(found.origin === "document" && invalid.has(recordKey(found.group, site.kind, found.id)))) continue;
2831
+ const diagnostic = unresolvedReference(document, site.kind, site.reference, path, options);
2832
+ 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);
2833
+ findings.push(
2834
+ finding("opf/unresolved-reference", {
2835
+ path,
2836
+ message: diagnostic.message,
2837
+ 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.",
2838
+ definition: `${schemas.presentation.$id}#/$defs/Catalogs`,
2839
+ lookup: ["opf", "catalog", site.kind],
2840
+ suggestions
2841
+ })
2842
+ );
2843
+ }
2844
+ }
2845
+ function referenceFindings(document, engine, options) {
2846
+ const findings = [];
2847
+ const catalogs = { catalogs: options.catalogs ?? [] };
2848
+ contentReferenceFindings(document, catalogs, findings);
2849
+ const assetValues = object(document) && object(document.assets) ? document.assets : {};
2850
+ for (const field of fields(document)) {
2851
+ if (typeof field.value === "string" && field.value.startsWith("asset:") && field.assetReference) {
2852
+ const id = field.value.slice(6);
2853
+ if (!own(assetValues, id))
2854
+ findings.push(
2855
+ finding("opf/asset-reference", {
2856
+ path: field.path,
2857
+ message: `Asset ${JSON.stringify(id)} is missing from the document registry.`,
2858
+ help: "Supply the intended asset in document.assets or choose the correct existing asset ID. External files and URLs are not fetched or verified.",
2859
+ definition: schemas.presentation.$id + "#/$defs/Assets",
2860
+ lookup: ["opf", "schema", "presentation", "/$defs/Assets"],
2861
+ suggestions: Object.keys(assetValues).sort(
2862
+ (a, b) => distance(id, a) - distance(id, b) || (a < b ? -1 : a > b ? 1 : 0)
2863
+ ).slice(0, 5).map((id2) => ({
2864
+ value: `asset:${id2}`,
2865
+ label: id2,
2866
+ origin: "document",
2867
+ definition: `document#${pointer3(["assets", id2])}`
2868
+ }))
2869
+ })
2870
+ );
2871
+ }
2872
+ }
2873
+ const reportedCycles = /* @__PURE__ */ new Set();
2874
+ for (const id of Object.keys(assetValues)) {
2875
+ const chain = [], seenIds = /* @__PURE__ */ new Set();
2876
+ let current = id;
2877
+ while (current !== void 0 && own(assetValues, current)) {
2878
+ if (seenIds.has(current)) {
2879
+ const cycle = chain.slice(chain.indexOf(current));
2880
+ if (!cycle.some((id2) => reportedCycles.has(id2))) {
2881
+ for (const id2 of cycle) reportedCycles.add(id2);
2882
+ findings.push(
2883
+ finding("opf/asset-cycle", {
2884
+ path: pointer3(["assets", current]),
2885
+ message: `Asset references form a cycle: ${[...cycle, current].map((id2) => JSON.stringify(id2)).join(" \u2192 ")}.`,
2886
+ help: "Replace one link with the intended concrete source. Preserve the original asset metadata; no files or URLs were fetched."
2887
+ })
2888
+ );
2889
+ }
2890
+ break;
2891
+ }
2892
+ seenIds.add(current);
2893
+ chain.push(current);
2894
+ const value = assetValues[current], source = object(value) ? value.src : value;
2895
+ current = typeof source === "string" && source.startsWith("asset:") ? source.slice(6) : void 0;
2896
+ }
2897
+ }
2898
+ findings.push(...narrativeFindings(document, catalogs));
2899
+ for (const issue2 of unusedReferenceWarnings(document))
2900
+ findings.push(
2901
+ finding("opf/unused-reference", {
2902
+ path: issue2.path,
2903
+ message: issue2.message,
2904
+ help: "Cite the reference from a text, bullet or list item run ({ text, cite: id }) so it is listed in that slide's footnote area, or remove the entry. Nothing is drawn for an uncited reference.",
2905
+ definition: schemas.presentation.$id + "#/$defs/Reference",
2906
+ lookup: ["opf", "schema", "presentation", "/$defs/Reference"],
2907
+ validation: issue2
2908
+ })
2909
+ );
2910
+ for (const id of unusedDatasets(document))
2911
+ findings.push(
2912
+ finding("opf/unused-dataset", {
2913
+ path: `/datasets/${id.replaceAll("~", "~0").replaceAll("/", "~1")}`,
2914
+ message: `Dataset ${JSON.stringify(id)} is never referenced; reference it from a chart ("data": { "dataset": ${JSON.stringify(id)} }) or a table ({ "dataset": ${JSON.stringify(id)} }), or remove it.`,
2915
+ help: "Reference the dataset from chart.data or a table so it is drawn, or remove the entry. Nothing is drawn for an unreferenced dataset.",
2916
+ definition: schemas.presentation.$id + "#/$defs/Dataset",
2917
+ lookup: ["opf", "schema", "presentation", "/$defs/Dataset"]
2918
+ })
2919
+ );
2920
+ return findings;
2921
+ }
2922
+ function narrativeFindings(document, options) {
2923
+ const findings = [];
2924
+ if (!object(document)) return findings;
2925
+ const narrative = resolveNarrative(document, options);
2926
+ if (narrative) {
2927
+ const id = String(document.narrative);
2928
+ 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` }));
2929
+ for (const reference of unknownBeatReferences(document, narrative))
2930
+ findings.push(
2931
+ finding("opf/unknown-beat", {
2932
+ path: reference.path,
2933
+ message: `Slide ${reference.slide + 1} names beat ${JSON.stringify(reference.beat)}, which narrative ${JSON.stringify(id)} does not define.`,
2934
+ 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.`,
2935
+ definition: schemas.presentation.$id + "#/$defs/Slide/properties/beat",
2936
+ lookup: ["opf", "catalog", "narratives"],
2937
+ suggestions: beatSuggestions(reference.beat)
2938
+ })
2939
+ );
2940
+ const outside = durationOutsideNarrative(document, narrative);
2941
+ if (outside)
2942
+ findings.push(
2943
+ finding("opf/duration-outside-narrative", {
2944
+ path: "/duration",
2945
+ message: `The target duration of ${outside.duration} minutes is outside the ${describeDurationRange(outside.range)} narrative ${JSON.stringify(id)} suits.`,
2946
+ help: "Change the target duration, choose a narrative that suits it, or widen the range of an inline narrative record. The range describes the narrative, so nothing is drawn or exported differently.",
2947
+ definition: schemas.presentation.$id + "#/properties/duration",
2948
+ lookup: ["opf", "catalog", "narratives"]
2949
+ })
2950
+ );
2951
+ }
2952
+ for (const [group, value] of Object.entries(object(document.catalogs) ? document.catalogs : {}))
2953
+ for (const [id, record] of Object.entries(object(value) && object(value.narratives) ? value.narratives : {})) {
2954
+ if (durationRangeInverted(record))
2955
+ findings.push(
2956
+ finding("opf/narrative-duration-range", {
2957
+ path: pointer3(["catalogs", group, "narratives", id, "duration"]),
2958
+ message: "The narrative duration range has min greater than max.",
2959
+ help: "Swap the bounds so min is the shortest and max the longest talk length, in minutes.",
2960
+ definition: schemas.narrative.$id + "#/properties/duration",
2961
+ lookup: ["opf", "schema", "narrative", "/properties/duration"]
2962
+ })
2963
+ );
2964
+ }
2965
+ return findings;
2966
+ }
2967
+ function contractFindings(document, contracts) {
2968
+ const findings = [];
2969
+ for (const [index, contract] of contracts.entries()) {
2970
+ const pattern = parts(contract.path), stack = [{ value: document, path: [], depth: 0 }];
2971
+ while (stack.length) {
2972
+ const entry = stack.pop();
2973
+ if (!entry) break;
2974
+ if (entry.depth === pattern.length) {
2975
+ if (contract.allowedValues.some((value) => Object.is(value, entry.value)))
2976
+ continue;
2977
+ const path = pointer3(entry.path), definition = contract.documentation ?? `context#/contracts/${index}`, values = {
2978
+ path,
2979
+ value: JSON.stringify(entry.value),
2980
+ allowed: contract.allowedValues.map((value) => JSON.stringify(value)).join(", "),
2981
+ file: definition
2982
+ };
2983
+ const message = (contract.message ?? "This value is outside the allowed values for {{path}}.").replace(
2984
+ /\{\{(path|value|allowed|file)\}\}/g,
2985
+ (_, key) => values[key] ?? ""
2986
+ );
2987
+ findings.push(
2988
+ finding("opf/contract", {
2989
+ severity: contract.severity ?? "error",
2990
+ path,
2991
+ message,
2992
+ help: `Use one of the configured values: ${values.allowed}. This is an explicit host policy; no source was changed.`,
2993
+ definition,
2994
+ suggestions: contract.allowedValues.map((value) => ({
2995
+ value,
2996
+ label: String(value),
2997
+ origin: "contract",
2998
+ definition
2999
+ }))
3000
+ })
3001
+ );
3002
+ } else if (object(entry.value) || Array.isArray(entry.value)) {
3003
+ const key = pattern[entry.depth];
3004
+ for (const [name, value] of Object.entries(entry.value))
3005
+ if (key === "*" || key === name)
3006
+ stack.push({
3007
+ value,
3008
+ path: [...entry.path, name],
3009
+ depth: entry.depth + 1
3010
+ });
3011
+ }
3012
+ }
3013
+ }
3014
+ return findings;
3015
+ }
3016
+ var YAML_START = /^[A-Za-z_][\w.-]*\s*:(?:\s|$)/;
3017
+ function hint(source) {
3018
+ const text = source.replace(/^\uFEFF/, "").trimStart();
3019
+ if (text.startsWith("{") || text.startsWith("[") || text === "") return "";
3020
+ if (text.startsWith("---") || !text.startsWith("#") && YAML_START.test(text))
3021
+ return " This looks like YAML: read it with fromYaml, then validate the presentation it returns.";
3022
+ if (text.startsWith("#")) return " This looks like Markdown: read it with fromMarkdown, then validate the presentation it returns.";
3023
+ return "";
3024
+ }
3025
+ function parseSource(source) {
3026
+ const errors = [], tree = parseTree(
3027
+ source.startsWith("\uFEFF") ? " " + source.slice(1) : source,
3028
+ errors,
3029
+ {
3030
+ disallowComments: true,
3031
+ allowTrailingComma: false,
3032
+ allowEmptyContent: false
3033
+ }
3034
+ );
3035
+ const lineStarts = [0];
3036
+ for (let i = 0; i < source.length; i++) {
3037
+ if (source[i] === "\r") {
3038
+ if (source[i + 1] === "\n") i++;
3039
+ lineStarts.push(i + 1);
3040
+ } else if (source[i] === "\n") lineStarts.push(i + 1);
3041
+ }
3042
+ const location = (offset, length) => {
3043
+ let lo = 0, hi = lineStarts.length;
3044
+ while (lo + 1 < hi) {
3045
+ const mid = lo + hi >> 1;
3046
+ if ((lineStarts[mid] ?? 0) <= offset) lo = mid;
3047
+ else hi = mid;
3048
+ }
3049
+ return {
3050
+ offset,
3051
+ length,
3052
+ line: lo + 1,
3053
+ column: offset - (lineStarts[lo] ?? 0) + 1
3054
+ };
3055
+ };
3056
+ if (errors.length || !tree) {
3057
+ const note = hint(source);
3058
+ return {
3059
+ syntax: errors.map(
3060
+ (error, index) => finding("opf/json-syntax", {
3061
+ path: "",
3062
+ message: printParseErrorCode(error.error),
3063
+ help: `Repair the JSON syntax at this source range. No content or whitespace has been rewritten.${index === 0 ? note : ""}`,
3064
+ location: location(error.offset, error.length)
3065
+ })
3066
+ ),
3067
+ duplicates: [],
3068
+ locate: () => void 0
3069
+ };
3070
+ }
3071
+ const duplicates = [], stack = [tree];
3072
+ while (stack.length) {
3073
+ const node = stack.pop();
3074
+ if (!node) break;
3075
+ if (node.type === "object") {
3076
+ const keys = /* @__PURE__ */ new Set();
3077
+ for (const property of node.children ?? []) {
3078
+ const key = property.children?.[0];
3079
+ if (!key) continue;
3080
+ if (keys.has(key.value))
3081
+ duplicates.push(
3082
+ finding("opf/duplicate-key", {
3083
+ path: pointer3(getNodePath(key)),
3084
+ message: `Duplicate JSON property ${JSON.stringify(key.value)}.`,
3085
+ help: "Resolve the duplicate explicitly, preserving the intended content from both occurrences. JSON.parse would otherwise hide an earlier value.",
3086
+ location: location(key.offset, key.length)
3087
+ })
3088
+ );
3089
+ keys.add(key.value);
3090
+ }
3091
+ }
3092
+ stack.push(...node.children ?? []);
3093
+ }
3094
+ return {
3095
+ value: JSON.parse(source.replace(/^\uFEFF/, "")),
3096
+ syntax: [],
3097
+ duplicates,
3098
+ locate(path) {
3099
+ try {
3100
+ let node = tree;
3101
+ const found = [];
3102
+ const wanted = parts(path);
3103
+ for (const part of wanted) {
3104
+ const next = node?.type === "array" ? node.children?.[Number(part)] : node?.type === "object" ? node.children?.find(
3105
+ (property) => property.children?.[0]?.value === part
3106
+ )?.children?.[1] : void 0;
3107
+ if (!next) break;
3108
+ node = next;
3109
+ found.push(next);
3110
+ }
3111
+ const target = found.length === wanted.length ? node : found[found.length - 1] ?? tree;
3112
+ return target ? location(target.offset, target.length) : void 0;
3113
+ } catch {
3114
+ return void 0;
3115
+ }
3116
+ }
3117
+ };
3118
+ }
3119
+
3120
+ // src/slide-context.ts
3121
+ var rec2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
3122
+ function resolveSlideContext(presentation, index, options = {}) {
3123
+ const deck = rec2(presentation);
3124
+ const slides = Array.isArray(deck.slides) ? deck.slides : [];
3125
+ if (!Number.isInteger(index) || index < 0 || index >= slides.length) throw new RangeError(`Slide index ${String(index)} is outside the presentation's ${slides.length} slides.`);
3126
+ const slide = rec2(slides[index]);
3127
+ const catalogs = { catalogs: checkCatalogsOption(options.catalogs, "resolveSlideContext") };
3128
+ const records = resolveDesignRecords(presentation, index, catalogs);
3129
+ const diagnostics = [...records.diagnostics];
3130
+ const { theme, colorScheme, fontScheme } = records;
3131
+ const design = { ...rec2(deck.design), ...rec2(slide.design) };
3132
+ let layout;
3133
+ let layoutProvenance;
3134
+ if (typeof slide.layout === "string") {
3135
+ const found = resolveReference(presentation, "layouts", slide.layout, catalogs);
3136
+ if (found) {
3137
+ layout = { ...found.record };
3138
+ layoutProvenance = provenanceOf(found);
3139
+ } else diagnostics.push(unresolvedReference(presentation, "layouts", slide.layout, `slides.${index}.layout`, catalogs));
3140
+ }
3141
+ if (options.strictReferences && diagnostics.length) throw new OPFUnresolvedReferenceError(diagnostics);
3142
+ const composeOptions = {
3143
+ ...resolveCanvasDimensions(design.dimensions ?? theme.dimensions),
3144
+ ...layout ? { layout } : {},
3145
+ presentation,
3146
+ slideIndex: index,
3147
+ slideNumber: options.slideNumber ?? index + 1,
3148
+ slideCount: options.slideCount ?? slides.length,
3149
+ fontFamilies: resolveFontFamilies(fontScheme),
3150
+ darkBackground: resolveColorRoles(colorScheme, { background: decisionColor(design.background ?? theme.background, colorScheme, rec2(deck.variables)) }).dark,
3151
+ ...theme.background !== void 0 ? { themeBackground: theme.background } : {},
3152
+ ...options.fonts?.textMeasurement ? { textMeasurement: options.fonts.textMeasurement } : {},
3153
+ ...options.date !== void 0 ? { date: options.date } : {}
3154
+ };
3155
+ return { options: composeOptions, diagnostics, resolved: { theme, colorScheme, fontScheme, fontSchemePath: records.fontSchemePath, ...layout ? { layout } : {}, provenance: { ...layoutProvenance ? { layout: layoutProvenance } : {}, ...records.provenance } } };
3156
+ }
3157
+
3158
+ // src/rule-slides.ts
3159
+ var measurementFor = (options, index) => {
3160
+ const measurement = options.fonts?.textMeasurement;
3161
+ return typeof measurement === "function" ? measurement(index) : measurement;
3162
+ };
3163
+ var lazy = (compute) => {
3164
+ let done = false;
3165
+ let value;
3166
+ return () => {
3167
+ if (!done) {
3168
+ value = compute();
3169
+ done = true;
3170
+ }
3171
+ return value;
3172
+ };
3173
+ };
3174
+ function relax(value) {
3175
+ const copy = JSON.parse(JSON.stringify(value));
3176
+ const walk = (node) => {
3177
+ if (Array.isArray(node)) return node.forEach(walk);
3178
+ if (node && typeof node === "object") {
3179
+ const object2 = node;
3180
+ if (object2.overflow === "error") object2.overflow = "warn";
3181
+ for (const child of Object.values(object2)) walk(child);
3182
+ }
3183
+ };
3184
+ walk(copy);
3185
+ return copy;
3186
+ }
3187
+ function buildSlides(document, options) {
3188
+ const slides = Array.isArray(document.slides) ? document.slides : [];
3189
+ return slides.map((slide, index) => {
3190
+ const path = pointer2("slides", index);
3191
+ const measurement = lazy(() => measurementFor(options, index));
3192
+ const resolved = lazy(() => {
3193
+ const textMeasurement = measurement();
3194
+ return resolveSlideContext(document, index, { catalogs: options.catalogs, ...textMeasurement ? { fonts: { textMeasurement } } : {}, date: "2000-01-01" });
3195
+ });
3196
+ const design = lazy(() => resolveDesign(document, index, resolved()));
3197
+ const layout = lazy(() => resolved().options.layout);
3198
+ const composed = lazy(() => {
3199
+ const result = { layoutDiagnostics: [] };
3200
+ const compose = (input, withLayout) => composeSlide(input, { ...resolved().options, layout: withLayout });
3201
+ try {
3202
+ result.composition = compose(slide, layout());
3203
+ result.layoutDiagnostics.push(...result.composition.diagnostics.map((diagnostic) => ({ diagnostic, strict: false })));
3204
+ } catch (error) {
3205
+ if (error instanceof OPFCompositionError) {
3206
+ result.layoutDiagnostics.push(...error.diagnostics.map((diagnostic) => ({ diagnostic, strict: true })));
3207
+ try {
3208
+ const relaxedLayout = layout();
3209
+ result.composition = compose(relax(slide), relaxedLayout ? relax(relaxedLayout) : void 0);
3210
+ } catch (second) {
3211
+ result.layoutError = second instanceof Error ? second.message : String(second);
3212
+ }
3213
+ } else result.layoutError = error instanceof Error ? error.message : String(error);
3214
+ }
3215
+ return result;
3216
+ });
3217
+ const payloads = lazy(() => slidePayloads(slide, path));
3218
+ const texts = lazy(() => textValues(slide, path));
3219
+ const rtl = lazy(() => {
3220
+ try {
3221
+ return resolveScriptFonts(document, { slideIndex: index }).rtl === true;
3222
+ } catch {
3223
+ return false;
3224
+ }
3225
+ });
3226
+ const context = { index, slide, path, ...typeof slide.id === "string" ? { id: slide.id } : {} };
3227
+ return Object.defineProperties(context, {
3228
+ design: { get: design, enumerable: true },
3229
+ layout: { get: layout, enumerable: true },
3230
+ composition: { get: () => composed().composition, enumerable: true },
3231
+ layoutDiagnostics: { get: () => composed().layoutDiagnostics, enumerable: true },
3232
+ layoutError: { get: () => composed().layoutError, enumerable: true },
3233
+ measurement: { get: measurement, enumerable: true },
3234
+ payloads: { get: payloads, enumerable: true },
3235
+ texts: { get: texts, enumerable: true },
3236
+ rtl: { get: rtl, enumerable: true }
3237
+ });
3238
+ });
3239
+ }
3240
+
3241
+ // src/validator.ts
3242
+ var SEVERITIES = ["error", "warning", "info"];
3243
+ var OPTION_KEYS = ["only", "ignore", "severity", "ignorePaths", "catalogs", "contracts", "thresholds", "fonts", "chartPalette", "template", "values"];
3244
+ var DOCS = "https://github.com/OpenPresentation/opf/blob/main/docs/validate.md";
3245
+ var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3246
+ var OPFValidationError = class extends Error {
3247
+ /** The full report that failed. */
3248
+ report;
3249
+ /** The error-severity findings of the report. */
3250
+ findings;
3251
+ constructor(report) {
3252
+ const errors = report.findings.filter((entry) => entry.severity === "error");
3253
+ const first = errors[0];
3254
+ super(first ? `OPF validation failed at ${first.path || "/"}: ${first.message}` : "OPF validation failed");
3255
+ this.name = "OPFValidationError";
3256
+ this.report = report;
3257
+ this.findings = errors;
3258
+ }
3259
+ };
3260
+ function ruleFor(name, what) {
3261
+ if (validationCategories.includes(name)) return name;
3262
+ const found = findValidationRule(name);
3263
+ if (!found) throw new TypeError(`Unknown validation rule or category ${JSON.stringify(name)} in ${what}. Categories: ${validationCategories.join(", ")}. Rules: ${validationRules.map((info) => info.id).join(", ")}.`);
3264
+ return found;
3265
+ }
3266
+ function names(list, what) {
3267
+ if (list === void 0) return void 0;
3268
+ if (!Array.isArray(list) || !list.every((entry) => typeof entry === "string")) throw new TypeError(`Validate ${what} must be an array of rule ids or category names.`);
3269
+ const result = { rules: /* @__PURE__ */ new Set(), categories: /* @__PURE__ */ new Set() };
3270
+ for (const name of list) {
3271
+ const found = ruleFor(name, what);
3272
+ if (typeof found === "string") result.categories.add(found);
3273
+ else result.rules.add(found.id);
3274
+ }
3275
+ return result;
3276
+ }
3277
+ function resolveOptions(options) {
3278
+ if (!isObject(options)) throw new TypeError("Validate options must be an object.");
3279
+ for (const key of Object.keys(options)) if (!OPTION_KEYS.includes(key)) throw new TypeError(`Unknown validate option ${JSON.stringify(key)}. Options: ${OPTION_KEYS.join(", ")}.`);
3280
+ checkDocumentOptions(options);
3281
+ const only = names(options.only, "only");
3282
+ const ignore = names(options.ignore, "ignore");
3283
+ const severity = /* @__PURE__ */ new Map();
3284
+ if (options.severity !== void 0) {
3285
+ if (!isObject(options.severity)) throw new TypeError('Validate severity must map rule ids or category names to a severity or "off".');
3286
+ for (const [name, value] of Object.entries(options.severity)) {
3287
+ if (typeof value !== "string" || value !== "off" && !SEVERITIES.includes(value)) throw new TypeError(`Validate severity for ${JSON.stringify(name)} must be error, warning, info or off.`);
3288
+ const found = ruleFor(name, "severity");
3289
+ severity.set(typeof found === "string" ? found : found.id, value);
3290
+ }
3291
+ }
3292
+ const ignorePaths = [];
3293
+ if (options.ignorePaths !== void 0) {
3294
+ if (!Array.isArray(options.ignorePaths)) throw new TypeError("Validate ignorePaths must be an array of {rule, path}.");
3295
+ for (const entry of options.ignorePaths) {
3296
+ if (!isObject(entry) || typeof entry.rule !== "string" || typeof entry.path !== "string" || entry.path !== "" && !entry.path.startsWith("/"))
3297
+ throw new TypeError('Each validate ignorePaths entry needs a rule id, category name or "*", and a JSON Pointer path.');
3298
+ const found = entry.rule === "*" ? "*" : ruleFor(entry.rule, "ignorePaths");
3299
+ ignorePaths.push({
3300
+ match: (info) => found === "*" || (typeof found === "string" ? found === info.category : found.id === info.id),
3301
+ path: splitPointer(entry.path)
3302
+ });
3303
+ }
3304
+ }
3305
+ const thresholds = { ...DEFAULT_VALIDATION_THRESHOLDS };
3306
+ if (options.thresholds !== void 0) {
3307
+ if (!isObject(options.thresholds)) throw new TypeError("Validate thresholds must be an object.");
3308
+ for (const [key, value] of Object.entries(options.thresholds)) {
3309
+ if (!(key in DEFAULT_VALIDATION_THRESHOLDS)) throw new TypeError(`Unknown validate threshold ${JSON.stringify(key)}. Thresholds: ${Object.keys(DEFAULT_VALIDATION_THRESHOLDS).join(", ")}.`);
3310
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) throw new TypeError(`Validate threshold ${key} must be a positive number.`);
3311
+ thresholds[key] = value;
3312
+ }
3313
+ }
3314
+ if (options.fonts !== void 0) {
3315
+ if (!isObject(options.fonts)) throw new TypeError("Validate fonts must be a fonts object with a textMeasurement.");
3316
+ const measurement = options.fonts.textMeasurement;
3317
+ if (measurement !== void 0 && typeof measurement !== "function" && !isObject(measurement)) throw new TypeError("Validate fonts.textMeasurement must be a text measurement, or a function of the slide index.");
3318
+ }
3319
+ if (options.chartPalette !== void 0 && (!Array.isArray(options.chartPalette) || !options.chartPalette.every((color) => typeof color === "string" && /^#[0-9a-fA-F]{6}$/.test(color))))
3320
+ throw new TypeError("Validate chartPalette must be an array of #RRGGBB colours.");
3321
+ if (options.template !== void 0 && typeof options.template !== "boolean") throw new TypeError("Validate template must be true or false.");
3322
+ if (options.values !== void 0 && !isObject(options.values)) throw new TypeError("Validate values must be an object keyed by variable id.");
3323
+ const level = (info) => severity.get(info.id) ?? severity.get(info.category);
3324
+ const off = (info) => level(info) === "off" || !!ignore?.rules.has(info.id) || !!ignore?.categories.has(info.category);
3325
+ return {
3326
+ options,
3327
+ off,
3328
+ enabled: (info) => !off(info) && (!only || only.rules.has(info.id) || only.categories.has(info.category)),
3329
+ severity: (info) => {
3330
+ const value = level(info);
3331
+ return value === "off" ? void 0 : value;
3332
+ },
3333
+ ignored: (info, path) => ignorePaths.some((entry) => entry.match(info) && entry.path.every((part, index) => path[index] === part)),
3334
+ thresholds,
3335
+ chartPalette: options.chartPalette ?? DEFAULT_CHART_PALETTE
3336
+ };
3337
+ }
3338
+ var order = new Map(validationRules.map((info, index) => [info.id, index]));
3339
+ var categoryOrder = (category) => {
3340
+ const index = validationCategories.indexOf(category);
3341
+ return index < 0 ? validationCategories.length : index;
3342
+ };
3343
+ var auditInfos = ruleImplementations.flatMap((entry) => [entry.info, ...entry.also ?? []]);
3344
+ var isLayoutRule = (info) => info.category === "layout" && info.cost === "composition";
3345
+ function slideOf(path) {
3346
+ const match = /^\/slides\/(\d+)(?:\/|$)/.exec(path);
3347
+ return match ? Number(match[1]) : void 0;
3348
+ }
3349
+ function counts(findings) {
3350
+ const result = { error: 0, warning: 0, info: 0 };
3351
+ for (const entry of findings) result[entry.severity]++;
3352
+ return result;
3353
+ }
3354
+ var FIXED = { backgroundPixels: "not-read", imageBytes: "embedded-only", nativeExport: "not-checked" };
3355
+ function syntaxReport(parsed, run) {
3356
+ const findings = [];
3357
+ for (const entry of parsed.syntax) {
3358
+ const info = findValidationRule(entry.ruleId);
3359
+ if (info && run.off(info)) continue;
3360
+ const severity = info ? run.severity(info) : void 0;
3361
+ findings.push(severity ? { ...entry, severity } : entry);
3362
+ }
3363
+ const checks = {
3364
+ syntax: "checked",
3365
+ schema: "not-run",
3366
+ references: "not-run",
3367
+ policy: "not-run",
3368
+ accessibility: "not-run",
3369
+ content: "not-run",
3370
+ layout: "not-run",
3371
+ ...FIXED
3372
+ };
3373
+ const tally = counts(findings);
3374
+ return { valid: tally.error === 0, schemaValid: null, findings, counts: tally, checks };
3375
+ }
3376
+ function validateDocument(document, run, parsed) {
3377
+ const engine = validateAgainstSchema(document, "presentation", { template: run.options.template, values: run.options.values, ...run.options.catalogs ? { catalogs: run.options.catalogs } : {} });
3378
+ const schemaValid = engine.errors.length === 0;
3379
+ const slides = isObject(document) && Array.isArray(document.slides) ? document.slides : [];
3380
+ const auditWanted = auditInfos.some((info) => run.enabled(info));
3381
+ const explainFormat = !schemaValid && auditWanted;
3382
+ const enabledRule = (info) => info ? run.enabled(info) || explainFormat && info.category === "format" && !run.off(info) : true;
3383
+ const findings = [];
3384
+ findings.push(...engineFindings(document, engine, (ruleId) => enabledRule(findValidationRule(ruleId))));
3385
+ if (enabledRule(findValidationRule("opf/variable-unfilled"))) findings.push(...variableFindings(document, engine, schemaValid));
3386
+ if (enabledRule(findValidationRule("opf/undeclared-catalog"))) findings.push(...undeclaredCatalogFindings(document));
3387
+ if (parsed) {
3388
+ findings.push(...parsed.duplicates.filter((entry) => enabledRule(findValidationRule(entry.ruleId))));
3389
+ }
3390
+ const referencesWanted = validationRules.some((info) => info.category === "references" && run.enabled(info));
3391
+ if (referencesWanted) {
3392
+ findings.push(...referenceFindings(document, engine, run.options).filter((entry) => enabledRule(findValidationRule(entry.ruleId))));
3393
+ }
3394
+ const contracts = run.options.contracts ?? [];
3395
+ const contractRule = findValidationRule("opf/contract");
3396
+ const policyRan = contracts.length > 0 && !!contractRule && enabledRule(contractRule);
3397
+ if (policyRan && contractRule) {
3398
+ findings.push(...contractFindings(document, contracts));
3399
+ }
3400
+ const auditRan = /* @__PURE__ */ new Set();
3401
+ if (schemaValid && auditWanted) {
3402
+ const doc = document;
3403
+ const context = {
3404
+ document: doc,
3405
+ slides: buildSlides(doc, run.options),
3406
+ thresholds: run.thresholds,
3407
+ options: run.options,
3408
+ chartPalette: run.chartPalette,
3409
+ report(info, input) {
3410
+ const severity = run.severity(info) ?? input.severity ?? info.severity;
3411
+ const entry = {
3412
+ ruleId: info.id,
3413
+ severity,
3414
+ category: info.category,
3415
+ scope: "document",
3416
+ path: input.path,
3417
+ message: input.message,
3418
+ help: input.help,
3419
+ definition: `${DOCS}#${info.id.replace("/", "")}`,
3420
+ ...input.slide ? { slide: input.slide.index, ...input.slide.id ? { slideId: input.slide.id } : {} } : {},
3421
+ ...input.measured ? { measured: input.measured } : {},
3422
+ ...input.fixes?.length ? { fixes: input.fixes } : {},
3423
+ ...input.suggestions ? { suggestions: input.suggestions } : {}
3424
+ };
3425
+ if (run.enabled(info)) findings.push(entry);
3426
+ return entry;
3427
+ }
3428
+ };
3429
+ for (const entry of ruleImplementations) {
3430
+ const all = [entry.info, ...entry.also ?? []];
3431
+ if (!all.some((info) => run.enabled(info))) continue;
3432
+ for (const info of all) if (run.enabled(info)) auditRan.add(info.id);
3433
+ entry.run(context);
3434
+ }
3435
+ }
3436
+ const finished = [];
3437
+ for (const entry of findings) {
3438
+ const info = findValidationRule(entry.ruleId);
3439
+ const override = info ? run.severity(info) : void 0;
3440
+ const next = override && override !== entry.severity ? { ...entry, severity: override } : entry;
3441
+ if (info && run.ignored(info, splitPointer(next.path))) continue;
3442
+ const index = slideOf(next.path);
3443
+ if (next.slide === void 0 && index !== void 0 && next.scope !== "context") {
3444
+ const id = isObject(slides[index]) ? slides[index].id : void 0;
3445
+ next.slide = index;
3446
+ if (typeof id === "string") next.slideId = id;
3447
+ }
3448
+ if (parsed && next.location === void 0 && next.scope !== "context") {
3449
+ const location = parsed.locate(next.path);
3450
+ if (location) next.location = location;
3451
+ }
3452
+ finished.push(next);
3453
+ }
3454
+ finished.sort(
3455
+ (a, b) => (a.slide ?? -1) - (b.slide ?? -1) || categoryOrder(a.category) - categoryOrder(b.category) || (order.get(a.ruleId) ?? Number.MAX_SAFE_INTEGER) - (order.get(b.ruleId) ?? Number.MAX_SAFE_INTEGER)
3456
+ );
3457
+ const tally = counts(finished);
3458
+ const ranCategory = (category) => auditInfos.some((info) => info.category === category && auditRan.has(info.id));
3459
+ const layoutRan = auditInfos.some((info) => isLayoutRule(info) && auditRan.has(info.id));
3460
+ const checks = {
3461
+ syntax: parsed ? "checked" : "not-applicable",
3462
+ schema: "checked",
3463
+ references: referencesWanted ? "checked" : "not-run",
3464
+ policy: policyRan ? "checked" : "not-run",
3465
+ accessibility: ranCategory("accessibility") ? "checked" : "not-run",
3466
+ content: ranCategory("content") ? "checked" : "not-run",
3467
+ layout: layoutRan || ranCategory("layout") ? run.options.fonts?.textMeasurement !== void 0 ? "measured" : "estimated" : "not-run",
3468
+ ...FIXED
3469
+ };
3470
+ return {
3471
+ valid: tally.error === 0,
3472
+ schemaValid,
3473
+ findings: finished,
3474
+ counts: tally,
3475
+ checks,
3476
+ ...engine.template !== void 0 ? { template: engine.template, unfilledVariables: engine.unfilledVariables } : {}
3477
+ };
3478
+ }
3479
+ function validate(input, options = {}) {
3480
+ const run = resolveOptions(options);
3481
+ if (typeof input === "string") {
3482
+ const parsed = parseSource(input);
3483
+ return parsed.syntax.length ? syntaxReport(parsed, run) : validateDocument(parsed.value, run, parsed);
3484
+ }
3485
+ return validateDocument(input, run);
3486
+ }
3487
+ function assertValid(value, options = {}) {
3488
+ if (typeof value === "string") throw new TypeError("assertValid takes a parsed presentation. Use validate(text) for JSON text.");
3489
+ const report = validate(value, options);
3490
+ if (!report.valid) throw new OPFValidationError(report);
3491
+ }
3492
+ function validateCatalogRecord(kind, value) {
3493
+ const kinds = Object.keys(catalogSchemaNames);
3494
+ if (!kinds.includes(kind)) throw new TypeError(`Unknown catalog kind ${JSON.stringify(kind)}. Kinds: ${kinds.join(", ")}.`);
3495
+ const engine = validateAgainstSchema(value, kind);
3496
+ const findings = engine.errors.map((issue2) => issueFinding(issue2, issueRuleId(issue2), engine.schemaName));
3497
+ const tally = counts(findings);
3498
+ const checks = {
3499
+ syntax: "not-applicable",
3500
+ schema: "checked",
3501
+ references: "not-run",
3502
+ policy: "not-run",
3503
+ accessibility: "not-run",
3504
+ content: "not-run",
3505
+ layout: "not-run",
3506
+ ...FIXED
3507
+ };
3508
+ return { valid: tally.error === 0, schemaValid: engine.valid, findings, counts: tally, checks };
3509
+ }
3510
+ function assertValidCatalogRecord(kind, value) {
3511
+ const report = validateCatalogRecord(kind, value);
3512
+ if (!report.valid) throw new OPFValidationError(report);
3513
+ }
3514
+
3515
+ export { DEFAULT_CHART_PALETTE, DEFAULT_VALIDATION_THRESHOLDS, OPFValidationError, assertValid, assertValidCatalogRecord, findValidationRule, resolveSlideContext, validate, validateCatalogRecord, validationCategories, validationRules };