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
@@ -15,6 +15,9 @@ import { frameElementId } from '../composed/ids';
15
15
  import { frameLinePx, frameRuleBlocks } from '../composed/rules';
16
16
  import { offsetCalloutToAbsolute } from './calloutLayout';
17
17
  import { isMarkerBlock } from './buildHelpers';
18
+ import { createMeasureMemo } from './measureMemo';
19
+ import { computeOrderedListRunMetrics } from './lists';
20
+ import { planParagraphContainers } from './paragraphContainers';
18
21
  import { CUT_START, fragmentRange, frameCutRules, makeCalloutLayouter, splitCalloutFragment } from './frameLayout';
19
22
  const MM_PER_IN = 25.4;
20
23
  const NO_FIT = { patches: new Map(), text: new Map(), pictures: new Map(), yieldAll: new Set(), goOn: new Set() };
@@ -66,7 +69,10 @@ export function layoutComposedPage(given) {
66
69
  // A page added by continuation (#859) lays its template with the
67
70
  // continuation's rules (and, for the template laid again, its other
68
71
  // frames collapsed).
69
- const input = given.continuing ? { ...given, template: continuationTemplate(given.template, given.continuing) } : given;
72
+ // The ladder lays the page again at each step: the measurements of one
73
+ // step answer the next (#953).
74
+ const memoed = { ...given, pass: { ...given.pass, measureMemo: createMeasureMemo() } };
75
+ const input = given.continuing ? { ...memoed, template: continuationTemplate(given.template, given.continuing) } : memoed;
70
76
  const { template, fence, dpi } = input;
71
77
  const fi = fence.fenceIndex;
72
78
  const mmPx = dpi / MM_PER_IN;
@@ -427,14 +433,133 @@ export function layoutComposedPage(given) {
427
433
  }
428
434
  return { composed, blocks, warnings, laid, ...(fitted.size > 0 ? { fitted } : {}), ...(continuations.length > 0 ? { continuations } : {}) };
429
435
  }
436
+ /** The pass of a trial layout (#953): `base` with `extra` blocks appended to
437
+ * its content, each a copy of the block at `originOf[k]`, and every lookup
438
+ * the box typesetter keys by content index answered for the appended
439
+ * blocks as for the blocks they copy. The paragraph groups
440
+ * (`paragraphContainers.byBlock`), the heading and list numbers, the drop
441
+ * cap and indent rules (which read the neighbours of a block in
442
+ * `contentBlocks`), the comic strips and heading designs follow the
443
+ * copies, so a trial sets a merged story as the build sets the story
444
+ * itself. The trial also keeps its own notes (`dropCapNotes`,
445
+ * `joiningLetterSpacing`), which the build reports for the page it lays. */
446
+ function trialPass(base, extra, originOf, measureOrigin) {
447
+ const n = base.contentBlocks.length;
448
+ const at = (idx) => (idx < n ? idx : originOf[idx - n] ?? idx);
449
+ const extend = (arr) => (arr && extra.length > 0 ? [...arr, ...originOf.map((o) => arr[o])] : arr);
450
+ const contentBlocks = extra.length > 0 ? [...base.contentBlocks, ...extra] : base.contentBlocks;
451
+ const contexts = new Map();
452
+ const trialCtx = (ctx) => {
453
+ const known = contexts.get(ctx);
454
+ if (known)
455
+ return known;
456
+ const perBlock = new Map(ctx.orderedMetrics.perBlock);
457
+ originOf.forEach((o, k) => {
458
+ const m = ctx.orderedMetrics.perBlock.get(o);
459
+ if (m)
460
+ perBlock.set(n + k, m);
461
+ });
462
+ const out = {
463
+ ...ctx,
464
+ contentBlocks,
465
+ headingPrefixes: extend(ctx.headingPrefixes),
466
+ ...(ctx.headingNumbers ? { headingNumbers: extend(ctx.headingNumbers) } : {}),
467
+ orderedMetrics: { ...ctx.orderedMetrics, perBlock },
468
+ dropCapNotes: new Map(),
469
+ joiningLetterSpacing: new Set(),
470
+ };
471
+ contexts.set(ctx, out);
472
+ return out;
473
+ };
474
+ const byBlock = base.paragraphContainers.byBlock;
475
+ return {
476
+ ...base,
477
+ contentBlocks,
478
+ paragraphContainers: extra.length > 0 ? { byId: base.paragraphContainers.byId, byBlock: [...byBlock, ...originOf.map((o) => byBlock[o])] } : base.paragraphContainers,
479
+ // A merged story's box is set in the context of the story whose box
480
+ // it replaces.
481
+ measureCtxFor: (startIdx, style) => trialCtx(base.measureCtxFor(measureOrigin.get(startIdx) ?? startIdx, style)),
482
+ stripBlock: (raw, idx, width) => base.stripBlock(raw, at(idx), width),
483
+ ...(base.headingDesign ? { headingDesign: (raw, blockIdx, heading, width) => base.headingDesign(raw, at(blockIdx), heading, width) } : {}),
484
+ };
485
+ }
486
+ /**
487
+ * The pass the stories carried in from other chapters (#943) are laid with:
488
+ * `base` with their blocks appended to its content, and every lookup the
489
+ * box typesetter keys by content index answered for them from what the
490
+ * sender gave (heading numbers) or worked out again from the carried
491
+ * blocks alone (paragraph groups, ordered-list markers). A carried story's
492
+ * box is set in the context of the text, with the list indents its sender
493
+ * set its lists with; the typography of a styled section or a part of this
494
+ * chapter does not reach it.
495
+ */
496
+ export function carriedPass(base, carried) {
497
+ if (carried.blocks.length === 0)
498
+ return base;
499
+ const n = base.contentBlocks.length;
500
+ const contentBlocks = [...base.contentBlocks, ...carried.blocks];
501
+ const local = planParagraphContainers(carried.blocks, base.resolved);
502
+ const byBlock = [...base.paragraphContainers.byBlock];
503
+ byBlock.length = n;
504
+ byBlock.push(...local.byBlock);
505
+ const paragraphContainers = { byId: new Map([...base.paragraphContainers.byId, ...local.byId]), byBlock };
506
+ const numberAt = new Map();
507
+ for (const story of carried.stories.values())
508
+ for (const [at, num] of story.numbers)
509
+ numberAt.set(at, num);
510
+ const extend = (arr, pick) => {
511
+ if (!arr)
512
+ return arr;
513
+ const out = [...arr];
514
+ out.length = n;
515
+ out.length = contentBlocks.length;
516
+ for (const [at, num] of numberAt)
517
+ out[at] = pick(num);
518
+ return out;
519
+ };
520
+ const contexts = new Map();
521
+ const carriedCtx = (ctx, startIdx) => {
522
+ const story = carried.stories.get(startIdx);
523
+ const of = contexts.get(ctx) ?? new Map();
524
+ contexts.set(ctx, of);
525
+ const known = of.get(startIdx);
526
+ if (known)
527
+ return known;
528
+ const metrics = computeOrderedListRunMetrics(carried.blocks, base.resolved, ctx.bodyStyle.fontSizePx);
529
+ const perBlock = new Map(ctx.orderedMetrics.perBlock);
530
+ for (const [i, m] of metrics.perBlock)
531
+ perBlock.set(n + i, m);
532
+ const out = {
533
+ ...ctx,
534
+ contentBlocks,
535
+ headingPrefixes: extend(ctx.headingPrefixes, (num) => num.prefix),
536
+ ...(ctx.headingNumbers ? { headingNumbers: extend(ctx.headingNumbers, (num) => num.value) } : {}),
537
+ orderedMetrics: { perBlock, maxWidthByDepth: ctx.orderedMetrics.maxWidthByDepth },
538
+ ...(story.jump.orderedLevelIndentsPx ? { orderedLevelIndentsPx: story.jump.orderedLevelIndentsPx } : {}),
539
+ dropCapNotes: new Map(),
540
+ joiningLetterSpacing: new Set(),
541
+ };
542
+ of.set(startIdx, out);
543
+ return out;
544
+ };
545
+ return {
546
+ ...base,
547
+ contentBlocks,
548
+ paragraphContainers,
549
+ measureCtxFor: (startIdx, style) => {
550
+ const ctx = base.measureCtxFor(startIdx, style);
551
+ return carried.stories.has(startIdx) ? carriedCtx(ctx, startIdx) : ctx;
552
+ },
553
+ };
554
+ }
430
555
  /** The page's stories as `wrapComposedPages` would group them for template
431
556
  * `alt` (#937): a story whose head names a frame of `alt` stays the story
432
557
  * of that frame's chain, one a `takes` frame took goes to `alt`'s frame
433
558
  * that takes the same level, and the rest go to `alt`'s flow frame, after
434
559
  * its own content, in Markdown order. Merged stories are boxes appended
435
- * past the pass's content blocks (a pass of their own), so the page can
436
- * be laid with them. `undefined` when some content would leave the page
437
- * (no frame of `alt` takes it). */
560
+ * past the pass's content blocks (`trialPass`), so the page can be laid
561
+ * with them. `undefined` when some content would leave the page (no frame
562
+ * of `alt` takes it). */
438
563
  function regroupStories(input, alt) {
439
564
  const names = new Set(alt.frames.map((f) => f.name));
440
565
  const flow = alt.frames.find((f) => f.flow);
@@ -454,10 +579,13 @@ function regroupStories(input, alt) {
454
579
  return undefined;
455
580
  groups.set(to, [...(groups.get(to) ?? []), [head, story]]);
456
581
  }
582
+ // Every story stays its frame's own: the page as it is.
457
583
  if ([...groups].every(([to, list]) => list.length === 1 && list[0][0] === to))
458
- return { pass: input.pass, stories: new Map(input.stories) };
584
+ return { pass: trialPass(input.pass, [], [], new Map()), stories: new Map(input.stories) };
459
585
  const base = input.pass.contentBlocks;
460
586
  const extra = [];
587
+ /** The block of the page each appended block copies. */
588
+ const originOf = [];
461
589
  /** Where each appended box's blocks come from: the first story's box. */
462
590
  const origin = new Map();
463
591
  let nextId = base.reduce((m, b) => Math.max(m, b.containerId ?? 0), 0) + 1;
@@ -473,19 +601,19 @@ function regroupStories(input, alt) {
473
601
  const startIdx = base.length + extra.length;
474
602
  const attrs = { ...own.plan.attrs, [FRAME_ATTR]: to };
475
603
  extra.push({ ...open, containerAttrs: attrs, containerId: id });
476
- for (const [, s] of list)
604
+ originOf.push(own.startIdx);
605
+ for (const [, s] of list) {
477
606
  extra.push(...base.slice(s.startIdx + 1, s.plan.endIdx));
478
- const close = base[list[list.length - 1][1].plan.endIdx];
479
- extra.push({ ...close, containerId: id });
607
+ for (let i = s.startIdx + 1; i < s.plan.endIdx; i++)
608
+ originOf.push(i);
609
+ }
610
+ const last = list[list.length - 1][1];
611
+ extra.push({ ...base[last.plan.endIdx], containerId: id });
612
+ originOf.push(last.plan.endIdx);
480
613
  origin.set(startIdx, own.startIdx);
481
614
  stories.set(to, { startIdx, plan: { endIdx: base.length + extra.length - 1, attrs, containerId: id } });
482
615
  }
483
- const pass = {
484
- ...input.pass,
485
- contentBlocks: [...base, ...extra],
486
- measureCtxFor: (startIdx, style) => input.pass.measureCtxFor(origin.get(startIdx) ?? startIdx, style),
487
- };
488
- return { pass, stories };
616
+ return { pass: trialPass(input.pass, extra, originOf, origin), stories };
489
617
  }
490
618
  /** The page fits when set with template `alt` (#937): its stories regrouped
491
619
  * for it (`regroupStories`), its own reflow ladder run; no frame
@@ -682,9 +810,30 @@ function composePage(input, template, fit, collapsed) {
682
810
  * mode, else undefined. Its content is laid out in that mode, its lines
683
811
  * along the box's height and following each other across its width. */
684
812
  const pageMode = pageIsVertical(input.page) ? 'vertical-rl' : 'horizontal-tb';
685
- // Not on a right-to-left page, whose mirrored flow would set a vertical
686
- // frame's lines from left to right: there it keeps the page's mode.
687
- const ownMode = (frame) => frame.writingMode !== undefined && frame.writingMode !== pageMode && !pageIsMirrored(input.page) ? frame.writingMode : undefined;
813
+ const ownMode = (frame) => frame.writingMode !== undefined && frame.writingMode !== pageMode ? frame.writingMode : undefined;
814
+ /** A vertical frame of a right-to-left page (#960): the page's flow is
815
+ * painted mirrored, which would set its lines from left to right, so the
816
+ * frame is laid out as on a left-to-right page and painted unmirrored
817
+ * (`VDTFrame.unmirrorX`). The mirror's origin, else undefined. */
818
+ const unmirroredAt = (frame) => {
819
+ const flow = input.page.flow;
820
+ return ownMode(frame) && flow?.writingMode === 'horizontal-tb' ? flow.mirror.originX : undefined;
821
+ };
822
+ /** Whether a frame's lines are set as on a mirrored page: those of the
823
+ * page's own mode, not an unmirrored frame's. */
824
+ const mirroredIn = (frame) => input.mirrored && unmirroredAt(frame) === undefined;
825
+ /** The widest a picture's upright frame may be in a vertical frame of a
826
+ * horizontal page (#960): the frame's extent across its lines, less the
827
+ * box's padding. The page turns such a frame a quarter turn, so its
828
+ * pictures are laid out counter-rotated to stand upright; none of that
829
+ * in a frame of the page's own mode. */
830
+ const uprightIn = (frame, across) => {
831
+ if (ownMode(frame) !== 'vertical-rl')
832
+ return {};
833
+ const pad = styleOf(frame).padding;
834
+ const room = across - lengthPx(pad.top, dpi, input.emPx) - lengthPx(pad.bottom, dpi, input.emPx);
835
+ return { uprightMaxLength: Math.max(1, room) };
836
+ };
688
837
  /** `fn` measured in the frame's own writing mode. */
689
838
  const inMode = (frame, fn) => {
690
839
  const mode = ownMode(frame);
@@ -847,8 +996,8 @@ function composePage(input, template, fit, collapsed) {
847
996
  const known = memo.get(key);
848
997
  if (known !== undefined)
849
998
  return known;
850
- const opts = { ...(pic !== undefined ? { pictureScale: pic } : {}), ...(p.fromMark !== undefined ? { continuedMark: p.fromMark } : {}) };
851
- const across = (measure) => inMode(p.frame, () => p.st.L.layoutRange(p.st.cut, p.st.L.end, measure / s, p.blockId, p.continuation, input.mirrored, Object.keys(opts).length > 0 ? opts : undefined).totalHeight * s);
999
+ const opts = { ...(pic !== undefined ? { pictureScale: pic } : {}), ...(p.fromMark !== undefined ? { continuedMark: p.fromMark } : {}), ...uprightIn(p.frame, width / s) };
1000
+ const across = (measure) => inMode(p.frame, () => p.st.L.layoutRange(p.st.cut, p.st.L.end, measure / s, p.blockId, p.continuation, mirroredIn(p.frame), Object.keys(opts).length > 0 ? opts : undefined).totalHeight * s);
852
1001
  const most = Math.max(input.areas.area.height, input.emPx);
853
1002
  let h = most;
854
1003
  if (across(most) <= width + 0.5) {
@@ -1066,9 +1215,9 @@ function composePage(input, template, fit, collapsed) {
1066
1215
  // Text running round the frames that push it runs along the page's
1067
1216
  // lines: a frame set across them keeps its whole box.
1068
1217
  const exclusions = turned ? undefined : unscale(exclusionsFor(frame, rect, false), s);
1069
- const extra = { ...(exclusions ? { exclusions } : {}), ...(pic !== undefined ? { pictureScale: pic } : {}), ...(p.fromMark !== undefined ? { continuedMark: p.fromMark } : {}) };
1218
+ const extra = { ...(exclusions ? { exclusions } : {}), ...(pic !== undefined ? { pictureScale: pic } : {}), ...(p.fromMark !== undefined ? { continuedMark: p.fromMark } : {}), ...uprightIn(frame, laidH) };
1070
1219
  const opts = { boxHeight: laidH, verticalAlign: frame.verticalAlign, ...extra };
1071
- const whole = inMode(frame, () => st.L.layoutRange(from, st.L.end, laidW, blockId, continuation, input.mirrored, opts));
1220
+ const whole = inMode(frame, () => st.L.layoutRange(from, st.L.end, laidW, blockId, continuation, mirroredIn(frame), opts));
1072
1221
  const nextName = nextFrameOf(frame);
1073
1222
  const nextFrame = nextName !== undefined ? byName.get(nextName) : undefined;
1074
1223
  const chained = nextFrame !== undefined && !vdtByName.has(nextFrame.name);
@@ -1080,11 +1229,11 @@ function composePage(input, template, fit, collapsed) {
1080
1229
  const minLines = Math.max(1, style.splitMinLines);
1081
1230
  // A heading stays with what follows it, a list keeps two items
1082
1231
  // on each side of the cut (#913).
1083
- const frag = inMode(frame, () => splitCalloutFragment(pass, st.L, from, laidW, laidH, blockId, continuation, minLines, input.mirrored, extra.exclusions || extra.pictureScale !== undefined || extra.continuedMark !== undefined ? extra : undefined, cutRules));
1232
+ const frag = inMode(frame, () => splitCalloutFragment(pass, st.L, from, laidW, laidH, blockId, continuation, minLines, mirroredIn(frame), extra.exclusions || extra.pictureScale !== undefined || extra.continuedMark !== undefined || extra.uprightMaxLength !== undefined ? extra : undefined, cutRules));
1084
1233
  const to = frag?.to ?? from;
1085
1234
  // A frame none of whose content fits draws nothing: the whole box
1086
1235
  // goes on (#912), not a tab and a mark over an empty piece.
1087
- result = frag ? inMode(frame, () => st.L.layoutRange(from, to, laidW, blockId, continuation, input.mirrored, opts)) : undefined;
1236
+ result = frag ? inMode(frame, () => st.L.layoutRange(from, to, laidW, blockId, continuation, mirroredIn(frame), opts)) : undefined;
1088
1237
  st.cut = to;
1089
1238
  }
1090
1239
  else {
@@ -1096,7 +1245,7 @@ function composePage(input, template, fit, collapsed) {
1096
1245
  }
1097
1246
  else if (st && frame.style) {
1098
1247
  // A styled frame with nothing (left) to hold still draws its box.
1099
- result = inMode(frame, () => st.L.layoutRange(st.L.end, st.L.end, laidW, blockId, true, input.mirrored, { boxHeight: laidH }));
1248
+ result = inMode(frame, () => st.L.layoutRange(st.L.end, st.L.end, laidW, blockId, true, mirroredIn(frame), { boxHeight: laidH }));
1100
1249
  }
1101
1250
  // A frame cut below its least height overflows by the cut, holding
1102
1251
  // content or not (#911).
@@ -1126,6 +1275,7 @@ function composePage(input, template, fit, collapsed) {
1126
1275
  rect: { ...rect },
1127
1276
  rotate: frame.rotate + (turned ? frameModeTurn({ writingMode: turned }) : 0),
1128
1277
  ...(turned ? { writingMode: turned } : {}),
1278
+ ...(unmirroredAt(frame) !== undefined ? { unmirrorX: unmirroredAt(frame) } : {}),
1129
1279
  layer: frame.layer,
1130
1280
  ...(frame.style ? { styleId: frame.style } : {}),
1131
1281
  scale: s,
@@ -1149,7 +1299,11 @@ function composePage(input, template, fit, collapsed) {
1149
1299
  }
1150
1300
  else if (holds && cont?.receivers.has(frame.name)) {
1151
1301
  // Content from the page before (#859).
1152
- vdt.continuedFrom = cont.receivers.get(frame.name).fromFrameId;
1302
+ const came = cont.receivers.get(frame.name);
1303
+ vdt.continuedFrom = came.fromFrameId;
1304
+ // From a page of an earlier chapter (#943).
1305
+ if (came.foreign)
1306
+ vdt.continuedFromChapter = came.foreign.from;
1153
1307
  }
1154
1308
  if (result) {
1155
1309
  // The marks whose `{page}` the build fills in (#917).