@stll/folio-core 0.47.7 → 0.49.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 (213) hide show
  1. package/dist/ai-edits/apply.js +565 -121
  2. package/dist/ai-edits/minimal-replacement.d.ts +64 -0
  3. package/dist/ai-edits/minimal-replacement.js +282 -0
  4. package/dist/controller/fontReadiness.js +3 -3
  5. package/dist/controller/hyphenationReadiness.d.ts +19 -0
  6. package/dist/controller/hyphenationReadiness.js +47 -0
  7. package/dist/controller/layoutPipeline.d.ts +3 -0
  8. package/dist/controller/layoutPipeline.js +70 -18
  9. package/dist/display-list/build/buildDisplayList.js +39 -12
  10. package/dist/display-list/build/pageFurniture.d.ts +7 -1
  11. package/dist/display-list/build/pageFurniture.js +21 -8
  12. package/dist/display-list/build/paragraphPrimitives.js +68 -11
  13. package/dist/display-list/build/tablePrimitives.js +10 -6
  14. package/dist/display-list/build/textBoxPrimitives.js +1 -1
  15. package/dist/display-list/build/unsupported.d.ts +2 -2
  16. package/dist/display-list/build/unsupported.js +0 -0
  17. package/dist/display-list/dom/renderDisplayListToDom.js +0 -1
  18. package/dist/docx/attributeRemainder.js +10 -2
  19. package/dist/docx/blockContentParser.d.ts +2 -0
  20. package/dist/docx/blockContentParser.js +79 -64
  21. package/dist/docx/bulletMarkers.d.ts +18 -2
  22. package/dist/docx/bulletMarkers.js +27 -2
  23. package/dist/docx/commentParser.d.ts +2 -1
  24. package/dist/docx/commentParser.js +57 -45
  25. package/dist/docx/containerChildren.d.ts +21 -8
  26. package/dist/docx/containerChildren.js +39 -9
  27. package/dist/docx/documentParser.d.ts +5 -1
  28. package/dist/docx/documentParser.js +8 -5
  29. package/dist/docx/documentSectionFacts.d.ts +22 -0
  30. package/dist/docx/documentSectionFacts.js +43 -0
  31. package/dist/docx/drawingGroupChildren.d.ts +60 -0
  32. package/dist/docx/drawingGroupChildren.js +176 -0
  33. package/dist/docx/drawingUtils.js +3 -0
  34. package/dist/docx/fieldParser.d.ts +8 -1
  35. package/dist/docx/fieldParser.js +47 -3
  36. package/dist/docx/fontTableParser.js +34 -27
  37. package/dist/docx/footnoteParser.d.ts +3 -2
  38. package/dist/docx/footnoteParser.js +15 -12
  39. package/dist/docx/groupDrawingParser.d.ts +9 -2
  40. package/dist/docx/groupDrawingParser.js +125 -37
  41. package/dist/docx/headerFooterParser.d.ts +7 -2
  42. package/dist/docx/headerFooterParser.js +15 -5
  43. package/dist/docx/hyperlinkParser.d.ts +48 -36
  44. package/dist/docx/hyperlinkParser.js +97 -82
  45. package/dist/docx/numberingParser.js +108 -84
  46. package/dist/docx/packageParts.d.ts +45 -1
  47. package/dist/docx/packageParts.js +93 -2
  48. package/dist/docx/paraIdAttribute.d.ts +3 -2
  49. package/dist/docx/paragraphParser.d.ts +5 -1
  50. package/dist/docx/paragraphParser.js +468 -365
  51. package/dist/docx/paragraphProperties.js +123 -109
  52. package/dist/docx/paragraphPropertySource.d.ts +6 -1
  53. package/dist/docx/paragraphPropertySource.js +9 -1
  54. package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
  55. package/dist/docx/paragraphTextBoxEnrichment.js +143 -10
  56. package/dist/docx/parser.d.ts +7 -1
  57. package/dist/docx/parser.js +33 -16
  58. package/dist/docx/previewBudget.d.ts +55 -33
  59. package/dist/docx/previewBudget.js +59 -65
  60. package/dist/docx/rezip.js +41 -58
  61. package/dist/docx/runParser.d.ts +4 -1
  62. package/dist/docx/runParser.js +182 -184
  63. package/dist/docx/sdtProperties.js +87 -84
  64. package/dist/docx/sectionParser.js +169 -160
  65. package/dist/docx/selectiveSaveFlags.d.ts +10 -3
  66. package/dist/docx/selectiveSaveFlags.js +1 -1
  67. package/dist/docx/selectiveXmlPatch.d.ts +56 -34
  68. package/dist/docx/selectiveXmlPatch.js +222 -115
  69. package/dist/docx/serializer/groupTextBoxWriteBack.d.ts +12 -0
  70. package/dist/docx/serializer/groupTextBoxWriteBack.js +101 -0
  71. package/dist/docx/serializer/paragraphSerializer.js +47 -42
  72. package/dist/docx/serializer/runSerializer.d.ts +5 -1
  73. package/dist/docx/serializer/runSerializer.js +4 -3
  74. package/dist/docx/serializer/settingsSerializer.js +19 -1
  75. package/dist/docx/settingsParser.js +8 -0
  76. package/dist/docx/shapeAlternateContent.d.ts +25 -0
  77. package/dist/docx/shapeAlternateContent.js +52 -0
  78. package/dist/docx/streamingXmlParser.d.ts +6 -2
  79. package/dist/docx/streamingXmlParser.js +12 -7
  80. package/dist/docx/styleParser.js +34 -8
  81. package/dist/docx/tableParser.d.ts +21 -12
  82. package/dist/docx/tableParser.js +518 -441
  83. package/dist/docx/textBoxParser.d.ts +18 -5
  84. package/dist/docx/textBoxParser.js +12 -6
  85. package/dist/docx/verbatimCapture.d.ts +10 -8
  86. package/dist/docx/verbatimCapture.js +45 -2
  87. package/dist/docx/vmlImageParser.d.ts +15 -13
  88. package/dist/docx/vmlImageParser.js +75 -60
  89. package/dist/docx/vmlPreview.d.ts +3 -3
  90. package/dist/docx/vmlPreview.js +3 -4
  91. package/dist/docx/xmlNamespaceContext.d.ts +9 -0
  92. package/dist/docx/xmlNamespaceContext.js +54 -0
  93. package/dist/docx/xmlParser.d.ts +6 -3
  94. package/dist/docx/xmlParser.js +11 -41
  95. package/dist/docx/xmlResourceLimits.d.ts +2 -1
  96. package/dist/docx/xmlResourceLimits.js +4 -1
  97. package/dist/fonts/headlessMeasure.js +0 -0
  98. package/dist/headless-layout.d.ts +11 -2
  99. package/dist/headless-layout.js +153 -107
  100. package/dist/internal/headlessRevisionResolution.js +50 -13
  101. package/dist/internal/paragraphFormattingSerialization.js +3 -3
  102. package/dist/layout-bridge/convert/endnoteLayout.d.ts +65 -0
  103. package/dist/layout-bridge/convert/endnoteLayout.js +227 -0
  104. package/dist/layout-bridge/convert/footnoteLayout.d.ts +19 -3
  105. package/dist/layout-bridge/convert/footnoteLayout.js +40 -31
  106. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -7
  107. package/dist/layout-bridge/convert/headerFooterLayout.js +5 -20
  108. package/dist/layout-bridge/convert/paragraphMarkFormatting.d.ts +10 -0
  109. package/dist/layout-bridge/convert/paragraphMarkFormatting.js +59 -0
  110. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +29 -1
  111. package/dist/layout-bridge/convert/toFlowBlocks.js +284 -39
  112. package/dist/layout-bridge/engine/hitTest.js +5 -3
  113. package/dist/layout-bridge/engine/selectionRects.js +5 -3
  114. package/dist/layout-engine/anchorLayoutInCellCompatibility.d.ts +10 -0
  115. package/dist/layout-engine/anchorLayoutInCellCompatibility.js +22 -0
  116. package/dist/layout-engine/index.d.ts +2 -2
  117. package/dist/layout-engine/index.js +102 -14
  118. package/dist/layout-engine/justificationCompatibility.d.ts +1 -1
  119. package/dist/layout-engine/justificationCompatibility.js +9 -5
  120. package/dist/layout-engine/keep-together.d.ts +15 -6
  121. package/dist/layout-engine/keep-together.js +43 -16
  122. package/dist/layout-engine/layoutInstrumentation.d.ts +8 -2
  123. package/dist/layout-engine/layoutInstrumentation.js +8 -1
  124. package/dist/layout-engine/measure/advanceComposition.js +50 -7
  125. package/dist/layout-engine/measure/hyphenationDictionaries.d.ts +74 -0
  126. package/dist/layout-engine/measure/hyphenationDictionaries.js +138 -0
  127. package/dist/layout-engine/measure/hyphenationPreload.d.ts +11 -0
  128. package/dist/layout-engine/measure/hyphenationPreload.js +24 -0
  129. package/dist/layout-engine/measure/lineBreakProvider.d.ts +12 -2
  130. package/dist/layout-engine/measure/lineBreakProvider.js +30 -30
  131. package/dist/layout-engine/measure/lineBreaks.d.ts +6 -1
  132. package/dist/layout-engine/measure/lineBreaks.js +10 -3
  133. package/dist/layout-engine/measure/listMarkerWidth.d.ts +9 -6
  134. package/dist/layout-engine/measure/listMarkerWidth.js +20 -22
  135. package/dist/layout-engine/measure/measureBlocks.js +46 -10
  136. package/dist/layout-engine/measure/measureContainer.js +51 -8
  137. package/dist/layout-engine/measure/measureHelpers.d.ts +15 -3
  138. package/dist/layout-engine/measure/measureHelpers.js +34 -8
  139. package/dist/layout-engine/measure/measureParagraph.js +143 -101
  140. package/dist/layout-engine/measure/measureTypes.d.ts +5 -0
  141. package/dist/layout-engine/measure/smallCapsCasing.d.ts +56 -0
  142. package/dist/layout-engine/measure/smallCapsCasing.js +88 -0
  143. package/dist/layout-engine/measure/tabCalculator.d.ts +2 -0
  144. package/dist/layout-engine/measure/tabCalculator.js +2 -1
  145. package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
  146. package/dist/layout-engine/measure/tableCellFloating.js +51 -19
  147. package/dist/layout-engine/measure/tableCellGrid.d.ts +28 -3
  148. package/dist/layout-engine/measure/tableCellGrid.js +45 -5
  149. package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +2 -1
  150. package/dist/layout-engine/noteAreaFlow.d.ts +19 -0
  151. package/dist/layout-engine/noteAreaFlow.js +83 -0
  152. package/dist/layout-engine/paginator.d.ts +12 -1
  153. package/dist/layout-engine/paginator.js +18 -2
  154. package/dist/layout-engine/paragraphSpacing.d.ts +4 -2
  155. package/dist/layout-engine/paragraphSpacing.js +2 -1
  156. package/dist/layout-engine/tableRowBreak.d.ts +8 -1
  157. package/dist/layout-engine/tableRowBreak.js +25 -2
  158. package/dist/layout-engine/types.d.ts +108 -3
  159. package/dist/layout-engine/types.js +96 -1
  160. package/dist/layout-painter/index.d.ts +3 -0
  161. package/dist/layout-painter/renderPage.js +32 -83
  162. package/dist/layout-painter/renderParagraph.d.ts +6 -1
  163. package/dist/layout-painter/renderParagraph.js +75 -26
  164. package/dist/layout-painter/renderTable.js +26 -13
  165. package/dist/layout-painter/renderTextBox.d.ts +12 -2
  166. package/dist/layout-painter/renderTextBox.js +26 -2
  167. package/dist/prosemirror/alternateContentAttrs.d.ts +9 -0
  168. package/dist/prosemirror/alternateContentAttrs.js +35 -0
  169. package/dist/prosemirror/attrs/index.d.ts +9 -9
  170. package/dist/prosemirror/attrs/index.js +133 -11
  171. package/dist/prosemirror/commands/comments.js +51 -41
  172. package/dist/prosemirror/contentControlRevisions.d.ts +43 -0
  173. package/dist/prosemirror/contentControlRevisions.js +84 -0
  174. package/dist/prosemirror/conversion/fromProseDoc.js +54 -23
  175. package/dist/prosemirror/conversion/markInterner.d.ts +21 -0
  176. package/dist/prosemirror/conversion/markInterner.js +90 -0
  177. package/dist/prosemirror/conversion/toProseDoc.js +117 -58
  178. package/dist/prosemirror/extensions/core/DocExtension.js +2 -1
  179. package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
  180. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +7 -7
  181. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -7
  182. package/dist/prosemirror/extensions/marks/markUtils.d.ts +6 -2
  183. package/dist/prosemirror/extensions/marks/markUtils.js +39 -51
  184. package/dist/prosemirror/extensions/nodes/FieldExtension.js +6 -3
  185. package/dist/prosemirror/extensions/nodes/SdtExtension.js +9 -2
  186. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
  187. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +5 -1
  188. package/dist/prosemirror/listMarker.d.ts +1 -0
  189. package/dist/prosemirror/listMarker.js +4 -2
  190. package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
  191. package/dist/prosemirror/listRenderingAttrs.js +3 -0
  192. package/dist/prosemirror/paragraphDirection.d.ts +29 -2
  193. package/dist/prosemirror/paragraphDirection.js +21 -2
  194. package/dist/prosemirror/plugins/suggestionMode.js +37 -8
  195. package/dist/prosemirror/rejoinRunCarriers.d.ts +27 -0
  196. package/dist/prosemirror/rejoinRunCarriers.js +66 -0
  197. package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +10 -0
  198. package/dist/prosemirror/runIdentityAcrossRevisions.js +90 -0
  199. package/dist/prosemirror/schema/nodes.d.ts +50 -6
  200. package/dist/prosemirror/textBoxHostParagraph.d.ts +13 -0
  201. package/dist/prosemirror/textBoxHostParagraph.js +21 -0
  202. package/dist/prosemirror/validation.js +91 -80
  203. package/dist/types/content.d.ts +2 -2
  204. package/dist/utils/createDocument.js +10 -1
  205. package/dist/utils/scriptSegments.d.ts +33 -4
  206. package/dist/utils/scriptSegments.js +39 -6
  207. package/dist/utils/textFormattingMerge.d.ts +17 -3
  208. package/dist/utils/textFormattingMerge.js +18 -12
  209. package/dist/utils/trailingText.d.ts +22 -0
  210. package/dist/utils/trailingText.js +32 -0
  211. package/package.json +2 -2
  212. package/dist/layout-engine/justifiedLineFit.d.ts +0 -7
  213. package/dist/layout-engine/justifiedLineFit.js +0 -6
@@ -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 };
@@ -1,4 +1,4 @@
1
- import { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, resolveTableCellPadding, resolveTableWidthPx, tableColumnsArePinned } from "./types.js";
1
+ import { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, MathRun, Measure, MeasuredLine, NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION, NoteAreaLayout, NoteSeparatorKind, NoteSeparatorRule, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxGradientFill, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, resolveTableCellPadding, resolveTableWidthPx, stripFlowBlockPmAnchors, tableColumnsArePinned } from "./types.js";
2
2
  import { PageState, Paginator, PaginatorOptions, createPaginator } from "./paginator.js";
3
3
  import { KeepNextChain, calculateChainHeight, computeKeepNextChains, getMidChainIndices, hasKeepLines, hasPageBreakBefore } from "./keep-together.js";
4
4
  import { resolveSectionHeaderFooterRefs } from "./headerFooterRefs.js";
@@ -42,4 +42,4 @@ declare function layoutDocument(blocks: FlowBlock[], measures: Measure[], option
42
42
  */
43
43
  declare function getHeaderRowsHeight(measure: TableMeasure, headerRowCount: number): number;
44
44
  //#endregion
45
- export { BlockId, BorderStyle, type BreakDecision, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, type KeepNextChain, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, type PageState, type Paginator, type PaginatorOptions, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionLayoutConfig, SectionPageNumbering, type SectionState, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, resolveTableCellPadding, resolveTableWidthPx, scheduleSectionBreak, tableColumnsArePinned };
45
+ export { BlockId, BorderStyle, type BreakDecision, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, type KeepNextChain, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, MathRun, Measure, MeasuredLine, NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION, NoteAreaLayout, NoteSeparatorKind, NoteSeparatorRule, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, type PageState, type Paginator, type PaginatorOptions, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionLayoutConfig, SectionPageNumbering, type SectionState, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxGradientFill, TextBoxMeasure, TextRun, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, resolveTableCellPadding, resolveTableWidthPx, scheduleSectionBreak, stripFlowBlockPmAnchors, tableColumnsArePinned };
@@ -2,9 +2,10 @@ import { emuToPixels } from "../utils/units.js";
2
2
  import { reflowFootnoteColumns } from "./footnoteColumnReflow.js";
3
3
  import { resolveSectionHeaderFooterRefs } from "./headerFooterRefs.js";
4
4
  import { calculateChainHeight, computeKeepNextChains, getMidChainIndices, hasKeepLines, hasPageBreakBefore } from "./keep-together.js";
5
- import { measuredLineAdvance } from "./lineFlow.js";
5
+ import { measuredLineAdvance, measuredLineRangeHeight } from "./lineFlow.js";
6
6
  import { resolveFloatingTablePageX } from "./measure/floatingTablePosition.js";
7
7
  import { resolveTableInlineOffset } from "./measure/tableInlinePlacement.js";
8
+ import { annotateNoteSeparators, continuesNoteArea, createNoteAreaContinuation } from "./noteAreaFlow.js";
8
9
  import { SECTION_START_PLACEMENT, createPaginator } from "./paginator.js";
9
10
  import { getParagraphFragmentPmRange } from "./paragraphFragmentRange.js";
10
11
  import { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph, paragraphsShareStyle, resolveEffectiveParagraphSpacingTree } from "./paragraphSpacing.js";
@@ -12,9 +13,9 @@ import { assertExhaustiveFlowBlock, findPageIndexContainingPmPos } from "./pmPag
12
13
  import { INITIAL_RENDERED_BREAK_STATE, PAGE_ADVANCE, reconcileAfterBlock, reconcileBreakBeforeBlock, recordReflowBoundary } from "./renderedBreakReconciliation.js";
13
14
  import { applyPendingToActive, createInitialSectionState, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, normalizeSectionBreakType, physicalColumnRegionIsShared, scheduleSectionBreak } from "./section-breaks.js";
14
15
  import { applySectionVerticalAlignment } from "./sectionVerticalAlignment.js";
15
- import { buildTableRowBreakInfo, getRowContinuationSkip, snapRowBreak } from "./tableRowBreak.js";
16
+ import { buildTableRowBreakInfo, getRowContinuationSkip, snapRowBreak, tableKeepNextOpeningHeight } from "./tableRowBreak.js";
16
17
  import { bandFragmentX, bandTopContentY, isPageFrameRelativeAnchor } from "./textBoxFlow.js";
17
- import { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, resolveTableCellPadding, resolveTableWidthPx, tableColumnsArePinned } from "./types.js";
18
+ import { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, resolveTableCellPadding, resolveTableWidthPx, stripFlowBlockPmAnchors, tableColumnsArePinned } from "./types.js";
18
19
  import { panic } from "better-result";
19
20
  //#region src/layout-engine/index.ts
20
21
  /**
@@ -147,14 +148,14 @@ function applyContextualSpacing(blocks) {
147
148
  */
148
149
  function layoutDocument(blocks, measures, options) {
149
150
  const initialLayout = layoutDocumentPass(blocks, measures, options);
150
- return applySectionVerticalAlignment(options.footnoteHeightById ? reflowFootnoteColumns({
151
+ return annotateNoteSeparators(applySectionVerticalAlignment(options.footnoteHeightById ? reflowFootnoteColumns({
151
152
  initialLayout,
152
153
  ...options.footnoteReservedHeights ? { initialReserveFloors: options.footnoteReservedHeights } : {},
153
154
  runLayout: (reserveFloors) => layoutDocumentPass(blocks, measures, {
154
155
  ...options,
155
156
  footnoteReservedHeights: reserveFloors
156
157
  })
157
- }) : initialLayout, options.sectionVerticalAlignments);
158
+ }) : initialLayout, options.sectionVerticalAlignments), blocks, measures, options.noteAreas);
158
159
  }
159
160
  function layoutDocumentPass(blocks, measures, options) {
160
161
  if (blocks.length !== measures.length) panic(`layoutDocument: expected one measure per block (blocks=${blocks.length}, measures=${measures.length})`);
@@ -193,6 +194,7 @@ function layoutDocumentPass(blocks, measures, options) {
193
194
  ...options.mirrorMargins === true ? { mirrorMargins: true } : {},
194
195
  ...options.firstPageMargins !== void 0 ? { firstPageMargins: options.firstPageMargins } : {},
195
196
  ...options.sectionEvenPageMargins !== void 0 ? { sectionEvenPageMargins: options.sectionEvenPageMargins } : {},
197
+ ...options.sectionFirstPageMargins !== void 0 ? { sectionFirstPageMargins: options.sectionFirstPageMargins } : {},
196
198
  columns: initialConfig.columns ?? DEFAULT_COLUMNS,
197
199
  pageNumbering: initialConfig.pageNumbering,
198
200
  ...options.footnoteReservedHeights !== void 0 ? { footnoteReservedHeights: options.footnoteReservedHeights } : {},
@@ -208,9 +210,11 @@ function layoutDocumentPass(blocks, measures, options) {
208
210
  let activeSectionPageHeight = initialConfig.pageSize.h;
209
211
  let activeSectionMarginBottom = initialConfig.margins.bottom;
210
212
  let renderedBreakState = INITIAL_RENDERED_BREAK_STATE;
213
+ const noteAreaContinuation = createNoteAreaContinuation(options.noteAreas, paginator);
211
214
  for (let i = 0; i < blocks.length; i++) {
212
215
  const block = blocks[i];
213
216
  const measure = measures[i];
217
+ if (noteAreaContinuation !== void 0) paginator.setPageTopContinuation(continuesNoteArea(options.noteAreas, block) ? noteAreaContinuation : void 0);
214
218
  const firstLine = measure.kind === "paragraph" ? measure.lines.at(0) : void 0;
215
219
  const firstLineAdvance = firstLine ? measuredLineAdvance(firstLine) : 0;
216
220
  const renderedBreakNeedsSnap = measure.kind === "paragraph" && (!paginator.fits(measure.totalHeight) || firstLineAdvance > 0 && paginator.getAvailableHeight() <= firstLineAdvance * RENDERED_BREAK_REFLOW_TOLERANCE_LINES);
@@ -225,13 +229,13 @@ function layoutDocumentPass(blocks, measures, options) {
225
229
  renderedBreakNeedsSnap
226
230
  });
227
231
  if (block.kind !== "pageBreak") {
228
- if (breakDecision.pageAdvance === PAGE_ADVANCE.PHYSICAL) paginator.forcePageBreak();
232
+ if (breakDecision.pageAdvance === PAGE_ADVANCE.PHYSICAL) paginator.forcePageBreak({ coalesceBlankPage: hasPageBreakBefore(block) });
229
233
  else if (breakDecision.pageAdvance === PAGE_ADVANCE.COALESCED) paginator.coalescePageBreak();
230
234
  }
231
235
  renderedBreakState = breakDecision.state;
232
236
  const chain = keepNextChains.get(i);
233
237
  if (chain && !midChainIndices.has(i)) {
234
- const chainHeight = calculateChainHeight(chain, blocks, measures);
238
+ const chainHeight = calculateChainHeight(chain, blocks, measures, paginator.getCurrentState().trailingSpacing, tableKeepNextOpeningHeight);
235
239
  const pageBeforeChainLayout = paginator.getCurrentState().page.number;
236
240
  paginator.ensureFits(chainHeight);
237
241
  if (paginator.getCurrentState().page.number > pageBeforeChainLayout) renderedBreakState = recordReflowBoundary(renderedBreakState, true);
@@ -306,6 +310,7 @@ function layoutDocumentPass(blocks, measures, options) {
306
310
  previousPage: paginator.states[pageBeforeBlockLayout - 1]?.page
307
311
  });
308
312
  }
313
+ paginator.setPageTopContinuation(void 0);
309
314
  if (paginator.states.length === 0) paginator.getCurrentState();
310
315
  return {
311
316
  pageSize,
@@ -376,6 +381,16 @@ function layoutParagraph({ block, measure, paginator, contentWidth, footnoteHeig
376
381
  }
377
382
  const spaceBefore = suppressSpaceBefore ? 0 : getParagraphSpacingBefore(block);
378
383
  const spaceAfter = getParagraphSpacingAfter(block);
384
+ let keepLinesDemand;
385
+ if (lines.length > 1 && hasKeepLines(block)) {
386
+ let footnoteHeight = 0;
387
+ for (const line of lines) footnoteHeight += getLineFootnoteRefs(block, line.fromRun, line.toRun, footnoteHeightById).height;
388
+ keepLinesDemand = {
389
+ linesHeight: measuredLineRangeHeight(lines, 0, lines.length),
390
+ footnoteHeight
391
+ };
392
+ }
393
+ let keepLinesMoveAttempted = false;
379
394
  let currentLineIndex = 0;
380
395
  while (currentLineIndex < lines.length) {
381
396
  const state = paginator.getCurrentState();
@@ -400,6 +415,19 @@ function layoutParagraph({ block, measure, paginator, contentWidth, footnoteHeig
400
415
  continue;
401
416
  }
402
417
  }
418
+ if (keepLinesDemand !== void 0 && !keepLinesMoveAttempted && currentLineIndex === 0 && state.cursorY !== state.topMargin) {
419
+ const wholeHeight = keepLinesDemand.linesHeight + projectedFootnoteReserveGrowth(state, keepLinesDemand.footnoteHeight);
420
+ const collapsedLead = collapseParagraphSpacing({
421
+ before: spaceBefore,
422
+ after: state.trailingSpacing
423
+ });
424
+ const columnCapacity = state.contentBottom - state.topMargin;
425
+ if (collapsedLead + wholeHeight > availableHeight && spaceBefore + wholeHeight <= columnCapacity) {
426
+ keepLinesMoveAttempted = true;
427
+ paginator.ensureFits(collapsedLead + wholeHeight);
428
+ continue;
429
+ }
430
+ }
403
431
  let linesHeight = 0;
404
432
  let linesFnHeight = 0;
405
433
  const linesFnIds = [];
@@ -561,13 +589,19 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
561
589
  return previous?.kind === "table" && previous.blockId === block.id && previous.toRow === rowIndex && previous.y + previous.height === state.cursorY;
562
590
  };
563
591
  const shouldRepeatHeaderRows = (rowIndex, consumed, state = paginator.getCurrentState()) => headerRowCount > 0 && rowIndex >= headerRowCount && !(consumed === 0 && hasAdjacentPriorTableRows(rowIndex, state));
592
+ const rowOpensFlowRegion = (rowIndex, state) => {
593
+ if (state.cursorY === state.topMargin) return true;
594
+ const previous = state.page.fragments.at(-1);
595
+ return rowIndex > 0 && rowIndex <= headerRowCount && previous?.kind === "table" && previous.blockId === block.id && previous.fromRow === 0 && previous.toRow === rowIndex && previous.y === state.topMargin && previous.y + previous.height === state.cursorY;
596
+ };
564
597
  const canSplitRow = (rowIndex, state = paginator.getCurrentState()) => {
565
598
  const row = rows[rowIndex];
566
599
  const sourceRow = block.rows[rowIndex];
567
- if (!row || !sourceRow || sourceRow.cantSplit || sourceRow.isHeader || verticallyMergedRows.has(rowIndex)) return false;
600
+ if (!row || !sourceRow || sourceRow.isHeader || verticallyMergedRows.has(rowIndex)) return false;
568
601
  if ((breakInfo.breakOffsets[rowIndex]?.length ?? 0) <= 1) return false;
569
602
  const freshHeaderOverhead = headerRowCount > 0 && rowIndex >= headerRowCount ? headerRowsHeight : 0;
570
603
  const oversized = row.height + freshHeaderOverhead > getCurrentRowCapacity(state);
604
+ if (sourceRow.cantSplit) return oversized && sourceRow.heightRule !== "exact" && rowOpensFlowRegion(rowIndex, state);
571
605
  if (!oversized && (state.footnoteHeight > 0 || (rowFootnoteIds[rowIndex]?.length ?? 0) > 0)) return false;
572
606
  const currentHeaderOverhead = shouldRepeatHeaderRows(rowIndex, 0, state) ? headerRowsHeight : 0;
573
607
  const currentAvailableHeight = paginator.getAvailableHeight() - currentHeaderOverhead - state.trailingSpacing;
@@ -955,18 +989,44 @@ function layoutTextBox(block, measure, { paginator, sectionMarginTop, sectionPag
955
989
  paginator.addUnflowedFragment(fragment);
956
990
  return;
957
991
  }
958
- const state = paginator.ensureFits(measure.height);
992
+ const effect = block.effectExtent;
993
+ const occupiedHeight = measure.height + (effect?.top ?? 0) + (effect?.bottom ?? 0);
994
+ const occupiedWidth = measure.width + (effect?.left ?? 0) + (effect?.right ?? 0);
995
+ const host = block.hostParagraph;
959
996
  const fragment = {
960
997
  kind: "textBox",
961
998
  blockId: block.id,
962
- x: paginator.getColumnX(state.columnIndex),
999
+ x: 0,
963
1000
  y: 0,
964
1001
  width: measure.width,
965
1002
  height: measure.height,
966
1003
  ...block.pmStart !== void 0 ? { pmStart: block.pmStart } : {},
967
1004
  ...block.pmEnd !== void 0 ? { pmEnd: block.pmEnd } : {}
968
1005
  };
969
- fragment.y = paginator.addFragment(fragment, measure.height, 0, 0).y;
1006
+ const result = paginator.addFragment(fragment, occupiedHeight, host?.spacing?.before ?? 0, host?.spacing?.after ?? 0);
1007
+ fragment.x = result.x + hostParagraphLineOffset(host, paginator.columnWidth, occupiedWidth) + (effect?.left ?? 0);
1008
+ fragment.y = result.y + (effect?.top ?? 0);
1009
+ }
1010
+ /**
1011
+ * Where a line holding only an object of `width` starts within a column:
1012
+ * after the host paragraph's start and first-line indents, then aligned in
1013
+ * what remains before its end indent.
1014
+ */
1015
+ function hostParagraphLineOffset(host, columnWidth, width) {
1016
+ if (!host) return 0;
1017
+ const indent = host.indent;
1018
+ const start = (indent?.left ?? 0) + (indent?.firstLine ?? 0) - (indent?.hanging ?? 0);
1019
+ const slack = Math.max(0, columnWidth - start - (indent?.right ?? 0) - width);
1020
+ switch (host.alignment) {
1021
+ case "center": return start + slack / 2;
1022
+ case "right": return start + slack;
1023
+ case "left":
1024
+ case "justify":
1025
+ case void 0: return start;
1026
+ default:
1027
+ host.alignment;
1028
+ return start;
1029
+ }
970
1030
  }
971
1031
  /**
972
1032
  * A topAndBottom text box whose vertical anchor pins it to the page frame
@@ -1011,6 +1071,32 @@ function startSectionInPlace(paginator, nextSectionConfig, nextSectionIndex) {
1011
1071
  paginator.retargetCurrentBlankPage();
1012
1072
  }
1013
1073
  /**
1074
+ * Whether a section opening on `target` would give two consecutive sheets page
1075
+ * numbers that are both odd or both even under `w:evenAndOddHeaders` (§17.10.1). Odd and
1076
+ * even page numbers alternate between right- and left-hand sheets, so a
1077
+ * `w:pgNumType w:start` restart that repeats whether the previous sheet's number is odd or even
1078
+ * needs a blank sheet between them.
1079
+ */
1080
+ function sectionStartRepeatsOddEven(paginator, target) {
1081
+ if (target.page.headerFooterRefs?.evenAndOddHeaders !== true) return false;
1082
+ const previous = paginator.states.at(-2)?.page;
1083
+ if (previous === void 0 || previous.number !== target.page.number - 1) return false;
1084
+ return previous.logicalNumber % 2 === target.page.logicalNumber % 2;
1085
+ }
1086
+ /**
1087
+ * Keep the blank sheet the section break opened as a filler with no page
1088
+ * furniture, and open the section again on the following sheet so its first
1089
+ * page keeps the restarted page number.
1090
+ */
1091
+ function insertOddEvenFillerPage(paginator, filler, sectionIndex, sectionConfig) {
1092
+ filler.page.headerFooterRefs = {};
1093
+ paginator.startSection({
1094
+ sectionIndex,
1095
+ pageNumbering: sectionConfig.pageNumbering
1096
+ });
1097
+ paginator.forcePageBreak();
1098
+ }
1099
+ /**
1014
1100
  * Handle a section break block.
1015
1101
  * @param block - The section break block (current section's properties)
1016
1102
  * @param paginator - The paginator instance
@@ -1019,14 +1105,16 @@ function startSectionInPlace(paginator, nextSectionConfig, nextSectionIndex) {
1019
1105
  */
1020
1106
  function handleSectionBreak(_block, paginator, nextSectionConfig, nextSectionType, nextSectionIndex) {
1021
1107
  switch (nextSectionType) {
1022
- case "nextPage":
1108
+ case "nextPage": {
1023
1109
  paginator.updatePageLayout(nextSectionConfig.pageSize, nextSectionConfig.margins);
1024
1110
  if (nextSectionIndex !== void 0) paginator.startSection({
1025
1111
  sectionIndex: nextSectionIndex,
1026
1112
  pageNumbering: nextSectionConfig.pageNumbering
1027
1113
  });
1028
- paginator.forcePageBreak({ coalesceBlankPage: true });
1114
+ const target = paginator.forcePageBreak({ coalesceBlankPage: true });
1115
+ if (nextSectionIndex !== void 0 && sectionStartRepeatsOddEven(paginator, target)) insertOddEvenFillerPage(paginator, target, nextSectionIndex, nextSectionConfig);
1029
1116
  break;
1117
+ }
1030
1118
  case "evenPage":
1031
1119
  if (paginator.forcePageBreak({ coalesceBlankPage: true }).page.number % 2 !== 0) paginator.forcePageBreak();
1032
1120
  paginator.updatePageLayout(nextSectionConfig.pageSize, nextSectionConfig.margins);
@@ -1072,4 +1160,4 @@ function handleSectionBreak(_block, paginator, nextSectionConfig, nextSectionTyp
1072
1160
  paginator.updateColumns(nextSectionConfig.columns ?? DEFAULT_COLUMNS);
1073
1161
  }
1074
1162
  //#endregion
1075
- export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, resolveTableCellPadding, resolveTableWidthPx, scheduleSectionBreak, tableColumnsArePinned };
1163
+ export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, resolveTableCellPadding, resolveTableWidthPx, scheduleSectionBreak, stripFlowBlockPmAnchors, tableColumnsArePinned };
@@ -1,5 +1,5 @@
1
1
  import { ParagraphAttrs } from "./types.js";
2
2
  //#region src/layout-engine/justificationCompatibility.d.ts
3
- declare function resolveJustificationCompatibility(compatibilityMode: number | undefined): NonNullable<ParagraphAttrs["justificationCompatibility"]> | undefined;
3
+ declare const resolveJustificationCompatibility: (compatibilityMode: number | undefined) => NonNullable<ParagraphAttrs["justificationCompatibility"]> | undefined;
4
4
  //#endregion
5
5
  export { resolveJustificationCompatibility };
@@ -1,8 +1,12 @@
1
1
  //#region src/layout-engine/justificationCompatibility.ts
2
- const LEGACY_JUSTIFICATION_MAX_COMPATIBILITY_MODE = 14;
3
- function resolveJustificationCompatibility(compatibilityMode) {
4
- if (compatibilityMode === void 0 || compatibilityMode > LEGACY_JUSTIFICATION_MAX_COMPATIBILITY_MODE) return;
5
- return { type: "legacy" };
6
- }
2
+ /**
3
+ * First `compatibilityMode` that fits justified lines with the current rules.
4
+ *
5
+ * Earlier modes keep strict justified fitting. A document that declares no
6
+ * `compatSetting` at all is read with the oldest semantics, so it takes the
7
+ * legacy rule too.
8
+ */
9
+ const FIRST_MODERN_JUSTIFICATION_COMPATIBILITY_MODE = 15;
10
+ const resolveJustificationCompatibility = (compatibilityMode) => compatibilityMode !== void 0 && compatibilityMode >= FIRST_MODERN_JUSTIFICATION_COMPATIBILITY_MODE ? void 0 : { type: "legacy" };
7
11
  //#endregion
8
12
  export { resolveJustificationCompatibility };
@@ -1,4 +1,4 @@
1
- import { FlowBlock, Measure } from "./types.js";
1
+ import { FlowBlock, Measure, TableBlock, TableMeasure } from "./types.js";
2
2
  //#region src/layout-engine/keep-together.d.ts
3
3
  /**
4
4
  * A chain of paragraphs that Word keeps with following content. This includes
@@ -30,12 +30,21 @@ declare function computeKeepNextChains(blocks: FlowBlock[]): Map<number, KeepNex
30
30
  * Calculate the height needed to keep consecutive paragraph boundaries from
31
31
  * breaking across pages.
32
32
  *
33
- * Single-line and `keepLines` members are indivisible, so the reservation must
34
- * continue through them to the anchor's first line. A multi-line member without
35
- * `keepLines` may itself split; only its first line is needed to satisfy the
36
- * preceding member's `keepNext`, and the chain can stop there.
33
+ * Each successor (later member or the anchor) must bring its minimum
34
+ * unbreakable opening onto the page: see {@link minimumOpeningLineCount}. When
35
+ * that opening is the whole paragraph, the successor is indivisible and the
36
+ * reservation continues through it; otherwise the chain stops after the
37
+ * opening, because a later split cannot separate it from its predecessor.
38
+ *
39
+ * A table anchor contributes `tableOpeningHeight`: the part of the table
40
+ * that must start on the same page as the paragraph before it.
41
+ *
42
+ * A paragraph measure's `totalHeight` already includes its own spacing before
43
+ * and after. The chain accounts for spacing separately (collapsing each gap to
44
+ * the larger side, as the paginator does), so members contribute only their
45
+ * line advances; otherwise every gap would be reserved twice.
37
46
  */
38
- declare function calculateChainHeight(chain: KeepNextChain, blocks: FlowBlock[], measures: Measure[]): number;
47
+ declare function calculateChainHeight(chain: KeepNextChain, blocks: FlowBlock[], measures: Measure[], incomingSpacing?: number, tableOpeningHeight?: (block: TableBlock, measure: TableMeasure) => number): number;
39
48
  /**
40
49
  * Get the set of indices that are mid-chain (not chain starters).
41
50
  * These should skip the keepNext check since their chain starter already decided.
@@ -1,3 +1,4 @@
1
+ import { measuredLineRangeHeight } from "./lineFlow.js";
1
2
  import { collapseParagraphSpacing, isAuthoredEmptyParagraph, isEmptyParagraph } from "./paragraphSpacing.js";
2
3
  //#region src/layout-engine/keep-together.ts
3
4
  /**
@@ -6,6 +7,8 @@ import { collapseParagraphSpacing, isAuthoredEmptyParagraph, isEmptyParagraph }
6
7
  * DOCX paragraphs can have keepNext (keep with next paragraph) and keepLines
7
8
  * (keep all lines together) properties that affect pagination.
8
9
  */
10
+ /** Lines §17.3.1.44 widow control keeps on each side of a paragraph split. */
11
+ const MIN_WIDOW_CONTROL_SPLIT_LINES = 2;
9
12
  /**
10
13
  * Whether an empty paragraph is only a structural separator through which a
11
14
  * keep-with-next chain may pass. An explicitly styled or directly formatted
@@ -74,23 +77,50 @@ function computeKeepNextChains(blocks) {
74
77
  return chains;
75
78
  }
76
79
  /**
80
+ * Lines of a paragraph that must share a page with the paragraph before it for
81
+ * a `w:keepNext` link (§17.3.1.15) to hold.
82
+ *
83
+ * - `w:keepLines` (§17.3.1.14): the paragraph never splits, so all of it.
84
+ * - `w:widowControl` (§17.3.1.44): a split leaves at least two lines on each
85
+ * side, so a paragraph shorter than four lines cannot split at all and a
86
+ * longer one needs its first two lines.
87
+ * - Otherwise a single line.
88
+ */
89
+ function minimumOpeningLineCount(block, measure) {
90
+ const lineCount = measure.lines.length;
91
+ if (block.attrs?.keepLines === true) return lineCount;
92
+ if (block.attrs?.widowControl !== false) return lineCount < MIN_WIDOW_CONTROL_SPLIT_LINES * 2 ? lineCount : MIN_WIDOW_CONTROL_SPLIT_LINES;
93
+ return Math.min(1, lineCount);
94
+ }
95
+ /**
77
96
  * Calculate the height needed to keep consecutive paragraph boundaries from
78
97
  * breaking across pages.
79
98
  *
80
- * Single-line and `keepLines` members are indivisible, so the reservation must
81
- * continue through them to the anchor's first line. A multi-line member without
82
- * `keepLines` may itself split; only its first line is needed to satisfy the
83
- * preceding member's `keepNext`, and the chain can stop there.
99
+ * Each successor (later member or the anchor) must bring its minimum
100
+ * unbreakable opening onto the page: see {@link minimumOpeningLineCount}. When
101
+ * that opening is the whole paragraph, the successor is indivisible and the
102
+ * reservation continues through it; otherwise the chain stops after the
103
+ * opening, because a later split cannot separate it from its predecessor.
104
+ *
105
+ * A table anchor contributes `tableOpeningHeight`: the part of the table
106
+ * that must start on the same page as the paragraph before it.
107
+ *
108
+ * A paragraph measure's `totalHeight` already includes its own spacing before
109
+ * and after. The chain accounts for spacing separately (collapsing each gap to
110
+ * the larger side, as the paginator does), so members contribute only their
111
+ * line advances; otherwise every gap would be reserved twice.
84
112
  */
85
- function calculateChainHeight(chain, blocks, measures) {
113
+ function calculateChainHeight(chain, blocks, measures, incomingSpacing = 0, tableOpeningHeight) {
86
114
  const firstMemberIndex = chain.memberIndices.at(0);
87
115
  if (firstMemberIndex === void 0) return 0;
88
116
  const firstBlock = blocks[firstMemberIndex];
89
117
  const firstMeasure = measures[firstMemberIndex];
90
118
  if (firstBlock?.kind !== "paragraph" || firstMeasure?.kind !== "paragraph") return 0;
91
- let totalHeight = (firstBlock.attrs?.spacing?.before ?? 0) + firstMeasure.totalHeight;
119
+ let totalHeight = collapseParagraphSpacing({
120
+ before: firstBlock.attrs?.spacing?.before ?? 0,
121
+ after: incomingSpacing
122
+ }) + measuredLineRangeHeight(firstMeasure.lines, 0, firstMeasure.lines.length);
92
123
  let trailingSpacing = firstBlock.attrs?.spacing?.after ?? 0;
93
- const startsWithTrailingTableSeparator = blocks[firstMemberIndex - 1]?.kind === "table" && isEmptyParagraph(firstBlock) && !firstBlock.attrs?.keepNext;
94
124
  const successorIndices = [...chain.memberIndices.slice(1)];
95
125
  if (chain.anchorIndex !== -1) successorIndices.push(chain.anchorIndex);
96
126
  for (let index = 0; index < successorIndices.length; index++) {
@@ -98,21 +128,18 @@ function calculateChainHeight(chain, blocks, measures) {
98
128
  if (successorIndex === void 0) continue;
99
129
  const successorBlock = blocks[successorIndex];
100
130
  const successorMeasure = measures[successorIndex];
131
+ if (tableOpeningHeight !== void 0 && successorBlock?.kind === "table" && successorMeasure?.kind === "table" && successorBlock.floating === void 0) return totalHeight + trailingSpacing + tableOpeningHeight(successorBlock, successorMeasure);
101
132
  if (successorBlock?.kind !== "paragraph" || successorMeasure?.kind !== "paragraph") return totalHeight;
102
133
  totalHeight += collapseParagraphSpacing({
103
134
  before: successorBlock.attrs?.spacing?.before ?? 0,
104
135
  after: trailingSpacing
105
136
  });
106
- const firstLine = successorMeasure.lines.at(0);
107
- if (!firstLine) return totalHeight;
137
+ const lineCount = successorMeasure.lines.length;
138
+ if (lineCount === 0) return totalHeight;
108
139
  const isAnchor = index === successorIndices.length - 1 && chain.anchorIndex !== -1;
109
- const isSplittable = successorMeasure.lines.length > 1 && !successorBlock.attrs?.keepLines;
110
- if (isAnchor && startsWithTrailingTableSeparator && successorBlock.attrs?.widowControl !== false && successorMeasure.lines.length > 1) {
111
- const secondLine = successorMeasure.lines.at(1);
112
- return totalHeight + firstLine.lineHeight + (secondLine?.lineHeight ?? 0);
113
- }
114
- if (isAnchor || isSplittable) return totalHeight + firstLine.lineHeight;
115
- totalHeight += successorMeasure.totalHeight;
140
+ const openingLines = minimumOpeningLineCount(successorBlock, successorMeasure);
141
+ totalHeight += measuredLineRangeHeight(successorMeasure.lines, 0, openingLines);
142
+ if (isAnchor || openingLines < lineCount) return totalHeight;
116
143
  trailingSpacing = successorBlock.attrs?.spacing?.after ?? 0;
117
144
  }
118
145
  return totalHeight;
@@ -1,6 +1,6 @@
1
1
  import { FlowBlock } from "./types.js";
2
2
  //#region src/layout-engine/layoutInstrumentation.d.ts
3
- type LayoutRunReason = "font-ready" | "initial" | "layout-input" | "manual" | "transaction";
3
+ type LayoutRunReason = "font-ready" | "hyphenation-ready" | "initial" | "layout-input" | "manual" | "transaction";
4
4
  type LayoutPhase = "flow-blocks" | "header-footer" | "initial-fonts" | "layout-document" | "measure-blocks" | "render-pages";
5
5
  type HiddenEditorStateReason = "external-document" | "mount";
6
6
  type HiddenEditorPhase = "editor-state" | "editor-view" | "to-prose-doc" | "update-state";
@@ -18,6 +18,10 @@ type LayoutInstrumentation = {
18
18
  onHiddenEditorStateCreate?: (event: {
19
19
  reason: HiddenEditorStateReason;
20
20
  }) => void;
21
+ onHyphenationDictionaryError?: (event: {
22
+ dictionary: string;
23
+ message: string;
24
+ }) => void;
21
25
  onLayoutComplete?: (event: {
22
26
  reason: LayoutRunReason;
23
27
  }) => void;
@@ -51,9 +55,11 @@ declare function recordMeasureBlockError(blockIndex: number, block: FlowBlock, e
51
55
  declare function recordLayoutComplete(reason?: LayoutRunReason): void;
52
56
  declare function recordLayoutStart(reason: LayoutRunReason): void;
53
57
  declare function recordLayoutError(reason: LayoutRunReason, error: unknown): void;
58
+ /** A dictionary failed to load; layout continues without hyphenating that language. */
59
+ declare function recordHyphenationDictionaryError(dictionary: string, error: Error): void;
54
60
  declare function recordLayoutPhase(reason: LayoutRunReason, phase: LayoutPhase, durationMs: number): void;
55
61
  declare function recordHiddenEditorStateCreate(reason: HiddenEditorStateReason): void;
56
62
  declare function recordHiddenEditorPhase(reason: HiddenEditorStateReason, phase: HiddenEditorPhase, durationMs: number): void;
57
63
  declare function recordDocumentLoadPhase(phase: DocumentLoadPhase, durationMs: number): void;
58
64
  //#endregion
59
- export { DocumentLoadPhase, HiddenEditorPhase, HiddenEditorStateReason, LayoutInstrumentation, LayoutPhase, LayoutRunReason, recordDocumentLoadPhase, recordHiddenEditorPhase, recordHiddenEditorStateCreate, recordLayoutComplete, recordLayoutError, recordLayoutPhase, recordLayoutStart, recordMeasureBlock, recordMeasureBlockError };
65
+ export { DocumentLoadPhase, HiddenEditorPhase, HiddenEditorStateReason, LayoutInstrumentation, LayoutPhase, LayoutRunReason, recordDocumentLoadPhase, recordHiddenEditorPhase, recordHiddenEditorStateCreate, recordHyphenationDictionaryError, recordLayoutComplete, recordLayoutError, recordLayoutPhase, recordLayoutStart, recordMeasureBlock, recordMeasureBlockError };
@@ -25,6 +25,13 @@ function recordLayoutError(reason, error) {
25
25
  reason
26
26
  });
27
27
  }
28
+ /** A dictionary failed to load; layout continues without hyphenating that language. */
29
+ function recordHyphenationDictionaryError(dictionary, error) {
30
+ globalThis.__folioLayoutInstrumentation?.onHyphenationDictionaryError?.({
31
+ dictionary,
32
+ message: error.message
33
+ });
34
+ }
28
35
  function recordLayoutPhase(reason, phase, durationMs) {
29
36
  globalThis.__folioLayoutInstrumentation?.onLayoutPhase?.({
30
37
  durationMs,
@@ -49,4 +56,4 @@ function recordDocumentLoadPhase(phase, durationMs) {
49
56
  });
50
57
  }
51
58
  //#endregion
52
- export { recordDocumentLoadPhase, recordHiddenEditorPhase, recordHiddenEditorStateCreate, recordLayoutComplete, recordLayoutError, recordLayoutPhase, recordLayoutStart, recordMeasureBlock, recordMeasureBlockError };
59
+ export { recordDocumentLoadPhase, recordHiddenEditorPhase, recordHiddenEditorStateCreate, recordHyphenationDictionaryError, recordLayoutComplete, recordLayoutError, recordLayoutPhase, recordLayoutStart, recordMeasureBlock, recordMeasureBlockError };