postext 1.33.11 → 1.34.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 (140) hide show
  1. package/README.md +2 -2
  2. package/dist/__tests__/bundle/designOverflowPin.test.js +1 -1
  3. package/dist/__tests__/bundle/gridBreakRulesPin.test.js +2 -2
  4. package/dist/__tests__/bundle/shapingLanguagePin.test.js +3 -3
  5. package/dist/__tests__/bundle/taskBoxPin.test.js +1 -1
  6. package/dist/__tests__/bundle.test.js +1 -1
  7. package/dist/__tests__/codeBlocks.test.js +1 -1
  8. package/dist/__tests__/composed/corpus.test.d.ts +2 -0
  9. package/dist/__tests__/composed/corpus.test.d.ts.map +1 -0
  10. package/dist/__tests__/composed/corpus.test.js +33 -0
  11. package/dist/__tests__/composed/corpus.test.js.map +1 -0
  12. package/dist/__tests__/composed/slotChecks.test.d.ts +2 -0
  13. package/dist/__tests__/composed/slotChecks.test.d.ts.map +1 -0
  14. package/dist/__tests__/composed/slotChecks.test.js +148 -0
  15. package/dist/__tests__/composed/slotChecks.test.js.map +1 -0
  16. package/dist/__tests__/composedTemplates.test.d.ts +2 -0
  17. package/dist/__tests__/composedTemplates.test.d.ts.map +1 -0
  18. package/dist/__tests__/composedTemplates.test.js +349 -0
  19. package/dist/__tests__/composedTemplates.test.js.map +1 -0
  20. package/dist/__tests__/contentInConfig.test.d.ts +2 -0
  21. package/dist/__tests__/contentInConfig.test.d.ts.map +1 -0
  22. package/dist/__tests__/contentInConfig.test.js +201 -0
  23. package/dist/__tests__/contentInConfig.test.js.map +1 -0
  24. package/dist/__tests__/defaults/stripRoundTrip.test.js +3 -1
  25. package/dist/__tests__/defaults/stripRoundTrip.test.js.map +1 -1
  26. package/dist/__tests__/exports.test.js +22 -0
  27. package/dist/__tests__/exports.test.js.map +1 -1
  28. package/dist/__tests__/hardBreaks.test.js +1 -1
  29. package/dist/__tests__/headingInlineMarks.test.js +1 -1
  30. package/dist/__tests__/mathSizeMigration.test.js +2 -2
  31. package/dist/__tests__/parse/composedPages.test.d.ts +2 -0
  32. package/dist/__tests__/parse/composedPages.test.d.ts.map +1 -0
  33. package/dist/__tests__/parse/composedPages.test.js +323 -0
  34. package/dist/__tests__/parse/composedPages.test.js.map +1 -0
  35. package/dist/__tests__/parse/containers.test.js +2 -2
  36. package/dist/__tests__/parse/containers.test.js.map +1 -1
  37. package/dist/__tests__/pipeline/calloutInlineResourceGap.test.js +1 -1
  38. package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.js +1 -1
  39. package/dist/__tests__/pipeline/colonListRoom.test.js +1 -1
  40. package/dist/__tests__/pipeline/compoundBreaks.test.js +1 -1
  41. package/dist/__tests__/pipeline/dashBreaks.test.js +1 -1
  42. package/dist/__tests__/pipeline/headingSplitOrphans.test.js +1 -1
  43. package/dist/__tests__/pipeline/inlineFigureSpace.test.js +1 -1
  44. package/dist/__tests__/pipeline/paragraphContainerTail.test.js +1 -1
  45. package/dist/__tests__/pipeline/raggedOptimal.test.js +1 -1
  46. package/dist/__tests__/tocGroups.test.d.ts +2 -0
  47. package/dist/__tests__/tocGroups.test.d.ts.map +1 -0
  48. package/dist/__tests__/tocGroups.test.js +213 -0
  49. package/dist/__tests__/tocGroups.test.js.map +1 -0
  50. package/dist/__tests__/verseLines.test.js +1 -1
  51. package/dist/bundle/configVersion.d.ts +16 -5
  52. package/dist/bundle/configVersion.d.ts.map +1 -1
  53. package/dist/bundle/configVersion.js +17 -4
  54. package/dist/bundle/configVersion.js.map +1 -1
  55. package/dist/bundle/types.d.ts +4 -3
  56. package/dist/bundle/types.d.ts.map +1 -1
  57. package/dist/composed/document.d.ts +43 -0
  58. package/dist/composed/document.d.ts.map +1 -0
  59. package/dist/composed/document.js +88 -0
  60. package/dist/composed/document.js.map +1 -0
  61. package/dist/composed/slotChecks.d.ts +29 -0
  62. package/dist/composed/slotChecks.d.ts.map +1 -0
  63. package/dist/composed/slotChecks.js +109 -0
  64. package/dist/composed/slotChecks.js.map +1 -0
  65. package/dist/composed/templates.d.ts +133 -0
  66. package/dist/composed/templates.d.ts.map +1 -0
  67. package/dist/composed/templates.js +702 -0
  68. package/dist/composed/templates.js.map +1 -0
  69. package/dist/composed/types.d.ts +404 -0
  70. package/dist/composed/types.d.ts.map +1 -0
  71. package/dist/composed/types.js +2 -0
  72. package/dist/composed/types.js.map +1 -0
  73. package/dist/composed/validate.d.ts +26 -0
  74. package/dist/composed/validate.d.ts.map +1 -0
  75. package/dist/composed/validate.js +276 -0
  76. package/dist/composed/validate.js.map +1 -0
  77. package/dist/configWarnings.d.ts +19 -3
  78. package/dist/configWarnings.d.ts.map +1 -1
  79. package/dist/configWarnings.js +170 -40
  80. package/dist/configWarnings.js.map +1 -1
  81. package/dist/defaults/checks.d.ts +7 -0
  82. package/dist/defaults/checks.d.ts.map +1 -0
  83. package/dist/defaults/checks.js +49 -0
  84. package/dist/defaults/checks.js.map +1 -0
  85. package/dist/defaults/index.d.ts +1 -0
  86. package/dist/defaults/index.d.ts.map +1 -1
  87. package/dist/defaults/index.js +26 -0
  88. package/dist/defaults/index.js.map +1 -1
  89. package/dist/defaults/toc.d.ts +17 -2
  90. package/dist/defaults/toc.d.ts.map +1 -1
  91. package/dist/defaults/toc.js +53 -1
  92. package/dist/defaults/toc.js.map +1 -1
  93. package/dist/design/slots.d.ts +29 -0
  94. package/dist/design/slots.d.ts.map +1 -0
  95. package/dist/design/slots.js +48 -0
  96. package/dist/design/slots.js.map +1 -0
  97. package/dist/index.d.ts +17 -3
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +10 -1
  100. package/dist/index.js.map +1 -1
  101. package/dist/parse/blockParser.d.ts.map +1 -1
  102. package/dist/parse/blockParser.js +295 -36
  103. package/dist/parse/blockParser.js.map +1 -1
  104. package/dist/parse/index.d.ts +1 -1
  105. package/dist/parse/index.d.ts.map +1 -1
  106. package/dist/parse/index.js.map +1 -1
  107. package/dist/parse/indexMarks.d.ts.map +1 -1
  108. package/dist/parse/indexMarks.js +24 -0
  109. package/dist/parse/indexMarks.js.map +1 -1
  110. package/dist/parse/types.d.ts +95 -2
  111. package/dist/parse/types.d.ts.map +1 -1
  112. package/dist/pipeline/build.d.ts.map +1 -1
  113. package/dist/pipeline/build.js +178 -3
  114. package/dist/pipeline/build.js.map +1 -1
  115. package/dist/pipeline/config.d.ts.map +1 -1
  116. package/dist/pipeline/config.js +17 -2
  117. package/dist/pipeline/config.js.map +1 -1
  118. package/dist/pipeline/contentWarnings.d.ts.map +1 -1
  119. package/dist/pipeline/contentWarnings.js +73 -0
  120. package/dist/pipeline/contentWarnings.js.map +1 -1
  121. package/dist/pipeline/placeholders.d.ts +7 -0
  122. package/dist/pipeline/placeholders.d.ts.map +1 -1
  123. package/dist/pipeline/placeholders.js +41 -0
  124. package/dist/pipeline/placeholders.js.map +1 -1
  125. package/dist/pipeline/textWrap.js +1 -1
  126. package/dist/pipeline/textWrap.js.map +1 -1
  127. package/dist/pipeline/toc.d.ts +11 -1
  128. package/dist/pipeline/toc.d.ts.map +1 -1
  129. package/dist/pipeline/toc.js +147 -8
  130. package/dist/pipeline/toc.js.map +1 -1
  131. package/dist/types.d.ts +99 -0
  132. package/dist/types.d.ts.map +1 -1
  133. package/dist/util/closestKey.d.ts +4 -0
  134. package/dist/util/closestKey.d.ts.map +1 -0
  135. package/dist/util/closestKey.js +40 -0
  136. package/dist/util/closestKey.js.map +1 -0
  137. package/dist/vdt.d.ts +125 -7
  138. package/dist/vdt.d.ts.map +1 -1
  139. package/dist/vdt.js.map +1 -1
  140. package/package.json +1 -1
@@ -3033,11 +3033,13 @@ function placeDocumentPass(content, config, cache, options, hints) {
3033
3033
  // one in an Arabic book. Its lines carry their span (`VDTLine.measure`).
3034
3034
  if (blk.type !== 'resource' && raw.direction !== undefined && raw.direction !== resolvedDirection(resolved))
3035
3035
  blk.direction = raw.direction;
3036
+ const tocContinued = raw.toc?.group?.role === 'continuation' ? { continued: true } : {};
3036
3037
  if (raw.toc?.kind === 'entry') {
3037
- blk.tocEntry = raw.toc.pageIndex !== undefined ? { pageIndex: raw.toc.pageIndex } : {};
3038
+ blk.tocEntry = { ...(raw.toc.pageIndex !== undefined ? { pageIndex: raw.toc.pageIndex } : {}), ...tocContinued };
3038
3039
  }
3039
3040
  if (raw.toc?.kind === 'part') {
3040
3041
  blk.tocPart = {
3042
+ ...tocContinued,
3041
3043
  number: raw.toc.number,
3042
3044
  title: raw.toc.title ?? '',
3043
3045
  pageLabel: raw.toc.pageLabel ?? '',
@@ -3706,6 +3708,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
3706
3708
  /** A closed `:::paper` still owes a page break before the next placed
3707
3709
  * block. */
3708
3710
  let pendingPaperBreak = false;
3711
+ /** A composed page (`::::page`, #854) ended: the next placed block opens
3712
+ * a new page. */
3713
+ let pendingComposedBreak = false;
3709
3714
  /** A comic page (`:::page`) was laid out on the current page: the next
3710
3715
  * placed block opens a new one (a page that ends the document leaves no
3711
3716
  * empty page behind). */
@@ -5933,6 +5938,123 @@ function placeDocumentPass(content, config, cache, options, hints) {
5933
5938
  inlineGapOwed = pendingSpacing > 0 ? { afterIdx: blockIdx, pending: pendingSpacing, exact: owed } : null;
5934
5939
  return undefined;
5935
5940
  };
5941
+ // --- Blocks of the contents kept together (`toc.groups`, #850) -----------
5942
+ /** The measure context of content block `idx` (a part's, a styled
5943
+ * section's). */
5944
+ const measureCtxOf = (idx) => (partPlan.byBlock[idx] ? partMeasureCtx : sectionMeasureCtx(sectionPlan.byBlock[idx]));
5945
+ /** The rows of the contents block whose lead row is `idx`, in order (its
5946
+ * head's repeats aside). */
5947
+ const tocGroupRows = (idx) => {
5948
+ const id = contentBlocks[idx]?.toc?.group?.id;
5949
+ const rows = [];
5950
+ if (id === undefined)
5951
+ return rows;
5952
+ for (let i = idx; i < contentBlocks.length; i++) {
5953
+ const b = contentBlocks[i];
5954
+ if (isMarkerBlock(b))
5955
+ continue;
5956
+ if (b.toc?.group?.id !== id)
5957
+ break;
5958
+ if (b.toc.group.role !== 'continuation')
5959
+ rows.push(i);
5960
+ }
5961
+ return rows;
5962
+ };
5963
+ /**
5964
+ * The room the contents block led by row `idx` takes in a column `width`
5965
+ * wide, from the top of its first row (the space above it aside): `whole`
5966
+ * for every row, `lead` down to the `minEntries`-th entry under its head
5967
+ * (the least a cut block keeps with its head). The rows are spaced as
5968
+ * the layout spaces them, by the larger of the margins that meet.
5969
+ */
5970
+ const tocGroupNeed = (idx, width) => {
5971
+ const rows = tocGroupRows(idx);
5972
+ if (rows.length < 2)
5973
+ return undefined;
5974
+ const min = contentBlocks[idx].toc.group.minEntries;
5975
+ let height = 0;
5976
+ let lead = 0;
5977
+ let prevBottom;
5978
+ let afterHead = -1;
5979
+ for (const i of rows) {
5980
+ const raw = contentBlocks[i];
5981
+ const m = measureContentBlock(raw, i, width, measureCtxOf(i));
5982
+ if (!m)
5983
+ return undefined;
5984
+ const style = m.kind.style;
5985
+ if (prevBottom !== undefined)
5986
+ height += Math.max(prevBottom, style.marginTopPx);
5987
+ height += m.measured.lines.length * style.lineHeightPx;
5988
+ prevBottom = style.marginBottomPx;
5989
+ if (raw.toc.group.role === 'head')
5990
+ afterHead = 0;
5991
+ else if (afterHead >= 0 && raw.toc.kind === 'entry')
5992
+ afterHead++;
5993
+ if (afterHead >= 0 && afterHead <= min)
5994
+ lead = height;
5995
+ }
5996
+ return { whole: height, lead };
5997
+ };
5998
+ /**
5999
+ * Whether the repeat of a contents block's head row at `idx` is set: the
6000
+ * entry after it goes on in a fresh column, which the repeat opens. When
6001
+ * the entry does not fit in the room the cursor's column has left (an
6002
+ * entry is not cut between columns once its block is), the cursor moves
6003
+ * on to the next column first.
6004
+ */
6005
+ const tocContinuationOpens = (idx) => {
6006
+ let next = idx + 1;
6007
+ while (next < contentBlocks.length && isMarkerBlock(contentBlocks[next]))
6008
+ next++;
6009
+ const entry = contentBlocks[next];
6010
+ if (entry?.toc?.group?.id !== contentBlocks[idx].toc.group.id)
6011
+ return false;
6012
+ const col = currentColumn(doc, cursor);
6013
+ if (col.blocks.length === 0)
6014
+ return true;
6015
+ const m = measureContentBlock(entry, next, col.bbox.width, measureCtxOf(next));
6016
+ if (!m)
6017
+ return false;
6018
+ const style = m.kind.style;
6019
+ const need = m.measured.lines.length * style.lineHeightPx + Math.max(pendingSpacing, style.marginTopPx);
6020
+ if (need <= col.availableHeight + FIT_EPS)
6021
+ return false;
6022
+ pendingSpacing = 0;
6023
+ advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
6024
+ return true;
6025
+ };
6026
+ /** A `tocGroupSplit` warning for each contents block set in more than
6027
+ * one column, with its head's title and the page it is cut on. */
6028
+ const tocGroupWarnings = () => {
6029
+ const placed = new Map();
6030
+ for (const page of doc.pages) {
6031
+ for (const c of page.columns) {
6032
+ for (const b of c.blocks) {
6033
+ const raw = b.contentIndex !== undefined ? contentBlocks[b.contentIndex] : undefined;
6034
+ const group = raw?.toc?.group;
6035
+ if (!raw || !group || group.role === 'continuation')
6036
+ continue;
6037
+ const at = placed.get(group.id) ?? { title: group.title, columns: new Set(), pageIndex: page.index, source: raw };
6038
+ at.columns.add(`${page.index}:${c.index}`);
6039
+ placed.set(group.id, at);
6040
+ }
6041
+ }
6042
+ }
6043
+ const out = [];
6044
+ for (const at of placed.values()) {
6045
+ if (at.columns.size < 2)
6046
+ continue;
6047
+ out.push({
6048
+ kind: 'tocGroupSplit',
6049
+ title: at.title,
6050
+ columns: at.columns.size,
6051
+ sourceStart: at.source.sourceStart + bodyOffset,
6052
+ sourceEnd: at.source.sourceEnd + bodyOffset,
6053
+ pageIndex: at.pageIndex,
6054
+ });
6055
+ }
6056
+ return out;
6057
+ };
5936
6058
  /** The first block of the document that sets something: a heading there
5937
6059
  * opens the document's first page (`openerPages`, #749). */
5938
6060
  const firstFlowBlockIdx = contentBlocks.findIndex((b) => !isMarkerBlock(b) && b.type !== 'directive');
@@ -5946,6 +6068,10 @@ function placeDocumentPass(content, config, cache, options, hints) {
5946
6068
  const blockSection = sectionPlan.byBlock[blockIdx];
5947
6069
  if (blockSection !== currentSection)
5948
6070
  enterSection(blockSection);
6071
+ // The repeat of a contents block's head row (#850): set where a cut
6072
+ // block goes on in a fresh column, skipped everywhere else.
6073
+ if (rawBlock.toc?.group?.role === 'continuation' && !tocContinuationOpens(blockIdx))
6074
+ continue;
5949
6075
  // Floats whose reference landed in an earlier iteration take the first
5950
6076
  // free slot of the current page now — after their reference in reading
5951
6077
  // order. Then enqueue the floats first-referenced in this block, so the
@@ -6153,6 +6279,26 @@ function placeDocumentPass(content, config, cache, options, hints) {
6153
6279
  pendingPaperBreak = false;
6154
6280
  breakForPaper(blockIdx);
6155
6281
  }
6282
+ // A composed page (`::::page{template=…}`, #854). Until templates are
6283
+ // laid out (P4, #855) its content is set in the flow, in Markdown order
6284
+ // and on pages of its own: the page fence breaks the page as `:::paper`
6285
+ // does, its frames are transparent containers (the generic marker branch
6286
+ // below), and the block after the closing fence opens a new page. P4
6287
+ // replaces this branch with the template layout (DESIGN §5.1).
6288
+ if ((rawBlock.type === 'containerStart' || rawBlock.type === 'containerEnd') && rawBlock.containerName === 'composedPage') {
6289
+ if (rawBlock.type === 'containerStart') {
6290
+ pendingComposedBreak = false;
6291
+ breakForPaper(blockIdx);
6292
+ }
6293
+ else {
6294
+ pendingComposedBreak = true;
6295
+ }
6296
+ continue;
6297
+ }
6298
+ if (pendingComposedBreak) {
6299
+ pendingComposedBreak = false;
6300
+ breakForPaper(blockIdx);
6301
+ }
6156
6302
  // `:::page`: a comic page owns a fresh page — its panels, split lines
6157
6303
  // and lettering laid out on the sheet (`layoutComicPage`); nothing
6158
6304
  // flows on it, and what follows starts on the next page.
@@ -7389,6 +7535,35 @@ function placeDocumentPass(content, config, cache, options, hints) {
7389
7535
  }
7390
7536
  }
7391
7537
  }
7538
+ // A block of the contents is set whole (`toc.groups.keepTogether`,
7539
+ // `:::toc{keep}`, #850): when its rows do not all fit in the room the
7540
+ // column has left and a full column would hold them, it opens the
7541
+ // next column, with the headings right above it. One taller than a
7542
+ // column is cut, but keeps its head with `minEntriesAfterHead`
7543
+ // entries. Not a block that opens its column, or stands under
7544
+ // nothing but headings: it cannot do better anywhere.
7545
+ if (rawBlock.toc?.group?.lead && partIndex === 0 && curCol.blocks.length > 0) {
7546
+ const headingRun = trailingHeadingRun(curCol);
7547
+ const need = headingRun < curCol.blocks.length ? tocGroupNeed(blockIdx, curCol.bbox.width) : undefined;
7548
+ if (need) {
7549
+ const room = curCol.availableHeight - spacingBefore;
7550
+ const asked = need.whole <= contentArea.height + FIT_EPS ? need.whole : need.lead;
7551
+ if (asked > room + FIT_EPS) {
7552
+ if (resolved.headings.keepWithNext && headingRun > 0) {
7553
+ const rolledBack = rollbackHeadings(curCol);
7554
+ if (rolledBack.length > 0) {
7555
+ blockIdx = (rolledBack[0].contentIndex ?? blockIdx - rolledBack.length) - 1;
7556
+ pendingSpacing = 0;
7557
+ advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
7558
+ break;
7559
+ }
7560
+ }
7561
+ pendingSpacing = 0;
7562
+ advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
7563
+ continue;
7564
+ }
7565
+ }
7566
+ }
7392
7567
  // A short list is set whole (`bodyText.shortListSplit: 'avoid'`,
7393
7568
  // #749): when its items do not all fit under what the column holds
7394
7569
  // and an empty column would hold them, the list opens the next
@@ -8148,7 +8323,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
8148
8323
  if (textWrapNotes.length > 0 || columnsNotes.length > 0)
8149
8324
  textWrapWarningsByDoc.set(doc, [...textWrapNotes, ...columnsNotes]);
8150
8325
  // The composition rules (#749): short lists cut, floats on opener pages.
8151
- const ruleNotes = [...shortListWarnings(), ...openerFloatNotes];
8326
+ const ruleNotes = [...shortListWarnings(), ...openerFloatNotes, ...tocGroupWarnings()];
8152
8327
  if (ruleNotes.length > 0)
8153
8328
  compositionWarningsByDoc.set(doc, ruleNotes);
8154
8329
  const { proposals: citingPageProposals, refused: citingPageRefused } = settleCitingPageFloats();
@@ -8262,7 +8437,7 @@ export function* buildDocumentGen(content, config, cache, options) {
8262
8437
  // Config values the build replaced (an unknown number format, a font
8263
8438
  // stack, a side column no column width can take): walked once per build,
8264
8439
  // not per pass — they belong to no page.
8265
- const configWarnings = collectConfigWarnings(config);
8440
+ const configWarnings = collectConfigWarnings(config, { resources: content.resources, metadata: doc.metadata });
8266
8441
  if (configWarnings.length > 0)
8267
8442
  doc.configWarnings = configWarnings;
8268
8443
  return doc;