@stll/folio-core 0.48.0 → 0.50.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 (170) hide show
  1. package/dist/ai-edits/apply.js +565 -121
  2. package/dist/ai-edits/headless.d.ts +37 -1
  3. package/dist/ai-edits/headless.js +47 -16
  4. package/dist/ai-edits/minimal-replacement.d.ts +64 -0
  5. package/dist/ai-edits/minimal-replacement.js +282 -0
  6. package/dist/controller/layoutPipeline.js +11 -1
  7. package/dist/display-list/build/paragraphPrimitives.js +63 -7
  8. package/dist/display-list/build/textBoxPrimitives.js +1 -1
  9. package/dist/display-list/build/unsupported.d.ts +2 -2
  10. package/dist/display-list/build/unsupported.js +0 -0
  11. package/dist/display-list/dom/renderDisplayListToDom.d.ts +10 -1
  12. package/dist/display-list/dom/renderDisplayListToDom.js +8 -8
  13. package/dist/display-list/html/renderDisplayListToHtml.d.ts +21 -0
  14. package/dist/display-list/html/renderDisplayListToHtml.js +126 -0
  15. package/dist/display-list/selectDisplayPages.d.ts +6 -0
  16. package/dist/display-list/selectDisplayPages.js +44 -0
  17. package/dist/docx/blockContentParser.js +2 -2
  18. package/dist/docx/bulletMarkers.d.ts +18 -2
  19. package/dist/docx/bulletMarkers.js +27 -2
  20. package/dist/docx/drawingGroupChildren.d.ts +60 -0
  21. package/dist/docx/drawingGroupChildren.js +176 -0
  22. package/dist/docx/drawingUtils.js +3 -0
  23. package/dist/docx/groupDrawingParser.d.ts +7 -1
  24. package/dist/docx/groupDrawingParser.js +123 -32
  25. package/dist/docx/packageParts.d.ts +45 -1
  26. package/dist/docx/packageParts.js +93 -2
  27. package/dist/docx/paraIdAttribute.d.ts +3 -2
  28. package/dist/docx/paragraphParser.js +91 -7
  29. package/dist/docx/paragraphTextBoxEnrichment.js +126 -4
  30. package/dist/docx/runParser.js +10 -0
  31. package/dist/docx/selectiveSaveFlags.d.ts +10 -3
  32. package/dist/docx/selectiveSaveFlags.js +1 -1
  33. package/dist/docx/selectiveXmlPatch.d.ts +56 -34
  34. package/dist/docx/selectiveXmlPatch.js +222 -115
  35. package/dist/docx/serializer/groupTextBoxWriteBack.d.ts +12 -0
  36. package/dist/docx/serializer/groupTextBoxWriteBack.js +101 -0
  37. package/dist/docx/serializer/paragraphSerializer.js +5 -4
  38. package/dist/docx/serializer/runSerializer.d.ts +5 -1
  39. package/dist/docx/serializer/runSerializer.js +4 -3
  40. package/dist/docx/shapeAlternateContent.d.ts +25 -0
  41. package/dist/docx/shapeAlternateContent.js +52 -0
  42. package/dist/docx/textBoxParser.d.ts +10 -2
  43. package/dist/docx/textBoxParser.js +9 -3
  44. package/dist/export-pdf.d.ts +35 -2
  45. package/dist/export-pdf.js +51 -20
  46. package/dist/fonts/fontsourceFaces.d.ts +47 -0
  47. package/dist/fonts/fontsourceFaces.js +230 -0
  48. package/dist/internal/headlessRevisionResolution.js +50 -13
  49. package/dist/layout-bridge/convert/flowBlockPostprocessing.d.ts +8 -0
  50. package/dist/layout-bridge/convert/flowBlockPostprocessing.js +106 -0
  51. package/dist/layout-bridge/convert/flowBorders.d.ts +17 -0
  52. package/dist/layout-bridge/convert/flowBorders.js +31 -0
  53. package/dist/layout-bridge/convert/flowConversionShared.d.ts +111 -0
  54. package/dist/layout-bridge/convert/flowConversionShared.js +23 -0
  55. package/dist/layout-bridge/convert/footnoteLayout.d.ts +2 -1
  56. package/dist/layout-bridge/convert/footnoteLayout.js +2 -1
  57. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +2 -1
  58. package/dist/layout-bridge/convert/imageConversion.d.ts +33 -0
  59. package/dist/layout-bridge/convert/imageConversion.js +118 -0
  60. package/dist/layout-bridge/convert/listMarkers.d.ts +28 -0
  61. package/dist/layout-bridge/convert/listMarkers.js +98 -0
  62. package/dist/layout-bridge/convert/noteReferences.d.ts +14 -0
  63. package/dist/layout-bridge/convert/noteReferences.js +29 -0
  64. package/dist/layout-bridge/convert/pageBreakSplitting.d.ts +16 -0
  65. package/dist/layout-bridge/convert/pageBreakSplitting.js +140 -0
  66. package/dist/layout-bridge/convert/paragraphAttrs.d.ts +20 -0
  67. package/dist/layout-bridge/convert/paragraphAttrs.js +234 -0
  68. package/dist/layout-bridge/convert/paragraphConversion.d.ts +12 -0
  69. package/dist/layout-bridge/convert/paragraphConversion.js +112 -0
  70. package/dist/layout-bridge/convert/paragraphRuns.d.ts +15 -0
  71. package/dist/layout-bridge/convert/paragraphRuns.js +281 -0
  72. package/dist/layout-bridge/convert/runFormattingMerge.d.ts +19 -0
  73. package/dist/layout-bridge/convert/runFormattingMerge.js +167 -0
  74. package/dist/layout-bridge/convert/runMarkFormatting.d.ts +11 -0
  75. package/dist/layout-bridge/convert/runMarkFormatting.js +291 -0
  76. package/dist/layout-bridge/convert/sectionBoundaries.d.ts +35 -0
  77. package/dist/layout-bridge/convert/sectionBoundaries.js +114 -0
  78. package/dist/layout-bridge/convert/tableConversion.d.ts +12 -0
  79. package/dist/layout-bridge/convert/tableConversion.js +303 -0
  80. package/dist/layout-bridge/convert/textBoxFill.d.ts +20 -0
  81. package/dist/layout-bridge/convert/textBoxFill.js +42 -0
  82. package/dist/layout-bridge/convert/textFormattingConversion.d.ts +22 -0
  83. package/dist/layout-bridge/convert/textFormattingConversion.js +120 -0
  84. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +5 -113
  85. package/dist/layout-bridge/convert/toFlowBlocks.js +11 -2060
  86. package/dist/layout-bridge/engine/hitTest.js +1 -1
  87. package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
  88. package/dist/layout-bridge/engine/measuring/index.js +2 -1
  89. package/dist/layout-bridge/engine/selectionRects.js +1 -1
  90. package/dist/layout-engine/imageLayout.d.ts +9 -0
  91. package/dist/layout-engine/imageLayout.js +48 -0
  92. package/dist/layout-engine/index.d.ts +4 -19
  93. package/dist/layout-engine/index.js +14 -910
  94. package/dist/layout-engine/keep-together.d.ts +5 -2
  95. package/dist/layout-engine/keep-together.js +5 -1
  96. package/dist/layout-engine/layoutFlowShared.d.ts +6 -0
  97. package/dist/layout-engine/layoutFlowShared.js +8 -0
  98. package/dist/layout-engine/measure/advanceComposition.js +49 -3
  99. package/dist/layout-engine/measure/crossRunWords.d.ts +45 -0
  100. package/dist/layout-engine/measure/crossRunWords.js +172 -0
  101. package/dist/layout-engine/measure/floatingLineClearance.d.ts +24 -0
  102. package/dist/layout-engine/measure/floatingLineClearance.js +44 -0
  103. package/dist/layout-engine/measure/inlineObjectMetrics.d.ts +6 -0
  104. package/dist/layout-engine/measure/inlineObjectMetrics.js +52 -0
  105. package/dist/layout-engine/measure/justification.d.ts +32 -0
  106. package/dist/layout-engine/measure/justification.js +83 -0
  107. package/dist/layout-engine/measure/lineFitting.d.ts +48 -0
  108. package/dist/layout-engine/measure/lineFitting.js +145 -0
  109. package/dist/layout-engine/measure/lineTypography.d.ts +44 -0
  110. package/dist/layout-engine/measure/lineTypography.js +124 -0
  111. package/dist/layout-engine/measure/measureBlocks.js +7 -4
  112. package/dist/layout-engine/measure/measureContainer.js +46 -3
  113. package/dist/layout-engine/measure/measureHelpers.js +13 -3
  114. package/dist/layout-engine/measure/measureParagraph.d.ts +1 -20
  115. package/dist/layout-engine/measure/measureParagraph.js +41 -780
  116. package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +109 -0
  117. package/dist/layout-engine/measure/paragraphMeasureShared.js +82 -0
  118. package/dist/layout-engine/measure/paragraphTabs.d.ts +18 -0
  119. package/dist/layout-engine/measure/paragraphTabs.js +78 -0
  120. package/dist/layout-engine/measure/smallCapsCasing.d.ts +56 -0
  121. package/dist/layout-engine/measure/smallCapsCasing.js +88 -0
  122. package/dist/layout-engine/paginator.d.ts +2 -0
  123. package/dist/layout-engine/paginator.js +5 -1
  124. package/dist/layout-engine/paragraphLayout.d.ts +23 -0
  125. package/dist/layout-engine/paragraphLayout.js +204 -0
  126. package/dist/layout-engine/paragraphSpacing.d.ts +4 -2
  127. package/dist/layout-engine/paragraphSpacing.js +2 -1
  128. package/dist/layout-engine/sectionLayout.d.ts +45 -0
  129. package/dist/layout-engine/sectionLayout.js +216 -0
  130. package/dist/layout-engine/tableLayout.d.ts +17 -0
  131. package/dist/layout-engine/tableLayout.js +369 -0
  132. package/dist/layout-engine/tableRowBreak.d.ts +8 -1
  133. package/dist/layout-engine/tableRowBreak.js +22 -1
  134. package/dist/layout-engine/textBoxLayout.d.ts +15 -0
  135. package/dist/layout-engine/textBoxLayout.js +137 -0
  136. package/dist/layout-engine/types.d.ts +21 -2
  137. package/dist/layout-painter/renderPage.js +1 -0
  138. package/dist/layout-painter/renderParagraph.js +41 -4
  139. package/dist/layout-painter/renderTextBox.d.ts +12 -2
  140. package/dist/layout-painter/renderTextBox.js +26 -2
  141. package/dist/paged-layout/sectionBlockWidths.d.ts +2 -1
  142. package/dist/paged-layout/sectionBlockWidths.js +1 -1
  143. package/dist/prosemirror/alternateContentAttrs.d.ts +9 -0
  144. package/dist/prosemirror/alternateContentAttrs.js +35 -0
  145. package/dist/prosemirror/attrs/index.js +90 -0
  146. package/dist/prosemirror/commands/comments.js +51 -41
  147. package/dist/prosemirror/contentControlRevisions.d.ts +43 -0
  148. package/dist/prosemirror/contentControlRevisions.js +84 -0
  149. package/dist/prosemirror/conversion/fromProseDoc.js +46 -22
  150. package/dist/prosemirror/conversion/toProseDoc.js +42 -19
  151. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +7 -7
  152. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -7
  153. package/dist/prosemirror/extensions/nodes/FieldExtension.js +6 -3
  154. package/dist/prosemirror/extensions/nodes/SdtExtension.js +9 -2
  155. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
  156. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +3 -0
  157. package/dist/prosemirror/listMarker.js +2 -2
  158. package/dist/prosemirror/paragraphDirection.d.ts +29 -2
  159. package/dist/prosemirror/paragraphDirection.js +21 -2
  160. package/dist/prosemirror/plugins/suggestionMode.js +37 -8
  161. package/dist/prosemirror/rejoinRunCarriers.d.ts +27 -0
  162. package/dist/prosemirror/rejoinRunCarriers.js +66 -0
  163. package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +10 -0
  164. package/dist/prosemirror/runIdentityAcrossRevisions.js +90 -0
  165. package/dist/prosemirror/schema/nodes.d.ts +37 -5
  166. package/dist/server.d.ts +3 -2
  167. package/dist/types/content.d.ts +2 -2
  168. package/package.json +2 -2
  169. package/dist/layout-engine/justifiedLineFit.d.ts +0 -7
  170. package/dist/layout-engine/justifiedLineFit.js +0 -6
@@ -0,0 +1,204 @@
1
+ import { hasKeepLines } from "./keep-together.js";
2
+ import { projectedFootnoteReserveGrowth } from "./layoutFlowShared.js";
3
+ import { measuredLineAdvance, measuredLineRangeHeight } from "./lineFlow.js";
4
+ import { getParagraphFragmentPmRange } from "./paragraphFragmentRange.js";
5
+ import { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore } from "./paragraphSpacing.js";
6
+ import "./types.js";
7
+ //#region src/layout-engine/paragraphLayout.ts
8
+ /**
9
+ * Places a measured paragraph's lines onto pages: keep rules, widow/orphan
10
+ * control, rendered-break reconciliation, and per-line footnote reservation.
11
+ */
12
+ function hasWidowControl(block) {
13
+ return block.attrs?.widowControl !== false;
14
+ }
15
+ /**
16
+ * Footnote refs whose run sits in line `[fromRun..toRun]`, with their
17
+ * pre-measured content heights. Empty when the engine isn't tracking
18
+ * dynamic fn demand. Used by `layoutParagraph` to (a) reserve fn
19
+ * height per line and (b) record the IDs on the host page so the
20
+ * painter renders the fn on the page where the ref-bearing line
21
+ * actually landed — even when the paragraph splits across pages
22
+ * (fragment pmStart/pmEnd is paragraph-wide and cannot disambiguate
23
+ * between split halves).
24
+ */
25
+ function getLineFootnoteRefs(block, fromRun, toRun, fnHeights) {
26
+ if (!fnHeights) return {
27
+ ids: [],
28
+ height: 0
29
+ };
30
+ const ids = [];
31
+ let height = 0;
32
+ for (let r = fromRun; r <= toRun; r++) {
33
+ const run = block.runs[r];
34
+ if (!run || run.kind !== "text") continue;
35
+ const id = run.footnoteRefId;
36
+ if (id === void 0) continue;
37
+ const h = fnHeights.get(id);
38
+ if (h !== void 0) {
39
+ ids.push(id);
40
+ height += h;
41
+ }
42
+ }
43
+ return {
44
+ ids,
45
+ height
46
+ };
47
+ }
48
+ function layoutParagraph({ block, measure, paginator, contentWidth, footnoteHeightById, suppressSpaceBefore }) {
49
+ const lines = measure.lines;
50
+ if (lines.length === 0) {
51
+ const spaceBefore = suppressSpaceBefore ? 0 : getParagraphSpacingBefore(block);
52
+ const spaceAfter = getParagraphSpacingAfter(block);
53
+ const state = paginator.getCurrentState();
54
+ const fragment = {
55
+ kind: "paragraph",
56
+ ...block.attrs?.suppressEmptyParagraphHeight === true ? { paginationRole: "empty-carrier" } : {},
57
+ blockId: block.id,
58
+ x: paginator.getColumnX(state.columnIndex),
59
+ y: state.cursorY + spaceBefore,
60
+ width: contentWidth,
61
+ height: 0,
62
+ fromLine: 0,
63
+ toLine: 0,
64
+ ...block.pmStart !== void 0 ? { pmStart: block.pmStart } : {},
65
+ ...block.pmEnd !== void 0 ? { pmEnd: block.pmEnd } : {},
66
+ ...block.sdtGroups ? { sdtGroups: block.sdtGroups } : {}
67
+ };
68
+ paginator.addFragment(fragment, 0, spaceBefore, spaceAfter);
69
+ return;
70
+ }
71
+ const spaceBefore = suppressSpaceBefore ? 0 : getParagraphSpacingBefore(block);
72
+ const spaceAfter = getParagraphSpacingAfter(block);
73
+ let keepLinesDemand;
74
+ if (lines.length > 1 && hasKeepLines(block)) {
75
+ let footnoteHeight = 0;
76
+ for (const line of lines) footnoteHeight += getLineFootnoteRefs(block, line.fromRun, line.toRun, footnoteHeightById).height;
77
+ keepLinesDemand = {
78
+ linesHeight: measuredLineRangeHeight(lines, 0, lines.length),
79
+ footnoteHeight
80
+ };
81
+ }
82
+ let keepLinesMoveAttempted = false;
83
+ let currentLineIndex = 0;
84
+ while (currentLineIndex < lines.length) {
85
+ const state = paginator.getCurrentState();
86
+ const availableHeight = paginator.getAvailableHeight();
87
+ const currentLine = lines.at(currentLineIndex);
88
+ const markerAlreadySatisfied = state.columnIndex === 0 && state.cursorY === state.topMargin && state.page.fragments.length === 0;
89
+ if (currentLine?.renderedPageBreakBefore === true && !markerAlreadySatisfied && availableHeight <= measuredLineAdvance(currentLine) * 3) {
90
+ paginator.forcePageBreak();
91
+ continue;
92
+ }
93
+ if (currentLineIndex === 0 && state.trailingSpacing > 0 && state.cursorY !== state.topMargin) {
94
+ const firstLine = lines[0];
95
+ const firstLineRefs = getLineFootnoteRefs(block, firstLine.fromRun, firstLine.toRun, footnoteHeightById);
96
+ const firstLineHeight = measuredLineAdvance(firstLine) + projectedFootnoteReserveGrowth(state, firstLineRefs.height);
97
+ const collapsedLead = collapseParagraphSpacing({
98
+ before: spaceBefore,
99
+ after: state.trailingSpacing
100
+ });
101
+ const columnCapacity = state.contentBottom - state.topMargin;
102
+ if (collapsedLead + firstLineHeight > availableHeight && spaceBefore + firstLineHeight <= columnCapacity) {
103
+ paginator.ensureFits(collapsedLead + firstLineHeight);
104
+ continue;
105
+ }
106
+ }
107
+ if (keepLinesDemand !== void 0 && !keepLinesMoveAttempted && currentLineIndex === 0 && state.cursorY !== state.topMargin) {
108
+ const wholeHeight = keepLinesDemand.linesHeight + projectedFootnoteReserveGrowth(state, keepLinesDemand.footnoteHeight);
109
+ const collapsedLead = collapseParagraphSpacing({
110
+ before: spaceBefore,
111
+ after: state.trailingSpacing
112
+ });
113
+ const columnCapacity = state.contentBottom - state.topMargin;
114
+ if (collapsedLead + wholeHeight > availableHeight && spaceBefore + wholeHeight <= columnCapacity) {
115
+ keepLinesMoveAttempted = true;
116
+ paginator.ensureFits(collapsedLead + wholeHeight);
117
+ continue;
118
+ }
119
+ }
120
+ let linesHeight = 0;
121
+ let linesFnHeight = 0;
122
+ const linesFnIds = [];
123
+ let fittingLines = 0;
124
+ let forcePageBreakAfterFragment = false;
125
+ const firstFragmentSpaceBefore = currentLineIndex === 0 ? collapseParagraphSpacing({
126
+ before: spaceBefore,
127
+ after: state.trailingSpacing
128
+ }) : 0;
129
+ for (let j = currentLineIndex; j < lines.length; j++) {
130
+ const line = lines[j];
131
+ const lineAdvance = measuredLineAdvance(line);
132
+ if (j > currentLineIndex && line.renderedPageBreakBefore === true && availableHeight - (linesHeight + firstFragmentSpaceBefore + linesFnHeight) <= lineAdvance * 3) {
133
+ forcePageBreakAfterFragment = true;
134
+ break;
135
+ }
136
+ const lineRefs = getLineFootnoteRefs(block, line.fromRun, line.toRun, footnoteHeightById);
137
+ const totalWithLine = linesHeight + lineAdvance;
138
+ const footnoteGrowth = projectedFootnoteReserveGrowth(state, linesFnHeight + lineRefs.height);
139
+ if (totalWithLine + firstFragmentSpaceBefore + footnoteGrowth <= availableHeight || fittingLines === 0) {
140
+ linesHeight = totalWithLine;
141
+ linesFnHeight += lineRefs.height;
142
+ for (const id of lineRefs.ids) linesFnIds.push(id);
143
+ fittingLines++;
144
+ } else break;
145
+ }
146
+ let forceBreakAfterFragment = false;
147
+ if (hasWidowControl(block)) {
148
+ const remainingAfter = lines.length - (currentLineIndex + fittingLines);
149
+ if (fittingLines > 1 && remainingAfter === 1) {
150
+ if (currentLineIndex === 0 && fittingLines === 2 && state.cursorY !== state.topMargin) {
151
+ paginator.forceColumnBreak();
152
+ continue;
153
+ }
154
+ fittingLines -= 1;
155
+ forceBreakAfterFragment = true;
156
+ linesHeight = 0;
157
+ linesFnHeight = 0;
158
+ linesFnIds.length = 0;
159
+ for (let j = currentLineIndex; j < currentLineIndex + fittingLines; j++) {
160
+ const line = lines[j];
161
+ linesHeight += measuredLineAdvance(line);
162
+ const lineRefs = getLineFootnoteRefs(block, line.fromRun, line.toRun, footnoteHeightById);
163
+ linesFnHeight += lineRefs.height;
164
+ for (const id of lineRefs.ids) linesFnIds.push(id);
165
+ }
166
+ }
167
+ }
168
+ const isFirstFragment = currentLineIndex === 0;
169
+ const isLastFragment = currentLineIndex + fittingLines >= lines.length;
170
+ const effectiveSpaceBefore = isFirstFragment ? spaceBefore : 0;
171
+ const effectiveSpaceAfter = isLastFragment ? spaceAfter : 0;
172
+ const pmRange = getParagraphFragmentPmRange(block, measure, currentLineIndex, currentLineIndex + fittingLines);
173
+ const fragment = {
174
+ kind: "paragraph",
175
+ ...block.attrs?.suppressEmptyParagraphHeight === true ? { paginationRole: "empty-carrier" } : {},
176
+ blockId: block.id,
177
+ x: paginator.getColumnX(state.columnIndex),
178
+ y: 0,
179
+ width: contentWidth,
180
+ height: linesHeight,
181
+ fromLine: currentLineIndex,
182
+ toLine: currentLineIndex + fittingLines,
183
+ ...pmRange.pmStart !== void 0 ? { pmStart: pmRange.pmStart } : {},
184
+ ...pmRange.pmEnd !== void 0 ? { pmEnd: pmRange.pmEnd } : {},
185
+ ...!isFirstFragment ? { continuesFromPrev: true } : {},
186
+ ...!isLastFragment ? { continuesOnNext: true } : {},
187
+ ...block.sdtGroups ? { sdtGroups: block.sdtGroups } : {}
188
+ };
189
+ if (linesFnHeight > 0) {
190
+ const stateBefore = paginator.getCurrentState();
191
+ paginator.ensureFits(effectiveSpaceBefore + linesHeight + projectedFootnoteReserveGrowth(stateBefore, linesFnHeight));
192
+ const stateAfter = paginator.getCurrentState();
193
+ if (stateAfter.footnoteDemandHeight === 0) paginator.ensureFits(effectiveSpaceBefore + linesHeight + projectedFootnoteReserveGrowth(stateAfter, linesFnHeight + 12));
194
+ }
195
+ fragment.y = paginator.addFragment(fragment, linesHeight, effectiveSpaceBefore, effectiveSpaceAfter).y;
196
+ if (linesFnHeight > 0) paginator.addFootnoteHeight(linesFnHeight, linesFnIds);
197
+ currentLineIndex += fittingLines;
198
+ if (currentLineIndex < lines.length) if (forcePageBreakAfterFragment) paginator.forcePageBreak();
199
+ else if (forceBreakAfterFragment) paginator.forceColumnBreak();
200
+ else paginator.ensureFits(measuredLineAdvance(lines[currentLineIndex]));
201
+ }
202
+ }
203
+ //#endregion
204
+ export { layoutParagraph };
@@ -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 };
@@ -0,0 +1,45 @@
1
+ import { ColumnLayout, FlowBlock, Measure, PageMargins, SectionBreakBlock, SectionPageNumbering } from "./types.js";
2
+ import { createPaginator } from "./paginator.js";
3
+ import { SectionStart } from "@stll/docx-core/model";
4
+ //#region src/layout-engine/sectionLayout.d.ts
5
+ type SectionLayoutConfig = {
6
+ pageSize: {
7
+ w: number;
8
+ h: number;
9
+ };
10
+ margins: PageMargins;
11
+ pageNumbering: SectionPageNumbering;
12
+ columns?: ColumnLayout;
13
+ };
14
+ declare const DEFAULT_COLUMNS: ColumnLayout;
15
+ declare const CONTINUE_PAGE_NUMBERING: SectionPageNumbering;
16
+ declare function collectSectionConfigs(blocks: FlowBlock[], initialConfig: SectionLayoutConfig, finalConfig: SectionLayoutConfig): {
17
+ configs: SectionLayoutConfig[];
18
+ breakIndices: number[];
19
+ };
20
+ /**
21
+ * Estimate the height of a short paragraph-only multi-column section so its
22
+ * final page can use Word-style balanced columns. Sections containing tables,
23
+ * floating blocks, or authored breaks keep the normal bottom-up pagination;
24
+ * those need structure-aware balancing rather than a height estimate.
25
+ */
26
+ type BalancedParagraphSectionHeightOptions = {
27
+ blocks: FlowBlock[];
28
+ measures: Measure[];
29
+ startIndex: number;
30
+ endIndex: number;
31
+ incomingSpacing: number;
32
+ columnCount: number;
33
+ availableHeight: number;
34
+ };
35
+ declare function balancedParagraphSectionHeight({ blocks, measures, startIndex, endIndex, incomingSpacing, columnCount, availableHeight }: BalancedParagraphSectionHeightOptions): number | undefined;
36
+ /**
37
+ * Handle a section break block.
38
+ * @param block - The section break block (current section's properties)
39
+ * @param paginator - The paginator instance
40
+ * @param nextSectionConfig - Page layout for the NEXT section
41
+ * @param nextSectionType - Break type of the NEXT section (how it starts relative to current)
42
+ */
43
+ declare function handleSectionBreak(_block: SectionBreakBlock, paginator: ReturnType<typeof createPaginator>, nextSectionConfig: SectionLayoutConfig, nextSectionType: SectionStart, nextSectionIndex?: number): void;
44
+ //#endregion
45
+ export { CONTINUE_PAGE_NUMBERING, DEFAULT_COLUMNS, SectionLayoutConfig, balancedParagraphSectionHeight, collectSectionConfigs, handleSectionBreak };
@@ -0,0 +1,216 @@
1
+ import { measuredLineAdvance } from "./lineFlow.js";
2
+ import { SECTION_START_PLACEMENT } from "./paginator.js";
3
+ import { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore } from "./paragraphSpacing.js";
4
+ import { physicalColumnRegionIsShared } from "./section-breaks.js";
5
+ import { panic } from "better-result";
6
+ //#region src/layout-engine/sectionLayout.ts
7
+ /**
8
+ * Section handling during pagination: per-section layout configuration,
9
+ * section-break scheduling (in place, next page, odd/even filler pages), and
10
+ * balanced column height for continuous sections.
11
+ */
12
+ const DEFAULT_COLUMNS = {
13
+ count: 1,
14
+ gap: 0
15
+ };
16
+ const CONTINUE_PAGE_NUMBERING = { type: "continue" };
17
+ function collectSectionConfigs(blocks, initialConfig, finalConfig) {
18
+ const configs = [];
19
+ const breakIndices = [];
20
+ let previousConfig = initialConfig;
21
+ for (let index = 0; index < blocks.length; index += 1) {
22
+ const block = blocks[index];
23
+ if (block?.kind !== "sectionBreak") continue;
24
+ const sectionBreak = block;
25
+ const config = {
26
+ pageSize: sectionBreak.pageSize ?? previousConfig.pageSize,
27
+ margins: sectionBreak.margins ?? previousConfig.margins,
28
+ pageNumbering: sectionBreak.pageNumbering ?? CONTINUE_PAGE_NUMBERING
29
+ };
30
+ if (sectionBreak.columns !== void 0) config.columns = sectionBreak.columns;
31
+ configs.push(config);
32
+ breakIndices.push(index);
33
+ previousConfig = configs.at(-1) ?? previousConfig;
34
+ }
35
+ configs.push(finalConfig);
36
+ return {
37
+ configs,
38
+ breakIndices
39
+ };
40
+ }
41
+ function balancedParagraphSectionHeight({ blocks, measures, startIndex, endIndex, incomingSpacing, columnCount, availableHeight }) {
42
+ if (columnCount <= 1 || startIndex >= endIndex || availableHeight <= 0) return;
43
+ const lineUnits = [];
44
+ let totalHeight = 0;
45
+ let tallestUnit = 0;
46
+ let trailingSpacing = incomingSpacing;
47
+ for (let index = startIndex; index < endIndex; index++) {
48
+ const block = blocks[index];
49
+ const measure = measures[index];
50
+ if (block?.kind !== "paragraph" || measure?.kind !== "paragraph") return;
51
+ if (block.attrs?.keepNext === true || block.attrs?.keepLines === true || block.runs.some((run) => run.kind === "text" && run.footnoteRefId !== void 0)) return;
52
+ const leadingSpacing = collapseParagraphSpacing({
53
+ before: getParagraphSpacingBefore(block),
54
+ after: trailingSpacing
55
+ });
56
+ if (measure.lines.length === 0) {
57
+ if (leadingSpacing > 0) {
58
+ lineUnits.push(leadingSpacing);
59
+ totalHeight += leadingSpacing;
60
+ tallestUnit = Math.max(tallestUnit, leadingSpacing);
61
+ }
62
+ }
63
+ for (let lineIndex = 0; lineIndex < measure.lines.length; lineIndex++) {
64
+ const line = measure.lines[lineIndex];
65
+ if (!line) continue;
66
+ const unitHeight = measuredLineAdvance(line) + (lineIndex === 0 ? leadingSpacing : 0);
67
+ lineUnits.push(unitHeight);
68
+ totalHeight += unitHeight;
69
+ tallestUnit = Math.max(tallestUnit, unitHeight);
70
+ }
71
+ if (tallestUnit > availableHeight || totalHeight > availableHeight * columnCount) return;
72
+ trailingSpacing = getParagraphSpacingAfter(block);
73
+ }
74
+ if (totalHeight <= 0) return;
75
+ const columnsNeeded = (targetHeight) => {
76
+ let usedHeight = 0;
77
+ let usedColumns = 1;
78
+ for (const unitHeight of lineUnits) if (usedHeight > 0 && usedHeight + unitHeight > targetHeight) {
79
+ usedColumns += 1;
80
+ usedHeight = unitHeight;
81
+ } else usedHeight += unitHeight;
82
+ return usedColumns;
83
+ };
84
+ let lower = Math.max(tallestUnit, totalHeight / columnCount);
85
+ let upper = Math.min(totalHeight, availableHeight);
86
+ if (columnsNeeded(upper) > columnCount) return;
87
+ for (let iteration = 0; iteration < 32; iteration++) {
88
+ const middle = (lower + upper) / 2;
89
+ if (columnsNeeded(middle) <= columnCount) upper = middle;
90
+ else lower = middle;
91
+ }
92
+ return Math.ceil(upper * 1e3) / 1e3;
93
+ }
94
+ /**
95
+ * Start the next section in the current page region, below the outgoing
96
+ * content.
97
+ *
98
+ * ECMA-376 §17.6.22: a `continuous` break normally keeps the current page
99
+ * geometry and defers the new size/margins to the next natural page break. But
100
+ * a break that changes page size or orientation cannot share a physical sheet
101
+ * with the preceding section, so Word and LibreOffice promote it to a page
102
+ * break (eigenpal/docx-editor#841).
103
+ *
104
+ * Compare against the last laid-out page without materializing one: a break
105
+ * before any content has no sheet to share, so it defers (the first content
106
+ * then opens a page with the new geometry) rather than stranding a blank
107
+ * leading page.
108
+ */
109
+ function startSectionInPlace(paginator, nextSectionConfig, nextSectionIndex) {
110
+ const currentPage = paginator.states.at(-1)?.page;
111
+ const nextSize = nextSectionConfig.pageSize;
112
+ const pageSizeChanges = currentPage != null && (Math.round(nextSize.w) !== Math.round(currentPage.size.w) || Math.round(nextSize.h) !== Math.round(currentPage.size.h));
113
+ if (nextSectionIndex !== void 0) paginator.startSection({
114
+ sectionIndex: nextSectionIndex,
115
+ pageNumbering: nextSectionConfig.pageNumbering,
116
+ placement: pageSizeChanges ? SECTION_START_PLACEMENT.NEXT_PAGE : SECTION_START_PLACEMENT.CONTINUOUS
117
+ });
118
+ if (pageSizeChanges) {
119
+ paginator.updatePageLayout(nextSize, nextSectionConfig.margins);
120
+ if (!paginator.retargetCurrentBlankPage()) paginator.forcePageBreak({ coalesceBlankPage: true });
121
+ return;
122
+ }
123
+ paginator.updatePageLayout(nextSize, nextSectionConfig.margins, false);
124
+ paginator.retargetCurrentBlankPage();
125
+ }
126
+ /**
127
+ * Whether a section opening on `target` would give two consecutive sheets page
128
+ * numbers that are both odd or both even under `w:evenAndOddHeaders` (§17.10.1). Odd and
129
+ * even page numbers alternate between right- and left-hand sheets, so a
130
+ * `w:pgNumType w:start` restart that repeats whether the previous sheet's number is odd or even
131
+ * needs a blank sheet between them.
132
+ */
133
+ function sectionStartRepeatsOddEven(paginator, target) {
134
+ if (target.page.headerFooterRefs?.evenAndOddHeaders !== true) return false;
135
+ const previous = paginator.states.at(-2)?.page;
136
+ if (previous === void 0 || previous.number !== target.page.number - 1) return false;
137
+ return previous.logicalNumber % 2 === target.page.logicalNumber % 2;
138
+ }
139
+ /**
140
+ * Keep the blank sheet the section break opened as a filler with no page
141
+ * furniture, and open the section again on the following sheet so its first
142
+ * page keeps the restarted page number.
143
+ */
144
+ function insertOddEvenFillerPage(paginator, filler, sectionIndex, sectionConfig) {
145
+ filler.page.headerFooterRefs = {};
146
+ paginator.startSection({
147
+ sectionIndex,
148
+ pageNumbering: sectionConfig.pageNumbering
149
+ });
150
+ paginator.forcePageBreak();
151
+ }
152
+ /**
153
+ * Handle a section break block.
154
+ * @param block - The section break block (current section's properties)
155
+ * @param paginator - The paginator instance
156
+ * @param nextSectionConfig - Page layout for the NEXT section
157
+ * @param nextSectionType - Break type of the NEXT section (how it starts relative to current)
158
+ */
159
+ function handleSectionBreak(_block, paginator, nextSectionConfig, nextSectionType, nextSectionIndex) {
160
+ switch (nextSectionType) {
161
+ case "nextPage": {
162
+ paginator.updatePageLayout(nextSectionConfig.pageSize, nextSectionConfig.margins);
163
+ if (nextSectionIndex !== void 0) paginator.startSection({
164
+ sectionIndex: nextSectionIndex,
165
+ pageNumbering: nextSectionConfig.pageNumbering
166
+ });
167
+ const target = paginator.forcePageBreak({ coalesceBlankPage: true });
168
+ if (nextSectionIndex !== void 0 && sectionStartRepeatsOddEven(paginator, target)) insertOddEvenFillerPage(paginator, target, nextSectionIndex, nextSectionConfig);
169
+ break;
170
+ }
171
+ case "evenPage":
172
+ if (paginator.forcePageBreak({ coalesceBlankPage: true }).page.number % 2 !== 0) paginator.forcePageBreak();
173
+ paginator.updatePageLayout(nextSectionConfig.pageSize, nextSectionConfig.margins);
174
+ if (nextSectionIndex !== void 0) paginator.startSection({
175
+ sectionIndex: nextSectionIndex,
176
+ pageNumbering: nextSectionConfig.pageNumbering
177
+ });
178
+ if (!paginator.retargetCurrentBlankPage()) panic("Even-page section target must be blank");
179
+ break;
180
+ case "oddPage":
181
+ if (paginator.forcePageBreak({ coalesceBlankPage: true }).page.number % 2 === 0) paginator.forcePageBreak();
182
+ paginator.updatePageLayout(nextSectionConfig.pageSize, nextSectionConfig.margins);
183
+ if (nextSectionIndex !== void 0) paginator.startSection({
184
+ sectionIndex: nextSectionIndex,
185
+ pageNumbering: nextSectionConfig.pageNumbering
186
+ });
187
+ if (!paginator.retargetCurrentBlankPage()) panic("Odd-page section target must be blank");
188
+ break;
189
+ case "nextColumn": {
190
+ const currentPage = paginator.states.at(-1)?.page;
191
+ if (currentPage === void 0 || !physicalColumnRegionIsShared({
192
+ pageSize: currentPage.size,
193
+ margins: currentPage.margins,
194
+ columns: paginator.columns
195
+ }, nextSectionConfig)) {
196
+ startSectionInPlace(paginator, nextSectionConfig, nextSectionIndex);
197
+ break;
198
+ }
199
+ if (nextSectionIndex !== void 0) paginator.startSection({
200
+ sectionIndex: nextSectionIndex,
201
+ pageNumbering: nextSectionConfig.pageNumbering,
202
+ placement: SECTION_START_PLACEMENT.CONTINUOUS
203
+ });
204
+ paginator.updatePageLayout(nextSectionConfig.pageSize, nextSectionConfig.margins, false);
205
+ paginator.forceColumnBreak();
206
+ return;
207
+ }
208
+ case "continuous":
209
+ startSectionInPlace(paginator, nextSectionConfig, nextSectionIndex);
210
+ break;
211
+ default:
212
+ }
213
+ paginator.updateColumns(nextSectionConfig.columns ?? DEFAULT_COLUMNS);
214
+ }
215
+ //#endregion
216
+ export { CONTINUE_PAGE_NUMBERING, DEFAULT_COLUMNS, balancedParagraphSectionHeight, collectSectionConfigs, handleSectionBreak };
@@ -0,0 +1,17 @@
1
+ import { TableBlock, TableMeasure } from "./types.js";
2
+ import { createPaginator } from "./paginator.js";
3
+ //#region src/layout-engine/tableLayout.d.ts
4
+ /**
5
+ * Calculate total height of header rows from their measures.
6
+ */
7
+ declare function getHeaderRowsHeight(measure: TableMeasure, headerRowCount: number): number;
8
+ /**
9
+ * Layout a table block onto pages.
10
+ */
11
+ declare function layoutTable(block: TableBlock, measure: TableMeasure, paginator: ReturnType<typeof createPaginator>, footnoteHeightById?: Map<number, number>): void;
12
+ /**
13
+ * Layout a floating table (anchored) without advancing the cursor.
14
+ */
15
+ declare function layoutFloatingTable(block: TableBlock, measure: TableMeasure, paginator: ReturnType<typeof createPaginator>, contentWidth: number): void;
16
+ //#endregion
17
+ export { getHeaderRowsHeight, layoutFloatingTable, layoutTable };