postext 1.20.2 → 1.21.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 (185) hide show
  1. package/dist/__tests__/color.test.d.ts +2 -0
  2. package/dist/__tests__/color.test.d.ts.map +1 -0
  3. package/dist/__tests__/color.test.js +110 -0
  4. package/dist/__tests__/color.test.js.map +1 -0
  5. package/dist/__tests__/comics/build.test.js +4 -3
  6. package/dist/__tests__/comics/build.test.js.map +1 -1
  7. package/dist/__tests__/comics/canvas.test.js +21 -2
  8. package/dist/__tests__/comics/canvas.test.js.map +1 -1
  9. package/dist/__tests__/comics/config.test.js +62 -0
  10. package/dist/__tests__/comics/config.test.js.map +1 -1
  11. package/dist/__tests__/comics/geometry.test.js +9 -2
  12. package/dist/__tests__/comics/geometry.test.js.map +1 -1
  13. package/dist/__tests__/comics/html.test.js +31 -0
  14. package/dist/__tests__/comics/html.test.js.map +1 -1
  15. package/dist/__tests__/comics/readingDirection.test.d.ts +2 -0
  16. package/dist/__tests__/comics/readingDirection.test.d.ts.map +1 -0
  17. package/dist/__tests__/comics/readingDirection.test.js +95 -0
  18. package/dist/__tests__/comics/readingDirection.test.js.map +1 -0
  19. package/dist/__tests__/comics/skew.test.d.ts +2 -0
  20. package/dist/__tests__/comics/skew.test.d.ts.map +1 -0
  21. package/dist/__tests__/comics/skew.test.js +57 -0
  22. package/dist/__tests__/comics/skew.test.js.map +1 -0
  23. package/dist/__tests__/comics/strip.test.js +186 -6
  24. package/dist/__tests__/comics/strip.test.js.map +1 -1
  25. package/dist/__tests__/exports.test.js +43 -0
  26. package/dist/__tests__/exports.test.js.map +1 -1
  27. package/dist/__tests__/pipeline/resourceNumbering.test.js +33 -1
  28. package/dist/__tests__/pipeline/resourceNumbering.test.js.map +1 -1
  29. package/dist/__tests__/preflight.test.d.ts +2 -0
  30. package/dist/__tests__/preflight.test.d.ts.map +1 -0
  31. package/dist/__tests__/preflight.test.js +115 -0
  32. package/dist/__tests__/preflight.test.js.map +1 -0
  33. package/dist/__tests__/printConfig.test.d.ts +2 -0
  34. package/dist/__tests__/printConfig.test.d.ts.map +1 -0
  35. package/dist/__tests__/printConfig.test.js +44 -0
  36. package/dist/__tests__/printConfig.test.js.map +1 -0
  37. package/dist/canvas-backend/blockRender.d.ts.map +1 -1
  38. package/dist/canvas-backend/blockRender.js +36 -1
  39. package/dist/canvas-backend/blockRender.js.map +1 -1
  40. package/dist/canvas-backend/comic.d.ts +2 -1
  41. package/dist/canvas-backend/comic.d.ts.map +1 -1
  42. package/dist/canvas-backend/comic.js +6 -9
  43. package/dist/canvas-backend/comic.js.map +1 -1
  44. package/dist/color/authored.d.ts +9 -0
  45. package/dist/color/authored.d.ts.map +1 -0
  46. package/dist/color/authored.js +31 -0
  47. package/dist/color/authored.js.map +1 -0
  48. package/dist/color/catalogue.d.ts +29 -0
  49. package/dist/color/catalogue.d.ts.map +1 -0
  50. package/dist/color/catalogue.js +52 -0
  51. package/dist/color/catalogue.js.map +1 -0
  52. package/dist/color/icc.d.ts +68 -0
  53. package/dist/color/icc.d.ts.map +1 -0
  54. package/dist/color/icc.js +533 -0
  55. package/dist/color/icc.js.map +1 -0
  56. package/dist/color/index.d.ts +6 -0
  57. package/dist/color/index.d.ts.map +1 -0
  58. package/dist/color/index.js +6 -0
  59. package/dist/color/index.js.map +1 -0
  60. package/dist/color/srgbProfile.d.ts +8 -0
  61. package/dist/color/srgbProfile.d.ts.map +1 -0
  62. package/dist/color/srgbProfile.js +86 -0
  63. package/dist/color/srgbProfile.js.map +1 -0
  64. package/dist/color/transform.d.ts +73 -0
  65. package/dist/color/transform.d.ts.map +1 -0
  66. package/dist/color/transform.js +329 -0
  67. package/dist/color/transform.js.map +1 -0
  68. package/dist/comics/index.d.ts +3 -3
  69. package/dist/comics/index.d.ts.map +1 -1
  70. package/dist/comics/index.js +2 -2
  71. package/dist/comics/index.js.map +1 -1
  72. package/dist/comics/layoutPage.d.ts +8 -4
  73. package/dist/comics/layoutPage.d.ts.map +1 -1
  74. package/dist/comics/layoutPage.js +17 -9
  75. package/dist/comics/layoutPage.js.map +1 -1
  76. package/dist/comics/lettering/__tests__/letterPanel.test.js +19 -9
  77. package/dist/comics/lettering/__tests__/letterPanel.test.js.map +1 -1
  78. package/dist/comics/lettering/letter.js +13 -7
  79. package/dist/comics/lettering/letter.js.map +1 -1
  80. package/dist/comics/lettering/placement.d.ts +2 -0
  81. package/dist/comics/lettering/placement.d.ts.map +1 -1
  82. package/dist/comics/lettering/placement.js.map +1 -1
  83. package/dist/comics/lettering/types.d.ts +5 -0
  84. package/dist/comics/lettering/types.d.ts.map +1 -1
  85. package/dist/comics/paint.d.ts +6 -0
  86. package/dist/comics/paint.d.ts.map +1 -1
  87. package/dist/comics/paint.js +23 -0
  88. package/dist/comics/paint.js.map +1 -1
  89. package/dist/comics/script.d.ts.map +1 -1
  90. package/dist/comics/script.js +4 -1
  91. package/dist/comics/script.js.map +1 -1
  92. package/dist/comics/strip.d.ts +50 -3
  93. package/dist/comics/strip.d.ts.map +1 -1
  94. package/dist/comics/strip.js +55 -5
  95. package/dist/comics/strip.js.map +1 -1
  96. package/dist/comics/types.d.ts +3 -0
  97. package/dist/comics/types.d.ts.map +1 -1
  98. package/dist/configWarnings.d.ts +1 -1
  99. package/dist/configWarnings.d.ts.map +1 -1
  100. package/dist/configWarnings.js +93 -4
  101. package/dist/configWarnings.js.map +1 -1
  102. package/dist/defaults/comics.d.ts +48 -1
  103. package/dist/defaults/comics.d.ts.map +1 -1
  104. package/dist/defaults/comics.js +77 -8
  105. package/dist/defaults/comics.js.map +1 -1
  106. package/dist/defaults/index.d.ts +2 -1
  107. package/dist/defaults/index.d.ts.map +1 -1
  108. package/dist/defaults/index.js +10 -1
  109. package/dist/defaults/index.js.map +1 -1
  110. package/dist/defaults/page.d.ts +12 -6
  111. package/dist/defaults/page.d.ts.map +1 -1
  112. package/dist/defaults/page.js +15 -9
  113. package/dist/defaults/page.js.map +1 -1
  114. package/dist/defaults/print.d.ts +13 -0
  115. package/dist/defaults/print.d.ts.map +1 -0
  116. package/dist/defaults/print.js +147 -0
  117. package/dist/defaults/print.js.map +1 -0
  118. package/dist/html-backend.d.ts.map +1 -1
  119. package/dist/html-backend.js +46 -2
  120. package/dist/html-backend.js.map +1 -1
  121. package/dist/htmlComic.d.ts.map +1 -1
  122. package/dist/htmlComic.js +8 -3
  123. package/dist/htmlComic.js.map +1 -1
  124. package/dist/index.d.ts +11 -6
  125. package/dist/index.d.ts.map +1 -1
  126. package/dist/index.js +6 -3
  127. package/dist/index.js.map +1 -1
  128. package/dist/locale.d.ts +9 -0
  129. package/dist/locale.d.ts.map +1 -1
  130. package/dist/locale.js +11 -0
  131. package/dist/locale.js.map +1 -1
  132. package/dist/pipeline/build.d.ts.map +1 -1
  133. package/dist/pipeline/build.js +211 -36
  134. package/dist/pipeline/build.js.map +1 -1
  135. package/dist/pipeline/calloutLayout.d.ts +5 -0
  136. package/dist/pipeline/calloutLayout.d.ts.map +1 -1
  137. package/dist/pipeline/calloutLayout.js +36 -3
  138. package/dist/pipeline/calloutLayout.js.map +1 -1
  139. package/dist/pipeline/config.d.ts.map +1 -1
  140. package/dist/pipeline/config.js +13 -2
  141. package/dist/pipeline/config.js.map +1 -1
  142. package/dist/pipeline/contentWarnings.d.ts.map +1 -1
  143. package/dist/pipeline/contentWarnings.js +8 -0
  144. package/dist/pipeline/contentWarnings.js.map +1 -1
  145. package/dist/pipeline/crossRefs.d.ts.map +1 -1
  146. package/dist/pipeline/crossRefs.js +2 -1
  147. package/dist/pipeline/crossRefs.js.map +1 -1
  148. package/dist/pipeline/resourceLayout.d.ts +51 -2
  149. package/dist/pipeline/resourceLayout.d.ts.map +1 -1
  150. package/dist/pipeline/resourceLayout.js +96 -51
  151. package/dist/pipeline/resourceLayout.js.map +1 -1
  152. package/dist/pipeline/resourceNumbering.d.ts +5 -0
  153. package/dist/pipeline/resourceNumbering.d.ts.map +1 -1
  154. package/dist/pipeline/resourceNumbering.js +34 -5
  155. package/dist/pipeline/resourceNumbering.js.map +1 -1
  156. package/dist/pipeline/spanEmbeds.d.ts +6 -1
  157. package/dist/pipeline/spanEmbeds.d.ts.map +1 -1
  158. package/dist/pipeline/spanEmbeds.js +18 -3
  159. package/dist/pipeline/spanEmbeds.js.map +1 -1
  160. package/dist/preflight.d.ts +89 -0
  161. package/dist/preflight.d.ts.map +1 -0
  162. package/dist/preflight.js +314 -0
  163. package/dist/preflight.js.map +1 -0
  164. package/dist/types.d.ts +167 -9
  165. package/dist/types.d.ts.map +1 -1
  166. package/dist/vdt.d.ts +47 -5
  167. package/dist/vdt.d.ts.map +1 -1
  168. package/dist/vdt.js.map +1 -1
  169. package/icc/README.md +43 -0
  170. package/icc/fogra27.icc +0 -0
  171. package/icc/fogra28.icc +0 -0
  172. package/icc/fogra29.icc +0 -0
  173. package/icc/fogra30.icc +0 -0
  174. package/icc/fogra39.icc +0 -0
  175. package/icc/fogra40.icc +0 -0
  176. package/icc/fogra45.icc +0 -0
  177. package/icc/fogra47.icc +0 -0
  178. package/icc/fogra51.icc +0 -0
  179. package/icc/fogra52.icc +0 -0
  180. package/icc/gracol2006.icc +0 -0
  181. package/icc/ifra26.icc +0 -0
  182. package/icc/snap2007.icc +0 -0
  183. package/icc/swop3.icc +0 -0
  184. package/icc/swop5.icc +0 -0
  185. package/package.json +4 -2
@@ -1,6 +1,6 @@
1
1
  import { plainTitleBreaks } from '../parse/inlineFormatting';
2
2
  import { dimensionToPx } from '../units';
3
- import { createVDTDocument, createVDTBlock, createBoundingBox, createVDTColumn, pageIsMirrored, pageIsVertical, } from '../vdt';
3
+ import { createVDTDocument, createVDTBlock, createBoundingBox, createVDTColumn, pageIsMirrored, pageIsVertical, flowRectToPage, } from '../vdt';
4
4
  import { anchorBox } from '../design/layout';
5
5
  import { parseMarkdownMemo, spaceDirectiveLines } from '../parse';
6
6
  import { buildPageLabels, computeHeadingNumbering, parseNumberFormat, } from '../numbering';
@@ -15,14 +15,14 @@ import { orientLinesToFrames } from './mirrorFrame';
15
15
  import { verseMarginTopPx } from './buildBlockKind';
16
16
  import { resolveAllConfig, computeBaselineGrid, resolvedDirection, resolvedLocale } from './config';
17
17
  import { asciiDigits } from '../arabicNumerals';
18
- import { getMeasureDirection, setMeasureDirection } from '../measure/bidiLines';
18
+ import { getMeasureDirection, setMeasureDirection, shiftLineX } from '../measure/bidiLines';
19
19
  import { createHeadingLevelResolver, deriveSectionGeometryConfig, sectionWritingMode, deriveSectionMeasureContext, headingIsHidden, headingIsNumbered, headingMarksFor, headingStyleOf, numberTitlesFor, planHeadingSections, } from './headingStyles';
20
20
  import { locateAnchors } from './anchors';
21
21
  import { applyCitations, citationLocale, processCitations, withBibliographyStyle } from './citations';
22
22
  import { bookCitationContexts, needsCitationContext } from '../citations/context';
23
23
  import { anchorTargetsOf, hasAnchorRefs, printsAnchorPages } from './crossRefs';
24
24
  import { applyStatementNumbering, numberStatements, outlineLookup } from './statementNumbering';
25
- import { spanEmbedStyle, wrapPageSpanEmbeds } from './spanEmbeds';
25
+ import { SPAN_EMBED_STYLE_ID, isPageSpanStrip, spanEmbedStyle, wrapPageSpanEmbeds } from './spanEmbeds';
26
26
  import { anchorOutline, computeOutline, hasIndexDirective, hasTocDirective, headingNumberingOptions, headingTemplatesOf, outlineFromDoc, sameOutline } from './outline';
27
27
  import { expandTocDirectives } from './toc';
28
28
  import { expandIndexDirectives, locateIndexMarks } from './indexDirective';
@@ -31,7 +31,7 @@ import { computeLevelIndentsPx, computeOrderedLevelIndentsPx, computeOrderedList
31
31
  import { resetLinePositions, createPageWithColumns, currentColumn, advanceToNextColumn, advanceToNextPageBoundary, enforcePageParity, placeBlockInColumn, placeAtomicBlock, createPartPage, pageHasContent, sideColumnOf, sideColumns, sideUsedBottom, pageIsOccupied, bandColumns, currentBand, isBandLevel, bandUsedBottom, closeBandAndInsertSpan, closeBandAt, pageLayoutOf, } from './placement';
32
32
  import { chooseParagraphSplit } from './orphanWidow';
33
33
  import { layoutComicPage, comicPageLayoutWarnings } from '../comics/layoutPage';
34
- import { comicStripExtent, comicStripPlacement, layoutComicStrip } from '../comics/strip';
34
+ import { comicStripExtent, comicStripOffset, comicStripPlacement, comicStripWidth, layoutComicStrip } from '../comics/strip';
35
35
  import { isComicSpread, layoutComicSpread } from '../comics/spread';
36
36
  import { applyStyleAttrs, computePageMetrics, flowPageMirrored, pageMirrored, sheetRectToFlow, isMarkerBlock, nextNonMarkerBlock, spaceLinesAfter, prevNonMarkerBlock, rollbackTrailingBlocks, } from './buildHelpers';
37
37
  import { measureContentBlock } from './measureContentBlock';
@@ -39,10 +39,10 @@ import { containerStyle, paragraphStyleIdOf, planParagraphContainers } from './p
39
39
  import { appendChapterEndNotes, footnoteIdsOfLines, footnoteParagraphStyle, noteContentBlock, noteNumberPositionOf, numberFootnotes, footnoteFormatOf, numberFootnotesByPlacement, sameFootnoteNumbers, splitFootnoteDefinitions, withFootnoteStyle, } from './footnotes';
40
40
  import { planParts, derivePartMeasureContext } from './parts';
41
41
  import { layoutCallout, offsetBlock, offsetCalloutToAbsolute, pickCalloutStyle, planCallouts, resolveCalloutAttrs, } from './calloutLayout';
42
- import { layoutResourceBlock, planTableSlice } from './resourceLayout';
42
+ import { layoutCaptionText, layoutResourceBlock, planTableSlice } from './resourceLayout';
43
43
  import { computeFloatPlan, floatedResourceIds, floatColumnCount, } from './floatPlacement';
44
44
  import { enumerateCurrentPageSlots, measureFloatBand, floatGapAbove, measureSideStack, clearSideObstacles, columnHasFloatBand, fitsStrict, trueBottom, } from './floatSlots';
45
- import { computeHeadingContext, computeResourceNumbering, } from './resourceNumbering';
45
+ import { computeHeadingContext, computeResourceNumberingState, } from './resourceNumbering';
46
46
  import { documentLocale, effectiveResourceTypes } from '../defaults/resourceTypes';
47
47
  import { resolveCalloutStylesConfig } from '../defaults/calloutStyles';
48
48
  import { pickTableStyle } from '../defaults/tableStyle';
@@ -392,7 +392,10 @@ function placeDocumentPass(content, config, cache, options, hints) {
392
392
  const resources = content.resources ?? [];
393
393
  const resourceTypes = effectiveResourceTypes(config, resources);
394
394
  const headingContext = computeHeadingContext(contentBlocks, headingStart, isNumbered);
395
- const resourceNumbering = computeResourceNumbering(contentBlocks, resourceTypes, resources, headingContext, continuation ? { counters: continuation.resourceCounters, numbered: continuation.resourceNumbers } : undefined, resolved.numerals, documentLocale(config));
395
+ const numberingState = computeResourceNumberingState(contentBlocks, resourceTypes, resources, headingContext, continuation ? { counters: continuation.resourceCounters, numbered: continuation.resourceNumbers } : undefined, resolved.numerals, documentLocale(config));
396
+ const resourceNumbering = numberingState.map;
397
+ /** The numbers of the strips counted in a resource type (#590). */
398
+ const stripNumbers = numberingState.strips;
396
399
  // Lookups threaded into block-kind resolution + measurement.
397
400
  const resourceById = new Map();
398
401
  for (const r of resources)
@@ -643,14 +646,69 @@ function placeDocumentPass(content, config, cache, options, hints) {
643
646
  const source = contentBlocks[idx]?.comic;
644
647
  return source ? { idx, source } : undefined;
645
648
  };
646
- /** A strip's extent across the flow at a measure of `width` px, at most
647
- * `max` px (a fresh column, less what a float band keeps). */
648
- const comicStripExtentAt = (source, width, max) => comicStripExtent(source, width, resolved.page.dpi, pageIsVertical(doc.pages[cursor.pageIndex]), max);
649
- /** The block of a strip `width` × `extent` px (flow), its comic laid out
650
- * in its box on the sheet (the flow box turned on a vertical page); `y`
651
- * is 0 until it is placed. */
652
- const buildComicStripBlock = (blockIdx, source, x, width, extent, pageIndex) => {
649
+ const stripGeometryMemo = new Map();
650
+ const stripGeometry = (blockIdx, source, measure, max, vertical) => {
651
+ const key = `${blockIdx}:${measure.toFixed(2)}:${max.toFixed(2)}:${vertical ? 1 : 0}`;
652
+ const memo = stripGeometryMemo.get(key);
653
+ if (memo)
654
+ return memo;
655
+ const dpi = resolved.page.dpi;
656
+ const place = comicStripPlacement(source.attrs);
657
+ const stripWidth = comicStripWidth(place, measure, dpi);
658
+ const dx = comicStripOffset(place.align, measure, stripWidth);
659
+ const typeId = place.type !== undefined && resourceTypeById.has(place.type) ? place.type : undefined;
660
+ const numbered = typeId !== undefined ? stripNumbers[blockIdx] : undefined;
661
+ const caption = place.caption
662
+ ? layoutCaptionText({
663
+ text: place.caption,
664
+ // A type the strip is not counted in (no caption, an unknown id)
665
+ // lends nothing: the caption is plain.
666
+ resourceType: numbered ? resourceTypeById.get(typeId) : undefined,
667
+ number: numbered?.number ?? '',
668
+ resolved,
669
+ width: stripWidth,
670
+ resourceNumbering,
671
+ resourceTypes,
672
+ resources,
673
+ })
674
+ : undefined;
675
+ const captionRoom = caption && caption.bandHeight > 0 ? caption.bandHeight + caption.gapPx : 0;
676
+ const extent = comicStripExtent(source, stripWidth, dpi, vertical, Math.max(1, max - captionRoom));
677
+ const geometry = {
678
+ stripWidth,
679
+ dx,
680
+ extent,
681
+ ...(caption && captionRoom > 0 ? { caption } : {}),
682
+ captionAbove: captionRoom > 0 && caption.style.position === 'above',
683
+ captionRoom,
684
+ height: extent + captionRoom,
685
+ };
686
+ stripGeometryMemo.set(key, geometry);
687
+ return geometry;
688
+ };
689
+ /** Geometry `g` at another measure: the strip's width and offset
690
+ * follow it, its extent and caption stay. */
691
+ const stripGeometryKept = (g, source, measure) => {
692
+ const place = comicStripPlacement(source.attrs);
693
+ const stripWidth = comicStripWidth(place, measure, resolved.page.dpi);
694
+ return { ...g, stripWidth, dx: comicStripOffset(place.align, measure, stripWidth) };
695
+ };
696
+ /**
697
+ * The block of a strip a measure `width` px wide and at most `max` px
698
+ * tall (flow): its comic laid out in the strip's box on the sheet (the
699
+ * flow box turned on a vertical page, mirrored on a right-to-left one),
700
+ * placed in the block's box; its caption as the block's lines, in the
701
+ * caption style, block-relative (placing the block moves them). `x` is
702
+ * where the block stands; `y` is 0 until it is placed.
703
+ */
704
+ const buildComicStripBlock = (blockIdx, source, x, width, max, pageIndex,
705
+ /** The geometry the block was placed with, kept at another measure
706
+ * (its extent and caption; only its width and offset follow). */
707
+ keep) => {
653
708
  const raw = contentBlocks[blockIdx];
709
+ const page = doc.pages[pageIndex] ?? doc.pages[cursor.pageIndex];
710
+ const vertical = pageIsVertical(page);
711
+ const g = keep ? stripGeometryKept(keep, source, width) : stripGeometry(blockIdx, source, width, max, vertical);
654
712
  const blk = createVDTBlock(`comic-strip-${blockIdx}`, 'resource', bodyStyle.fontString, bodyStyle.color, bodyStyle.textAlign);
655
713
  blk.contentIndex = blockIdx;
656
714
  blk.sourceStart = raw.sourceStart + bodyOffset;
@@ -658,18 +716,92 @@ function placeDocumentPass(content, config, cache, options, hints) {
658
716
  blk.dirty = false;
659
717
  blk.snappedToGrid = false;
660
718
  blk.lines = [];
661
- blk.bbox = createBoundingBox(x, 0, width, extent);
662
- const vertical = pageIsVertical(doc.pages[pageIndex] ?? doc.pages[cursor.pageIndex]);
719
+ blk.bbox = createBoundingBox(x, 0, width, g.height);
720
+ // The strip's box inside the block's, then both on the sheet: the
721
+ // offset between them is where the comic stands in the block's box
722
+ // there (a mirrored or turned frame moves it, wherever the block is).
723
+ const stripY = g.captionAbove ? g.captionRoom : 0;
724
+ const blockOnSheet = flowRectToPage(page, createBoundingBox(0, 0, width, g.height));
725
+ const stripOnSheet = flowRectToPage(page, createBoundingBox(g.dx, stripY, g.stripWidth, g.extent));
663
726
  blk.comic = layoutComicStrip(source, {
664
727
  resolved,
665
- width: vertical ? extent : width,
666
- height: vertical ? width : extent,
728
+ width: vertical ? g.extent : g.stripWidth,
729
+ height: vertical ? g.stripWidth : g.extent,
730
+ x: stripOnSheet.x - blockOnSheet.x,
731
+ y: stripOnSheet.y - blockOnSheet.y,
667
732
  resources: resourceById,
668
733
  sourceOffset: bodyOffset,
669
734
  pageIndex,
670
735
  });
736
+ if (g.caption) {
737
+ const c = g.caption;
738
+ const fonts = c.fonts;
739
+ const color = c.style.color.hex;
740
+ const labelColor = c.style.labelColor.hex;
741
+ // Under the strip after the gap, or over it with the gap below.
742
+ const bandY = g.captionAbove ? 0 : g.extent + c.gapPx;
743
+ blk.fontString = fonts.fontString;
744
+ blk.boldFontString = fonts.boldFontString;
745
+ blk.italicFontString = fonts.italicFontString;
746
+ blk.boldItalicFontString = fonts.boldItalicFontString;
747
+ blk.color = color;
748
+ blk.refColor = c.linkColor;
749
+ blk.textAlign = c.style.align;
750
+ // Every segment carries its font and colour: a renderer paints them
751
+ // as the lines of a paragraph, in the caption's face and colours.
752
+ const fontOf = (bold, italic) => bold && italic ? fonts.boldItalicFontString : bold ? fonts.boldFontString : italic ? fonts.italicFontString : fonts.fontString;
753
+ blk.lines = c.lines.map((line) => ({
754
+ ...line,
755
+ bbox: createBoundingBox(line.bbox.x + g.dx + c.paddingPx, line.bbox.y + bandY + c.paddingPx, line.bbox.width, line.bbox.height),
756
+ baseline: line.baseline + bandY + c.paddingPx,
757
+ ...(line.segments
758
+ ? {
759
+ segments: line.segments.map((seg) => ({
760
+ ...seg,
761
+ ...(seg.fontString === undefined && (seg.kind === 'text' || seg.kind === 'space') ? { fontString: fontOf(seg.bold, seg.italic) } : {}),
762
+ ...(seg.color === undefined && seg.refResourceId === undefined ? { color: seg.captionLabel ? labelColor : color } : {}),
763
+ })),
764
+ }
765
+ : {}),
766
+ }));
767
+ const place = comicStripPlacement(source.attrs);
768
+ const numbered = place.type !== undefined ? stripNumbers[blockIdx] : undefined;
769
+ blk.stripCaption = {
770
+ firstLine: 0,
771
+ lineCount: blk.lines.length,
772
+ position: g.captionAbove ? 'above' : 'below',
773
+ ...(numbered ? { typeId: numbered.typeId, number: numbered.number, ...(place.id ? { id: place.id } : {}) } : {}),
774
+ ...(c.style.backgroundEnabled
775
+ ? { bar: { rect: createBoundingBox(g.dx, bandY, g.stripWidth, c.bandHeight), background: c.style.background.hex } }
776
+ : {}),
777
+ };
778
+ }
671
779
  return blk;
672
780
  };
781
+ /** Give a strip set in the flow (`here`, or in a box) the placeholder
782
+ * line that carries its height, as an inline figure's does: before its
783
+ * caption, or after a caption set over it (block-relative). */
784
+ const withStripPlaceholder = (blk, g) => {
785
+ const y = g.captionAbove ? g.captionRoom : 0;
786
+ const placeholder = { text: '', bbox: { x: g.dx, y, width: g.stripWidth, height: g.extent }, baseline: y, hyphenated: false, segments: [], isLastLine: true };
787
+ if (g.captionAbove) {
788
+ blk.lines = [...blk.lines, placeholder];
789
+ }
790
+ else {
791
+ blk.lines = [placeholder, ...blk.lines];
792
+ if (blk.stripCaption)
793
+ blk.stripCaption.firstLine = 1;
794
+ }
795
+ };
796
+ /** Shift a strip block's lines (its caption, its placeholder) by
797
+ * `(dx, dy)`: a float set at its band. */
798
+ const shiftStripLines = (blk, dx, dy) => {
799
+ for (const line of blk.lines) {
800
+ shiftLineX(line, dx);
801
+ line.bbox.y += dy;
802
+ line.baseline += dy;
803
+ }
804
+ };
673
805
  /** Boxes laid out for a band, waiting for the band's `y` (built in
674
806
  * `buildFloatBlock`, committed in `commitFloatBlock`). */
675
807
  const calloutFloatResults = new Map();
@@ -837,9 +969,16 @@ function placeDocumentPass(content, config, cache, options, hints) {
837
969
  return memo;
838
970
  const strip = comicStripOf(resourceId);
839
971
  if (strip) {
840
- // A strip: its height at the band's width, never taller than a page
841
- // keeps for a float band.
842
- const ms = { height: comicStripExtentAt(strip.source, width, contentArea.height - 2 * floatGapPx) };
972
+ // A strip: its height at the band's width (its caption's included),
973
+ // never taller than a page keeps for a float band. A caption under
974
+ // it is the last text line a bottom band aligns to the grid.
975
+ const max = contentArea.height - 2 * floatGapPx;
976
+ const g = stripGeometry(strip.idx, strip.source, width, max, pageIsVertical(doc.pages[cursor.pageIndex]));
977
+ const last = g.caption && !g.captionAbove ? g.caption.lines[g.caption.lines.length - 1] : undefined;
978
+ const ms = {
979
+ height: g.height,
980
+ ...(last ? { lastCaptionBaseline: g.extent + g.caption.gapPx + g.caption.paddingPx + last.baseline } : {}),
981
+ };
843
982
  floatMeasureMemo.set(key, ms);
844
983
  return ms;
845
984
  }
@@ -901,8 +1040,8 @@ function placeDocumentPass(content, config, cache, options, hints) {
901
1040
  mirrored = false) => {
902
1041
  const strip = comicStripOf(resourceId);
903
1042
  if (strip) {
904
- const height = comicStripExtentAt(strip.source, width, contentArea.height - 2 * floatGapPx);
905
- return { block: buildComicStripBlock(strip.idx, strip.source, x, width, height, cursor.pageIndex), height };
1043
+ const block = buildComicStripBlock(strip.idx, strip.source, x, width, contentArea.height - 2 * floatGapPx, cursor.pageIndex);
1044
+ return { block, height: block.bbox.height };
906
1045
  }
907
1046
  const cf = calloutFloatOf(resourceId);
908
1047
  if (cf) {
@@ -1191,9 +1330,12 @@ function placeDocumentPass(content, config, cache, options, hints) {
1191
1330
  calloutBandPages.add(page);
1192
1331
  return;
1193
1332
  }
1194
- // (A strip's comic is relative to its block: nothing to move.)
1333
+ // (A strip's comic is relative to its block; its caption lines are
1334
+ // relative to it until now.)
1195
1335
  if (built.block.resourceBlock)
1196
1336
  offsetResourceBlockToAbsolute(built.block.resourceBlock, 0, y);
1337
+ else if (built.block.comic)
1338
+ shiftStripLines(built.block, x, y);
1197
1339
  built.block.bbox = createBoundingBox(x, y, width, built.height);
1198
1340
  built.block.pageIndex = page.index;
1199
1341
  built.block.columnIndex = col.index;
@@ -2877,11 +3019,21 @@ function placeDocumentPass(content, config, cache, options, hints) {
2877
3019
  return Math.abs(last.width - width) <= 0.5 ? before : [...before, { fromLine: from.line, width }];
2878
3020
  };
2879
3021
  const CUT_START = { child: 0, line: 0 };
3022
+ /** A strip in a box (the box of a page-wide strip set `here`, #590):
3023
+ * laid out at the box's inner width, as tall as a page allows, with its
3024
+ * placeholder line; the page it lays out for is the cursor's. */
3025
+ const calloutStripBlock = (raw, idx, width) => {
3026
+ if (!raw.comic)
3027
+ return undefined;
3028
+ const blk = buildComicStripBlock(idx, raw.comic, 0, width, contentArea.height - 2 * floatGapPx, cursor.pageIndex);
3029
+ withStripPlaceholder(blk, stripGeometry(idx, raw.comic, width, contentArea.height - 2 * floatGapPx, pageIsVertical(doc.pages[cursor.pageIndex])));
3030
+ return blk;
3031
+ };
2880
3032
  const makeCalloutLayouter = (startIdx, plan, style) => {
2881
3033
  const children = contentBlocks.slice(startIdx + 1, plan.endIdx);
2882
3034
  const realAt = [];
2883
3035
  children.forEach((c, k) => {
2884
- if (c.type !== 'directive' && !isMarkerBlock(c))
3036
+ if ((c.type !== 'directive' || (c.directiveName === 'strip' && c.comic)) && !isMarkerBlock(c))
2885
3037
  realAt.push(k);
2886
3038
  });
2887
3039
  // `:::columns` groups among the children, as [start marker, end marker]
@@ -2939,6 +3091,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
2939
3091
  ...(to.line > 0 ? { lineTo: to.line } : {}),
2940
3092
  ...(open.length > 0 ? { openNested: open } : {}),
2941
3093
  mirrored,
3094
+ stripBlock: calloutStripBlock,
2942
3095
  });
2943
3096
  };
2944
3097
  return { children, childBase: startIdx + 1, realAt, end: { child: children.length, line: 0 }, layoutRange, groups };
@@ -4201,10 +4354,15 @@ function placeDocumentPass(content, config, cache, options, hints) {
4201
4354
  * several columns, it waits in the float queue for the first free band
4202
4355
  * from here on, as a figure does.
4203
4356
  */
4357
+ /** Boxes of page-wide strips set in the flow (no band of several
4358
+ * columns where they stood): their markers are passed over. */
4359
+ const looseStripBoxes = new Set();
4204
4360
  const placeComicStrip = (blockIdx, source) => {
4205
4361
  const place = comicStripPlacement(source.attrs);
4206
4362
  const page = doc.pages[cursor.pageIndex];
4207
- if (place.position !== 'here' || (place.span === 'page' && multiColumnBand(page))) {
4363
+ // (A page-wide strip set `here` on a page of several columns is boxed
4364
+ // by `wrapPageSpanEmbeds` and cuts the band as a page-span box does.)
4365
+ if (place.position !== 'here') {
4208
4366
  const key = `${COMIC_STRIP_PREFIX}${blockIdx}`;
4209
4367
  // A keep-with-next replay passes the fence again: enqueue once.
4210
4368
  if (!enqueuedFloatIds.has(key)) {
@@ -4213,7 +4371,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
4213
4371
  pendingFloats.push({
4214
4372
  resourceId: key,
4215
4373
  firstBlockIdx: blockIdx,
4216
- position: place.position === 'here' ? 'auto' : place.position,
4374
+ position: place.position,
4217
4375
  span: place.span,
4218
4376
  ...(col ? { refPageIndex: page.index, refY: col.bbox.y + (col.bbox.height - col.availableHeight) } : {}),
4219
4377
  });
@@ -4221,27 +4379,26 @@ function placeDocumentPass(content, config, cache, options, hints) {
4221
4379
  return undefined;
4222
4380
  }
4223
4381
  const col = currentColumn(doc, cursor);
4224
- const extent = comicStripExtentAt(source, col.bbox.width, contentArea.height);
4382
+ const g = stripGeometry(blockIdx, source, col.bbox.width, contentArea.height, pageIsVertical(page));
4383
+ const height = g.height;
4225
4384
  const spacingBefore = Math.max(pendingSpacing, floatGapPx);
4226
- if (col.blocks.length > 0 && extent > col.availableHeight - spacingBefore + FIT_EPS) {
4385
+ if (col.blocks.length > 0 && height > col.availableHeight - spacingBefore + FIT_EPS) {
4227
4386
  const run = trailingHeadingRun(col);
4228
4387
  if (run > 0 && run < col.blocks.length) {
4229
4388
  const rolled = rollbackHeadings(col);
4230
4389
  pendingSpacing = 0;
4231
- advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onAtomicNewPage(extent));
4390
+ advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onAtomicNewPage(height));
4232
4391
  return (rolled[0].contentIndex ?? blockIdx - rolled.length) - 1;
4233
4392
  }
4234
4393
  }
4235
- const blk = buildComicStripBlock(blockIdx, source, 0, col.bbox.width, extent, cursor.pageIndex);
4236
- // One placeholder line carries the strip's height (as an inline
4237
- // figure's does).
4238
- blk.lines = [{ text: '', bbox: { x: 0, y: 0, width: col.bbox.width, height: extent }, baseline: 0, hyphenated: false, segments: [], isLastLine: true }];
4394
+ const blk = buildComicStripBlock(blockIdx, source, 0, col.bbox.width, contentArea.height, cursor.pageIndex);
4395
+ withStripPlaceholder(blk, g);
4239
4396
  enterBand(blockIdx, 0);
4240
- placeAtomicBlock(blk, extent, spacingBefore, cursor, doc, geomResolved, contentArea, pageWidthPx, pageHeightPx, onAtomicNewPage(extent));
4397
+ placeAtomicBlock(blk, height, spacingBefore, cursor, doc, geomResolved, contentArea, pageWidthPx, pageHeightPx, onAtomicNewPage(height));
4241
4398
  enterBand(blockIdx, 0);
4242
4399
  // Laid out again where it landed: the page it reports on, and the
4243
4400
  // measure of that column (a side column, another section's).
4244
- const landed = buildComicStripBlock(blockIdx, source, 0, blk.bbox.width, extent, cursor.pageIndex);
4401
+ const landed = buildComicStripBlock(blockIdx, source, 0, blk.bbox.width, contentArea.height, cursor.pageIndex, g);
4245
4402
  blk.comic = landed.comic;
4246
4403
  doc.blocks.push(blk);
4247
4404
  // The flow after the strip goes back to the baseline grid, and the
@@ -4497,6 +4654,24 @@ function placeDocumentPass(content, config, cache, options, hints) {
4497
4654
  flushPendingNumberingAtBoundary();
4498
4655
  continue;
4499
4656
  }
4657
+ // The box of a page-wide strip set `here` (`wrapPageSpanEmbeds`) cuts a
4658
+ // band of several columns; elsewhere the strip is set in the flow as any
4659
+ // strip, its box markers passed over.
4660
+ if (rawBlock.type === 'containerStart' && rawBlock.containerName === 'callout'
4661
+ && rawBlock.containerAttrs?.type === SPAN_EMBED_STYLE_ID && isPageSpanStrip(contentBlocks[blockIdx + 1])
4662
+ && !multiColumnBand(doc.pages[cursor.pageIndex])) {
4663
+ looseStripBoxes.add(rawBlock.containerId);
4664
+ continue;
4665
+ }
4666
+ if (rawBlock.type === 'containerEnd' && rawBlock.containerId !== undefined && looseStripBoxes.has(rawBlock.containerId)) {
4667
+ // What the strip owes below it passes through its box's closing marker.
4668
+ // (Set by the strip's placement, in a callback the flow analysis
4669
+ // does not follow.)
4670
+ const owed = inlineGapOwed;
4671
+ if (owed && owed.afterIdx === blockIdx - 1)
4672
+ inlineGapOwed = { ...owed, afterIdx: blockIdx };
4673
+ continue;
4674
+ }
4500
4675
  if (rawBlock.type === 'containerStart' && rawBlock.containerName === 'callout') {
4501
4676
  const plan = calloutPlan.get(blockIdx);
4502
4677
  if (plan && pickCalloutStyle(calloutStyles, plan.attrs.type)) {