@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
@@ -53,6 +53,7 @@ function getTableCellFloatingImages(cell, cellMeasure, contentWidth, resolvePosi
53
53
  const distLeft = run.distLeft ?? 12;
54
54
  const distRight = run.distRight ?? 12;
55
55
  const verticalOriginY = run.position?.vertical?.relativeTo === "paragraph" ? placement.top : placement.contentTop;
56
+ if (run.layoutInCell === false && !resolvePosition) continue;
56
57
  const resolved = run.layoutInCell === false && resolvePosition ? resolvePosition(run, verticalOriginY) : resolveCellScopedPosition({
57
58
  run,
58
59
  paragraphY: verticalOriginY,
@@ -82,6 +83,7 @@ function getTableCellFloatingImages(cell, cellMeasure, contentWidth, resolvePosi
82
83
  distLeft,
83
84
  distRight,
84
85
  wrapText,
86
+ ...run.wrapType !== void 0 ? { wrapType: run.wrapType } : {},
85
87
  ...run.pmStart !== void 0 ? { pmStart: run.pmStart } : {},
86
88
  ...run.pmEnd !== void 0 ? { pmEnd: run.pmEnd } : {}
87
89
  });
@@ -89,26 +91,56 @@ function getTableCellFloatingImages(cell, cellMeasure, contentWidth, resolvePosi
89
91
  }
90
92
  return result;
91
93
  }
94
+ /**
95
+ * The text exclusion one cell-scoped picture casts, in cell content
96
+ * coordinates, or `undefined` when text ignores it.
97
+ *
98
+ * - `wp:wrapNone` (`behind` / `inFront`): text paints over or under the
99
+ * picture, so nothing is excluded.
100
+ * - `wp:wrapTopAndBottom`: no text beside the picture; lines it overlaps move
101
+ * below it.
102
+ * - `wp:wrapSquare` and the contour modes: text runs on the picture's open
103
+ * side. When the picture and its wrap distances cover the cell's content
104
+ * width there is no open side, and the text moves below the picture as for
105
+ * `wrapTopAndBottom`.
106
+ */
107
+ function tableCellFloatingZone(img, contentWidth) {
108
+ if (img.wrapType === "behind" || img.wrapType === "inFront") return;
109
+ const rectLeft = img.x - img.distLeft;
110
+ const rectRight = img.x + img.width + img.distRight;
111
+ const topY = img.y - img.distTop;
112
+ const bottomY = img.y + img.height + img.distBottom;
113
+ const belowPicture = {
114
+ leftMargin: 0,
115
+ rightMargin: 0,
116
+ topY,
117
+ bottomY,
118
+ fullWidthBlock: true
119
+ };
120
+ if (img.wrapType === "topAndBottom") return belowPicture;
121
+ let leftMargin = 0;
122
+ let rightMargin = 0;
123
+ const wrapText = img.wrapText ?? "bothSides";
124
+ if (wrapText === "right") leftMargin = rectRight;
125
+ else if (wrapText === "left") rightMargin = contentWidth - rectLeft;
126
+ else if (img.side === "left") leftMargin = rectRight;
127
+ else rightMargin = contentWidth - rectLeft;
128
+ if (rectRight > 0 && rectLeft < contentWidth && Math.max(leftMargin, rightMargin) >= contentWidth) return belowPicture;
129
+ const clamped = clampFloatingWrapMargins(leftMargin, rightMargin, contentWidth);
130
+ return {
131
+ leftMargin: clamped.leftMargin,
132
+ rightMargin: clamped.rightMargin,
133
+ topY,
134
+ bottomY
135
+ };
136
+ }
92
137
  function buildTableCellFloatingZones(floatingImages, contentWidth) {
93
- return floatingImages.map((img) => {
94
- const rectRight = img.x + img.width + img.distRight;
95
- const rectTop = img.y - img.distTop;
96
- const rectBottom = img.y + img.height + img.distBottom;
97
- let leftMargin = 0;
98
- let rightMargin = 0;
99
- const wrapText = img.wrapText ?? "bothSides";
100
- if (wrapText === "right") leftMargin = rectRight;
101
- else if (wrapText === "left") rightMargin = contentWidth - (img.x - img.distLeft);
102
- else if (img.side === "left") leftMargin = rectRight;
103
- else rightMargin = contentWidth - (img.x - img.distLeft);
104
- const clamped = clampFloatingWrapMargins(leftMargin, rightMargin, contentWidth);
105
- return {
106
- leftMargin: clamped.leftMargin,
107
- rightMargin: clamped.rightMargin,
108
- topY: rectTop,
109
- bottomY: rectBottom
110
- };
111
- });
138
+ const zones = [];
139
+ for (const img of floatingImages) {
140
+ const zone = tableCellFloatingZone(img, contentWidth);
141
+ if (zone !== void 0) zones.push(zone);
142
+ }
143
+ return zones;
112
144
  }
113
145
  //#endregion
114
146
  export { buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages };
@@ -16,13 +16,38 @@ type BuildTableCellPlacementsOptions = {
16
16
  grid: TableCellGrid;
17
17
  columnWidths: readonly number[];
18
18
  bidi: boolean;
19
+ /** `w:tblCellSpacing` in pixels; see {@link getTableCellSpacingInsetsX}. */
20
+ cellSpacing?: number | undefined;
19
21
  };
22
+ type TableCellSpacingInsets = {
23
+ readonly start: number;
24
+ readonly end: number;
25
+ };
26
+ type TableCellSpacingInsetsY = {
27
+ readonly top: number;
28
+ readonly bottom: number;
29
+ };
30
+ /**
31
+ * Logical inline insets of a cell box inside its grid slot under
32
+ * `w:tblCellSpacing` (§17.4.43). The spacing separates every cell from its
33
+ * neighbours and from the table edge: each cell keeps one unit on each side of
34
+ * its slot, and a slot at the table's leading or trailing grid edge keeps one
35
+ * more for the edge itself.
36
+ */
37
+ declare const getTableCellSpacingInsetsX: (cellSpacing: number | undefined, sourceColumn: number, columnSpan: number, columnCount: number) => TableCellSpacingInsets;
38
+ /**
39
+ * Block-direction insets of a cell box inside the rows it spans, the
40
+ * `w:tblCellSpacing` counterpart of {@link getTableCellSpacingInsetsX}: one
41
+ * unit above and below every cell, plus one more at the table's first and
42
+ * last rows.
43
+ */
44
+ declare const getTableCellSpacingInsetsY: (cellSpacing: number | undefined, rowIndex: number, rowSpan: number, rowCount: number) => TableCellSpacingInsetsY;
20
45
  declare const buildTableCellGrid: (rows: readonly TableRow[], columnCount: number) => TableCellGrid;
21
46
  declare const getFirstAvailableColumn: (grid: TableCellGrid, rowIndex: number, startingColumn: number) => number;
22
47
  declare const getSourceCellAt: (grid: TableCellGrid, rowIndex: number, columnIndex: number) => TableCell | undefined;
23
48
  declare const getSourceCellColumn: (grid: TableCellGrid, cell: TableCell) => number | undefined;
24
49
  /** Resolve all source cells to canonical logical columns and physical boxes. */
25
- declare const buildTableCellPlacements: ({ grid, columnWidths, bidi }: BuildTableCellPlacementsOptions) => TableCellPlacements;
26
- declare const getTableCellVerticalBorderHeight: (grid: TableCellGrid, cell: TableCell | undefined, rowIndex: number) => number;
50
+ declare const buildTableCellPlacements: ({ grid, columnWidths, bidi, cellSpacing }: BuildTableCellPlacementsOptions) => TableCellPlacements;
51
+ declare const getTableCellVerticalBorderHeight: (grid: TableCellGrid, cell: TableCell | undefined, rowIndex: number, separated?: boolean) => number;
27
52
  //#endregion
28
- export { TableCellGrid, TableCellPlacement, TableCellPlacements, buildTableCellGrid, buildTableCellPlacements, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellVerticalBorderHeight };
53
+ export { TableCellGrid, TableCellPlacement, TableCellPlacements, TableCellSpacingInsets, TableCellSpacingInsetsY, buildTableCellGrid, buildTableCellPlacements, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellSpacingInsetsX, getTableCellSpacingInsetsY, getTableCellVerticalBorderHeight };
@@ -1,5 +1,41 @@
1
1
  import { paintsCssBorder } from "../../utils/borderCss.js";
2
2
  //#region src/layout-engine/measure/tableCellGrid.ts
3
+ const spacingUnit = (cellSpacing) => cellSpacing !== void 0 && Number.isFinite(cellSpacing) && cellSpacing > 0 ? cellSpacing : 0;
4
+ /**
5
+ * Logical inline insets of a cell box inside its grid slot under
6
+ * `w:tblCellSpacing` (§17.4.43). The spacing separates every cell from its
7
+ * neighbours and from the table edge: each cell keeps one unit on each side of
8
+ * its slot, and a slot at the table's leading or trailing grid edge keeps one
9
+ * more for the edge itself.
10
+ */
11
+ const getTableCellSpacingInsetsX = (cellSpacing, sourceColumn, columnSpan, columnCount) => {
12
+ const unit = spacingUnit(cellSpacing);
13
+ if (unit === 0) return {
14
+ start: 0,
15
+ end: 0
16
+ };
17
+ return {
18
+ start: sourceColumn <= 0 ? unit * 2 : unit,
19
+ end: sourceColumn + columnSpan >= columnCount ? unit * 2 : unit
20
+ };
21
+ };
22
+ /**
23
+ * Block-direction insets of a cell box inside the rows it spans, the
24
+ * `w:tblCellSpacing` counterpart of {@link getTableCellSpacingInsetsX}: one
25
+ * unit above and below every cell, plus one more at the table's first and
26
+ * last rows.
27
+ */
28
+ const getTableCellSpacingInsetsY = (cellSpacing, rowIndex, rowSpan, rowCount) => {
29
+ const unit = spacingUnit(cellSpacing);
30
+ if (unit === 0) return {
31
+ top: 0,
32
+ bottom: 0
33
+ };
34
+ return {
35
+ top: rowIndex <= 0 ? unit * 2 : unit,
36
+ bottom: rowIndex + Math.max(1, rowSpan) >= rowCount ? unit * 2 : unit
37
+ };
38
+ };
3
39
  const buildTableCellGrid = (rows, columnCount) => {
4
40
  const occupiedColumnsByRow = /* @__PURE__ */ new Map();
5
41
  const sourceCellsByRow = /* @__PURE__ */ new Map();
@@ -36,7 +72,7 @@ const getFirstAvailableColumn = (grid, rowIndex, startingColumn) => firstAvailab
36
72
  const getSourceCellAt = (grid, rowIndex, columnIndex) => grid.sourceCellsByRow.get(rowIndex)?.get(columnIndex);
37
73
  const getSourceCellColumn = (grid, cell) => grid.sourceColumnsByCell.get(cell);
38
74
  /** Resolve all source cells to canonical logical columns and physical boxes. */
39
- const buildTableCellPlacements = ({ grid, columnWidths, bidi }) => {
75
+ const buildTableCellPlacements = ({ grid, columnWidths, bidi, cellSpacing }) => {
40
76
  const columnOffsets = [0];
41
77
  for (const columnWidth of columnWidths) columnOffsets.push((columnOffsets.at(-1) ?? 0) + columnWidth);
42
78
  const tableWidth = columnOffsets.at(-1) ?? 0;
@@ -45,8 +81,11 @@ const buildTableCellPlacements = ({ grid, columnWidths, bidi }) => {
45
81
  if (sourceColumn < 0 || sourceColumn >= columnWidths.length) continue;
46
82
  const declaredColumnSpan = Math.max(1, Math.trunc(cell.colSpan ?? 1));
47
83
  const columnSpan = Math.min(declaredColumnSpan, columnWidths.length - sourceColumn);
48
- const logicalLeft = columnOffsets.at(sourceColumn) ?? 0;
49
- const logicalRight = columnOffsets.at(sourceColumn + columnSpan) ?? logicalLeft;
84
+ const slotLeft = columnOffsets.at(sourceColumn) ?? 0;
85
+ const slotRight = columnOffsets.at(sourceColumn + columnSpan) ?? slotLeft;
86
+ const insets = getTableCellSpacingInsetsX(cellSpacing, sourceColumn, columnSpan, columnWidths.length);
87
+ const logicalLeft = Math.min(slotRight, slotLeft + insets.start);
88
+ const logicalRight = Math.max(logicalLeft, slotRight - insets.end);
50
89
  const width = logicalRight - logicalLeft;
51
90
  placements.set(cell, {
52
91
  sourceColumn,
@@ -57,7 +96,8 @@ const buildTableCellPlacements = ({ grid, columnWidths, bidi }) => {
57
96
  }
58
97
  return placements;
59
98
  };
60
- const getTableCellVerticalBorderHeight = (grid, cell, rowIndex) => {
99
+ const getTableCellVerticalBorderHeight = (grid, cell, rowIndex, separated = false) => {
100
+ if (separated) return (cell?.borders?.top?.width ?? 0) + (cell?.borders?.bottom?.width ?? 0);
61
101
  const sourceColumn = cell ? getSourceCellColumn(grid, cell) : void 0;
62
102
  const aboveBottom = (sourceColumn === void 0 ? void 0 : getSourceCellAt(grid, rowIndex - 1, sourceColumn))?.borders?.bottom;
63
103
  const aboveOwnsEdge = aboveBottom !== void 0 && paintsCssBorder(aboveBottom.style);
@@ -83,4 +123,4 @@ const getOrCreateOccupiedRow = (occupiedColumnsByRow, rowIndex) => {
83
123
  return occupiedColumns;
84
124
  };
85
125
  //#endregion
86
- export { buildTableCellGrid, buildTableCellPlacements, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellVerticalBorderHeight };
126
+ export { buildTableCellGrid, buildTableCellPlacements, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellSpacingInsetsX, getTableCellSpacingInsetsY, getTableCellVerticalBorderHeight };
@@ -9,7 +9,8 @@ const tableFragmentBottomBorders = ({ fragment, block, measure, placements: supp
9
9
  const placements = suppliedPlacements ?? buildTableCellPlacements({
10
10
  grid: buildTableCellGrid(block.rows, measure.columnWidths.length),
11
11
  columnWidths: measure.columnWidths,
12
- bidi: block.bidi === true
12
+ bidi: block.bidi === true,
13
+ cellSpacing: block.cellSpacing
13
14
  });
14
15
  return splitRow.cells.flatMap((cell) => {
15
16
  const placement = placements.get(cell);
@@ -0,0 +1,19 @@
1
+ import { FlowBlock, Layout, Measure, NoteAreaLayout } from "./types.js";
2
+ import { PageTopContinuation, Paginator } from "./paginator.js";
3
+ //#region src/layout-engine/noteAreaFlow.d.ts
4
+ /**
5
+ * The continuation separator a page opens with while a note area continues
6
+ * onto it. Its space after collapses with the next note paragraph's space
7
+ * before, as between any two paragraphs.
8
+ */
9
+ declare function createNoteAreaContinuation(noteAreas: NoteAreaLayout | undefined, paginator: Paginator): PageTopContinuation | undefined;
10
+ /** Whether laying out `block` continues a note area onto any page it opens. */
11
+ declare function continuesNoteArea(noteAreas: NoteAreaLayout | undefined, block: FlowBlock): boolean;
12
+ /**
13
+ * Record where each separator mark landed. A rule is centred on the first line
14
+ * of the separator paragraph: a `w:separator` spans part of the column and a
15
+ * `w:continuationSeparator` all of it.
16
+ */
17
+ declare function annotateNoteSeparators(layout: Layout, blocks: readonly FlowBlock[], measures: readonly Measure[], noteAreas: NoteAreaLayout | undefined): Layout;
18
+ //#endregion
19
+ export { annotateNoteSeparators, continuesNoteArea, createNoteAreaContinuation };
@@ -0,0 +1,83 @@
1
+ import { measuredLineRangeHeight } from "./lineFlow.js";
2
+ import { getParagraphSpacingAfter } from "./paragraphSpacing.js";
3
+ import { NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION } from "./types.js";
4
+ //#region src/layout-engine/noteAreaFlow.ts
5
+ /**
6
+ * Note areas that flow with the body (ECMA-376 §17.11).
7
+ *
8
+ * Endnotes are collected after the last body block of the document, or of a
9
+ * section, and paginate exactly as body content does. Two things set them
10
+ * apart from the body: the area opens with its `w:separator` story, and every
11
+ * page the area continues onto opens with its `w:continuationSeparator` story.
12
+ * Both stories draw their separator mark as a horizontal rule.
13
+ */
14
+ /**
15
+ * The continuation separator a page opens with while a note area continues
16
+ * onto it. Its space after collapses with the next note paragraph's space
17
+ * before, as between any two paragraphs.
18
+ */
19
+ function createNoteAreaContinuation(noteAreas, paginator) {
20
+ const continuation = noteAreas?.continuationSeparator;
21
+ if (continuation === void 0) return;
22
+ const { block, measure } = continuation;
23
+ const height = measuredLineRangeHeight(measure.lines, 0, measure.lines.length);
24
+ return (state, x, width) => {
25
+ const fragment = {
26
+ kind: "paragraph",
27
+ blockId: block.id,
28
+ x,
29
+ y: state.cursorY,
30
+ width,
31
+ height,
32
+ fromLine: 0,
33
+ toLine: measure.lines.length
34
+ };
35
+ paginator.addUnflowedFragment(fragment);
36
+ state.trailingSpacing = getParagraphSpacingAfter(block);
37
+ return height;
38
+ };
39
+ }
40
+ /** Whether laying out `block` continues a note area onto any page it opens. */
41
+ function continuesNoteArea(noteAreas, block) {
42
+ return noteAreas?.contentBlockIds.has(String(block.id)) === true;
43
+ }
44
+ /**
45
+ * Record where each separator mark landed. A rule is centred on the first line
46
+ * of the separator paragraph: a `w:separator` spans part of the column and a
47
+ * `w:continuationSeparator` all of it.
48
+ */
49
+ function annotateNoteSeparators(layout, blocks, measures, noteAreas) {
50
+ if (noteAreas === void 0 || noteAreas.separatorBlockIds.size === 0) return layout;
51
+ const measureById = /* @__PURE__ */ new Map();
52
+ for (const [index, block] of blocks.entries()) {
53
+ const measure = measures[index];
54
+ if (noteAreas.separatorBlockIds.has(String(block.id)) && measure?.kind === "paragraph") measureById.set(String(block.id), measure);
55
+ }
56
+ const continuation = noteAreas.continuationSeparator;
57
+ if (continuation !== void 0) measureById.set(String(continuation.block.id), continuation.measure);
58
+ for (const page of layout.pages) {
59
+ const rules = [];
60
+ for (const fragment of page.fragments) {
61
+ if (fragment.kind !== "paragraph" || fragment.fromLine !== 0) continue;
62
+ const kind = noteAreas.separatorBlockIds.get(String(fragment.blockId));
63
+ const firstLine = measureById.get(String(fragment.blockId))?.lines.at(0);
64
+ if (kind === void 0 || firstLine === void 0) continue;
65
+ rules.push({
66
+ x: fragment.x,
67
+ y: fragment.y + (firstLine.lineHeight - NOTE_SEPARATOR_RULE_THICKNESS) / 2,
68
+ width: fragment.width * separatorWidthFraction(kind)
69
+ });
70
+ }
71
+ if (rules.length > 0) page.noteSeparators = rules;
72
+ }
73
+ return layout;
74
+ }
75
+ function separatorWidthFraction(kind) {
76
+ switch (kind) {
77
+ case "separator": return NOTE_SEPARATOR_WIDTH_FRACTION;
78
+ case "continuationSeparator": return 1;
79
+ default: return kind;
80
+ }
81
+ }
82
+ //#endregion
83
+ export { annotateNoteSeparators, continuesNoteArea, createNoteAreaContinuation };
@@ -51,6 +51,8 @@ type PaginatorOptions = {
51
51
  firstPageMargins?: PageMargins;
52
52
  /** Per-section body margins used on even authored page numbers. */
53
53
  sectionEvenPageMargins?: (PageMargins | undefined)[];
54
+ /** Per-section body margins used on the first page of a `w:titlePg` section. */
55
+ sectionFirstPageMargins?: (PageMargins | undefined)[];
54
56
  /** Page-number policy for the initial section. */
55
57
  pageNumbering?: SectionPageNumbering;
56
58
  /** Column configuration (optional). */
@@ -65,6 +67,13 @@ type PaginatorOptions = {
65
67
  type ForcePageBreakOptions = {
66
68
  coalesceBlankPage?: boolean;
67
69
  };
70
+ /**
71
+ * Content that opens every page created while a flow continues from an
72
+ * earlier page (a note area's continuation separator). Called with the new
73
+ * page's state and the first column's box; returns the height it placed,
74
+ * which the page's flow then starts below.
75
+ */
76
+ type PageTopContinuation = (state: PageState, x: number, width: number) => number;
68
77
  declare const SECTION_START_PLACEMENT: {
69
78
  readonly CONTINUOUS: "continuous";
70
79
  readonly NEXT_PAGE: "nextPage";
@@ -147,7 +156,9 @@ declare function createPaginator(options: PaginatorOptions): {
147
156
  }, newMargins?: PageMargins, applyImmediately?: boolean) => void;
148
157
  /** Mark the next created page as the first page of a new section. */
149
158
  startSection: ({ sectionIndex, pageNumbering, placement }: StartSectionOptions) => void;
159
+ /** Set (or clear) the content that opens each page a flow continues onto. */
160
+ setPageTopContinuation: (continuation: PageTopContinuation | undefined) => void;
150
161
  };
151
162
  type Paginator = ReturnType<typeof createPaginator>;
152
163
  //#endregion
153
- export { FOOTNOTE_SEPARATOR_HEIGHT, PageState, Paginator, PaginatorOptions, SECTION_START_PLACEMENT, calculateColumnLefts, calculateColumnWidths, createPaginator, resolveColumnLeft };
164
+ export { FOOTNOTE_SEPARATOR_HEIGHT, PageState, PageTopContinuation, Paginator, PaginatorOptions, SECTION_START_PLACEMENT, calculateColumnLefts, calculateColumnWidths, createPaginator, resolveColumnLeft };
@@ -62,6 +62,7 @@ function createPaginator(options) {
62
62
  type: "pending",
63
63
  placement: "nextPage"
64
64
  };
65
+ let pageTopContinuation;
65
66
  const pages = [];
66
67
  const states = [];
67
68
  const paginationStateByPage = /* @__PURE__ */ new WeakMap();
@@ -93,7 +94,11 @@ function createPaginator(options) {
93
94
  }
94
95
  function getPageMargins(pageNumber, logicalPageNumber) {
95
96
  const evenMargins = logicalPageNumber % 2 === 0 ? options.sectionEvenPageMargins?.[currentSectionIndex] : void 0;
96
- const pageMargins = pageNumber === 1 && options.firstPageMargins ? { ...options.firstPageMargins } : { ...evenMargins ?? margins };
97
+ const titlePageMargins = currentSectionPageNumber === 1 ? options.sectionFirstPageMargins?.[currentSectionIndex] : void 0;
98
+ let pageMargins;
99
+ if (titlePageMargins) pageMargins = { ...titlePageMargins };
100
+ else if (pageNumber === 1 && options.firstPageMargins) pageMargins = { ...options.firstPageMargins };
101
+ else pageMargins = { ...evenMargins ?? margins };
97
102
  if (options.mirrorMargins === true && pageNumber % 2 === 0) return {
98
103
  ...pageMargins,
99
104
  left: pageMargins.right,
@@ -180,6 +185,13 @@ function createPaginator(options) {
180
185
  states.push(state);
181
186
  columnRegionTop = topMargin;
182
187
  columnRegionMaxBottom = topMargin;
188
+ const continuationHeight = pageTopContinuation?.(state, getColumnX(0), columnWidths[0] ?? getContentWidth()) ?? 0;
189
+ if (continuationHeight > 0) {
190
+ state.cursorY += continuationHeight;
191
+ state.topMargin += continuationHeight;
192
+ columnRegionTop = state.topMargin;
193
+ columnRegionMaxBottom = state.topMargin;
194
+ }
183
195
  if (options.onNewPage) options.onNewPage(state);
184
196
  return state;
185
197
  }
@@ -503,7 +515,11 @@ function createPaginator(options) {
503
515
  /** Update page size/margins for subsequent pages. */
504
516
  updatePageLayout,
505
517
  /** Mark the next created page as the first page of a new section. */
506
- startSection
518
+ startSection,
519
+ /** Set (or clear) the content that opens each page a flow continues onto. */
520
+ setPageTopContinuation: (continuation) => {
521
+ pageTopContinuation = continuation;
522
+ }
507
523
  };
508
524
  }
509
525
  //#endregion
@@ -1,4 +1,4 @@
1
- import { FlowBlock, ParagraphBlock } from "./types.js";
1
+ import { FlowBlock, ParagraphBlock, TableBlock } from "./types.js";
2
2
  //#region src/layout-engine/paragraphSpacing.d.ts
3
3
  type EffectiveParagraphSpacing = Readonly<{
4
4
  before: number;
@@ -35,5 +35,7 @@ declare const resolveEffectiveParagraphSpacing: (block: ParagraphBlock) => Effec
35
35
  */
36
36
  declare const resolveEffectiveParagraphSpacingTree: (blocks: FlowBlock[]) => FlowBlock[];
37
37
  declare const paragraphsShareStyle: (previous: ParagraphBlock, current: ParagraphBlock) => boolean;
38
+ /** Derive the effective paragraph spacing of every cell story in a table. */
39
+ declare const resolveTableSpacing: (block: TableBlock) => TableBlock;
38
40
  //#endregion
39
- export { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isAuthoredEmptyParagraph, isEmptyParagraph, paragraphsShareStyle, resolveEffectiveParagraphSpacing, resolveEffectiveParagraphSpacingTree };
41
+ export { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isAuthoredEmptyParagraph, isEmptyParagraph, paragraphsShareStyle, resolveEffectiveParagraphSpacing, resolveEffectiveParagraphSpacingTree, resolveTableSpacing };
@@ -88,6 +88,7 @@ const resolveTextBoxContentSpacing = (blocks) => {
88
88
  });
89
89
  return changed ? resolved : blocks;
90
90
  };
91
+ /** Derive the effective paragraph spacing of every cell story in a table. */
91
92
  const resolveTableSpacing = (block) => {
92
93
  let changed = false;
93
94
  const rows = block.rows.map((row) => {
@@ -122,4 +123,4 @@ const resolveTextBoxSpacing = (block) => {
122
123
  };
123
124
  };
124
125
  //#endregion
125
- export { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isAuthoredEmptyParagraph, isEmptyParagraph, paragraphsShareStyle, resolveEffectiveParagraphSpacing, resolveEffectiveParagraphSpacingTree };
126
+ export { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isAuthoredEmptyParagraph, isEmptyParagraph, paragraphsShareStyle, resolveEffectiveParagraphSpacing, resolveEffectiveParagraphSpacingTree, resolveTableSpacing };
@@ -19,6 +19,13 @@ type TableRowBreakInfo = {
19
19
  /** Suppressible leading paragraph whitespace after each matching break offset. */
20
20
  continuationSkips: number[][];
21
21
  };
22
+ /**
23
+ * Height of the start of a table that a preceding `w:keepNext` paragraph
24
+ * (§17.3.1.15) must share a page with: the leading header rows (§17.4.49) and
25
+ * the first body row. A row that may break across pages (no `w:cantSplit`,
26
+ * §17.4.6) needs only its first line there; an unbreakable row needs all of it.
27
+ */
28
+ declare function tableKeepNextOpeningHeight(block: TableBlock, measure: TableMeasure): number;
22
29
  /** Build break geometry for a table from its block + measure. */
23
30
  declare function buildTableRowBreakInfo(block: TableBlock, measure: TableMeasure): TableRowBreakInfo;
24
31
  /** Leading empty-paragraph whitespace to consume when a row resumes after `offset`. */
@@ -30,4 +37,4 @@ declare function getRowContinuationSkip(info: TableRowBreakInfo, rowIndex: numbe
30
37
  */
31
38
  declare function snapRowBreak(info: TableRowBreakInfo, rowIndex: number, fromOffset: number, maxSlice: number): number;
32
39
  //#endregion
33
- export { TableRowBreakInfo, buildTableRowBreakInfo, getRowContinuationSkip, snapRowBreak };
40
+ export { TableRowBreakInfo, buildTableRowBreakInfo, getRowContinuationSkip, snapRowBreak, tableKeepNextOpeningHeight };
@@ -2,6 +2,7 @@ import { measuredLineAdvance } from "./lineFlow.js";
2
2
  import { measureParagraph } from "./measure/measureParagraph.js";
3
3
  import { buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages } from "./measure/tableCellFloating.js";
4
4
  import { createTableCellFlowState, placeTableCellBlock } from "./measure/tableCellFlow.js";
5
+ import { getTableCellSpacingInsetsY } from "./measure/tableCellGrid.js";
5
6
  import { isEmptyParagraph } from "./paragraphSpacing.js";
6
7
  import { resolveTableCellPadding } from "./types.js";
7
8
  //#region src/layout-engine/tableRowBreak.ts
@@ -140,6 +141,27 @@ const continuationSkipAfter = (geometries, offset) => {
140
141
  if (skips.length === 0 || skips.some((skip) => skip <= BREAK_OFFSET_EPSILON)) return 0;
141
142
  return Math.min(...skips);
142
143
  };
144
+ /**
145
+ * Height of the start of a table that a preceding `w:keepNext` paragraph
146
+ * (§17.3.1.15) must share a page with: the leading header rows (§17.4.49) and
147
+ * the first body row. A row that may break across pages (no `w:cantSplit`,
148
+ * §17.4.6) needs only its first line there; an unbreakable row needs all of it.
149
+ */
150
+ function tableKeepNextOpeningHeight(block, measure) {
151
+ let height = 0;
152
+ let rowIndex = 0;
153
+ while (rowIndex < measure.rows.length && block.rows[rowIndex]?.isHeader === true) {
154
+ height += measure.rows[rowIndex]?.height ?? 0;
155
+ rowIndex += 1;
156
+ }
157
+ const row = measure.rows[rowIndex];
158
+ const sourceRow = block.rows[rowIndex];
159
+ if (!row || !sourceRow) return height;
160
+ const breakOffsets = buildTableRowBreakInfo(block, measure).breakOffsets[rowIndex] ?? [];
161
+ const firstBreak = breakOffsets.at(0);
162
+ const rowMayBreak = sourceRow.cantSplit !== true && breakOffsets.length > 1;
163
+ return height + (rowMayBreak && firstBreak !== void 0 ? firstBreak : row.height);
164
+ }
143
165
  /** Build break geometry for a table from its block + measure. */
144
166
  function buildTableRowBreakInfo(block, measure) {
145
167
  const rowCount = measure.rows.length;
@@ -164,7 +186,8 @@ function buildTableRowBreakInfo(block, measure) {
164
186
  const measuredCell = measuredCells[c];
165
187
  if (!measuredCell) continue;
166
188
  const sourceCell = sourceCells[c];
167
- const geometry = shiftCellGeometry(cellBreakGeometry(sourceCell, measuredCell), getVerticalAlignmentOffset(sourceCell, measuredCell, rowHeight));
189
+ const spacing = getTableCellSpacingInsetsY(block.cellSpacing, r, sourceCell?.rowSpan ?? 1, block.rows.length);
190
+ const geometry = shiftCellGeometry(cellBreakGeometry(sourceCell, measuredCell), spacing.top + getVerticalAlignmentOffset(sourceCell, measuredCell, Math.max(0, rowHeight - spacing.top - spacing.bottom)));
168
191
  cellGeometries.push(geometry);
169
192
  for (const b of geometry.bottoms) if (b > 0 && b < rowHeight) offsets.add(b);
170
193
  }
@@ -206,4 +229,4 @@ function snapRowBreak(info, rowIndex, fromOffset, maxSlice) {
206
229
  return best;
207
230
  }
208
231
  //#endregion
209
- export { buildTableRowBreakInfo, getRowContinuationSkip, snapRowBreak };
232
+ export { buildTableRowBreakInfo, getRowContinuationSkip, snapRowBreak, tableKeepNextOpeningHeight };