postext 1.39.1 → 1.41.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 (167) hide show
  1. package/dist/__tests__/comics/frames.test.js +0 -8
  2. package/dist/__tests__/comics/frames.test.js.map +1 -1
  3. package/dist/__tests__/comics/textPanelsStripsSpreads.test.d.ts +2 -0
  4. package/dist/__tests__/comics/textPanelsStripsSpreads.test.d.ts.map +1 -0
  5. package/dist/__tests__/comics/textPanelsStripsSpreads.test.js +277 -0
  6. package/dist/__tests__/comics/textPanelsStripsSpreads.test.js.map +1 -0
  7. package/dist/__tests__/composed/crossChapterJumps.test.d.ts +2 -0
  8. package/dist/__tests__/composed/crossChapterJumps.test.d.ts.map +1 -0
  9. package/dist/__tests__/composed/crossChapterJumps.test.js +299 -0
  10. package/dist/__tests__/composed/crossChapterJumps.test.js.map +1 -0
  11. package/dist/__tests__/composed/fit.test.js +77 -0
  12. package/dist/__tests__/composed/fit.test.js.map +1 -1
  13. package/dist/__tests__/composed/frameModesChain.test.d.ts +2 -0
  14. package/dist/__tests__/composed/frameModesChain.test.d.ts.map +1 -0
  15. package/dist/__tests__/composed/frameModesChain.test.js +69 -0
  16. package/dist/__tests__/composed/frameModesChain.test.js.map +1 -0
  17. package/dist/__tests__/composed/framePictures.test.d.ts +2 -0
  18. package/dist/__tests__/composed/framePictures.test.d.ts.map +1 -0
  19. package/dist/__tests__/composed/framePictures.test.js +121 -0
  20. package/dist/__tests__/composed/framePictures.test.js.map +1 -0
  21. package/dist/__tests__/composed/frameWritingMode.test.js +79 -3
  22. package/dist/__tests__/composed/frameWritingMode.test.js.map +1 -1
  23. package/dist/__tests__/composed/labels.test.js +19 -0
  24. package/dist/__tests__/composed/labels.test.js.map +1 -1
  25. package/dist/__tests__/composed/labelsMirrored.test.d.ts +2 -0
  26. package/dist/__tests__/composed/labelsMirrored.test.d.ts.map +1 -0
  27. package/dist/__tests__/composed/labelsMirrored.test.js +128 -0
  28. package/dist/__tests__/composed/labelsMirrored.test.js.map +1 -0
  29. package/dist/__tests__/composed/measureMemo.test.d.ts +2 -0
  30. package/dist/__tests__/composed/measureMemo.test.d.ts.map +1 -0
  31. package/dist/__tests__/composed/measureMemo.test.js +45 -0
  32. package/dist/__tests__/composed/measureMemo.test.js.map +1 -0
  33. package/dist/__tests__/composedTemplates.test.js +5 -2
  34. package/dist/__tests__/composedTemplates.test.js.map +1 -1
  35. package/dist/__tests__/exports.test.js +6 -0
  36. package/dist/__tests__/exports.test.js.map +1 -1
  37. package/dist/__tests__/migrate/labelsOnPictures.test.js +49 -1
  38. package/dist/__tests__/migrate/labelsOnPictures.test.js.map +1 -1
  39. package/dist/bundle/book.d.ts +5 -0
  40. package/dist/bundle/book.d.ts.map +1 -1
  41. package/dist/bundle/book.js +53 -7
  42. package/dist/bundle/book.js.map +1 -1
  43. package/dist/bundle/layoutCache.d.ts +5 -0
  44. package/dist/bundle/layoutCache.d.ts.map +1 -1
  45. package/dist/bundle/layoutCache.js.map +1 -1
  46. package/dist/canvas-backend/composed.d.ts +2 -2
  47. package/dist/canvas-backend/composed.d.ts.map +1 -1
  48. package/dist/canvas-backend/composed.js +11 -2
  49. package/dist/canvas-backend/composed.js.map +1 -1
  50. package/dist/canvas-backend/index.d.ts.map +1 -1
  51. package/dist/canvas-backend/index.js +10 -1
  52. package/dist/canvas-backend/index.js.map +1 -1
  53. package/dist/canvas-backend/mirrorFrame.d.ts +19 -0
  54. package/dist/canvas-backend/mirrorFrame.d.ts.map +1 -1
  55. package/dist/canvas-backend/mirrorFrame.js +47 -0
  56. package/dist/canvas-backend/mirrorFrame.js.map +1 -1
  57. package/dist/canvas-backend/renderResourceBlock.d.ts.map +1 -1
  58. package/dist/canvas-backend/renderResourceBlock.js +35 -26
  59. package/dist/canvas-backend/renderResourceBlock.js.map +1 -1
  60. package/dist/comics/frames.d.ts +5 -0
  61. package/dist/comics/frames.d.ts.map +1 -1
  62. package/dist/comics/frames.js +7 -0
  63. package/dist/comics/frames.js.map +1 -1
  64. package/dist/comics/layoutPage.d.ts +5 -0
  65. package/dist/comics/layoutPage.d.ts.map +1 -1
  66. package/dist/comics/layoutPage.js +9 -0
  67. package/dist/comics/layoutPage.js.map +1 -1
  68. package/dist/comics/sourceMap.d.ts.map +1 -1
  69. package/dist/comics/sourceMap.js +6 -4
  70. package/dist/comics/sourceMap.js.map +1 -1
  71. package/dist/comics/spread.d.ts +6 -1
  72. package/dist/comics/spread.d.ts.map +1 -1
  73. package/dist/comics/spread.js +15 -1
  74. package/dist/comics/spread.js.map +1 -1
  75. package/dist/comics/textPanels.d.ts +30 -6
  76. package/dist/comics/textPanels.d.ts.map +1 -1
  77. package/dist/comics/textPanels.js +226 -69
  78. package/dist/comics/textPanels.js.map +1 -1
  79. package/dist/comics/transform.d.ts.map +1 -1
  80. package/dist/comics/transform.js +4 -2
  81. package/dist/comics/transform.js.map +1 -1
  82. package/dist/composed/carry.d.ts +53 -0
  83. package/dist/composed/carry.d.ts.map +1 -0
  84. package/dist/composed/carry.js +114 -0
  85. package/dist/composed/carry.js.map +1 -0
  86. package/dist/composed/carryTypes.d.ts +76 -0
  87. package/dist/composed/carryTypes.d.ts.map +1 -0
  88. package/dist/composed/carryTypes.js +2 -0
  89. package/dist/composed/carryTypes.js.map +1 -0
  90. package/dist/composed/continuation.d.ts +8 -0
  91. package/dist/composed/continuation.d.ts.map +1 -1
  92. package/dist/composed/continuation.js.map +1 -1
  93. package/dist/composed/labels.d.ts +9 -0
  94. package/dist/composed/labels.d.ts.map +1 -1
  95. package/dist/composed/labels.js +36 -4
  96. package/dist/composed/labels.js.map +1 -1
  97. package/dist/composed/paint.d.ts +10 -6
  98. package/dist/composed/paint.d.ts.map +1 -1
  99. package/dist/composed/paint.js +38 -7
  100. package/dist/composed/paint.js.map +1 -1
  101. package/dist/composed/sourceMap.d.ts +16 -0
  102. package/dist/composed/sourceMap.d.ts.map +1 -1
  103. package/dist/composed/sourceMap.js +43 -9
  104. package/dist/composed/sourceMap.js.map +1 -1
  105. package/dist/composed/templates.d.ts.map +1 -1
  106. package/dist/composed/templates.js +9 -4
  107. package/dist/composed/templates.js.map +1 -1
  108. package/dist/composed/types.d.ts +9 -4
  109. package/dist/composed/types.d.ts.map +1 -1
  110. package/dist/html-backend.d.ts.map +1 -1
  111. package/dist/html-backend.js +36 -14
  112. package/dist/html-backend.js.map +1 -1
  113. package/dist/index.d.ts +4 -3
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.js +3 -3
  116. package/dist/index.js.map +1 -1
  117. package/dist/migrate/pageDesign.d.ts +6 -1
  118. package/dist/migrate/pageDesign.d.ts.map +1 -1
  119. package/dist/migrate/pageDesign.js +26 -10
  120. package/dist/migrate/pageDesign.js.map +1 -1
  121. package/dist/migrate/textToMarkdown.js +4 -3
  122. package/dist/migrate/textToMarkdown.js.map +1 -1
  123. package/dist/pipeline/build.d.ts +12 -0
  124. package/dist/pipeline/build.d.ts.map +1 -1
  125. package/dist/pipeline/build.js +304 -25
  126. package/dist/pipeline/build.js.map +1 -1
  127. package/dist/pipeline/calloutLayout.d.ts +13 -17
  128. package/dist/pipeline/calloutLayout.d.ts.map +1 -1
  129. package/dist/pipeline/calloutLayout.js +20 -1
  130. package/dist/pipeline/calloutLayout.js.map +1 -1
  131. package/dist/pipeline/composedLayout.d.ts +12 -0
  132. package/dist/pipeline/composedLayout.d.ts.map +1 -1
  133. package/dist/pipeline/composedLayout.js +179 -25
  134. package/dist/pipeline/composedLayout.js.map +1 -1
  135. package/dist/pipeline/contentWarnings.d.ts.map +1 -1
  136. package/dist/pipeline/contentWarnings.js +5 -3
  137. package/dist/pipeline/contentWarnings.js.map +1 -1
  138. package/dist/pipeline/continuation.d.ts +11 -0
  139. package/dist/pipeline/continuation.d.ts.map +1 -1
  140. package/dist/pipeline/continuation.js +36 -0
  141. package/dist/pipeline/continuation.js.map +1 -1
  142. package/dist/pipeline/cutTypes.d.ts +42 -0
  143. package/dist/pipeline/cutTypes.d.ts.map +1 -0
  144. package/dist/pipeline/cutTypes.js +6 -0
  145. package/dist/pipeline/cutTypes.js.map +1 -0
  146. package/dist/pipeline/frameLayout.d.ts +8 -22
  147. package/dist/pipeline/frameLayout.d.ts.map +1 -1
  148. package/dist/pipeline/frameLayout.js +1 -0
  149. package/dist/pipeline/frameLayout.js.map +1 -1
  150. package/dist/pipeline/index.d.ts +2 -1
  151. package/dist/pipeline/index.d.ts.map +1 -1
  152. package/dist/pipeline/index.js +2 -1
  153. package/dist/pipeline/index.js.map +1 -1
  154. package/dist/pipeline/measureMemo.d.ts +39 -0
  155. package/dist/pipeline/measureMemo.d.ts.map +1 -0
  156. package/dist/pipeline/measureMemo.js +61 -0
  157. package/dist/pipeline/measureMemo.js.map +1 -0
  158. package/dist/pipeline/mirrorFrame.d.ts +2 -1
  159. package/dist/pipeline/mirrorFrame.d.ts.map +1 -1
  160. package/dist/pipeline/mirrorFrame.js +31 -5
  161. package/dist/pipeline/mirrorFrame.js.map +1 -1
  162. package/dist/types.d.ts +16 -0
  163. package/dist/types.d.ts.map +1 -1
  164. package/dist/vdt.d.ts +53 -0
  165. package/dist/vdt.d.ts.map +1 -1
  166. package/dist/vdt.js.map +1 -1
  167. package/package.json +1 -1
@@ -44,7 +44,8 @@ import { layoutComposedSpread } from '../composed/spread';
44
44
  import { assignJumpReceivers, assignReceivers, continuationTarget, MAX_CONTINUATION_PAGES } from '../composed/continuation';
45
45
  import { frameElementId } from '../composed/ids';
46
46
  import { nextFrameOf } from '../composed/geometry';
47
- import { composedPageAt, frameStyleOf, layoutComposedPage, planComposedPages } from './composedLayout';
47
+ import { carriedPass, composedPageAt, frameStyleOf, layoutComposedPage, planComposedPages } from './composedLayout';
48
+ import { importJumps } from '../composed/carry';
48
49
  import { configKeyOf, layoutComposedPageMemo } from '../composed/pageMemo';
49
50
  import { anchorOutline, computeOutline, hasIndexDirective, hasTocDirective, headingNumberingOptions, headingTemplatesOf, outlineFromDoc, sameOutline } from './outline';
50
51
  import { expandTocDirectives } from './toc';
@@ -55,8 +56,8 @@ import { resetLinePositions, createPageWithColumns, currentColumn, advanceToNext
55
56
  import { chooseParagraphSplit } from './orphanWidow';
56
57
  import { layoutComicPage, comicPageLayoutWarnings } from '../comics/layoutPage';
57
58
  import { comicStripExtent, comicStripOffset, comicStripPlacement, comicStripWidth, layoutComicStrip } from '../comics/strip';
58
- import { isComicSpread, layoutComicSpread } from '../comics/spread';
59
- import { comicTextPanelsSupported, layoutComicTextFrames, planComicTextPanels, wrapComicTextPanels } from '../comics/textPanels';
59
+ import { comicSpreadGutter, isComicSpread, layoutComicSpread } from '../comics/spread';
60
+ import { comicTextPanelsSupported, layoutComicTextFrames, layoutStripTextFrames, planComicTextPanels, wrapComicTextPanels } from '../comics/textPanels';
60
61
  import { applyStyleAttrs, computePageMetrics, flowPageMirrored, pageMirrored, sheetRectToFlow, isMarkerBlock, nextNonMarkerBlock, spaceLinesAfter, prevNonMarkerBlock, rollbackTrailingBlocks, } from './buildHelpers';
61
62
  import { measureContentBlock } from './measureContentBlock';
62
63
  import { containerStyle, paragraphStyleIdOf, planParagraphContainers } from './paragraphContainers';
@@ -485,7 +486,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
485
486
  const anchorRefs = hasAnchorRefs(parsedBlocks, resourceIds);
486
487
  const outline = content.outline
487
488
  ?? (hasTocDirective(parsedBlocks) || hasIndexDirective(parsedBlocks) || anchorRefs ? computeOutline(parsedBlocks, resolved, headingStart, continuation?.statementCounters) : undefined);
488
- const anchorRefContext = anchorRefs
489
+ // A story carried in from another chapter (#943) may refer to anchors too.
490
+ const carriedRefs = continuation?.jumps?.some((j) => j.outline) === true;
491
+ const anchorRefContext = anchorRefs || carriedRefs
489
492
  ? { targets: anchorTargetsOf(outline), strings: resolved.crossRefs }
490
493
  : undefined;
491
494
  const indexExpanded = expandIndexDirectives(expandTocDirectives(parsedBlocks, outline, resolved), outline, resolved);
@@ -518,6 +521,15 @@ function placeDocumentPass(content, config, cache, options, hints) {
518
521
  // callout machinery places and splits.
519
522
  const codeBoxes = wrapCodeBlocks(spanEmbeds.blocks, resolved);
520
523
  const contentBlocks = codeBoxes.blocks;
524
+ // Stories of earlier chapters that jump into a page of this one (#943):
525
+ // appended past the chapter's blocks by the composed pages' layout, with
526
+ // the notes they cite numbered as their chapter numbered them.
527
+ const carried = continuation?.jumps && continuation.jumps.length > 0 ? importJumps(continuation.jumps, contentBlocks.length, bodyOffset) : undefined;
528
+ for (const note of carried?.footnotes ?? []) {
529
+ footnoteNumbering.numbers.set(note.id, note.number);
530
+ if (note.def)
531
+ footnoteDefs.set(note.id, note.def);
532
+ }
521
533
  if (indexExpanded.warnings.length > 0)
522
534
  indexWarnings.set(doc, indexExpanded.warnings);
523
535
  const isNumbered = (b) => headingIsNumbered(b, resolved);
@@ -5712,7 +5724,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
5712
5724
  * needed; the verso and the recto after it get one half each
5713
5725
  * (`layoutComicSpread`). The cursor is left on the recto.
5714
5726
  */
5715
- const placeComicSpread = (source) => {
5727
+ const placeComicSpread = (source, textPlan) => {
5716
5728
  breakToEvenPage();
5717
5729
  const verso = doc.pages[cursor.pageIndex];
5718
5730
  verso.columns = [createVDTColumn(0, verso.contentArea)];
@@ -5738,6 +5750,17 @@ function placeDocumentPass(content, config, cache, options, hints) {
5738
5750
  });
5739
5751
  verso.comic = versoIsLeft ? left : right;
5740
5752
  recto.comic = versoIsLeft ? right : left;
5753
+ // The text panels (#959): each on the page that holds most of its
5754
+ // panel, off the gutter.
5755
+ if (textPlan) {
5756
+ const sides = [[verso, verso.comic, recto.comic], [recto, recto.comic, verso.comic]];
5757
+ for (const [page, own, other] of sides) {
5758
+ const width = (c, index) => c.panels.find((q) => q.index === index)?.bbox.width ?? 0;
5759
+ const mine = new Set(own.panels.filter((q) => width(own, q.index) > width(other, q.index) || (width(own, q.index) === width(other, q.index) && page === (versoIsLeft ? verso : recto))).map((q) => q.index));
5760
+ const gutter = comicSpreadGutter(own, dpi, page.height);
5761
+ placeComicText(page, own, textPlan, { only: mine, ...(gutter ? { avoid: [gutter] } : {}) });
5762
+ }
5763
+ }
5741
5764
  };
5742
5765
  /**
5743
5766
  * `:::strip` (#566): a comic in the flow, kept together. Set `here`, it
@@ -5986,6 +6009,93 @@ function placeDocumentPass(content, config, cache, options, hints) {
5986
6009
  }
5987
6010
  /** Stories waiting for the later page fence they jump to, by its id. */
5988
6011
  const jumps = new Map();
6012
+ /** The closing marker of the fence each composed page was set from. */
6013
+ const fenceEnd = new Map();
6014
+ /** The carried stories are laid through a pass that answers for them;
6015
+ * they wait for their fence with the others. */
6016
+ const composedPass = carried ? carriedPass(framePass, carried) : framePass;
6017
+ /** Stories of this chapter that jump into a page of a later one (#943). */
6018
+ const pendingJumps = [];
6019
+ /** The rest of the story `c` cut, as the chapter it goes to needs it
6020
+ * (#943): its box and children as this chapter has them ready to lay,
6021
+ * the cut, and the numbers the lookups by position would give. */
6022
+ const exportJump = (c, page) => {
6023
+ const startIdx = c.story.startIdx;
6024
+ const again = carried?.stories.get(startIdx);
6025
+ const all = composedPass.contentBlocks;
6026
+ const blocks = all.slice(startIdx, c.story.plan.endIdx + 1);
6027
+ const numbers = again ? [...(again.jump.numbers ?? [])] : [];
6028
+ if (!again) {
6029
+ for (let i = startIdx; i <= c.story.plan.endIdx; i++) {
6030
+ if (headingPrefixes[i] === undefined && headingNumbers[i] === undefined)
6031
+ continue;
6032
+ numbers.push({
6033
+ at: i - startIdx,
6034
+ ...(headingPrefixes[i] !== undefined ? { prefix: headingPrefixes[i] } : {}),
6035
+ ...(headingNumbers[i] !== undefined ? { value: headingNumbers[i] } : {}),
6036
+ ...(chapterNumberByBlock[i] ? { chapterNumber: chapterNumberByBlock[i] } : {}),
6037
+ });
6038
+ }
6039
+ }
6040
+ const notes = [];
6041
+ const seen = new Set();
6042
+ for (const b of blocks) {
6043
+ for (const span of b.spans) {
6044
+ const id = span.footnote?.id;
6045
+ if (id === undefined || seen.has(id))
6046
+ continue;
6047
+ seen.add(id);
6048
+ const number = footnoteNumbering.numbers.get(id);
6049
+ if (number === undefined)
6050
+ continue;
6051
+ const def = footnoteDefs.get(id);
6052
+ notes.push({ id, number, ...(def ? { def } : {}) });
6053
+ }
6054
+ }
6055
+ const rule = c.rule;
6056
+ const frame = continueInPage(rule)?.frame;
6057
+ return {
6058
+ ...(again ? { chapter: again.chapter } : {}),
6059
+ target: page,
6060
+ ...(frame !== undefined ? { targetFrame: frame } : {}),
6061
+ rule,
6062
+ frame: c.frame,
6063
+ fromFrameId: c.fromFrameId,
6064
+ head: c.head,
6065
+ template: c.template,
6066
+ collapsed: c.collapsed,
6067
+ slot: c.slot,
6068
+ pieces: c.pieces,
6069
+ headBlockId: c.headBlockId,
6070
+ ...(c.trailing ? { trailing: c.trailing } : {}),
6071
+ ...(c.fromMark !== undefined ? { fromMark: c.fromMark } : {}),
6072
+ cut: c.cut,
6073
+ blocks: blocks,
6074
+ bodyOffset,
6075
+ ...(numbers.length > 0 ? { numbers } : {}),
6076
+ orderedLevelIndentsPx: again?.jump.orderedLevelIndentsPx ?? orderedLevelIndentsPx,
6077
+ ...(notes.length > 0 ? { footnotes: notes } : {}),
6078
+ ...(hasAnchorRefs(blocks, new Set((content.resources ?? []).map((r) => r.id))) ? { outline: true } : {}),
6079
+ };
6080
+ };
6081
+ for (const [id, list] of carried?.byTarget ?? []) {
6082
+ if (fenceById.has(id)) {
6083
+ jumps.set(id, [...(jumps.get(id) ?? []), ...list]);
6084
+ continue;
6085
+ }
6086
+ // The host named this chapter for a page it does not have.
6087
+ for (const c of list) {
6088
+ composedNotes.push({
6089
+ kind: 'continueTargetMissing',
6090
+ template: c.template,
6091
+ frame: c.frame,
6092
+ fenceIndex: -1,
6093
+ target: id,
6094
+ fromChapter: c.foreign?.chapter ?? -1,
6095
+ configPath: '',
6096
+ });
6097
+ }
6098
+ }
5989
6099
  /** Set the composed page `planned` on a fresh page (#855), or a spread
5990
6100
  * template on the two facing pages from the next verso (#861). */
5991
6101
  const placeComposedPage = (planned, fence) => {
@@ -5999,7 +6109,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
5999
6109
  * other templates of the same kind (single pages for this side, or
6000
6110
  * spreads). */
6001
6111
  const common = (page, current) => ({
6002
- pass: framePass,
6112
+ pass: composedPass,
6003
6113
  fence,
6004
6114
  startIdx: planned.startIdx,
6005
6115
  endIdx: planned.endIdx,
@@ -6132,6 +6242,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
6132
6242
  const commit = (list) => {
6133
6243
  for (const { page, composed, blocks } of list) {
6134
6244
  page.composed = composed;
6245
+ fenceEnd.set(composed, planned.endIdx);
6135
6246
  const floats = (page.floats ??= []);
6136
6247
  for (const b of blocks) {
6137
6248
  floats.push(b);
@@ -6153,12 +6264,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
6153
6264
  indexFrames(placed);
6154
6265
  /** The sending frame of `c` points to the frame `to` that took it, and
6155
6266
  * its page's log says where it went. */
6156
- const link = (c, to) => {
6157
- const src = frameAt.get(c.fromFrameId);
6158
- const dst = frameAt.get(to);
6159
- if (!src || !dst)
6160
- return;
6161
- src.frame.continuesTo = to;
6267
+ const logContinued = (src, c, event) => {
6162
6268
  const log = src.page.composed.log;
6163
6269
  let at = log.length;
6164
6270
  for (let i = log.length - 1; i >= 0; i--) {
@@ -6167,7 +6273,19 @@ function placeDocumentPass(content, config, cache, options, hints) {
6167
6273
  break;
6168
6274
  }
6169
6275
  }
6170
- log.splice(at, 0, { frame: c.frame, kind: 'continued', toPage: dst.page.index, toFrame: to });
6276
+ log.splice(at, 0, { frame: c.frame, kind: 'continued', ...event });
6277
+ };
6278
+ const link = (c, to) => {
6279
+ // The sending frame is on a page of another chapter: the host links
6280
+ // the two (`resolveJumpMarks`).
6281
+ if (c.foreign)
6282
+ return;
6283
+ const src = frameAt.get(c.fromFrameId);
6284
+ const dst = frameAt.get(to);
6285
+ if (!src || !dst)
6286
+ return;
6287
+ src.frame.continuesTo = to;
6288
+ logContinued(src, c, { toPage: dst.page.index, toFrame: to });
6171
6289
  };
6172
6290
  for (const [name, c] of jumpReceivers)
6173
6291
  link(c, frameElementId(fence.fenceIndex, name));
@@ -6190,6 +6308,16 @@ function placeDocumentPass(content, config, cache, options, hints) {
6190
6308
  continue;
6191
6309
  }
6192
6310
  const from = frameAt.get(c.fromFrameId);
6311
+ // A page of a later chapter (#943): the host hands the rest over.
6312
+ const later = !to && content.laterPageIds && Object.prototype.hasOwnProperty.call(content.laterPageIds, target.page) ? content.laterPageIds[target.page] : undefined;
6313
+ if (later !== undefined) {
6314
+ pendingJumps.push(exportJump(c, target.page));
6315
+ if (from) {
6316
+ from.frame.continuesToChapter = later;
6317
+ logContinued(from, c, { toChapter: later });
6318
+ }
6319
+ continue;
6320
+ }
6193
6321
  composedNotes.push({
6194
6322
  kind: 'continueTargetMissing',
6195
6323
  template: c.template,
@@ -6308,13 +6436,12 @@ function placeDocumentPass(content, config, cache, options, hints) {
6308
6436
  * frame (`layoutComicTextFrames`), its blocks floats of the page; the
6309
6437
  * stories laid are done for the main loop.
6310
6438
  */
6311
- const placeComicText = (page, comic, plan) => {
6439
+ const comicTextBase = (page, plan) => {
6312
6440
  const frameless = calloutStyles.find((st) => st.id === FRAME_STYLE_ID);
6313
- const out = layoutComicTextFrames({
6441
+ return {
6314
6442
  pass: framePass,
6315
6443
  resolved,
6316
6444
  page,
6317
- comic,
6318
6445
  plan,
6319
6446
  styleOf: (frame) => frameStyleOf((frame.style ? resolved.calloutStyles.find((st) => st.id === frame.style) : undefined) ?? frameless, frame),
6320
6447
  dpi,
@@ -6323,19 +6450,64 @@ function placeDocumentPass(content, config, cache, options, hints) {
6323
6450
  mirrored: mirroredOf(page),
6324
6451
  pushGapPx: wrapGapPx(resolved.layout.wrap?.gap, dpi, bodyStyle.fontSizePx, floatGapPx),
6325
6452
  locale: resolvedLocale(resolved),
6326
- });
6453
+ };
6454
+ };
6455
+ /** The blocks a comic's text frames set, floats of the page. */
6456
+ const addComicTextBlocks = (page, blocks) => {
6327
6457
  const floats = (page.floats ??= []);
6328
- for (const b of out.blocks) {
6458
+ for (const b of blocks) {
6329
6459
  floats.push(b);
6330
6460
  doc.blocks.push(b);
6331
6461
  }
6462
+ };
6463
+ /** The stories of a plan are done for the main loop. */
6464
+ const markComicStoriesLaid = (plan, laid) => {
6332
6465
  for (const [key, story] of plan.stories) {
6333
- if (!out.laid.has(key))
6466
+ if (laid && !laid.has(key))
6334
6467
  continue;
6335
6468
  for (let i = story.startIdx; i <= story.plan.endIdx; i++)
6336
6469
  composedLaid.add(i);
6337
6470
  }
6338
6471
  };
6472
+ // The text panels of a strip are laid where the strip ends up
6473
+ // (`placeStripTexts`): the blocks that follow it are done from the start.
6474
+ for (const [idx, plan] of comicTextPlans)
6475
+ if (contentBlocks[idx]?.directiveName === 'strip')
6476
+ markComicStoriesLaid(plan);
6477
+ const placeComicText = (page, comic, plan, extra) => {
6478
+ const out = layoutComicTextFrames({ ...extra, ...comicTextBase(page, plan), comic });
6479
+ addComicTextBlocks(page, out.blocks);
6480
+ markComicStoriesLaid(plan, out.laid);
6481
+ return out;
6482
+ };
6483
+ /**
6484
+ * The text panels of the strips on the pages (#959), laid where the strips
6485
+ * stand once the pass has placed everything (a strip moves with its
6486
+ * column, its band and the pages' final adjustments): each strip's comic
6487
+ * moved onto the sheet, its stories set in the page's flow frame, the
6488
+ * result written back on the strip.
6489
+ */
6490
+ const placeStripTexts = () => {
6491
+ if (comicTextPlans.size === 0)
6492
+ return;
6493
+ for (const page of doc.pages) {
6494
+ const strips = new Set();
6495
+ for (const col of page.columns)
6496
+ for (const b of col.blocks)
6497
+ if (b.comic && !b.hidden)
6498
+ strips.add(b);
6499
+ for (const b of page.floats ?? [])
6500
+ if (b.comic && !b.hidden)
6501
+ strips.add(b);
6502
+ for (const block of strips) {
6503
+ const plan = block.contentIndex !== undefined && block.contentIndex >= 0 ? comicTextPlans.get(block.contentIndex) : undefined;
6504
+ if (!plan)
6505
+ continue;
6506
+ const out = layoutStripTextFrames({ ...comicTextBase(page, plan), block });
6507
+ addComicTextBlocks(page, out.blocks);
6508
+ }
6509
+ }
6510
+ };
6339
6511
  /** The first block of the document that sets something: a heading there
6340
6512
  * opens the document's first page (`openerPages`, #749). */
6341
6513
  const firstFlowBlockIdx = contentBlocks.findIndex((b) => !isMarkerBlock(b) && b.type !== 'directive');
@@ -6607,7 +6779,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
6607
6779
  leaveCurrentPage();
6608
6780
  cursor.columnIndex = 0;
6609
6781
  if (isComicSpread(rawBlock.comic))
6610
- placeComicSpread(rawBlock.comic);
6782
+ placeComicSpread(rawBlock.comic, comicTextPlans.get(blockIdx));
6611
6783
  else {
6612
6784
  const page = doc.pages[cursor.pageIndex];
6613
6785
  page.columns = [createVDTColumn(0, page.contentArea)];
@@ -8518,6 +8690,8 @@ function placeDocumentPass(content, config, cache, options, hints) {
8518
8690
  if (page)
8519
8691
  centrePageText(page, reservedOf);
8520
8692
  }
8693
+ // The text panels of the strips, where the strips stand now (#959).
8694
+ placeStripTexts();
8521
8695
  // A band that is still cut at the end of the pass (a trailing cap: the
8522
8696
  // closing band of a chapter or of the document) must hold what it took,
8523
8697
  // under the same rules as an uncut one. Two ways a cut too low breaks
@@ -8642,6 +8816,10 @@ function placeDocumentPass(content, config, cache, options, hints) {
8642
8816
  // Composed pages (#855): frames whose content does not fit.
8643
8817
  if (composedNotes.length > 0)
8644
8818
  composedWarningsByDoc.set(doc, composedNotes);
8819
+ if (pendingJumps.length > 0)
8820
+ doc.pendingJumps = pendingJumps;
8821
+ if (carried)
8822
+ stampCarriedSource(doc, carried, fenceEnd);
8645
8823
  // The composition rules (#749): short lists cut, floats on opener pages.
8646
8824
  const ruleNotes = [...shortListWarnings(), ...openerFloatNotes, ...tocGroupWarnings()];
8647
8825
  if (ruleNotes.length > 0)
@@ -8810,6 +8988,39 @@ function afterTextWarnings(doc) {
8810
8988
  }
8811
8989
  return out;
8812
8990
  }
8991
+ /**
8992
+ * Mark the blocks laid from stories that came in from other chapters
8993
+ * (#943): `VDTBlock.sourceChapter` says whose file their source offsets
8994
+ * are offsets in.
8995
+ */
8996
+ function stampCarriedSource(doc, carried, fenceEnd) {
8997
+ const stories = [...carried.stories.values()];
8998
+ const noteChapter = new Map(carried.footnotes.map((n) => [n.id, n.chapter]));
8999
+ for (const block of doc.blocks) {
9000
+ const note = block.footnoteNote !== undefined ? noteChapter.get(block.footnoteNote) : undefined;
9001
+ if (note !== undefined)
9002
+ block.sourceChapter = note;
9003
+ const at = block.contentIndex;
9004
+ if (at === undefined)
9005
+ continue;
9006
+ const story = stories.find((st) => at >= st.startIdx && at <= st.endIdx);
9007
+ if (story)
9008
+ block.sourceChapter = story.chapter;
9009
+ }
9010
+ // The frames that set a carried story's text: the box (a block of the
9011
+ // frame) says whose it is.
9012
+ for (const page of doc.pages) {
9013
+ for (const frame of page.composed?.frames ?? []) {
9014
+ const box = frame.blockIds.length > 0 ? page.floats?.find((b) => b.id === frame.blockIds[0]) : undefined;
9015
+ if (box?.sourceChapter === undefined)
9016
+ continue;
9017
+ frame.sourceChapter = box.sourceChapter;
9018
+ const end = fenceEnd.get(page.composed);
9019
+ if (end !== undefined)
9020
+ frame.readIndex = end + 0.5;
9021
+ }
9022
+ }
9023
+ }
8813
9024
  /**
8814
9025
  * Write the page numbers into the continuation marks of composed pages'
8815
9026
  * frames (#917, `VDTDesignTextBlock.pageMark`): `{page}` becomes the label
@@ -8823,6 +9034,22 @@ function fillPageMarks(pages) {
8823
9034
  for (const f of p.composed?.frames ?? [])
8824
9035
  if (!f.facingBlocks)
8825
9036
  frames.set(f.id, p);
9037
+ forEachPageMark(pages, (f, d) => {
9038
+ // A page of another chapter is `resolveJumpMarks`'s (its frame ids are
9039
+ // another chapter's).
9040
+ if ((d.pageMark.page === 'to' ? f.continuesToChapter : f.continuedFromChapter) !== undefined)
9041
+ return undefined;
9042
+ const other = d.pageMark.page === 'to' ? f.continuesTo : f.continuedFrom;
9043
+ const at = other !== undefined ? frames.get(other) : undefined;
9044
+ return at ? pageLabelOf(at) : undefined;
9045
+ });
9046
+ }
9047
+ /** The label a page prints. */
9048
+ const pageLabelOf = (page) => page.pageLabel ?? String(page.pageNumberValue ?? page.index + 1);
9049
+ /** Each continuation mark with a page number of `pages`' composed frames,
9050
+ * with `label` answering for the page it names: the mark's text is laid
9051
+ * out again with the number when it answers. */
9052
+ function forEachPageMark(pages, label) {
8826
9053
  for (const p of pages) {
8827
9054
  for (const f of p.composed?.frames ?? []) {
8828
9055
  for (const id of f.blockIds) {
@@ -8830,12 +9057,10 @@ function fillPageMarks(pages) {
8830
9057
  for (const d of box?.designOverlay?.blocks ?? []) {
8831
9058
  if (d.kind !== 'text' || !d.pageMark)
8832
9059
  continue;
8833
- const other = d.pageMark.page === 'to' ? f.continuesTo : f.continuedFrom;
8834
- const at = other !== undefined ? frames.get(other) : undefined;
8835
- if (!at)
9060
+ const shown = label(f, d);
9061
+ if (shown === undefined)
8836
9062
  continue;
8837
- const label = at.pageLabel ?? String(at.pageNumberValue ?? at.index + 1);
8838
- const text = d.pageMark.text.split('{page}').join(label);
9063
+ const text = d.pageMark.text.split('{page}').join(shown);
8839
9064
  const lh = d.lines.length > 0 ? d.bbox.height / d.lines.length : d.bbox.height;
8840
9065
  const first = d.lines[0];
8841
9066
  const slack0 = first ? d.bbox.width - first.width : 0;
@@ -8853,6 +9078,60 @@ function fillPageMarks(pages) {
8853
9078
  }
8854
9079
  }
8855
9080
  }
9081
+ /**
9082
+ * Link the stories that jump from one chapter to a page of another (#943)
9083
+ * and write the page numbers into their continuation marks. `docs` is the
9084
+ * book laid out, one document per chapter, in the order of the book (a
9085
+ * story's `chapter` is its position in it): the sending frame gets the
9086
+ * frame that took the rest (`continuesTo`, its `continued` event the page
9087
+ * and frame there), and every mark naming a page of another chapter
9088
+ * prints that page's label as its chapter numbered it. Marks within a
9089
+ * chapter were filled by its own build. Safe to run again on the same
9090
+ * documents.
9091
+ */
9092
+ export function resolveJumpMarks(docs) {
9093
+ const byId = docs.map((doc) => {
9094
+ const out = new Map();
9095
+ for (const p of doc.pages)
9096
+ for (const f of p.composed?.frames ?? [])
9097
+ if (!f.facingBlocks)
9098
+ out.set(f.id, p);
9099
+ return out;
9100
+ });
9101
+ const frameOf = (chapter, id) => {
9102
+ const page = byId[chapter]?.get(id);
9103
+ const frame = page?.composed?.frames.find((f) => f.id === id);
9104
+ return page && frame ? { page, frame } : undefined;
9105
+ };
9106
+ // The receiving frames name where their story came from; give the
9107
+ // sending frame the way on.
9108
+ docs.forEach((doc) => {
9109
+ for (const p of doc.pages) {
9110
+ for (const f of p.composed?.frames ?? []) {
9111
+ if (f.continuedFromChapter === undefined || f.continuedFrom === undefined)
9112
+ continue;
9113
+ const from = frameOf(f.continuedFromChapter, f.continuedFrom);
9114
+ if (!from)
9115
+ continue;
9116
+ from.frame.continuesTo = f.id;
9117
+ const event = from.page.composed.log.find((e) => e.kind === 'continued' && e.frame === from.frame.name && e.toChapter !== undefined);
9118
+ if (event) {
9119
+ event.toPage = p.index;
9120
+ event.toFrame = f.id;
9121
+ }
9122
+ }
9123
+ }
9124
+ });
9125
+ docs.forEach((doc) => {
9126
+ forEachPageMark(doc.pages, (f, d) => {
9127
+ const [chapter, id] = d.pageMark.page === 'to' ? [f.continuesToChapter, f.continuesTo] : [f.continuedFromChapter, f.continuedFrom];
9128
+ if (chapter === undefined || id === undefined)
9129
+ return undefined;
9130
+ const at = byId[chapter]?.get(id);
9131
+ return at ? pageLabelOf(at) : undefined;
9132
+ });
9133
+ });
9134
+ }
8856
9135
  /** `doc` with the page of each of its index marks (`doc.indexMarks`). */
8857
9136
  function withIndexMarks(doc, content) {
8858
9137
  const { content: body, contentOffset } = extractFrontmatter(content.markdown);