postext 1.37.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 (194) 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/continuationRules.test.d.ts +2 -0
  6. package/dist/__tests__/composed/continuationRules.test.d.ts.map +1 -0
  7. package/dist/__tests__/composed/continuationRules.test.js +389 -0
  8. package/dist/__tests__/composed/continuationRules.test.js.map +1 -0
  9. package/dist/__tests__/composed/coverImages.test.d.ts +2 -0
  10. package/dist/__tests__/composed/coverImages.test.d.ts.map +1 -0
  11. package/dist/__tests__/composed/coverImages.test.js +75 -0
  12. package/dist/__tests__/composed/coverImages.test.js.map +1 -0
  13. package/dist/__tests__/composed/decorationSize.test.d.ts +2 -0
  14. package/dist/__tests__/composed/decorationSize.test.d.ts.map +1 -0
  15. package/dist/__tests__/composed/decorationSize.test.js +79 -0
  16. package/dist/__tests__/composed/decorationSize.test.js.map +1 -0
  17. package/dist/__tests__/composed/emptyFrames.test.d.ts +2 -0
  18. package/dist/__tests__/composed/emptyFrames.test.d.ts.map +1 -0
  19. package/dist/__tests__/composed/emptyFrames.test.js +129 -0
  20. package/dist/__tests__/composed/emptyFrames.test.js.map +1 -0
  21. package/dist/__tests__/composed/fit.test.js +10 -4
  22. package/dist/__tests__/composed/fit.test.js.map +1 -1
  23. package/dist/__tests__/composed/groups.test.js +20 -0
  24. package/dist/__tests__/composed/groups.test.js.map +1 -1
  25. package/dist/__tests__/composed/ruledFrames.test.d.ts +2 -0
  26. package/dist/__tests__/composed/ruledFrames.test.d.ts.map +1 -0
  27. package/dist/__tests__/composed/ruledFrames.test.js +93 -0
  28. package/dist/__tests__/composed/ruledFrames.test.js.map +1 -0
  29. package/dist/__tests__/composed/spread.test.js +82 -0
  30. package/dist/__tests__/composed/spread.test.js.map +1 -1
  31. package/dist/__tests__/exports.test.js +12 -0
  32. package/dist/__tests__/exports.test.js.map +1 -1
  33. package/dist/__tests__/migrate/labelsOnPictures.test.d.ts +2 -0
  34. package/dist/__tests__/migrate/labelsOnPictures.test.d.ts.map +1 -0
  35. package/dist/__tests__/migrate/labelsOnPictures.test.js +114 -0
  36. package/dist/__tests__/migrate/labelsOnPictures.test.js.map +1 -0
  37. package/dist/__tests__/paragraphDropCap.test.js +38 -0
  38. package/dist/__tests__/paragraphDropCap.test.js.map +1 -1
  39. package/dist/__tests__/resourceTypePartial.test.d.ts +2 -0
  40. package/dist/__tests__/resourceTypePartial.test.d.ts.map +1 -0
  41. package/dist/__tests__/resourceTypePartial.test.js +60 -0
  42. package/dist/__tests__/resourceTypePartial.test.js.map +1 -0
  43. package/dist/canvas-backend/composed.d.ts +3 -0
  44. package/dist/canvas-backend/composed.d.ts.map +1 -1
  45. package/dist/canvas-backend/composed.js +47 -26
  46. package/dist/canvas-backend/composed.js.map +1 -1
  47. package/dist/canvas-backend/headerFooter.d.ts.map +1 -1
  48. package/dist/canvas-backend/headerFooter.js +13 -0
  49. package/dist/canvas-backend/headerFooter.js.map +1 -1
  50. package/dist/canvas-backend/index.d.ts.map +1 -1
  51. package/dist/canvas-backend/index.js +16 -5
  52. package/dist/canvas-backend/index.js.map +1 -1
  53. package/dist/comics/frames.d.ts +82 -0
  54. package/dist/comics/frames.d.ts.map +1 -0
  55. package/dist/comics/frames.js +182 -0
  56. package/dist/comics/frames.js.map +1 -0
  57. package/dist/comics/index.d.ts +4 -1
  58. package/dist/comics/index.d.ts.map +1 -1
  59. package/dist/comics/index.js +3 -1
  60. package/dist/comics/index.js.map +1 -1
  61. package/dist/comics/layoutPage.d.ts +9 -0
  62. package/dist/comics/layoutPage.d.ts.map +1 -1
  63. package/dist/comics/layoutPage.js +53 -68
  64. package/dist/comics/layoutPage.js.map +1 -1
  65. package/dist/comics/page.d.ts.map +1 -1
  66. package/dist/comics/page.js +10 -1
  67. package/dist/comics/page.js.map +1 -1
  68. package/dist/comics/sourceMap.d.ts +60 -0
  69. package/dist/comics/sourceMap.d.ts.map +1 -0
  70. package/dist/comics/sourceMap.js +166 -0
  71. package/dist/comics/sourceMap.js.map +1 -0
  72. package/dist/comics/spread.d.ts.map +1 -1
  73. package/dist/comics/spread.js +6 -0
  74. package/dist/comics/spread.js.map +1 -1
  75. package/dist/comics/textPanels.d.ts +113 -0
  76. package/dist/comics/textPanels.d.ts.map +1 -0
  77. package/dist/comics/textPanels.js +327 -0
  78. package/dist/comics/textPanels.js.map +1 -0
  79. package/dist/comics/transform.d.ts +6 -2
  80. package/dist/comics/transform.d.ts.map +1 -1
  81. package/dist/comics/transform.js +14 -1
  82. package/dist/comics/transform.js.map +1 -1
  83. package/dist/comics/types.d.ts +10 -0
  84. package/dist/comics/types.d.ts.map +1 -1
  85. package/dist/composed/continuation.d.ts +55 -5
  86. package/dist/composed/continuation.d.ts.map +1 -1
  87. package/dist/composed/continuation.js +118 -16
  88. package/dist/composed/continuation.js.map +1 -1
  89. package/dist/composed/groups.d.ts +3 -0
  90. package/dist/composed/groups.d.ts.map +1 -1
  91. package/dist/composed/groups.js +34 -4
  92. package/dist/composed/groups.js.map +1 -1
  93. package/dist/composed/paint.d.ts +20 -1
  94. package/dist/composed/paint.d.ts.map +1 -1
  95. package/dist/composed/paint.js +40 -0
  96. package/dist/composed/paint.js.map +1 -1
  97. package/dist/composed/rules.d.ts +31 -0
  98. package/dist/composed/rules.d.ts.map +1 -0
  99. package/dist/composed/rules.js +45 -0
  100. package/dist/composed/rules.js.map +1 -0
  101. package/dist/composed/slotChecks.d.ts +2 -1
  102. package/dist/composed/slotChecks.d.ts.map +1 -1
  103. package/dist/composed/slotChecks.js +56 -2
  104. package/dist/composed/slotChecks.js.map +1 -1
  105. package/dist/composed/sourceMap.d.ts.map +1 -1
  106. package/dist/composed/sourceMap.js +15 -1
  107. package/dist/composed/sourceMap.js.map +1 -1
  108. package/dist/composed/spread.d.ts +20 -5
  109. package/dist/composed/spread.d.ts.map +1 -1
  110. package/dist/composed/spread.js +51 -26
  111. package/dist/composed/spread.js.map +1 -1
  112. package/dist/composed/templates.d.ts +11 -0
  113. package/dist/composed/templates.d.ts.map +1 -1
  114. package/dist/composed/templates.js +51 -2
  115. package/dist/composed/templates.js.map +1 -1
  116. package/dist/composed/types.d.ts +43 -3
  117. package/dist/composed/types.d.ts.map +1 -1
  118. package/dist/composed/validate.d.ts.map +1 -1
  119. package/dist/composed/validate.js +5 -3
  120. package/dist/composed/validate.js.map +1 -1
  121. package/dist/configWarnings.d.ts.map +1 -1
  122. package/dist/configWarnings.js +11 -1
  123. package/dist/configWarnings.js.map +1 -1
  124. package/dist/defaults/headerFooter.d.ts.map +1 -1
  125. package/dist/defaults/headerFooter.js +4 -0
  126. package/dist/defaults/headerFooter.js.map +1 -1
  127. package/dist/defaults/resourceTypes.d.ts +15 -0
  128. package/dist/defaults/resourceTypes.d.ts.map +1 -1
  129. package/dist/defaults/resourceTypes.js +39 -1
  130. package/dist/defaults/resourceTypes.js.map +1 -1
  131. package/dist/design/layout.d.ts +7 -0
  132. package/dist/design/layout.d.ts.map +1 -1
  133. package/dist/design/layout.js +109 -16
  134. package/dist/design/layout.js.map +1 -1
  135. package/dist/html-backend.d.ts.map +1 -1
  136. package/dist/html-backend.js +43 -5
  137. package/dist/html-backend.js.map +1 -1
  138. package/dist/htmlComic.d.ts +3 -0
  139. package/dist/htmlComic.d.ts.map +1 -1
  140. package/dist/htmlComic.js +2 -0
  141. package/dist/htmlComic.js.map +1 -1
  142. package/dist/index.d.ts +5 -5
  143. package/dist/index.d.ts.map +1 -1
  144. package/dist/index.js +2 -2
  145. package/dist/index.js.map +1 -1
  146. package/dist/migrate/pageDesign.d.ts +35 -2
  147. package/dist/migrate/pageDesign.d.ts.map +1 -1
  148. package/dist/migrate/pageDesign.js +134 -7
  149. package/dist/migrate/pageDesign.js.map +1 -1
  150. package/dist/migrate/textToMarkdown.d.ts +33 -1
  151. package/dist/migrate/textToMarkdown.d.ts.map +1 -1
  152. package/dist/migrate/textToMarkdown.js +206 -9
  153. package/dist/migrate/textToMarkdown.js.map +1 -1
  154. package/dist/parse/blockParser.js +26 -0
  155. package/dist/parse/blockParser.js.map +1 -1
  156. package/dist/pipeline/build.d.ts.map +1 -1
  157. package/dist/pipeline/build.js +219 -40
  158. package/dist/pipeline/build.js.map +1 -1
  159. package/dist/pipeline/calloutLayout.d.ts +5 -0
  160. package/dist/pipeline/calloutLayout.d.ts.map +1 -1
  161. package/dist/pipeline/calloutLayout.js +70 -0
  162. package/dist/pipeline/calloutLayout.js.map +1 -1
  163. package/dist/pipeline/composedLayout.d.ts.map +1 -1
  164. package/dist/pipeline/composedLayout.js +246 -49
  165. package/dist/pipeline/composedLayout.js.map +1 -1
  166. package/dist/pipeline/contentWarnings.d.ts.map +1 -1
  167. package/dist/pipeline/contentWarnings.js +21 -1
  168. package/dist/pipeline/contentWarnings.js.map +1 -1
  169. package/dist/pipeline/dropCap.d.ts +5 -0
  170. package/dist/pipeline/dropCap.d.ts.map +1 -1
  171. package/dist/pipeline/dropCap.js +23 -2
  172. package/dist/pipeline/dropCap.js.map +1 -1
  173. package/dist/pipeline/frameLayout.d.ts +17 -2
  174. package/dist/pipeline/frameLayout.d.ts.map +1 -1
  175. package/dist/pipeline/frameLayout.js +47 -2
  176. package/dist/pipeline/frameLayout.js.map +1 -1
  177. package/dist/pipeline/headerFooter.d.ts.map +1 -1
  178. package/dist/pipeline/headerFooter.js +41 -2
  179. package/dist/pipeline/headerFooter.js.map +1 -1
  180. package/dist/pipeline/measureContentBlock.d.ts +1 -1
  181. package/dist/pipeline/measureContentBlock.d.ts.map +1 -1
  182. package/dist/pipeline/measureContentBlock.js +4 -1
  183. package/dist/pipeline/measureContentBlock.js.map +1 -1
  184. package/dist/pipeline/resourceNumbering.d.ts.map +1 -1
  185. package/dist/pipeline/resourceNumbering.js +13 -4
  186. package/dist/pipeline/resourceNumbering.js.map +1 -1
  187. package/dist/preflight.js +5 -1
  188. package/dist/preflight.js.map +1 -1
  189. package/dist/types.d.ts +32 -2
  190. package/dist/types.d.ts.map +1 -1
  191. package/dist/vdt.d.ts +90 -2
  192. package/dist/vdt.d.ts.map +1 -1
  193. package/dist/vdt.js.map +1 -1
  194. package/package.json +1 -1
@@ -10,7 +10,7 @@ import { parseMarkdownMemo, spaceDirectiveLines } from '../parse';
10
10
  import { buildPageLabels, computeHeadingNumbering, parseNumberFormat, } from '../numbering';
11
11
  import { extractFrontmatter, normalizeMetadata } from '../frontmatter';
12
12
  import { collectConfigWarnings } from '../configWarnings';
13
- import { initHyphenator } from '../measure';
13
+ import { initHyphenator, measureRichBlock } from '../measure';
14
14
  import { setHyphenationRules } from '../hyphenate';
15
15
  import { citedNames } from '../citations/names';
16
16
  import { getCjkLineBreak, setCjkLineBreak } from '../measure/cjkClasses';
@@ -38,9 +38,9 @@ import { applyStatementNumbering, numberStatements, outlineLookup } from './stat
38
38
  import { SPAN_EMBED_STYLE_ID, isPageSpanStrip, spanEmbedStyle, wrapPageSpanEmbeds } from './spanEmbeds';
39
39
  import { flowColumnsStyles, isFlowColumnsStyle, wrapFlowColumns } from './flowColumns';
40
40
  import { FRAME_STYLE_ID, frameBoxStyle, wrapComposedPages } from '../composed/wrap';
41
- import { resolvePageTemplate } from '../composed/templates';
41
+ import { continueInPage, resolvePageTemplate } from '../composed/templates';
42
42
  import { layoutComposedSpread } from '../composed/spread';
43
- import { assignReceivers, continuationTarget, MAX_CONTINUATION_PAGES } from '../composed/continuation';
43
+ import { assignJumpReceivers, assignReceivers, continuationTarget, MAX_CONTINUATION_PAGES } from '../composed/continuation';
44
44
  import { frameElementId } from '../composed/ids';
45
45
  import { nextFrameOf } from '../composed/geometry';
46
46
  import { composedPageAt, frameStyleOf, layoutComposedPage, planComposedPages } from './composedLayout';
@@ -55,6 +55,7 @@ import { chooseParagraphSplit } from './orphanWidow';
55
55
  import { layoutComicPage, comicPageLayoutWarnings } from '../comics/layoutPage';
56
56
  import { comicStripExtent, comicStripOffset, comicStripPlacement, comicStripWidth, layoutComicStrip } from '../comics/strip';
57
57
  import { isComicSpread, layoutComicSpread } from '../comics/spread';
58
+ import { comicTextPanelsSupported, layoutComicTextFrames, planComicTextPanels, wrapComicTextPanels } from '../comics/textPanels';
58
59
  import { applyStyleAttrs, computePageMetrics, flowPageMirrored, pageMirrored, sheetRectToFlow, isMarkerBlock, nextNonMarkerBlock, spaceLinesAfter, prevNonMarkerBlock, rollbackTrailingBlocks, } from './buildHelpers';
59
60
  import { measureContentBlock } from './measureContentBlock';
60
61
  import { containerStyle, paragraphStyleIdOf, planParagraphContainers } from './paragraphContainers';
@@ -499,7 +500,10 @@ function placeDocumentPass(content, config, cache, options, hints) {
499
500
  // in a frame is a group of its box.
500
501
  const templateSource = { pageTemplates: resolved.pageTemplates, calloutStyles: resolved.calloutStyles };
501
502
  const composedWrap = wrapComposedPages(numberedBlocks, templateSource);
502
- const flowGroups = resolved.layout.flowColumns ? wrapFlowColumns(composedWrap.blocks) : { blocks: composedWrap.blocks, wrapped: false };
503
+ // A comic page's text panels (#885): each panel's Markdown in a story
504
+ // box of the same kind, set in the panel's frame (below).
505
+ const comicText = wrapComicTextPanels(composedWrap.blocks, comicTextPanelsSupported(resolved));
506
+ const flowGroups = resolved.layout.flowColumns ? wrapFlowColumns(comicText.blocks) : { blocks: comicText.blocks, wrapped: false };
503
507
  const spanEmbeds = wrapPageSpanEmbeds(flowGroups.blocks, content.resources ?? [], effectiveResourceTypes(config, content.resources ?? []));
504
508
  // Code listings (#624): each in a box of the code style, which the
505
509
  // callout machinery places and splits.
@@ -554,7 +558,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
554
558
  ...(spanEmbeds.wrapped ? [spanEmbedStyle(bodyStyle.lineHeightPx, resolved.layout.inlineResourceGap !== 'above')] : []),
555
559
  ...(codeBoxes.wrapped ? [codeBoxStyle(resolved)] : []),
556
560
  // The frameless box of a composed page's story (#855).
557
- ...(composedWrap.wrapped ? [frameBoxStyle(Math.max(resolved.bodyText.avoidOrphans ? resolved.bodyText.orphanMinLines : 1, resolved.bodyText.avoidWidows ? resolved.bodyText.widowMinLines : 1))] : []),
561
+ ...(composedWrap.wrapped || comicText.wrapped ? [frameBoxStyle(Math.max(resolved.bodyText.avoidOrphans ? resolved.bodyText.orphanMinLines : 1, resolved.bodyText.avoidWidows ? resolved.bodyText.widowMinLines : 1))] : []),
558
562
  // The boxes of main-flow groups: sub-columns a body line apart in the
559
563
  // text column, a gutter apart across the page.
560
564
  ...(flowGroups.wrapped
@@ -566,7 +570,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
566
570
  : resolved.calloutStyles;
567
571
  // A box nested in another looks its style up in the configuration it is
568
572
  // laid out with: the listings' box among them.
569
- const calloutResolved = codeBoxes.wrapped || flowGroups.wrapped || composedWrap.wrapped ? { ...resolved, calloutStyles } : resolved;
573
+ const calloutResolved = codeBoxes.wrapped || flowGroups.wrapped || composedWrap.wrapped || comicText.wrapped ? { ...resolved, calloutStyles } : resolved;
570
574
  const blockquoteStyle = resolveBlockquoteStyle(resolved);
571
575
  const listLevelIndentsPx = computeLevelIndentsPx(resolved, bodyStyle.fontSizePx);
572
576
  const orderedMetrics = computeOrderedListRunMetrics(contentBlocks, resolved, bodyStyle.fontSizePx);
@@ -602,10 +606,12 @@ function placeDocumentPass(content, config, cache, options, hints) {
602
606
  // story holds does not float. A reference in a frame to a picture set
603
607
  // elsewhere floats from the page's end, to the pages after it.
604
608
  const composedPlans = planComposedPages(contentBlocks, calloutPlan, templateSource);
605
- if (composedPlans.size > 0) {
609
+ // A comic page's text panels (#885) likewise.
610
+ const comicTextPlans = comicText.wrapped ? planComicTextPanels(contentBlocks, calloutPlan) : new Map();
611
+ if (composedPlans.size > 0 || comicTextPlans.size > 0) {
606
612
  for (let k = floatPlan.length - 1; k >= 0; k--) {
607
613
  const f = floatPlan[k];
608
- const page = composedPageAt(composedPlans, f.firstBlockIdx);
614
+ const page = composedPageAt(composedPlans, f.firstBlockIdx) ?? composedPageAt(comicTextPlans, f.firstBlockIdx);
609
615
  if (!page)
610
616
  continue;
611
617
  const raw = contentBlocks[f.firstBlockIdx];
@@ -5955,6 +5961,19 @@ function placeDocumentPass(content, config, cache, options, hints) {
5955
5961
  return false;
5956
5962
  },
5957
5963
  };
5964
+ /** Every frame of the composed pages laid so far, by id: a story that
5965
+ * jumps to a later page (#917) links its pieces across pages. */
5966
+ const frameAt = new Map();
5967
+ /** The planned composed pages by their fence's `id` (#917): where a
5968
+ * `'page:<id>'` continuation lands. */
5969
+ const fenceById = new Map();
5970
+ for (const pl of composedPlans.values()) {
5971
+ const id = contentBlocks[pl.startIdx]?.composedPage?.attrs.id;
5972
+ if (id !== undefined && id !== '' && !fenceById.has(id))
5973
+ fenceById.set(id, pl);
5974
+ }
5975
+ /** Stories waiting for the later page fence they jump to, by its id. */
5976
+ const jumps = new Map();
5958
5977
  /** Set the composed page `planned` on a fresh page (#855), or a spread
5959
5978
  * template on the two facing pages from the next verso (#861). */
5960
5979
  const placeComposedPage = (planned, fence) => {
@@ -6005,11 +6024,15 @@ function placeDocumentPass(content, config, cache, options, hints) {
6005
6024
  recto.columns = [createVDTColumn(0, recto.contentArea)];
6006
6025
  cursor.columnIndex = 0;
6007
6026
  // The sheet is laid in the pages' flow: the left page first, or the
6008
- // right one where the flow is mirrored (right to left).
6027
+ // right one where the flow is mirrored (right to left) or turned (a
6028
+ // vertical page, #926: the flow's y runs from the sheet's right edge
6029
+ // to its left, and the right page comes first along it).
6009
6030
  const versoIsLeft = resolved.page.binding !== 'right';
6010
- const flowMirrored = pageIsMirrored(verso);
6031
+ const vertical = pageIsVertical(verso);
6032
+ const flowMirrored = pageIsMirrored(verso) || vertical;
6011
6033
  const left = versoIsLeft ? verso : recto;
6012
6034
  const right = versoIsLeft ? recto : verso;
6035
+ const frames = designFramesOn(verso);
6013
6036
  const out = layoutComposedSpread({
6014
6037
  ...common(flowMirrored ? right : left, { id: sheet.id, spread: true }),
6015
6038
  ...extra,
@@ -6017,8 +6040,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
6017
6040
  first: flowMirrored ? right : left,
6018
6041
  second: flowMirrored ? left : right,
6019
6042
  firstSide: flowMirrored ? 'right' : 'left',
6020
- trim: pageMetrics.physical.trimBox,
6021
- bleed: pageMetrics.physical.bleedBox,
6043
+ trim: frames.page,
6044
+ bleed: frames.bleed,
6045
+ ...(vertical ? { axis: 'y' } : {}),
6022
6046
  });
6023
6047
  return { sides: out.sides, warnings: out.warnings, laid: out.laid, continuations: out.continuations };
6024
6048
  };
@@ -6034,9 +6058,43 @@ function placeDocumentPass(content, config, cache, options, hints) {
6034
6058
  return { parity, template, areas: { area, page: frames.page, bleed: frames.bleed } };
6035
6059
  };
6036
6060
  let continuations;
6061
+ // Stories that jump to this page (#917): they go on in the frames its
6062
+ // own content leaves empty; those that find none go on after it.
6063
+ const incoming = fence.attrs.id !== undefined ? jumps.get(fence.attrs.id) ?? [] : [];
6064
+ if (fence.attrs.id !== undefined)
6065
+ jumps.delete(fence.attrs.id);
6066
+ let jumpReceivers = new Map();
6067
+ let jumpLeft = [];
6068
+ const receive = (template) => {
6069
+ if (incoming.length === 0)
6070
+ return {};
6071
+ const names = new Set(template.frames.map((f) => f.name));
6072
+ const prev = new Map();
6073
+ for (const f of template.frames) {
6074
+ const n = nextFrameOf(f);
6075
+ if (n && names.has(n) && !prev.has(n))
6076
+ prev.set(n, f.name);
6077
+ }
6078
+ const headOf = (n) => {
6079
+ const seen = new Set([n]);
6080
+ let at = n;
6081
+ for (let q = prev.get(at); q !== undefined && !seen.has(q); q = prev.get(at)) {
6082
+ seen.add(q);
6083
+ at = q;
6084
+ }
6085
+ return at;
6086
+ };
6087
+ const assigned = assignJumpReceivers(template, incoming, (n) => planned.stories.has(headOf(n)));
6088
+ jumpReceivers = assigned.receivers;
6089
+ jumpLeft = assigned.left;
6090
+ return {
6091
+ stories: new Map([...planned.stories, ...[...jumpReceivers].map(([n, c]) => [n, c.story])]),
6092
+ continuing: { receivers: jumpReceivers, generation: 0, collapse: false, last: false, jump: true },
6093
+ };
6094
+ };
6037
6095
  const sheet = resolvePageTemplate(templateSource, fence.template, { spread: true });
6038
6096
  if (sheet?.spread) {
6039
- const out = laySpread(sheet, {});
6097
+ const out = laySpread(sheet, receive(sheet));
6040
6098
  placed.push(...out.sides);
6041
6099
  composedNotes.push(...out.warnings);
6042
6100
  laid = out.laid;
@@ -6045,19 +6103,20 @@ function placeDocumentPass(content, config, cache, options, hints) {
6045
6103
  else {
6046
6104
  const page = doc.pages[cursor.pageIndex];
6047
6105
  const { parity, template, areas } = singleOn(page, fence.template);
6106
+ const extra = receive(template);
6048
6107
  // Kept by what it reads (#857): a page met again is not set again.
6049
- // A spread is laid out afresh each time (its two sides are one sheet).
6050
- const out = layoutComposedPageMemo({
6051
- ...common(page, { id: template.id, spread: false, parity }),
6052
- page,
6053
- template,
6054
- areas,
6055
- }, composedMemo);
6108
+ // A spread is laid out afresh each time (its two sides are one sheet),
6109
+ // and so is a page a story jumps to.
6110
+ const args = { ...common(page, { id: template.id, spread: false, parity }), page, template, areas };
6111
+ const out = extra.continuing ? layoutComposedPage({ ...args, ...extra }) : layoutComposedPageMemo(args, composedMemo);
6056
6112
  placed.push({ page, composed: out.composed, blocks: out.blocks });
6057
6113
  composedNotes.push(...out.warnings);
6058
6114
  laid = out.laid;
6059
6115
  continuations = out.continuations;
6060
6116
  }
6117
+ // A jumping story that found no free frame here goes on after it.
6118
+ if (jumpLeft.length > 0)
6119
+ continuations = [...(continuations ?? []), ...jumpLeft.map((c) => ({ ...c, rule: 'next-page', template: placed[0].composed.template, collapsed: false }))];
6061
6120
  const commit = (list) => {
6062
6121
  for (const { page, composed, blocks } of list) {
6063
6122
  page.composed = composed;
@@ -6073,7 +6132,6 @@ function placeDocumentPass(content, config, cache, options, hints) {
6073
6132
  // (or spread) per template it goes on to, generation after generation,
6074
6133
  // each laid afresh after the pages before it. The added pages are
6075
6134
  // ordinary pages (numbered, with running heads, sides kept).
6076
- const frameAt = new Map();
6077
6135
  const indexFrames = (list) => {
6078
6136
  for (const s of list)
6079
6137
  for (const f of s.composed.frames)
@@ -6081,9 +6139,63 @@ function placeDocumentPass(content, config, cache, options, hints) {
6081
6139
  frameAt.set(f.id, { page: s.page, frame: f });
6082
6140
  };
6083
6141
  indexFrames(placed);
6142
+ /** The sending frame of `c` points to the frame `to` that took it, and
6143
+ * its page's log says where it went. */
6144
+ const link = (c, to) => {
6145
+ const src = frameAt.get(c.fromFrameId);
6146
+ const dst = frameAt.get(to);
6147
+ if (!src || !dst)
6148
+ return;
6149
+ src.frame.continuesTo = to;
6150
+ const log = src.page.composed.log;
6151
+ let at = log.length;
6152
+ for (let i = log.length - 1; i >= 0; i--) {
6153
+ if (log[i].frame === c.frame) {
6154
+ at = i + 1;
6155
+ break;
6156
+ }
6157
+ }
6158
+ log.splice(at, 0, { frame: c.frame, kind: 'continued', toPage: dst.page.index, toFrame: to });
6159
+ };
6160
+ for (const [name, c] of jumpReceivers)
6161
+ link(c, frameElementId(fence.fenceIndex, name));
6162
+ /** Stories that jump to a later page fence (#917) wait for it; one
6163
+ * whose fence is not after this page goes on on the next page, and
6164
+ * says so. */
6165
+ const route = (list) => {
6166
+ const out = [];
6167
+ for (const c of list) {
6168
+ const target = continueInPage(c.rule);
6169
+ if (!target) {
6170
+ out.push(c);
6171
+ continue;
6172
+ }
6173
+ const to = fenceById.get(target.page);
6174
+ if (to && to.startIdx > planned.endIdx) {
6175
+ const waiting = jumps.get(target.page) ?? [];
6176
+ waiting.push(c);
6177
+ jumps.set(target.page, waiting);
6178
+ continue;
6179
+ }
6180
+ const from = frameAt.get(c.fromFrameId);
6181
+ composedNotes.push({
6182
+ kind: 'continueTargetMissing',
6183
+ template: c.template,
6184
+ frame: c.frame,
6185
+ fenceIndex: fence.fenceIndex,
6186
+ target: target.page,
6187
+ configPath: resolvePageTemplate(templateSource, c.template)?.frames.find((f) => f.name === c.frame)?.configPath ?? '',
6188
+ ...(to ? { before: true } : {}),
6189
+ ...(from?.frame.sourceStart !== undefined ? { sourceStart: from.frame.sourceStart, sourceEnd: from.frame.sourceEnd } : {}),
6190
+ ...(from ? { pageIndex: from.page.index } : {}),
6191
+ });
6192
+ out.push({ ...c, rule: 'next-page' });
6193
+ }
6194
+ return out;
6195
+ };
6084
6196
  const overflowTemplateOf = (id) => resolvePageTemplate(templateSource, id)?.overflowTemplate;
6085
6197
  const firstPage = placed[0]?.page.index ?? cursor.pageIndex;
6086
- let pending = continuations ?? [];
6198
+ let pending = route(continuations ?? []);
6087
6199
  for (let generation = 1; pending.length > 0; generation++) {
6088
6200
  const last = generation >= MAX_CONTINUATION_PAGES;
6089
6201
  let queue = pending;
@@ -6164,25 +6276,10 @@ function placeDocumentPass(content, config, cache, options, hints) {
6164
6276
  composedNotes.push(...notes);
6165
6277
  // Each frame that sent content on points to the frame that took it,
6166
6278
  // and its page's log says where it went.
6167
- for (const c of receivers.values()) {
6168
- const to = frameElementId(fence.fenceIndex, c.slot, generation);
6169
- const src = frameAt.get(c.fromFrameId);
6170
- const dst = frameAt.get(to);
6171
- if (!src || !dst)
6172
- continue;
6173
- src.frame.continuesTo = to;
6174
- const log = src.page.composed.log;
6175
- let at = log.length;
6176
- for (let i = log.length - 1; i >= 0; i--) {
6177
- if (log[i].frame === c.frame) {
6178
- at = i + 1;
6179
- break;
6180
- }
6181
- }
6182
- log.splice(at, 0, { frame: c.frame, kind: 'continued', toPage: dst.page.index, toFrame: to });
6183
- }
6279
+ for (const c of receivers.values())
6280
+ link(c, frameElementId(fence.fenceIndex, c.slot, generation));
6184
6281
  if (more)
6185
- pending.push(...more);
6282
+ pending.push(...route(more));
6186
6283
  }
6187
6284
  }
6188
6285
  for (const [head, story] of planned.stories) {
@@ -6194,6 +6291,39 @@ function placeDocumentPass(content, config, cache, options, hints) {
6194
6291
  pendingComposedBreak = 'page';
6195
6292
  flushPendingNumberingAtBoundary();
6196
6293
  };
6294
+ /**
6295
+ * The text panels of a comic page (#885): each story set in its panel's
6296
+ * frame (`layoutComicTextFrames`), its blocks floats of the page; the
6297
+ * stories laid are done for the main loop.
6298
+ */
6299
+ const placeComicText = (page, comic, plan) => {
6300
+ const frameless = calloutStyles.find((st) => st.id === FRAME_STYLE_ID);
6301
+ const out = layoutComicTextFrames({
6302
+ pass: framePass,
6303
+ resolved,
6304
+ page,
6305
+ comic,
6306
+ plan,
6307
+ styleOf: (frame) => frameStyleOf((frame.style ? resolved.calloutStyles.find((st) => st.id === frame.style) : undefined) ?? frameless, frame),
6308
+ dpi,
6309
+ emPx: bodyStyle.fontSizePx,
6310
+ bodyOffset,
6311
+ mirrored: mirroredOf(page),
6312
+ pushGapPx: wrapGapPx(resolved.layout.wrap?.gap, dpi, bodyStyle.fontSizePx, floatGapPx),
6313
+ locale: resolvedLocale(resolved),
6314
+ });
6315
+ const floats = (page.floats ??= []);
6316
+ for (const b of out.blocks) {
6317
+ floats.push(b);
6318
+ doc.blocks.push(b);
6319
+ }
6320
+ for (const [key, story] of plan.stories) {
6321
+ if (!out.laid.has(key))
6322
+ continue;
6323
+ for (let i = story.startIdx; i <= story.plan.endIdx; i++)
6324
+ composedLaid.add(i);
6325
+ }
6326
+ };
6197
6327
  /** The first block of the document that sets something: a heading there
6198
6328
  * opens the document's first page (`openerPages`, #749). */
6199
6329
  const firstFlowBlockIdx = contentBlocks.findIndex((b) => !isMarkerBlock(b) && b.type !== 'directive');
@@ -6478,6 +6608,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
6478
6608
  sourceOffset: bodyOffset,
6479
6609
  mirrorMargins: pageMirrored(resolved, page.index, pageIndexOffset, true),
6480
6610
  });
6611
+ const textPlan = comicTextPlans.get(blockIdx);
6612
+ if (textPlan)
6613
+ placeComicText(page, page.comic, textPlan);
6481
6614
  }
6482
6615
  pendingComicBreak = true;
6483
6616
  flushPendingNumberingAtBoundary();
@@ -8434,6 +8567,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
8434
8567
  page.pageLabel = info.label;
8435
8568
  page.pageNumberFormat = info.format;
8436
8569
  }
8570
+ // Continuation marks with a page number (#917): the page the rest goes
8571
+ // on on, or comes from, now that pages are numbered.
8572
+ fillPageMarks(doc.pages);
8437
8573
  const restarts = pageNumberSegments
8438
8574
  .filter((s, i) => i > 0 && s.startAt !== undefined && s.startPageIndex < doc.pages.length)
8439
8575
  .map((s) => s.startPageIndex);
@@ -8662,6 +8798,49 @@ function afterTextWarnings(doc) {
8662
8798
  }
8663
8799
  return out;
8664
8800
  }
8801
+ /**
8802
+ * Write the page numbers into the continuation marks of composed pages'
8803
+ * frames (#917, `VDTDesignTextBlock.pageMark`): `{page}` becomes the label
8804
+ * of the page the frame's rest goes on on (`continuesTo`) or comes from
8805
+ * (`continuedFrom`). The mark was laid out with a guess of the number; its
8806
+ * lines are measured again with the true one and keep their alignment.
8807
+ */
8808
+ function fillPageMarks(pages) {
8809
+ const frames = new Map();
8810
+ for (const p of pages)
8811
+ for (const f of p.composed?.frames ?? [])
8812
+ if (!f.facingBlocks)
8813
+ frames.set(f.id, p);
8814
+ for (const p of pages) {
8815
+ for (const f of p.composed?.frames ?? []) {
8816
+ for (const id of f.blockIds) {
8817
+ const box = p.floats?.find((b) => b.id === id);
8818
+ for (const d of box?.designOverlay?.blocks ?? []) {
8819
+ if (d.kind !== 'text' || !d.pageMark)
8820
+ continue;
8821
+ const other = d.pageMark.page === 'to' ? f.continuesTo : f.continuedFrom;
8822
+ const at = other !== undefined ? frames.get(other) : undefined;
8823
+ if (!at)
8824
+ continue;
8825
+ const label = at.pageLabel ?? String(at.pageNumberValue ?? at.index + 1);
8826
+ const text = d.pageMark.text.split('{page}').join(label);
8827
+ const lh = d.lines.length > 0 ? d.bbox.height / d.lines.length : d.bbox.height;
8828
+ const first = d.lines[0];
8829
+ const slack0 = first ? d.bbox.width - first.width : 0;
8830
+ const align = !first || first.xOffset < 0.5 ? 'left' : Math.abs(first.xOffset - slack0) < 0.5 ? 'right' : 'center';
8831
+ const base = first ? first.baselineY - d.bbox.y : lh * 0.8;
8832
+ const { lines } = measureRichBlock([{ text, bold: false, italic: false }], d.fontString, d.fontString, d.fontString, d.fontString, d.bbox.width, lh, { textAlign: 'left' });
8833
+ if (lines.length === 0)
8834
+ continue;
8835
+ d.lines = lines.map((ln, i) => {
8836
+ const slack = Math.max(0, d.bbox.width - ln.bbox.width);
8837
+ return { text: ln.text, xOffset: align === 'right' ? slack : align === 'center' ? slack / 2 : 0, baselineY: d.bbox.y + base + i * lh, width: ln.bbox.width };
8838
+ });
8839
+ }
8840
+ }
8841
+ }
8842
+ }
8843
+ }
8665
8844
  /** `doc` with the page of each of its index marks (`doc.indexMarks`). */
8666
8845
  function withIndexMarks(doc, content) {
8667
8846
  const { content: body, contentOffset } = extractFrontmatter(content.markdown);