@stll/folio-core 0.6.1 → 0.8.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/blockRange.d.ts +13 -2
- package/dist/ai-edits/blockRange.js +23 -2
- package/dist/ai-edits/index.d.ts +4 -2
- package/dist/ai-edits/index.js +3 -1
- package/dist/ai-edits/scoped-reading.d.ts +9 -0
- package/dist/ai-edits/scoped-reading.js +60 -0
- package/dist/ai-edits/snapshot.js +13 -4
- package/dist/ai-edits/types.d.ts +44 -2
- package/dist/compat/eigenpal.d.ts +5 -2
- package/dist/compat/eigenpal.js +4 -1
- package/dist/controller/fontReadiness.d.ts +29 -0
- package/dist/controller/fontReadiness.js +139 -0
- package/dist/controller/layoutPipeline.js +86 -11
- package/dist/docx/blockContentParser.js +51 -8
- package/dist/docx/capabilities.d.ts +41 -0
- package/dist/docx/capabilities.js +322 -0
- package/dist/docx/compatibility.d.ts +34 -2
- package/dist/docx/compatibility.js +128 -38
- package/dist/docx/conformance.d.ts +6 -0
- package/dist/docx/conformance.js +18 -0
- package/dist/docx/ensureParaIds.d.ts +29 -0
- package/dist/docx/ensureParaIds.js +423 -0
- package/dist/docx/groupDrawingParser.d.ts +1 -1
- package/dist/docx/groupDrawingParser.js +49 -8
- package/dist/docx/numberingParser.js +1 -0
- package/dist/docx/paragraphParser.js +9 -0
- package/dist/docx/paragraphTextBoxEnrichment.js +141 -2
- package/dist/docx/parser.js +2 -0
- package/dist/docx/runParser.js +11 -2
- package/dist/docx/selectiveXmlPatch.d.ts +10 -1
- package/dist/docx/selectiveXmlPatch.js +1 -1
- package/dist/docx/serializer/paragraphSerializer.js +4 -0
- package/dist/docx/serializer/runSerializer.js +7 -2
- package/dist/docx/serializer/tableSerializer.js +4 -0
- package/dist/docx/server/boundedArchive.d.ts +24 -0
- package/dist/docx/server/boundedArchive.js +106 -0
- package/dist/docx/server/extractDocxText.d.ts +23 -0
- package/dist/docx/server/extractDocxText.js +154 -0
- package/dist/docx/tableParser.js +8 -0
- package/dist/docx/textBoxParser.js +6 -1
- package/dist/docx/vmlImageParser.js +145 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -1
- package/dist/layout-bridge/convert/headerFooterLayout.js +16 -2
- package/dist/layout-bridge/convert/paragraphFrames.d.ts +7 -0
- package/dist/layout-bridge/convert/paragraphFrames.js +136 -0
- package/dist/layout-bridge/convert/toFlowBlocks.js +106 -13
- package/dist/layout-bridge/engine/hitTest.js +2 -1
- package/dist/layout-bridge/engine/selectionRects.js +2 -1
- package/dist/layout-engine/headerFooterRefs.d.ts +7 -0
- package/dist/layout-engine/headerFooterRefs.js +40 -0
- package/dist/layout-engine/index.d.ts +3 -2
- package/dist/layout-engine/index.js +125 -72
- package/dist/layout-engine/keep-together.d.ts +7 -5
- package/dist/layout-engine/keep-together.js +20 -4
- package/dist/layout-engine/measure/cache.js +2 -0
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +3 -1
- package/dist/layout-engine/measure/listMarkerWidth.js +23 -4
- package/dist/layout-engine/measure/measureBlocks.d.ts +3 -0
- package/dist/layout-engine/measure/measureBlocks.js +133 -51
- package/dist/layout-engine/measure/measureParagraph.js +42 -15
- package/dist/layout-engine/measure/tableCellGrid.d.ts +15 -0
- package/dist/layout-engine/measure/tableCellGrid.js +62 -0
- package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +18 -0
- package/dist/layout-engine/measure/textBoxParagraphLayout.js +32 -0
- package/dist/layout-engine/paginator.d.ts +2 -1
- package/dist/layout-engine/paginator.js +17 -8
- package/dist/layout-engine/paragraphFrame.d.ts +22 -0
- package/dist/layout-engine/paragraphFrame.js +17 -0
- package/dist/layout-engine/paragraphSequence.d.ts +7 -0
- package/dist/layout-engine/paragraphSequence.js +25 -0
- package/dist/layout-engine/paragraphSpacing.d.ts +17 -0
- package/dist/layout-engine/paragraphSpacing.js +30 -0
- package/dist/layout-engine/renderedBreakReconciliation.d.ts +58 -0
- package/dist/layout-engine/renderedBreakReconciliation.js +63 -0
- package/dist/layout-engine/types.d.ts +31 -10
- package/dist/layout-engine/types.js +15 -2
- package/dist/layout-painter/renderPage.js +2 -2
- package/dist/layout-painter/renderParagraph.js +94 -10
- package/dist/layout-painter/renderTable.js +42 -12
- package/dist/layout-painter/renderTextBox.js +17 -7
- package/dist/managers/DocumentLoaderManager.js +1 -1
- package/dist/paged-layout/sectionBlockWidths.d.ts +3 -0
- package/dist/paged-layout/sectionBlockWidths.js +9 -0
- package/dist/paged-layout/sectionGeometry.js +1 -1
- package/dist/prosemirror/attrs/index.js +11 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +2 -0
- package/dist/prosemirror/conversion/toProseDoc.js +29 -23
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +1 -0
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +68 -16
- package/dist/prosemirror/extensions/nodes/TableExtension.js +1 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -1
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +7 -0
- package/dist/prosemirror/schema/nodes.d.ts +6 -3
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
- package/dist/prosemirror/utils/tabCalculator.js +1 -1
- package/dist/server.d.ts +6 -2
- package/dist/server.js +5 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/utils/createDocument.js +26 -18
- package/dist/utils/formatToStyle.js +3 -3
- package/dist/utils/hexId.d.ts +8 -1
- package/dist/utils/hexId.js +11 -3
- package/dist/utils/units.d.ts +6 -6
- package/dist/utils/units.js +8 -8
- package/package.json +1 -1
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { emuToPixels } from "../utils/units.js";
|
|
2
2
|
import { measuredLineAdvance } from "./lineFlow.js";
|
|
3
|
-
import { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned } from "./types.js";
|
|
3
|
+
import { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned } from "./types.js";
|
|
4
|
+
import { getParagraphSpacingAfter, getParagraphSpacingBefore } from "./paragraphSpacing.js";
|
|
4
5
|
import { calculateChainHeight, computeKeepNextChains, getMidChainIndices, hasKeepLines, hasPageBreakBefore } from "./keep-together.js";
|
|
6
|
+
import { continuesNumberedSequence } from "./paragraphSequence.js";
|
|
5
7
|
import { createPaginator } from "./paginator.js";
|
|
6
8
|
import { getParagraphFragmentPmRange } from "./paragraphFragmentRange.js";
|
|
9
|
+
import { INITIAL_RENDERED_BREAK_STATE, reconcileAfterBlock, reconcileBreakBeforeBlock, recordReflowBoundary } from "./renderedBreakReconciliation.js";
|
|
7
10
|
import { buildTableRowBreakInfo, snapRowBreak } from "./tableRowBreak.js";
|
|
8
11
|
import { bandFragmentX, bandTopContentY, isPageFrameRelativeAnchor } from "./textBoxFlow.js";
|
|
12
|
+
import { resolveSectionHeaderFooterRefs } from "./headerFooterRefs.js";
|
|
9
13
|
import { applyPendingToActive, createInitialSectionState, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, scheduleSectionBreak } from "./section-breaks.js";
|
|
10
14
|
import { assertExhaustiveFlowBlock, findPageIndexContainingPmPos } from "./pmPageIndex.js";
|
|
11
15
|
import { panic } from "better-result";
|
|
@@ -42,53 +46,55 @@ function collectSectionConfigs(blocks, initialConfig, finalConfig) {
|
|
|
42
46
|
breakIndices
|
|
43
47
|
};
|
|
44
48
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
49
|
+
function balancedParagraphSectionHeight({ blocks, measures, startIndex, endIndex, incomingSpacing, columnCount, availableHeight }) {
|
|
50
|
+
if (columnCount <= 1 || startIndex >= endIndex || availableHeight <= 0) return;
|
|
51
|
+
const lineUnits = [];
|
|
52
|
+
let totalHeight = 0;
|
|
53
|
+
let tallestUnit = 0;
|
|
54
|
+
let trailingSpacing = incomingSpacing;
|
|
55
|
+
for (let index = startIndex; index < endIndex; index++) {
|
|
56
|
+
const block = blocks[index];
|
|
57
|
+
const measure = measures[index];
|
|
58
|
+
if (block?.kind !== "paragraph" || measure?.kind !== "paragraph") return;
|
|
59
|
+
if (block.attrs?.keepNext === true || block.attrs?.keepLines === true || block.runs.some((run) => run.kind === "text" && run.footnoteRefId !== void 0)) return;
|
|
60
|
+
const leadingSpacing = Math.max(getParagraphSpacingBefore(block), trailingSpacing);
|
|
61
|
+
if (measure.lines.length === 0) {
|
|
62
|
+
if (leadingSpacing > 0) {
|
|
63
|
+
lineUnits.push(leadingSpacing);
|
|
64
|
+
totalHeight += leadingSpacing;
|
|
65
|
+
tallestUnit = Math.max(tallestUnit, leadingSpacing);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for (let lineIndex = 0; lineIndex < measure.lines.length; lineIndex++) {
|
|
69
|
+
const line = measure.lines[lineIndex];
|
|
70
|
+
if (!line) continue;
|
|
71
|
+
const unitHeight = measuredLineAdvance(line) + (lineIndex === 0 ? leadingSpacing : 0);
|
|
72
|
+
lineUnits.push(unitHeight);
|
|
73
|
+
totalHeight += unitHeight;
|
|
74
|
+
tallestUnit = Math.max(tallestUnit, unitHeight);
|
|
75
|
+
}
|
|
76
|
+
if (tallestUnit > availableHeight || totalHeight > availableHeight * columnCount) return;
|
|
77
|
+
trailingSpacing = getParagraphSpacingAfter(block);
|
|
68
78
|
}
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const value = block.attrs?.spacing?.after ?? 0;
|
|
89
|
-
if (value === 0) return 0;
|
|
90
|
-
if (isEmptyParagraph(block) && !block.attrs?.styleId && !block.attrs?.spacingExplicit?.after) return 0;
|
|
91
|
-
return value;
|
|
79
|
+
if (totalHeight <= 0) return;
|
|
80
|
+
const columnsNeeded = (targetHeight) => {
|
|
81
|
+
let usedHeight = 0;
|
|
82
|
+
let usedColumns = 1;
|
|
83
|
+
for (const unitHeight of lineUnits) if (usedHeight > 0 && usedHeight + unitHeight > targetHeight) {
|
|
84
|
+
usedColumns += 1;
|
|
85
|
+
usedHeight = unitHeight;
|
|
86
|
+
} else usedHeight += unitHeight;
|
|
87
|
+
return usedColumns;
|
|
88
|
+
};
|
|
89
|
+
let lower = Math.max(tallestUnit, totalHeight / columnCount);
|
|
90
|
+
let upper = Math.min(totalHeight, availableHeight);
|
|
91
|
+
if (columnsNeeded(upper) > columnCount) return;
|
|
92
|
+
for (let iteration = 0; iteration < 32; iteration++) {
|
|
93
|
+
const middle = (lower + upper) / 2;
|
|
94
|
+
if (columnsNeeded(middle) <= columnCount) upper = middle;
|
|
95
|
+
else lower = middle;
|
|
96
|
+
}
|
|
97
|
+
return Math.ceil(upper * 1e3) / 1e3;
|
|
92
98
|
}
|
|
93
99
|
function hasWidowControl(block) {
|
|
94
100
|
return block.attrs?.widowControl !== false;
|
|
@@ -123,6 +129,24 @@ function applyContextualSpacing(blocks) {
|
|
|
123
129
|
for (const block of blocks) if (block.kind === "table") for (const row of block.rows) for (const cell of row.cells) applyContextualSpacing(cell.blocks);
|
|
124
130
|
else if (block.kind === "textBox") applyContextualSpacing(block.content);
|
|
125
131
|
}
|
|
132
|
+
/** Suppress automatic inter-item spacing within one numbered sequence. */
|
|
133
|
+
const applyAutomaticListSpacing = (blocks) => {
|
|
134
|
+
for (let index = 1; index < blocks.length; index++) {
|
|
135
|
+
const previous = blocks[index - 1];
|
|
136
|
+
const current = blocks[index];
|
|
137
|
+
if (previous?.kind !== "paragraph" || current?.kind !== "paragraph" || !continuesNumberedSequence(previous, current)) continue;
|
|
138
|
+
if (previous.attrs?.automaticSpacing?.after && previous.attrs.spacing) previous.attrs.spacing = {
|
|
139
|
+
...previous.attrs.spacing,
|
|
140
|
+
after: 0
|
|
141
|
+
};
|
|
142
|
+
if (current.attrs?.automaticSpacing?.before && current.attrs.spacing) current.attrs.spacing = {
|
|
143
|
+
...current.attrs.spacing,
|
|
144
|
+
before: 0
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
for (const block of blocks) if (block.kind === "table") for (const row of block.rows) for (const cell of row.cells) applyAutomaticListSpacing(cell.blocks);
|
|
148
|
+
else if (block.kind === "textBox") applyAutomaticListSpacing(block.content);
|
|
149
|
+
};
|
|
126
150
|
/**
|
|
127
151
|
* Layout a document: convert blocks + measures into pages with positioned fragments.
|
|
128
152
|
*
|
|
@@ -166,11 +190,13 @@ function layoutDocument(blocks, measures, options) {
|
|
|
166
190
|
margins: initialConfig.margins,
|
|
167
191
|
...options.mirrorMargins === true ? { mirrorMargins: true } : {},
|
|
168
192
|
...options.firstPageMargins !== void 0 ? { firstPageMargins: options.firstPageMargins } : {},
|
|
193
|
+
...options.sectionEvenPageMargins !== void 0 ? { sectionEvenPageMargins: options.sectionEvenPageMargins } : {},
|
|
169
194
|
columns: initialConfig.columns ?? DEFAULT_COLUMNS,
|
|
170
195
|
...options.footnoteReservedHeights !== void 0 ? { footnoteReservedHeights: options.footnoteReservedHeights } : {},
|
|
171
196
|
...options.sectionHeaderFooterRefs !== void 0 ? { sectionHeaderFooterRefs: options.sectionHeaderFooterRefs } : {}
|
|
172
197
|
});
|
|
173
198
|
applyContextualSpacing(blocks);
|
|
199
|
+
applyAutomaticListSpacing(blocks);
|
|
174
200
|
const keepNextChains = computeKeepNextChains(blocks);
|
|
175
201
|
const midChainIndices = getMidChainIndices(keepNextChains);
|
|
176
202
|
const blocksById = /* @__PURE__ */ new Map();
|
|
@@ -179,21 +205,28 @@ function layoutDocument(blocks, measures, options) {
|
|
|
179
205
|
let activeSectionMarginTop = initialConfig.margins.top;
|
|
180
206
|
let activeSectionPageHeight = initialConfig.pageSize.h;
|
|
181
207
|
let activeSectionMarginBottom = initialConfig.margins.bottom;
|
|
182
|
-
let
|
|
208
|
+
let renderedBreakState = INITIAL_RENDERED_BREAK_STATE;
|
|
183
209
|
for (let i = 0; i < blocks.length; i++) {
|
|
184
210
|
const block = blocks[i];
|
|
185
211
|
const measure = measures[i];
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
212
|
+
const renderedBreakNeedsSnap = measure.kind === "paragraph" && !paginator.fits(measure.totalHeight);
|
|
213
|
+
const breakDecision = reconcileBreakBeforeBlock({
|
|
214
|
+
state: renderedBreakState,
|
|
215
|
+
block,
|
|
216
|
+
previousBlock: blocks[i - 1],
|
|
217
|
+
page: paginator.getCurrentState().page,
|
|
218
|
+
blocksById,
|
|
219
|
+
hasExplicitPageBreak: hasPageBreakBefore(block),
|
|
220
|
+
renderedBreakNeedsSnap
|
|
221
|
+
});
|
|
222
|
+
if (breakDecision.forcePageBreak) paginator.forcePageBreak();
|
|
223
|
+
renderedBreakState = breakDecision.state;
|
|
193
224
|
const chain = keepNextChains.get(i);
|
|
194
225
|
if (chain && !midChainIndices.has(i)) {
|
|
195
226
|
const chainHeight = calculateChainHeight(chain, blocks, measures);
|
|
227
|
+
const pageBeforeChainLayout = paginator.getCurrentState().page.number;
|
|
196
228
|
paginator.ensureFits(chainHeight);
|
|
229
|
+
if (paginator.getCurrentState().page.number > pageBeforeChainLayout) renderedBreakState = recordReflowBoundary(renderedBreakState, true);
|
|
197
230
|
}
|
|
198
231
|
const pageBeforeBlockLayout = paginator.getCurrentState().page.number;
|
|
199
232
|
switch (block.kind) {
|
|
@@ -224,6 +257,23 @@ function layoutDocument(blocks, measures, options) {
|
|
|
224
257
|
case "sectionBreak": {
|
|
225
258
|
const nextSectionConfig = sectionConfigs[sectionIdx + 1] ?? initialConfig;
|
|
226
259
|
handleSectionBreak(block, paginator, nextSectionConfig, sectionBreakTypes[sectionIdx + 1] ?? sectionBreakTypes[sectionIdx], sectionIdx + 1);
|
|
260
|
+
const nextColumns = nextSectionConfig.columns;
|
|
261
|
+
const nextBreakIndex = breakIndices[sectionIdx + 1] ?? blocks.length;
|
|
262
|
+
const nextBreak = blocks[nextBreakIndex];
|
|
263
|
+
const sectionEndsContinuously = nextBreakIndex === blocks.length || nextBreak?.kind === "sectionBreak" && nextBreak.type === "continuous";
|
|
264
|
+
if (nextColumns && sectionEndsContinuously) {
|
|
265
|
+
const state = paginator.getCurrentState();
|
|
266
|
+
const balancedHeight = balancedParagraphSectionHeight({
|
|
267
|
+
blocks,
|
|
268
|
+
measures,
|
|
269
|
+
startIndex: i + 1,
|
|
270
|
+
endIndex: nextBreakIndex,
|
|
271
|
+
incomingSpacing: state.trailingSpacing,
|
|
272
|
+
columnCount: nextColumns.count,
|
|
273
|
+
availableHeight: paginator.getAvailableHeight()
|
|
274
|
+
});
|
|
275
|
+
if (balancedHeight !== void 0) state.contentBottom = Math.min(state.contentBottom, state.cursorY + balancedHeight);
|
|
276
|
+
}
|
|
227
277
|
activeSectionMarginTop = nextSectionConfig.margins.top;
|
|
228
278
|
activeSectionPageHeight = nextSectionConfig.pageSize.h;
|
|
229
279
|
activeSectionMarginBottom = nextSectionConfig.margins.bottom;
|
|
@@ -232,9 +282,13 @@ function layoutDocument(blocks, measures, options) {
|
|
|
232
282
|
}
|
|
233
283
|
default: break;
|
|
234
284
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
285
|
+
renderedBreakState = reconcileAfterBlock({
|
|
286
|
+
state: renderedBreakState,
|
|
287
|
+
block,
|
|
288
|
+
pageNumberBefore: pageBeforeBlockLayout,
|
|
289
|
+
pageNumberAfter: paginator.getCurrentState().page.number,
|
|
290
|
+
previousPage: paginator.pages[pageBeforeBlockLayout - 1]
|
|
291
|
+
});
|
|
238
292
|
}
|
|
239
293
|
if (paginator.pages.length === 0) paginator.getCurrentState();
|
|
240
294
|
return {
|
|
@@ -290,8 +344,8 @@ function getLineFootnoteRefs(block, fromRun, toRun, fnHeights) {
|
|
|
290
344
|
function layoutParagraph(block, measure, paginator, contentWidth, footnoteHeightById) {
|
|
291
345
|
const lines = measure.lines;
|
|
292
346
|
if (lines.length === 0) {
|
|
293
|
-
const spaceBefore =
|
|
294
|
-
const spaceAfter =
|
|
347
|
+
const spaceBefore = getParagraphSpacingBefore(block);
|
|
348
|
+
const spaceAfter = getParagraphSpacingAfter(block);
|
|
295
349
|
const state = paginator.getCurrentState();
|
|
296
350
|
const fragment = {
|
|
297
351
|
kind: "paragraph",
|
|
@@ -309,8 +363,8 @@ function layoutParagraph(block, measure, paginator, contentWidth, footnoteHeight
|
|
|
309
363
|
paginator.addFragment(fragment, 0, spaceBefore, spaceAfter);
|
|
310
364
|
return;
|
|
311
365
|
}
|
|
312
|
-
const spaceBefore =
|
|
313
|
-
const spaceAfter =
|
|
366
|
+
const spaceBefore = getParagraphSpacingBefore(block);
|
|
367
|
+
const spaceAfter = getParagraphSpacingAfter(block);
|
|
314
368
|
let currentLineIndex = 0;
|
|
315
369
|
while (currentLineIndex < lines.length) {
|
|
316
370
|
const state = paginator.getCurrentState();
|
|
@@ -429,14 +483,14 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
|
|
|
429
483
|
let x = paginator.getColumnX(columnIndex);
|
|
430
484
|
if (block.justification === "center") x += (paginator.columnWidth - measure.totalWidth) / 2;
|
|
431
485
|
else if (block.justification === "right") x = x + paginator.columnWidth - measure.totalWidth;
|
|
486
|
+
else if (block.indent !== void 0) x += block.indent;
|
|
432
487
|
else {
|
|
433
488
|
const leadingCellMargin = block.rows.at(0)?.cells.at(0)?.padding?.left ?? 0;
|
|
434
|
-
x
|
|
489
|
+
x -= leadingCellMargin;
|
|
435
490
|
}
|
|
436
491
|
return x;
|
|
437
492
|
};
|
|
438
493
|
const getCurrentRowCapacity = (state = paginator.getCurrentState()) => state.rawContentBottom - state.topMargin;
|
|
439
|
-
const getCurrentAvailableHeight = (state = paginator.getCurrentState()) => state.contentBottom - state.cursorY;
|
|
440
494
|
const hasAdjacentPriorTableRows = (rowIndex, state = paginator.getCurrentState()) => {
|
|
441
495
|
const previous = state.page.fragments.at(-1);
|
|
442
496
|
return previous?.kind === "table" && previous.blockId === block.id && previous.toRow === rowIndex && previous.y + previous.height === state.cursorY && previous.x === computeTableX(state.columnIndex);
|
|
@@ -445,11 +499,9 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
|
|
|
445
499
|
const canSplitRow = (rowIndex, state = paginator.getCurrentState()) => {
|
|
446
500
|
const row = rows[rowIndex];
|
|
447
501
|
if (!row) return false;
|
|
448
|
-
const repeatedHeaderOverhead = shouldRepeatHeaderRows(rowIndex, 0, state) ? headerRowsHeight : 0;
|
|
449
502
|
if ((breakInfo.breakOffsets[rowIndex]?.length ?? 0) <= 1) return false;
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
return hasAdjacentPriorTableRows(rowIndex, state) && requiredHeight > getCurrentAvailableHeight(state) - state.trailingSpacing;
|
|
503
|
+
const freshHeaderOverhead = headerRowCount > 0 && rowIndex >= headerRowCount ? headerRowsHeight : 0;
|
|
504
|
+
return row.height + freshHeaderOverhead > getCurrentRowCapacity(state);
|
|
453
505
|
};
|
|
454
506
|
while (currentRowIndex < rows.length) {
|
|
455
507
|
const oversizedRow = rows[currentRowIndex];
|
|
@@ -638,9 +690,10 @@ function layoutFloatingTable(block, measure, paginator, contentWidth) {
|
|
|
638
690
|
else if (spec === "center") y = baseY + (contentHeight - tableHeight) / 2;
|
|
639
691
|
}
|
|
640
692
|
if (!usedExplicitY) y = paginator.ensureFits(tableHeight).cursorY;
|
|
641
|
-
const
|
|
642
|
-
const
|
|
643
|
-
const
|
|
693
|
+
const usesNumericOffset = floating?.tblpX !== void 0;
|
|
694
|
+
const clampToPage = floating?.horzAnchor === "page" || usesNumericOffset;
|
|
695
|
+
const minX = clampToPage ? 0 : margins.left;
|
|
696
|
+
const maxX = clampToPage ? page.size.w - tableWidth : margins.left + contentWidth - tableWidth;
|
|
644
697
|
if (Number.isFinite(maxX)) x = Math.max(minX, Math.min(x, maxX));
|
|
645
698
|
const fragment = {
|
|
646
699
|
kind: "table",
|
|
@@ -830,4 +883,4 @@ function handleSectionBreak(_block, paginator, nextSectionConfig, nextSectionTyp
|
|
|
830
883
|
paginator.updateColumns(nextSectionConfig.columns ?? DEFAULT_COLUMNS);
|
|
831
884
|
}
|
|
832
885
|
//#endregion
|
|
833
|
-
export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, layoutDocument, scheduleSectionBreak, tableColumnsArePinned };
|
|
886
|
+
export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, scheduleSectionBreak, tableColumnsArePinned };
|
|
@@ -2,8 +2,9 @@ import { FlowBlock, Measure } from "./types.js";
|
|
|
2
2
|
|
|
3
3
|
//#region src/layout-engine/keep-together.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* A chain of
|
|
6
|
-
*
|
|
5
|
+
* A chain of paragraphs that Word keeps with following content. This includes
|
|
6
|
+
* explicit keepNext links and a trailing table separator that would otherwise
|
|
7
|
+
* be stranded at the bottom of a page.
|
|
7
8
|
*/
|
|
8
9
|
type KeepNextChain = {
|
|
9
10
|
/** Index of the first paragraph in the chain. */startIndex: number; /** Index of the last keepNext or pass-through empty member. */
|
|
@@ -14,9 +15,10 @@ type KeepNextChain = {
|
|
|
14
15
|
/**
|
|
15
16
|
* Pre-scan blocks to find all keepNext chains.
|
|
16
17
|
*
|
|
17
|
-
* A
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* A chain starts with a paragraph whose keepNext=true or with an empty
|
|
19
|
+
* separator immediately following a table. It continues through further
|
|
20
|
+
* keepNext paragraphs and structural empty separators. The first visible
|
|
21
|
+
* non-keepNext paragraph is its anchor.
|
|
20
22
|
*
|
|
21
23
|
* Returns a map from chain start index to chain info.
|
|
22
24
|
*/
|
|
@@ -5,12 +5,23 @@ function isVisuallyEmptyParagraph(block) {
|
|
|
5
5
|
const run = block.runs.at(0);
|
|
6
6
|
return run?.kind === "text" && run.text === "";
|
|
7
7
|
}
|
|
8
|
+
function startsTrailingTableSeparatorChain(blocks, index) {
|
|
9
|
+
const block = blocks[index];
|
|
10
|
+
if (block?.kind !== "paragraph" || !isVisuallyEmptyParagraph(block) || blocks[index - 1]?.kind !== "table") return false;
|
|
11
|
+
for (let nextIndex = index + 1; nextIndex < blocks.length; nextIndex++) {
|
|
12
|
+
const nextBlock = blocks[nextIndex];
|
|
13
|
+
if (nextBlock?.kind !== "paragraph") return false;
|
|
14
|
+
if (!isVisuallyEmptyParagraph(nextBlock)) return true;
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
8
18
|
/**
|
|
9
19
|
* Pre-scan blocks to find all keepNext chains.
|
|
10
20
|
*
|
|
11
|
-
* A
|
|
12
|
-
*
|
|
13
|
-
*
|
|
21
|
+
* A chain starts with a paragraph whose keepNext=true or with an empty
|
|
22
|
+
* separator immediately following a table. It continues through further
|
|
23
|
+
* keepNext paragraphs and structural empty separators. The first visible
|
|
24
|
+
* non-keepNext paragraph is its anchor.
|
|
14
25
|
*
|
|
15
26
|
* Returns a map from chain start index to chain info.
|
|
16
27
|
*/
|
|
@@ -21,7 +32,7 @@ function computeKeepNextChains(blocks) {
|
|
|
21
32
|
if (processed.has(i)) continue;
|
|
22
33
|
const block = blocks[i];
|
|
23
34
|
if (block.kind !== "paragraph") continue;
|
|
24
|
-
if (!block.attrs?.keepNext) continue;
|
|
35
|
+
if (!block.attrs?.keepNext && !startsTrailingTableSeparatorChain(blocks, i)) continue;
|
|
25
36
|
const memberIndices = [i];
|
|
26
37
|
let endIndex = i;
|
|
27
38
|
for (let j = i + 1; j < blocks.length; j++) {
|
|
@@ -67,6 +78,7 @@ function calculateChainHeight(chain, blocks, measures) {
|
|
|
67
78
|
if (firstBlock?.kind !== "paragraph" || firstMeasure?.kind !== "paragraph") return 0;
|
|
68
79
|
let totalHeight = (firstBlock.attrs?.spacing?.before ?? 0) + firstMeasure.totalHeight;
|
|
69
80
|
let trailingSpacing = firstBlock.attrs?.spacing?.after ?? 0;
|
|
81
|
+
const startsWithTrailingTableSeparator = blocks[firstMemberIndex - 1]?.kind === "table" && isVisuallyEmptyParagraph(firstBlock) && !firstBlock.attrs?.keepNext;
|
|
70
82
|
const successorIndices = [...chain.memberIndices.slice(1)];
|
|
71
83
|
if (chain.anchorIndex !== -1) successorIndices.push(chain.anchorIndex);
|
|
72
84
|
for (let index = 0; index < successorIndices.length; index++) {
|
|
@@ -80,6 +92,10 @@ function calculateChainHeight(chain, blocks, measures) {
|
|
|
80
92
|
if (!firstLine) return totalHeight;
|
|
81
93
|
const isAnchor = index === successorIndices.length - 1 && chain.anchorIndex !== -1;
|
|
82
94
|
const isSplittable = successorMeasure.lines.length > 1 && !successorBlock.attrs?.keepLines;
|
|
95
|
+
if (isAnchor && startsWithTrailingTableSeparator && successorBlock.attrs?.widowControl !== false && successorMeasure.lines.length > 1) {
|
|
96
|
+
const secondLine = successorMeasure.lines.at(1);
|
|
97
|
+
return totalHeight + firstLine.lineHeight + (secondLine?.lineHeight ?? 0);
|
|
98
|
+
}
|
|
83
99
|
if (isAnchor || isSplittable) return totalHeight + firstLine.lineHeight;
|
|
84
100
|
totalHeight += successorMeasure.totalHeight;
|
|
85
101
|
trailingSpacing = successorBlock.attrs?.spacing?.after ?? 0;
|
|
@@ -160,11 +160,13 @@ function hashParagraphBlock(block) {
|
|
|
160
160
|
const attrs = block.attrs;
|
|
161
161
|
if (attrs) {
|
|
162
162
|
if (attrs.alignment) parts.push(`align:${attrs.alignment}`);
|
|
163
|
+
if (attrs.outlineLevel !== void 0) parts.push(`outline:${attrs.outlineLevel}`);
|
|
163
164
|
if (attrs.indent) parts.push(`indent:${attrs.indent.left}|${attrs.indent.right}|${attrs.indent.firstLine}|${attrs.indent.hanging}`);
|
|
164
165
|
if (attrs.spacing) parts.push(`spacing:${attrs.spacing.before}|${attrs.spacing.after}|${attrs.spacing.line}|${attrs.spacing.lineRule}`);
|
|
165
166
|
if (attrs.defaultFontSize != null) parts.push(`dfs:${attrs.defaultFontSize}`);
|
|
166
167
|
if (attrs.defaultFontFamily != null) parts.push(`dff:${attrs.defaultFontFamily}`);
|
|
167
168
|
if (attrs.suppressEmptyParagraphHeight) parts.push("sup");
|
|
169
|
+
if (attrs.reserveEmptyOutlineHeight) parts.push("outline-empty-reserve");
|
|
168
170
|
const borders = attrs.borders;
|
|
169
171
|
if (borders) {
|
|
170
172
|
const signature = (border) => border ? `${border.width ?? ""},${border.style ?? ""},${border.color ?? ""}` : "";
|
|
@@ -36,5 +36,7 @@ declare function resolveListMarkerFont(block: ParagraphBlock): {
|
|
|
36
36
|
* grid is not erased by custom tabs, just augmented).
|
|
37
37
|
*/
|
|
38
38
|
declare function getListMarkerInlineWidth(block: ParagraphBlock): number;
|
|
39
|
+
/** Paint-only offset that places the marker around its authored list anchor. */
|
|
40
|
+
declare function getListMarkerVisualOffset(block: ParagraphBlock): number;
|
|
39
41
|
//#endregion
|
|
40
|
-
export { DEFAULT_TAB_STOP_TWIPS, getListMarkerInlineWidth, resolveListMarkerFont };
|
|
42
|
+
export { DEFAULT_TAB_STOP_TWIPS, getListMarkerInlineWidth, getListMarkerVisualOffset, resolveListMarkerFont };
|
|
@@ -51,15 +51,16 @@ function getListMarkerInlineWidth(block) {
|
|
|
51
51
|
fontSize
|
|
52
52
|
};
|
|
53
53
|
const naturalWidth = measureTextWidth(attrs.listMarker, style);
|
|
54
|
+
const markerEndOffset = getMarkerEndOffset(naturalWidth, attrs.listMarkerAlignment);
|
|
54
55
|
const suffix = attrs.listMarkerSuffix ?? "tab";
|
|
55
|
-
if (suffix === "nothing") return
|
|
56
|
-
if (suffix === "space") return
|
|
56
|
+
if (suffix === "nothing") return markerEndOffset;
|
|
57
|
+
if (suffix === "space") return markerEndOffset + measureTextWidth(" ", style);
|
|
57
58
|
const indent = attrs.indent;
|
|
58
59
|
const indentLeft = indent?.left ?? 0;
|
|
59
60
|
const firstLine = indent?.firstLine ?? 0;
|
|
60
61
|
const hanging = indent?.hanging ?? 0;
|
|
61
62
|
const markerStartPx = hanging > 0 ? indentLeft - hanging : indentLeft + firstLine;
|
|
62
|
-
const minBodyStart = markerStartPx +
|
|
63
|
+
const minBodyStart = markerStartPx + markerEndOffset;
|
|
63
64
|
const customTabs = (attrs.tabs ?? []).filter((t) => t.val !== "clear" && t.val !== "bar").map((t) => t.pos * TWIPS_TO_PX);
|
|
64
65
|
if (hanging > 0) customTabs.push(indentLeft);
|
|
65
66
|
const searchStart = hanging > 0 ? Math.max(minBodyStart, indentLeft) : minBodyStart;
|
|
@@ -72,5 +73,23 @@ function getListMarkerInlineWidth(block) {
|
|
|
72
73
|
if (bodyStart === void 0) return naturalWidth + ptToPx(fontSize) * .5;
|
|
73
74
|
return bodyStart - markerStartPx;
|
|
74
75
|
}
|
|
76
|
+
/** Paint-only offset that places the marker around its authored list anchor. */
|
|
77
|
+
function getListMarkerVisualOffset(block) {
|
|
78
|
+
const attrs = block.attrs;
|
|
79
|
+
if (!attrs?.listMarker || attrs.listMarkerHidden) return 0;
|
|
80
|
+
const { fontFamily, fontSize } = resolveListMarkerFont(block);
|
|
81
|
+
const naturalWidth = measureTextWidth(attrs.listMarker, {
|
|
82
|
+
fontFamily,
|
|
83
|
+
fontSize
|
|
84
|
+
});
|
|
85
|
+
if (attrs.listMarkerAlignment === "right") return -naturalWidth;
|
|
86
|
+
if (attrs.listMarkerAlignment === "center") return -naturalWidth / 2;
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
const getMarkerEndOffset = (naturalWidth, alignment) => {
|
|
90
|
+
if (alignment === "right") return 0;
|
|
91
|
+
if (alignment === "center") return naturalWidth / 2;
|
|
92
|
+
return naturalWidth;
|
|
93
|
+
};
|
|
75
94
|
//#endregion
|
|
76
|
-
export { DEFAULT_TAB_STOP_TWIPS, getListMarkerInlineWidth, resolveListMarkerFont };
|
|
95
|
+
export { DEFAULT_TAB_STOP_TWIPS, getListMarkerInlineWidth, getListMarkerVisualOffset, resolveListMarkerFont };
|
|
@@ -4,7 +4,10 @@ import { FloatingImageZone } from "./floatingZones.js";
|
|
|
4
4
|
declare function measureTableCellBlockVisualHeight(block: FlowBlock, blockMeasure: Measure): number;
|
|
5
5
|
declare function measureTableBlock(tableBlock: TableBlock, contentWidth: number, fieldValues?: ReadonlyMap<number, string>): TableMeasure;
|
|
6
6
|
type BandPageGeometry = {
|
|
7
|
+
pageWidth?: number | number[];
|
|
7
8
|
pageHeight: number | number[];
|
|
9
|
+
marginLeft?: number | number[];
|
|
10
|
+
marginRight?: number | number[];
|
|
8
11
|
marginBottom: number | number[];
|
|
9
12
|
};
|
|
10
13
|
/**
|