postext 1.52.0 → 1.53.1

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/dist/__tests__/breakOpportunities.test.js +12 -7
  2. package/dist/__tests__/breakOpportunities.test.js.map +1 -1
  3. package/dist/__tests__/bundle/balloonPlacementPin.test.js +11 -9
  4. package/dist/__tests__/bundle/balloonPlacementPin.test.js.map +1 -1
  5. package/dist/__tests__/bundle/cjkRulesPin.test.js +12 -12
  6. package/dist/__tests__/bundle/cjkRulesPin.test.js.map +1 -1
  7. package/dist/__tests__/bundle/designOverflowPin.test.js +3 -3
  8. package/dist/__tests__/bundle/designOverflowPin.test.js.map +1 -1
  9. package/dist/__tests__/bundle/flowColumnsPin.test.js +4 -4
  10. package/dist/__tests__/bundle/flowColumnsPin.test.js.map +1 -1
  11. package/dist/__tests__/bundle/gridBalancingPin.test.js +11 -9
  12. package/dist/__tests__/bundle/gridBalancingPin.test.js.map +1 -1
  13. package/dist/__tests__/bundle/gridBreakRulesPin.test.js +26 -24
  14. package/dist/__tests__/bundle/gridBreakRulesPin.test.js.map +1 -1
  15. package/dist/__tests__/bundle/indexGroupSplitPin.test.js +4 -4
  16. package/dist/__tests__/bundle/indexGroupSplitPin.test.js.map +1 -1
  17. package/dist/__tests__/bundle/looseOrderPin.test.js +11 -9
  18. package/dist/__tests__/bundle/looseOrderPin.test.js.map +1 -1
  19. package/dist/__tests__/bundle/markRadiusPin.test.js +3 -3
  20. package/dist/__tests__/bundle/markRadiusPin.test.js.map +1 -1
  21. package/dist/__tests__/bundle/refLabelBreaksPin.test.js +11 -9
  22. package/dist/__tests__/bundle/refLabelBreaksPin.test.js.map +1 -1
  23. package/dist/__tests__/bundle/scriptContextPin.test.js +12 -10
  24. package/dist/__tests__/bundle/scriptContextPin.test.js.map +1 -1
  25. package/dist/__tests__/bundle/shapingLanguagePin.test.js +20 -18
  26. package/dist/__tests__/bundle/shapingLanguagePin.test.js.map +1 -1
  27. package/dist/__tests__/bundle/taskBoxPin.test.js +1 -1
  28. package/dist/__tests__/bundle/trailingStepPin.test.js +4 -4
  29. package/dist/__tests__/bundle/trailingStepPin.test.js.map +1 -1
  30. package/dist/__tests__/bundle/wideFloatStretchPin.test.js +4 -4
  31. package/dist/__tests__/bundle/wideFloatStretchPin.test.js.map +1 -1
  32. package/dist/__tests__/bundle.test.js +5 -5
  33. package/dist/__tests__/bundle.test.js.map +1 -1
  34. package/dist/__tests__/codeBlocks.test.js +1 -1
  35. package/dist/__tests__/composed/folioMissing.test.d.ts +2 -0
  36. package/dist/__tests__/composed/folioMissing.test.d.ts.map +1 -0
  37. package/dist/__tests__/composed/folioMissing.test.js +90 -0
  38. package/dist/__tests__/composed/folioMissing.test.js.map +1 -0
  39. package/dist/__tests__/composedTemplates.test.js +8 -6
  40. package/dist/__tests__/composedTemplates.test.js.map +1 -1
  41. package/dist/__tests__/hardBreaks.test.js +1 -1
  42. package/dist/__tests__/headingInlineMarks.test.js +1 -1
  43. package/dist/__tests__/mathSizeMigration.test.js +4 -4
  44. package/dist/__tests__/mathSizeMigration.test.js.map +1 -1
  45. package/dist/__tests__/pipeline/boxParagraphStyles.test.js +2 -2
  46. package/dist/__tests__/pipeline/calloutInlineResourceGap.test.js +1 -1
  47. package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.js +3 -3
  48. package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.js.map +1 -1
  49. package/dist/__tests__/pipeline/colonListRoom.test.js +3 -3
  50. package/dist/__tests__/pipeline/colonListRoom.test.js.map +1 -1
  51. package/dist/__tests__/pipeline/compoundBreaks.test.js +6 -6
  52. package/dist/__tests__/pipeline/compoundBreaks.test.js.map +1 -1
  53. package/dist/__tests__/pipeline/dashBreaks.test.js +5 -5
  54. package/dist/__tests__/pipeline/dashBreaks.test.js.map +1 -1
  55. package/dist/__tests__/pipeline/headingSplitOrphans.test.js +3 -3
  56. package/dist/__tests__/pipeline/headingSplitOrphans.test.js.map +1 -1
  57. package/dist/__tests__/pipeline/inlineFigureSpace.test.js +5 -5
  58. package/dist/__tests__/pipeline/inlineFigureSpace.test.js.map +1 -1
  59. package/dist/__tests__/pipeline/openerChips.test.d.ts +2 -0
  60. package/dist/__tests__/pipeline/openerChips.test.d.ts.map +1 -0
  61. package/dist/__tests__/pipeline/openerChips.test.js +181 -0
  62. package/dist/__tests__/pipeline/openerChips.test.js.map +1 -0
  63. package/dist/__tests__/pipeline/paragraphContainerTail.test.js +7 -7
  64. package/dist/__tests__/pipeline/paragraphContainerTail.test.js.map +1 -1
  65. package/dist/__tests__/pipeline/raggedOptimal.test.js +7 -7
  66. package/dist/__tests__/pipeline/raggedOptimal.test.js.map +1 -1
  67. package/dist/__tests__/scriptContext.test.js +10 -1
  68. package/dist/__tests__/scriptContext.test.js.map +1 -1
  69. package/dist/__tests__/slashBreaks.test.d.ts +2 -0
  70. package/dist/__tests__/slashBreaks.test.d.ts.map +1 -0
  71. package/dist/__tests__/slashBreaks.test.js +160 -0
  72. package/dist/__tests__/slashBreaks.test.js.map +1 -0
  73. package/dist/__tests__/verseLines.test.js +7 -7
  74. package/dist/__tests__/verseLines.test.js.map +1 -1
  75. package/dist/bundle/configVersion.d.ts +28 -7
  76. package/dist/bundle/configVersion.d.ts.map +1 -1
  77. package/dist/bundle/configVersion.js +40 -7
  78. package/dist/bundle/configVersion.js.map +1 -1
  79. package/dist/bundle/index.d.ts +1 -1
  80. package/dist/bundle/index.d.ts.map +1 -1
  81. package/dist/bundle/index.js +1 -1
  82. package/dist/bundle/index.js.map +1 -1
  83. package/dist/canvas-backend/headerFooter.d.ts.map +1 -1
  84. package/dist/canvas-backend/headerFooter.js +10 -0
  85. package/dist/canvas-backend/headerFooter.js.map +1 -1
  86. package/dist/composed/furniture.d.ts +41 -0
  87. package/dist/composed/furniture.d.ts.map +1 -0
  88. package/dist/composed/furniture.js +108 -0
  89. package/dist/composed/furniture.js.map +1 -0
  90. package/dist/defaults/bodyText.d.ts.map +1 -1
  91. package/dist/defaults/bodyText.js +7 -0
  92. package/dist/defaults/bodyText.js.map +1 -1
  93. package/dist/design/bidiText.d.ts +2 -0
  94. package/dist/design/bidiText.d.ts.map +1 -1
  95. package/dist/design/bidiText.js +1 -1
  96. package/dist/design/bidiText.js.map +1 -1
  97. package/dist/design/cjkText.d.ts.map +1 -1
  98. package/dist/design/cjkText.js +6 -1
  99. package/dist/design/cjkText.js.map +1 -1
  100. package/dist/design/layout.d.ts +7 -1
  101. package/dist/design/layout.d.ts.map +1 -1
  102. package/dist/design/layout.js +49 -15
  103. package/dist/design/layout.js.map +1 -1
  104. package/dist/design/placeholders.d.ts +6 -1
  105. package/dist/design/placeholders.d.ts.map +1 -1
  106. package/dist/design/placeholders.js.map +1 -1
  107. package/dist/design/richText.d.ts +28 -4
  108. package/dist/design/richText.d.ts.map +1 -1
  109. package/dist/design/richText.js +105 -6
  110. package/dist/design/richText.js.map +1 -1
  111. package/dist/html-backend.d.ts.map +1 -1
  112. package/dist/html-backend.js +28 -5
  113. package/dist/html-backend.js.map +1 -1
  114. package/dist/measure/breakRules.d.ts +33 -0
  115. package/dist/measure/breakRules.d.ts.map +1 -1
  116. package/dist/measure/breakRules.js +82 -0
  117. package/dist/measure/breakRules.js.map +1 -1
  118. package/dist/measure/cache.d.ts.map +1 -1
  119. package/dist/measure/cache.js +4 -2
  120. package/dist/measure/cache.js.map +1 -1
  121. package/dist/measure/rich.d.ts +6 -2
  122. package/dist/measure/rich.d.ts.map +1 -1
  123. package/dist/measure/rich.js +69 -6
  124. package/dist/measure/rich.js.map +1 -1
  125. package/dist/pipeline/build.d.ts.map +1 -1
  126. package/dist/pipeline/build.js +16 -7
  127. package/dist/pipeline/build.js.map +1 -1
  128. package/dist/pipeline/contentWarnings.d.ts.map +1 -1
  129. package/dist/pipeline/contentWarnings.js +3 -0
  130. package/dist/pipeline/contentWarnings.js.map +1 -1
  131. package/dist/pipeline/headerFooter.d.ts +17 -3
  132. package/dist/pipeline/headerFooter.d.ts.map +1 -1
  133. package/dist/pipeline/headerFooter.js +199 -45
  134. package/dist/pipeline/headerFooter.js.map +1 -1
  135. package/dist/types.d.ts +17 -0
  136. package/dist/types.d.ts.map +1 -1
  137. package/dist/vdt.d.ts +26 -0
  138. package/dist/vdt.d.ts.map +1 -1
  139. package/dist/vdt.js.map +1 -1
  140. package/package.json +1 -1
@@ -2,7 +2,7 @@ import { resolvedComics } from '../defaults/comics.js';
2
2
  import { fontFamilyOf, getMeasureRegion, getMeasureUprightDigits, getMeasureWritingMode, measureCentralBaseline, setMeasureWritingMode, withMeasureWritingMode } from '../measure/vertical.js';
3
3
  import { applyPartPalettesToFlow } from './partPalette.js';
4
4
  import { recolorLineNumbers } from './lineNumbers.js';
5
- import { CHIP_PLACEHOLDER, applyTitleBreaks, parseInlineFormatting, replaceTitleBreaks } from '../parse/inlineFormatting.js';
5
+ import { CHIP_PLACEHOLDER, applyTitleBreaks, extractInlineChips, injectChipSpans, parseInlineFormatting, replaceTitleBreaks, withoutChips } from '../parse/inlineFormatting.js';
6
6
  import { createBoundingBox, flowRectToPage, pageIsMirrored, pageIsVertical, } from '../vdt.js';
7
7
  import { computeChapterTitles, computeChapterTitlesAtTop, computeChapterNumbers, computeChapterNumbersAtTop, computeChapterNumbersByBlock, computeChapterAttrs, computePageMarks, computePartValues, lineJoin, markSourceOf } from './placeholders.js';
8
8
  import { parsePartNumber, parsePartPalette, partMarkPages } from './parts.js';
@@ -17,6 +17,7 @@ import { splitSpreadDecoration } from '../composed/spread.js';
17
17
  import { pathInkBox } from '../design/pathBlock.js';
18
18
  import { frameOfBlock, framePageRect } from '../composed/paint.js';
19
19
  import { figureAnchorPointOnPage } from '../composed/labels.js';
20
+ import { expectedFurniture, folioMissingWarnings } from '../composed/furniture.js';
20
21
  import { layoutDesignSlot, textVerticalOffset, trailingTracking, } from '../design/layout.js';
21
22
  import { resolveDesignText } from '../design/placeholders.js';
22
23
  import { plainDesignText } from '../design/richText.js';
@@ -678,14 +679,40 @@ const PLAIN_RUN = { bold: false, italic: false };
678
679
  const TITLE_ESCAPE_RE = /[*_^~`]/g;
679
680
  const escapeTitleRun = (text) => text.replace(TITLE_ESCAPE_RE, (c) => `\\${c}`);
680
681
  const sameEmphasis = (a, b) => a.bold === b.bold && a.italic === b.italic;
681
- /** The title a page-span heading's default opener prints, from the lines the
682
- * heading was measured into (see {@link DefaultOpenerTitle}). The same
683
- * reading serves the band's measure (`build.ts`) and its paint, so the
684
- * two lay out one text. */
685
- export function defaultOpenerTitle(lines, numberPrefix, titleBreaks, titleLength,
686
- /** What joins the number to the title in the lines
687
- * (`HeadingLevelConfig.numberSeparator`). */
688
- numberSeparator = ' ') {
682
+ /** The characters a chip's label escapes: the inline markers and the
683
+ * brackets (`\]`, see `extractInlineChips`). */
684
+ const CHIP_LABEL_ESCAPE_RE = /[*_^~`[\]]/g;
685
+ /** A chip style id that can be written back as `{style="…"}`. */
686
+ const CHIP_STYLE_ID_RE = /^[^"{}\n]*$/;
687
+ /** A laid-out chip written back as `:chip[…]{style="…"}` (#1162): its
688
+ * runs in logical order, each with its own bold, italic and script, so a
689
+ * design text that reads inline marks sets the same words. Undefined when
690
+ * the style id cannot be written in an attribute. */
691
+ function chipMarkdown(chip) {
692
+ if (!CHIP_STYLE_ID_RE.test(chip.styleId))
693
+ return undefined;
694
+ let label = '';
695
+ for (const run of chip.runs) {
696
+ if (run.text.length === 0)
697
+ continue;
698
+ let t = run.text.replace(CHIP_LABEL_ESCAPE_RE, (c) => `\\${c}`);
699
+ if (run.baselineShift !== undefined && run.baselineShift !== 0)
700
+ t = run.baselineShift < 0 ? `^${t}^` : `~${t}~`;
701
+ const marker = run.bold && run.italic ? '***' : run.bold ? '**' : run.italic ? '*' : '';
702
+ // Markers go round the words, their edge spaces outside them.
703
+ const core = t.trim();
704
+ label += marker && core ? t.replace(core, `${marker}${core}${marker}`) : t;
705
+ }
706
+ if (label.trim() === '')
707
+ label = ' ';
708
+ return `:chip[${label}]${chip.styleId ? `{style="${chip.styleId}"}` : ''}`;
709
+ }
710
+ /** The characters of a heading's lines read back as the title, with the
711
+ * run flags of each (see {@link defaultOpenerTitle}). With `keepChips`
712
+ * each chip stays one {@link CHIP_PLACEHOLDER} carrying its box; else it
713
+ * is left out with the space beside it (#1100), recorded in `removedAt`.
714
+ * Undefined when a chip is to be kept and its segment carries no box. */
715
+ function readTitleChars(lines, keepChips) {
689
716
  const chars = [];
690
717
  const flags = [];
691
718
  /** Where a line break was read back as a space or a no-break space. */
@@ -696,6 +723,7 @@ numberSeparator = ' ') {
696
723
  * before it: a chip is a box of the page, not words of the title. */
697
724
  const removedAt = [];
698
725
  let afterChip = false;
726
+ let missing = false;
699
727
  lines.forEach((ln, i) => {
700
728
  const start = chars.length;
701
729
  // The hyphen repeated from the line before (`repeatHyphen`) is not part
@@ -709,6 +737,14 @@ numberSeparator = ' ') {
709
737
  skipRepeated = false;
710
738
  }
711
739
  if (s.text === CHIP_PLACEHOLDER) {
740
+ if (keepChips) {
741
+ // The chip, one character with its box (#1162).
742
+ if (!s.chip)
743
+ missing = true;
744
+ chars.push(CHIP_PLACEHOLDER);
745
+ flags.push({ bold: false, italic: false, ...(s.chip ? { chip: s.chip } : {}) });
746
+ continue;
747
+ }
712
748
  removedAt.push(chars.length);
713
749
  afterChip = true;
714
750
  continue;
@@ -749,6 +785,8 @@ numberSeparator = ' ') {
749
785
  flags.push(PLAIN_RUN);
750
786
  }
751
787
  });
788
+ if (missing)
789
+ return undefined;
752
790
  // The space two lines were broken at takes the emphasis of the words on
753
791
  // both sides of it, when they share one.
754
792
  for (let i = 1; i < chars.length - 1; i++) {
@@ -758,25 +796,14 @@ numberSeparator = ' ') {
758
796
  flags[i] = { bold: f.bold, italic: f.italic };
759
797
  }
760
798
  }
761
- const full = chars.join('');
762
- const drop = numberPrefix && full.startsWith(`${numberPrefix}${numberSeparator}`) ? numberPrefix.length + numberSeparator.length : 0;
763
- if (removedAt.length > 0) {
764
- // The forced breaks and the length of the title without what was left
765
- // out; a space a closing chip stood after goes too.
766
- titleBreaks = titleBreaks?.map((i) => i - removedAt.filter((c, k) => c - drop + k < i).length);
767
- titleLength -= removedAt.length;
768
- while (chars.length > drop && chars[chars.length - 1] === ' ') {
769
- chars.pop();
770
- flags.pop();
771
- titleLength--;
772
- }
773
- }
774
- const title = chars.join('').slice(drop);
775
- const titleText = applyTitleBreaks(title, titleBreaks, titleLength);
776
- if (!hasMarks)
777
- return { titleText };
778
- let tChars = chars.slice(drop);
779
- let tFlags = flags.slice(drop);
799
+ return { chars, flags, hasMarks, removedAt };
800
+ }
801
+ /** The title written as inline Markdown from its characters and their run
802
+ * flags (the number prefix dropped), when it reads back as `expected`
803
+ * with the same runs; a chip is written as `:chip[…]` (#1162). */
804
+ function markedTitle(chars, flags, titleBreaks, titleLength, title, expected) {
805
+ let tChars = chars.slice();
806
+ let tFlags = flags.slice();
780
807
  // The forced breaks, as `applyTitleBreaks` sets them: a newline at each
781
808
  // recorded index, the spaces and tabs around it dropped.
782
809
  if (titleBreaks && titleBreaks.length > 0 && title.length === titleLength) {
@@ -797,15 +824,26 @@ numberSeparator = ' ') {
797
824
  tFlags = tFlags.filter((_, i) => keep[i]);
798
825
  tChars = tChars.filter((_, i) => keep[i]);
799
826
  }
800
- if (tChars.join('') !== titleText)
801
- return { titleText };
827
+ if (tChars.join('') !== expected)
828
+ return undefined;
829
+ const chips = tFlags.some((f) => f.chip);
830
+ let failed = false;
802
831
  const scripted = (a, b) => {
803
832
  let out = '';
804
833
  let i = a;
805
834
  while (i < b) {
835
+ const chip = tFlags[i].chip;
836
+ if (chip) {
837
+ const md = chipMarkdown(chip);
838
+ if (md === undefined)
839
+ failed = true;
840
+ out += md ?? '';
841
+ i++;
842
+ continue;
843
+ }
806
844
  const script = tFlags[i].script;
807
845
  let j = i + 1;
808
- while (j < b && tFlags[j].script === script)
846
+ while (j < b && tFlags[j].script === script && !tFlags[j].chip)
809
847
  j++;
810
848
  const text = escapeTitleRun(tChars.slice(i, j).join(''));
811
849
  out += script === 'sup' ? `^${text}^` : script === 'sub' ? `~${text}~` : text;
@@ -844,23 +882,109 @@ numberSeparator = ' ') {
844
882
  }
845
883
  i = j;
846
884
  }
847
- // Kept only when it reads back as the same text with the same runs.
848
- const spans = parseInlineFormatting(marked);
849
- if (spans.map((s) => s.text).join('') !== titleText)
850
- return { titleText };
885
+ if (failed)
886
+ return undefined;
887
+ // Kept only when it reads back as the same text with the same runs (a
888
+ // chip as a chip).
889
+ let spans;
890
+ if (chips) {
891
+ const { cleaned, chips: metas } = extractInlineChips(marked, 0);
892
+ spans = injectChipSpans(parseInlineFormatting(cleaned), metas);
893
+ }
894
+ else
895
+ spans = parseInlineFormatting(marked);
896
+ if (spans.map((s) => s.text).join('') !== expected)
897
+ return undefined;
851
898
  let at = 0;
852
899
  for (const s of spans) {
853
900
  if (s.smallCaps || s.links || s.math)
854
- return { titleText };
901
+ return undefined;
855
902
  for (let k = 0; k < s.text.length; k++, at++) {
856
- if (/\s/.test(s.text[k]))
857
- continue;
858
903
  const f = tFlags[at];
904
+ if ((s.chip !== undefined) !== (f.chip !== undefined))
905
+ return undefined;
906
+ if (s.chip || /\s/.test(s.text[k]))
907
+ continue;
859
908
  if (f.bold !== !!s.bold || f.italic !== !!s.italic || f.script !== s.script)
860
- return { titleText };
909
+ return undefined;
910
+ }
911
+ }
912
+ return marked;
913
+ }
914
+ /** The title a page-span heading's default opener prints, from the lines the
915
+ * heading was measured into (see {@link DefaultOpenerTitle}). The same
916
+ * reading serves the band's measure (`build.ts`) and its paint, so the
917
+ * two lay out one text. */
918
+ export function defaultOpenerTitle(lines, numberPrefix, titleBreaks, titleLength,
919
+ /** What joins the number to the title in the lines
920
+ * (`HeadingLevelConfig.numberSeparator`). */
921
+ numberSeparator = ' ') {
922
+ const { chars, flags, hasMarks, removedAt } = readTitleChars(lines, false);
923
+ const keptBreaks = titleBreaks;
924
+ const keptLength = titleLength;
925
+ const full = chars.join('');
926
+ const drop = numberPrefix && full.startsWith(`${numberPrefix}${numberSeparator}`) ? numberPrefix.length + numberSeparator.length : 0;
927
+ if (removedAt.length > 0) {
928
+ // The forced breaks and the length of the title without what was left
929
+ // out; a space a closing chip stood after goes too.
930
+ titleBreaks = titleBreaks?.map((i) => i - removedAt.filter((c, k) => c - drop + k < i).length);
931
+ titleLength -= removedAt.length;
932
+ while (chars.length > drop && chars[chars.length - 1] === ' ') {
933
+ chars.pop();
934
+ flags.pop();
935
+ titleLength--;
861
936
  }
937
+ // A chip alone on the first line leaves the space its line was
938
+ // broken at (#1162).
939
+ while (chars.length > drop && chars[drop] === ' ') {
940
+ chars.splice(drop, 1);
941
+ flags.splice(drop, 1);
942
+ titleLength--;
943
+ titleBreaks = titleBreaks?.map((i) => Math.max(0, i - 1));
944
+ }
945
+ }
946
+ const title = chars.join('').slice(drop);
947
+ const titleText = applyTitleBreaks(title, titleBreaks, titleLength);
948
+ // The heading's chips (#1162): the title with them, written as Markdown
949
+ // a design text that reads inline marks sets them from. The plain title
950
+ // leaves them out.
951
+ if (removedAt.length > 0) {
952
+ const marked = markedTitleWithChips(lines, numberPrefix, numberSeparator, keptBreaks, keptLength, titleText);
953
+ if (marked !== undefined)
954
+ return { titleText, marked, chips: true };
862
955
  }
863
- return { titleText, marked };
956
+ if (!hasMarks)
957
+ return { titleText };
958
+ const marked = markedTitle(chars.slice(drop), flags.slice(drop), titleBreaks, titleLength, title, titleText);
959
+ return marked === undefined ? { titleText } : { titleText, marked };
960
+ }
961
+ /** {@link DefaultOpenerTitle.marked} of a heading with chips (#1162): its
962
+ * lines read back with each chip kept, the title written with them, when
963
+ * it reads as `titleText` once its chips are left out again. */
964
+ function markedTitleWithChips(lines, numberPrefix, numberSeparator, titleBreaks, titleLength, titleText) {
965
+ const kept = readTitleChars(lines, true);
966
+ if (!kept)
967
+ return undefined;
968
+ const full = kept.chars.join('');
969
+ const drop = numberPrefix && full.startsWith(`${numberPrefix}${numberSeparator}`) ? numberPrefix.length + numberSeparator.length : 0;
970
+ const title = full.slice(drop);
971
+ const withChips = applyTitleBreaks(title, titleBreaks, titleLength);
972
+ if (withoutChips(withChips) !== titleText)
973
+ return undefined;
974
+ // Text written as `:chip[…]` in a code span would read as a chip.
975
+ if (titleText.includes(':chip['))
976
+ return undefined;
977
+ return markedTitle(kept.chars.slice(drop), kept.flags.slice(drop), titleBreaks, titleLength, title, withChips);
978
+ }
979
+ /** The marked title of a heading placeholder (`titleMarked`), with the
980
+ * document's chip styles when it holds the heading's chips (#1162). A
981
+ * title with chips and no styles to set them in is left plain. */
982
+ export function markedTitleFields(title, chipStyles) {
983
+ if (title.marked === undefined)
984
+ return {};
985
+ if (!title.chips)
986
+ return { titleMarked: title.marked };
987
+ return chipStyles ? { titleMarked: title.marked, chipStyles } : {};
864
988
  }
865
989
  /** The height of the title a page-span heading's default opener paints
866
990
  * across `width`, with the opener's own painter (a greedy wrap at natural
@@ -869,9 +993,11 @@ numberSeparator = ' ') {
869
993
  * even where the heading's own measure fits more on a line: a justified
870
994
  * line whose spaces shrink, a forced break, a run the opener sets plain
871
995
  * (EF-100). */
872
- export function measureDefaultOpenerHeight(level, textAlign, title, numberPrefix, width, dpi, metadata, pageIndex) {
996
+ export function measureDefaultOpenerHeight(level, textAlign, title, numberPrefix, width, dpi, metadata, pageIndex,
997
+ /** The document's chip styles, for a title with chips (#1162). */
998
+ chipStyles) {
873
999
  const slot = synthesiseDefaultOpenerSlot(level, numberPrefix.length > 0, textAlign, title.marked !== undefined);
874
- const placeholders = headingPlaceholders({ titleText: title.titleText, ...(title.marked !== undefined ? { titleMarked: title.marked } : {}), formattedNumber: numberPrefix }, metadata, pageIndex);
1000
+ const placeholders = headingPlaceholders({ titleText: title.titleText, ...markedTitleFields(title, chipStyles), formattedNumber: numberPrefix }, metadata, pageIndex);
875
1001
  const result = layoutDesignSlot(slot, { container: { x: 0, y: 0, width, height: MEASURE_CONTAINER_HEIGHT }, dpi, placeholders }, pageIndex);
876
1002
  let bottom = 0;
877
1003
  for (const prim of result.primitives) {
@@ -1033,6 +1159,15 @@ export function framesInFrame(frames, frame) {
1033
1159
  * (#1057), per document, as `labelAnchorUnknown` warnings with the
1034
1160
  * connector's id (read by {@link connectorAnchorUnknownWarnings}). */
1035
1161
  const connectorAnchorWarnings = new WeakMap();
1162
+ /** Per document, the furniture each composed page's running heads and
1163
+ * decoration meant to draw (#1163), read by {@link pageFurnitureWarnings}. */
1164
+ const pageFurnitureByDoc = new WeakMap();
1165
+ /** The `folioMissing` warnings of `doc` (#1163): a composed page that draws
1166
+ * no folio or running head most pages of its template and side draw. */
1167
+ export function pageFurnitureWarnings(doc) {
1168
+ const records = pageFurnitureByDoc.get(doc);
1169
+ return records && records.length > 0 ? folioMissingWarnings(doc.pages, records) : [];
1170
+ }
1036
1171
  /** The `labelAnchorUnknown` warnings of the connectors of `doc`'s composed
1037
1172
  * pages (#1057): an end set at an anchor no picture of its frame has. */
1038
1173
  export function connectorAnchorUnknownWarnings(doc) {
@@ -1100,6 +1235,9 @@ function layoutHeadersAndFooters(doc, resourceById, inputs) {
1100
1235
  // Parity (odd/even elements) counts the pages before a continued document.
1101
1236
  const pageIndexOffset = doc.pageIndexOffset ?? 0;
1102
1237
  const chapterAttrsByPageIndex = computeChapterAttrs(doc.blocks, doc.pages.length, doc.pages);
1238
+ /** Per composed page, the furniture its slots mean to draw (#1163). */
1239
+ const furnitureRecords = [];
1240
+ pageFurnitureByDoc.set(doc, furnitureRecords);
1103
1241
  // `{bookTotalPages}`: the host's count of the whole book, else the pages
1104
1242
  // up to the end of this document.
1105
1243
  const bookTotalPages = doc.bookPageCount ?? pageIndexOffset + doc.pages.length;
@@ -1350,7 +1488,7 @@ function layoutHeadersAndFooters(doc, resourceById, inputs) {
1350
1488
  partPaletteByPageIndex,
1351
1489
  heading: {
1352
1490
  titleText: opener.title.titleText,
1353
- ...(marked !== undefined ? { titleMarked: marked } : {}),
1491
+ ...markedTitleFields(opener.title, resolved.chipStyles),
1354
1492
  formattedNumber: opener.numberPrefix,
1355
1493
  numericValue: opener.block.headingNumber,
1356
1494
  locale,
@@ -1406,9 +1544,10 @@ function layoutHeadersAndFooters(doc, resourceById, inputs) {
1406
1544
  // The lines joined back into the title (EF-162), with a forced break
1407
1545
  // (`\\`) as a newline, as the block's height was measured
1408
1546
  // (`build.ts`) and as an opener prints it (EF-152).
1409
- const { titleText: title, marked } = own
1547
+ const read = own
1410
1548
  ? defaultOpenerTitle(block.lines, pref, own.titleBreaks, own.titleLength ?? -1, block.numberSeparator)
1411
1549
  : defaultOpenerTitle(block.lines, pref, block.titleBreaks, block.titleLength ?? -1, block.numberSeparator);
1550
+ const { titleText: title, marked } = read;
1412
1551
  const placeholders = {
1413
1552
  kind: 'heading',
1414
1553
  page,
@@ -1425,7 +1564,7 @@ function layoutHeadersAndFooters(doc, resourceById, inputs) {
1425
1564
  partPaletteByPageIndex,
1426
1565
  heading: {
1427
1566
  titleText: title,
1428
- ...(marked !== undefined ? { titleMarked: marked } : {}),
1567
+ ...markedTitleFields(read, resolved.chipStyles),
1429
1568
  formattedNumber: pref,
1430
1569
  numericValue: block.headingNumber,
1431
1570
  locale,
@@ -1545,6 +1684,21 @@ function layoutHeadersAndFooters(doc, resourceById, inputs) {
1545
1684
  };
1546
1685
  page.footer = asFurniture(withMeasureWritingMode('horizontal-tb', () => layoutSlotToVdt(footerSlot, footerContainerBbox(sheetArea, pageMetrics.physical.trimBox), page.index + pageIndexOffset, placeholders, dpi, { ...headExtras, slot: 'footer', ...withPath(slotPath('footer')) })));
1547
1686
  }
1687
+ // What the page's running heads and decoration mean to draw on it, for
1688
+ // `folioMissing` (#1163). A spread's decoration is laid on its sheet of
1689
+ // two pages, so only its running heads are compared.
1690
+ if (page.composed && template) {
1691
+ const role = page.composed.role ?? page.role;
1692
+ const at = page.index + pageIndexOffset;
1693
+ const expected = [
1694
+ ...(furniture ? expectedFurniture(headerSlot.elements, 'header', at, role, pageAttrs) : []),
1695
+ ...(furniture ? expectedFurniture(footerSlot.elements, 'footer', at, role, pageAttrs) : []),
1696
+ ...(page.composed.spread === undefined && template.decoration.elements.some((el) => el.kind === 'text')
1697
+ ? expectedFurniture(resolveDesignSlot(template.decoration, 'header').elements, 'decoration', at, role, pageAttrs)
1698
+ : []),
1699
+ ];
1700
+ furnitureRecords.push({ pageIndex: page.index, template: page.composed.template, side: recto ? 'recto' : 'verso', expected });
1701
+ }
1548
1702
  // The template's decoration, laid with the frames' final boxes (its
1549
1703
  // elements may anchor to `'#frame'`), on the sheet like the running
1550
1704
  // heads; painted under the frames of layer 1 and up. A spread's (#861)