@stll/folio-core 0.47.7 → 0.48.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 (160) hide show
  1. package/dist/controller/fontReadiness.js +3 -3
  2. package/dist/controller/hyphenationReadiness.d.ts +19 -0
  3. package/dist/controller/hyphenationReadiness.js +47 -0
  4. package/dist/controller/layoutPipeline.d.ts +3 -0
  5. package/dist/controller/layoutPipeline.js +59 -17
  6. package/dist/display-list/build/buildDisplayList.js +39 -12
  7. package/dist/display-list/build/pageFurniture.d.ts +7 -1
  8. package/dist/display-list/build/pageFurniture.js +21 -8
  9. package/dist/display-list/build/paragraphPrimitives.js +5 -4
  10. package/dist/display-list/build/tablePrimitives.js +10 -6
  11. package/dist/docx/attributeRemainder.js +10 -2
  12. package/dist/docx/blockContentParser.d.ts +2 -0
  13. package/dist/docx/blockContentParser.js +77 -62
  14. package/dist/docx/commentParser.d.ts +2 -1
  15. package/dist/docx/commentParser.js +57 -45
  16. package/dist/docx/containerChildren.d.ts +21 -8
  17. package/dist/docx/containerChildren.js +39 -9
  18. package/dist/docx/documentParser.d.ts +5 -1
  19. package/dist/docx/documentParser.js +8 -5
  20. package/dist/docx/documentSectionFacts.d.ts +22 -0
  21. package/dist/docx/documentSectionFacts.js +43 -0
  22. package/dist/docx/fieldParser.d.ts +8 -1
  23. package/dist/docx/fieldParser.js +47 -3
  24. package/dist/docx/fontTableParser.js +34 -27
  25. package/dist/docx/footnoteParser.d.ts +3 -2
  26. package/dist/docx/footnoteParser.js +15 -12
  27. package/dist/docx/groupDrawingParser.d.ts +2 -1
  28. package/dist/docx/groupDrawingParser.js +3 -6
  29. package/dist/docx/headerFooterParser.d.ts +7 -2
  30. package/dist/docx/headerFooterParser.js +15 -5
  31. package/dist/docx/hyperlinkParser.d.ts +48 -36
  32. package/dist/docx/hyperlinkParser.js +97 -82
  33. package/dist/docx/numberingParser.js +108 -84
  34. package/dist/docx/paragraphParser.d.ts +5 -1
  35. package/dist/docx/paragraphParser.js +381 -362
  36. package/dist/docx/paragraphProperties.js +123 -109
  37. package/dist/docx/paragraphPropertySource.d.ts +6 -1
  38. package/dist/docx/paragraphPropertySource.js +9 -1
  39. package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
  40. package/dist/docx/paragraphTextBoxEnrichment.js +17 -6
  41. package/dist/docx/parser.d.ts +7 -1
  42. package/dist/docx/parser.js +33 -16
  43. package/dist/docx/previewBudget.d.ts +55 -33
  44. package/dist/docx/previewBudget.js +59 -65
  45. package/dist/docx/rezip.js +41 -58
  46. package/dist/docx/runParser.d.ts +4 -1
  47. package/dist/docx/runParser.js +172 -184
  48. package/dist/docx/sdtProperties.js +87 -84
  49. package/dist/docx/sectionParser.js +169 -160
  50. package/dist/docx/serializer/paragraphSerializer.js +42 -38
  51. package/dist/docx/serializer/settingsSerializer.js +19 -1
  52. package/dist/docx/settingsParser.js +8 -0
  53. package/dist/docx/streamingXmlParser.d.ts +6 -2
  54. package/dist/docx/streamingXmlParser.js +12 -7
  55. package/dist/docx/styleParser.js +34 -8
  56. package/dist/docx/tableParser.d.ts +21 -12
  57. package/dist/docx/tableParser.js +518 -441
  58. package/dist/docx/textBoxParser.d.ts +8 -3
  59. package/dist/docx/textBoxParser.js +3 -3
  60. package/dist/docx/verbatimCapture.d.ts +10 -8
  61. package/dist/docx/verbatimCapture.js +45 -2
  62. package/dist/docx/vmlImageParser.d.ts +15 -13
  63. package/dist/docx/vmlImageParser.js +75 -60
  64. package/dist/docx/vmlPreview.d.ts +3 -3
  65. package/dist/docx/vmlPreview.js +3 -4
  66. package/dist/docx/xmlNamespaceContext.d.ts +9 -0
  67. package/dist/docx/xmlNamespaceContext.js +54 -0
  68. package/dist/docx/xmlParser.d.ts +6 -3
  69. package/dist/docx/xmlParser.js +11 -41
  70. package/dist/docx/xmlResourceLimits.d.ts +2 -1
  71. package/dist/docx/xmlResourceLimits.js +4 -1
  72. package/dist/fonts/headlessMeasure.js +0 -0
  73. package/dist/headless-layout.d.ts +11 -2
  74. package/dist/headless-layout.js +153 -107
  75. package/dist/internal/paragraphFormattingSerialization.js +3 -3
  76. package/dist/layout-bridge/convert/endnoteLayout.d.ts +65 -0
  77. package/dist/layout-bridge/convert/endnoteLayout.js +227 -0
  78. package/dist/layout-bridge/convert/footnoteLayout.d.ts +19 -3
  79. package/dist/layout-bridge/convert/footnoteLayout.js +40 -31
  80. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -7
  81. package/dist/layout-bridge/convert/headerFooterLayout.js +5 -20
  82. package/dist/layout-bridge/convert/paragraphMarkFormatting.d.ts +10 -0
  83. package/dist/layout-bridge/convert/paragraphMarkFormatting.js +59 -0
  84. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +29 -1
  85. package/dist/layout-bridge/convert/toFlowBlocks.js +258 -35
  86. package/dist/layout-bridge/engine/hitTest.js +5 -3
  87. package/dist/layout-bridge/engine/selectionRects.js +5 -3
  88. package/dist/layout-engine/anchorLayoutInCellCompatibility.d.ts +10 -0
  89. package/dist/layout-engine/anchorLayoutInCellCompatibility.js +22 -0
  90. package/dist/layout-engine/index.d.ts +2 -2
  91. package/dist/layout-engine/index.js +70 -11
  92. package/dist/layout-engine/justificationCompatibility.d.ts +1 -1
  93. package/dist/layout-engine/justificationCompatibility.js +9 -5
  94. package/dist/layout-engine/keep-together.d.ts +11 -5
  95. package/dist/layout-engine/keep-together.js +39 -16
  96. package/dist/layout-engine/layoutInstrumentation.d.ts +8 -2
  97. package/dist/layout-engine/layoutInstrumentation.js +8 -1
  98. package/dist/layout-engine/measure/advanceComposition.js +4 -7
  99. package/dist/layout-engine/measure/hyphenationDictionaries.d.ts +74 -0
  100. package/dist/layout-engine/measure/hyphenationDictionaries.js +138 -0
  101. package/dist/layout-engine/measure/hyphenationPreload.d.ts +11 -0
  102. package/dist/layout-engine/measure/hyphenationPreload.js +24 -0
  103. package/dist/layout-engine/measure/lineBreakProvider.d.ts +12 -2
  104. package/dist/layout-engine/measure/lineBreakProvider.js +30 -30
  105. package/dist/layout-engine/measure/lineBreaks.d.ts +6 -1
  106. package/dist/layout-engine/measure/lineBreaks.js +10 -3
  107. package/dist/layout-engine/measure/listMarkerWidth.d.ts +9 -6
  108. package/dist/layout-engine/measure/listMarkerWidth.js +20 -22
  109. package/dist/layout-engine/measure/measureBlocks.js +42 -8
  110. package/dist/layout-engine/measure/measureContainer.js +5 -5
  111. package/dist/layout-engine/measure/measureHelpers.d.ts +15 -3
  112. package/dist/layout-engine/measure/measureHelpers.js +21 -5
  113. package/dist/layout-engine/measure/measureParagraph.js +109 -30
  114. package/dist/layout-engine/measure/measureTypes.d.ts +5 -0
  115. package/dist/layout-engine/measure/tabCalculator.d.ts +2 -0
  116. package/dist/layout-engine/measure/tabCalculator.js +2 -1
  117. package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
  118. package/dist/layout-engine/measure/tableCellFloating.js +51 -19
  119. package/dist/layout-engine/measure/tableCellGrid.d.ts +28 -3
  120. package/dist/layout-engine/measure/tableCellGrid.js +45 -5
  121. package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +2 -1
  122. package/dist/layout-engine/noteAreaFlow.d.ts +19 -0
  123. package/dist/layout-engine/noteAreaFlow.js +83 -0
  124. package/dist/layout-engine/paginator.d.ts +10 -1
  125. package/dist/layout-engine/paginator.js +13 -1
  126. package/dist/layout-engine/tableRowBreak.js +3 -1
  127. package/dist/layout-engine/types.d.ts +89 -3
  128. package/dist/layout-engine/types.js +96 -1
  129. package/dist/layout-painter/index.d.ts +3 -0
  130. package/dist/layout-painter/renderPage.js +32 -83
  131. package/dist/layout-painter/renderParagraph.d.ts +6 -1
  132. package/dist/layout-painter/renderParagraph.js +34 -22
  133. package/dist/layout-painter/renderTable.js +26 -13
  134. package/dist/prosemirror/attrs/index.d.ts +9 -9
  135. package/dist/prosemirror/attrs/index.js +43 -11
  136. package/dist/prosemirror/conversion/fromProseDoc.js +8 -1
  137. package/dist/prosemirror/conversion/markInterner.d.ts +21 -0
  138. package/dist/prosemirror/conversion/markInterner.js +90 -0
  139. package/dist/prosemirror/conversion/toProseDoc.js +77 -41
  140. package/dist/prosemirror/extensions/core/DocExtension.js +2 -1
  141. package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
  142. package/dist/prosemirror/extensions/marks/markUtils.d.ts +6 -2
  143. package/dist/prosemirror/extensions/marks/markUtils.js +39 -51
  144. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +2 -1
  145. package/dist/prosemirror/listMarker.d.ts +1 -0
  146. package/dist/prosemirror/listMarker.js +2 -0
  147. package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
  148. package/dist/prosemirror/listRenderingAttrs.js +3 -0
  149. package/dist/prosemirror/schema/nodes.d.ts +13 -1
  150. package/dist/prosemirror/textBoxHostParagraph.d.ts +13 -0
  151. package/dist/prosemirror/textBoxHostParagraph.js +21 -0
  152. package/dist/prosemirror/validation.js +91 -80
  153. package/dist/utils/createDocument.js +10 -1
  154. package/dist/utils/scriptSegments.d.ts +33 -4
  155. package/dist/utils/scriptSegments.js +39 -6
  156. package/dist/utils/textFormattingMerge.d.ts +17 -3
  157. package/dist/utils/textFormattingMerge.js +18 -12
  158. package/dist/utils/trailingText.d.ts +22 -0
  159. package/dist/utils/trailingText.js +32 -0
  160. package/package.json +2 -2
@@ -17,6 +17,7 @@ import { directionIsRtl, directionToBidi } from "../../prosemirror/paragraphDire
17
17
  import { resolveEffectiveRunStyleFormatting } from "../../prosemirror/runStyleFormatting.js";
18
18
  import { parseSectionBreakType } from "../../prosemirror/sectionCarrier.js";
19
19
  import { expectTextBoxAnchorAttrs } from "../../prosemirror/textBoxAnchorAttrs.js";
20
+ import { textBoxHostParagraph } from "../../prosemirror/textBoxHostParagraph.js";
20
21
  import { assertValidProseMirrorDocument } from "../../prosemirror/validation.js";
21
22
  import { createStyleEngine } from "../../style-engine/styleEngine.js";
22
23
  import { normalizeShapeTextAnchor } from "../../types/documentEnumValues.js";
@@ -26,12 +27,14 @@ import { resolveThemeFont } from "../../utils/fontResolver.js";
26
27
  import { resolveShadingFill } from "../../utils/formatToStyle.js";
27
28
  import { normalizeHorizontalScalePercent } from "../../utils/horizontalScale.js";
28
29
  import { decodeOoxmlSymbolCharacter } from "../../utils/ooxmlSymbol.js";
30
+ import { mergeParagraphTabStops } from "../../utils/paragraphFormattingMerge.js";
29
31
  import { sanitizeImageSrc } from "../../utils/sanitizeImageSrc.js";
30
32
  import { tableOfContentsStyleLevel } from "../../utils/tableOfContentsStyle.js";
31
- import { STYLE_TOGGLE_KEYS } from "../../utils/textFormattingMerge.js";
33
+ import { STYLE_TOGGLE_KEYS, mergeTextFormatting } from "../../utils/textFormattingMerge.js";
32
34
  import { AUTO_PARAGRAPH_SPACING_PX, halfPointsToPixels, halfPointsToPoints, pointsToPixels } from "../../utils/units.js";
33
35
  import { getColumns } from "../sectionColumns.js";
34
36
  import { groupParagraphFrames } from "./paragraphFrames.js";
37
+ import { resolveParagraphMarkFormatting, resolveParagraphMarkOwnFormatting } from "./paragraphMarkFormatting.js";
35
38
  import { panic } from "better-result";
36
39
  import { headingLevelOf, paragraphNumberingLevel, paragraphNumberingReferenceId, sameStatedParagraphNumbering, statesNoBorder } from "@stll/docx-core/model";
37
40
  //#region src/layout-bridge/convert/toFlowBlocks.ts
@@ -42,6 +45,32 @@ const expectDetachedWatermarkHostAttr = (attrs) => {
42
45
  if (typeof value !== "boolean") panic("Invalid ProseMirror detached watermark host attrs:\nparagraph.attrs._detachedWatermarkHost: Expected a boolean.");
43
46
  return value;
44
47
  };
48
+ const NOTE_REFERENCE_MARK_XML = /^<(?:[^\s:/>]+:)?(?:footnoteRef|endnoteRef)[\s/>]/u;
49
+ /**
50
+ * Runs with `idKey` set, in document order: the note references a block list
51
+ * makes, through table cells (and tables within cells) and text boxes.
52
+ */
53
+ function collectNoteRefs(blocks, idKey) {
54
+ const refs = [];
55
+ const walk = (containerBlocks) => {
56
+ for (const block of containerBlocks) if (block.kind === "paragraph") for (const run of block.runs) {
57
+ if (run.kind !== "text") continue;
58
+ const noteId = run[idKey];
59
+ if (noteId !== void 0) refs.push({
60
+ noteId,
61
+ pmPos: run.pmStart ?? 0
62
+ });
63
+ }
64
+ else if (block.kind === "table") for (const row of block.rows) for (const cell of row.cells) walk(cell.blocks);
65
+ else if (block.kind === "textBox") walk(block.content);
66
+ };
67
+ walk(blocks);
68
+ return refs;
69
+ }
70
+ /** Whether captured run-child markup is a note story's `w:footnoteRef`/`w:endnoteRef`. */
71
+ function isNoteReferenceMarkXml(xml) {
72
+ return NOTE_REFERENCE_MARK_XML.test(xml);
73
+ }
45
74
  /**
46
75
  * Word's layout fallback when an imported OOXML style hierarchy never supplies
47
76
  * `w:sz`. ECMA-376 deliberately leaves that terminal fallback to the consumer;
@@ -256,6 +285,7 @@ function extractRunFormatting(marks, theme, fontAlternates) {
256
285
  const alternate = getFontAlternate(eastAsiaFont, fontAlternates);
257
286
  if (alternate) formatting.eastAsiaAlternateFontFamily = alternate;
258
287
  }
288
+ if (attrs.hint) formatting.eastAsiaHint = attrs.hint === "eastAsia";
259
289
  const complexScriptFont = resolveComplexScriptThemeFont(attrs, theme);
260
290
  if (complexScriptFont) {
261
291
  formatting.complexScriptFontFamily = complexScriptFont;
@@ -564,6 +594,7 @@ function textFormattingToRunFormatting(defaultTextFormatting, theme, fontAlterna
564
594
  const alternate = getFontAlternate(eastAsiaFontFamily, fontAlternates);
565
595
  if (alternate) result.eastAsiaAlternateFontFamily = alternate;
566
596
  }
597
+ if (defaultTextFormatting.fontFamily?.hint) result.eastAsiaHint = defaultTextFormatting.fontFamily.hint === "eastAsia";
567
598
  const complexScriptFontFamily = defaultTextFormatting.fontFamily ? resolveComplexScriptThemeFont(defaultTextFormatting.fontFamily, theme) : void 0;
568
599
  if (complexScriptFontFamily) {
569
600
  result.complexScriptFontFamily = complexScriptFontFamily;
@@ -608,6 +639,34 @@ function textFormattingToRunFormatting(defaultTextFormatting, theme, fontAlterna
608
639
  if (defaultTextFormatting.emphasisMark && defaultTextFormatting.emphasisMark !== "none") result.emphasisMark = defaultTextFormatting.emphasisMark;
609
640
  return result;
610
641
  }
642
+ const LIST_MARKER_FORMATTING_KEYS = [
643
+ "fontFamily",
644
+ "alternateFontFamily",
645
+ "eastAsiaFontFamily",
646
+ "eastAsiaAlternateFontFamily",
647
+ "complexScriptFontFamily",
648
+ "complexScriptAlternateFontFamily",
649
+ "fontSize",
650
+ "complexScriptFontSize",
651
+ "bold",
652
+ "complexScriptBold",
653
+ "italic",
654
+ "complexScriptItalic",
655
+ "rtl",
656
+ "forceComplexScript",
657
+ "color"
658
+ ];
659
+ /**
660
+ * The numbering symbol's typography: the numbering level's `w:rPr` applied
661
+ * over the paragraph mark's run properties (ECMA-376 §17.9.24, §17.3.1.29).
662
+ * Text runs of the paragraph play no part.
663
+ */
664
+ function listMarkerFormattingFor(levelFormatting, paragraphMarkFormatting, theme, fontAlternates) {
665
+ const runFormatting = textFormattingToRunFormatting(mergeTextFormatting(paragraphMarkFormatting, levelFormatting), theme, fontAlternates);
666
+ const formatting = {};
667
+ for (const key of LIST_MARKER_FORMATTING_KEYS) if (runFormatting[key] !== void 0) Reflect.set(formatting, key, runFormatting[key]);
668
+ return Object.keys(formatting).length > 0 ? formatting : void 0;
669
+ }
611
670
  function paragraphRunDefaults(pmAttrs, theme, fontAlternates) {
612
671
  return textFormattingToRunFormatting(pmAttrs.defaultTextFormatting, theme, fontAlternates);
613
672
  }
@@ -615,7 +674,7 @@ function paragraphRunDefaults(pmAttrs, theme, fontAlternates) {
615
674
  * Build an ImageRun from ProseMirror node attrs, applying conditional property assignment
616
675
  * to satisfy exactOptionalPropertyTypes.
617
676
  */
618
- function buildImageRun(attrs, constrained, pmStart, pmEnd, trackedChange) {
677
+ function buildImageRun(attrs, constrained, pmStart, pmEnd, trackedChange, forceLayoutInCell) {
619
678
  const run = {
620
679
  kind: "image",
621
680
  src: attrs.src,
@@ -647,7 +706,8 @@ function buildImageRun(attrs, constrained, pmStart, pmEnd, trackedChange) {
647
706
  const runBorderStyle = cssBorderStyleOf(attrs.borderStyle);
648
707
  if (runBorderStyle) run.borderStyle = runBorderStyle;
649
708
  if (attrs.position !== void 0) run.position = attrs.position;
650
- if (attrs.anchor?.layoutInCell !== void 0) run.layoutInCell = attrs.anchor.layoutInCell;
709
+ if (forceLayoutInCell) run.layoutInCell = true;
710
+ else if (attrs.anchor?.layoutInCell !== void 0) run.layoutInCell = attrs.anchor.layoutInCell;
651
711
  if (trackedChange?.isInsertion) run.isInsertion = true;
652
712
  if (trackedChange?.isDeletion) run.isDeletion = true;
653
713
  if (trackedChange?.changeAuthor !== void 0) run.changeAuthor = trackedChange.changeAuthor;
@@ -790,7 +850,8 @@ function paragraphToRuns(node, startPos, _options, pageBreaks) {
790
850
  return;
791
851
  }
792
852
  if (child.type.name === "preservedXml") {
793
- const { text } = expectPreservedXmlAttrs(child);
853
+ const { xml, text: capturedText } = expectPreservedXmlAttrs(child);
854
+ const text = capturedText === "" && _options.noteReferenceMarkText !== void 0 && isNoteReferenceMarkXml(xml) ? _options.noteReferenceMarkText : capturedText;
794
855
  if (text === "") return;
795
856
  const formatting = extractRunFormatting(effectiveMarks, theme, fontAlternates);
796
857
  applyCharacterStyleToggleFormatting({
@@ -838,7 +899,7 @@ function paragraphToRuns(node, startPos, _options, pageBreaks) {
838
899
  if (!hasPaintableImageSource(attrs) && !hasRelationshipBackedImageBox(attrs)) return;
839
900
  const constrained = constrainImageToPage(attrs.width ?? 100, attrs.height ?? 100, _options.pageContentHeight);
840
901
  const trackedFmt = extractRunFormatting(effectiveMarks, theme, fontAlternates);
841
- const run = buildImageRun(attrs, constrained, childPos, childPos + child.nodeSize, trackedFmt);
902
+ const run = buildImageRun(attrs, constrained, childPos, childPos + child.nodeSize, trackedFmt, _options.forceAnchorLayoutInCell);
842
903
  runs.push(run);
843
904
  return;
844
905
  }
@@ -996,7 +1057,7 @@ function resolveDeletedListMarker(previousListAttrs, listCounterState) {
996
1057
  if (previousListAttrs.listIsBullet) return "•";
997
1058
  return null;
998
1059
  }
999
- function applyDeletedListMarkerAttrs(attrs, change, listCounterState, theme, fontAlternates) {
1060
+ function applyDeletedListMarkerAttrs(attrs, change, listCounterState, theme, fontAlternates, paragraphMarkFormatting) {
1000
1061
  const previousListAttrs = toPreviousListAttrs(change.previousFormatting);
1001
1062
  const marker = resolveDeletedListMarker(previousListAttrs, listCounterState);
1002
1063
  if (!marker) return;
@@ -1004,7 +1065,8 @@ function applyDeletedListMarkerAttrs(attrs, change, listCounterState, theme, fon
1004
1065
  attrs.listMarkerRevision = toListMarkerRevision("del", change.info);
1005
1066
  if (previousListAttrs.listIsBullet !== void 0) attrs.listIsBullet = previousListAttrs.listIsBullet;
1006
1067
  if (previousListAttrs.listMarkerHidden !== void 0) attrs.listMarkerHidden = previousListAttrs.listMarkerHidden;
1007
- if (previousListAttrs.listMarkerFormatting) attrs.listMarkerFormatting = textFormattingToRunFormatting(previousListAttrs.listMarkerFormatting, theme, fontAlternates);
1068
+ const listMarkerFormatting = listMarkerFormattingFor(previousListAttrs.listMarkerFormatting, paragraphMarkFormatting(), theme, fontAlternates);
1069
+ if (listMarkerFormatting) attrs.listMarkerFormatting = listMarkerFormatting;
1008
1070
  if (previousListAttrs.listMarkerAlignment) attrs.listMarkerAlignment = previousListAttrs.listMarkerAlignment;
1009
1071
  if (previousListAttrs.listMarkerSuffix) attrs.listMarkerSuffix = previousListAttrs.listMarkerSuffix;
1010
1072
  }
@@ -1041,7 +1103,7 @@ const resolveFlowAlignment = (alignment, rightToLeft) => {
1041
1103
  default: return flow;
1042
1104
  }
1043
1105
  };
1044
- function convertParagraphAttrs(pmAttrs, { theme, fontAlternates, listCounterStreams, defaultTabStopTwips }) {
1106
+ function convertParagraphAttrs(pmAttrs, { theme, fontAlternates, listCounterStreams, defaultTabStopTwips, paragraphMarkFormatting }) {
1045
1107
  const attrs = {};
1046
1108
  if (pmAttrs.alignment) attrs.alignment = resolveFlowAlignment(pmAttrs.alignment, directionIsRtl(pmAttrs.direction));
1047
1109
  if (pmAttrs.outlineLevel !== void 0) attrs.outlineLevel = pmAttrs.outlineLevel;
@@ -1116,9 +1178,10 @@ function convertParagraphAttrs(pmAttrs, { theme, fontAlternates, listCounterStre
1116
1178
  }
1117
1179
  const shadingRgb = pmAttrs.shading?.fill?.rgb?.toUpperCase();
1118
1180
  if (shadingRgb && shadingRgb !== "FFFFFF" && shadingRgb !== "FFFFFE") attrs.shading = `#${pmAttrs.shading?.fill?.rgb}`;
1119
- if (pmAttrs.tabs && pmAttrs.tabs.length > 0) {
1181
+ const tabs = mergeParagraphTabStops(pmAttrs.listLevelTabs ?? void 0, pmAttrs.tabs ?? void 0);
1182
+ if (tabs && tabs.length > 0) {
1120
1183
  const rightToLeft = directionIsRtl(pmAttrs.direction);
1121
- attrs.tabs = pmAttrs.tabs.map((tab) => {
1184
+ attrs.tabs = tabs.map((tab) => {
1122
1185
  const tabStop = {
1123
1186
  val: resolveTabAlignment(tab.alignment, rightToLeft),
1124
1187
  pos: tab.position
@@ -1159,14 +1222,17 @@ function convertParagraphAttrs(pmAttrs, { theme, fontAlternates, listCounterStre
1159
1222
  else if (pmAttrs.listMarker) attrs.listMarker = pmAttrs.listIsBullet ? convertBulletToUnicode(pmAttrs.listMarker) : pmAttrs.listMarker;
1160
1223
  if (pmAttrs.listIsBullet !== void 0) attrs.listIsBullet = pmAttrs.listIsBullet;
1161
1224
  if (pmAttrs.listMarkerHidden) attrs.listMarkerHidden = true;
1162
- if (pmAttrs.listMarkerFormatting) attrs.listMarkerFormatting = textFormattingToRunFormatting(pmAttrs.listMarkerFormatting, theme, fontAlternates);
1225
+ if (attrs.listMarker !== void 0 || pmAttrs.listMarkerFormatting) {
1226
+ const listMarkerFormatting = listMarkerFormattingFor(pmAttrs.listMarkerFormatting, paragraphMarkFormatting(), theme, fontAlternates);
1227
+ if (listMarkerFormatting) attrs.listMarkerFormatting = listMarkerFormatting;
1228
+ }
1163
1229
  if (pmAttrs.listMarkerAlignment) attrs.listMarkerAlignment = pmAttrs.listMarkerAlignment;
1164
1230
  if (pmAttrs.listMarkerSuffix) attrs.listMarkerSuffix = pmAttrs.listMarkerSuffix;
1165
1231
  if (pmAttrs.listMarkerSecondSlotOffsetTwips !== void 0) attrs.listMarkerSecondSlotOffsetTwips = pmAttrs.listMarkerSecondSlotOffsetTwips;
1166
1232
  if (!pmAttrs.numPr) {
1167
1233
  const numberingRemovedChange = propertyChanges.find(isRemovedNumberingChange);
1168
1234
  if (numberingRemovedChange) {
1169
- applyDeletedListMarkerAttrs(attrs, numberingRemovedChange, void 0, theme, fontAlternates);
1235
+ applyDeletedListMarkerAttrs(attrs, numberingRemovedChange, void 0, theme, fontAlternates, paragraphMarkFormatting);
1170
1236
  if (resolvedMarker !== null) {
1171
1237
  attrs.listMarker = resolvedMarker;
1172
1238
  attrs.listMarkerRevision = toListMarkerRevision("del", numberingRemovedChange.info);
@@ -1231,6 +1297,18 @@ function hasOnlyHiddenTextRuns(runs) {
1231
1297
  function hasVisibleParagraphPayload(attrs) {
1232
1298
  return attrs.listMarker !== void 0 && !attrs.listMarkerHidden || attrs.borders?.top !== void 0 || attrs.borders?.bottom !== void 0 || attrs.borders?.left !== void 0 || attrs.borders?.right !== void 0 || attrs.borders?.between !== void 0 || attrs.borders?.bar !== void 0 || attrs.shading !== void 0;
1233
1299
  }
1300
+ /** Whether any `w:pBdr` side draws a rule (`none`/`nil` sides draw nothing). */
1301
+ function drawsParagraphBorder(borders) {
1302
+ if (!borders) return false;
1303
+ return [
1304
+ borders.top,
1305
+ borders.bottom,
1306
+ borders.left,
1307
+ borders.right,
1308
+ borders.between,
1309
+ borders.bar
1310
+ ].some((border) => border?.style !== void 0 && !statesNoBorder(border.style));
1311
+ }
1234
1312
  function convertParagraph(node, startPos, options, pageBreaks) {
1235
1313
  const pmAttrs = expectParagraphAttrs(node);
1236
1314
  const runs = paragraphToRuns(node, startPos, options, pageBreaks);
@@ -1238,8 +1316,10 @@ function convertParagraph(node, startPos, options, pageBreaks) {
1238
1316
  theme: options.theme,
1239
1317
  fontAlternates: options.fontAlternates,
1240
1318
  listCounterStreams: options.listCounterStreams,
1241
- defaultTabStopTwips: options.defaultTabStopTwips
1319
+ defaultTabStopTwips: options.defaultTabStopTwips,
1320
+ paragraphMarkFormatting: () => resolveParagraphMarkFormatting(pmAttrs, options.styleResolver)
1242
1321
  });
1322
+ if (options.numberingTabIgnoresIndent && attrs.listMarker !== void 0) attrs.listNumberingTabIgnoresIndent = true;
1243
1323
  if (options.defaultFont !== void 0) attrs.defaultFontFamily ??= options.defaultFont;
1244
1324
  if (options.defaultSize !== void 0) attrs.defaultFontSize ??= options.defaultSize;
1245
1325
  if (options.lineBreakRules) attrs.lineBreakRules = options.lineBreakRules;
@@ -1248,8 +1328,9 @@ function convertParagraph(node, startPos, options, pageBreaks) {
1248
1328
  const defaultTextFormatting = pmAttrs.defaultTextFormatting;
1249
1329
  if (runs.length === 0 || hasOnlyVisuallyEmptyTextRuns(runs)) {
1250
1330
  if (pmAttrs._originalFormatting && Object.entries(pmAttrs._originalFormatting).some(([key, value]) => key !== "runProperties" && value !== void 0 && value !== null)) attrs.hasDirectParagraphFormatting = true;
1251
- const paragraphMarkFormatting = pmAttrs._originalFormatting?.runProperties;
1252
- if (paragraphMarkFormatting && Object.values(paragraphMarkFormatting).some((value) => value !== void 0 && value !== null)) attrs.hasDirectParagraphMarkFormatting = true;
1331
+ const directParagraphMarkFormatting = pmAttrs._originalFormatting?.runProperties;
1332
+ if (directParagraphMarkFormatting && Object.values(directParagraphMarkFormatting).some((value) => value !== void 0 && value !== null)) attrs.hasDirectParagraphMarkFormatting = true;
1333
+ const paragraphMarkFormatting = resolveParagraphMarkOwnFormatting(directParagraphMarkFormatting, options.styleResolver);
1253
1334
  if (paragraphMarkFormatting?.fontSize !== void 0) attrs.defaultFontSize = paragraphMarkFormatting.fontSize / 2;
1254
1335
  const paragraphMarkFontFamily = paragraphMarkFormatting?.fontFamily ? resolveWesternThemeFont(paragraphMarkFormatting.fontFamily, options.theme) : void 0;
1255
1336
  if (paragraphMarkFontFamily) {
@@ -1393,14 +1474,18 @@ function splitParagraphAtPageBreaks({ pageBreaks, paragraph, splitPageBreakAndPa
1393
1474
  fragmentStart = pageBreak.pmEnd;
1394
1475
  }
1395
1476
  const remainingRuns = partitioned.remaining;
1477
+ let paragraphMarkCarrier;
1396
1478
  if (remainingRuns.length > 0) appendParagraph(remainingRuns, fragmentStart, paragraph.pmEnd ?? fragmentStart);
1397
1479
  else if (paragraph.runs.length === 0 || splitPageBreakAndParagraphMark && pageBreaks.at(-1)?.pmEnd === (paragraph.pmEnd ?? fragmentStart) - 1) {
1398
1480
  appendParagraph([], fragmentStart, paragraph.pmEnd ?? fragmentStart);
1399
1481
  const carrier = result.at(-1);
1400
- if (carrier?.kind === "paragraph" && !splitPageBreakAndParagraphMark && !hasVisibleParagraphPayload(carrier.attrs ?? {})) carrier.attrs = {
1401
- ...carrier.attrs,
1402
- suppressEmptyParagraphHeight: true
1403
- };
1482
+ if (carrier?.kind === "paragraph" && !splitPageBreakAndParagraphMark && !hasVisibleParagraphPayload(carrier.attrs ?? {})) {
1483
+ carrier.attrs = {
1484
+ ...carrier.attrs,
1485
+ suppressEmptyParagraphHeight: true
1486
+ };
1487
+ paragraphMarkCarrier = carrier;
1488
+ }
1404
1489
  }
1405
1490
  const fragments = result.filter((block) => block.kind === "paragraph");
1406
1491
  for (let index = 0; index < fragments.length; index += 1) {
@@ -1410,8 +1495,20 @@ function splitParagraphAtPageBreaks({ pageBreaks, paragraph, splitPageBreakAndPa
1410
1495
  if (attrs) fragment.attrs = attrs;
1411
1496
  else delete fragment.attrs;
1412
1497
  }
1498
+ if (paragraphMarkCarrier?.attrs) paragraphMarkCarrier.attrs = withoutParagraphSpacing(paragraphMarkCarrier.attrs);
1413
1499
  return result;
1414
1500
  }
1501
+ function withoutParagraphSpacing(source) {
1502
+ const attrs = { ...source };
1503
+ if (attrs.spacing) {
1504
+ const { before: _before, after: _after, ...lineSpacing } = attrs.spacing;
1505
+ if (Object.keys(lineSpacing).length > 0) attrs.spacing = lineSpacing;
1506
+ else delete attrs.spacing;
1507
+ }
1508
+ delete attrs.automaticSpacing;
1509
+ delete attrs.spacingExplicit;
1510
+ return attrs;
1511
+ }
1415
1512
  /**
1416
1513
  * Word keeps a final empty body paragraph after a table as an editable anchor,
1417
1514
  * but that final anchor does not create a page of its own. Earlier authored
@@ -1513,12 +1610,22 @@ function convertTableCell(node, startPos, options, tableCellMargins) {
1513
1610
  const authoredPageBreakPosition = options.firstPageBreakRunPosition(node);
1514
1611
  const leadingParagraph = node.firstChild?.type.name === "paragraph" && countPageBreakRuns(node) === 1 && options.firstPageBreakRunPosition(node.firstChild) === authoredPageBreakPosition ? node.firstChild : void 0;
1515
1612
  const pageBreaks = [];
1516
- node.forEach((child) => {
1613
+ const convertCellChild = (child, childStart) => {
1517
1614
  if (child.type.name === "paragraph") {
1518
- const block = convertParagraph(child, offset, options, child === leadingParagraph ? pageBreaks : void 0);
1615
+ const block = convertParagraph(child, childStart, options, child === leadingParagraph ? pageBreaks : void 0);
1519
1616
  blocks.push(block);
1520
- } else if (child.type.name === "table") blocks.push(convertTable(child, offset, options));
1521
- else if (child.type.name === "textBox") blocks.push(convertTextBoxNode(child, offset, options));
1617
+ } else if (child.type.name === "table") blocks.push(convertTable(child, childStart, options));
1618
+ else if (child.type.name === "textBox") blocks.push(convertTextBoxNode(child, childStart, options));
1619
+ else if (child.type.name === "blockSdt") {
1620
+ let sdtChildStart = childStart + 1;
1621
+ child.forEach((sdtChild) => {
1622
+ convertCellChild(sdtChild, sdtChildStart);
1623
+ sdtChildStart += sdtChild.nodeSize;
1624
+ });
1625
+ }
1626
+ };
1627
+ node.forEach((child) => {
1628
+ convertCellChild(child, offset);
1522
1629
  offset += child.nodeSize;
1523
1630
  });
1524
1631
  const trailingBlock = blocks.at(-1);
@@ -1669,6 +1776,8 @@ function convertTable(node, startPos, options) {
1669
1776
  if (originalFormatting?.layout !== void 0) tableBlock.layout = originalFormatting.layout;
1670
1777
  if (justification) tableBlock.justification = justification;
1671
1778
  if (indentPx !== void 0) tableBlock.indent = indentPx;
1779
+ const cellSpacing = originalFormatting?.cellSpacing;
1780
+ if (cellSpacing?.type === "dxa" && cellSpacing.value > 0) tableBlock.cellSpacing = twipsToPixels(cellSpacing.value);
1672
1781
  if (options.tableIndentCompatibility && !dropsAuthoredIndent) tableBlock.indentCompatibility = options.tableIndentCompatibility;
1673
1782
  if (floatingPx) tableBlock.floating = floatingPx;
1674
1783
  if (rightToLeft) tableBlock.bidi = true;
@@ -1718,6 +1827,26 @@ function convertImage(node, startPos, pageContentHeight) {
1718
1827
  if (imageBorderStyle) imgBlock.borderStyle = imageBorderStyle;
1719
1828
  return imgBlock;
1720
1829
  }
1830
+ const EMUS_PER_PIXEL = 9525;
1831
+ /**
1832
+ * The `wp:effectExtent` of an inline (`wp:inline`) box, in unrounded pixels.
1833
+ *
1834
+ * An inline object occupies its extent plus the effect extent on every side,
1835
+ * so the box itself sits `l`/`t` inside the space the line gives it. On a
1836
+ * `wp:anchor` the position offsets already locate the shape's own extent, so
1837
+ * the reservation only affects wrapping and is not applied here.
1838
+ */
1839
+ function inlineEffectExtentPx(wrapType, extent) {
1840
+ if (wrapType !== void 0 && wrapType !== "inline" || extent === void 0) return;
1841
+ const px = (emu) => Math.max(0, (emu ?? 0) / EMUS_PER_PIXEL);
1842
+ const resolved = {
1843
+ top: px(extent.top),
1844
+ bottom: px(extent.bottom),
1845
+ left: px(extent.left),
1846
+ right: px(extent.right)
1847
+ };
1848
+ return resolved.top === 0 && resolved.bottom === 0 && resolved.left === 0 && resolved.right === 0 ? void 0 : resolved;
1849
+ }
1721
1850
  /** Convert a textBox PM node to a TextBoxBlock. */
1722
1851
  function convertTextBoxNode(node, startPos, opts) {
1723
1852
  const attrs = expectTextBoxAttrs(node);
@@ -1763,6 +1892,19 @@ function convertTextBoxNode(node, startPos, opts) {
1763
1892
  if (attrs.distLeft !== void 0) textBox.distLeft = attrs.distLeft;
1764
1893
  if (attrs.distRight !== void 0) textBox.distRight = attrs.distRight;
1765
1894
  if (attrs.position !== void 0) textBox.position = attrs.position;
1895
+ const effectExtent = inlineEffectExtentPx(attrs.wrapType, attrs.wrapEffectExtentSlots?.drawing);
1896
+ if (effectExtent !== void 0) textBox.effectExtent = effectExtent;
1897
+ const host = textBoxHostParagraph(node);
1898
+ if (host && attrs.position === void 0 && (attrs.wrapType ?? "inline") === "inline") {
1899
+ const hostAttrs = expectParagraphAttrs(host);
1900
+ textBox.hostParagraph = convertParagraphAttrs(hostAttrs, {
1901
+ theme: opts.theme,
1902
+ fontAlternates: opts.fontAlternates,
1903
+ listCounterStreams: opts.listCounterStreams,
1904
+ defaultTabStopTwips: opts.defaultTabStopTwips,
1905
+ paragraphMarkFormatting: () => resolveParagraphMarkFormatting(hostAttrs, opts.styleResolver)
1906
+ });
1907
+ }
1766
1908
  if (attrs._docxGroupId !== void 0) setTextBoxGroupId(textBox, attrs._docxGroupId);
1767
1909
  const anchorBlockId = attrs._docxAnchorId ? opts.textBoxAnchorBlockIds.get(attrs._docxAnchorId) : void 0;
1768
1910
  if (anchorBlockId !== void 0) Reflect.set(textBox, TEXT_BOX_ANCHOR_BLOCK_ID, anchorBlockId);
@@ -1804,8 +1946,12 @@ function applySectionStartsToBoundaries(blocks, finalSectionStart) {
1804
1946
  * continuous section resumes there too. Accept both the legacy generated
1805
1947
  * carrier and the inline atom's one-token paragraph-mark fragment; neither is
1806
1948
  * a physical layout boundary.
1949
+ *
1950
+ * A section break in `breaksWithoutMarker` ends at an empty w:sectPr paragraph
1951
+ * that projected no block, so a page break just before it ends an earlier
1952
+ * paragraph and still starts the next page.
1807
1953
  */
1808
- function coalesceTrailingPageBreakBeforeContinuousSection(blocks, splitPageBreakAndParagraphMark) {
1954
+ function coalesceTrailingPageBreakBeforeContinuousSection(blocks, splitPageBreakAndParagraphMark, breaksWithoutMarker) {
1809
1955
  if (splitPageBreakAndParagraphMark) return [...blocks];
1810
1956
  const result = [];
1811
1957
  for (let index = 0; index < blocks.length; index += 1) {
@@ -1815,8 +1961,8 @@ function coalesceTrailingPageBreakBeforeContinuousSection(blocks, splitPageBreak
1815
1961
  const isLegacyGeneratedCarrier = block?.kind === "pageBreak" && carrier?.kind === "paragraph" && carrier.runs.length === 0 && block.pmStart !== void 0 && block.pmStart === carrier.pmStart && carrier.pmStart === carrier.pmEnd;
1816
1962
  const isInlineParagraphMarkCarrier = block?.kind === "pageBreak" && carrier?.kind === "paragraph" && carrier.runs.length === 0 && block.pmEnd !== void 0 && carrier.pmStart === block.pmEnd && carrier.pmEnd === block.pmEnd + 1;
1817
1963
  const isUnresolvedInsertion = block?.kind === "pageBreak" && block.isInsertion === true;
1818
- if (block?.kind === "pageBreak" && !isUnresolvedInsertion && carrier?.kind === "sectionBreak" && carrier.type === "continuous") continue;
1819
- if (block?.kind === "pageBreak" && !isUnresolvedInsertion && (isLegacyGeneratedCarrier || isInlineParagraphMarkCarrier) && section?.kind === "sectionBreak" && section.type === "continuous") {
1964
+ if (block?.kind === "pageBreak" && !isUnresolvedInsertion && carrier?.kind === "sectionBreak" && carrier.type === "continuous" && !breaksWithoutMarker.has(carrier.id)) continue;
1965
+ if (block?.kind === "pageBreak" && !isUnresolvedInsertion && (isLegacyGeneratedCarrier || isInlineParagraphMarkCarrier) && section?.kind === "sectionBreak" && section.type === "continuous" && !breaksWithoutMarker.has(section.id)) {
1820
1966
  index += 1;
1821
1967
  continue;
1822
1968
  }
@@ -1884,6 +2030,7 @@ function toFlowBlocks(doc, options = {}) {
1884
2030
  },
1885
2031
  firstPageBreakRunPosition,
1886
2032
  textBoxAnchorBlockIds: /* @__PURE__ */ new Map(),
2033
+ numberingTabIgnoresIndent: doc.attrs["_doNotUseIndentAsNumberingTabStop"] === true,
1887
2034
  styleResolver: createStyleEngine(options.styles),
1888
2035
  numberedRefResults: resolveNumberedRefFields(doc, {
1889
2036
  listCounterState: cloneListCounterState(listCounterState),
@@ -1917,11 +2064,72 @@ function toFlowBlocks(doc, options = {}) {
1917
2064
  const pageBreaks = [];
1918
2065
  const paragraph = convertParagraph(node, pos, opts, pageBreaks);
1919
2066
  if (stripLeadingLineBreak && paragraph.runs.at(0)?.kind === "lineBreak") paragraph.runs.shift();
1920
- for (const block of splitParagraphAtPageBreaks({
2067
+ const projected = splitParagraphAtPageBreaks({
1921
2068
  pageBreaks,
1922
2069
  paragraph,
1923
2070
  splitPageBreakAndParagraphMark: options.splitPageBreakAndParagraphMark === true
1924
- })) trackedPush(block);
2071
+ });
2072
+ if (pageBreaks.length > 0) placeTextBoxAnchorsBeforePageBreaks(node, pos, projected);
2073
+ for (const block of projected) trackedPush(block);
2074
+ };
2075
+ /** Blocks emitted before the current section's first block. */
2076
+ let sectionStartBlockCount = 0;
2077
+ /** Section breaks whose empty w:sectPr paragraph projected no block. */
2078
+ const sectionBreaksWithoutMarker = /* @__PURE__ */ new Set();
2079
+ /**
2080
+ * The page break a text box anchored in an earlier part of its paragraph
2081
+ * must be laid out before, keyed by `_docxAnchorId`.
2082
+ */
2083
+ const textBoxPageBreakByAnchorId = /* @__PURE__ */ new Map();
2084
+ /** Text boxes held back until every block is projected. */
2085
+ const textBoxesBeforePageBreak = /* @__PURE__ */ new Map();
2086
+ /**
2087
+ * An anchored object sits on the page holding its anchor run. When that run
2088
+ * precedes a `w:br w:type="page"` in the same paragraph, the object belongs
2089
+ * to the page the break ends. Either way its paragraph-relative position is
2090
+ * measured from the part of the paragraph holding the anchor.
2091
+ */
2092
+ const placeTextBoxAnchorsBeforePageBreaks = (node, pos, projected) => {
2093
+ let breakIndex = 0;
2094
+ node.descendants((child, childOffset) => {
2095
+ if (child.type.name !== "textBoxAnchor") return true;
2096
+ const anchorPos = pos + 1 + childOffset;
2097
+ while (breakIndex < projected.length) {
2098
+ const block = projected[breakIndex];
2099
+ if (block?.kind === "pageBreak" && (block.pmStart ?? 0) > anchorPos) break;
2100
+ breakIndex += 1;
2101
+ }
2102
+ const { anchorId } = expectTextBoxAnchorAttrs(child);
2103
+ const pageBreak = projected[breakIndex];
2104
+ if (pageBreak?.kind === "pageBreak") textBoxPageBreakByAnchorId.set(anchorId, pageBreak);
2105
+ const anchorFragment = pageBreak ? projected[breakIndex - 1] : projected.at(-1);
2106
+ if (anchorFragment?.kind === "paragraph") opts.textBoxAnchorBlockIds.set(anchorId, anchorFragment.id);
2107
+ else opts.textBoxAnchorBlockIds.delete(anchorId);
2108
+ return false;
2109
+ });
2110
+ };
2111
+ const pushTextBox = (node, pos) => {
2112
+ const textBox = convertTextBoxNode(node, pos, opts);
2113
+ const anchorId = expectTextBoxAttrs(node)._docxAnchorId;
2114
+ const pageBreak = anchorId === void 0 ? void 0 : textBoxPageBreakByAnchorId.get(anchorId);
2115
+ if (!pageBreak) {
2116
+ trackedPush(textBox);
2117
+ return;
2118
+ }
2119
+ tagBlockWithSdtStack(textBox);
2120
+ const heldBack = textBoxesBeforePageBreak.get(pageBreak);
2121
+ if (heldBack) heldBack.push(textBox);
2122
+ else textBoxesBeforePageBreak.set(pageBreak, [textBox]);
2123
+ };
2124
+ /** Put every held-back text box just before its page break, in one pass. */
2125
+ const releaseTextBoxesBeforePageBreaks = () => {
2126
+ if (textBoxesBeforePageBreak.size === 0) return;
2127
+ const inFlowOrder = blocks.splice(0);
2128
+ for (const block of inFlowOrder) {
2129
+ const heldBack = block.kind === "pageBreak" ? textBoxesBeforePageBreak.get(block) : void 0;
2130
+ for (const textBox of heldBack ?? []) blocks.push(textBox);
2131
+ blocks.push(block);
2132
+ }
1925
2133
  };
1926
2134
  const trailingPageBreakSectionPositions = /* @__PURE__ */ new Set();
1927
2135
  const consumedPageBreakPositions = /* @__PURE__ */ new Set();
@@ -2004,7 +2212,11 @@ function toFlowBlocks(doc, options = {}) {
2004
2212
  const hasListFormatting = pmAttrs.numPr !== null && pmAttrs.numPr !== void 0 || pmAttrs.listMarker !== null && pmAttrs.listMarker !== void 0;
2005
2213
  const firstChild = node.firstChild;
2006
2214
  const startsWithColumnBreak = firstChild?.type.name === "hardBreak" && expectHardBreakAttrs(firstChild).breakType === "column";
2007
- if (node.childCount === 1 && startsWithColumnBreak) {
2215
+ const isStandaloneColumnBreak = node.childCount === 1 && startsWithColumnBreak;
2216
+ const isEmptySectionMark = hasSectionBreak && node.content.size === 0;
2217
+ const opensSection = blocks.length === sectionStartBlockCount;
2218
+ let markerDropped = false;
2219
+ if (isStandaloneColumnBreak) {
2008
2220
  const columnBreak = {
2009
2221
  kind: "columnBreak",
2010
2222
  id: nextBlockId(),
@@ -2021,7 +2233,12 @@ function toFlowBlocks(doc, options = {}) {
2021
2233
  };
2022
2234
  trackedPush(columnBreak);
2023
2235
  pushParagraphProjection(node, pos, true);
2024
- } else if (node.content.size > 0 || hasListFormatting || !hasSectionBreak) pushParagraphProjection(node, pos);
2236
+ } else if (!isEmptySectionMark) pushParagraphProjection(node, pos);
2237
+ else if (hasListFormatting || drawsParagraphBorder(pmAttrs.borders) || opensSection) {
2238
+ pushParagraphProjection(node, pos);
2239
+ const mark = blocks.at(-1);
2240
+ if (!opensSection && mark?.kind === "paragraph" && mark.attrs?.pageBreakBefore) delete mark.attrs.pageBreakBefore;
2241
+ } else markerDropped = true;
2025
2242
  if (hasSectionBreak) {
2026
2243
  if (trailingPageBreakSectionPositions.has(pos)) {
2027
2244
  const sourceParagraph = blocks.at(-1);
@@ -2093,6 +2310,8 @@ function toFlowBlocks(doc, options = {}) {
2093
2310
  if (columns) sectionBreak.columns = columns;
2094
2311
  }
2095
2312
  trackedPush(sectionBreak);
2313
+ sectionStartBlockCount = blocks.length;
2314
+ if (markerDropped) sectionBreaksWithoutMarker.add(sectionBreak.id);
2096
2315
  }
2097
2316
  break;
2098
2317
  }
@@ -2105,7 +2324,7 @@ function toFlowBlocks(doc, options = {}) {
2105
2324
  break;
2106
2325
  }
2107
2326
  case "textBox":
2108
- trackedPush(convertTextBoxNode(node, pos, opts));
2327
+ pushTextBox(node, pos);
2109
2328
  break;
2110
2329
  case "horizontalRule":
2111
2330
  case "pageBreak": {
@@ -2125,15 +2344,19 @@ function toFlowBlocks(doc, options = {}) {
2125
2344
  doc.forEach((node, nodeOffset) => {
2126
2345
  visit(node, offset + nodeOffset);
2127
2346
  });
2347
+ releaseTextBoxesBeforePageBreaks();
2128
2348
  reserveLeadingEmptyOutlineHeight(blocks);
2129
- suppressFinalEmptyParagraphAfterTable(blocks);
2130
- suppressFinalParagraphInRepeatedEmptySuffix(blocks);
2349
+ const trailingEndnoteIds = options.trailingEndnoteIds;
2350
+ if (!(trailingEndnoteIds !== void 0 && trailingEndnoteIds.size > 0 && collectNoteRefs(blocks, "endnoteRefId").some(({ noteId }) => trailingEndnoteIds.has(noteId)))) {
2351
+ suppressFinalEmptyParagraphAfterTable(blocks);
2352
+ suppressFinalParagraphInRepeatedEmptySuffix(blocks);
2353
+ }
2131
2354
  const mergedBlocks = mergeRunInParagraphs(blocks);
2132
2355
  const tableCellLinePitch = doc.attrs["_adjustLineHeightInTable"] === true ? "sectionGrid" : void 0;
2133
2356
  return groupParagraphFrames(coalesceTrailingPageBreakBeforeContinuousSection(applySectionStartsToBoundaries(applySectionDocumentGrid(mergedBlocks, {
2134
2357
  finalLinePitchTwips: opts.finalSectionDocumentGridLinePitchTwips,
2135
2358
  tableCellLinePitch
2136
- }), readFinalSectionStart(doc)), options.splitPageBreakAndParagraphMark === true), nextBlockId);
2359
+ }), readFinalSectionStart(doc)), options.splitPageBreakAndParagraphMark === true, sectionBreaksWithoutMarker), nextBlockId);
2137
2360
  }
2138
2361
  function applySectionDocumentGrid(blocks, { finalLinePitchTwips, tableCellLinePitch }) {
2139
2362
  const result = [...blocks];
@@ -2234,4 +2457,4 @@ function mergeRunInParagraphs(blocks) {
2234
2457
  return out;
2235
2458
  }
2236
2459
  //#endregion
2237
- export { WORD_UNSPECIFIED_FONT_SIZE, convertBorderSpecToLayout, formatCounter, resetBlockIdCounter, resolveListTemplate, toFlowBlocks };
2460
+ export { WORD_UNSPECIFIED_FONT_SIZE, collectNoteRefs, convertBorderSpecToLayout, formatCounter, isNoteReferenceMarkXml, resetBlockIdCounter, resolveListTemplate, toFlowBlocks };
@@ -1,7 +1,7 @@
1
1
  import { getHeaderRowsHeight } from "../../layout-engine/index.js";
2
2
  import { measuredLineRangeHeight } from "../../layout-engine/lineFlow.js";
3
3
  import { getTableCellContentWidth } from "../../layout-engine/measure/tableCellFloating.js";
4
- import { buildTableCellGrid, buildTableCellPlacements } from "../../layout-engine/measure/tableCellGrid.js";
4
+ import { buildTableCellGrid, buildTableCellPlacements, getTableCellSpacingInsetsY } from "../../layout-engine/measure/tableCellGrid.js";
5
5
  import { resolveTableCellPadding } from "../../layout-engine/types.js";
6
6
  //#region src/layout-bridge/engine/hitTest.ts
7
7
  /**
@@ -222,7 +222,8 @@ function hitTestTableCell(pageHit, blocks, measures, pagePoint) {
222
222
  const cellPlacements = buildTableCellPlacements({
223
223
  grid: buildTableCellGrid(tableBlock.rows, tableMeasure.columnWidths.length),
224
224
  columnWidths: tableMeasure.columnWidths,
225
- bidi: tableBlock.bidi === true
225
+ bidi: tableBlock.bidi === true,
226
+ cellSpacing: tableBlock.cellSpacing
226
227
  });
227
228
  let nearestDistance = Infinity;
228
229
  for (let c = 0; c < row.cells.length; c++) {
@@ -267,7 +268,8 @@ function hitTestTableCell(pageHit, blocks, measures, pagePoint) {
267
268
  const cellLocalX = localX - cellLeft - padLeft;
268
269
  const cellContentWidth = getTableCellContentWidth(cell, cellMeasure);
269
270
  const clipOffset = isClickOnHeader ? 0 : tableFragment.topClip ?? 0;
270
- const cellLocalY = localY - rowTop + clipOffset;
271
+ const cellSpacingTop = getTableCellSpacingInsetsY(tableBlock.cellSpacing, rowIndex, cell.rowSpan ?? 1, tableBlock.rows.length).top;
272
+ const cellLocalY = localY - rowTop + clipOffset - cellSpacingTop;
271
273
  return {
272
274
  fragment: tableFragment,
273
275
  block: tableBlock,
@@ -4,7 +4,7 @@ import { buildRunFontStyle } from "../../layout-engine/measure/measureHelpers.js
4
4
  import { measureParagraph } from "../../layout-engine/measure/measureParagraph.js";
5
5
  import { measureRun } from "../../layout-engine/measure/measureProvider.js";
6
6
  import { buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages } from "../../layout-engine/measure/tableCellFloating.js";
7
- import { buildTableCellGrid, buildTableCellPlacements } from "../../layout-engine/measure/tableCellGrid.js";
7
+ import { buildTableCellGrid, buildTableCellPlacements, getTableCellSpacingInsetsY } from "../../layout-engine/measure/tableCellGrid.js";
8
8
  import { resolveTableCellPadding } from "../../layout-engine/types.js";
9
9
  import { normalizeHorizontalScalePercent } from "../../utils/horizontalScale.js";
10
10
  import { resolvePhysicalParagraphInlineLayout } from "../../utils/paragraphInlineLayout.js";
@@ -312,7 +312,8 @@ function selectionToRects(layout, blocks, measures, from, to) {
312
312
  cellPlacements = buildTableCellPlacements({
313
313
  grid: buildTableCellGrid(tableBlock.rows, tableMeasure.columnWidths.length),
314
314
  columnWidths: tableMeasure.columnWidths,
315
- bidi: tableBlock.bidi === true
315
+ bidi: tableBlock.bidi === true,
316
+ cellSpacing: tableBlock.cellSpacing
316
317
  });
317
318
  tableCellPlacements.set(tableBlock, cellPlacements);
318
319
  }
@@ -333,7 +334,8 @@ function selectionToRects(layout, blocks, measures, from, to) {
333
334
  const contentWidth = getTableCellContentWidth(cell, cellMeasure);
334
335
  const floatingZones = buildTableCellFloatingZones(getTableCellFloatingImages(cell, cellMeasure, contentWidth), contentWidth);
335
336
  const contentOffsetX = getCellContentOffsetX(cell);
336
- const contentOffsetY = getCellContentOffsetY(cell, cellMeasure, rowMeasure.height);
337
+ const spacing = getTableCellSpacingInsetsY(tableBlock.cellSpacing, rowIndex, cell.rowSpan ?? 1, tableBlock.rows.length);
338
+ const contentOffsetY = spacing.top + getCellContentOffsetY(cell, cellMeasure, Math.max(0, rowMeasure.height - spacing.top - spacing.bottom));
337
339
  let blockY = 0;
338
340
  for (let blockIdx = 0; blockIdx < cell.blocks.length; blockIdx++) {
339
341
  const cellBlock = cell.blocks[blockIdx];
@@ -0,0 +1,10 @@
1
+ //#region src/layout-engine/anchorLayoutInCellCompatibility.d.ts
2
+ /**
3
+ * `true` when the document's compatibility mode forces every table-cell
4
+ * anchor to lay out inside its cell, ignoring an authored
5
+ * `layoutInCell="0"`. `false` means the authored attribute (or its
6
+ * OOXML default of "confined to the cell") should be honoured as stated.
7
+ */
8
+ declare const resolveAnchorLayoutInCellCompatibility: (compatibilityMode: number | undefined) => boolean;
9
+ //#endregion
10
+ export { resolveAnchorLayoutInCellCompatibility };
@@ -0,0 +1,22 @@
1
+ //#region src/layout-engine/anchorLayoutInCellCompatibility.ts
2
+ /**
3
+ * First `compatibilityMode` that ignores `wp:anchor/@layoutInCell="0"`.
4
+ *
5
+ * From `compatibilityMode` 15 onward, an anchor inside a table cell is laid
6
+ * out in the cell regardless of what `wp:anchor/@layoutInCell` states: the
7
+ * attribute stops opting the anchor out of the cell's coordinate space.
8
+ * Earlier modes still honour an explicit `layoutInCell="0"` and let the
9
+ * anchor position itself relative to the page. A document that declares no
10
+ * `compatSetting` at all is read with the oldest semantics, so it honours
11
+ * the attribute too.
12
+ */
13
+ const FIRST_ALWAYS_IN_CELL_COMPATIBILITY_MODE = 15;
14
+ /**
15
+ * `true` when the document's compatibility mode forces every table-cell
16
+ * anchor to lay out inside its cell, ignoring an authored
17
+ * `layoutInCell="0"`. `false` means the authored attribute (or its
18
+ * OOXML default of "confined to the cell") should be honoured as stated.
19
+ */
20
+ const resolveAnchorLayoutInCellCompatibility = (compatibilityMode) => compatibilityMode !== void 0 && compatibilityMode >= FIRST_ALWAYS_IN_CELL_COMPATIBILITY_MODE;
21
+ //#endregion
22
+ export { resolveAnchorLayoutInCellCompatibility };