@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.
- package/dist/ai-edits/apply.js +565 -121
- package/dist/ai-edits/headless.d.ts +37 -1
- package/dist/ai-edits/headless.js +47 -16
- package/dist/ai-edits/minimal-replacement.d.ts +64 -0
- package/dist/ai-edits/minimal-replacement.js +282 -0
- package/dist/controller/layoutPipeline.js +11 -1
- package/dist/display-list/build/paragraphPrimitives.js +63 -7
- package/dist/display-list/build/textBoxPrimitives.js +1 -1
- package/dist/display-list/build/unsupported.d.ts +2 -2
- package/dist/display-list/build/unsupported.js +0 -0
- package/dist/display-list/dom/renderDisplayListToDom.d.ts +10 -1
- package/dist/display-list/dom/renderDisplayListToDom.js +8 -8
- package/dist/display-list/html/renderDisplayListToHtml.d.ts +21 -0
- package/dist/display-list/html/renderDisplayListToHtml.js +126 -0
- package/dist/display-list/selectDisplayPages.d.ts +6 -0
- package/dist/display-list/selectDisplayPages.js +44 -0
- package/dist/docx/blockContentParser.js +2 -2
- package/dist/docx/bulletMarkers.d.ts +18 -2
- package/dist/docx/bulletMarkers.js +27 -2
- package/dist/docx/drawingGroupChildren.d.ts +60 -0
- package/dist/docx/drawingGroupChildren.js +176 -0
- package/dist/docx/drawingUtils.js +3 -0
- package/dist/docx/groupDrawingParser.d.ts +7 -1
- package/dist/docx/groupDrawingParser.js +123 -32
- package/dist/docx/packageParts.d.ts +45 -1
- package/dist/docx/packageParts.js +93 -2
- package/dist/docx/paraIdAttribute.d.ts +3 -2
- package/dist/docx/paragraphParser.js +91 -7
- package/dist/docx/paragraphTextBoxEnrichment.js +126 -4
- package/dist/docx/runParser.js +10 -0
- package/dist/docx/selectiveSaveFlags.d.ts +10 -3
- package/dist/docx/selectiveSaveFlags.js +1 -1
- package/dist/docx/selectiveXmlPatch.d.ts +56 -34
- package/dist/docx/selectiveXmlPatch.js +222 -115
- package/dist/docx/serializer/groupTextBoxWriteBack.d.ts +12 -0
- package/dist/docx/serializer/groupTextBoxWriteBack.js +101 -0
- package/dist/docx/serializer/paragraphSerializer.js +5 -4
- package/dist/docx/serializer/runSerializer.d.ts +5 -1
- package/dist/docx/serializer/runSerializer.js +4 -3
- package/dist/docx/shapeAlternateContent.d.ts +25 -0
- package/dist/docx/shapeAlternateContent.js +52 -0
- package/dist/docx/textBoxParser.d.ts +10 -2
- package/dist/docx/textBoxParser.js +9 -3
- package/dist/export-pdf.d.ts +35 -2
- package/dist/export-pdf.js +51 -20
- package/dist/fonts/fontsourceFaces.d.ts +47 -0
- package/dist/fonts/fontsourceFaces.js +230 -0
- package/dist/internal/headlessRevisionResolution.js +50 -13
- package/dist/layout-bridge/convert/flowBlockPostprocessing.d.ts +8 -0
- package/dist/layout-bridge/convert/flowBlockPostprocessing.js +106 -0
- package/dist/layout-bridge/convert/flowBorders.d.ts +17 -0
- package/dist/layout-bridge/convert/flowBorders.js +31 -0
- package/dist/layout-bridge/convert/flowConversionShared.d.ts +111 -0
- package/dist/layout-bridge/convert/flowConversionShared.js +23 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +2 -1
- package/dist/layout-bridge/convert/footnoteLayout.js +2 -1
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +2 -1
- package/dist/layout-bridge/convert/imageConversion.d.ts +33 -0
- package/dist/layout-bridge/convert/imageConversion.js +118 -0
- package/dist/layout-bridge/convert/listMarkers.d.ts +28 -0
- package/dist/layout-bridge/convert/listMarkers.js +98 -0
- package/dist/layout-bridge/convert/noteReferences.d.ts +14 -0
- package/dist/layout-bridge/convert/noteReferences.js +29 -0
- package/dist/layout-bridge/convert/pageBreakSplitting.d.ts +16 -0
- package/dist/layout-bridge/convert/pageBreakSplitting.js +140 -0
- package/dist/layout-bridge/convert/paragraphAttrs.d.ts +20 -0
- package/dist/layout-bridge/convert/paragraphAttrs.js +234 -0
- package/dist/layout-bridge/convert/paragraphConversion.d.ts +12 -0
- package/dist/layout-bridge/convert/paragraphConversion.js +112 -0
- package/dist/layout-bridge/convert/paragraphRuns.d.ts +15 -0
- package/dist/layout-bridge/convert/paragraphRuns.js +281 -0
- package/dist/layout-bridge/convert/runFormattingMerge.d.ts +19 -0
- package/dist/layout-bridge/convert/runFormattingMerge.js +167 -0
- package/dist/layout-bridge/convert/runMarkFormatting.d.ts +11 -0
- package/dist/layout-bridge/convert/runMarkFormatting.js +291 -0
- package/dist/layout-bridge/convert/sectionBoundaries.d.ts +35 -0
- package/dist/layout-bridge/convert/sectionBoundaries.js +114 -0
- package/dist/layout-bridge/convert/tableConversion.d.ts +12 -0
- package/dist/layout-bridge/convert/tableConversion.js +303 -0
- package/dist/layout-bridge/convert/textBoxFill.d.ts +20 -0
- package/dist/layout-bridge/convert/textBoxFill.js +42 -0
- package/dist/layout-bridge/convert/textFormattingConversion.d.ts +22 -0
- package/dist/layout-bridge/convert/textFormattingConversion.js +120 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +5 -113
- package/dist/layout-bridge/convert/toFlowBlocks.js +11 -2060
- package/dist/layout-bridge/engine/hitTest.js +1 -1
- package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
- package/dist/layout-bridge/engine/measuring/index.js +2 -1
- package/dist/layout-bridge/engine/selectionRects.js +1 -1
- package/dist/layout-engine/imageLayout.d.ts +9 -0
- package/dist/layout-engine/imageLayout.js +48 -0
- package/dist/layout-engine/index.d.ts +4 -19
- package/dist/layout-engine/index.js +14 -910
- package/dist/layout-engine/keep-together.d.ts +5 -2
- package/dist/layout-engine/keep-together.js +5 -1
- package/dist/layout-engine/layoutFlowShared.d.ts +6 -0
- package/dist/layout-engine/layoutFlowShared.js +8 -0
- package/dist/layout-engine/measure/advanceComposition.js +49 -3
- package/dist/layout-engine/measure/crossRunWords.d.ts +45 -0
- package/dist/layout-engine/measure/crossRunWords.js +172 -0
- package/dist/layout-engine/measure/floatingLineClearance.d.ts +24 -0
- package/dist/layout-engine/measure/floatingLineClearance.js +44 -0
- package/dist/layout-engine/measure/inlineObjectMetrics.d.ts +6 -0
- package/dist/layout-engine/measure/inlineObjectMetrics.js +52 -0
- package/dist/layout-engine/measure/justification.d.ts +32 -0
- package/dist/layout-engine/measure/justification.js +83 -0
- package/dist/layout-engine/measure/lineFitting.d.ts +48 -0
- package/dist/layout-engine/measure/lineFitting.js +145 -0
- package/dist/layout-engine/measure/lineTypography.d.ts +44 -0
- package/dist/layout-engine/measure/lineTypography.js +124 -0
- package/dist/layout-engine/measure/measureBlocks.js +7 -4
- package/dist/layout-engine/measure/measureContainer.js +46 -3
- package/dist/layout-engine/measure/measureHelpers.js +13 -3
- package/dist/layout-engine/measure/measureParagraph.d.ts +1 -20
- package/dist/layout-engine/measure/measureParagraph.js +41 -780
- package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +109 -0
- package/dist/layout-engine/measure/paragraphMeasureShared.js +82 -0
- package/dist/layout-engine/measure/paragraphTabs.d.ts +18 -0
- package/dist/layout-engine/measure/paragraphTabs.js +78 -0
- package/dist/layout-engine/measure/smallCapsCasing.d.ts +56 -0
- package/dist/layout-engine/measure/smallCapsCasing.js +88 -0
- package/dist/layout-engine/paginator.d.ts +2 -0
- package/dist/layout-engine/paginator.js +5 -1
- package/dist/layout-engine/paragraphLayout.d.ts +23 -0
- package/dist/layout-engine/paragraphLayout.js +204 -0
- package/dist/layout-engine/paragraphSpacing.d.ts +4 -2
- package/dist/layout-engine/paragraphSpacing.js +2 -1
- package/dist/layout-engine/sectionLayout.d.ts +45 -0
- package/dist/layout-engine/sectionLayout.js +216 -0
- package/dist/layout-engine/tableLayout.d.ts +17 -0
- package/dist/layout-engine/tableLayout.js +369 -0
- package/dist/layout-engine/tableRowBreak.d.ts +8 -1
- package/dist/layout-engine/tableRowBreak.js +22 -1
- package/dist/layout-engine/textBoxLayout.d.ts +15 -0
- package/dist/layout-engine/textBoxLayout.js +137 -0
- package/dist/layout-engine/types.d.ts +21 -2
- package/dist/layout-painter/renderPage.js +1 -0
- package/dist/layout-painter/renderParagraph.js +41 -4
- package/dist/layout-painter/renderTextBox.d.ts +12 -2
- package/dist/layout-painter/renderTextBox.js +26 -2
- package/dist/paged-layout/sectionBlockWidths.d.ts +2 -1
- package/dist/paged-layout/sectionBlockWidths.js +1 -1
- package/dist/prosemirror/alternateContentAttrs.d.ts +9 -0
- package/dist/prosemirror/alternateContentAttrs.js +35 -0
- package/dist/prosemirror/attrs/index.js +90 -0
- package/dist/prosemirror/commands/comments.js +51 -41
- package/dist/prosemirror/contentControlRevisions.d.ts +43 -0
- package/dist/prosemirror/contentControlRevisions.js +84 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +46 -22
- package/dist/prosemirror/conversion/toProseDoc.js +42 -19
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +7 -7
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -7
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +6 -3
- package/dist/prosemirror/extensions/nodes/SdtExtension.js +9 -2
- package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +3 -0
- package/dist/prosemirror/listMarker.js +2 -2
- package/dist/prosemirror/paragraphDirection.d.ts +29 -2
- package/dist/prosemirror/paragraphDirection.js +21 -2
- package/dist/prosemirror/plugins/suggestionMode.js +37 -8
- package/dist/prosemirror/rejoinRunCarriers.d.ts +27 -0
- package/dist/prosemirror/rejoinRunCarriers.js +66 -0
- package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +10 -0
- package/dist/prosemirror/runIdentityAcrossRevisions.js +90 -0
- package/dist/prosemirror/schema/nodes.d.ts +37 -5
- package/dist/server.d.ts +3 -2
- package/dist/types/content.d.ts +2 -2
- package/package.json +2 -2
- package/dist/layout-engine/justifiedLineFit.d.ts +0 -7
- 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 };
|