postext 1.33.11 → 1.34.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 (303) hide show
  1. package/README.md +2 -2
  2. package/dist/__tests__/bundle/designOverflowPin.test.js +1 -1
  3. package/dist/__tests__/bundle/gridBreakRulesPin.test.js +2 -2
  4. package/dist/__tests__/bundle/shapingLanguagePin.test.js +3 -3
  5. package/dist/__tests__/bundle/taskBoxPin.test.js +1 -1
  6. package/dist/__tests__/bundle.test.js +34 -2
  7. package/dist/__tests__/bundle.test.js.map +1 -1
  8. package/dist/__tests__/codeBlocks.test.js +89 -1
  9. package/dist/__tests__/codeBlocks.test.js.map +1 -1
  10. package/dist/__tests__/composed/corpus.test.d.ts +2 -0
  11. package/dist/__tests__/composed/corpus.test.d.ts.map +1 -0
  12. package/dist/__tests__/composed/corpus.test.js +33 -0
  13. package/dist/__tests__/composed/corpus.test.js.map +1 -0
  14. package/dist/__tests__/composed/fixtures/page8.d.ts +20 -0
  15. package/dist/__tests__/composed/fixtures/page8.d.ts.map +1 -0
  16. package/dist/__tests__/composed/fixtures/page8.js +159 -0
  17. package/dist/__tests__/composed/fixtures/page8.js.map +1 -0
  18. package/dist/__tests__/composed/layout.test.d.ts +2 -0
  19. package/dist/__tests__/composed/layout.test.d.ts.map +1 -0
  20. package/dist/__tests__/composed/layout.test.js +387 -0
  21. package/dist/__tests__/composed/layout.test.js.map +1 -0
  22. package/dist/__tests__/composed/slotChecks.test.d.ts +2 -0
  23. package/dist/__tests__/composed/slotChecks.test.d.ts.map +1 -0
  24. package/dist/__tests__/composed/slotChecks.test.js +151 -0
  25. package/dist/__tests__/composed/slotChecks.test.js.map +1 -0
  26. package/dist/__tests__/composedPaint.test.d.ts +2 -0
  27. package/dist/__tests__/composedPaint.test.d.ts.map +1 -0
  28. package/dist/__tests__/composedPaint.test.js +191 -0
  29. package/dist/__tests__/composedPaint.test.js.map +1 -0
  30. package/dist/__tests__/composedTemplates.test.d.ts +2 -0
  31. package/dist/__tests__/composedTemplates.test.d.ts.map +1 -0
  32. package/dist/__tests__/composedTemplates.test.js +349 -0
  33. package/dist/__tests__/composedTemplates.test.js.map +1 -0
  34. package/dist/__tests__/contentInConfig.test.d.ts +2 -0
  35. package/dist/__tests__/contentInConfig.test.d.ts.map +1 -0
  36. package/dist/__tests__/contentInConfig.test.js +201 -0
  37. package/dist/__tests__/contentInConfig.test.js.map +1 -0
  38. package/dist/__tests__/defaults/stripRoundTrip.test.js +6 -3
  39. package/dist/__tests__/defaults/stripRoundTrip.test.js.map +1 -1
  40. package/dist/__tests__/exports.test.js +34 -0
  41. package/dist/__tests__/exports.test.js.map +1 -1
  42. package/dist/__tests__/hardBreaks.test.js +1 -1
  43. package/dist/__tests__/headingInlineMarks.test.js +1 -1
  44. package/dist/__tests__/highlightMarks.test.d.ts +2 -0
  45. package/dist/__tests__/highlightMarks.test.d.ts.map +1 -0
  46. package/dist/__tests__/highlightMarks.test.js +169 -0
  47. package/dist/__tests__/highlightMarks.test.js.map +1 -0
  48. package/dist/__tests__/mathSizeMigration.test.js +2 -2
  49. package/dist/__tests__/parse/composedPages.test.d.ts +2 -0
  50. package/dist/__tests__/parse/composedPages.test.d.ts.map +1 -0
  51. package/dist/__tests__/parse/composedPages.test.js +323 -0
  52. package/dist/__tests__/parse/composedPages.test.js.map +1 -0
  53. package/dist/__tests__/parse/containers.test.js +2 -2
  54. package/dist/__tests__/parse/containers.test.js.map +1 -1
  55. package/dist/__tests__/pipeline/calloutInlineResourceGap.test.js +1 -1
  56. package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.js +1 -1
  57. package/dist/__tests__/pipeline/colonListRoom.test.js +1 -1
  58. package/dist/__tests__/pipeline/compoundBreaks.test.js +1 -1
  59. package/dist/__tests__/pipeline/dashBreaks.test.js +1 -1
  60. package/dist/__tests__/pipeline/headingSplitOrphans.test.js +1 -1
  61. package/dist/__tests__/pipeline/inlineFigureSpace.test.js +1 -1
  62. package/dist/__tests__/pipeline/paragraphContainerTail.test.js +1 -1
  63. package/dist/__tests__/pipeline/raggedOptimal.test.js +1 -1
  64. package/dist/__tests__/tocGroups.test.d.ts +2 -0
  65. package/dist/__tests__/tocGroups.test.d.ts.map +1 -0
  66. package/dist/__tests__/tocGroups.test.js +213 -0
  67. package/dist/__tests__/tocGroups.test.js.map +1 -0
  68. package/dist/__tests__/tocPartLeader.test.d.ts +2 -0
  69. package/dist/__tests__/tocPartLeader.test.d.ts.map +1 -0
  70. package/dist/__tests__/tocPartLeader.test.js +133 -0
  71. package/dist/__tests__/tocPartLeader.test.js.map +1 -0
  72. package/dist/__tests__/verseLines.test.js +1 -1
  73. package/dist/bundle/api.d.ts +8 -0
  74. package/dist/bundle/api.d.ts.map +1 -1
  75. package/dist/bundle/api.js +6 -3
  76. package/dist/bundle/api.js.map +1 -1
  77. package/dist/bundle/codec.d.ts +5 -0
  78. package/dist/bundle/codec.d.ts.map +1 -1
  79. package/dist/bundle/codec.js +4 -0
  80. package/dist/bundle/codec.js.map +1 -1
  81. package/dist/bundle/configVersion.d.ts +16 -5
  82. package/dist/bundle/configVersion.d.ts.map +1 -1
  83. package/dist/bundle/configVersion.js +17 -4
  84. package/dist/bundle/configVersion.js.map +1 -1
  85. package/dist/bundle/index.d.ts +2 -2
  86. package/dist/bundle/index.d.ts.map +1 -1
  87. package/dist/bundle/index.js +1 -1
  88. package/dist/bundle/index.js.map +1 -1
  89. package/dist/bundle/manifest.d.ts +14 -0
  90. package/dist/bundle/manifest.d.ts.map +1 -1
  91. package/dist/bundle/manifest.js +13 -0
  92. package/dist/bundle/manifest.js.map +1 -1
  93. package/dist/bundle/types.d.ts +11 -3
  94. package/dist/bundle/types.d.ts.map +1 -1
  95. package/dist/canvas-backend/annotations.d.ts +6 -0
  96. package/dist/canvas-backend/annotations.d.ts.map +1 -1
  97. package/dist/canvas-backend/annotations.js +39 -0
  98. package/dist/canvas-backend/annotations.js.map +1 -1
  99. package/dist/canvas-backend/blockRender.d.ts.map +1 -1
  100. package/dist/canvas-backend/blockRender.js +6 -1
  101. package/dist/canvas-backend/blockRender.js.map +1 -1
  102. package/dist/canvas-backend/composed.d.ts +15 -0
  103. package/dist/canvas-backend/composed.d.ts.map +1 -0
  104. package/dist/canvas-backend/composed.js +41 -0
  105. package/dist/canvas-backend/composed.js.map +1 -0
  106. package/dist/canvas-backend/index.d.ts.map +1 -1
  107. package/dist/canvas-backend/index.js +15 -1
  108. package/dist/canvas-backend/index.js.map +1 -1
  109. package/dist/canvas-backend/renderResourceBlock.d.ts.map +1 -1
  110. package/dist/canvas-backend/renderResourceBlock.js +4 -1
  111. package/dist/canvas-backend/renderResourceBlock.js.map +1 -1
  112. package/dist/composed/document.d.ts +43 -0
  113. package/dist/composed/document.d.ts.map +1 -0
  114. package/dist/composed/document.js +88 -0
  115. package/dist/composed/document.js.map +1 -0
  116. package/dist/composed/geometry.d.ts +108 -0
  117. package/dist/composed/geometry.d.ts.map +1 -0
  118. package/dist/composed/geometry.js +241 -0
  119. package/dist/composed/geometry.js.map +1 -0
  120. package/dist/composed/paint.d.ts +41 -0
  121. package/dist/composed/paint.d.ts.map +1 -0
  122. package/dist/composed/paint.js +83 -0
  123. package/dist/composed/paint.js.map +1 -0
  124. package/dist/composed/slotChecks.d.ts +29 -0
  125. package/dist/composed/slotChecks.d.ts.map +1 -0
  126. package/dist/composed/slotChecks.js +109 -0
  127. package/dist/composed/slotChecks.js.map +1 -0
  128. package/dist/composed/templates.d.ts +133 -0
  129. package/dist/composed/templates.d.ts.map +1 -0
  130. package/dist/composed/templates.js +702 -0
  131. package/dist/composed/templates.js.map +1 -0
  132. package/dist/composed/types.d.ts +361 -0
  133. package/dist/composed/types.d.ts.map +1 -0
  134. package/dist/composed/types.js +2 -0
  135. package/dist/composed/types.js.map +1 -0
  136. package/dist/composed/validate.d.ts +26 -0
  137. package/dist/composed/validate.d.ts.map +1 -0
  138. package/dist/composed/validate.js +276 -0
  139. package/dist/composed/validate.js.map +1 -0
  140. package/dist/composed/wrap.d.ts +52 -0
  141. package/dist/composed/wrap.d.ts.map +1 -0
  142. package/dist/composed/wrap.js +234 -0
  143. package/dist/composed/wrap.js.map +1 -0
  144. package/dist/configWarnings.d.ts +19 -3
  145. package/dist/configWarnings.d.ts.map +1 -1
  146. package/dist/configWarnings.js +171 -41
  147. package/dist/configWarnings.js.map +1 -1
  148. package/dist/defaults/checks.d.ts +7 -0
  149. package/dist/defaults/checks.d.ts.map +1 -0
  150. package/dist/defaults/checks.js +49 -0
  151. package/dist/defaults/checks.js.map +1 -0
  152. package/dist/defaults/codeStyle.d.ts.map +1 -1
  153. package/dist/defaults/codeStyle.js +1 -0
  154. package/dist/defaults/codeStyle.js.map +1 -1
  155. package/dist/defaults/index.d.ts +2 -0
  156. package/dist/defaults/index.d.ts.map +1 -1
  157. package/dist/defaults/index.js +35 -0
  158. package/dist/defaults/index.js.map +1 -1
  159. package/dist/defaults/markStyles.d.ts +23 -0
  160. package/dist/defaults/markStyles.d.ts.map +1 -0
  161. package/dist/defaults/markStyles.js +80 -0
  162. package/dist/defaults/markStyles.js.map +1 -0
  163. package/dist/defaults/shared.d.ts.map +1 -1
  164. package/dist/defaults/shared.js +12 -0
  165. package/dist/defaults/shared.js.map +1 -1
  166. package/dist/defaults/toc.d.ts +17 -2
  167. package/dist/defaults/toc.d.ts.map +1 -1
  168. package/dist/defaults/toc.js +53 -1
  169. package/dist/defaults/toc.js.map +1 -1
  170. package/dist/design/bidiText.d.ts +4 -1
  171. package/dist/design/bidiText.d.ts.map +1 -1
  172. package/dist/design/bidiText.js +5 -2
  173. package/dist/design/bidiText.js.map +1 -1
  174. package/dist/design/layout.d.ts +10 -0
  175. package/dist/design/layout.d.ts.map +1 -1
  176. package/dist/design/layout.js +110 -8
  177. package/dist/design/layout.js.map +1 -1
  178. package/dist/design/placeholders.d.ts +28 -2
  179. package/dist/design/placeholders.d.ts.map +1 -1
  180. package/dist/design/placeholders.js +24 -1
  181. package/dist/design/placeholders.js.map +1 -1
  182. package/dist/design/richText.d.ts +2 -0
  183. package/dist/design/richText.d.ts.map +1 -1
  184. package/dist/design/richText.js.map +1 -1
  185. package/dist/design/slots.d.ts +29 -0
  186. package/dist/design/slots.d.ts.map +1 -0
  187. package/dist/design/slots.js +48 -0
  188. package/dist/design/slots.js.map +1 -0
  189. package/dist/highlightMarks.d.ts +33 -0
  190. package/dist/highlightMarks.d.ts.map +1 -0
  191. package/dist/highlightMarks.js +209 -0
  192. package/dist/highlightMarks.js.map +1 -0
  193. package/dist/html-backend.d.ts.map +1 -1
  194. package/dist/html-backend.js +65 -8
  195. package/dist/html-backend.js.map +1 -1
  196. package/dist/htmlAnnotations.d.ts +5 -0
  197. package/dist/htmlAnnotations.d.ts.map +1 -1
  198. package/dist/htmlAnnotations.js +16 -0
  199. package/dist/htmlAnnotations.js.map +1 -1
  200. package/dist/index.d.ts +21 -5
  201. package/dist/index.d.ts.map +1 -1
  202. package/dist/index.js +14 -3
  203. package/dist/index.js.map +1 -1
  204. package/dist/knuthPlass/richAdapter.d.ts +3 -0
  205. package/dist/knuthPlass/richAdapter.d.ts.map +1 -1
  206. package/dist/knuthPlass/richAdapter.js +3 -2
  207. package/dist/knuthPlass/richAdapter.js.map +1 -1
  208. package/dist/measure/cache.js +3 -3
  209. package/dist/measure/cache.js.map +1 -1
  210. package/dist/measure/chipEdges.d.ts +13 -1
  211. package/dist/measure/chipEdges.d.ts.map +1 -1
  212. package/dist/measure/chipEdges.js +59 -1
  213. package/dist/measure/chipEdges.js.map +1 -1
  214. package/dist/measure/links.d.ts.map +1 -1
  215. package/dist/measure/links.js +78 -5
  216. package/dist/measure/links.js.map +1 -1
  217. package/dist/measure/rich.d.ts +3 -0
  218. package/dist/measure/rich.d.ts.map +1 -1
  219. package/dist/measure/rich.js +19 -4
  220. package/dist/measure/rich.js.map +1 -1
  221. package/dist/parse/blockParser.d.ts.map +1 -1
  222. package/dist/parse/blockParser.js +295 -36
  223. package/dist/parse/blockParser.js.map +1 -1
  224. package/dist/parse/index.d.ts +1 -1
  225. package/dist/parse/index.d.ts.map +1 -1
  226. package/dist/parse/index.js.map +1 -1
  227. package/dist/parse/indexMarks.d.ts.map +1 -1
  228. package/dist/parse/indexMarks.js +24 -0
  229. package/dist/parse/indexMarks.js.map +1 -1
  230. package/dist/parse/inlineFormatting.d.ts +14 -0
  231. package/dist/parse/inlineFormatting.d.ts.map +1 -1
  232. package/dist/parse/inlineFormatting.js +104 -5
  233. package/dist/parse/inlineFormatting.js.map +1 -1
  234. package/dist/parse/links.js +1 -1
  235. package/dist/parse/links.js.map +1 -1
  236. package/dist/parse/softBreaks.js +1 -1
  237. package/dist/parse/softBreaks.js.map +1 -1
  238. package/dist/parse/sourceMapping.d.ts.map +1 -1
  239. package/dist/parse/sourceMapping.js +19 -3
  240. package/dist/parse/sourceMapping.js.map +1 -1
  241. package/dist/parse/types.d.ts +119 -2
  242. package/dist/parse/types.d.ts.map +1 -1
  243. package/dist/pipeline/build.d.ts.map +1 -1
  244. package/dist/pipeline/build.js +318 -287
  245. package/dist/pipeline/build.js.map +1 -1
  246. package/dist/pipeline/calloutLayout.d.ts +33 -0
  247. package/dist/pipeline/calloutLayout.d.ts.map +1 -1
  248. package/dist/pipeline/calloutLayout.js +91 -2
  249. package/dist/pipeline/calloutLayout.js.map +1 -1
  250. package/dist/pipeline/codeInline.d.ts +10 -1
  251. package/dist/pipeline/codeInline.d.ts.map +1 -1
  252. package/dist/pipeline/codeInline.js +96 -15
  253. package/dist/pipeline/codeInline.js.map +1 -1
  254. package/dist/pipeline/composedLayout.d.ts +88 -0
  255. package/dist/pipeline/composedLayout.d.ts.map +1 -0
  256. package/dist/pipeline/composedLayout.js +320 -0
  257. package/dist/pipeline/composedLayout.js.map +1 -0
  258. package/dist/pipeline/config.d.ts.map +1 -1
  259. package/dist/pipeline/config.js +18 -2
  260. package/dist/pipeline/config.js.map +1 -1
  261. package/dist/pipeline/contentWarnings.d.ts.map +1 -1
  262. package/dist/pipeline/contentWarnings.js +105 -1
  263. package/dist/pipeline/contentWarnings.js.map +1 -1
  264. package/dist/pipeline/floatSlots.js +1 -1
  265. package/dist/pipeline/floatSlots.js.map +1 -1
  266. package/dist/pipeline/frameLayout.d.ts +120 -0
  267. package/dist/pipeline/frameLayout.d.ts.map +1 -0
  268. package/dist/pipeline/frameLayout.js +279 -0
  269. package/dist/pipeline/frameLayout.js.map +1 -0
  270. package/dist/pipeline/headerFooter.d.ts.map +1 -1
  271. package/dist/pipeline/headerFooter.js +48 -3
  272. package/dist/pipeline/headerFooter.js.map +1 -1
  273. package/dist/pipeline/pageRoles.d.ts +1 -0
  274. package/dist/pipeline/pageRoles.d.ts.map +1 -1
  275. package/dist/pipeline/pageRoles.js +3 -0
  276. package/dist/pipeline/pageRoles.js.map +1 -1
  277. package/dist/pipeline/placeholders.d.ts +7 -0
  278. package/dist/pipeline/placeholders.d.ts.map +1 -1
  279. package/dist/pipeline/placeholders.js +41 -0
  280. package/dist/pipeline/placeholders.js.map +1 -1
  281. package/dist/pipeline/placement.js +1 -1
  282. package/dist/pipeline/placement.js.map +1 -1
  283. package/dist/pipeline/resourceLayout.d.ts.map +1 -1
  284. package/dist/pipeline/resourceLayout.js +5 -3
  285. package/dist/pipeline/resourceLayout.js.map +1 -1
  286. package/dist/pipeline/tabs.js +1 -1
  287. package/dist/pipeline/tabs.js.map +1 -1
  288. package/dist/pipeline/textWrap.js +1 -1
  289. package/dist/pipeline/textWrap.js.map +1 -1
  290. package/dist/pipeline/toc.d.ts +11 -1
  291. package/dist/pipeline/toc.d.ts.map +1 -1
  292. package/dist/pipeline/toc.js +147 -8
  293. package/dist/pipeline/toc.js.map +1 -1
  294. package/dist/types.d.ts +168 -3
  295. package/dist/types.d.ts.map +1 -1
  296. package/dist/util/closestKey.d.ts +4 -0
  297. package/dist/util/closestKey.d.ts.map +1 -0
  298. package/dist/util/closestKey.js +40 -0
  299. package/dist/util/closestKey.js.map +1 -0
  300. package/dist/vdt.d.ts +293 -7
  301. package/dist/vdt.d.ts.map +1 -1
  302. package/dist/vdt.js.map +1 -1
  303. package/package.json +1 -1
@@ -0,0 +1,702 @@
1
+ // Page templates (composed pages, #853): defaults, `extends` and variant
2
+ // resolution, and the normalisation of frames, groups and label styles.
3
+ // Pure; nothing here lays anything out (that is P4, #855).
4
+ // ---------------------------------------------------------------------------
5
+ // Defaults
6
+ // ---------------------------------------------------------------------------
7
+ const ZERO = { value: 0, unit: 'mm' };
8
+ export const DEFAULT_FRAME_FIT = {
9
+ shrinkText: true,
10
+ minScale: 0.9,
11
+ scaleImages: true,
12
+ imageMinScale: 0.8,
13
+ };
14
+ /** What a frame that sets nothing comes to (besides its name). */
15
+ export const DEFAULT_FRAME = {
16
+ width: 'fill',
17
+ height: 'auto',
18
+ layer: 1,
19
+ rotate: 0,
20
+ verticalAlign: 'top',
21
+ columns: 1,
22
+ exclude: 'none',
23
+ flow: false,
24
+ pin: false,
25
+ grow: 0,
26
+ shrink: 0,
27
+ priority: 0,
28
+ optional: false,
29
+ crossGutter: false,
30
+ };
31
+ export const DEFAULT_FRAME_GROUP = {
32
+ kind: 'stack',
33
+ gap: ZERO,
34
+ align: 'start',
35
+ };
36
+ export const DEFAULT_PAGE_TEMPLATE = {
37
+ area: 'content',
38
+ spread: false,
39
+ gutter: ZERO,
40
+ role: 'composed',
41
+ };
42
+ export const DEFAULT_LABEL_STYLE = {
43
+ fontStyle: 'normal',
44
+ textTransform: 'none',
45
+ minScale: 0.85,
46
+ };
47
+ export const FRAME_VERTICAL_ALIGNS = ['top', 'middle', 'bottom', 'justify'];
48
+ export const FRAME_EXCLUDES = ['none', 'push'];
49
+ export const FRAME_TAKES = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
50
+ export const FRAME_GROUP_KINDS = ['stack', 'row'];
51
+ export const FRAME_GROUP_ALIGNS = ['start', 'center', 'end', 'space-between'];
52
+ export const TEMPLATE_AREAS = ['content', 'trim', 'bleed'];
53
+ export const FRAME_WRITING_MODES = ['horizontal-tb', 'vertical-rl'];
54
+ export const FRAME_DIRECTIONS = ['ltr', 'rtl'];
55
+ export const LABEL_FONT_STYLES = ['normal', 'italic'];
56
+ export const LABEL_TEXT_TRANSFORMS = ['none', 'uppercase'];
57
+ const DIMENSION_UNITS = ['cm', 'mm', 'in', 'pt', 'px', 'em', 'rem'];
58
+ // ---------------------------------------------------------------------------
59
+ // Small readers
60
+ // ---------------------------------------------------------------------------
61
+ const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
62
+ const finite = (v) => typeof v === 'number' && Number.isFinite(v);
63
+ const oneOf = (list, v) => typeof v === 'string' && list.includes(v);
64
+ /** A length the engine can use: a finite, non-negative value in a known
65
+ * unit. */
66
+ export function isFrameDimension(v) {
67
+ return isRecord(v) && finite(v.value) && v.value >= 0 && typeof v.unit === 'string' && DIMENSION_UNITS.includes(v.unit);
68
+ }
69
+ /** `v` as a frame size, or undefined when it is none. A percentage is
70
+ * clamped to 0 … 100. */
71
+ export function normalizeFrameSize(v) {
72
+ if (v === 'auto' || v === 'fill')
73
+ return v;
74
+ if (isRecord(v) && 'percent' in v) {
75
+ return finite(v.percent) ? { percent: Math.min(100, Math.max(0, v.percent)) } : undefined;
76
+ }
77
+ return isFrameDimension(v) ? { value: v.value, unit: v.unit } : undefined;
78
+ }
79
+ /** The id an anchor names (`'#dinero'` → `'dinero'`), or undefined. */
80
+ export function anchorTarget(placement) {
81
+ const to = isRecord(placement) && isRecord(placement.anchor) ? placement.anchor.to : undefined;
82
+ return typeof to === 'string' && to.startsWith('#') && to.length > 1 ? to.slice(1) : undefined;
83
+ }
84
+ /** The frame a `continueIn` of the form `'#frame'` names. */
85
+ export function continueInFrame(continueIn) {
86
+ return typeof continueIn === 'string' && continueIn.startsWith('#') && continueIn.length > 1 ? continueIn.slice(1) : undefined;
87
+ }
88
+ /** The template a `continueIn` of the form `'template:<id>'` names. */
89
+ export function continueInTemplate(continueIn) {
90
+ return typeof continueIn === 'string' && continueIn.startsWith('template:') && continueIn.length > 9 ? continueIn.slice(9) : undefined;
91
+ }
92
+ // ---------------------------------------------------------------------------
93
+ // The template table: which template each id means
94
+ // ---------------------------------------------------------------------------
95
+ /** id → index of the first template with it. */
96
+ function idTable(templates) {
97
+ const byId = new Map();
98
+ templates.forEach((t, i) => {
99
+ if (isRecord(t) && typeof t.id === 'string' && t.id !== '' && !byId.has(t.id))
100
+ byId.set(t.id, i);
101
+ });
102
+ return byId;
103
+ }
104
+ /** The chain of template indices from `index` up to its root, nearest
105
+ * first; `null` when it loops back on itself. A parent that does not
106
+ * exist ends the chain (the template stands alone). */
107
+ export function templateChain(templates, index, byId = idTable(templates)) {
108
+ const chain = [];
109
+ const seen = new Set();
110
+ let at = index;
111
+ while (at !== undefined) {
112
+ if (seen.has(at))
113
+ return null;
114
+ seen.add(at);
115
+ chain.push(at);
116
+ const parent = templates[at]?.extends;
117
+ at = typeof parent === 'string' ? byId.get(parent) : undefined;
118
+ }
119
+ return chain;
120
+ }
121
+ /** The index of the template `id` means: the first with that id. */
122
+ export function templateIndex(templates, id) {
123
+ return templates ? idTable(templates).get(id) : undefined;
124
+ }
125
+ /** The template-level keys a child or a variant inherits and overrides. */
126
+ const SCALAR_KEYS = ['area', 'overflowTemplate', 'bodyStyle', 'palette', 'header', 'footer'];
127
+ const INHERITED_KEYS = ['spread', 'gutter', 'role'];
128
+ /** Frame fields merged key by key rather than replaced. */
129
+ const MERGED_OBJECT_KEYS = new Set(['fit', 'overflow', 'padding']);
130
+ function mergeNamed(list, entries, basePath, issues) {
131
+ if (!Array.isArray(entries))
132
+ return;
133
+ const seen = new Set();
134
+ entries.forEach((raw, j) => {
135
+ if (!isRecord(raw) || typeof raw.name !== 'string' || raw.name === '')
136
+ return;
137
+ const name = raw.name;
138
+ const path = `${basePath}/${j}`;
139
+ if (seen.has(name)) {
140
+ issues.push({ kind: 'frameDuplicate', path, value: name, used: '' });
141
+ return;
142
+ }
143
+ seen.add(name);
144
+ const at = list.findIndex((e) => e.value.name === name);
145
+ if (raw.remove === true) {
146
+ if (at >= 0)
147
+ list.splice(at, 1);
148
+ return;
149
+ }
150
+ const entry = at >= 0
151
+ ? { value: { ...list[at].value }, configPath: path, fieldPaths: { ...list[at].fieldPaths } }
152
+ : { value: { name }, configPath: path, fieldPaths: {} };
153
+ for (const [key, value] of Object.entries(raw)) {
154
+ if (key === 'name' || key === 'remove' || value === undefined)
155
+ continue;
156
+ const target = entry.value;
157
+ if (MERGED_OBJECT_KEYS.has(key) && isRecord(value)) {
158
+ const before = target[key];
159
+ const merged = isRecord(before) ? { ...before } : {};
160
+ for (const [sub, v] of Object.entries(value)) {
161
+ if (v === undefined)
162
+ continue;
163
+ merged[sub] = v;
164
+ entry.fieldPaths[`${key}/${sub}`] = `${path}/${key}/${sub}`;
165
+ }
166
+ target[key] = merged;
167
+ }
168
+ else {
169
+ target[key] = value;
170
+ }
171
+ entry.fieldPaths[key] = `${path}/${key}`;
172
+ }
173
+ if (at >= 0)
174
+ list[at] = entry;
175
+ else
176
+ list.push(entry);
177
+ });
178
+ }
179
+ function mergePatch(target, patch, path, issues) {
180
+ if (!isRecord(patch))
181
+ return;
182
+ for (const key of SCALAR_KEYS) {
183
+ const value = patch[key];
184
+ if (value === undefined)
185
+ continue;
186
+ target.fields[key] = value;
187
+ target.fieldPaths[key] = `${path}/${key}`;
188
+ }
189
+ if (isRecord(patch.grid)) {
190
+ const before = target.fields.grid;
191
+ const merged = isRecord(before) ? { ...before } : {};
192
+ for (const [k, v] of Object.entries(patch.grid))
193
+ if (v !== undefined)
194
+ merged[k] = v;
195
+ target.fields.grid = merged;
196
+ target.fieldPaths.grid = `${path}/grid`;
197
+ }
198
+ mergeNamed(target.frames, patch.frames, `${path}/frames`, issues);
199
+ mergeNamed(target.groups, patch.groups, `${path}/groups`, issues);
200
+ const elements = isRecord(patch.decoration) ? patch.decoration.elements : undefined;
201
+ if (Array.isArray(elements)) {
202
+ elements.forEach((el, j) => {
203
+ if (!isRecord(el))
204
+ return;
205
+ const element = el;
206
+ const elementPath = `${path}/decoration/elements/${j}`;
207
+ const id = typeof el.id === 'string' && el.id !== '' ? el.id : undefined;
208
+ const at = id === undefined ? -1 : target.elements.findIndex((e) => e.value.id === id);
209
+ if (at >= 0) {
210
+ const merged = { ...target.elements[at].value };
211
+ for (const [k, v] of Object.entries(el))
212
+ if (v !== undefined)
213
+ merged[k] = v;
214
+ target.elements[at] = { value: merged, configPath: elementPath };
215
+ }
216
+ else {
217
+ target.elements.push({ value: element, configPath: elementPath });
218
+ }
219
+ });
220
+ }
221
+ }
222
+ // ---------------------------------------------------------------------------
223
+ // Normalisation
224
+ // ---------------------------------------------------------------------------
225
+ function normalizeGrid(raw) {
226
+ if (!isRecord(raw))
227
+ return undefined;
228
+ const columns = finite(raw.columns) && raw.columns >= 1 ? Math.round(raw.columns) : undefined;
229
+ const rows = finite(raw.rows) && raw.rows >= 1 ? Math.round(raw.rows) : undefined;
230
+ if (columns === undefined || rows === undefined)
231
+ return undefined;
232
+ return {
233
+ columns,
234
+ rows,
235
+ columnGap: isFrameDimension(raw.columnGap) ? raw.columnGap : ZERO,
236
+ rowGap: isFrameDimension(raw.rowGap) ? raw.rowGap : ZERO,
237
+ };
238
+ }
239
+ /** A grid cell clamped to the grid; undefined without a grid or a cell. */
240
+ function normalizeCell(raw, grid) {
241
+ if (!grid || !isRecord(raw) || !finite(raw.col) || !finite(raw.row))
242
+ return undefined;
243
+ const col = Math.min(grid.columns, Math.max(1, Math.round(raw.col)));
244
+ const row = Math.min(grid.rows, Math.max(1, Math.round(raw.row)));
245
+ const cols = finite(raw.cols) ? Math.min(grid.columns - col + 1, Math.max(1, Math.round(raw.cols))) : 1;
246
+ const rows = finite(raw.rows) ? Math.min(grid.rows - row + 1, Math.max(1, Math.round(raw.rows))) : 1;
247
+ return { col, row, cols, rows };
248
+ }
249
+ const scale = (v, fallback) => (finite(v) && v > 0 ? Math.min(1, v) : fallback);
250
+ function normalizeFit(raw) {
251
+ const f = isRecord(raw) ? raw : {};
252
+ return {
253
+ shrinkText: typeof f.shrinkText === 'boolean' ? f.shrinkText : DEFAULT_FRAME_FIT.shrinkText,
254
+ minScale: scale(f.minScale, DEFAULT_FRAME_FIT.minScale),
255
+ scaleImages: typeof f.scaleImages === 'boolean' ? f.scaleImages : DEFAULT_FRAME_FIT.scaleImages,
256
+ imageMinScale: scale(f.imageMinScale, DEFAULT_FRAME_FIT.imageMinScale),
257
+ };
258
+ }
259
+ function normalizeOverflow(raw) {
260
+ if (!isRecord(raw))
261
+ return {};
262
+ const out = {};
263
+ if (typeof raw.continueIn === 'string' && raw.continueIn !== '')
264
+ out.continueIn = raw.continueIn;
265
+ if (typeof raw.mark === 'string' || raw.mark === false)
266
+ out.mark = raw.mark;
267
+ return out;
268
+ }
269
+ const dim = (v) => (isFrameDimension(v) ? v : undefined);
270
+ const nonNegative = (v, fallback) => (finite(v) && v >= 0 ? v : fallback);
271
+ /** A frame as the layout reads it: every default filled in, sizes read
272
+ * from `width`/`height` and else `placement.size`, values the engine
273
+ * cannot use replaced by their default. */
274
+ export function normalizeFrame(frame, grid, where = { configPath: '', fieldPaths: {} }) {
275
+ const f = frame;
276
+ const placement = isRecord(f.placement) ? f.placement : undefined;
277
+ const cell = normalizeCell(f.grid, grid);
278
+ const out = {
279
+ name: f.name,
280
+ ...(placement ? { placement } : {}),
281
+ ...(cell ? { grid: cell } : {}),
282
+ width: normalizeFrameSize(f.width) ?? normalizeFrameSize(placement?.size?.width) ?? DEFAULT_FRAME.width,
283
+ height: normalizeFrameSize(f.height) ?? normalizeFrameSize(placement?.size?.height) ?? DEFAULT_FRAME.height,
284
+ layer: finite(f.layer) ? f.layer : DEFAULT_FRAME.layer,
285
+ rotate: finite(f.rotate) ? f.rotate : DEFAULT_FRAME.rotate,
286
+ verticalAlign: oneOf(FRAME_VERTICAL_ALIGNS, f.verticalAlign) ? f.verticalAlign : DEFAULT_FRAME.verticalAlign,
287
+ columns: finite(f.columns) && f.columns >= 1 ? Math.round(f.columns) : DEFAULT_FRAME.columns,
288
+ exclude: oneOf(FRAME_EXCLUDES, f.exclude) ? f.exclude : DEFAULT_FRAME.exclude,
289
+ flow: f.flow === true,
290
+ pin: f.pin === true,
291
+ grow: nonNegative(f.grow, DEFAULT_FRAME.grow),
292
+ shrink: nonNegative(f.shrink, DEFAULT_FRAME.shrink),
293
+ priority: finite(f.priority) ? f.priority : DEFAULT_FRAME.priority,
294
+ fit: normalizeFit(f.fit),
295
+ overflow: normalizeOverflow(f.overflow),
296
+ optional: f.optional === true,
297
+ crossGutter: f.crossGutter === true,
298
+ configPath: where.configPath,
299
+ fieldPaths: where.fieldPaths,
300
+ };
301
+ for (const key of ['minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'columnGap']) {
302
+ const d = dim(f[key]);
303
+ if (d)
304
+ out[key] = d;
305
+ }
306
+ if (typeof f.style === 'string' && f.style !== '')
307
+ out.style = f.style;
308
+ if (isRecord(f.padding)) {
309
+ const padding = {};
310
+ for (const side of ['top', 'right', 'bottom', 'left']) {
311
+ const d = dim(f.padding[side]);
312
+ if (d)
313
+ padding[side] = d;
314
+ }
315
+ out.padding = padding;
316
+ }
317
+ if (typeof f.next === 'string' && f.next !== '')
318
+ out.next = f.next;
319
+ if (oneOf(FRAME_TAKES, f.takes))
320
+ out.takes = f.takes;
321
+ if (oneOf(FRAME_WRITING_MODES, f.writingMode))
322
+ out.writingMode = f.writingMode;
323
+ if (oneOf(FRAME_DIRECTIONS, f.direction))
324
+ out.direction = f.direction;
325
+ return out;
326
+ }
327
+ function normalizeGroup(entry, grid) {
328
+ const g = entry.value;
329
+ const placement = isRecord(g.placement) ? g.placement : undefined;
330
+ const cell = normalizeCell(g.grid, grid);
331
+ return {
332
+ name: g.name,
333
+ kind: oneOf(FRAME_GROUP_KINDS, g.kind) ? g.kind : DEFAULT_FRAME_GROUP.kind,
334
+ frames: Array.isArray(g.frames) ? g.frames.filter((n) => typeof n === 'string') : [],
335
+ gap: dim(g.gap) ?? DEFAULT_FRAME_GROUP.gap,
336
+ ...(placement ? { placement } : {}),
337
+ ...(cell ? { grid: cell } : {}),
338
+ align: oneOf(FRAME_GROUP_ALIGNS, g.align) ? g.align : DEFAULT_FRAME_GROUP.align,
339
+ configPath: entry.configPath,
340
+ fieldPaths: entry.fieldPaths,
341
+ };
342
+ }
343
+ // ---------------------------------------------------------------------------
344
+ // References: groups, anchors, chains
345
+ // ---------------------------------------------------------------------------
346
+ /** Where a placement's anchor sits among a frame's or group's fields. */
347
+ const ANCHOR_FIELD = 'placement/anchor/to';
348
+ function withContainerAnchor(placement) {
349
+ return { ...placement, anchor: { ...placement.anchor, to: 'container' } };
350
+ }
351
+ /** The first loop of `edges` reachable in `order`, as the node list from
352
+ * the node it closes on back to it, or undefined. */
353
+ function findLoop(order, edges) {
354
+ const state = new Map();
355
+ const stack = [];
356
+ const visit = (n) => {
357
+ state.set(n, 1);
358
+ stack.push(n);
359
+ for (const m of edges.get(n) ?? []) {
360
+ const s = state.get(m);
361
+ if (s === 1)
362
+ return [...stack.slice(stack.indexOf(m)), m];
363
+ if (s === undefined) {
364
+ const found = visit(m);
365
+ if (found)
366
+ return found;
367
+ }
368
+ }
369
+ stack.pop();
370
+ state.set(n, 2);
371
+ return undefined;
372
+ };
373
+ for (const n of order) {
374
+ if (state.has(n))
375
+ continue;
376
+ const found = visit(n);
377
+ if (found)
378
+ return found;
379
+ }
380
+ return undefined;
381
+ }
382
+ /** Settle the references of a merged template: group members that do not
383
+ * exist or are claimed twice are skipped, anchors to nothing (or in a
384
+ * loop) read as `'container'`, `next`/`continueIn` links to nothing (or
385
+ * closing a loop) are dropped. Each fix is an issue. */
386
+ function settleReferences(frames, groups, decoration, context, overflowTemplate, issues) {
387
+ const frameNames = frames.map((f) => f.name);
388
+ const frameSet = new Set(frameNames);
389
+ // A group named like a frame: anchors read the frame.
390
+ for (const g of groups) {
391
+ if (frameSet.has(g.name))
392
+ issues.push({ kind: 'frameDuplicate', path: g.configPath, value: g.name, used: '' });
393
+ }
394
+ const boxNames = [...frameNames, ...groups.map((g) => g.name).filter((n) => !frameSet.has(n))];
395
+ const boxSet = new Set(boxNames);
396
+ // Styles.
397
+ if (context.calloutStyleIds) {
398
+ for (const f of frames) {
399
+ if (f.style !== undefined && !context.calloutStyleIds.has(f.style)) {
400
+ issues.push({ kind: 'frameStyleUnknown', path: f.fieldPaths.style ?? `${f.configPath}/style`, value: f.style, used: '', known: [...context.calloutStyleIds] });
401
+ delete f.style;
402
+ }
403
+ }
404
+ }
405
+ // Group members.
406
+ const claimed = new Set();
407
+ for (const g of groups) {
408
+ const raw = g.frames;
409
+ const kept = [];
410
+ raw.forEach((name, k) => {
411
+ const path = `${g.fieldPaths.frames ?? `${g.configPath}/frames`}/${k}`;
412
+ if (!frameSet.has(name)) {
413
+ issues.push({ kind: 'groupFrameUnknown', path, value: name, used: '', known: frameNames });
414
+ }
415
+ else if (claimed.has(name)) {
416
+ issues.push({ kind: 'groupFrameTwice', path, value: name, used: '' });
417
+ }
418
+ else {
419
+ claimed.add(name);
420
+ kept.push(name);
421
+ }
422
+ });
423
+ g.frames = kept;
424
+ }
425
+ const boxes = new Map();
426
+ for (const f of frames)
427
+ if (!boxes.has(f.name))
428
+ boxes.set(f.name, f);
429
+ for (const g of groups)
430
+ if (!boxes.has(g.name))
431
+ boxes.set(g.name, g);
432
+ const anchorPath = (b) => b.fieldPaths.placement ? `${b.fieldPaths.placement}/anchor/to` : `${b.configPath}/${ANCHOR_FIELD}`;
433
+ const anchored = (b) => (b.grid ? undefined : anchorTarget(b.placement));
434
+ for (const b of boxes.values()) {
435
+ const target = anchored(b);
436
+ if (target !== undefined && !boxSet.has(target)) {
437
+ issues.push({ kind: 'frameAnchorUnknown', path: anchorPath(b), value: `#${target}`, used: 'container', known: boxNames.map((n) => `#${n}`) });
438
+ b.placement = withContainerAnchor(b.placement);
439
+ }
440
+ }
441
+ for (;;) {
442
+ const edges = new Map();
443
+ for (const [name, b] of boxes) {
444
+ const out = [];
445
+ const target = anchored(b);
446
+ if (target !== undefined)
447
+ out.push(target);
448
+ // A group with no box of its own is the union of its frames' boxes.
449
+ if ('kind' in b && !b.placement && !b.grid)
450
+ out.push(...b.frames);
451
+ edges.set(name, out);
452
+ }
453
+ const loop = findLoop(boxNames, edges);
454
+ if (!loop)
455
+ break;
456
+ // Drop the explicit anchor of the loop set latest in template order.
457
+ let drop;
458
+ for (let k = 0; k < loop.length - 1; k++) {
459
+ const b = boxes.get(loop[k]);
460
+ if (anchored(b) === loop[k + 1] && (!drop || boxNames.indexOf(b.name) > boxNames.indexOf(drop.name)))
461
+ drop = b;
462
+ }
463
+ if (!drop)
464
+ break; // cannot happen: a group's members never loop by themselves
465
+ issues.push({ kind: 'frameAnchorUnknown', path: anchorPath(drop), value: loop.join(' → '), used: 'container' });
466
+ drop.placement = withContainerAnchor(drop.placement);
467
+ }
468
+ // Decoration anchors: another element of the decoration, or a box.
469
+ const elementIds = new Set(decoration.map((e) => e.value.id).filter((id) => typeof id === 'string'));
470
+ decoration.forEach((e) => {
471
+ const target = anchorTarget(e.value.placement);
472
+ if (target !== undefined && !elementIds.has(target) && !boxSet.has(target)) {
473
+ issues.push({ kind: 'frameAnchorUnknown', path: `${e.configPath}/${ANCHOR_FIELD}`, value: `#${target}`, used: 'container', known: [...boxNames, ...elementIds].map((n) => `#${n}`) });
474
+ e.value = { ...e.value, placement: withContainerAnchor(e.value.placement) };
475
+ }
476
+ });
477
+ // Chains: `next` and `overflow.continueIn`.
478
+ for (const f of frames) {
479
+ if (f.next !== undefined && !frameSet.has(f.next)) {
480
+ issues.push({ kind: 'overflowTargetUnknown', path: f.fieldPaths.next ?? `${f.configPath}/next`, value: f.next, used: '', known: frameNames });
481
+ delete f.next;
482
+ }
483
+ const continueIn = f.overflow.continueIn;
484
+ if (continueIn !== undefined) {
485
+ const frameTarget = continueInFrame(continueIn);
486
+ const templateTarget = continueInTemplate(continueIn);
487
+ const ok = continueIn === 'next-page'
488
+ || (frameTarget !== undefined && frameSet.has(frameTarget))
489
+ || (templateTarget !== undefined && context.templateIds.has(templateTarget));
490
+ if (!ok) {
491
+ const known = frameTarget !== undefined ? frameNames.map((n) => `#${n}`)
492
+ : templateTarget !== undefined ? [...context.templateIds].map((id) => `template:${id}`)
493
+ : ['next-page'];
494
+ issues.push({ kind: 'overflowTargetUnknown', path: f.fieldPaths['overflow/continueIn'] ?? `${f.configPath}/overflow/continueIn`, value: continueIn, used: '', known });
495
+ f.overflow = { ...f.overflow };
496
+ delete f.overflow.continueIn;
497
+ }
498
+ }
499
+ }
500
+ const byName = new Map(frames.map((f) => [f.name, f]));
501
+ for (;;) {
502
+ const edges = new Map();
503
+ for (const f of frames) {
504
+ const out = [];
505
+ if (f.next !== undefined)
506
+ out.push(f.next);
507
+ const target = continueInFrame(f.overflow.continueIn);
508
+ if (target !== undefined)
509
+ out.push(target);
510
+ edges.set(f.name, out);
511
+ }
512
+ const loop = findLoop(frameNames, edges);
513
+ if (!loop)
514
+ break;
515
+ // The link that closes the loop: from its last frame back to its first.
516
+ const from = byName.get(loop[loop.length - 2]);
517
+ const to = loop[loop.length - 1];
518
+ if (from.next === to) {
519
+ issues.push({ kind: 'frameChainLoop', path: from.fieldPaths.next ?? `${from.configPath}/next`, value: loop.join(' → '), used: '' });
520
+ delete from.next;
521
+ }
522
+ else {
523
+ issues.push({ kind: 'frameChainLoop', path: from.fieldPaths['overflow/continueIn'] ?? `${from.configPath}/overflow/continueIn`, value: loop.join(' → '), used: '' });
524
+ from.overflow = { ...from.overflow };
525
+ delete from.overflow.continueIn;
526
+ }
527
+ }
528
+ if (overflowTemplate && overflowTemplate.value !== undefined) {
529
+ const id = overflowTemplate.value;
530
+ if (typeof id === 'string' && context.templateIds.has(id))
531
+ return { overflowTemplate: id };
532
+ issues.push({ kind: 'overflowTargetUnknown', path: overflowTemplate.path, value: String(id), used: '', known: [...context.templateIds] });
533
+ }
534
+ return {};
535
+ }
536
+ /** The usable template ids: the first of each id, chains that do not loop. */
537
+ function usableIds(templates) {
538
+ const ids = new Set();
539
+ const byId = idTable(templates);
540
+ for (const [id, index] of byId)
541
+ if (templateChain(templates, index, byId))
542
+ ids.add(id);
543
+ return ids;
544
+ }
545
+ /**
546
+ * Resolve the template at `index` of `templates` (see
547
+ * {@link resolvePageTemplate}), with the issues met on the way. `prefix`
548
+ * is the JSON pointer of the list (`/pageTemplates`).
549
+ * @internal the validator's; hosts call `resolvePageTemplate`.
550
+ */
551
+ export function resolveTemplateAt(templates, index, options = {}) {
552
+ const chain = templateChain(templates, index);
553
+ const own = templates[index];
554
+ if (!chain || !isRecord(own) || typeof own.id !== 'string')
555
+ return undefined;
556
+ const prefix = options.prefix ?? '/pageTemplates';
557
+ const issues = [];
558
+ const merged = { fields: {}, fieldPaths: {}, frames: [], groups: [], elements: [] };
559
+ const inherited = {};
560
+ for (const i of [...chain].reverse()) {
561
+ const t = templates[i];
562
+ mergePatch(merged, t, `${prefix}/${i}`, issues);
563
+ for (const key of INHERITED_KEYS)
564
+ if (t[key] !== undefined)
565
+ inherited[key] = t[key];
566
+ }
567
+ const spread = inherited.spread === true;
568
+ const side = spread || options.spread ? undefined : options.parity;
569
+ let variant;
570
+ if (side) {
571
+ for (const i of [...chain].reverse()) {
572
+ const patch = templates[i].variants?.[side];
573
+ if (isRecord(patch)) {
574
+ mergePatch(merged, patch, `${prefix}/${i}/variants/${side}`, issues);
575
+ variant = side;
576
+ }
577
+ }
578
+ }
579
+ const grid = normalizeGrid(merged.fields.grid);
580
+ const frames = merged.frames.map((e) => normalizeFrame(e.value, grid, { configPath: e.configPath, fieldPaths: e.fieldPaths }));
581
+ const groups = merged.groups.map((e) => normalizeGroup(e, grid));
582
+ const settled = settleReferences(frames, groups, merged.elements, { templateIds: usableIds(templates), calloutStyleIds: options.calloutStyleIds }, merged.fields.overflowTemplate !== undefined ? { value: merged.fields.overflowTemplate, path: merged.fieldPaths.overflowTemplate } : undefined, issues);
583
+ const f = merged.fields;
584
+ const template = {
585
+ id: own.id,
586
+ ...(typeof own.name === 'string' ? { name: own.name } : {}),
587
+ area: oneOf(TEMPLATE_AREAS, f.area) ? f.area : DEFAULT_PAGE_TEMPLATE.area,
588
+ ...(grid ? { grid } : {}),
589
+ frames,
590
+ groups,
591
+ decoration: { elements: merged.elements.map((e) => e.value) },
592
+ decorationPaths: merged.elements.map((e) => e.configPath),
593
+ ...(f.header === false || isRecord(f.header) ? { header: f.header } : {}),
594
+ ...(f.footer === false || isRecord(f.footer) ? { footer: f.footer } : {}),
595
+ ...settled,
596
+ ...(typeof f.bodyStyle === 'string' ? { bodyStyle: f.bodyStyle } : {}),
597
+ ...(typeof f.palette === 'string' ? { palette: f.palette } : {}),
598
+ spread,
599
+ gutter: dim(inherited.gutter) ?? DEFAULT_PAGE_TEMPLATE.gutter,
600
+ role: typeof inherited.role === 'string' && inherited.role !== '' ? inherited.role : DEFAULT_PAGE_TEMPLATE.role,
601
+ ...(variant ? { variant } : {}),
602
+ chain: chain.map((i) => templates[i].id),
603
+ configPath: `${prefix}/${index}`,
604
+ };
605
+ return { template, issues };
606
+ }
607
+ const resolvedCache = new WeakMap();
608
+ /**
609
+ * The template `id` of `config.pageTemplates`, ready to lay out: its
610
+ * `extends` chain merged (scalar fields replace, `grid` merges key by key,
611
+ * frames and groups merge by `name` with `remove: true` dropping one,
612
+ * decoration elements merge by `id`), then the variant of the page's side
613
+ * (`options.parity`; none on a spread), then every default filled in and
614
+ * every reference settled (see `validatePageTemplates` for what is fixed
615
+ * and reported). Undefined when no template has that id, or its chain
616
+ * loops. The first template with an id is the one it means.
617
+ *
618
+ * Memoised per `pageTemplates` array (the resolved configuration holds a
619
+ * copy of its own): treat the result as read-only, and give a changed list
620
+ * as a new array.
621
+ */
622
+ export function resolvePageTemplate(config, id, options = {}) {
623
+ const templates = config?.pageTemplates;
624
+ if (!Array.isArray(templates))
625
+ return undefined;
626
+ const index = templateIndex(templates, id);
627
+ if (index === undefined)
628
+ return undefined;
629
+ const styleIds = config?.calloutStyles ? config.calloutStyles.map((s) => s.id).join('\u0000') : '';
630
+ const key = `${index}|${options.parity ?? ''}|${options.spread ? 1 : 0}|${config?.calloutStyles ? 1 : 0}|${styleIds}`;
631
+ let cache = resolvedCache.get(templates);
632
+ if (!cache) {
633
+ cache = new Map();
634
+ resolvedCache.set(templates, cache);
635
+ }
636
+ if (cache.has(key))
637
+ return cache.get(key) ?? undefined;
638
+ const calloutStyleIds = config?.calloutStyles ? new Set(config.calloutStyles.map((s) => s.id)) : undefined;
639
+ const result = resolveTemplateAt(templates, index, { ...options, calloutStyleIds })?.template;
640
+ cache.set(key, result ?? null);
641
+ return result;
642
+ }
643
+ // ---------------------------------------------------------------------------
644
+ // Label styles
645
+ // ---------------------------------------------------------------------------
646
+ /** The label styles with their defaults filled in. */
647
+ export function resolveLabelStyles(styles) {
648
+ if (!Array.isArray(styles))
649
+ return [];
650
+ return styles.filter((s) => isRecord(s) && typeof s.id === 'string').map((s) => ({
651
+ ...s,
652
+ fontStyle: oneOf(LABEL_FONT_STYLES, s.fontStyle) ? s.fontStyle : DEFAULT_LABEL_STYLE.fontStyle,
653
+ textTransform: oneOf(LABEL_TEXT_TRANSFORMS, s.textTransform) ? s.textTransform : DEFAULT_LABEL_STYLE.textTransform,
654
+ minScale: scale(s.minScale, DEFAULT_LABEL_STYLE.minScale),
655
+ }));
656
+ }
657
+ // ---------------------------------------------------------------------------
658
+ // Stripping defaults (what the Sandbox saves)
659
+ // ---------------------------------------------------------------------------
660
+ /** A deep copy of `value` with the keys set to `undefined` left out. */
661
+ function withoutUndefined(value) {
662
+ if (Array.isArray(value))
663
+ return value.map(withoutUndefined);
664
+ if (!isRecord(value))
665
+ return value;
666
+ const out = {};
667
+ for (const [k, v] of Object.entries(value))
668
+ if (v !== undefined)
669
+ out[k] = withoutUndefined(v);
670
+ return out;
671
+ }
672
+ /**
673
+ * The page templates as stored. Every field is kept as written, a value
674
+ * equal to its default included: in a template that `extends` another, or
675
+ * in a variant, it overrides the parent's, so it is never a restated
676
+ * default. Keys set to `undefined` are left out, and an empty list is no
677
+ * list.
678
+ */
679
+ export function stripPageTemplatesDefaults(templates) {
680
+ if (!Array.isArray(templates) || templates.length === 0)
681
+ return undefined;
682
+ return withoutUndefined([...templates]);
683
+ }
684
+ /** The label styles without the values that restate a default (and `name`
685
+ * equal to `id`); an empty list is no list. */
686
+ export function stripLabelStylesDefaults(styles) {
687
+ if (!Array.isArray(styles) || styles.length === 0)
688
+ return undefined;
689
+ return styles.map((s) => {
690
+ const out = withoutUndefined({ ...s });
691
+ if (out.name === out.id)
692
+ delete out.name;
693
+ if (out.fontStyle === DEFAULT_LABEL_STYLE.fontStyle)
694
+ delete out.fontStyle;
695
+ if (out.textTransform === DEFAULT_LABEL_STYLE.textTransform)
696
+ delete out.textTransform;
697
+ if (out.minScale === DEFAULT_LABEL_STYLE.minScale)
698
+ delete out.minScale;
699
+ return out;
700
+ });
701
+ }
702
+ //# sourceMappingURL=templates.js.map