postext 1.34.0 → 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 (202) hide show
  1. package/dist/__tests__/bundle.test.js +33 -1
  2. package/dist/__tests__/bundle.test.js.map +1 -1
  3. package/dist/__tests__/codeBlocks.test.js +88 -0
  4. package/dist/__tests__/codeBlocks.test.js.map +1 -1
  5. package/dist/__tests__/composed/fixtures/page8.d.ts +20 -0
  6. package/dist/__tests__/composed/fixtures/page8.d.ts.map +1 -0
  7. package/dist/__tests__/composed/fixtures/page8.js +159 -0
  8. package/dist/__tests__/composed/fixtures/page8.js.map +1 -0
  9. package/dist/__tests__/composed/layout.test.d.ts +2 -0
  10. package/dist/__tests__/composed/layout.test.d.ts.map +1 -0
  11. package/dist/__tests__/composed/layout.test.js +387 -0
  12. package/dist/__tests__/composed/layout.test.js.map +1 -0
  13. package/dist/__tests__/composed/slotChecks.test.js +6 -3
  14. package/dist/__tests__/composed/slotChecks.test.js.map +1 -1
  15. package/dist/__tests__/composedPaint.test.d.ts +2 -0
  16. package/dist/__tests__/composedPaint.test.d.ts.map +1 -0
  17. package/dist/__tests__/composedPaint.test.js +191 -0
  18. package/dist/__tests__/composedPaint.test.js.map +1 -0
  19. package/dist/__tests__/defaults/stripRoundTrip.test.js +3 -2
  20. package/dist/__tests__/defaults/stripRoundTrip.test.js.map +1 -1
  21. package/dist/__tests__/exports.test.js +12 -0
  22. package/dist/__tests__/exports.test.js.map +1 -1
  23. package/dist/__tests__/highlightMarks.test.d.ts +2 -0
  24. package/dist/__tests__/highlightMarks.test.d.ts.map +1 -0
  25. package/dist/__tests__/highlightMarks.test.js +169 -0
  26. package/dist/__tests__/highlightMarks.test.js.map +1 -0
  27. package/dist/__tests__/tocPartLeader.test.d.ts +2 -0
  28. package/dist/__tests__/tocPartLeader.test.d.ts.map +1 -0
  29. package/dist/__tests__/tocPartLeader.test.js +133 -0
  30. package/dist/__tests__/tocPartLeader.test.js.map +1 -0
  31. package/dist/bundle/api.d.ts +8 -0
  32. package/dist/bundle/api.d.ts.map +1 -1
  33. package/dist/bundle/api.js +6 -3
  34. package/dist/bundle/api.js.map +1 -1
  35. package/dist/bundle/codec.d.ts +5 -0
  36. package/dist/bundle/codec.d.ts.map +1 -1
  37. package/dist/bundle/codec.js +4 -0
  38. package/dist/bundle/codec.js.map +1 -1
  39. package/dist/bundle/index.d.ts +2 -2
  40. package/dist/bundle/index.d.ts.map +1 -1
  41. package/dist/bundle/index.js +1 -1
  42. package/dist/bundle/index.js.map +1 -1
  43. package/dist/bundle/manifest.d.ts +14 -0
  44. package/dist/bundle/manifest.d.ts.map +1 -1
  45. package/dist/bundle/manifest.js +13 -0
  46. package/dist/bundle/manifest.js.map +1 -1
  47. package/dist/bundle/types.d.ts +7 -0
  48. package/dist/bundle/types.d.ts.map +1 -1
  49. package/dist/canvas-backend/annotations.d.ts +6 -0
  50. package/dist/canvas-backend/annotations.d.ts.map +1 -1
  51. package/dist/canvas-backend/annotations.js +39 -0
  52. package/dist/canvas-backend/annotations.js.map +1 -1
  53. package/dist/canvas-backend/blockRender.d.ts.map +1 -1
  54. package/dist/canvas-backend/blockRender.js +6 -1
  55. package/dist/canvas-backend/blockRender.js.map +1 -1
  56. package/dist/canvas-backend/composed.d.ts +15 -0
  57. package/dist/canvas-backend/composed.d.ts.map +1 -0
  58. package/dist/canvas-backend/composed.js +41 -0
  59. package/dist/canvas-backend/composed.js.map +1 -0
  60. package/dist/canvas-backend/index.d.ts.map +1 -1
  61. package/dist/canvas-backend/index.js +15 -1
  62. package/dist/canvas-backend/index.js.map +1 -1
  63. package/dist/canvas-backend/renderResourceBlock.d.ts.map +1 -1
  64. package/dist/canvas-backend/renderResourceBlock.js +4 -1
  65. package/dist/canvas-backend/renderResourceBlock.js.map +1 -1
  66. package/dist/composed/geometry.d.ts +108 -0
  67. package/dist/composed/geometry.d.ts.map +1 -0
  68. package/dist/composed/geometry.js +241 -0
  69. package/dist/composed/geometry.js.map +1 -0
  70. package/dist/composed/paint.d.ts +41 -0
  71. package/dist/composed/paint.d.ts.map +1 -0
  72. package/dist/composed/paint.js +83 -0
  73. package/dist/composed/paint.js.map +1 -0
  74. package/dist/composed/types.d.ts +1 -44
  75. package/dist/composed/types.d.ts.map +1 -1
  76. package/dist/composed/wrap.d.ts +52 -0
  77. package/dist/composed/wrap.d.ts.map +1 -0
  78. package/dist/composed/wrap.js +234 -0
  79. package/dist/composed/wrap.js.map +1 -0
  80. package/dist/configWarnings.js +1 -1
  81. package/dist/configWarnings.js.map +1 -1
  82. package/dist/defaults/codeStyle.d.ts.map +1 -1
  83. package/dist/defaults/codeStyle.js +1 -0
  84. package/dist/defaults/codeStyle.js.map +1 -1
  85. package/dist/defaults/index.d.ts +1 -0
  86. package/dist/defaults/index.d.ts.map +1 -1
  87. package/dist/defaults/index.js +9 -0
  88. package/dist/defaults/index.js.map +1 -1
  89. package/dist/defaults/markStyles.d.ts +23 -0
  90. package/dist/defaults/markStyles.d.ts.map +1 -0
  91. package/dist/defaults/markStyles.js +80 -0
  92. package/dist/defaults/markStyles.js.map +1 -0
  93. package/dist/defaults/shared.d.ts.map +1 -1
  94. package/dist/defaults/shared.js +12 -0
  95. package/dist/defaults/shared.js.map +1 -1
  96. package/dist/design/bidiText.d.ts +4 -1
  97. package/dist/design/bidiText.d.ts.map +1 -1
  98. package/dist/design/bidiText.js +5 -2
  99. package/dist/design/bidiText.js.map +1 -1
  100. package/dist/design/layout.d.ts +10 -0
  101. package/dist/design/layout.d.ts.map +1 -1
  102. package/dist/design/layout.js +110 -8
  103. package/dist/design/layout.js.map +1 -1
  104. package/dist/design/placeholders.d.ts +28 -2
  105. package/dist/design/placeholders.d.ts.map +1 -1
  106. package/dist/design/placeholders.js +24 -1
  107. package/dist/design/placeholders.js.map +1 -1
  108. package/dist/design/richText.d.ts +2 -0
  109. package/dist/design/richText.d.ts.map +1 -1
  110. package/dist/design/richText.js.map +1 -1
  111. package/dist/highlightMarks.d.ts +33 -0
  112. package/dist/highlightMarks.d.ts.map +1 -0
  113. package/dist/highlightMarks.js +209 -0
  114. package/dist/highlightMarks.js.map +1 -0
  115. package/dist/html-backend.d.ts.map +1 -1
  116. package/dist/html-backend.js +65 -8
  117. package/dist/html-backend.js.map +1 -1
  118. package/dist/htmlAnnotations.d.ts +5 -0
  119. package/dist/htmlAnnotations.d.ts.map +1 -1
  120. package/dist/htmlAnnotations.js +16 -0
  121. package/dist/htmlAnnotations.js.map +1 -1
  122. package/dist/index.d.ts +6 -4
  123. package/dist/index.d.ts.map +1 -1
  124. package/dist/index.js +4 -2
  125. package/dist/index.js.map +1 -1
  126. package/dist/knuthPlass/richAdapter.d.ts +3 -0
  127. package/dist/knuthPlass/richAdapter.d.ts.map +1 -1
  128. package/dist/knuthPlass/richAdapter.js +3 -2
  129. package/dist/knuthPlass/richAdapter.js.map +1 -1
  130. package/dist/measure/cache.js +3 -3
  131. package/dist/measure/cache.js.map +1 -1
  132. package/dist/measure/chipEdges.d.ts +13 -1
  133. package/dist/measure/chipEdges.d.ts.map +1 -1
  134. package/dist/measure/chipEdges.js +59 -1
  135. package/dist/measure/chipEdges.js.map +1 -1
  136. package/dist/measure/links.d.ts.map +1 -1
  137. package/dist/measure/links.js +78 -5
  138. package/dist/measure/links.js.map +1 -1
  139. package/dist/measure/rich.d.ts +3 -0
  140. package/dist/measure/rich.d.ts.map +1 -1
  141. package/dist/measure/rich.js +19 -4
  142. package/dist/measure/rich.js.map +1 -1
  143. package/dist/parse/inlineFormatting.d.ts +14 -0
  144. package/dist/parse/inlineFormatting.d.ts.map +1 -1
  145. package/dist/parse/inlineFormatting.js +104 -5
  146. package/dist/parse/inlineFormatting.js.map +1 -1
  147. package/dist/parse/links.js +1 -1
  148. package/dist/parse/links.js.map +1 -1
  149. package/dist/parse/softBreaks.js +1 -1
  150. package/dist/parse/softBreaks.js.map +1 -1
  151. package/dist/parse/sourceMapping.d.ts.map +1 -1
  152. package/dist/parse/sourceMapping.js +19 -3
  153. package/dist/parse/sourceMapping.js.map +1 -1
  154. package/dist/parse/types.d.ts +24 -0
  155. package/dist/parse/types.d.ts.map +1 -1
  156. package/dist/pipeline/build.d.ts.map +1 -1
  157. package/dist/pipeline/build.js +156 -300
  158. package/dist/pipeline/build.js.map +1 -1
  159. package/dist/pipeline/calloutLayout.d.ts +33 -0
  160. package/dist/pipeline/calloutLayout.d.ts.map +1 -1
  161. package/dist/pipeline/calloutLayout.js +91 -2
  162. package/dist/pipeline/calloutLayout.js.map +1 -1
  163. package/dist/pipeline/codeInline.d.ts +10 -1
  164. package/dist/pipeline/codeInline.d.ts.map +1 -1
  165. package/dist/pipeline/codeInline.js +96 -15
  166. package/dist/pipeline/codeInline.js.map +1 -1
  167. package/dist/pipeline/composedLayout.d.ts +88 -0
  168. package/dist/pipeline/composedLayout.d.ts.map +1 -0
  169. package/dist/pipeline/composedLayout.js +320 -0
  170. package/dist/pipeline/composedLayout.js.map +1 -0
  171. package/dist/pipeline/config.d.ts.map +1 -1
  172. package/dist/pipeline/config.js +2 -1
  173. package/dist/pipeline/config.js.map +1 -1
  174. package/dist/pipeline/contentWarnings.d.ts.map +1 -1
  175. package/dist/pipeline/contentWarnings.js +32 -1
  176. package/dist/pipeline/contentWarnings.js.map +1 -1
  177. package/dist/pipeline/floatSlots.js +1 -1
  178. package/dist/pipeline/floatSlots.js.map +1 -1
  179. package/dist/pipeline/frameLayout.d.ts +120 -0
  180. package/dist/pipeline/frameLayout.d.ts.map +1 -0
  181. package/dist/pipeline/frameLayout.js +279 -0
  182. package/dist/pipeline/frameLayout.js.map +1 -0
  183. package/dist/pipeline/headerFooter.d.ts.map +1 -1
  184. package/dist/pipeline/headerFooter.js +48 -3
  185. package/dist/pipeline/headerFooter.js.map +1 -1
  186. package/dist/pipeline/pageRoles.d.ts +1 -0
  187. package/dist/pipeline/pageRoles.d.ts.map +1 -1
  188. package/dist/pipeline/pageRoles.js +3 -0
  189. package/dist/pipeline/pageRoles.js.map +1 -1
  190. package/dist/pipeline/placement.js +1 -1
  191. package/dist/pipeline/placement.js.map +1 -1
  192. package/dist/pipeline/resourceLayout.d.ts.map +1 -1
  193. package/dist/pipeline/resourceLayout.js +5 -3
  194. package/dist/pipeline/resourceLayout.js.map +1 -1
  195. package/dist/pipeline/tabs.js +1 -1
  196. package/dist/pipeline/tabs.js.map +1 -1
  197. package/dist/types.d.ts +69 -3
  198. package/dist/types.d.ts.map +1 -1
  199. package/dist/vdt.d.ts +169 -1
  200. package/dist/vdt.d.ts.map +1 -1
  201. package/dist/vdt.js.map +1 -1
  202. package/package.json +1 -1
@@ -34,6 +34,9 @@ import { anchorTargetsOf, hasAnchorRefs, printsAnchorPages } from './crossRefs';
34
34
  import { applyStatementNumbering, numberStatements, outlineLookup } from './statementNumbering';
35
35
  import { SPAN_EMBED_STYLE_ID, isPageSpanStrip, spanEmbedStyle, wrapPageSpanEmbeds } from './spanEmbeds';
36
36
  import { flowColumnsStyles, isFlowColumnsStyle, wrapFlowColumns } from './flowColumns';
37
+ import { FRAME_STYLE_ID, frameBoxStyle, wrapComposedPages } from '../composed/wrap';
38
+ import { resolvePageTemplate } from '../composed/templates';
39
+ import { composedPageAt, frameStyleOf, layoutComposedPage, planComposedPages } from './composedLayout';
37
40
  import { anchorOutline, computeOutline, hasIndexDirective, hasTocDirective, headingNumberingOptions, headingTemplatesOf, outlineFromDoc, sameOutline } from './outline';
38
41
  import { expandTocDirectives } from './toc';
39
42
  import { expandIndexDirectives, locateIndexMarks } from './indexDirective';
@@ -49,8 +52,8 @@ import { measureContentBlock } from './measureContentBlock';
49
52
  import { containerStyle, paragraphStyleIdOf, planParagraphContainers } from './paragraphContainers';
50
53
  import { appendChapterEndNotes, footnoteIdsOfLines, footnoteParagraphStyle, noteContentBlock, noteNumberPositionOf, numberFootnotes, footnoteFormatOf, numberFootnotesByPlacement, sameFootnoteNumbers, splitFootnoteDefinitions, withFootnoteStyle, } from './footnotes';
51
54
  import { planParts, derivePartMeasureContext } from './parts';
52
- import { layoutCallout, offsetBlock, offsetCalloutToAbsolute, pickCalloutStyle, planCallouts, resolveCalloutAttrs, } from './calloutLayout';
53
- import { fillGalley, galleyBreaks } from './columnsGroup';
55
+ import { offsetBlock, offsetCalloutToAbsolute, pickCalloutStyle, planCallouts, resolveCalloutAttrs, } from './calloutLayout';
56
+ import { CUT_START, fragmentRange, makeCalloutLayouter as makeCalloutLayouterIn, splitCalloutFragment as splitCalloutFragmentIn, tallerThanColumn as tallerThanColumnIn, } from './frameLayout';
54
57
  import { chooseTableSlice, layoutCaptionText, layoutResourceBlock, MIN_TAIL_ROWS } from './resourceLayout';
55
58
  import { computeFloatPlan, floatedResourceIds, floatColumnCount, resolveResourcePlacement, } from './floatPlacement';
56
59
  import { enumerateCurrentPageSlots, measureFloatBand, floatGapAbove, floatGapBelow, measureSideStack, clearSideObstacles, columnHasFloatBand, fitsStrict, trueBottom, } from './floatSlots';
@@ -77,6 +80,7 @@ import { annotateArabicMarks } from '../arabicMarks';
77
80
  import { literalBreaksFor } from './hardBreaks';
78
81
  import { tashkilFor } from './tashkil';
79
82
  import { overlineEmphasis } from '../emphasisOverline';
83
+ import { highlightDocument } from '../highlightMarks';
80
84
  import { withBookTitleBrackets } from './annotations';
81
85
  import { paperByBlock, stampPagePaper } from './paper';
82
86
  import { headingLineSpanBand } from './lineSpan';
@@ -479,7 +483,13 @@ function placeDocumentPass(content, config, cache, options, hints) {
479
483
  // set by the span-block path like any other.
480
484
  // A `:::columns` group in the running text (#634): each in a frameless
481
485
  // box the callout machinery places, cuts and splits.
482
- const flowGroups = resolved.layout.flowColumns ? wrapFlowColumns(numberedBlocks) : { blocks: numberedBlocks, wrapped: false };
486
+ // Composed pages (#855): each page's content regrouped into the stories
487
+ // its template's frames receive, one frameless box each (set at the
488
+ // page's template, below). Before the main-flow groups: a `:::columns`
489
+ // in a frame is a group of its box.
490
+ const templateSource = { pageTemplates: resolved.pageTemplates, calloutStyles: resolved.calloutStyles };
491
+ const composedWrap = wrapComposedPages(numberedBlocks, templateSource);
492
+ const flowGroups = resolved.layout.flowColumns ? wrapFlowColumns(composedWrap.blocks) : { blocks: composedWrap.blocks, wrapped: false };
483
493
  const spanEmbeds = wrapPageSpanEmbeds(flowGroups.blocks, content.resources ?? [], effectiveResourceTypes(config, content.resources ?? []));
484
494
  // Code listings (#624): each in a box of the code style, which the
485
495
  // callout machinery places and splits.
@@ -525,6 +535,8 @@ function placeDocumentPass(content, config, cache, options, hints) {
525
535
  const syntheticBoxes = [
526
536
  ...(spanEmbeds.wrapped ? [spanEmbedStyle(bodyStyle.lineHeightPx, resolved.layout.inlineResourceGap !== 'above')] : []),
527
537
  ...(codeBoxes.wrapped ? [codeBoxStyle(resolved)] : []),
538
+ // The frameless box of a composed page's story (#855).
539
+ ...(composedWrap.wrapped ? [frameBoxStyle(Math.max(resolved.bodyText.avoidOrphans ? resolved.bodyText.orphanMinLines : 1, resolved.bodyText.avoidWidows ? resolved.bodyText.widowMinLines : 1))] : []),
528
540
  // The boxes of main-flow groups: sub-columns a body line apart in the
529
541
  // text column, a gutter apart across the page.
530
542
  ...(flowGroups.wrapped
@@ -536,7 +548,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
536
548
  : resolved.calloutStyles;
537
549
  // A box nested in another looks its style up in the configuration it is
538
550
  // laid out with: the listings' box among them.
539
- const calloutResolved = codeBoxes.wrapped || flowGroups.wrapped ? { ...resolved, calloutStyles } : resolved;
551
+ const calloutResolved = codeBoxes.wrapped || flowGroups.wrapped || composedWrap.wrapped ? { ...resolved, calloutStyles } : resolved;
540
552
  const blockquoteStyle = resolveBlockquoteStyle(resolved);
541
553
  const listLevelIndentsPx = computeLevelIndentsPx(resolved, bodyStyle.fontSizePx);
542
554
  const orderedMetrics = computeOrderedListRunMetrics(contentBlocks, resolved, bodyStyle.fontSizePx);
@@ -568,6 +580,23 @@ function placeDocumentPass(content, config, cache, options, hints) {
568
580
  // mode of the styled section the reference sits in (a horizontal
569
581
  // appendix of a vertical book turns its figures as asked).
570
582
  const floatPlan = computeFloatPlan(contentBlocks, resources, resourceTypes, incorporated, (blockIdx) => sectionWritingMode(sectionPlan, resolved, blockIdx) === 'vertical-rl', resolved.layout.floatShrink, resolved.layout.wrap, resolved.layout.floatsAtCitingPage);
583
+ // A composed page's pictures are set in its frames: a `::resource` a
584
+ // story holds does not float. A reference in a frame to a picture set
585
+ // elsewhere floats from the page's end, to the pages after it.
586
+ const composedPlans = planComposedPages(contentBlocks, calloutPlan, templateSource);
587
+ if (composedPlans.size > 0) {
588
+ for (let k = floatPlan.length - 1; k >= 0; k--) {
589
+ const f = floatPlan[k];
590
+ const page = composedPageAt(composedPlans, f.firstBlockIdx);
591
+ if (!page)
592
+ continue;
593
+ const raw = contentBlocks[f.firstBlockIdx];
594
+ if (raw?.type === 'resourceBlock' && raw.resourceId === f.resourceId && page.storyAt(f.firstBlockIdx))
595
+ floatPlan.splice(k, 1);
596
+ else
597
+ f.firstBlockIdx = page.endIdx;
598
+ }
599
+ }
571
600
  const floatedIds = floatedResourceIds(floatPlan, incorporated, resources, resourceTypes);
572
601
  const floatsByFirstBlock = new Map();
573
602
  for (const f of floatPlan) {
@@ -2986,7 +3015,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
2986
3015
  // A page still empty takes the geometry right away — the document (or
2987
3016
  // a chapter laid out on its own) opening with a styled heading.
2988
3017
  const page = doc.pages[cursor.pageIndex];
2989
- if (page && !page.partInfo && !page.comic && cursor.columnIndex === 0
3018
+ if (page && !page.partInfo && !page.comic && !page.composed && cursor.columnIndex === 0
2990
3019
  && !pageHasContent(page) && !(page.floats && page.floats.length > 0)) {
2991
3020
  const fresh = createPageWithColumns(page.index, geomResolved, contentArea, pageWidthPx, pageHeightPx, pageIndexOffset);
2992
3021
  if (page.blankForParity)
@@ -3368,7 +3397,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
3368
3397
  */
3369
3398
  const openSpanHeadingHere = (blockIdx, rawBlock, level) => {
3370
3399
  const page = doc.pages[cursor.pageIndex];
3371
- if (page.partInfo || page.comic || currentColumn(doc, cursor).kind === 'span')
3400
+ if (page.partInfo || page.comic || page.composed || currentColumn(doc, cursor).kind === 'span')
3372
3401
  return false;
3373
3402
  const band = currentBand(page, cursor);
3374
3403
  const cols = bandColumns(page, band);
@@ -3502,7 +3531,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
3502
3531
  if (!balancingCfg.enabled || !balancingCfg.trailing)
3503
3532
  return;
3504
3533
  const page = doc.pages[cursor.pageIndex];
3505
- if (page.columns[cursor.columnIndex]?.kind === 'span' || page.partInfo || page.comic)
3534
+ if (page.columns[cursor.columnIndex]?.kind === 'span' || page.partInfo || page.comic || page.composed)
3506
3535
  return;
3507
3536
  const band = currentBand(page, cursor);
3508
3537
  const cols = bandColumns(page, band).filter((c) => c.bbox.height > 0.5);
@@ -3708,8 +3737,11 @@ function placeDocumentPass(content, config, cache, options, hints) {
3708
3737
  /** A closed `:::paper` still owes a page break before the next placed
3709
3738
  * block. */
3710
3739
  let pendingPaperBreak = false;
3711
- /** A composed page (`::::page`, #854) ended: the next placed block opens
3712
- * a new page. */
3740
+ /** A composed page (`::::page`, #854) owes a page break before the next
3741
+ * placed block: `'page'` once its template was laid on the current page
3742
+ * (nothing flowed on it), `'flow'` after content set in the flow (a page
3743
+ * of unknown template, or what no frame took), which closes as a
3744
+ * `:::paper` run does. */
3713
3745
  let pendingComposedBreak = false;
3714
3746
  /** A comic page (`:::page`) was laid out on the current page: the next
3715
3747
  * placed block opens a new one (a page that ends the document leaves no
@@ -3740,7 +3772,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
3740
3772
  let pendingNumberingPage = 0;
3741
3773
  /** A page holding something the reader sees: column content, a float, a
3742
3774
  * part opener. A parity blank is not one. */
3743
- const pageTakesNumbering = (page) => pageHasContent(page) || (page.floats?.length ?? 0) > 0 || page.partInfo !== undefined || page.comic !== undefined;
3775
+ const pageTakesNumbering = (page) => pageHasContent(page) || (page.floats?.length ?? 0) > 0 || page.partInfo !== undefined || page.comic !== undefined || page.composed !== undefined;
3744
3776
  /** Commits a pending `:::numbering` change to the first page from the
3745
3777
  * directive on that receives content: the page the directive opens (the
3746
3778
  * head of a chapter, right after a page break) or, when that page was
@@ -3905,16 +3937,14 @@ function placeDocumentPass(content, config, cache, options, hints) {
3905
3937
  doc.blocks.push(child);
3906
3938
  }
3907
3939
  };
3908
- /** The widths a child's lines are counted at in a fragment `width` wide
3909
- * that opens `from` (see `CalloutCut.widths`). */
3910
- const cutWidths = (from, width) => {
3911
- const before = from.line > 0 ? from.widths : undefined;
3912
- if (!before || before.length === 0)
3913
- return [{ fromLine: 0, width }];
3914
- const last = before[before.length - 1];
3915
- return Math.abs(last.width - width) <= 0.5 ? before : [...before, { fromLine: from.line, width }];
3916
- };
3917
- const CUT_START = { child: 0, line: 0 };
3940
+ // --- Callout fragments (`keepTogether: false`) ---------------------------
3941
+ // A splittable box breaks only BETWEEN its child blocks (a list item or a
3942
+ // paragraph is never cut in two): the fragment that fits closes the
3943
+ // current column / page, the rest continues in a box of its own — same
3944
+ // frame, stripe and marker, no title or icon — on the next one, and may
3945
+ // split again. Every
3946
+ // fragment's frame shares the fence's `contentIndex` / `containerId` and
3947
+ // records its `callout.part` / `callout.continued`.
3918
3948
  /** A strip in a box (the box of a page-wide strip set `here`, #590):
3919
3949
  * laid out at the box's inner width, as tall as a page allows, with its
3920
3950
  * placeholder line; the page it lays out for is the cursor's. */
@@ -3925,85 +3955,21 @@ function placeDocumentPass(content, config, cache, options, hints) {
3925
3955
  withStripPlaceholder(blk, stripGeometry(idx, raw.comic, width, contentArea.height - 2 * floatGapPx, pageIsVertical(doc.pages[cursor.pageIndex])));
3926
3956
  return blk;
3927
3957
  };
3928
- const makeCalloutLayouter = (startIdx, plan, style) => {
3929
- const children = contentBlocks.slice(startIdx + 1, plan.endIdx);
3930
- const realAt = [];
3931
- children.forEach((c, k) => {
3932
- if ((c.type !== 'directive' || (c.directiveName === 'strip' && c.comic)) && !isMarkerBlock(c))
3933
- realAt.push(k);
3934
- });
3935
- // `:::columns` groups among the children, as [start marker, end marker]
3936
- // positions: a split never cuts inside one.
3937
- const groups = [];
3938
- children.forEach((c, k) => {
3939
- if (c.type === 'containerStart' && c.containerName === 'columns') {
3940
- let e = k + 1;
3941
- while (e < children.length && !(children[e].type === 'containerEnd' && children[e].containerName === 'columns' && children[e].containerId === c.containerId))
3942
- e++;
3943
- groups.push([k, e]);
3944
- }
3945
- });
3946
- const layoutRange = (from, to, width, frameId, continuation, mirrored = false) => {
3947
- let n = 0;
3948
- // A fragment that opens inside a `:::columns` group starts at its
3949
- // opening marker and one that ends inside it at its closing marker:
3950
- // the group sets the part between the cut's positions (#634).
3951
- const fromChild = from.group ? from.group.open : from.child;
3952
- const closeOfGroup = (open) => groups.find(([gs]) => gs === open)?.[1] ?? children.length - 1;
3953
- // A cut inside child `to.child` includes that child (its first
3954
- // `to.line` lines); a cut at a child's head excludes it.
3955
- const toChild = to.group ? closeOfGroup(to.group.open) + 1 : to.line > 0 ? to.child + 1 : to.child;
3956
- const inSlice = (pos) => pos.map((p) => ({ ...p, child: p.child - fromChild }));
3957
- // Nested boxes still open at `from` (their closing markers leading
3958
- // the range close them): the fragment opens inside them.
3959
- const open = [];
3960
- for (let k = 0; k < fromChild; k++) {
3961
- const c = children[k];
3962
- if (c.containerName !== 'callout')
3963
- continue;
3964
- if (c.type === 'containerStart')
3965
- open.push({ idx: startIdx + 1 + k, block: c });
3966
- else if (c.type === 'containerEnd')
3967
- open.pop();
3968
- }
3969
- for (let k = fromChild; k < children.length && open.length > 0; k++) {
3970
- const c = children[k];
3971
- if (c.type !== 'containerEnd' || c.containerId !== open[open.length - 1].block.containerId)
3972
- break;
3973
- open.pop();
3974
- }
3975
- return layoutCallout({
3976
- style,
3977
- attrs: plan.attrs,
3978
- ...(contentBlocks[startIdx]?.direction ? { direction: contentBlocks[startIdx].direction } : {}),
3979
- continuation,
3980
- // Any range short of the box's end is a fragment that goes on.
3981
- ...(to.child < children.length || to.line > 0 || to.group ? { continues: true } : {}),
3982
- children: children.slice(fromChild, toChild),
3983
- childStartIdx: startIdx + 1 + fromChild,
3984
- width,
3985
- // A main-flow group's box sets its blocks as the text around it is
3986
- // set: a styled section's or a part's body (#634).
3987
- ctx: isFlowColumnsStyle(style.id)
3988
- ? (partPlan.byBlock[startIdx] ? partMeasureCtx : sectionMeasureCtx(sectionPlan.byBlock[startIdx]))
3989
- : measureCtx,
3990
- resolved: calloutResolved,
3991
- containerId: plan.containerId,
3992
- frameId,
3993
- nextChildId: () => `${frameId}-c${n++}`,
3994
- paragraphContainers,
3995
- ...(from.line > 0 && !from.group ? { lineFrom: from.line } : {}),
3996
- ...(from.line > 0 && !from.group && from.widths ? { lineWidths: from.widths } : {}),
3997
- ...(to.line > 0 && !to.group ? { lineTo: to.line } : {}),
3998
- ...(from.group ? { groupFrom: inSlice(from.group.pos) } : {}),
3999
- ...(to.group ? { groupTo: inSlice(to.group.pos) } : {}),
4000
- ...(open.length > 0 ? { openNested: open } : {}),
4001
- mirrored,
4002
- stripBlock: calloutStripBlock,
4003
- });
4004
- };
4005
- return { children, childBase: startIdx + 1, realAt, end: { child: children.length, line: 0 }, layoutRange, groups };
4006
- };
3958
+ /** What the box typesetter (`frameLayout.ts`) reads of this pass. */
3959
+ const framePass = {
3960
+ contentBlocks,
3961
+ resolved,
3962
+ calloutResolved,
3963
+ paragraphContainers,
3964
+ // A main-flow group's box sets its blocks as the text around it is
3965
+ // set: a styled section's or a part's body (#634).
3966
+ measureCtxFor: (startIdx, style) => (isFlowColumnsStyle(style.id) || style.id === FRAME_STYLE_ID
3967
+ ? (partPlan.byBlock[startIdx] ? partMeasureCtx : sectionMeasureCtx(sectionPlan.byBlock[startIdx]))
3968
+ : measureCtx),
3969
+ stripBlock: calloutStripBlock,
3970
+ columnHeight: () => contentArea.height,
3971
+ };
3972
+ const makeCalloutLayouter = (startIdx, plan, style) => makeCalloutLayouterIn(framePass, startIdx, plan, style);
4007
3973
  /** Whether a page swaps its mirrored margins (a verso of a left-bound
4008
3974
  * book, a recto of a right-bound one: an `'outer'` corner icon hangs on
4009
3975
  * the left there). A vertical page has no outer side: a box's outer
@@ -4029,192 +3995,8 @@ function placeDocumentPass(content, config, cache, options, hints) {
4029
3995
  const withSide = side && sideUsedBottom(side) > side.bbox.y + 0.5 ? [...cols, side] : cols;
4030
3996
  return bandUsedBottom(withSide);
4031
3997
  };
4032
- /** The longest leading fragment of the box from cut `from` on whose box
4033
- * is at most `roomPx` tall. Cuts fall between children or between the
4034
- * lines of a text child. `splitMinLines` guards text: a cut between
4035
- * children leaves on each side at least that many text lines or at least
4036
- * one indivisible block (a figure, table, display formula or nested
4037
- * box); a cut inside a text child counts every line on its side, a block
4038
- * as one line, and leaves at least `layout.boxChildSplitMinLines` lines
4039
- * of that child on each side (two by default, `splitMinLines` when that
4040
- * is lower; EF-115). A nested box that may split
4041
- * itself (`keepTogether: false`, or taller than a full column) offers the cuts
4042
- * inside it too, by its own `splitMinLines` over its own children; the
4043
- * fragments on both sides redraw its frame. The full layout's geometry
4044
- * ranks the candidates (box bottom = content bottom at the cut + the
4045
- * tails of the boxes closed below it); the deepest candidate that fits
4046
- * is laid out for real and taken when it truly fits. `null` when none
4047
- * does. */
4048
- const splitCalloutFragment = (L, from, width, roomPx, frameId, continuation, minLines, mirrored = false) => {
4049
- const full = L.layoutRange(from, L.end, width, frameId, continuation, mirrored);
4050
- /** Figures, tables and display formulas: never cut, not text lines. */
4051
- const indivisible = (c) => c.type === 'resource' || c.type === 'mathDisplay';
4052
- const unitBottom = (u) => {
4053
- const b = u.kind === 'block' ? u.block.bbox : u.box.result.frame.bbox;
4054
- return b.y + b.height;
4055
- };
4056
- /** Room a box keeps under its last item (padding, border, icon growth). */
4057
- const tailOf = (r) => {
4058
- const last = r.units[r.units.length - 1];
4059
- return last ? r.frame.bbox.y + r.totalHeight - unitBottom(last) : 0;
4060
- };
4061
- if (full.units.length === 0)
4062
- return null;
4063
- // A head that goes on also carries the style's continuation marker.
4064
- const tail = tailOf(full) + full.continuesMarkerPx;
4065
- /** Candidate cuts with the head's content bottom. A `soft` cut parts a
4066
- * wrapped code line from its continuation (#624): taken only when no
4067
- * other cut fits. */
4068
- const candidates = [];
4069
- /** Collect the cuts among `units` (one box's items; `below` = the tails
4070
- * of the nested boxes around them), each side of a cut holding enough
4071
- * by the box's `min` lines: a block counts as one line, a nested box
4072
- * as one indivisible block. */
4073
- const collect = (units, minLines, below) => {
4074
- const min = Math.max(1, minLines);
4075
- const lineCount = (u) => (u.kind === 'block' ? Math.max(1, u.block.lines.length) : 1);
4076
- const isBlock = (u) => u.kind === 'box' || indivisible(u.block);
4077
- const totalLines = units.reduce((n, u) => n + lineCount(u), 0);
4078
- const totalBlocks = units.filter(isBlock).length;
4079
- /** A side of a cut holds enough: `lines` (blocks included) of which
4080
- * `blocks` are indivisible. Between children, one block suffices or
4081
- * the text lines alone meet the minimum; inside a text child, the
4082
- * lines do. */
4083
- const holds = (lines, blocks, betweenChildren) => betweenChildren ? blocks > 0 || lines - blocks >= min : lines >= min;
4084
- const bothHold = (lines, blocks, betweenChildren) => holds(lines, blocks, betweenChildren) && holds(totalLines - lines, totalBlocks - blocks, betweenChildren);
4085
- let linesBefore = 0;
4086
- let blocksBefore = 0;
4087
- units.forEach((u, i) => {
4088
- let next;
4089
- if (u.kind === 'block') {
4090
- const c = u.block;
4091
- const k = (c.contentIndex ?? L.childBase) - L.childBase;
4092
- next = k + 1;
4093
- if (!indivisible(c) && c.lines.length > 1) {
4094
- // The first laid-out child of a continuation opens after
4095
- // `from.line` lines: cuts inside it are counted from the
4096
- // child's own head. Each side of the box still counts all its
4097
- // lines against `min`, and a cut inside a child leaves at least
4098
- // `layout.boxChildSplitMinLines` lines of it on each side (2 by
4099
- // default, so no lone line: EF-115), or `min` when that is
4100
- // lower. The count is of this fragment's part of the child,
4101
- // since a part left behind already passed the test.
4102
- const childMin = Math.min(min, resolved.layout.boxChildSplitMinLines);
4103
- const lineBase = k === from.child ? from.line : 0;
4104
- for (let l = childMin; l <= c.lines.length - childMin; l++) {
4105
- const line = c.lines[l - 1];
4106
- if (bothHold(linesBefore + l, blocksBefore, false)) {
4107
- candidates.push({
4108
- cut: { child: k, line: lineBase + l, widths: k === from.child ? cutWidths(from, width) : [{ fromLine: 0, width }] },
4109
- bottom: line.bbox.y + line.bbox.height + below,
4110
- ...(c.lines[l]?.codeLine?.continued ? { soft: true } : {}),
4111
- });
4112
- }
4113
- }
4114
- }
4115
- }
4116
- else {
4117
- const { box } = u;
4118
- if (box.endIdx !== undefined)
4119
- next = box.endIdx + 1 - L.childBase;
4120
- if (!box.style.keepTogether || tallerThanColumn(box.result)) {
4121
- collect(box.result.units, box.style.splitMinLines, below + tailOf(box.result));
4122
- }
4123
- }
4124
- linesBefore += lineCount(u);
4125
- if (isBlock(u))
4126
- blocksBefore++;
4127
- if (i < units.length - 1 && next !== undefined && bothHold(linesBefore, blocksBefore, true)) {
4128
- candidates.push({ cut: { child: next, line: 0 }, bottom: unitBottom(u) + below });
4129
- }
4130
- });
4131
- };
4132
- collect(full.units, minLines, 0);
4133
- // Inside a `:::columns` group of the box (#634): the cut the group
4134
- // offers for the room, where each of its sub-columns (each stream, in
4135
- // a parallel group) ends when the galley fills them in turn; at least
4136
- // one stream goes on and one moves on. A group of a nested box is
4137
- // never cut, nor any group under 1.24's rules (`layout.flowColumns`).
4138
- if (resolved.layout.flowColumns) {
4139
- const base = from.group ? from.group.open : from.child;
4140
- const childMin = Math.min(Math.max(1, minLines), resolved.layout.boxChildSplitMinLines);
4141
- for (const g of full.groups ?? []) {
4142
- const h = roomPx - tail - g.top;
4143
- if (h <= 0.5)
4144
- continue;
4145
- const reopened = from.group?.open === g.open + base ? from.group : undefined;
4146
- const pos = [];
4147
- let moved = false;
4148
- let left = false;
4149
- let used = 0;
4150
- g.streams.forEach((items, s) => {
4151
- const end = { child: (g.ends[s] ?? g.close) + base, line: 0 };
4152
- if (items.length === 0) {
4153
- pos.push(end);
4154
- return;
4155
- }
4156
- const breaks = galleyBreaks(items, childMin);
4157
- const fill = fillGalley(items, breaks, g.flow === 'snake' ? g.columns : 1, h);
4158
- if (fill.end) {
4159
- moved = true;
4160
- used = Math.max(used, fill.used);
4161
- pos.push(end);
4162
- return;
4163
- }
4164
- left = true;
4165
- if (fill.cuts.length === 0) {
4166
- // This stream takes nothing in the fragment: it stays where it is.
4167
- pos.push(reopened?.pos[s] ?? { child: items[0].child + base, line: 0 });
4168
- return;
4169
- }
4170
- moved = true;
4171
- used = Math.max(used, fill.used);
4172
- const b = breaks[fill.cuts[fill.cuts.length - 1]];
4173
- const it = items[b.item];
4174
- if (b.line === 0) {
4175
- pos.push({ child: it.child + base, line: 0 });
4176
- return;
4177
- }
4178
- // The sub-column widths the paragraph's lines are set at.
4179
- const before = it.lineBase > 0 ? reopened?.pos[s]?.widths : undefined;
4180
- const lastW = before?.[before.length - 1];
4181
- const widths = !before || before.length === 0
4182
- ? [{ fromLine: 0, width: g.columnWidth }]
4183
- : Math.abs(lastW.width - g.columnWidth) <= 0.5 ? before : [...before, { fromLine: it.lineBase, width: g.columnWidth }];
4184
- pos.push({ child: it.child + base, line: it.lineBase + b.line, widths });
4185
- });
4186
- if (!moved || !left)
4187
- continue;
4188
- candidates.push({
4189
- cut: { child: Math.min(...pos.map((p) => p.child)), line: 0, group: { open: g.open + base, pos } },
4190
- bottom: g.top + used,
4191
- });
4192
- }
4193
- }
4194
- const insideGroup = (cut) => !cut.group && L.groups.some(([gs, ge]) => (cut.line === 0 ? gs < cut.child && cut.child <= ge : gs < cut.child && cut.child < ge));
4195
- const viable = candidates
4196
- .filter((c) => !insideGroup(c.cut))
4197
- .sort((a, b) => b.bottom - a.bottom);
4198
- // A wrapped code line stays with its continuations when a cut
4199
- // elsewhere fits (#624).
4200
- viable.sort((a, b) => (a.soft ? 1 : 0) - (b.soft ? 1 : 0));
4201
- for (const c of viable) {
4202
- if (c.bottom + tail > roomPx + 0.01)
4203
- continue;
4204
- const result = L.layoutRange(from, c.cut, width, frameId, continuation, mirrored);
4205
- if (result.totalHeight <= roomPx + 0.01)
4206
- return { to: c.cut, result };
4207
- }
4208
- return null;
4209
- };
4210
- /** Absolute content indices of the children a fragment from cut `from`
4211
- * to cut `to` of `L` touches, for the fragment's source range. */
4212
- const fragmentRange = (L, from, to) => ({ firstChildIdx: L.childBase + from.child, lastChildIdx: L.childBase + (to.line > 0 ? to.child : to.child - 1) });
4213
- /** The (rest of the) box is taller than an empty, full-height column —
4214
- * a whole page's content area for a page-span box: no column could
4215
- * hold it, so even a keep-together box splits (with the rules of a
4216
- * `keepTogether: false` one) rather than overflow. */
4217
- const tallerThanColumn = (result) => result.totalHeight > contentArea.height + 0.01;
3998
+ const splitCalloutFragment = (L, from, width, roomPx, frameId, continuation, minLines, mirrored = false) => splitCalloutFragmentIn(framePass, L, from, width, roomPx, frameId, continuation, minLines, mirrored);
3999
+ const tallerThanColumn = (result) => tallerThanColumnIn(framePass, result);
4218
4000
  const markFragment = (result, part, continued) => {
4219
4001
  if (result.frame.callout) {
4220
4002
  result.frame.callout.part = part;
@@ -6055,6 +5837,57 @@ function placeDocumentPass(content, config, cache, options, hints) {
6055
5837
  }
6056
5838
  return out;
6057
5839
  };
5840
+ /** Content indices a composed page set in its frames: the main loop
5841
+ * passes over them. */
5842
+ const composedLaid = new Set();
5843
+ /** The composed pages' layout warnings (`frameOverflow`). */
5844
+ const composedNotes = [];
5845
+ /** Set the composed page `planned` on a fresh page (#855). */
5846
+ const placeComposedPage = (planned, fence) => {
5847
+ pendingSpacing = 0;
5848
+ closeFlowSegment(planned.startIdx);
5849
+ leaveCurrentPage();
5850
+ cursor.columnIndex = 0;
5851
+ const page = doc.pages[cursor.pageIndex];
5852
+ page.columns = [createVDTColumn(0, page.contentArea)];
5853
+ // The physical side: a recto has an odd folio (in either binding).
5854
+ const parity = (page.index + pageIndexOffset) % 2 === 0 ? 'recto' : 'verso';
5855
+ const template = resolvePageTemplate(templateSource, fence.template, { parity });
5856
+ const frameless = calloutStyles.find((st) => st.id === FRAME_STYLE_ID);
5857
+ const frames = designFramesOn(page);
5858
+ const area = template.area === 'trim' ? frames.page : template.area === 'bleed' ? frames.bleed : page.contentArea;
5859
+ const out = layoutComposedPage({
5860
+ pass: framePass,
5861
+ page,
5862
+ template,
5863
+ fence,
5864
+ startIdx: planned.startIdx,
5865
+ endIdx: planned.endIdx,
5866
+ stories: planned.stories,
5867
+ styleOf: (frame) => frameStyleOf((frame.style ? resolved.calloutStyles.find((st) => st.id === frame.style) : undefined) ?? frameless, frame),
5868
+ areas: { area, page: frames.page, bleed: frames.bleed },
5869
+ dpi,
5870
+ emPx: bodyStyle.fontSizePx,
5871
+ bodyOffset,
5872
+ mirrored: mirroredOf(page),
5873
+ pushGapPx: wrapGapPx(resolved.layout.wrap?.gap, dpi, bodyStyle.fontSizePx, floatGapPx),
5874
+ });
5875
+ page.composed = out.composed;
5876
+ const floats = (page.floats ??= []);
5877
+ for (const b of out.blocks) {
5878
+ floats.push(b);
5879
+ doc.blocks.push(b);
5880
+ }
5881
+ composedNotes.push(...out.warnings);
5882
+ for (const [head, story] of planned.stories) {
5883
+ if (!out.laid.has(head))
5884
+ continue;
5885
+ for (let i = story.startIdx; i <= story.plan.endIdx; i++)
5886
+ composedLaid.add(i);
5887
+ }
5888
+ pendingComposedBreak = 'page';
5889
+ flushPendingNumberingAtBoundary();
5890
+ };
6058
5891
  /** The first block of the document that sets something: a heading there
6059
5892
  * opens the document's first page (`openerPages`, #749). */
6060
5893
  const firstFlowBlockIdx = contentBlocks.findIndex((b) => !isMarkerBlock(b) && b.type !== 'directive');
@@ -6063,6 +5896,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
6063
5896
  throw new BuildCancelledError();
6064
5897
  options?.onProgress?.({ pass: 1, blocks: blockIdx, totalBlocks: contentBlocks.length, pages: doc.pages.length });
6065
5898
  const rawBlock = contentBlocks[blockIdx];
5899
+ // Blocks a composed page set in its frames (#855) are done.
5900
+ if (composedLaid.has(blockIdx))
5901
+ continue;
6066
5902
  // The styled section this block sits in: its geometry applies to the
6067
5903
  // pages opened from here (a heading with `breakBefore` opens one).
6068
5904
  const blockSection = sectionPlan.byBlock[blockIdx];
@@ -6279,26 +6115,39 @@ function placeDocumentPass(content, config, cache, options, hints) {
6279
6115
  pendingPaperBreak = false;
6280
6116
  breakForPaper(blockIdx);
6281
6117
  }
6282
- // A composed page (`::::page{template=…}`, #854). Until templates are
6283
- // laid out (P4, #855) its content is set in the flow, in Markdown order
6284
- // and on pages of its own: the page fence breaks the page as `:::paper`
6285
- // does, its frames are transparent containers (the generic marker branch
6286
- // below), and the block after the closing fence opens a new page. P4
6287
- // replaces this branch with the template layout (DESIGN §5.1).
6288
- if ((rawBlock.type === 'containerStart' || rawBlock.type === 'containerEnd') && rawBlock.containerName === 'composedPage') {
6289
- if (rawBlock.type === 'containerStart') {
6290
- pendingComposedBreak = false;
6118
+ // A composed page (`::::page{template=…}`, #855): it owns a fresh page,
6119
+ // its template's frames filled with its stories (`layoutComposedPage`);
6120
+ // nothing flows on it, and what follows starts on the next page. A
6121
+ // story whose frame this side lacks, and content no frame takes, are
6122
+ // set in the flow there. A page whose template is unknown is set in
6123
+ // the flow on pages of its own (`templateUnknown`).
6124
+ if (rawBlock.type === 'containerStart' && rawBlock.containerName === 'composedPage') {
6125
+ const planned = composedPlans.get(blockIdx);
6126
+ pendingComposedBreak = false;
6127
+ if (planned && rawBlock.composedPage)
6128
+ placeComposedPage(planned, rawBlock.composedPage);
6129
+ else
6291
6130
  breakForPaper(blockIdx);
6292
- }
6293
- else {
6294
- pendingComposedBreak = true;
6295
- }
6296
6131
  continue;
6297
6132
  }
6298
- if (pendingComposedBreak) {
6133
+ if (rawBlock.type === 'containerEnd' && rawBlock.containerName === 'composedPage') {
6134
+ // (Set to `'page'` by `placeComposedPage`, a closure the flow
6135
+ // analysis does not follow.)
6136
+ if (pendingComposedBreak !== 'page')
6137
+ pendingComposedBreak = 'flow';
6138
+ continue;
6139
+ }
6140
+ const composedBreak = pendingComposedBreak;
6141
+ if (composedBreak === 'flow') {
6299
6142
  pendingComposedBreak = false;
6300
6143
  breakForPaper(blockIdx);
6301
6144
  }
6145
+ else if (composedBreak === 'page') {
6146
+ pendingComposedBreak = false;
6147
+ pendingSpacing = 0;
6148
+ leaveCurrentPage();
6149
+ flushPendingNumberingAtBoundary();
6150
+ }
6302
6151
  // `:::page`: a comic page owns a fresh page — its panels, split lines
6303
6152
  // and lettering laid out on the sheet (`layoutComicPage`); nothing
6304
6153
  // flows on it, and what follows starts on the next page.
@@ -8322,6 +8171,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
8322
8171
  floatShrinkWarningsByDoc.set(doc, shrunk);
8323
8172
  if (textWrapNotes.length > 0 || columnsNotes.length > 0)
8324
8173
  textWrapWarningsByDoc.set(doc, [...textWrapNotes, ...columnsNotes]);
8174
+ // Composed pages (#855): frames whose content does not fit.
8175
+ if (composedNotes.length > 0)
8176
+ composedWarningsByDoc.set(doc, composedNotes);
8325
8177
  // The composition rules (#749): short lists cut, floats on opener pages.
8326
8178
  const ruleNotes = [...shortListWarnings(), ...openerFloatNotes, ...tocGroupWarnings()];
8327
8179
  if (ruleNotes.length > 0)
@@ -8396,7 +8248,7 @@ export function* buildDocumentGen(content, config, cache, options) {
8396
8248
  // cap; words of a joining script that run past their line, and styles
8397
8249
  // whose letter-spacing such words do not take.
8398
8250
  // Line numbers set over a float of the side column (#621).
8399
- const loose = [...cjkLooseLineWarnings(doc), ...raggedLineWarnings(doc), ...wordOverflowWarnings(doc), ...(joiningSpacingWarnings.get(doc) ?? []), ...(dropCapWarningsByDoc.get(doc) ?? []), ...lineNumberWarnings(doc), ...codeOverflowWarnings(doc), ...(floatShrinkWarningsByDoc.get(doc) ?? []), ...(textWrapWarningsByDoc.get(doc) ?? []), ...(compositionWarningsByDoc.get(doc) ?? [])];
8251
+ const loose = [...cjkLooseLineWarnings(doc), ...raggedLineWarnings(doc), ...wordOverflowWarnings(doc), ...(joiningSpacingWarnings.get(doc) ?? []), ...(dropCapWarningsByDoc.get(doc) ?? []), ...lineNumberWarnings(doc), ...codeOverflowWarnings(doc), ...(floatShrinkWarningsByDoc.get(doc) ?? []), ...(textWrapWarningsByDoc.get(doc) ?? []), ...(compositionWarningsByDoc.get(doc) ?? []), ...(composedWarningsByDoc.get(doc) ?? [])];
8400
8252
  // Side boxes and side floats set on pages after the text, and what the
8401
8253
  // layout could place nowhere (#639).
8402
8254
  loose.push(...afterTextWarnings(doc), ...(unplacedByDoc.get(doc) ?? []));
@@ -8427,6 +8279,8 @@ export function* buildDocumentGen(content, config, cache, options) {
8427
8279
  const harakat = annotateArabicMarks(doc);
8428
8280
  // The rules over `*…*` runs under `bodyText.emphasis: 'overline'`.
8429
8281
  overlineEmphasis(doc);
8282
+ // The bands of the highlight marks (`:mark[…]`, #888).
8283
+ highlightDocument(doc);
8430
8284
  // Faces the text was set in that the font set could not give (#629).
8431
8285
  const fallbacks = fontSet && resolveDebugConfig(config?.debug).warnings.missingFont
8432
8286
  ? fontFallbacks(documentFontFaces(doc), fontSet).map((f) => ({ kind: 'fontFallback', family: f.family, weight: f.weight, style: f.style, reason: f.reason }))
@@ -8455,6 +8309,8 @@ const floatShrinkWarningsByDoc = new WeakMap();
8455
8309
  /** The `textWrap` warnings of a build (#627) and its `columnsTooNarrow`
8456
8310
  * ones (#634), found by its last pass. */
8457
8311
  const textWrapWarningsByDoc = new WeakMap();
8312
+ /** The composed pages' layout warnings of a pass (`frameOverflow`, #855). */
8313
+ const composedWarningsByDoc = new WeakMap();
8458
8314
  /** The `shortListSplit` and `openerFloat` warnings of a build (#749),
8459
8315
  * found by its last pass. */
8460
8316
  const compositionWarningsByDoc = new WeakMap();