@stll/folio-core 0.7.0 → 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 +76 -9
- 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/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/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/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 +42 -3
- 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 +36 -82
- 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 +13 -4
- 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 +7 -6
- 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 +17 -8
- package/dist/layout-engine/types.js +15 -2
- package/dist/layout-painter/renderPage.js +2 -2
- package/dist/layout-painter/renderParagraph.js +6 -5
- 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 +27 -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/TextBoxExtension.d.ts +2 -1
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +7 -0
- package/dist/prosemirror/schema/nodes.d.ts +4 -2
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
- package/dist/server.d.ts +4 -2
- package/dist/server.js +3 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/utils/createDocument.js +26 -18
- package/dist/utils/hexId.d.ts +8 -1
- package/dist/utils/hexId.js +11 -3
- 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,63 +46,6 @@ function collectSectionConfigs(blocks, initialConfig, finalConfig) {
|
|
|
42
46
|
breakIndices
|
|
43
47
|
};
|
|
44
48
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Whether a paragraph block has no visible content (no runs, or a single
|
|
47
|
-
* empty text run).
|
|
48
|
-
*/
|
|
49
|
-
function isEmptyParagraph(block) {
|
|
50
|
-
if (block.runs.length === 0) return true;
|
|
51
|
-
if (block.runs.length !== 1) return false;
|
|
52
|
-
const r = block.runs[0];
|
|
53
|
-
return r?.kind === "text" && r.text === "";
|
|
54
|
-
}
|
|
55
|
-
function isPaginationEmptyParagraph(block) {
|
|
56
|
-
return block.runs.every((run) => {
|
|
57
|
-
if (run.kind === "text") return run.text.trim().length === 0;
|
|
58
|
-
return run.kind === "tab" || run.kind === "lineBreak";
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
function pageHasVisibleBodyContent(page, blocksById) {
|
|
62
|
-
for (const fragment of page.fragments) {
|
|
63
|
-
if (fragment.kind !== "paragraph") return true;
|
|
64
|
-
const block = blocksById.get(String(fragment.blockId));
|
|
65
|
-
if (block?.kind !== "paragraph" || !isPaginationEmptyParagraph(block)) return true;
|
|
66
|
-
}
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
function continuesNumberedSequence(previous, current) {
|
|
70
|
-
if (previous?.kind !== "paragraph" || current.kind !== "paragraph") return false;
|
|
71
|
-
const previousNumPr = previous.attrs?.numPr;
|
|
72
|
-
const currentNumPr = current.attrs?.numPr;
|
|
73
|
-
if (previousNumPr?.numId === void 0 || currentNumPr?.numId === void 0) return false;
|
|
74
|
-
return previousNumPr.numId === currentNumPr.numId && previousNumPr.ilvl === currentNumPr.ilvl;
|
|
75
|
-
}
|
|
76
|
-
function pageStartsWithPreviousParagraphContinuation(page, previousBlock) {
|
|
77
|
-
if (previousBlock?.kind !== "paragraph") return false;
|
|
78
|
-
return page.fragments.some((fragment) => fragment.kind === "paragraph" && String(fragment.blockId) === String(previousBlock.id) && fragment.continuesFromPrev === true);
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Get spacing before a paragraph block. Empty paragraphs whose
|
|
82
|
-
* `before` came only from the implicit default paragraph style collapse to
|
|
83
|
-
* zero. Reference layout keeps inherited spacing when the empty paragraph itself is
|
|
84
|
-
* authored through direct `w:pPr` formatting or an explicit `w:pStyle`.
|
|
85
|
-
*/
|
|
86
|
-
function getSpacingBefore(block) {
|
|
87
|
-
const value = block.attrs?.spacing?.before ?? 0;
|
|
88
|
-
if (value === 0) return 0;
|
|
89
|
-
if (isEmptyParagraph(block) && !block.attrs?.styleId && !block.attrs?.hasDirectParagraphFormatting && !block.attrs?.spacingExplicit?.before) return 0;
|
|
90
|
-
return value;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Get spacing after a paragraph block. Same implicit-default-style collapse
|
|
94
|
-
* rule as `getSpacingBefore`.
|
|
95
|
-
*/
|
|
96
|
-
function getSpacingAfter(block) {
|
|
97
|
-
const value = block.attrs?.spacing?.after ?? 0;
|
|
98
|
-
if (value === 0) return 0;
|
|
99
|
-
if (isEmptyParagraph(block) && !block.attrs?.styleId && !block.attrs?.hasDirectParagraphFormatting && !block.attrs?.spacingExplicit?.after) return 0;
|
|
100
|
-
return value;
|
|
101
|
-
}
|
|
102
49
|
function balancedParagraphSectionHeight({ blocks, measures, startIndex, endIndex, incomingSpacing, columnCount, availableHeight }) {
|
|
103
50
|
if (columnCount <= 1 || startIndex >= endIndex || availableHeight <= 0) return;
|
|
104
51
|
const lineUnits = [];
|
|
@@ -110,7 +57,7 @@ function balancedParagraphSectionHeight({ blocks, measures, startIndex, endIndex
|
|
|
110
57
|
const measure = measures[index];
|
|
111
58
|
if (block?.kind !== "paragraph" || measure?.kind !== "paragraph") return;
|
|
112
59
|
if (block.attrs?.keepNext === true || block.attrs?.keepLines === true || block.runs.some((run) => run.kind === "text" && run.footnoteRefId !== void 0)) return;
|
|
113
|
-
const leadingSpacing = Math.max(
|
|
60
|
+
const leadingSpacing = Math.max(getParagraphSpacingBefore(block), trailingSpacing);
|
|
114
61
|
if (measure.lines.length === 0) {
|
|
115
62
|
if (leadingSpacing > 0) {
|
|
116
63
|
lineUnits.push(leadingSpacing);
|
|
@@ -127,7 +74,7 @@ function balancedParagraphSectionHeight({ blocks, measures, startIndex, endIndex
|
|
|
127
74
|
tallestUnit = Math.max(tallestUnit, unitHeight);
|
|
128
75
|
}
|
|
129
76
|
if (tallestUnit > availableHeight || totalHeight > availableHeight * columnCount) return;
|
|
130
|
-
trailingSpacing =
|
|
77
|
+
trailingSpacing = getParagraphSpacingAfter(block);
|
|
131
78
|
}
|
|
132
79
|
if (totalHeight <= 0) return;
|
|
133
80
|
const columnsNeeded = (targetHeight) => {
|
|
@@ -243,6 +190,7 @@ function layoutDocument(blocks, measures, options) {
|
|
|
243
190
|
margins: initialConfig.margins,
|
|
244
191
|
...options.mirrorMargins === true ? { mirrorMargins: true } : {},
|
|
245
192
|
...options.firstPageMargins !== void 0 ? { firstPageMargins: options.firstPageMargins } : {},
|
|
193
|
+
...options.sectionEvenPageMargins !== void 0 ? { sectionEvenPageMargins: options.sectionEvenPageMargins } : {},
|
|
246
194
|
columns: initialConfig.columns ?? DEFAULT_COLUMNS,
|
|
247
195
|
...options.footnoteReservedHeights !== void 0 ? { footnoteReservedHeights: options.footnoteReservedHeights } : {},
|
|
248
196
|
...options.sectionHeaderFooterRefs !== void 0 ? { sectionHeaderFooterRefs: options.sectionHeaderFooterRefs } : {}
|
|
@@ -257,23 +205,28 @@ function layoutDocument(blocks, measures, options) {
|
|
|
257
205
|
let activeSectionMarginTop = initialConfig.margins.top;
|
|
258
206
|
let activeSectionPageHeight = initialConfig.pageSize.h;
|
|
259
207
|
let activeSectionMarginBottom = initialConfig.margins.bottom;
|
|
260
|
-
let
|
|
208
|
+
let renderedBreakState = INITIAL_RENDERED_BREAK_STATE;
|
|
261
209
|
for (let i = 0; i < blocks.length; i++) {
|
|
262
210
|
const block = blocks[i];
|
|
263
211
|
const measure = measures[i];
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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;
|
|
271
224
|
const chain = keepNextChains.get(i);
|
|
272
225
|
if (chain && !midChainIndices.has(i)) {
|
|
273
226
|
const chainHeight = calculateChainHeight(chain, blocks, measures);
|
|
274
227
|
const pageBeforeChainLayout = paginator.getCurrentState().page.number;
|
|
275
228
|
paginator.ensureFits(chainHeight);
|
|
276
|
-
if (paginator.getCurrentState().page.number > pageBeforeChainLayout)
|
|
229
|
+
if (paginator.getCurrentState().page.number > pageBeforeChainLayout) renderedBreakState = recordReflowBoundary(renderedBreakState, true);
|
|
277
230
|
}
|
|
278
231
|
const pageBeforeBlockLayout = paginator.getCurrentState().page.number;
|
|
279
232
|
switch (block.kind) {
|
|
@@ -329,9 +282,13 @@ function layoutDocument(blocks, measures, options) {
|
|
|
329
282
|
}
|
|
330
283
|
default: break;
|
|
331
284
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
285
|
+
renderedBreakState = reconcileAfterBlock({
|
|
286
|
+
state: renderedBreakState,
|
|
287
|
+
block,
|
|
288
|
+
pageNumberBefore: pageBeforeBlockLayout,
|
|
289
|
+
pageNumberAfter: paginator.getCurrentState().page.number,
|
|
290
|
+
previousPage: paginator.pages[pageBeforeBlockLayout - 1]
|
|
291
|
+
});
|
|
335
292
|
}
|
|
336
293
|
if (paginator.pages.length === 0) paginator.getCurrentState();
|
|
337
294
|
return {
|
|
@@ -387,8 +344,8 @@ function getLineFootnoteRefs(block, fromRun, toRun, fnHeights) {
|
|
|
387
344
|
function layoutParagraph(block, measure, paginator, contentWidth, footnoteHeightById) {
|
|
388
345
|
const lines = measure.lines;
|
|
389
346
|
if (lines.length === 0) {
|
|
390
|
-
const spaceBefore =
|
|
391
|
-
const spaceAfter =
|
|
347
|
+
const spaceBefore = getParagraphSpacingBefore(block);
|
|
348
|
+
const spaceAfter = getParagraphSpacingAfter(block);
|
|
392
349
|
const state = paginator.getCurrentState();
|
|
393
350
|
const fragment = {
|
|
394
351
|
kind: "paragraph",
|
|
@@ -406,8 +363,8 @@ function layoutParagraph(block, measure, paginator, contentWidth, footnoteHeight
|
|
|
406
363
|
paginator.addFragment(fragment, 0, spaceBefore, spaceAfter);
|
|
407
364
|
return;
|
|
408
365
|
}
|
|
409
|
-
const spaceBefore =
|
|
410
|
-
const spaceAfter =
|
|
366
|
+
const spaceBefore = getParagraphSpacingBefore(block);
|
|
367
|
+
const spaceAfter = getParagraphSpacingAfter(block);
|
|
411
368
|
let currentLineIndex = 0;
|
|
412
369
|
while (currentLineIndex < lines.length) {
|
|
413
370
|
const state = paginator.getCurrentState();
|
|
@@ -534,7 +491,6 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
|
|
|
534
491
|
return x;
|
|
535
492
|
};
|
|
536
493
|
const getCurrentRowCapacity = (state = paginator.getCurrentState()) => state.rawContentBottom - state.topMargin;
|
|
537
|
-
const getCurrentAvailableHeight = (state = paginator.getCurrentState()) => state.contentBottom - state.cursorY;
|
|
538
494
|
const hasAdjacentPriorTableRows = (rowIndex, state = paginator.getCurrentState()) => {
|
|
539
495
|
const previous = state.page.fragments.at(-1);
|
|
540
496
|
return previous?.kind === "table" && previous.blockId === block.id && previous.toRow === rowIndex && previous.y + previous.height === state.cursorY && previous.x === computeTableX(state.columnIndex);
|
|
@@ -543,11 +499,9 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
|
|
|
543
499
|
const canSplitRow = (rowIndex, state = paginator.getCurrentState()) => {
|
|
544
500
|
const row = rows[rowIndex];
|
|
545
501
|
if (!row) return false;
|
|
546
|
-
const repeatedHeaderOverhead = shouldRepeatHeaderRows(rowIndex, 0, state) ? headerRowsHeight : 0;
|
|
547
502
|
if ((breakInfo.breakOffsets[rowIndex]?.length ?? 0) <= 1) return false;
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
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);
|
|
551
505
|
};
|
|
552
506
|
while (currentRowIndex < rows.length) {
|
|
553
507
|
const oversizedRow = rows[currentRowIndex];
|
|
@@ -929,4 +883,4 @@ function handleSectionBreak(_block, paginator, nextSectionConfig, nextSectionTyp
|
|
|
929
883
|
paginator.updateColumns(nextSectionConfig.columns ?? DEFAULT_COLUMNS);
|
|
930
884
|
}
|
|
931
885
|
//#endregion
|
|
932
|
-
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 };
|
|
@@ -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
|
/**
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, isFloatingTextBoxBlock, tableColumnsArePinned } from "../types.js";
|
|
1
|
+
import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingTextBoxBlock, tableColumnsArePinned } from "../types.js";
|
|
2
2
|
import { findClearLineY, measureParagraph } from "./measureParagraph.js";
|
|
3
3
|
import { getCachedParagraphMeasure, setCachedParagraphMeasure } from "./cache.js";
|
|
4
|
+
import { layoutTextBoxParagraphs } from "./textBoxParagraphLayout.js";
|
|
4
5
|
import { getTextBoxGroupId } from "../textBoxGroup.js";
|
|
6
|
+
import { isParagraphFrameTextBox } from "../paragraphFrame.js";
|
|
5
7
|
import { recordMeasureBlock, recordMeasureBlockError } from "../layoutInstrumentation.js";
|
|
6
|
-
import { bandTopContentY, isPageFrameRelativeAnchor } from "../textBoxFlow.js";
|
|
8
|
+
import { bandFragmentX, bandTopContentY, isPageFrameRelativeAnchor } from "../textBoxFlow.js";
|
|
9
|
+
import { buildTableCellGrid, getFirstAvailableColumn, getTableCellVerticalBorderHeight } from "./tableCellGrid.js";
|
|
7
10
|
//#region src/layout-engine/measure/measureBlocks.ts
|
|
8
11
|
/**
|
|
9
12
|
* Pseudo-infinite measurement width (px) used for `w:noWrap` table cells so
|
|
@@ -25,7 +28,7 @@ const NO_WRAP_MEASURE_WIDTH = 1e6;
|
|
|
25
28
|
* around a background letterhead or a foreground overlay (Codex
|
|
26
29
|
* PR #258 review).
|
|
27
30
|
*/
|
|
28
|
-
function
|
|
31
|
+
function isSideWrappingImageRun(run) {
|
|
29
32
|
const wrapType = run.wrapType;
|
|
30
33
|
const displayMode = run.displayMode;
|
|
31
34
|
if (wrapType === "behind" || wrapType === "inFront") return false;
|
|
@@ -82,9 +85,6 @@ function measureTableCellBlockVisualHeight(block, blockMeasure) {
|
|
|
82
85
|
const spacingAfter = paragraphBlock.attrs?.spacing?.after ?? 0;
|
|
83
86
|
return spacingBefore + maxImageHeight + spacingAfter;
|
|
84
87
|
}
|
|
85
|
-
function getTableCellVerticalBorderHeight(cell, isFirstRow) {
|
|
86
|
-
return (isFirstRow ? cell?.borders?.top?.width ?? 0 : 0) + (cell?.borders?.bottom?.width ?? 0);
|
|
87
|
-
}
|
|
88
88
|
function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
89
89
|
const DEFAULT_CELL_PADDING_X = 7;
|
|
90
90
|
const DEFAULT_CELL_PADDING_Y = 0;
|
|
@@ -101,38 +101,17 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
101
101
|
columnWidths = columnWidths.map((w) => w * scale);
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
-
const
|
|
105
|
-
for (let rowIdx = 0; rowIdx < tableBlock.rows.length; rowIdx++) {
|
|
106
|
-
const row = tableBlock.rows[rowIdx];
|
|
107
|
-
if (!row) continue;
|
|
108
|
-
let colIdx = 0;
|
|
109
|
-
const occupied = occupiedColumnsPerRow.get(rowIdx) ?? /* @__PURE__ */ new Set();
|
|
110
|
-
while (occupied.has(colIdx)) colIdx++;
|
|
111
|
-
for (const cell of row.cells) {
|
|
112
|
-
const colSpan = cell.colSpan ?? 1;
|
|
113
|
-
const rowSpan = cell.rowSpan ?? 1;
|
|
114
|
-
if (rowSpan > 1) for (let r = rowIdx + 1; r < rowIdx + rowSpan; r++) {
|
|
115
|
-
if (!occupiedColumnsPerRow.has(r)) occupiedColumnsPerRow.set(r, /* @__PURE__ */ new Set());
|
|
116
|
-
const occSet = occupiedColumnsPerRow.get(r);
|
|
117
|
-
for (let c = 0; c < colSpan; c++) occSet.add(colIdx + c);
|
|
118
|
-
}
|
|
119
|
-
colIdx += colSpan;
|
|
120
|
-
while (occupied.has(colIdx)) colIdx++;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
104
|
+
const cellGrid = buildTableCellGrid(tableBlock.rows, columnWidths.length);
|
|
123
105
|
const columnsPinned = tableColumnsArePinned(tableBlock);
|
|
124
106
|
const rows = tableBlock.rows.map((row, rowIdx) => {
|
|
125
|
-
let columnIndex = 0;
|
|
126
|
-
const occupied = occupiedColumnsPerRow.get(rowIdx) ?? /* @__PURE__ */ new Set();
|
|
127
|
-
while (occupied.has(columnIndex)) columnIndex++;
|
|
107
|
+
let columnIndex = getFirstAvailableColumn(cellGrid, rowIdx, row.gridBefore ?? 0);
|
|
128
108
|
return {
|
|
129
109
|
cells: row.cells.map((cell) => {
|
|
130
110
|
const colSpan = cell.colSpan ?? 1;
|
|
131
111
|
let cellWidth = 0;
|
|
132
112
|
for (let c = 0; c < colSpan && columnIndex + c < columnWidths.length; c++) cellWidth += columnWidths[columnIndex + c] ?? 0;
|
|
133
113
|
if (cellWidth === 0) cellWidth = cell.width ?? 100;
|
|
134
|
-
columnIndex
|
|
135
|
-
while (occupied.has(columnIndex)) columnIndex++;
|
|
114
|
+
columnIndex = getFirstAvailableColumn(cellGrid, rowIdx, columnIndex + colSpan);
|
|
136
115
|
const padLeft = cell.padding?.left ?? DEFAULT_CELL_PADDING_X;
|
|
137
116
|
const padRight = cell.padding?.right ?? DEFAULT_CELL_PADDING_X;
|
|
138
117
|
const cellContentWidth = Math.max(1, cellWidth - padLeft - padRight);
|
|
@@ -163,7 +142,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
163
142
|
}
|
|
164
143
|
const sourceRowCells = tableBlock.rows[rowIdx]?.cells;
|
|
165
144
|
let maxCellHeightWithBorders = 0;
|
|
166
|
-
let
|
|
145
|
+
let maxCellInsets = 0;
|
|
167
146
|
for (let cellIdx = 0; cellIdx < row.cells.length; cellIdx++) {
|
|
168
147
|
const cell = row.cells[cellIdx];
|
|
169
148
|
const sourceCell = sourceRowCells?.[cellIdx];
|
|
@@ -178,14 +157,14 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
178
157
|
const padBottom = sourceCell?.padding?.bottom ?? DEFAULT_CELL_PADDING_Y;
|
|
179
158
|
cell.height += padTop + padBottom;
|
|
180
159
|
if ((sourceCell?.rowSpan ?? 1) > 1) continue;
|
|
181
|
-
const borderHeight = getTableCellVerticalBorderHeight(sourceCell, rowIdx
|
|
160
|
+
const borderHeight = getTableCellVerticalBorderHeight(cellGrid, sourceCell, rowIdx);
|
|
182
161
|
maxCellHeightWithBorders = Math.max(maxCellHeightWithBorders, cell.height + borderHeight);
|
|
183
|
-
|
|
162
|
+
maxCellInsets = Math.max(maxCellInsets, padTop + padBottom + borderHeight);
|
|
184
163
|
}
|
|
185
164
|
const explicitHeight = sourceRow?.height;
|
|
186
165
|
const heightRule = sourceRow?.heightRule;
|
|
187
166
|
if (explicitHeight && heightRule === "exact") row.height = explicitHeight;
|
|
188
|
-
else if (explicitHeight) row.height = Math.max(maxCellHeightWithBorders, explicitHeight +
|
|
167
|
+
else if (explicitHeight) row.height = Math.max(maxCellHeightWithBorders, explicitHeight + maxCellInsets);
|
|
189
168
|
else row.height = maxCellHeightWithBorders;
|
|
190
169
|
}
|
|
191
170
|
for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
|
|
@@ -200,7 +179,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
|
|
|
200
179
|
const spanEnd = Math.min(rows.length, rowIdx + rowSpan);
|
|
201
180
|
let combinedHeight = 0;
|
|
202
181
|
for (let spannedRowIdx = rowIdx; spannedRowIdx < spanEnd; spannedRowIdx++) combinedHeight += rows[spannedRowIdx]?.height ?? 0;
|
|
203
|
-
const deficit = measuredCell.height + getTableCellVerticalBorderHeight(sourceCell, rowIdx
|
|
182
|
+
const deficit = measuredCell.height + getTableCellVerticalBorderHeight(cellGrid, sourceCell, rowIdx) - combinedHeight;
|
|
204
183
|
if (deficit <= 0) continue;
|
|
205
184
|
for (let spannedRowIdx = spanEnd - 1; spannedRowIdx >= rowIdx; spannedRowIdx--) {
|
|
206
185
|
if (tableBlock.rows[spannedRowIdx]?.heightRule === "exact") continue;
|
|
@@ -223,13 +202,34 @@ function perBlockNumberValue(value, blockIndex, fallback) {
|
|
|
223
202
|
if (Array.isArray(value)) return value[blockIndex] ?? fallback;
|
|
224
203
|
return value;
|
|
225
204
|
}
|
|
205
|
+
function textBoxWrapSide({ box, contentX, boxWidth, contentWidth }) {
|
|
206
|
+
if (box.wrapText === "left") return "left";
|
|
207
|
+
if (box.wrapText === "right") return "right";
|
|
208
|
+
const leftSpace = contentX;
|
|
209
|
+
const rightSpace = contentWidth - contentX - boxWidth;
|
|
210
|
+
if (box.wrapText === "largest") return leftSpace >= rightSpace ? "left" : "right";
|
|
211
|
+
return contentX + boxWidth / 2 < contentWidth / 2 ? "right" : "left";
|
|
212
|
+
}
|
|
226
213
|
function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry) {
|
|
227
214
|
const zones = [];
|
|
215
|
+
const defaultContentWidth = Array.isArray(contentWidth) ? contentWidth[0] ?? 0 : contentWidth;
|
|
228
216
|
const textBoxGroupAnchors = /* @__PURE__ */ new Map();
|
|
217
|
+
const paragraphFrameGroupSizes = /* @__PURE__ */ new Map();
|
|
218
|
+
for (const block of blocks) {
|
|
219
|
+
if (block.kind !== "textBox" || !isParagraphFrameTextBox(block)) continue;
|
|
220
|
+
const groupId = getTextBoxGroupId(block);
|
|
221
|
+
if (groupId !== void 0) paragraphFrameGroupSizes.set(groupId, (paragraphFrameGroupSizes.get(groupId) ?? 0) + 1);
|
|
222
|
+
}
|
|
229
223
|
const defaultMarginTop = Array.isArray(marginTop) ? marginTop[0] ?? 0 : marginTop;
|
|
224
|
+
const pageWidthInput = pageGeometry?.pageWidth ?? defaultContentWidth;
|
|
230
225
|
const pageHeightInput = pageGeometry?.pageHeight ?? 0;
|
|
226
|
+
const marginLeftInput = pageGeometry?.marginLeft ?? 0;
|
|
227
|
+
const marginRightInput = pageGeometry?.marginRight ?? 0;
|
|
231
228
|
const marginBottomInput = pageGeometry?.marginBottom ?? 0;
|
|
229
|
+
const defaultPageWidth = Array.isArray(pageWidthInput) ? pageWidthInput[0] ?? defaultContentWidth : pageWidthInput;
|
|
232
230
|
const defaultPageHeight = Array.isArray(pageHeightInput) ? pageHeightInput[0] ?? 0 : pageHeightInput;
|
|
231
|
+
const defaultMarginLeft = Array.isArray(marginLeftInput) ? marginLeftInput[0] ?? 0 : marginLeftInput;
|
|
232
|
+
const defaultMarginRight = Array.isArray(marginRightInput) ? marginRightInput[0] ?? 0 : marginRightInput;
|
|
233
233
|
const defaultMarginBottom = Array.isArray(marginBottomInput) ? marginBottomInput[0] ?? 0 : marginBottomInput;
|
|
234
234
|
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
|
|
235
235
|
const block = blocks[blockIndex];
|
|
@@ -238,13 +238,36 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
|
|
|
238
238
|
for (const run of paragraphBlock.runs) {
|
|
239
239
|
if (run.kind !== "image") continue;
|
|
240
240
|
const imgRun = run;
|
|
241
|
-
|
|
241
|
+
const positionedBand = imgRun.wrapType === "topAndBottom" && imgRun.position !== void 0;
|
|
242
|
+
if (!isSideWrappingImageRun(imgRun) && !positionedBand) continue;
|
|
242
243
|
let topY = 0;
|
|
243
244
|
const position = imgRun.position;
|
|
244
245
|
const distTop = imgRun.distTop ?? 0;
|
|
245
246
|
const distBottom = imgRun.distBottom ?? 0;
|
|
246
247
|
const distLeft = imgRun.distLeft ?? 12;
|
|
247
248
|
const distRight = imgRun.distRight ?? 12;
|
|
249
|
+
if (positionedBand && position !== void 0) {
|
|
250
|
+
const vertical = position.vertical;
|
|
251
|
+
const blockMarginTop = perBlockNumberValue(marginTop, blockIndex, defaultMarginTop);
|
|
252
|
+
const pageFrameRelative = isPageFrameRelativeAnchor(vertical?.relativeTo);
|
|
253
|
+
const rawTop = pageFrameRelative ? bandTopContentY(vertical, {
|
|
254
|
+
pageHeight: perBlockNumberValue(pageHeightInput, blockIndex, defaultPageHeight),
|
|
255
|
+
marginTop: blockMarginTop,
|
|
256
|
+
marginBottom: perBlockNumberValue(marginBottomInput, blockIndex, defaultMarginBottom),
|
|
257
|
+
boxHeight: imgRun.height
|
|
258
|
+
}) : emuToPixels(vertical?.posOffset ?? 0);
|
|
259
|
+
const bottomY = rawTop + imgRun.height + distBottom;
|
|
260
|
+
if (bottomY > 0) zones.push({
|
|
261
|
+
leftMargin: 0,
|
|
262
|
+
rightMargin: 0,
|
|
263
|
+
topY: Math.max(0, rawTop - distTop),
|
|
264
|
+
bottomY,
|
|
265
|
+
anchorBlockIndex: blockIndex,
|
|
266
|
+
...pageFrameRelative ? { isMarginRelative: true } : {},
|
|
267
|
+
fullWidthBlock: true
|
|
268
|
+
});
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
248
271
|
if (position?.vertical) {
|
|
249
272
|
const v = position.vertical;
|
|
250
273
|
if (v.align === "top" && v.relativeTo === "margin") topY = 0;
|
|
@@ -259,8 +282,8 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
|
|
|
259
282
|
else if (h.align === "right") rightMargin = imgRun.width + distLeft;
|
|
260
283
|
else if (h.posOffset !== void 0) {
|
|
261
284
|
const x = emuToPixels(h.posOffset);
|
|
262
|
-
if (x <
|
|
263
|
-
else rightMargin =
|
|
285
|
+
if (x < defaultContentWidth / 2) leftMargin = x + imgRun.width + distRight;
|
|
286
|
+
else rightMargin = defaultContentWidth - x + distLeft;
|
|
264
287
|
}
|
|
265
288
|
} else if (imgRun.cssFloat === "left") leftMargin = imgRun.width + distRight;
|
|
266
289
|
else if (imgRun.cssFloat === "right") rightMargin = imgRun.width + distLeft;
|
|
@@ -283,7 +306,7 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
|
|
|
283
306
|
const tableBlock = block;
|
|
284
307
|
const floating = tableBlock.floating;
|
|
285
308
|
if (!floating) continue;
|
|
286
|
-
const tableMeasure = measureTableBlock(tableBlock,
|
|
309
|
+
const tableMeasure = measureTableBlock(tableBlock, defaultContentWidth);
|
|
287
310
|
const tableWidth = tableMeasure.totalWidth;
|
|
288
311
|
const tableHeight = tableMeasure.totalHeight;
|
|
289
312
|
const distLeft = floating.leftFromText ?? 12;
|
|
@@ -295,12 +318,12 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
|
|
|
295
318
|
let x = 0;
|
|
296
319
|
if (floating.tblpX !== void 0) x = floating.tblpX;
|
|
297
320
|
else if (floating.tblpXSpec) if (floating.tblpXSpec === "left" || floating.tblpXSpec === "inside") x = 0;
|
|
298
|
-
else if (floating.tblpXSpec === "right" || floating.tblpXSpec === "outside") x =
|
|
299
|
-
else x = (
|
|
300
|
-
else if (tableBlock.justification === "center") x = (
|
|
301
|
-
else if (tableBlock.justification === "right") x =
|
|
302
|
-
if (x <
|
|
303
|
-
else rightMargin =
|
|
321
|
+
else if (floating.tblpXSpec === "right" || floating.tblpXSpec === "outside") x = defaultContentWidth - tableWidth;
|
|
322
|
+
else x = (defaultContentWidth - tableWidth) / 2;
|
|
323
|
+
else if (tableBlock.justification === "center") x = (defaultContentWidth - tableWidth) / 2;
|
|
324
|
+
else if (tableBlock.justification === "right") x = defaultContentWidth - tableWidth;
|
|
325
|
+
if (x < defaultContentWidth / 2) leftMargin = x + tableWidth + distRight;
|
|
326
|
+
else rightMargin = defaultContentWidth - x + distLeft;
|
|
304
327
|
const topY = floating.tblpY ?? 0;
|
|
305
328
|
const bottomY = topY + tableHeight;
|
|
306
329
|
zones.push({
|
|
@@ -311,6 +334,64 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
|
|
|
311
334
|
anchorBlockIndex: blockIndex
|
|
312
335
|
});
|
|
313
336
|
}
|
|
337
|
+
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
|
|
338
|
+
const block = blocks[blockIndex];
|
|
339
|
+
if (block.kind !== "textBox") continue;
|
|
340
|
+
const tb = block;
|
|
341
|
+
if (!floatingTextBoxWrapsText(tb) || tb.position === void 0) continue;
|
|
342
|
+
const measure = measureTextBoxBlock(tb);
|
|
343
|
+
const blockMarginTop = perBlockNumberValue(marginTop, blockIndex, defaultMarginTop);
|
|
344
|
+
const blockMarginLeft = perBlockNumberValue(marginLeftInput, blockIndex, defaultMarginLeft);
|
|
345
|
+
const blockMarginRight = perBlockNumberValue(marginRightInput, blockIndex, defaultMarginRight);
|
|
346
|
+
const blockPageWidth = perBlockNumberValue(pageWidthInput, blockIndex, defaultPageWidth);
|
|
347
|
+
const blockContentWidth = perBlockNumberValue(contentWidth, blockIndex, defaultContentWidth);
|
|
348
|
+
const vertical = tb.position.vertical;
|
|
349
|
+
const pageFrameRelative = isPageFrameRelativeAnchor(vertical?.relativeTo);
|
|
350
|
+
const topY = pageFrameRelative ? bandTopContentY(vertical, {
|
|
351
|
+
pageHeight: perBlockNumberValue(pageHeightInput, blockIndex, defaultPageHeight),
|
|
352
|
+
marginTop: blockMarginTop,
|
|
353
|
+
marginBottom: perBlockNumberValue(marginBottomInput, blockIndex, defaultMarginBottom),
|
|
354
|
+
boxHeight: measure.height
|
|
355
|
+
}) : emuToPixels(vertical?.posOffset ?? 0);
|
|
356
|
+
const groupId = getTextBoxGroupId(tb);
|
|
357
|
+
const anchorBlockIndex = groupId ? textBoxGroupAnchors.get(groupId) ?? blockIndex : blockIndex;
|
|
358
|
+
if (groupId && !textBoxGroupAnchors.has(groupId)) textBoxGroupAnchors.set(groupId, blockIndex);
|
|
359
|
+
if (isParagraphFrameTextBox(tb) && groupId !== void 0 && (paragraphFrameGroupSizes.get(groupId) ?? 0) > 1) {
|
|
360
|
+
zones.push({
|
|
361
|
+
leftMargin: 0,
|
|
362
|
+
rightMargin: 0,
|
|
363
|
+
topY: 0,
|
|
364
|
+
bottomY: topY + measure.height + (tb.distBottom ?? 0),
|
|
365
|
+
anchorBlockIndex,
|
|
366
|
+
...pageFrameRelative ? { isMarginRelative: true } : {},
|
|
367
|
+
fullWidthBlock: true
|
|
368
|
+
});
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const horizontal = tb.position.horizontal;
|
|
372
|
+
const contentX = (horizontal ? bandFragmentX(horizontal, {
|
|
373
|
+
pageWidth: blockPageWidth,
|
|
374
|
+
marginLeft: blockMarginLeft,
|
|
375
|
+
marginRight: blockMarginRight,
|
|
376
|
+
boxWidth: measure.width
|
|
377
|
+
}) : blockMarginLeft) - blockMarginLeft;
|
|
378
|
+
const wrapSide = textBoxWrapSide({
|
|
379
|
+
box: tb,
|
|
380
|
+
contentX,
|
|
381
|
+
boxWidth: measure.width,
|
|
382
|
+
contentWidth: blockContentWidth
|
|
383
|
+
});
|
|
384
|
+
const leftMargin = wrapSide === "right" ? contentX + measure.width + (tb.distRight ?? 12) : 0;
|
|
385
|
+
const rightMargin = wrapSide === "left" ? blockContentWidth - contentX + (tb.distLeft ?? 12) : 0;
|
|
386
|
+
zones.push({
|
|
387
|
+
leftMargin: Math.max(0, leftMargin),
|
|
388
|
+
rightMargin: Math.max(0, rightMargin),
|
|
389
|
+
topY: topY - (tb.distTop ?? 0),
|
|
390
|
+
bottomY: topY + measure.height + (tb.distBottom ?? 0),
|
|
391
|
+
anchorBlockIndex,
|
|
392
|
+
...pageFrameRelative ? { isMarginRelative: true } : {}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
314
395
|
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
|
|
315
396
|
const block = blocks[blockIndex];
|
|
316
397
|
if (block.kind !== "textBox") continue;
|
|
@@ -389,8 +470,8 @@ function measureTextBoxBlock(tb, fieldValues) {
|
|
|
389
470
|
const margins = tb.margins ?? DEFAULT_TEXTBOX_MARGINS;
|
|
390
471
|
const innerWidth = tb.width - margins.left - margins.right;
|
|
391
472
|
const innerMeasures = tb.content.map((p) => measureParagraph(p, innerWidth, fieldValues ? { fieldValues } : void 0));
|
|
392
|
-
const
|
|
393
|
-
const totalHeight = tb.height ??
|
|
473
|
+
const contentBoxHeight = layoutTextBoxParagraphs(tb.content, innerMeasures).totalHeight + margins.top + margins.bottom;
|
|
474
|
+
const totalHeight = tb.autoFit === "shape" ? Math.max(tb.height ?? 0, contentBoxHeight) : tb.height ?? contentBoxHeight;
|
|
394
475
|
return {
|
|
395
476
|
kind: "textBox",
|
|
396
477
|
width: tb.width,
|
|
@@ -418,7 +499,7 @@ function isNextPageSectionBreak(block) {
|
|
|
418
499
|
*/
|
|
419
500
|
function measureBlocks(blocks, contentWidth, marginTop = 0, pageGeometry, fieldValues) {
|
|
420
501
|
const defaultWidth = Array.isArray(contentWidth) ? contentWidth[0] ?? 0 : contentWidth;
|
|
421
|
-
const floatingZonesWithAnchors = extractFloatingZones(blocks,
|
|
502
|
+
const floatingZonesWithAnchors = extractFloatingZones(blocks, contentWidth, marginTop, pageGeometry);
|
|
422
503
|
const marginRelative = floatingZonesWithAnchors.filter((z) => z.isMarginRelative && !z.fullWidthBlock);
|
|
423
504
|
const ownAnchorZones = floatingZonesWithAnchors.filter((z) => !z.isMarginRelative || z.fullWidthBlock);
|
|
424
505
|
const marginByTopY = /* @__PURE__ */ new Map();
|
|
@@ -461,10 +542,11 @@ function measureBlocks(blocks, contentWidth, marginTop = 0, pageGeometry, fieldV
|
|
|
461
542
|
try {
|
|
462
543
|
const blockWidth = Array.isArray(contentWidth) ? contentWidth[blockIndex] ?? defaultWidth : contentWidth;
|
|
463
544
|
const measure = measureBlock(block, blockWidth, zones, cumulativeY, fieldValues);
|
|
464
|
-
const
|
|
465
|
-
if (
|
|
545
|
+
const clearingZones = measure.kind === "table" ? zones : zones?.filter((zone) => zone.fullWidthBlock);
|
|
546
|
+
if (clearingZones?.length && (measure.kind === "image" || measure.kind === "table" && !block.floating)) {
|
|
466
547
|
const blockHeight = measure.kind === "image" ? measure.height : measure.totalHeight;
|
|
467
|
-
const
|
|
548
|
+
const requiredWidth = measure.kind === "table" ? Math.min(blockWidth, measure.totalWidth) : 24;
|
|
549
|
+
const skip = findClearLineY(cumulativeY, blockHeight, clearingZones, blockWidth, Math.max(24, requiredWidth)) - cumulativeY;
|
|
468
550
|
if (skip > 0) {
|
|
469
551
|
measure.bandSkipBefore = skip;
|
|
470
552
|
cumulativeY += skip;
|
|
@@ -22,8 +22,9 @@ const DEFAULT_FONT_FAMILY = "Calibri";
|
|
|
22
22
|
const DEFAULT_LINE_HEIGHT_MULTIPLIER = 1;
|
|
23
23
|
const WIDTH_TOLERANCE = .5;
|
|
24
24
|
const JUSTIFY_SHRINK_TOLERANCE_RATIO = .016;
|
|
25
|
+
const JUSTIFY_INSET_LIST_SHRINK_TOLERANCE_RATIO = .015;
|
|
25
26
|
const JUSTIFY_LITERAL_TAB_CONTINUATION_SHRINK_TOLERANCE_RATIO = .017;
|
|
26
|
-
const JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO = .
|
|
27
|
+
const JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO = .02;
|
|
27
28
|
const JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO = .021;
|
|
28
29
|
const DEFAULT_LIST_HANGING_INDENT_PX = 24;
|
|
29
30
|
const ALL_CAPS_RATIO_THRESHOLD = .8;
|
|
@@ -245,6 +246,7 @@ function isEmptyTextRun(run) {
|
|
|
245
246
|
* preceding a floating image.
|
|
246
247
|
*/
|
|
247
248
|
function isBlockLayoutImageRun(run) {
|
|
249
|
+
if (isFloatingImageRun(run)) return false;
|
|
248
250
|
return run.wrapType === "topAndBottom" || run.displayMode === "block";
|
|
249
251
|
}
|
|
250
252
|
function measureInlineWidthAfterTab(runs, tabIndex, fieldValues) {
|
|
@@ -339,7 +341,7 @@ function justifyShrinkToleranceRatio(block, isFirstLine, regularSpaceCount, nonB
|
|
|
339
341
|
const hanging = block.attrs.indent?.hanging ?? 0;
|
|
340
342
|
if (isFirstLine) return hanging <= DEFAULT_LIST_HANGING_INDENT_PX ? JUSTIFY_SHRINK_TOLERANCE_RATIO : JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO;
|
|
341
343
|
const left = block.attrs.indent?.left ?? 0;
|
|
342
|
-
if (hanging > 0 && left > hanging) return
|
|
344
|
+
if (hanging > 0 && left > hanging) return JUSTIFY_INSET_LIST_SHRINK_TOLERANCE_RATIO;
|
|
343
345
|
return fixedSpaceAdjustedShrinkTolerance({
|
|
344
346
|
tolerance: JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO,
|
|
345
347
|
regularSpaceCount,
|
|
@@ -552,7 +554,14 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
552
554
|
...firstLineFloatingMargins.segments?.length ? { segmentZones: firstLineFloatingMargins.segments } : {}
|
|
553
555
|
};
|
|
554
556
|
const calculateLineTypography = (line) => {
|
|
555
|
-
const
|
|
557
|
+
const paragraphFontSize = attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE;
|
|
558
|
+
const paragraphFontFamily = attrs?.defaultFontFamily ?? DEFAULT_FONT_FAMILY;
|
|
559
|
+
const fontSize = line.maxFontMetrics ? line.maxFontSize : paragraphFontSize;
|
|
560
|
+
const metrics = line.maxFontMetrics ?? getFontMetrics({
|
|
561
|
+
fontSize: paragraphFontSize,
|
|
562
|
+
fontFamily: paragraphFontFamily
|
|
563
|
+
});
|
|
564
|
+
const finalTypography = { ...calculateTypographyMetrics(fontSize, spacing, metrics) };
|
|
556
565
|
const inlineObjectHeight = Math.max(line.maxImageHeightPx, line.maxMathHeightPx);
|
|
557
566
|
if (inlineObjectHeight > finalTypography.lineHeight) {
|
|
558
567
|
const objectHeight = inlineObjectHeight;
|
|
@@ -682,7 +691,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
682
691
|
}
|
|
683
692
|
if (isImageRun(run)) {
|
|
684
693
|
const wrapType = run.wrapType;
|
|
685
|
-
if (run
|
|
694
|
+
if (isFloatingImageRun(run)) {
|
|
686
695
|
currentLine.toRun = runIndex;
|
|
687
696
|
currentLine.toChar = 1;
|
|
688
697
|
continue;
|