postext 1.36.0 → 1.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/dist/__tests__/comics/frames.test.d.ts +2 -0
  2. package/dist/__tests__/comics/frames.test.d.ts.map +1 -0
  3. package/dist/__tests__/comics/frames.test.js +251 -0
  4. package/dist/__tests__/comics/frames.test.js.map +1 -0
  5. package/dist/__tests__/composed/continuation.test.d.ts +2 -0
  6. package/dist/__tests__/composed/continuation.test.d.ts.map +1 -0
  7. package/dist/__tests__/composed/continuation.test.js +352 -0
  8. package/dist/__tests__/composed/continuation.test.js.map +1 -0
  9. package/dist/__tests__/composed/continuationRules.test.d.ts +2 -0
  10. package/dist/__tests__/composed/continuationRules.test.d.ts.map +1 -0
  11. package/dist/__tests__/composed/continuationRules.test.js +389 -0
  12. package/dist/__tests__/composed/continuationRules.test.js.map +1 -0
  13. package/dist/__tests__/composed/contrast.test.d.ts +2 -0
  14. package/dist/__tests__/composed/contrast.test.d.ts.map +1 -0
  15. package/dist/__tests__/composed/contrast.test.js +124 -0
  16. package/dist/__tests__/composed/contrast.test.js.map +1 -0
  17. package/dist/__tests__/composed/corpus.test.js +7 -1
  18. package/dist/__tests__/composed/corpus.test.js.map +1 -1
  19. package/dist/__tests__/composed/coverImages.test.d.ts +2 -0
  20. package/dist/__tests__/composed/coverImages.test.d.ts.map +1 -0
  21. package/dist/__tests__/composed/coverImages.test.js +75 -0
  22. package/dist/__tests__/composed/coverImages.test.js.map +1 -0
  23. package/dist/__tests__/composed/decorationSize.test.d.ts +2 -0
  24. package/dist/__tests__/composed/decorationSize.test.d.ts.map +1 -0
  25. package/dist/__tests__/composed/decorationSize.test.js +79 -0
  26. package/dist/__tests__/composed/decorationSize.test.js.map +1 -0
  27. package/dist/__tests__/composed/emptyFrames.test.d.ts +2 -0
  28. package/dist/__tests__/composed/emptyFrames.test.d.ts.map +1 -0
  29. package/dist/__tests__/composed/emptyFrames.test.js +129 -0
  30. package/dist/__tests__/composed/emptyFrames.test.js.map +1 -0
  31. package/dist/__tests__/composed/fit.test.js +19 -10
  32. package/dist/__tests__/composed/fit.test.js.map +1 -1
  33. package/dist/__tests__/composed/groups.test.js +34 -0
  34. package/dist/__tests__/composed/groups.test.js.map +1 -1
  35. package/dist/__tests__/composed/headingDesigns.test.d.ts +2 -0
  36. package/dist/__tests__/composed/headingDesigns.test.d.ts.map +1 -0
  37. package/dist/__tests__/composed/headingDesigns.test.js +262 -0
  38. package/dist/__tests__/composed/headingDesigns.test.js.map +1 -0
  39. package/dist/__tests__/composed/labels.test.d.ts +2 -0
  40. package/dist/__tests__/composed/labels.test.d.ts.map +1 -0
  41. package/dist/__tests__/composed/labels.test.js +326 -0
  42. package/dist/__tests__/composed/labels.test.js.map +1 -0
  43. package/dist/__tests__/composed/outsidePrint.test.js +5 -2
  44. package/dist/__tests__/composed/outsidePrint.test.js.map +1 -1
  45. package/dist/__tests__/composed/ruledFrames.test.d.ts +2 -0
  46. package/dist/__tests__/composed/ruledFrames.test.d.ts.map +1 -0
  47. package/dist/__tests__/composed/ruledFrames.test.js +93 -0
  48. package/dist/__tests__/composed/ruledFrames.test.js.map +1 -0
  49. package/dist/__tests__/composed/sourceMap.test.js +26 -0
  50. package/dist/__tests__/composed/sourceMap.test.js.map +1 -1
  51. package/dist/__tests__/composed/spread.test.js +82 -0
  52. package/dist/__tests__/composed/spread.test.js.map +1 -1
  53. package/dist/__tests__/composed/spreadLabels.test.d.ts +2 -0
  54. package/dist/__tests__/composed/spreadLabels.test.d.ts.map +1 -0
  55. package/dist/__tests__/composed/spreadLabels.test.js +131 -0
  56. package/dist/__tests__/composed/spreadLabels.test.js.map +1 -0
  57. package/dist/__tests__/exports.test.js +23 -0
  58. package/dist/__tests__/exports.test.js.map +1 -1
  59. package/dist/__tests__/migrate/fixtures/textbookPort.d.ts +14 -0
  60. package/dist/__tests__/migrate/fixtures/textbookPort.d.ts.map +1 -0
  61. package/dist/__tests__/migrate/fixtures/textbookPort.js +116 -0
  62. package/dist/__tests__/migrate/fixtures/textbookPort.js.map +1 -0
  63. package/dist/__tests__/migrate/labelsOnPictures.test.d.ts +2 -0
  64. package/dist/__tests__/migrate/labelsOnPictures.test.d.ts.map +1 -0
  65. package/dist/__tests__/migrate/labelsOnPictures.test.js +114 -0
  66. package/dist/__tests__/migrate/labelsOnPictures.test.js.map +1 -0
  67. package/dist/__tests__/migrate/textToMarkdown.test.d.ts +2 -0
  68. package/dist/__tests__/migrate/textToMarkdown.test.d.ts.map +1 -0
  69. package/dist/__tests__/migrate/textToMarkdown.test.js +268 -0
  70. package/dist/__tests__/migrate/textToMarkdown.test.js.map +1 -0
  71. package/dist/__tests__/paragraphDropCap.test.js +38 -0
  72. package/dist/__tests__/paragraphDropCap.test.js.map +1 -1
  73. package/dist/__tests__/paragraphStyleLetterSpacing.test.d.ts +2 -0
  74. package/dist/__tests__/paragraphStyleLetterSpacing.test.d.ts.map +1 -0
  75. package/dist/__tests__/paragraphStyleLetterSpacing.test.js +89 -0
  76. package/dist/__tests__/paragraphStyleLetterSpacing.test.js.map +1 -0
  77. package/dist/__tests__/pictureBelowPrintSize.test.d.ts +2 -0
  78. package/dist/__tests__/pictureBelowPrintSize.test.d.ts.map +1 -0
  79. package/dist/__tests__/pictureBelowPrintSize.test.js +82 -0
  80. package/dist/__tests__/pictureBelowPrintSize.test.js.map +1 -0
  81. package/dist/__tests__/pipeline/paragraphStyleFaces.test.js +1 -1
  82. package/dist/__tests__/pipeline/paragraphStyleFaces.test.js.map +1 -1
  83. package/dist/__tests__/resourceTypePartial.test.d.ts +2 -0
  84. package/dist/__tests__/resourceTypePartial.test.d.ts.map +1 -0
  85. package/dist/__tests__/resourceTypePartial.test.js +60 -0
  86. package/dist/__tests__/resourceTypePartial.test.js.map +1 -0
  87. package/dist/canvas-backend/composed.d.ts +8 -2
  88. package/dist/canvas-backend/composed.d.ts.map +1 -1
  89. package/dist/canvas-backend/composed.js +52 -29
  90. package/dist/canvas-backend/composed.js.map +1 -1
  91. package/dist/canvas-backend/headerFooter.d.ts.map +1 -1
  92. package/dist/canvas-backend/headerFooter.js +13 -0
  93. package/dist/canvas-backend/headerFooter.js.map +1 -1
  94. package/dist/canvas-backend/index.d.ts.map +1 -1
  95. package/dist/canvas-backend/index.js +53 -24
  96. package/dist/canvas-backend/index.js.map +1 -1
  97. package/dist/canvas-backend/renderResourceBlock.d.ts +5 -1
  98. package/dist/canvas-backend/renderResourceBlock.d.ts.map +1 -1
  99. package/dist/canvas-backend/renderResourceBlock.js +60 -1
  100. package/dist/canvas-backend/renderResourceBlock.js.map +1 -1
  101. package/dist/comics/frames.d.ts +82 -0
  102. package/dist/comics/frames.d.ts.map +1 -0
  103. package/dist/comics/frames.js +182 -0
  104. package/dist/comics/frames.js.map +1 -0
  105. package/dist/comics/index.d.ts +4 -1
  106. package/dist/comics/index.d.ts.map +1 -1
  107. package/dist/comics/index.js +3 -1
  108. package/dist/comics/index.js.map +1 -1
  109. package/dist/comics/layoutPage.d.ts +9 -0
  110. package/dist/comics/layoutPage.d.ts.map +1 -1
  111. package/dist/comics/layoutPage.js +53 -68
  112. package/dist/comics/layoutPage.js.map +1 -1
  113. package/dist/comics/page.d.ts.map +1 -1
  114. package/dist/comics/page.js +10 -1
  115. package/dist/comics/page.js.map +1 -1
  116. package/dist/comics/sourceMap.d.ts +60 -0
  117. package/dist/comics/sourceMap.d.ts.map +1 -0
  118. package/dist/comics/sourceMap.js +166 -0
  119. package/dist/comics/sourceMap.js.map +1 -0
  120. package/dist/comics/spread.d.ts.map +1 -1
  121. package/dist/comics/spread.js +6 -0
  122. package/dist/comics/spread.js.map +1 -1
  123. package/dist/comics/textPanels.d.ts +113 -0
  124. package/dist/comics/textPanels.d.ts.map +1 -0
  125. package/dist/comics/textPanels.js +327 -0
  126. package/dist/comics/textPanels.js.map +1 -0
  127. package/dist/comics/transform.d.ts +6 -2
  128. package/dist/comics/transform.d.ts.map +1 -1
  129. package/dist/comics/transform.js +14 -1
  130. package/dist/comics/transform.js.map +1 -1
  131. package/dist/comics/types.d.ts +10 -0
  132. package/dist/comics/types.d.ts.map +1 -1
  133. package/dist/composed/continuation.d.ts +152 -0
  134. package/dist/composed/continuation.d.ts.map +1 -0
  135. package/dist/composed/continuation.js +189 -0
  136. package/dist/composed/continuation.js.map +1 -0
  137. package/dist/composed/groups.d.ts +3 -0
  138. package/dist/composed/groups.d.ts.map +1 -1
  139. package/dist/composed/groups.js +34 -4
  140. package/dist/composed/groups.js.map +1 -1
  141. package/dist/composed/labels.d.ts +154 -0
  142. package/dist/composed/labels.d.ts.map +1 -0
  143. package/dist/composed/labels.js +524 -0
  144. package/dist/composed/labels.js.map +1 -0
  145. package/dist/composed/pageMemo.d.ts +3 -2
  146. package/dist/composed/pageMemo.d.ts.map +1 -1
  147. package/dist/composed/pageMemo.js +3 -1
  148. package/dist/composed/pageMemo.js.map +1 -1
  149. package/dist/composed/paint.d.ts +28 -3
  150. package/dist/composed/paint.d.ts.map +1 -1
  151. package/dist/composed/paint.js +48 -2
  152. package/dist/composed/paint.js.map +1 -1
  153. package/dist/composed/reflowLog.d.ts +3 -1
  154. package/dist/composed/reflowLog.d.ts.map +1 -1
  155. package/dist/composed/reflowLog.js +20 -6
  156. package/dist/composed/reflowLog.js.map +1 -1
  157. package/dist/composed/rules.d.ts +31 -0
  158. package/dist/composed/rules.d.ts.map +1 -0
  159. package/dist/composed/rules.js +45 -0
  160. package/dist/composed/rules.js.map +1 -0
  161. package/dist/composed/slotChecks.d.ts +2 -1
  162. package/dist/composed/slotChecks.d.ts.map +1 -1
  163. package/dist/composed/slotChecks.js +56 -2
  164. package/dist/composed/slotChecks.js.map +1 -1
  165. package/dist/composed/sourceMap.d.ts +0 -21
  166. package/dist/composed/sourceMap.d.ts.map +1 -1
  167. package/dist/composed/sourceMap.js +102 -8
  168. package/dist/composed/sourceMap.js.map +1 -1
  169. package/dist/composed/spread.d.ts +23 -5
  170. package/dist/composed/spread.d.ts.map +1 -1
  171. package/dist/composed/spread.js +52 -27
  172. package/dist/composed/spread.js.map +1 -1
  173. package/dist/composed/templates.d.ts +11 -0
  174. package/dist/composed/templates.d.ts.map +1 -1
  175. package/dist/composed/templates.js +51 -2
  176. package/dist/composed/templates.js.map +1 -1
  177. package/dist/composed/types.d.ts +43 -3
  178. package/dist/composed/types.d.ts.map +1 -1
  179. package/dist/composed/validate.d.ts.map +1 -1
  180. package/dist/composed/validate.js +5 -3
  181. package/dist/composed/validate.js.map +1 -1
  182. package/dist/configWarnings.d.ts +15 -0
  183. package/dist/configWarnings.d.ts.map +1 -1
  184. package/dist/configWarnings.js +40 -18
  185. package/dist/configWarnings.js.map +1 -1
  186. package/dist/contrast.d.ts +5 -3
  187. package/dist/contrast.d.ts.map +1 -1
  188. package/dist/contrast.js +46 -5
  189. package/dist/contrast.js.map +1 -1
  190. package/dist/defaults/headerFooter.d.ts.map +1 -1
  191. package/dist/defaults/headerFooter.js +4 -0
  192. package/dist/defaults/headerFooter.js.map +1 -1
  193. package/dist/defaults/paragraphStyles.d.ts +1 -1
  194. package/dist/defaults/paragraphStyles.d.ts.map +1 -1
  195. package/dist/defaults/paragraphStyles.js +5 -1
  196. package/dist/defaults/paragraphStyles.js.map +1 -1
  197. package/dist/defaults/resourceTypes.d.ts +15 -0
  198. package/dist/defaults/resourceTypes.d.ts.map +1 -1
  199. package/dist/defaults/resourceTypes.js +39 -1
  200. package/dist/defaults/resourceTypes.js.map +1 -1
  201. package/dist/design/layout.d.ts +7 -0
  202. package/dist/design/layout.d.ts.map +1 -1
  203. package/dist/design/layout.js +109 -16
  204. package/dist/design/layout.js.map +1 -1
  205. package/dist/design/slots.d.ts +12 -10
  206. package/dist/design/slots.d.ts.map +1 -1
  207. package/dist/design/slots.js +26 -2
  208. package/dist/design/slots.js.map +1 -1
  209. package/dist/fonts/faces.d.ts.map +1 -1
  210. package/dist/fonts/faces.js +6 -0
  211. package/dist/fonts/faces.js.map +1 -1
  212. package/dist/highlightMarks.d.ts.map +1 -1
  213. package/dist/highlightMarks.js +6 -0
  214. package/dist/highlightMarks.js.map +1 -1
  215. package/dist/html-backend.d.ts.map +1 -1
  216. package/dist/html-backend.js +83 -7
  217. package/dist/html-backend.js.map +1 -1
  218. package/dist/htmlComic.d.ts +3 -0
  219. package/dist/htmlComic.d.ts.map +1 -1
  220. package/dist/htmlComic.js +2 -0
  221. package/dist/htmlComic.js.map +1 -1
  222. package/dist/index.d.ts +12 -8
  223. package/dist/index.d.ts.map +1 -1
  224. package/dist/index.js +7 -3
  225. package/dist/index.js.map +1 -1
  226. package/dist/migrate/markdownRuns.d.ts +70 -0
  227. package/dist/migrate/markdownRuns.d.ts.map +1 -0
  228. package/dist/migrate/markdownRuns.js +215 -0
  229. package/dist/migrate/markdownRuns.js.map +1 -0
  230. package/dist/migrate/pageDesign.d.ts +152 -0
  231. package/dist/migrate/pageDesign.d.ts.map +1 -0
  232. package/dist/migrate/pageDesign.js +573 -0
  233. package/dist/migrate/pageDesign.js.map +1 -0
  234. package/dist/migrate/textToMarkdown.d.ts +156 -0
  235. package/dist/migrate/textToMarkdown.d.ts.map +1 -0
  236. package/dist/migrate/textToMarkdown.js +913 -0
  237. package/dist/migrate/textToMarkdown.js.map +1 -0
  238. package/dist/parse/blockParser.js +26 -0
  239. package/dist/parse/blockParser.js.map +1 -1
  240. package/dist/pipeline/build.d.ts.map +1 -1
  241. package/dist/pipeline/build.js +388 -41
  242. package/dist/pipeline/build.js.map +1 -1
  243. package/dist/pipeline/buildMeasurement.d.ts.map +1 -1
  244. package/dist/pipeline/buildMeasurement.js +12 -1
  245. package/dist/pipeline/buildMeasurement.js.map +1 -1
  246. package/dist/pipeline/calloutLayout.d.ts +25 -1
  247. package/dist/pipeline/calloutLayout.d.ts.map +1 -1
  248. package/dist/pipeline/calloutLayout.js +100 -2
  249. package/dist/pipeline/calloutLayout.js.map +1 -1
  250. package/dist/pipeline/composedLayout.d.ts +16 -2
  251. package/dist/pipeline/composedLayout.d.ts.map +1 -1
  252. package/dist/pipeline/composedLayout.js +337 -70
  253. package/dist/pipeline/composedLayout.js.map +1 -1
  254. package/dist/pipeline/contentWarnings.d.ts +15 -0
  255. package/dist/pipeline/contentWarnings.d.ts.map +1 -1
  256. package/dist/pipeline/contentWarnings.js +106 -1
  257. package/dist/pipeline/contentWarnings.js.map +1 -1
  258. package/dist/pipeline/dropCap.d.ts +5 -0
  259. package/dist/pipeline/dropCap.d.ts.map +1 -1
  260. package/dist/pipeline/dropCap.js +23 -2
  261. package/dist/pipeline/dropCap.js.map +1 -1
  262. package/dist/pipeline/frameLayout.d.ts +21 -3
  263. package/dist/pipeline/frameLayout.d.ts.map +1 -1
  264. package/dist/pipeline/frameLayout.js +52 -4
  265. package/dist/pipeline/frameLayout.js.map +1 -1
  266. package/dist/pipeline/headerFooter.d.ts +11 -1
  267. package/dist/pipeline/headerFooter.d.ts.map +1 -1
  268. package/dist/pipeline/headerFooter.js +210 -68
  269. package/dist/pipeline/headerFooter.js.map +1 -1
  270. package/dist/pipeline/measureContentBlock.d.ts +1 -1
  271. package/dist/pipeline/measureContentBlock.d.ts.map +1 -1
  272. package/dist/pipeline/measureContentBlock.js +4 -1
  273. package/dist/pipeline/measureContentBlock.js.map +1 -1
  274. package/dist/pipeline/mirrorFrame.d.ts.map +1 -1
  275. package/dist/pipeline/mirrorFrame.js +25 -2
  276. package/dist/pipeline/mirrorFrame.js.map +1 -1
  277. package/dist/pipeline/resourceLayout.d.ts +28 -0
  278. package/dist/pipeline/resourceLayout.d.ts.map +1 -1
  279. package/dist/pipeline/resourceLayout.js +21 -0
  280. package/dist/pipeline/resourceLayout.js.map +1 -1
  281. package/dist/pipeline/resourceNumbering.d.ts.map +1 -1
  282. package/dist/pipeline/resourceNumbering.js +13 -4
  283. package/dist/pipeline/resourceNumbering.js.map +1 -1
  284. package/dist/pipeline/styles.d.ts +1 -1
  285. package/dist/pipeline/styles.d.ts.map +1 -1
  286. package/dist/pipeline/styles.js +4 -0
  287. package/dist/pipeline/styles.js.map +1 -1
  288. package/dist/preflight.js +5 -1
  289. package/dist/preflight.js.map +1 -1
  290. package/dist/types.d.ts +45 -2
  291. package/dist/types.d.ts.map +1 -1
  292. package/dist/vdt.d.ts +228 -9
  293. package/dist/vdt.d.ts.map +1 -1
  294. package/dist/vdt.js.map +1 -1
  295. package/dist/word/toDocx.d.ts.map +1 -1
  296. package/dist/word/toDocx.js +14 -0
  297. package/dist/word/toDocx.js.map +1 -1
  298. package/dist/word/word.test.js +11 -1
  299. package/dist/word/word.test.js.map +1 -1
  300. package/package.json +1 -1
@@ -7,14 +7,16 @@ import { FRAME_ATTR } from '../composed/wrap';
7
7
  import { framePlaceable, nextFrameOf, groupPlaceable, placeBox, unionRect } from '../composed/geometry';
8
8
  import { composedOrder, groupOfFrames, layoutGroup } from '../composed/groups';
9
9
  import { reflowEvents } from '../composed/reflowLog';
10
- import { anchorTarget, continueInTemplate } from '../composed/templates';
10
+ import { anchorTarget, resolveContinueIn } from '../composed/templates';
11
+ import { collapsedOnAddedPage, collapseFrames, continuationTemplate, continuesCollapsed, continuesOffPage, trailingFrames } from '../composed/continuation';
11
12
  import { resolvePageTemplate } from '../composed/templates';
12
13
  import { frameElementId } from '../composed/ids';
14
+ import { frameLinePx, frameRuleBlocks } from '../composed/rules';
13
15
  import { offsetCalloutToAbsolute } from './calloutLayout';
14
16
  import { isMarkerBlock } from './buildHelpers';
15
- import { CUT_START, fragmentRange, makeCalloutLayouter, splitCalloutFragment } from './frameLayout';
17
+ import { CUT_START, fragmentRange, frameCutRules, makeCalloutLayouter, splitCalloutFragment } from './frameLayout';
16
18
  const MM_PER_IN = 25.4;
17
- const NO_FIT = { patches: new Map(), text: new Map(), pictures: new Map(), yieldAll: new Set() };
19
+ const NO_FIT = { patches: new Map(), text: new Map(), pictures: new Map(), yieldAll: new Set(), goOn: new Set() };
18
20
  /** Step of the text and picture scales the ladder tries (DESIGN §5.4). */
19
21
  const FIT_STEP = 0.025;
20
22
  /** Lowest text scale an `allowShrink` proposal goes to. */
@@ -46,18 +48,30 @@ const fixedHeight = (f) => {
46
48
  const h = f.height;
47
49
  return h !== null && typeof h === 'object' && 'value' in h && 'unit' in h ? f.height : undefined;
48
50
  };
49
- export function layoutComposedPage(input) {
50
- const { template, fence, dpi } = input;
51
- const fi = fence.fenceIndex;
52
- const mmPx = dpi / MM_PER_IN;
53
- const byName = new Map(template.frames.map((f) => [f.name, f]));
54
- const groupOf = groupOfFrames(template);
51
+ /** The frame before each frame of a chain on the page. On a page added by
52
+ * continuation (#859) a frame that receives content starts its chain. */
53
+ function chainPrev(template, receivers) {
54
+ const names = new Set(template.frames.map((f) => f.name));
55
55
  const prev = new Map();
56
56
  for (const f of template.frames) {
57
57
  const n = nextFrameOf(f);
58
- if (n && byName.has(n) && !prev.has(n))
58
+ if (n && names.has(n) && !prev.has(n) && !receivers?.has(n))
59
59
  prev.set(n, f.name);
60
60
  }
61
+ return prev;
62
+ }
63
+ const sameCut = (a, b) => a.child === b.child && a.line === b.line && JSON.stringify(a.group ?? null) === JSON.stringify(b.group ?? null);
64
+ export function layoutComposedPage(given) {
65
+ // A page added by continuation (#859) lays its template with the
66
+ // continuation's rules (and, for the template laid again, its other
67
+ // frames collapsed).
68
+ const input = given.continuing ? { ...given, template: continuationTemplate(given.template, given.continuing) } : given;
69
+ const { template, fence, dpi } = input;
70
+ const fi = fence.fenceIndex;
71
+ const mmPx = dpi / MM_PER_IN;
72
+ const byName = new Map(template.frames.map((f) => [f.name, f]));
73
+ const groupOf = groupOfFrames(template);
74
+ const prev = chainPrev(template, input.continuing?.receivers);
61
75
  const headOf = (name) => {
62
76
  let at = name;
63
77
  const seen = new Set([at]);
@@ -71,19 +85,97 @@ export function layoutComposedPage(input) {
71
85
  // twice.
72
86
  const memo = new Map();
73
87
  let runs = 0;
74
- const keyOf = (tpl, fit) => JSON.stringify([tpl.id, [...fit.patches], [...fit.text], [...fit.pictures], [...fit.yieldAll]]);
88
+ const keyOf = (tpl, fit) => JSON.stringify([tpl.id, [...fit.patches], [...fit.text], [...fit.pictures], [...fit.yieldAll], [...fit.goOn]]);
89
+ /** Frames a page added by continuation lays with no room (#859). */
90
+ const collapsedHere = input.continuing ? collapsedOnAddedPage(given.template, input.continuing) : new Set();
91
+ const holdsStory = (name) => input.stories.has(headOf(name));
92
+ /** The empty frames that go with the rest of a frame cut for a page
93
+ * laid from this template again (#912), by that frame. */
94
+ const travelling = (tpl, fit) => {
95
+ const out = new Map();
96
+ for (const f of tpl.frames) {
97
+ if (!fit.goOn.has(f.name) || !continuesCollapsed(tpl, f, input.continuing?.collapse ?? false))
98
+ continue;
99
+ const t = trailingFrames(tpl, f.name, holdsStory);
100
+ if (t.length > 0)
101
+ out.set(f.name, t);
102
+ }
103
+ return out;
104
+ };
75
105
  const compose = (fit, tpl = template) => {
76
106
  const key = keyOf(tpl, fit);
77
107
  const known = memo.get(key);
78
108
  if (known)
79
109
  return known;
80
110
  runs++;
81
- const c = composePage(input, patchTemplate(tpl, fit.patches), fit);
111
+ const travel = travelling(tpl, fit);
112
+ const lay = (leaving, whole = new Set()) => composePage(input, collapseFrames(patchTemplate(tpl, fit.patches), leaving, whole), fit, leaving.size > 0 ? new Set([...collapsedHere, ...leaving]) : collapsedHere);
113
+ const all = new Map(travel);
114
+ let c = lay(new Set([...travel.values()].flat()));
115
+ // The empty frames go with the rest only when there is a rest: a frame
116
+ // that holds all of it once they leave keeps them after it…
117
+ const staying = [...travel.keys()].filter((f) => !c.spills.has(f));
118
+ if (staying.length > 0) {
119
+ for (const f of staying)
120
+ travel.delete(f);
121
+ c = lay(new Set([...travel.values()].flat()));
122
+ // …unless they then do not fit under it at their size: the whole box
123
+ // goes on with them, and takes no room here.
124
+ const whole = staying.filter((f) => squeezes(c, f));
125
+ if (whole.length > 0) {
126
+ for (const f of whole)
127
+ travel.set(f, all.get(f));
128
+ c = lay(new Set([...whole, ...[...travel.values()].flat()]), new Set(whole));
129
+ }
130
+ }
131
+ // A box none of whose content fits goes on whole and takes no room
132
+ // here either.
133
+ const empty = [...fit.goOn].filter((f) => { const sp = c.spills.get(f); return sp !== undefined && !sp.advanced && sp.pieces === 0 && !frameIn(c, f)?.collapsed; });
134
+ if (empty.length > 0) {
135
+ for (const f of empty)
136
+ if (all.has(f))
137
+ travel.set(f, all.get(f));
138
+ c = lay(new Set([...empty, ...[...travel.values()].flat()]), new Set(empty));
139
+ }
82
140
  memo.set(key, c);
83
141
  return c;
84
142
  };
85
143
  const frameIn = (c, name) => c.frames.find((f) => f.name === name);
86
144
  const overflows = (c, name) => (frameIn(c, name)?.overflowPx ?? 0) > 0.5;
145
+ /** A frame that can go on to another page squeezes the frames after it
146
+ * in its stack below their declared size (#912): the stack reached the
147
+ * foot of its area and the last frames were cut to fit, an answer space
148
+ * under the box lost its lines. The ladder treats it as content that
149
+ * does not fit, so the box goes on and they keep their size. */
150
+ const squeezes = (c, name) => {
151
+ const frame = byName.get(name);
152
+ if (!frame || !continuesOffPage(frame.overflow.continueIn))
153
+ return false;
154
+ const g = groupOf.get(name);
155
+ if (!g || g.kind !== 'stack')
156
+ return false;
157
+ for (const other of g.frames.slice(g.frames.indexOf(name) + 1)) {
158
+ const f = byName.get(other);
159
+ const v = frameIn(c, other);
160
+ if (!f || !v || v.collapsed)
161
+ continue;
162
+ const min = f.minHeight ? dimensionToPx(f.minHeight, dpi, input.emPx) : fixedHeight(f) ? dimensionToPx(fixedHeight(f), dpi, input.emPx) : 0;
163
+ if (v.rect.height < min - 0.5)
164
+ return true;
165
+ }
166
+ return false;
167
+ };
168
+ /** The frame's content does not fit, or it squeezes the frames after it. */
169
+ const short = (c, name) => overflows(c, name) || squeezes(c, name);
170
+ /** The ladder's last step (#859): the frame is cut at its foot and the
171
+ * rest goes on on another page, its text and pictures back at full
172
+ * size (they go on anyway). */
173
+ const goingOn = (f, frame) => {
174
+ const text = new Map(f.text);
175
+ text.delete(headOf(frame.name));
176
+ const pictures = new Map([...f.pictures].filter(([, p]) => p.by !== frame.name));
177
+ return { ...f, text, pictures, goOn: new Set([...f.goOn, frame.name]) };
178
+ };
87
179
  // The ladder, frame by frame in template order (ties break there).
88
180
  let fit = NO_FIT;
89
181
  let cur = compose(fit);
@@ -101,7 +193,7 @@ export function layoutComposedPage(input) {
101
193
  const attempt = (next, name) => {
102
194
  fit = next;
103
195
  cur = compose(fit);
104
- return !overflows(cur, name);
196
+ return !short(cur, name);
105
197
  };
106
198
  /** The largest scale among `steps` (descending) at which `name` fits,
107
199
  * checked at the lowest first, then by bisection; adopted. */
@@ -114,7 +206,7 @@ export function layoutComposedPage(input) {
114
206
  let hi = steps.length - 1; // fits at hi
115
207
  while (lo < hi && runs < COMPOSE_BUDGET) {
116
208
  const mid = Math.floor((lo + hi) / 2);
117
- if (!overflows(compose(make(steps[mid])), name))
209
+ if (!short(compose(make(steps[mid])), name))
118
210
  hi = mid;
119
211
  else
120
212
  lo = mid + 1;
@@ -174,7 +266,7 @@ export function layoutComposedPage(input) {
174
266
  };
175
267
  for (const frame of template.frames) {
176
268
  const name = frame.name;
177
- if (!overflows(cur, name) || runs >= COMPOSE_BUDGET)
269
+ if (!short(cur, name) || runs >= COMPOSE_BUDGET)
178
270
  continue;
179
271
  const steps = [];
180
272
  tried.set(name, steps);
@@ -189,32 +281,57 @@ export function layoutComposedPage(input) {
189
281
  if (movesOthers(frame))
190
282
  steps.push('push');
191
283
  // 3. Pictures of the frame and its group, down to imageMinScale.
192
- const heads = frame.fit.scaleImages ? pictureHeads(frame) : [];
193
- if (heads.length > 0 && frame.fit.imageMinScale < 1) {
284
+ const scaleImages = () => {
285
+ const heads = frame.fit.scaleImages ? pictureHeads(frame) : [];
286
+ if (heads.length === 0 || frame.fit.imageMinScale >= 1)
287
+ return false;
194
288
  steps.push('scaleImages');
195
- if (searchScale(scaleSteps(frame.fit.imageMinScale), (s) => withPictures(fit, heads, s, name), name))
196
- continue;
197
- }
289
+ return searchScale(scaleSteps(frame.fit.imageMinScale), (s) => withPictures(fit, heads, s, name), name);
290
+ };
198
291
  // 4. Redistribute: every other frame of the group gives space (or
199
292
  // width) back, down to its minimum or its content.
200
- const g = groupOf.get(name);
201
- if (g && g.frames.filter((m) => byName.has(m)).length > 1) {
293
+ const redistribute = () => {
294
+ const g = groupOf.get(name);
295
+ if (!g || g.frames.filter((m) => byName.has(m)).length <= 1)
296
+ return false;
202
297
  steps.push('redistribute');
203
- if (attempt({ ...fit, yieldAll: new Set([...fit.yieldAll, g.name]) }, name))
204
- continue;
205
- }
298
+ return attempt({ ...fit, yieldAll: new Set([...fit.yieldAll, g.name]) }, name);
299
+ };
206
300
  // 5. The frame's text (its whole story), down to fit.minScale.
207
- if (frame.fit.shrinkText && frame.fit.minScale < 1 && holdsText(frame)) {
301
+ const shrinkText = () => {
302
+ if (!frame.fit.shrinkText || frame.fit.minScale >= 1 || !holdsText(frame))
303
+ return false;
208
304
  steps.push('shrinkText');
209
- const head = headOf(name);
210
- if (searchScale(scaleSteps(frame.fit.minScale), (s) => withText(fit, head, s), name))
211
- continue;
212
- }
213
- // 6. Continuation on another page (`overflow.continueIn: 'next-page'`
214
- // or `'template:<id>'`) is #859's: the step is reported, the content
215
- // still stops here.
216
- if (continueOnOtherPage(frame))
305
+ return searchScale(scaleSteps(frame.fit.minScale), (s) => withText(fit, headOf(name), s), name);
306
+ };
307
+ // 6. Continuation on another page (`overflow.continueIn: 'next-page'`,
308
+ // `'template:<id>'` or `'page:<id>'`, #859). On a page added by
309
+ // continuation, a frame that would take none of what it receives
310
+ // sends nothing on (it would go on for ever): it warns.
311
+ const goOn = () => {
312
+ if (!continuesOffPage(frame.overflow.continueIn))
313
+ return false;
217
314
  steps.push('continue');
315
+ const before = { fit, cur };
316
+ // (Only for a story that came from another page: a page's own story
317
+ // may go on whole.)
318
+ const received = input.continuing?.receivers.has(headOf(name)) ?? false;
319
+ if (attempt(goingOn(fit, frame), name) && (!received || cur.spills.get(name)?.advanced !== false))
320
+ return true;
321
+ ({ fit, cur } = before);
322
+ return false;
323
+ };
324
+ // A frame whose content can go on to another page goes on before its
325
+ // pictures and text shrink (`fit.preferContinue`, #920): a story keeps
326
+ // one size from page to page. The pictures and the text shrink only
327
+ // when it cannot go on (the last page continuation may add, or a
328
+ // receiver that takes none of it).
329
+ const order = continuesOffPage(frame.overflow.continueIn) && frame.fit.preferContinue
330
+ ? [redistribute, goOn, scaleImages, shrinkText]
331
+ : [scaleImages, redistribute, shrinkText, goOn];
332
+ for (const step of order)
333
+ if (step())
334
+ break;
218
335
  }
219
336
  // 7. Warn, with what would make each frame that still overflows fit.
220
337
  const warnings = [...(cur.notes ?? [])];
@@ -278,12 +395,34 @@ export function layoutComposedPage(input) {
278
395
  sourceStart: contentBlocks[input.startIdx].sourceStart + input.bodyOffset,
279
396
  sourceEnd: contentBlocks[input.endIdx].sourceEnd + input.bodyOffset,
280
397
  };
281
- return { composed, blocks, warnings, laid, ...(fitted.size > 0 ? { fitted } : {}) };
282
- }
283
- /** The frame sends what does not fit to another page (#859). */
284
- function continueOnOtherPage(frame) {
285
- const to = frame.overflow.continueIn;
286
- return to === 'next-page' || continueInTemplate(to) !== undefined;
398
+ // What goes on on other pages (#859), in template order.
399
+ const continuations = [];
400
+ const trailing = new Map([...travelling(template, fit)].filter(([f, names]) => cur.spills.has(f) && names.every((n) => frameIn(cur, n)?.collapsed)));
401
+ for (const frame of template.frames) {
402
+ const spill = cur.spills.get(frame.name);
403
+ const story = spill ? input.stories.get(spill.head) : undefined;
404
+ if (!spill || !story)
405
+ continue;
406
+ const came = input.continuing?.receivers.get(spill.head);
407
+ continuations.push({
408
+ frame: frame.name,
409
+ fromFrameId: spill.frameId,
410
+ head: spill.head,
411
+ // `{attr.x}` read from the fence (#917).
412
+ rule: resolveContinueIn(frame.overflow.continueIn, fence.attrs),
413
+ template: template.id,
414
+ collapsed: input.continuing?.collapse ?? false,
415
+ story,
416
+ cut: spill.cut,
417
+ pieces: spill.pieces,
418
+ headBlockId: spill.headBlockId,
419
+ // A story that jumped here (#917) goes on named by this page's frame.
420
+ slot: (input.continuing?.jump ? undefined : came?.slot) ?? frame.name,
421
+ ...(trailing.has(frame.name) ? { trailing: trailing.get(frame.name) } : {}),
422
+ ...(frame.overflow.fromMark !== undefined ? { fromMark: frame.overflow.fromMark } : {}),
423
+ });
424
+ }
425
+ return { composed, blocks, warnings, laid, ...(fitted.size > 0 ? { fitted } : {}), ...(continuations.length > 0 ? { continuations } : {}) };
287
426
  }
288
427
  const mmDim = (value) => ({ value, unit: 'mm' });
289
428
  /** What would make `frame`'s content fit, each value worked out from the
@@ -315,9 +454,12 @@ function overflowProposals(c) {
315
454
  ...(frame.fit.shrinkText ? {} : { also: [{ path: path('fit/shrinkText'), value: true }] }),
316
455
  });
317
456
  }
457
+ // A frame that holds content (an empty one is short of its own box, #911:
458
+ // more height, text or pages do not help it).
459
+ const holds = cur.needs.has(name);
318
460
  // A higher maximum: the frame's height plus the shortfall, in whole mm.
319
461
  const fixed = fixedHeight(frame);
320
- if (frame.maxHeight || fixed) {
462
+ if (holds && (frame.maxHeight || fixed)) {
321
463
  const want = Math.ceil((vdt.rect.height + vdt.overflowPx) / mmPx - 1e-6);
322
464
  const patch = { maxHeight: mmDim(want), ...(fixed ? { height: 'auto', minHeight: fixed } : {}) };
323
465
  if (fits(c.patched(fit, name, patch)))
@@ -330,7 +472,9 @@ function overflowProposals(c) {
330
472
  if (g) {
331
473
  for (const other of g.frames) {
332
474
  const f = c.byName.get(other);
333
- if (!f || other === name || f.pin || !f.minHeight)
475
+ // An empty frame's minimum is the room it stands for (an answer
476
+ // space, a picture slot), not room to take (#911).
477
+ if (!f || other === name || f.pin || !f.minHeight || !cur.needs.has(other))
334
478
  continue;
335
479
  const box = cur.frames.find((x) => x.name === other);
336
480
  if (!box)
@@ -347,8 +491,16 @@ function overflowProposals(c) {
347
491
  }
348
492
  }
349
493
  }
494
+ // An empty frame cut short by its group: a lower minimum of its own, the
495
+ // height it was left (#911).
496
+ if (!holds && g && frame.minHeight) {
497
+ const lowered = Math.floor(vdt.rect.height / mmPx + 1e-6);
498
+ if (lowered * mmPx < dimensionToPx(frame.minHeight, c.dpi, c.emPx) - 0.5 && fits(c.patched(fit, name, { minHeight: mmDim(lowered) }))) {
499
+ out.push({ kind: 'lowerMin', path: path('minHeight'), value: mmDim(lowered), frame: name });
500
+ }
501
+ }
350
502
  // Continue on the next page (#859 lays it out).
351
- if (!frame.overflow.continueIn)
503
+ if (holds && !frame.overflow.continueIn)
352
504
  out.push({ kind: 'continueNextPage', path: path('overflow/continueIn'), value: 'next-page' });
353
505
  // Another template of the book with a frame of this name, where the
354
506
  // content fits as it is.
@@ -403,17 +555,17 @@ export function keepChromeAt(style, scale) {
403
555
  };
404
556
  }
405
557
  /** Lay out every frame of the page once, with the reflow ladder's `fit`. */
406
- function composePage(input, template, fit) {
558
+ function composePage(input, template, fit, collapsed) {
407
559
  const { pass, fence, dpi } = input;
408
560
  const fi = fence.fenceIndex;
409
- const frameIdOf = (name) => frameElementId(fi, name);
561
+ const cont = input.continuing;
562
+ const cutRules = frameCutRules(pass.resolved);
563
+ /** A frame's id: `cp<fence>:<name>`; on a page added by continuation
564
+ * (#859) `~<k>`, a frame that receives content named by the frame of
565
+ * the fence's page that first sent it on. */
566
+ const frameIdOf = (name) => (cont && !cont.jump ? frameElementId(fi, cont.receivers.get(name)?.slot ?? name, cont.generation) : frameElementId(fi, name));
410
567
  const byName = new Map(template.frames.map((f) => [f.name, f]));
411
- const prev = new Map();
412
- for (const f of template.frames) {
413
- const n = nextFrameOf(f);
414
- if (n && byName.has(n) && !prev.has(n))
415
- prev.set(n, f.name);
416
- }
568
+ const prev = chainPrev(template, cont?.receivers);
417
569
  /** The head of the chain a frame belongs to. */
418
570
  const headOf = (name) => {
419
571
  let at = name;
@@ -426,8 +578,29 @@ function composePage(input, template, fit) {
426
578
  };
427
579
  /** The text scale of a frame's story (#858). */
428
580
  const textScale = (frame) => fit.text.get(headOf(frame.name)) ?? 1;
581
+ /** A page number a mark's `{page}` is laid out with (#917): a guess of
582
+ * the right width, the build writing the true one once pages are
583
+ * numbered (`VDTDesignTextBlock.pageMark`). */
584
+ const toGuess = String(input.page.index + 2);
585
+ const fromGuess = String(Math.max(1, input.page.index));
429
586
  /** The style a frame is laid out in: its box as drawn at any text scale. */
430
- const styleOf = (frame) => keepChromeAt(input.styleOf(frame), textScale(frame));
587
+ const styleOf = (frame) => {
588
+ const style = keepChromeAt(input.styleOf(frame), textScale(frame));
589
+ if (!fit.goOn.has(frame.name))
590
+ return style;
591
+ // A frame cut for another page (#859) ends with its mark: its own
592
+ // words, none (`false`), or its style's localized ones.
593
+ const mark = frame.overflow.mark;
594
+ return { ...style, continuesMarkerEnabled: mark !== false, continuesMarker: typeof mark === 'string' ? withPage(mark, toGuess) : style.continuesMarker };
595
+ };
596
+ /** The mark of a frame that receives a story from another page (#917):
597
+ * its own `overflow.fromMark`, else the sending frame's, set at the
598
+ * head of the first piece it lays here. */
599
+ const fromMarkOf = (frame) => {
600
+ const came = cont?.receivers.get(headOf(frame.name));
601
+ const words = frame.overflow.fromMark ?? came?.fromMark;
602
+ return came && words !== undefined ? withPage(words, fromGuess) : undefined;
603
+ };
431
604
  /** The scale of a frame's pictures (#858), below 1, or undefined. */
432
605
  const pictureScale = (frame) => fit.pictures.get(headOf(frame.name))?.scale;
433
606
  /** Rectangles in a frame's box coordinates, in its laid-out (scaled)
@@ -454,13 +627,16 @@ function composePage(input, template, fit) {
454
627
  const headFrame = byName.get(head);
455
628
  if (!story || !headFrame)
456
629
  return undefined;
630
+ // Content from the page before (#859) goes on where it was cut, its
631
+ // blocks numbered on from its pieces there.
632
+ const came = cont?.receivers.get(head);
457
633
  const st = {
458
634
  story,
459
635
  L: makeCalloutLayouter(pass, story.startIdx, story.plan, styleOf(frame)),
460
- cut: CUT_START,
461
- pieces: 0,
462
- headBlockId: frameIdOf(head),
463
- headFrameId: frameIdOf(head),
636
+ cut: came?.cut ?? CUT_START,
637
+ pieces: came?.pieces ?? 0,
638
+ headBlockId: came?.headBlockId ?? frameIdOf(head),
639
+ headFrameId: came?.fromFrameId ?? frameIdOf(head),
464
640
  };
465
641
  states.set(head, st);
466
642
  return st;
@@ -488,6 +664,8 @@ function composePage(input, template, fit) {
488
664
  const declaredRefOf = (name) => declared.get(name) ?? groupRect(name, declared, groupDeclared, declaredRefOf);
489
665
  /** Pinned frames laid so far: a group's frames flow round them. */
490
666
  const pinned = [];
667
+ /** How far a stack cut each frame below its least height (#911). */
668
+ const boxShort = new Map();
491
669
  const frames = [];
492
670
  const vdtByName = new Map();
493
671
  const blocks = [];
@@ -497,6 +675,7 @@ function composePage(input, template, fit) {
497
675
  /** What `fitBox` did to each frame (#861), last placement wins. */
498
676
  const fitted = new Map();
499
677
  const laid = new Set();
678
+ const spills = new Map();
500
679
  const pushed = seed
501
680
  ? [...seed].map(([name, box]) => ({ name, box, ...(seedShapes?.get(name) ? { contour: seedShapes.get(name) } : {}) }))
502
681
  : [];
@@ -536,7 +715,10 @@ function composePage(input, template, fit) {
536
715
  const holds = reached && !atEnd(st);
537
716
  const continuation = st !== undefined && st.pieces > 0;
538
717
  const blockId = st && st.pieces > 0 ? `${st.headBlockId}-cont-${st.pieces}` : frameIdOf(frame.name);
539
- return { frame, st, before, holds, continuation, blockId };
718
+ // The first piece here of a story that came from another page.
719
+ const came = cont?.receivers.get(headOf(frame.name));
720
+ const fromMark = st && came && continuation && st.pieces === came.pieces ? fromMarkOf(frame) : undefined;
721
+ return { frame, st, before, holds, continuation, blockId, ...(fromMark !== undefined ? { fromMark } : {}) };
540
722
  };
541
723
  /** The content's height at `width`, running round the pushing frames
542
724
  * below the top of `at` (the frame's box as first placed). A frame
@@ -554,8 +736,8 @@ function composePage(input, template, fit) {
554
736
  if (known !== undefined)
555
737
  return known;
556
738
  const exclusions = at ? unscale(exclusionsFor(p.frame, { ...at, width }, true), s) : undefined;
557
- const opts = { ...(exclusions ? { exclusions } : {}), ...(pic !== undefined ? { pictureScale: pic } : {}) };
558
- const r = p.st.L.layoutRange(p.st.cut, p.st.L.end, width / s, p.blockId, p.continuation, input.mirrored, opts.exclusions || opts.pictureScale !== undefined ? opts : undefined);
739
+ const opts = { ...(exclusions ? { exclusions } : {}), ...(pic !== undefined ? { pictureScale: pic } : {}), ...(p.fromMark !== undefined ? { continuedMark: p.fromMark } : {}) };
740
+ const r = p.st.L.layoutRange(p.st.cut, p.st.L.end, width / s, p.blockId, p.continuation, input.mirrored, opts.exclusions || opts.pictureScale !== undefined || opts.continuedMark !== undefined ? opts : undefined);
559
741
  const h = r.totalHeight * s;
560
742
  memo.set(key, h);
561
743
  return h;
@@ -652,6 +834,7 @@ function composePage(input, template, fit) {
652
834
  pin: f.pin,
653
835
  fixedHeight: !auto && !fill,
654
836
  need: natural,
837
+ floor: minH,
655
838
  };
656
839
  if (yieldAll && mode === 'final' && !f.pin) {
657
840
  // Down to its minimum, never under what its content needs, never
@@ -663,6 +846,14 @@ function composePage(input, template, fit) {
663
846
  if (group.kind === 'stack')
664
847
  item.min = Math.min(height, Math.max(minH, natural(box.width)));
665
848
  }
849
+ if (fit.goOn.has(f.name) && mode === 'final' && !f.pin) {
850
+ // A frame cut for another page (#859) gives the room first,
851
+ // down to its minimum: the frames after it in the group keep
852
+ // their size (#912), and its rest goes on.
853
+ item.shrink = Math.max(f.shrink, 1);
854
+ item.priority = Number.NEGATIVE_INFINITY;
855
+ item.min = minH;
856
+ }
666
857
  return item;
667
858
  });
668
859
  const firstFree = items.find((it) => !it.pin) ?? items[0];
@@ -684,7 +875,7 @@ function composePage(input, template, fit) {
684
875
  for (const it of items)
685
876
  if (it.pin)
686
877
  out.set(it.name, it.own);
687
- return { rects: out, box: ownBox ?? unionRect([...out.values()]) ?? box };
878
+ return { rects: out, box: ownBox ?? unionRect([...out.values()]) ?? box, items };
688
879
  };
689
880
  let naturals = preps.map((p) => naturalOf(p));
690
881
  let fin = solve('final', naturals);
@@ -696,6 +887,20 @@ function composePage(input, template, fit) {
696
887
  fin = solve('final', naturals);
697
888
  }
698
889
  const decl = solve('declared', naturals);
890
+ // A frame the stack cut below the least it may have (its
891
+ // `minHeight`, a fixed height its own) is short of its box, content
892
+ // or none (#911): an empty answer space or picture slot overflows
893
+ // too.
894
+ for (const it of fin.items) {
895
+ if (it.pin || group.kind !== 'stack')
896
+ continue;
897
+ const least = it.fixedHeight && (it.floor ?? 0) <= 0.5 ? it.own.height : it.floor ?? 0;
898
+ const got = fin.rects.get(it.name).height;
899
+ if (least - got > 0.5)
900
+ boxShort.set(it.name, least - got);
901
+ else
902
+ boxShort.delete(it.name);
903
+ }
699
904
  for (const m of members) {
700
905
  rects.set(m.name, fin.rects.get(m.name));
701
906
  declared.set(m.name, decl.rects.get(m.name));
@@ -718,6 +923,7 @@ function composePage(input, template, fit) {
718
923
  const laidH = rect.height / s;
719
924
  let result;
720
925
  let overflowPx = 0;
926
+ let goesOn = false;
721
927
  const from = st?.cut ?? CUT_START;
722
928
  if (holds) {
723
929
  // What its content needs at its width, measured only when a
@@ -727,22 +933,29 @@ function composePage(input, template, fit) {
727
933
  let known;
728
934
  needs.set(frame.name, () => (known ??= naturalOf(startAt)(width)));
729
935
  const exclusions = unscale(exclusionsFor(frame, rect, false), s);
730
- const extra = { ...(exclusions ? { exclusions } : {}), ...(pic !== undefined ? { pictureScale: pic } : {}) };
936
+ const extra = { ...(exclusions ? { exclusions } : {}), ...(pic !== undefined ? { pictureScale: pic } : {}), ...(p.fromMark !== undefined ? { continuedMark: p.fromMark } : {}) };
731
937
  const opts = { boxHeight: laidH, verticalAlign: frame.verticalAlign, ...extra };
732
938
  const whole = st.L.layoutRange(from, st.L.end, laidW, blockId, continuation, input.mirrored, opts);
733
939
  const nextName = nextFrameOf(frame);
734
940
  const nextFrame = nextName !== undefined ? byName.get(nextName) : undefined;
735
- if ((whole.overflowPx ?? 0) > 0.5 && nextFrame && !vdtByName.has(nextFrame.name)) {
736
- // The rest goes on in the next frame of the chain.
941
+ const chained = nextFrame !== undefined && !vdtByName.has(nextFrame.name);
942
+ // Cut for another page (#859) when the chain ends here.
943
+ goesOn = !chained && fit.goOn.has(frame.name);
944
+ if ((whole.overflowPx ?? 0) > 0.5 && (chained || goesOn)) {
945
+ // The rest goes on in the next frame of the chain, or on another
946
+ // page.
737
947
  const minLines = Math.max(1, style.splitMinLines);
738
- const frag = splitCalloutFragment(pass, st.L, from, laidW, laidH, blockId, continuation, minLines, input.mirrored, extra.exclusions || extra.pictureScale !== undefined ? extra : undefined);
948
+ // A heading stays with what follows it, a list keeps two items
949
+ // on each side of the cut (#913).
950
+ const frag = splitCalloutFragment(pass, st.L, from, laidW, laidH, blockId, continuation, minLines, input.mirrored, extra.exclusions || extra.pictureScale !== undefined || extra.continuedMark !== undefined ? extra : undefined, cutRules);
739
951
  const to = frag?.to ?? from;
740
- result = frag
741
- ? st.L.layoutRange(from, to, laidW, blockId, continuation, input.mirrored, opts)
742
- : undefined;
952
+ // A frame none of whose content fits draws nothing: the whole box
953
+ // goes on (#912), not a tab and a mark over an empty piece.
954
+ result = frag ? st.L.layoutRange(from, to, laidW, blockId, continuation, input.mirrored, opts) : undefined;
743
955
  st.cut = to;
744
956
  }
745
957
  else {
958
+ goesOn = false;
746
959
  result = whole;
747
960
  overflowPx = (whole.overflowPx ?? 0) * s;
748
961
  st.cut = st.L.end;
@@ -752,6 +965,26 @@ function composePage(input, template, fit) {
752
965
  // A styled frame with nothing (left) to hold still draws its box.
753
966
  result = st.L.layoutRange(st.L.end, st.L.end, laidW, blockId, true, input.mirrored, { boxHeight: laidH });
754
967
  }
968
+ // A frame cut below its least height overflows by the cut, holding
969
+ // content or not (#911).
970
+ overflowPx = Math.max(overflowPx, boxShort.get(frame.name) ?? 0);
971
+ // Ruled lines (#914): on the box, over its background, in its
972
+ // laid-out coordinates; an empty frame keeps them for the decoration.
973
+ let emptyRules;
974
+ if (frame.rules) {
975
+ const linePx = frameLinePx(style, dpi);
976
+ if (result?.frame.designOverlay && result.frame.callout) {
977
+ result.frame.designOverlay.blocks.push(...frameRuleBlocks(frame.rules, result.frame.callout.innerRect, linePx, s, dpi, input.emPx));
978
+ }
979
+ else if (!result) {
980
+ const pad = (d) => dimensionToPx(d, dpi, input.emPx);
981
+ const p = style.padding;
982
+ const inner = { x: rect.x + pad(p.left), y: rect.y + pad(p.top), width: rect.width - pad(p.left) - pad(p.right), height: rect.height - pad(p.top) - pad(p.bottom) };
983
+ const blocks = frameRuleBlocks(frame.rules, inner, linePx, 1, dpi, input.emPx);
984
+ if (blocks.length > 0)
985
+ emptyRules = blocks;
986
+ }
987
+ }
755
988
  const vdt = {
756
989
  id: frameIdOf(frame.name),
757
990
  name: frame.name,
@@ -765,6 +998,8 @@ function composePage(input, template, fit) {
765
998
  blockIds: [],
766
999
  configPath: frame.configPath,
767
1000
  ...(frame.exclude === 'push' ? { push: true } : {}),
1001
+ ...(collapsed.has(frame.name) ? { collapsed: true } : {}),
1002
+ ...(emptyRules ? { rules: emptyRules } : {}),
768
1003
  };
769
1004
  const decl = declared.get(frame.name);
770
1005
  if (decl && !sameRect(decl, rect))
@@ -775,6 +1010,18 @@ function composePage(input, template, fit) {
775
1010
  if (q)
776
1011
  q.continuesTo = vdt.id;
777
1012
  }
1013
+ else if (holds && cont?.receivers.has(frame.name)) {
1014
+ // Content from the page before (#859).
1015
+ vdt.continuedFrom = cont.receivers.get(frame.name).fromFrameId;
1016
+ }
1017
+ if (result) {
1018
+ // The marks whose `{page}` the build fills in (#917).
1019
+ if (goesOn && typeof frame.overflow.mark === 'string' && frame.overflow.mark.includes('{page}'))
1020
+ tagPageMark(result.frame, withPage(frame.overflow.mark, toGuess), frame.overflow.mark, 'to');
1021
+ const fromWords = frame.overflow.fromMark ?? cont?.receivers.get(headOf(frame.name))?.fromMark;
1022
+ if (p.fromMark !== undefined && fromWords?.includes('{page}'))
1023
+ tagPageMark(result.frame, p.fromMark, fromWords, 'from');
1024
+ }
778
1025
  if (result && st) {
779
1026
  const range = fragmentRange(st.L, from, st.cut);
780
1027
  commitFrame(result, rect, st.story, range, input, vdt, blocks);
@@ -789,6 +1036,11 @@ function composePage(input, template, fit) {
789
1036
  if (s !== 1 && holds)
790
1037
  fitLog.push({ frame: frame.name, kind: 'textScaled', scale: s });
791
1038
  }
1039
+ if (goesOn && st) {
1040
+ // Laid here even when none of it fits: the rest goes on (#912).
1041
+ laid.add(headOf(frame.name));
1042
+ spills.set(frame.name, { head: headOf(frame.name), cut: st.cut, advanced: !sameCut(from, st.cut), pieces: st.pieces, headBlockId: st.headBlockId, frameId: vdt.id });
1043
+ }
792
1044
  if (overflowPx > 0.5) {
793
1045
  const overflowMm = Math.round((overflowPx / dpi) * MM_PER_IN * 10) / 10;
794
1046
  overflowLog.push({ frame: frame.name, kind: 'overflow', toMm: overflowMm });
@@ -828,11 +1080,11 @@ function composePage(input, template, fit) {
828
1080
  fill(prepare(f), solved.get(f.name));
829
1081
  }
830
1082
  const log = mergeLog(reflowEvents(motionsOf(template, rects, declared), dpi / MM_PER_IN), [...fitLog, ...overflowLog], template);
831
- return { frames, blocks, log, laid, needs, pushRects, pushShapes, fitted, notes };
1083
+ return { frames, blocks, log, laid, needs, pushRects, pushShapes, fitted, notes, spills };
832
1084
  };
833
1085
  const first = run();
834
- const { frames, blocks, log, laid, needs, fitted, notes } = first.pushRects.size > 0 ? run(first.pushRects, first.pushShapes) : first;
835
- return { frames, blocks, log, laid, needs, template, fitted, ...(notes.length > 0 ? { notes } : {}) };
1086
+ const { frames, blocks, log, laid, needs, fitted, notes, spills } = first.pushRects.size > 0 ? run(first.pushRects, first.pushShapes) : first;
1087
+ return { frames, blocks, log, laid, needs, template, fitted, spills, ...(notes.length > 0 ? { notes } : {}) };
836
1088
  }
837
1089
  const sameRect = (a, b) => Math.abs(a.x - b.x) < 0.01 && Math.abs(a.y - b.y) < 0.01 && Math.abs(a.width - b.width) < 0.01 && Math.abs(a.height - b.height) < 0.01;
838
1090
  /** Each laid frame's declared and final boxes, in template order, with
@@ -1002,4 +1254,19 @@ export function composedPageAt(pages, idx) {
1002
1254
  return p;
1003
1255
  return undefined;
1004
1256
  }
1257
+ /** `words` with `{page}` as `page` (#917). */
1258
+ const withPage = (words, page) => words.split('{page}').join(page);
1259
+ /** Mark the design text of `box` that sets `shown` as a continuation mark
1260
+ * whose `{page}` the build fills in once pages are numbered (#917). */
1261
+ function tagPageMark(box, shown, text, page) {
1262
+ const want = shown.trim();
1263
+ for (const b of box.designOverlay?.blocks ?? []) {
1264
+ if (b.kind !== 'text' || !b.artifact || b.pageMark)
1265
+ continue;
1266
+ if (b.lines.map((l) => l.text).join(' ').trim() !== want && b.lines.map((l) => l.text).join('').trim() !== want)
1267
+ continue;
1268
+ b.pageMark = { text: text.trim(), page };
1269
+ return;
1270
+ }
1271
+ }
1005
1272
  //# sourceMappingURL=composedLayout.js.map