@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,369 @@
|
|
|
1
|
+
import { projectedFootnoteReserveGrowth } from "./layoutFlowShared.js";
|
|
2
|
+
import { resolveFloatingTablePageX } from "./measure/floatingTablePosition.js";
|
|
3
|
+
import { resolveTableInlineOffset } from "./measure/tableInlinePlacement.js";
|
|
4
|
+
import { isEmptyParagraph } from "./paragraphSpacing.js";
|
|
5
|
+
import { buildTableRowBreakInfo, getRowContinuationSkip, snapRowBreak } from "./tableRowBreak.js";
|
|
6
|
+
import "./types.js";
|
|
7
|
+
//#region src/layout-engine/tableLayout.ts
|
|
8
|
+
/**
|
|
9
|
+
* Places measured tables onto pages: row splitting across pages, repeated
|
|
10
|
+
* header rows, rendered-break and tracked-change row handling, row footnote
|
|
11
|
+
* collection, and floating table placement.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Count consecutive header rows at the start of a table.
|
|
15
|
+
* Header rows are marked with isHeader: true in the block data.
|
|
16
|
+
*/
|
|
17
|
+
function countHeaderRows(block) {
|
|
18
|
+
let count = 0;
|
|
19
|
+
for (const row of block.rows) if (row.isHeader) count++;
|
|
20
|
+
else break;
|
|
21
|
+
return count;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Calculate total height of header rows from their measures.
|
|
25
|
+
*/
|
|
26
|
+
function getHeaderRowsHeight(measure, headerRowCount) {
|
|
27
|
+
let height = 0;
|
|
28
|
+
for (let i = 0; i < headerRowCount && i < measure.rows.length; i++) height += measure.rows[i].height;
|
|
29
|
+
return height;
|
|
30
|
+
}
|
|
31
|
+
const tableRowStartsWithRenderedPageBreak = (block, rowIndex) => {
|
|
32
|
+
const row = block.rows[rowIndex];
|
|
33
|
+
const visibleCells = row?.cells.filter((cell) => cell.blocks.some((cellBlock) => cellBlock.kind !== "paragraph" || !isEmptyParagraph(cellBlock)));
|
|
34
|
+
if (!visibleCells || visibleCells.length === 0) return false;
|
|
35
|
+
const startsWithRenderedPageBreak = (cell) => {
|
|
36
|
+
const firstVisibleBlock = cell.blocks.find((cellBlock) => cellBlock.kind !== "paragraph" || !isEmptyParagraph(cellBlock));
|
|
37
|
+
return firstVisibleBlock?.kind === "paragraph" && firstVisibleBlock.attrs?.renderedPageBreakBefore === true;
|
|
38
|
+
};
|
|
39
|
+
if (row?.heightRule === "exact") return startsWithRenderedPageBreak(visibleCells[0]);
|
|
40
|
+
return visibleCells.every(startsWithRenderedPageBreak);
|
|
41
|
+
};
|
|
42
|
+
const getVerticallyMergedRows = (block) => {
|
|
43
|
+
const mergedRows = /* @__PURE__ */ new Set();
|
|
44
|
+
for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex += 1) {
|
|
45
|
+
const row = block.rows[rowIndex];
|
|
46
|
+
if (!row) continue;
|
|
47
|
+
for (const cell of row.cells) {
|
|
48
|
+
const rowSpan = cell.rowSpan ?? 1;
|
|
49
|
+
if (rowSpan <= 1) continue;
|
|
50
|
+
for (let mergedRowIndex = rowIndex; mergedRowIndex < Math.min(block.rows.length, rowIndex + rowSpan); mergedRowIndex += 1) mergedRows.add(mergedRowIndex);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return mergedRows;
|
|
54
|
+
};
|
|
55
|
+
const flowBlockHasTrackedChanges = (block) => {
|
|
56
|
+
if (block.kind === "paragraph") return block.runs.some((run) => {
|
|
57
|
+
if (run.kind === "lineBreak") return false;
|
|
58
|
+
return run.isInsertion || run.isDeletion;
|
|
59
|
+
});
|
|
60
|
+
if (block.kind === "table") return block.rows.some((row) => row.cells.some((cell) => cell.blocks.some(flowBlockHasTrackedChanges)));
|
|
61
|
+
if (block.kind === "textBox") return block.content.some(flowBlockHasTrackedChanges);
|
|
62
|
+
return false;
|
|
63
|
+
};
|
|
64
|
+
const tableRowHasTrackedChanges = (block, rowIndex) => block.rows[rowIndex]?.cells.some((cell) => cell.blocks.some(flowBlockHasTrackedChanges)) ?? false;
|
|
65
|
+
/**
|
|
66
|
+
* Layout a table block onto pages.
|
|
67
|
+
*/
|
|
68
|
+
function layoutTable(block, measure, paginator, footnoteHeightById) {
|
|
69
|
+
const rows = measure.rows;
|
|
70
|
+
if (rows.length === 0) return;
|
|
71
|
+
const rowFootnoteIds = block.rows.map((row) => collectTableRowFootnoteIds(row, footnoteHeightById));
|
|
72
|
+
const headerRowCount = countHeaderRows(block);
|
|
73
|
+
const headerRowsHeight = getHeaderRowsHeight(measure, headerRowCount);
|
|
74
|
+
let currentRowIndex = 0;
|
|
75
|
+
const breakInfo = buildTableRowBreakInfo(block, measure);
|
|
76
|
+
const verticallyMergedRows = getVerticallyMergedRows(block);
|
|
77
|
+
const computeTableX = ({ columnIndex, rowIndex }) => {
|
|
78
|
+
return paginator.getColumnX(columnIndex) + resolveTableInlineOffset({
|
|
79
|
+
table: block,
|
|
80
|
+
rowJustification: block.rows[rowIndex]?.justification,
|
|
81
|
+
frameWidth: paginator.columnWidth,
|
|
82
|
+
tableWidth: measure.totalWidth
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
const getCurrentRowCapacity = (state = paginator.getCurrentState()) => state.rawContentBottom - state.topMargin;
|
|
86
|
+
const hasAdjacentPriorTableRows = (rowIndex, state = paginator.getCurrentState()) => {
|
|
87
|
+
const previous = state.page.fragments.at(-1);
|
|
88
|
+
return previous?.kind === "table" && previous.blockId === block.id && previous.toRow === rowIndex && previous.y + previous.height === state.cursorY;
|
|
89
|
+
};
|
|
90
|
+
const shouldRepeatHeaderRows = (rowIndex, consumed, state = paginator.getCurrentState()) => headerRowCount > 0 && rowIndex >= headerRowCount && !(consumed === 0 && hasAdjacentPriorTableRows(rowIndex, state));
|
|
91
|
+
const rowOpensFlowRegion = (rowIndex, state) => {
|
|
92
|
+
if (state.cursorY === state.topMargin) return true;
|
|
93
|
+
const previous = state.page.fragments.at(-1);
|
|
94
|
+
return rowIndex > 0 && rowIndex <= headerRowCount && previous?.kind === "table" && previous.blockId === block.id && previous.fromRow === 0 && previous.toRow === rowIndex && previous.y === state.topMargin && previous.y + previous.height === state.cursorY;
|
|
95
|
+
};
|
|
96
|
+
const canSplitRow = (rowIndex, state = paginator.getCurrentState()) => {
|
|
97
|
+
const row = rows[rowIndex];
|
|
98
|
+
const sourceRow = block.rows[rowIndex];
|
|
99
|
+
if (!row || !sourceRow || sourceRow.isHeader || verticallyMergedRows.has(rowIndex)) return false;
|
|
100
|
+
if ((breakInfo.breakOffsets[rowIndex]?.length ?? 0) <= 1) return false;
|
|
101
|
+
const freshHeaderOverhead = headerRowCount > 0 && rowIndex >= headerRowCount ? headerRowsHeight : 0;
|
|
102
|
+
const oversized = row.height + freshHeaderOverhead > getCurrentRowCapacity(state);
|
|
103
|
+
if (sourceRow.cantSplit) return oversized && sourceRow.heightRule !== "exact" && rowOpensFlowRegion(rowIndex, state);
|
|
104
|
+
if (!oversized && (state.footnoteHeight > 0 || (rowFootnoteIds[rowIndex]?.length ?? 0) > 0)) return false;
|
|
105
|
+
const currentHeaderOverhead = shouldRepeatHeaderRows(rowIndex, 0, state) ? headerRowsHeight : 0;
|
|
106
|
+
const currentAvailableHeight = paginator.getAvailableHeight() - currentHeaderOverhead - state.trailingSpacing;
|
|
107
|
+
return oversized || row.height > currentAvailableHeight;
|
|
108
|
+
};
|
|
109
|
+
while (currentRowIndex < rows.length) {
|
|
110
|
+
const rowState = paginator.getCurrentState();
|
|
111
|
+
const rowHeaderOverhead = shouldRepeatHeaderRows(currentRowIndex, 0, rowState) ? headerRowsHeight : 0;
|
|
112
|
+
const rowAvailableHeight = paginator.getAvailableHeight() - rowHeaderOverhead - rowState.trailingSpacing;
|
|
113
|
+
const rowStartsFreshPage = rowState.cursorY === rowState.topMargin && rowState.page.fragments.length === 0;
|
|
114
|
+
if (block.rows[currentRowIndex]?.breakBefore === "page" && !rowStartsFreshPage) {
|
|
115
|
+
paginator.forcePageBreak();
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!rowStartsFreshPage && tableRowStartsWithRenderedPageBreak(block, currentRowIndex) && !tableRowHasTrackedChanges(block, currentRowIndex) && rows[currentRowIndex].height > rowAvailableHeight) {
|
|
119
|
+
paginator.forcePageBreak();
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const splittableRow = rows[currentRowIndex];
|
|
123
|
+
if (canSplitRow(currentRowIndex)) {
|
|
124
|
+
let consumed = 0;
|
|
125
|
+
const renderedBreakHints = tableRowHasTrackedChanges(block, currentRowIndex) ? [] : breakInfo.renderedBreakHints[currentRowIndex] ?? [];
|
|
126
|
+
let renderedBreakHintIndex = 0;
|
|
127
|
+
const discardPassedRenderedBreakHints = () => {
|
|
128
|
+
while ((renderedBreakHints[renderedBreakHintIndex]?.offset ?? Infinity) <= consumed) renderedBreakHintIndex += 1;
|
|
129
|
+
};
|
|
130
|
+
const advanceAfterNaturalFlowBreak = (previousPageNumber) => {
|
|
131
|
+
discardPassedRenderedBreakHints();
|
|
132
|
+
if (paginator.getCurrentState().page.number > previousPageNumber && renderedBreakHintIndex < renderedBreakHints.length) renderedBreakHintIndex += 1;
|
|
133
|
+
};
|
|
134
|
+
while (consumed < splittableRow.height) {
|
|
135
|
+
const sliceState = paginator.getCurrentState();
|
|
136
|
+
const repeatHeaderRows = shouldRepeatHeaderRows(currentRowIndex, consumed, sliceState);
|
|
137
|
+
const headerOverhead = repeatHeaderRows ? headerRowsHeight : 0;
|
|
138
|
+
const sliceAvail = paginator.getAvailableHeight() - headerOverhead - (consumed === 0 ? sliceState.trailingSpacing : 0);
|
|
139
|
+
let slice = snapRowBreak(breakInfo, currentRowIndex, consumed, sliceAvail);
|
|
140
|
+
let forceRenderedPageBreak = false;
|
|
141
|
+
if (slice <= 0) {
|
|
142
|
+
if (!(sliceState.cursorY === sliceState.topMargin && sliceState.page.fragments.length === 0)) {
|
|
143
|
+
const previousPageNumber = sliceState.page.number;
|
|
144
|
+
paginator.forceColumnBreak();
|
|
145
|
+
advanceAfterNaturalFlowBreak(previousPageNumber);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const from = consumed;
|
|
149
|
+
slice = (breakInfo.breakOffsets[currentRowIndex]?.find((o) => o > from) ?? splittableRow.height) - consumed;
|
|
150
|
+
}
|
|
151
|
+
discardPassedRenderedBreakHints();
|
|
152
|
+
const renderedBreakHint = renderedBreakHints[renderedBreakHintIndex];
|
|
153
|
+
if (renderedBreakHint && renderedBreakHint.offset <= consumed + slice && sliceAvail - (renderedBreakHint.offset - consumed) <= renderedBreakHint.lineAdvance * 3) {
|
|
154
|
+
slice = renderedBreakHint.offset - consumed;
|
|
155
|
+
renderedBreakHintIndex += 1;
|
|
156
|
+
forceRenderedPageBreak = true;
|
|
157
|
+
}
|
|
158
|
+
const sliceBottom = consumed + slice;
|
|
159
|
+
const continuationSkip = sliceBottom < splittableRow.height ? getRowContinuationSkip(breakInfo, currentRowIndex, sliceBottom) : 0;
|
|
160
|
+
const nextConsumed = Math.min(splittableRow.height, sliceBottom + continuationSkip);
|
|
161
|
+
const moreAfter = !(nextConsumed >= splittableRow.height) || currentRowIndex + 1 < rows.length;
|
|
162
|
+
const fragmentHeight = headerOverhead + slice;
|
|
163
|
+
const sliceFragment = {
|
|
164
|
+
kind: "table",
|
|
165
|
+
blockId: block.id,
|
|
166
|
+
x: computeTableX({
|
|
167
|
+
columnIndex: sliceState.columnIndex,
|
|
168
|
+
rowIndex: currentRowIndex
|
|
169
|
+
}),
|
|
170
|
+
y: 0,
|
|
171
|
+
width: measure.totalWidth,
|
|
172
|
+
height: fragmentHeight,
|
|
173
|
+
fromRow: currentRowIndex,
|
|
174
|
+
toRow: currentRowIndex + 1,
|
|
175
|
+
...block.pmStart !== void 0 ? { pmStart: block.pmStart } : {},
|
|
176
|
+
...block.pmEnd !== void 0 ? { pmEnd: block.pmEnd } : {},
|
|
177
|
+
...consumed > 0 || currentRowIndex > 0 ? { continuesFromPrev: true } : {},
|
|
178
|
+
...moreAfter ? { continuesOnNext: true } : {},
|
|
179
|
+
...repeatHeaderRows ? { headerRowCount } : {},
|
|
180
|
+
...consumed > 0 ? { topClip: consumed } : {},
|
|
181
|
+
...sliceBottom >= splittableRow.height ? {} : { bottomClip: sliceBottom },
|
|
182
|
+
...block.sdtGroups ? { sdtGroups: block.sdtGroups } : {}
|
|
183
|
+
};
|
|
184
|
+
const sliceResult = paginator.addFragment(sliceFragment, fragmentHeight, 0, 0);
|
|
185
|
+
sliceFragment.y = sliceResult.y;
|
|
186
|
+
sliceFragment.x = computeTableX({
|
|
187
|
+
columnIndex: sliceResult.state.columnIndex,
|
|
188
|
+
rowIndex: currentRowIndex
|
|
189
|
+
});
|
|
190
|
+
consumed = nextConsumed;
|
|
191
|
+
if (consumed < splittableRow.height) if (forceRenderedPageBreak) paginator.forcePageBreak();
|
|
192
|
+
else {
|
|
193
|
+
const previousPageNumber = paginator.getCurrentState().page.number;
|
|
194
|
+
paginator.forceColumnBreak();
|
|
195
|
+
advanceAfterNaturalFlowBreak(previousPageNumber);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
currentRowIndex += 1;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const state = paginator.getCurrentState();
|
|
202
|
+
const rawAvailableHeight = paginator.getAvailableHeight();
|
|
203
|
+
const isFirstFragment = currentRowIndex === 0;
|
|
204
|
+
const bandSkip = isFirstFragment ? measure.bandSkipBefore ?? 0 : 0;
|
|
205
|
+
const availableHeight = rawAvailableHeight - (isFirstFragment ? Math.max(bandSkip, state.trailingSpacing) : 0);
|
|
206
|
+
const repeatHeaderRowsForNormalFragment = shouldRepeatHeaderRows(currentRowIndex, 0, state);
|
|
207
|
+
const normalHeaderOverhead = repeatHeaderRowsForNormalFragment ? headerRowsHeight : 0;
|
|
208
|
+
let rowsHeight = 0;
|
|
209
|
+
let fittingRows = 0;
|
|
210
|
+
const pageFootnoteIds = new Set(state.page.footnoteIds ?? []);
|
|
211
|
+
const fragmentFootnoteIds = [];
|
|
212
|
+
let fragmentFootnoteHeight = 0;
|
|
213
|
+
let retryOnNextFlowRegion = false;
|
|
214
|
+
const fragmentX = computeTableX({
|
|
215
|
+
columnIndex: state.columnIndex,
|
|
216
|
+
rowIndex: currentRowIndex
|
|
217
|
+
});
|
|
218
|
+
for (let j = currentRowIndex; j < rows.length; j++) {
|
|
219
|
+
if (j > currentRowIndex && block.rows[j]?.breakBefore === "page") break;
|
|
220
|
+
if (j > currentRowIndex && computeTableX({
|
|
221
|
+
columnIndex: state.columnIndex,
|
|
222
|
+
rowIndex: j
|
|
223
|
+
}) !== fragmentX) break;
|
|
224
|
+
const rowHeight = rows[j].height;
|
|
225
|
+
const currentRowFootnoteIds = rowFootnoteIds[j] ?? [];
|
|
226
|
+
const fragmentFootnoteCountBeforeRow = fragmentFootnoteIds.length;
|
|
227
|
+
let rowFootnoteHeight = 0;
|
|
228
|
+
for (const id of currentRowFootnoteIds) {
|
|
229
|
+
if (pageFootnoteIds.has(id) || fragmentFootnoteIds.includes(id)) continue;
|
|
230
|
+
fragmentFootnoteIds.push(id);
|
|
231
|
+
rowFootnoteHeight += footnoteHeightById?.get(id) ?? 0;
|
|
232
|
+
}
|
|
233
|
+
const candidateFootnoteHeight = fragmentFootnoteHeight + rowFootnoteHeight;
|
|
234
|
+
const footnoteGrowth = projectedFootnoteReserveGrowth(state, candidateFootnoteHeight + (state.footnoteDemandHeight === 0 && candidateFootnoteHeight > 0 ? 12 : 0));
|
|
235
|
+
if (rowsHeight + rowHeight + normalHeaderOverhead + footnoteGrowth <= availableHeight) {
|
|
236
|
+
rowsHeight += rowHeight;
|
|
237
|
+
fragmentFootnoteHeight += rowFootnoteHeight;
|
|
238
|
+
fittingRows++;
|
|
239
|
+
} else if (fittingRows === 0) {
|
|
240
|
+
if (state.cursorY === state.topMargin && state.page.fragments.length === 0) {
|
|
241
|
+
rowsHeight += rowHeight;
|
|
242
|
+
fragmentFootnoteHeight += rowFootnoteHeight;
|
|
243
|
+
fittingRows++;
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
fragmentFootnoteIds.splice(fragmentFootnoteCountBeforeRow);
|
|
247
|
+
paginator.forceColumnBreak();
|
|
248
|
+
retryOnNextFlowRegion = true;
|
|
249
|
+
break;
|
|
250
|
+
} else {
|
|
251
|
+
fragmentFootnoteIds.splice(fragmentFootnoteCountBeforeRow);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (retryOnNextFlowRegion) continue;
|
|
256
|
+
const fragmentHeight = rowsHeight + normalHeaderOverhead;
|
|
257
|
+
const isLastFragment = currentRowIndex + fittingRows >= rows.length;
|
|
258
|
+
const desiredX = fragmentX;
|
|
259
|
+
const fragment = {
|
|
260
|
+
kind: "table",
|
|
261
|
+
blockId: block.id,
|
|
262
|
+
x: desiredX,
|
|
263
|
+
y: 0,
|
|
264
|
+
width: measure.totalWidth,
|
|
265
|
+
height: fragmentHeight,
|
|
266
|
+
fromRow: currentRowIndex,
|
|
267
|
+
toRow: currentRowIndex + fittingRows,
|
|
268
|
+
...block.pmStart !== void 0 ? { pmStart: block.pmStart } : {},
|
|
269
|
+
...block.pmEnd !== void 0 ? { pmEnd: block.pmEnd } : {},
|
|
270
|
+
...!isFirstFragment ? { continuesFromPrev: true } : {},
|
|
271
|
+
...!isLastFragment ? { continuesOnNext: true } : {},
|
|
272
|
+
...repeatHeaderRowsForNormalFragment ? { headerRowCount } : {},
|
|
273
|
+
...block.sdtGroups ? { sdtGroups: block.sdtGroups } : {}
|
|
274
|
+
};
|
|
275
|
+
if (fragmentFootnoteHeight > 0) paginator.addFootnoteHeight(fragmentFootnoteHeight, fragmentFootnoteIds);
|
|
276
|
+
fragment.y = paginator.addFragment(fragment, fragmentHeight, bandSkip, 0).y;
|
|
277
|
+
fragment.x = desiredX;
|
|
278
|
+
currentRowIndex += fittingRows;
|
|
279
|
+
if (currentRowIndex < rows.length) {
|
|
280
|
+
if (canSplitRow(currentRowIndex)) {
|
|
281
|
+
const nextState = paginator.getCurrentState();
|
|
282
|
+
const nextHeaderOverhead = shouldRepeatHeaderRows(currentRowIndex, 0, nextState) ? headerRowsHeight : 0;
|
|
283
|
+
const nextSliceAvail = paginator.getAvailableHeight() - nextHeaderOverhead - nextState.trailingSpacing;
|
|
284
|
+
if (snapRowBreak(breakInfo, currentRowIndex, 0, nextSliceAvail) > 0) continue;
|
|
285
|
+
}
|
|
286
|
+
const nextState = paginator.getCurrentState();
|
|
287
|
+
const nextRowHeight = rows[currentRowIndex].height + (shouldRepeatHeaderRows(currentRowIndex, 0, nextState) ? headerRowsHeight : 0);
|
|
288
|
+
paginator.ensureFits(nextRowHeight);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function collectTableRowFootnoteIds(row, footnoteHeightById) {
|
|
293
|
+
if (!row || !footnoteHeightById || row.hidden) return [];
|
|
294
|
+
const ids = [];
|
|
295
|
+
const walk = (blocks) => {
|
|
296
|
+
for (const block of blocks) {
|
|
297
|
+
if (block.kind === "paragraph") {
|
|
298
|
+
for (const run of block.runs) if (run.kind === "text" && run.footnoteRefId !== void 0 && footnoteHeightById.has(run.footnoteRefId) && !ids.includes(run.footnoteRefId)) ids.push(run.footnoteRefId);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (block.kind === "table") {
|
|
302
|
+
for (const nestedRow of block.rows) {
|
|
303
|
+
if (nestedRow.hidden) continue;
|
|
304
|
+
for (const cell of nestedRow.cells) walk(cell.blocks);
|
|
305
|
+
}
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (block.kind === "textBox") walk(block.content);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
for (const cell of row.cells) walk(cell.blocks);
|
|
312
|
+
return ids;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Layout a floating table (anchored) without advancing the cursor.
|
|
316
|
+
*/
|
|
317
|
+
function layoutFloatingTable(block, measure, paginator, contentWidth) {
|
|
318
|
+
const state = paginator.getCurrentState();
|
|
319
|
+
const floating = block.floating;
|
|
320
|
+
const page = state.page;
|
|
321
|
+
const margins = page.margins;
|
|
322
|
+
const tableWidth = measure.totalWidth;
|
|
323
|
+
const tableHeight = measure.totalHeight;
|
|
324
|
+
const contentHeight = page.size.h - margins.top - margins.bottom;
|
|
325
|
+
let baseY = margins.top;
|
|
326
|
+
if (floating?.vertAnchor === "page") baseY = 0;
|
|
327
|
+
else if (floating?.vertAnchor === "text") baseY = state.cursorY;
|
|
328
|
+
let x = paginator.getColumnX(state.columnIndex);
|
|
329
|
+
if (floating) x = resolveFloatingTablePageX({
|
|
330
|
+
anchor: floating,
|
|
331
|
+
justification: block.justification,
|
|
332
|
+
tableWidth,
|
|
333
|
+
marginWidth: contentWidth,
|
|
334
|
+
pageWidth: page.size.w,
|
|
335
|
+
marginLeft: margins.left,
|
|
336
|
+
textFrameWidth: paginator.columnWidth,
|
|
337
|
+
textFrameLeft: paginator.getColumnX(state.columnIndex)
|
|
338
|
+
});
|
|
339
|
+
let y = state.cursorY;
|
|
340
|
+
let usedExplicitY = false;
|
|
341
|
+
if (floating?.tblpY !== void 0) {
|
|
342
|
+
y = baseY + floating.tblpY;
|
|
343
|
+
usedExplicitY = true;
|
|
344
|
+
} else if (floating?.tblpYSpec) {
|
|
345
|
+
usedExplicitY = true;
|
|
346
|
+
const spec = floating.tblpYSpec;
|
|
347
|
+
if (spec === "top") y = baseY;
|
|
348
|
+
else if (spec === "bottom") y = baseY + contentHeight - tableHeight;
|
|
349
|
+
else if (spec === "center") y = baseY + (contentHeight - tableHeight) / 2;
|
|
350
|
+
}
|
|
351
|
+
if (!usedExplicitY) y = paginator.ensureFits(tableHeight).cursorY;
|
|
352
|
+
const fragment = {
|
|
353
|
+
kind: "table",
|
|
354
|
+
blockId: block.id,
|
|
355
|
+
x,
|
|
356
|
+
y,
|
|
357
|
+
width: tableWidth,
|
|
358
|
+
height: tableHeight,
|
|
359
|
+
fromRow: 0,
|
|
360
|
+
toRow: block.rows.length,
|
|
361
|
+
...block.pmStart !== void 0 ? { pmStart: block.pmStart } : {},
|
|
362
|
+
...block.pmEnd !== void 0 ? { pmEnd: block.pmEnd } : {},
|
|
363
|
+
isFloating: true,
|
|
364
|
+
...block.sdtGroups ? { sdtGroups: block.sdtGroups } : {}
|
|
365
|
+
};
|
|
366
|
+
paginator.addUnflowedFragment(fragment);
|
|
367
|
+
}
|
|
368
|
+
//#endregion
|
|
369
|
+
export { getHeaderRowsHeight, layoutFloatingTable, layoutTable };
|
|
@@ -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 };
|
|
@@ -141,6 +141,27 @@ const continuationSkipAfter = (geometries, offset) => {
|
|
|
141
141
|
if (skips.length === 0 || skips.some((skip) => skip <= BREAK_OFFSET_EPSILON)) return 0;
|
|
142
142
|
return Math.min(...skips);
|
|
143
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
|
+
}
|
|
144
165
|
/** Build break geometry for a table from its block + measure. */
|
|
145
166
|
function buildTableRowBreakInfo(block, measure) {
|
|
146
167
|
const rowCount = measure.rows.length;
|
|
@@ -208,4 +229,4 @@ function snapRowBreak(info, rowIndex, fromOffset, maxSlice) {
|
|
|
208
229
|
return best;
|
|
209
230
|
}
|
|
210
231
|
//#endregion
|
|
211
|
-
export { buildTableRowBreakInfo, getRowContinuationSkip, snapRowBreak };
|
|
232
|
+
export { buildTableRowBreakInfo, getRowContinuationSkip, snapRowBreak, tableKeepNextOpeningHeight };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { TextBoxBlock, TextBoxMeasure } from "./types.js";
|
|
2
|
+
import { createPaginator } from "./paginator.js";
|
|
3
|
+
//#region src/layout-engine/textBoxLayout.d.ts
|
|
4
|
+
type LayoutTextBoxOptions = {
|
|
5
|
+
paginator: ReturnType<typeof createPaginator>;
|
|
6
|
+
sectionMarginTop: number;
|
|
7
|
+
sectionPageHeight: number;
|
|
8
|
+
sectionMarginBottom: number;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Layout a text box block onto pages.
|
|
12
|
+
*/
|
|
13
|
+
declare function layoutTextBox(block: TextBoxBlock, measure: TextBoxMeasure, { paginator, sectionMarginTop, sectionPageHeight, sectionMarginBottom }: LayoutTextBoxOptions): void;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { layoutTextBox };
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { emuToPixels } from "../utils/units.js";
|
|
2
|
+
import { bandFragmentX, bandTopContentY, isPageFrameRelativeAnchor } from "./textBoxFlow.js";
|
|
3
|
+
import { floatingTextBoxReservesBand } from "./types.js";
|
|
4
|
+
//#region src/layout-engine/textBoxLayout.ts
|
|
5
|
+
/**
|
|
6
|
+
* Places text boxes onto pages: inline and anchored placement, page-pinned
|
|
7
|
+
* wrap bands, and host-paragraph line offsets.
|
|
8
|
+
*/
|
|
9
|
+
const TEXT_BOX_ANCHOR_BLOCK_ID = Symbol.for("stll.textBoxAnchorBlockId");
|
|
10
|
+
const readTextBoxAnchorBlockId = (block) => Reflect.get(block, TEXT_BOX_ANCHOR_BLOCK_ID);
|
|
11
|
+
/**
|
|
12
|
+
* Layout a text box block onto pages.
|
|
13
|
+
*/
|
|
14
|
+
function layoutTextBox(block, measure, { paginator, sectionMarginTop, sectionPageHeight, sectionMarginBottom }) {
|
|
15
|
+
if (isPagePinnedBandTextBox(block)) {
|
|
16
|
+
const state = paginator.getCurrentState();
|
|
17
|
+
const bandTop = bandTopContentY(block.position?.vertical, {
|
|
18
|
+
pageHeight: sectionPageHeight,
|
|
19
|
+
marginTop: sectionMarginTop,
|
|
20
|
+
marginBottom: sectionMarginBottom,
|
|
21
|
+
boxHeight: measure.height
|
|
22
|
+
});
|
|
23
|
+
const x = bandFragmentX(block.position?.horizontal, {
|
|
24
|
+
pageWidth: state.page.size.w,
|
|
25
|
+
marginLeft: state.page.margins.left,
|
|
26
|
+
marginRight: state.page.margins.right,
|
|
27
|
+
activeColumnLeft: paginator.getColumnX(state.columnIndex),
|
|
28
|
+
activeColumnWidth: paginator.columnWidth,
|
|
29
|
+
boxWidth: measure.width
|
|
30
|
+
});
|
|
31
|
+
const fragment = {
|
|
32
|
+
kind: "textBox",
|
|
33
|
+
blockId: block.id,
|
|
34
|
+
x,
|
|
35
|
+
y: block.position?.vertical?.relativeTo === "page" ? sectionMarginTop + bandTop : state.topMargin + bandTop,
|
|
36
|
+
width: measure.width,
|
|
37
|
+
height: measure.height,
|
|
38
|
+
isPositioned: true,
|
|
39
|
+
...block.pmStart !== void 0 ? { pmStart: block.pmStart } : {},
|
|
40
|
+
...block.pmEnd !== void 0 ? { pmEnd: block.pmEnd } : {}
|
|
41
|
+
};
|
|
42
|
+
paginator.addUnflowedFragment(fragment);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (block.position !== void 0) {
|
|
46
|
+
const state = paginator.getCurrentState();
|
|
47
|
+
const x = bandFragmentX(block.position.horizontal, {
|
|
48
|
+
pageWidth: state.page.size.w,
|
|
49
|
+
marginLeft: state.page.margins.left,
|
|
50
|
+
marginRight: state.page.margins.right,
|
|
51
|
+
activeColumnLeft: paginator.getColumnX(state.columnIndex),
|
|
52
|
+
activeColumnWidth: paginator.columnWidth,
|
|
53
|
+
boxWidth: measure.width
|
|
54
|
+
});
|
|
55
|
+
const vertical = block.position.vertical;
|
|
56
|
+
const anchorBlockId = readTextBoxAnchorBlockId(block);
|
|
57
|
+
const anchorParagraph = vertical?.relativeTo === "paragraph" && typeof anchorBlockId === "string" ? state.page.fragments.find((fragment) => fragment.kind === "paragraph" && fragment.blockId === anchorBlockId) : void 0;
|
|
58
|
+
const bandGeometry = {
|
|
59
|
+
pageHeight: sectionPageHeight,
|
|
60
|
+
marginTop: sectionMarginTop,
|
|
61
|
+
marginBottom: sectionMarginBottom,
|
|
62
|
+
boxHeight: measure.height
|
|
63
|
+
};
|
|
64
|
+
let y;
|
|
65
|
+
if (vertical?.relativeTo === "page") y = sectionMarginTop + bandTopContentY(vertical, bandGeometry);
|
|
66
|
+
else if (isPageFrameRelativeAnchor(vertical?.relativeTo)) y = state.topMargin + bandTopContentY(vertical, {
|
|
67
|
+
pageHeight: sectionPageHeight,
|
|
68
|
+
marginTop: sectionMarginTop,
|
|
69
|
+
marginBottom: sectionMarginBottom,
|
|
70
|
+
boxHeight: measure.height
|
|
71
|
+
});
|
|
72
|
+
else y = (anchorParagraph?.y ?? state.cursorY) + emuToPixels(vertical?.posOffset ?? 0);
|
|
73
|
+
const fragment = {
|
|
74
|
+
kind: "textBox",
|
|
75
|
+
blockId: block.id,
|
|
76
|
+
x,
|
|
77
|
+
y,
|
|
78
|
+
width: measure.width,
|
|
79
|
+
height: measure.height,
|
|
80
|
+
isPositioned: true,
|
|
81
|
+
...block.pmStart !== void 0 ? { pmStart: block.pmStart } : {},
|
|
82
|
+
...block.pmEnd !== void 0 ? { pmEnd: block.pmEnd } : {}
|
|
83
|
+
};
|
|
84
|
+
paginator.addUnflowedFragment(fragment);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const effect = block.effectExtent;
|
|
88
|
+
const occupiedHeight = measure.height + (effect?.top ?? 0) + (effect?.bottom ?? 0);
|
|
89
|
+
const occupiedWidth = measure.width + (effect?.left ?? 0) + (effect?.right ?? 0);
|
|
90
|
+
const host = block.hostParagraph;
|
|
91
|
+
const fragment = {
|
|
92
|
+
kind: "textBox",
|
|
93
|
+
blockId: block.id,
|
|
94
|
+
x: 0,
|
|
95
|
+
y: 0,
|
|
96
|
+
width: measure.width,
|
|
97
|
+
height: measure.height,
|
|
98
|
+
...block.pmStart !== void 0 ? { pmStart: block.pmStart } : {},
|
|
99
|
+
...block.pmEnd !== void 0 ? { pmEnd: block.pmEnd } : {}
|
|
100
|
+
};
|
|
101
|
+
const result = paginator.addFragment(fragment, occupiedHeight, host?.spacing?.before ?? 0, host?.spacing?.after ?? 0);
|
|
102
|
+
fragment.x = result.x + hostParagraphLineOffset(host, paginator.columnWidth, occupiedWidth) + (effect?.left ?? 0);
|
|
103
|
+
fragment.y = result.y + (effect?.top ?? 0);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Where a line holding only an object of `width` starts within a column:
|
|
107
|
+
* after the host paragraph's start and first-line indents, then aligned in
|
|
108
|
+
* what remains before its end indent.
|
|
109
|
+
*/
|
|
110
|
+
function hostParagraphLineOffset(host, columnWidth, width) {
|
|
111
|
+
if (!host) return 0;
|
|
112
|
+
const indent = host.indent;
|
|
113
|
+
const start = (indent?.left ?? 0) + (indent?.firstLine ?? 0) - (indent?.hanging ?? 0);
|
|
114
|
+
const slack = Math.max(0, columnWidth - start - (indent?.right ?? 0) - width);
|
|
115
|
+
switch (host.alignment) {
|
|
116
|
+
case "center": return start + slack / 2;
|
|
117
|
+
case "right": return start + slack;
|
|
118
|
+
case "left":
|
|
119
|
+
case "justify":
|
|
120
|
+
case void 0: return start;
|
|
121
|
+
default:
|
|
122
|
+
host.alignment;
|
|
123
|
+
return start;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* A topAndBottom text box whose vertical anchor pins it to the page frame
|
|
128
|
+
* (page/margin/margin-strip) — it floats to a fixed page position rather than
|
|
129
|
+
* flowing in document order. Must agree with the measure pass's band extraction
|
|
130
|
+
* (extractFloatingZones), which uses the same predicate. eigenpal #694.
|
|
131
|
+
*/
|
|
132
|
+
function isPagePinnedBandTextBox(block) {
|
|
133
|
+
if (!floatingTextBoxReservesBand(block)) return false;
|
|
134
|
+
return isPageFrameRelativeAnchor(block.position?.vertical?.relativeTo);
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
export { layoutTextBox };
|
|
@@ -2,7 +2,7 @@ import { OutlineStyleAttr } from "../types/documentEnumValues.js";
|
|
|
2
2
|
import { CssBorderStyle } from "../utils/borderCss.js";
|
|
3
3
|
import { ImagePosition, ImageWrap, InlineWrapper, NumberFormat, OutlineLevel, ParagraphNumberingOverride, PreviewDescriptor, SdtProperties, SdtType, SectionStart, ShapeTextBody, TableCellFormatting, TableWidthType, UnderlineStyle, VerticalAlign } from "@stll/docx-core/model";
|
|
4
4
|
declare namespace types_d_exports {
|
|
5
|
-
export { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, MathRun, Measure, MeasuredLine, NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION, NoteAreaLayout, NoteSeparatorKind, NoteSeparatorRule, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, resolveTableCellPadding, resolveTableWidthPx, stripFlowBlockPmAnchors, tableColumnsArePinned };
|
|
5
|
+
export { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, MathRun, Measure, MeasuredLine, NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION, NoteAreaLayout, NoteSeparatorKind, NoteSeparatorRule, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxGradientFill, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, resolveTableCellPadding, resolveTableWidthPx, stripFlowBlockPmAnchors, tableColumnsArePinned };
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
8
|
* Unique identifier for a block in the document.
|
|
@@ -849,6 +849,21 @@ declare const DEFAULT_TEXTBOX_MARGINS: {
|
|
|
849
849
|
};
|
|
850
850
|
/** Default text box width in pixels when no width is specified */
|
|
851
851
|
declare const DEFAULT_TEXTBOX_WIDTH = 200;
|
|
852
|
+
/**
|
|
853
|
+
* A linear DrawingML gradient fill with its colors resolved (ECMA-376
|
|
854
|
+
* §20.1.8.33, §20.1.8.41). It runs along `angle`, in degrees clockwise from
|
|
855
|
+
* the positive x axis; when `scaled`, that angle is stated in the unit square
|
|
856
|
+
* and stretched with the box.
|
|
857
|
+
*/
|
|
858
|
+
type TextBoxGradientFill = {
|
|
859
|
+
angle: number;
|
|
860
|
+
scaled: boolean;
|
|
861
|
+
/** Stops in ascending order; `offset` runs 0 to 1. */
|
|
862
|
+
stops: {
|
|
863
|
+
offset: number;
|
|
864
|
+
color: string;
|
|
865
|
+
}[];
|
|
866
|
+
};
|
|
852
867
|
/**
|
|
853
868
|
* Text box block — positioned container with block content.
|
|
854
869
|
*/
|
|
@@ -869,6 +884,8 @@ type TextBoxBlock = {
|
|
|
869
884
|
verticalAlign?: ShapeTextBody["anchor"];
|
|
870
885
|
/** Fill/background color */
|
|
871
886
|
fillColor?: string;
|
|
887
|
+
/** Gradient fill, painted when no `fillColor` states a solid one. */
|
|
888
|
+
fillGradient?: TextBoxGradientFill;
|
|
872
889
|
/** Border width in pixels */
|
|
873
890
|
outlineWidth?: number;
|
|
874
891
|
/** Border color */
|
|
@@ -1341,6 +1358,8 @@ type LayoutOptions = {
|
|
|
1341
1358
|
firstPageMargins?: PageMargins;
|
|
1342
1359
|
/** Per-section body margins used on even authored page numbers. */
|
|
1343
1360
|
sectionEvenPageMargins?: (PageMargins | undefined)[];
|
|
1361
|
+
/** Per-section body margins used on the first page of a `w:titlePg` section. */
|
|
1362
|
+
sectionFirstPageMargins?: (PageMargins | undefined)[];
|
|
1344
1363
|
/** Page-number policy for the initial section. */
|
|
1345
1364
|
pageNumbering?: SectionPageNumbering;
|
|
1346
1365
|
/** Body-level final section page size. */
|
|
@@ -1576,4 +1595,4 @@ declare const getTableRowLeadingWidth: (row: TableRow, columnWidths: readonly nu
|
|
|
1576
1595
|
*/
|
|
1577
1596
|
declare function stripFlowBlockPmAnchors(block: FlowBlock): FlowBlock;
|
|
1578
1597
|
//#endregion
|
|
1579
|
-
export { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, MathRun, Measure, MeasuredLine, NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION, NoteAreaLayout, NoteSeparatorKind, NoteSeparatorRule, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, resolveTableCellPadding, resolveTableWidthPx, stripFlowBlockPmAnchors, tableColumnsArePinned, types_d_exports };
|
|
1598
|
+
export { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, MathRun, Measure, MeasuredLine, NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION, NoteAreaLayout, NoteSeparatorKind, NoteSeparatorRule, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxGradientFill, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, resolveTableCellPadding, resolveTableWidthPx, stripFlowBlockPmAnchors, tableColumnsArePinned, types_d_exports };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "../layout-engine/measure/floatingLineClearance.js";
|
|
1
2
|
import { resolveFloatingTableX } from "../layout-engine/measure/floatingTablePosition.js";
|
|
2
3
|
import { rectsToFloatingZones } from "../layout-engine/measure/floatingZones.js";
|
|
3
4
|
import { DEFAULT_FONT_FAMILY } from "../layout-engine/measure/measureHelpers.js";
|