@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
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { TableCell, TableRow } from "../types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/layout-engine/measure/tableCellGrid.d.ts
|
|
4
|
+
type TableCellGrid = {
|
|
5
|
+
occupiedColumnsByRow: ReadonlyMap<number, ReadonlySet<number>>;
|
|
6
|
+
sourceCellsByRow: ReadonlyMap<number, ReadonlyMap<number, TableCell>>;
|
|
7
|
+
sourceColumnsByCell: ReadonlyMap<TableCell, number>;
|
|
8
|
+
};
|
|
9
|
+
declare const buildTableCellGrid: (rows: readonly TableRow[], columnCount: number) => TableCellGrid;
|
|
10
|
+
declare const getFirstAvailableColumn: (grid: TableCellGrid, rowIndex: number, startingColumn: number) => number;
|
|
11
|
+
declare const getSourceCellAt: (grid: TableCellGrid, rowIndex: number, columnIndex: number) => TableCell | undefined;
|
|
12
|
+
declare const getSourceCellColumn: (grid: TableCellGrid, cell: TableCell) => number | undefined;
|
|
13
|
+
declare const getTableCellVerticalBorderHeight: (grid: TableCellGrid, cell: TableCell | undefined, rowIndex: number) => number;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { TableCellGrid, buildTableCellGrid, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellVerticalBorderHeight };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//#region src/layout-engine/measure/tableCellGrid.ts
|
|
2
|
+
const buildTableCellGrid = (rows, columnCount) => {
|
|
3
|
+
const occupiedColumnsByRow = /* @__PURE__ */ new Map();
|
|
4
|
+
const sourceCellsByRow = /* @__PURE__ */ new Map();
|
|
5
|
+
const sourceColumnsByCell = /* @__PURE__ */ new Map();
|
|
6
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
|
|
7
|
+
const row = rows[rowIndex];
|
|
8
|
+
if (!row) continue;
|
|
9
|
+
let columnIndex = firstAvailableColumn(occupiedColumnsByRow.get(rowIndex), row.gridBefore ?? 0);
|
|
10
|
+
for (const cell of row.cells) {
|
|
11
|
+
const columnSpan = cell.colSpan ?? 1;
|
|
12
|
+
const rowSpan = cell.rowSpan ?? 1;
|
|
13
|
+
sourceColumnsByCell.set(cell, columnIndex);
|
|
14
|
+
const rowEnd = Math.min(rows.length, rowIndex + rowSpan);
|
|
15
|
+
const columnEnd = Math.min(columnCount, columnIndex + columnSpan);
|
|
16
|
+
for (let gridRowIndex = rowIndex; gridRowIndex < rowEnd; gridRowIndex++) {
|
|
17
|
+
const cellsByColumn = getOrCreateCellRow(sourceCellsByRow, gridRowIndex);
|
|
18
|
+
for (let gridColumnIndex = columnIndex; gridColumnIndex < columnEnd; gridColumnIndex++) cellsByColumn.set(gridColumnIndex, cell);
|
|
19
|
+
}
|
|
20
|
+
if (rowSpan > 1) for (let spannedRowIndex = rowIndex + 1; spannedRowIndex < rowIndex + rowSpan; spannedRowIndex++) {
|
|
21
|
+
const occupied = getOrCreateOccupiedRow(occupiedColumnsByRow, spannedRowIndex);
|
|
22
|
+
for (let columnOffset = 0; columnOffset < columnSpan; columnOffset++) occupied.add(columnIndex + columnOffset);
|
|
23
|
+
}
|
|
24
|
+
columnIndex = firstAvailableColumn(occupiedColumnsByRow.get(rowIndex), columnIndex + columnSpan);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
occupiedColumnsByRow,
|
|
29
|
+
sourceCellsByRow,
|
|
30
|
+
sourceColumnsByCell
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
const getFirstAvailableColumn = (grid, rowIndex, startingColumn) => firstAvailableColumn(grid.occupiedColumnsByRow.get(rowIndex), startingColumn);
|
|
34
|
+
const getSourceCellAt = (grid, rowIndex, columnIndex) => grid.sourceCellsByRow.get(rowIndex)?.get(columnIndex);
|
|
35
|
+
const getSourceCellColumn = (grid, cell) => grid.sourceColumnsByCell.get(cell);
|
|
36
|
+
const getTableCellVerticalBorderHeight = (grid, cell, rowIndex) => {
|
|
37
|
+
const sourceColumn = cell ? getSourceCellColumn(grid, cell) : void 0;
|
|
38
|
+
const aboveBottom = (sourceColumn === void 0 ? void 0 : getSourceCellAt(grid, rowIndex - 1, sourceColumn))?.borders?.bottom;
|
|
39
|
+
const aboveOwnsEdge = aboveBottom !== void 0 && aboveBottom.width !== 0 && aboveBottom.style !== "none" && aboveBottom.style !== "nil";
|
|
40
|
+
return (rowIndex === 0 || !aboveOwnsEdge ? cell?.borders?.top?.width ?? 0 : 0) + (cell?.borders?.bottom?.width ?? 0);
|
|
41
|
+
};
|
|
42
|
+
const firstAvailableColumn = (occupiedColumns, startingColumn) => {
|
|
43
|
+
let columnIndex = startingColumn;
|
|
44
|
+
while (occupiedColumns?.has(columnIndex)) columnIndex++;
|
|
45
|
+
return columnIndex;
|
|
46
|
+
};
|
|
47
|
+
const getOrCreateCellRow = (sourceCellsByRow, rowIndex) => {
|
|
48
|
+
const existing = sourceCellsByRow.get(rowIndex);
|
|
49
|
+
if (existing !== void 0) return existing;
|
|
50
|
+
const cellsByColumn = /* @__PURE__ */ new Map();
|
|
51
|
+
sourceCellsByRow.set(rowIndex, cellsByColumn);
|
|
52
|
+
return cellsByColumn;
|
|
53
|
+
};
|
|
54
|
+
const getOrCreateOccupiedRow = (occupiedColumnsByRow, rowIndex) => {
|
|
55
|
+
const existing = occupiedColumnsByRow.get(rowIndex);
|
|
56
|
+
if (existing !== void 0) return existing;
|
|
57
|
+
const occupiedColumns = /* @__PURE__ */ new Set();
|
|
58
|
+
occupiedColumnsByRow.set(rowIndex, occupiedColumns);
|
|
59
|
+
return occupiedColumns;
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
export { buildTableCellGrid, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellVerticalBorderHeight };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ParagraphBlock, ParagraphMeasure } from "../types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/layout-engine/measure/textBoxParagraphLayout.d.ts
|
|
4
|
+
type TextBoxParagraphPlacement = {
|
|
5
|
+
leadingSpacing: number;
|
|
6
|
+
contentHeight: number;
|
|
7
|
+
};
|
|
8
|
+
type TextBoxParagraphLayout = {
|
|
9
|
+
placements: TextBoxParagraphPlacement[];
|
|
10
|
+
totalHeight: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Lay out a text box's paragraph story as one unpaginated flow. Adjacent
|
|
14
|
+
* before/after spacing collapses to the larger value, matching body flow.
|
|
15
|
+
*/
|
|
16
|
+
declare function layoutTextBoxParagraphs(blocks: readonly ParagraphBlock[], measures: readonly ParagraphMeasure[]): TextBoxParagraphLayout;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { TextBoxParagraphLayout, TextBoxParagraphPlacement, layoutTextBoxParagraphs };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { measuredLineRangeHeight } from "../lineFlow.js";
|
|
2
|
+
import { getParagraphSpacingAfter, getParagraphSpacingBefore } from "../paragraphSpacing.js";
|
|
3
|
+
import { panic } from "better-result";
|
|
4
|
+
//#region src/layout-engine/measure/textBoxParagraphLayout.ts
|
|
5
|
+
/**
|
|
6
|
+
* Lay out a text box's paragraph story as one unpaginated flow. Adjacent
|
|
7
|
+
* before/after spacing collapses to the larger value, matching body flow.
|
|
8
|
+
*/
|
|
9
|
+
function layoutTextBoxParagraphs(blocks, measures) {
|
|
10
|
+
if (blocks.length !== measures.length) panic("layoutTextBoxParagraphs: block and measure counts must match");
|
|
11
|
+
const placements = [];
|
|
12
|
+
let totalHeight = 0;
|
|
13
|
+
let trailingSpacing = 0;
|
|
14
|
+
for (let index = 0; index < blocks.length; index += 1) {
|
|
15
|
+
const block = blocks[index];
|
|
16
|
+
const measure = measures[index];
|
|
17
|
+
const leadingSpacing = Math.max(getParagraphSpacingBefore(block), trailingSpacing);
|
|
18
|
+
const contentHeight = measuredLineRangeHeight(measure.lines, 0, measure.lines.length);
|
|
19
|
+
placements.push({
|
|
20
|
+
leadingSpacing,
|
|
21
|
+
contentHeight
|
|
22
|
+
});
|
|
23
|
+
totalHeight += leadingSpacing + contentHeight;
|
|
24
|
+
trailingSpacing = getParagraphSpacingAfter(block);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
placements,
|
|
28
|
+
totalHeight: totalHeight + trailingSpacing
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { layoutTextBoxParagraphs };
|
|
@@ -35,7 +35,8 @@ type PaginatorOptions = {
|
|
|
35
35
|
* its title page (typically extended top margin to clear an overflowing
|
|
36
36
|
* first-page header) vs. its body pages. Pages 2+ use `margins`.
|
|
37
37
|
*/
|
|
38
|
-
firstPageMargins?: PageMargins; /**
|
|
38
|
+
firstPageMargins?: PageMargins; /** Per-section body margins used on even section pages. */
|
|
39
|
+
sectionEvenPageMargins?: (PageMargins | undefined)[]; /** Column configuration (optional). */
|
|
39
40
|
columns?: ColumnLayout; /** Per-page footnote reserved heights (pageNumber → height in pixels). */
|
|
40
41
|
footnoteReservedHeights?: Map<number, number>; /** Header/footer refs by section index. */
|
|
41
42
|
sectionHeaderFooterRefs?: PageHeaderFooterRefs[]; /** Callback when a new page is created. */
|
|
@@ -47,7 +47,7 @@ function createPaginator(options) {
|
|
|
47
47
|
}
|
|
48
48
|
function currentPageUsesActiveLayout(state) {
|
|
49
49
|
if (pendingPageSize || pendingMargins) return false;
|
|
50
|
-
return arePageSizesEqual(state.page.size, pageSize) && areMarginsEqual(state.page.margins, getPageMargins(state.page.number));
|
|
50
|
+
return arePageSizesEqual(state.page.size, pageSize) && areMarginsEqual(state.page.margins, getPageMargins(state.page.number, state.page.sectionPageNumber ?? currentSectionPageNumber));
|
|
51
51
|
}
|
|
52
52
|
if (getContentHeight() <= 0) panic("Paginator: page size and margins yield no content area");
|
|
53
53
|
let columnWidths = calculateColumnWidths(pageSize.w, margins.left, margins.right, columns);
|
|
@@ -62,8 +62,9 @@ function createPaginator(options) {
|
|
|
62
62
|
if (getContentHeight() <= 0) panic("Paginator: section page size and margins yield no content area");
|
|
63
63
|
recalculateColumnWidths();
|
|
64
64
|
}
|
|
65
|
-
function getPageMargins(pageNumber) {
|
|
66
|
-
const
|
|
65
|
+
function getPageMargins(pageNumber, sectionPageNumber) {
|
|
66
|
+
const evenMargins = sectionPageNumber % 2 === 0 ? options.sectionEvenPageMargins?.[currentSectionIndex] : void 0;
|
|
67
|
+
const pageMargins = pageNumber === 1 && options.firstPageMargins ? { ...options.firstPageMargins } : { ...evenMargins ?? margins };
|
|
67
68
|
if (options.mirrorMargins === true && pageNumber % 2 === 0) return {
|
|
68
69
|
...pageMargins,
|
|
69
70
|
left: pageMargins.right,
|
|
@@ -77,7 +78,7 @@ function createPaginator(options) {
|
|
|
77
78
|
* Get X position for a given column index.
|
|
78
79
|
*/
|
|
79
80
|
function getColumnX(columnIndex) {
|
|
80
|
-
let x = states.at(-1)?.page.margins.left ?? getPageMargins(1).left;
|
|
81
|
+
let x = states.at(-1)?.page.margins.left ?? getPageMargins(1, 1).left;
|
|
81
82
|
for (let index = 0; index < columnIndex; index++) x += (columnWidths[index] ?? columnWidths[0] ?? 0) + gapAfterColumn(columns, index);
|
|
82
83
|
return x;
|
|
83
84
|
}
|
|
@@ -88,7 +89,7 @@ function createPaginator(options) {
|
|
|
88
89
|
if (pendingPageSize || pendingMargins) applyPendingLayout();
|
|
89
90
|
const pageNumber = pages.length + 1;
|
|
90
91
|
currentSectionPageNumber += 1;
|
|
91
|
-
const pageMargins = getPageMargins(pageNumber);
|
|
92
|
+
const pageMargins = getPageMargins(pageNumber, currentSectionPageNumber);
|
|
92
93
|
const topMargin = pageMargins.top;
|
|
93
94
|
const contentBottom = pageSize.h - pageMargins.bottom;
|
|
94
95
|
const footnoteHeight = options.footnoteReservedHeights?.get(pageNumber) ?? 0;
|
|
@@ -244,7 +245,7 @@ function createPaginator(options) {
|
|
|
244
245
|
const current = states.at(-1);
|
|
245
246
|
if (!current || current.page.fragments.length > 0 || current.cursorY !== current.topMargin) return false;
|
|
246
247
|
if (pendingPageSize || pendingMargins) applyPendingLayout();
|
|
247
|
-
const pageMargins = getPageMargins(current.page.number);
|
|
248
|
+
const pageMargins = getPageMargins(current.page.number, 1);
|
|
248
249
|
const topMargin = pageMargins.top;
|
|
249
250
|
const rawContentBottom = pageSize.h - pageMargins.bottom;
|
|
250
251
|
const footnoteHeight = options.footnoteReservedHeights?.get(current.page.number) ?? 0;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ParagraphBlock, TextBoxBlock } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/layout-engine/paragraphFrame.d.ts
|
|
4
|
+
type ParagraphFrame = {
|
|
5
|
+
width?: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
hSpace?: number;
|
|
8
|
+
vSpace?: number;
|
|
9
|
+
hAnchor?: "text" | "margin" | "page";
|
|
10
|
+
vAnchor?: "text" | "margin" | "page";
|
|
11
|
+
x?: number;
|
|
12
|
+
y?: number;
|
|
13
|
+
xAlign?: "left" | "center" | "right" | "inside" | "outside";
|
|
14
|
+
yAlign?: "top" | "center" | "bottom" | "inside" | "outside" | "inline";
|
|
15
|
+
wrap?: "around" | "auto" | "none" | "notBeside" | "through" | "tight";
|
|
16
|
+
};
|
|
17
|
+
declare function setParagraphFrame(block: ParagraphBlock, frame: ParagraphFrame): void;
|
|
18
|
+
declare function getParagraphFrame(block: ParagraphBlock): ParagraphFrame | undefined;
|
|
19
|
+
declare function markParagraphFrameTextBox(block: TextBoxBlock): void;
|
|
20
|
+
declare function isParagraphFrameTextBox(block: TextBoxBlock): boolean;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { ParagraphFrame, getParagraphFrame, isParagraphFrameTextBox, markParagraphFrameTextBox, setParagraphFrame };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/layout-engine/paragraphFrame.ts
|
|
2
|
+
const paragraphFrames = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
const paragraphFrameTextBoxes = /* @__PURE__ */ new WeakSet();
|
|
4
|
+
function setParagraphFrame(block, frame) {
|
|
5
|
+
paragraphFrames.set(block, frame);
|
|
6
|
+
}
|
|
7
|
+
function getParagraphFrame(block) {
|
|
8
|
+
return paragraphFrames.get(block);
|
|
9
|
+
}
|
|
10
|
+
function markParagraphFrameTextBox(block) {
|
|
11
|
+
paragraphFrameTextBoxes.add(block);
|
|
12
|
+
}
|
|
13
|
+
function isParagraphFrameTextBox(block) {
|
|
14
|
+
return paragraphFrameTextBoxes.has(block);
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { getParagraphFrame, isParagraphFrameTextBox, markParagraphFrameTextBox, setParagraphFrame };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FlowBlock } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/layout-engine/paragraphSequence.d.ts
|
|
4
|
+
declare const continuesNumberedSequence: (previous: FlowBlock | undefined, current: FlowBlock) => boolean;
|
|
5
|
+
declare const continuesTabbedParagraphSequence: (previous: FlowBlock | undefined, current: FlowBlock) => boolean;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { continuesNumberedSequence, continuesTabbedParagraphSequence };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/layout-engine/paragraphSequence.ts
|
|
2
|
+
const continuesNumberedSequence = (previous, current) => {
|
|
3
|
+
if (previous?.kind !== "paragraph" || current.kind !== "paragraph") return false;
|
|
4
|
+
const previousNumPr = previous.attrs?.numPr;
|
|
5
|
+
const currentNumPr = current.attrs?.numPr;
|
|
6
|
+
if (previousNumPr?.numId === void 0 || currentNumPr?.numId === void 0) return false;
|
|
7
|
+
return previousNumPr.numId === currentNumPr.numId && previousNumPr.ilvl === currentNumPr.ilvl;
|
|
8
|
+
};
|
|
9
|
+
const continuesTabbedParagraphSequence = (previous, current) => {
|
|
10
|
+
if (previous?.kind !== "paragraph" || current.kind !== "paragraph") return false;
|
|
11
|
+
if (!previous.attrs?.styleId || previous.attrs.styleId !== current.attrs?.styleId) return false;
|
|
12
|
+
if (!previous.runs.some((run) => run.kind === "tab") || !current.runs.some((run) => run.kind === "tab")) return false;
|
|
13
|
+
const previousIndent = previous.attrs.indent;
|
|
14
|
+
const currentIndent = current.attrs.indent;
|
|
15
|
+
if (previousIndent?.left !== currentIndent?.left || previousIndent?.right !== currentIndent?.right || previousIndent?.firstLine !== currentIndent?.firstLine || previousIndent?.hanging !== currentIndent?.hanging) return false;
|
|
16
|
+
const previousTabs = previous.attrs.tabs ?? [];
|
|
17
|
+
const currentTabs = current.attrs.tabs ?? [];
|
|
18
|
+
return previousTabs.length === currentTabs.length && previousTabs.every((tab, index) => {
|
|
19
|
+
const currentTab = currentTabs[index];
|
|
20
|
+
if (!currentTab) return false;
|
|
21
|
+
return tab.val === currentTab.val && tab.pos === currentTab.pos && tab.leader === currentTab.leader;
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export { continuesNumberedSequence, continuesTabbedParagraphSequence };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ParagraphBlock } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/layout-engine/paragraphSpacing.d.ts
|
|
4
|
+
/** Whether a paragraph has no visible run content. */
|
|
5
|
+
declare function isEmptyParagraph(block: ParagraphBlock): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Resolve leading paragraph spacing after applying the empty-paragraph
|
|
8
|
+
* inherited-spacing collapse rule.
|
|
9
|
+
*/
|
|
10
|
+
declare function getParagraphSpacingBefore(block: ParagraphBlock): number;
|
|
11
|
+
/**
|
|
12
|
+
* Resolve trailing paragraph spacing after applying the empty-paragraph
|
|
13
|
+
* inherited-spacing collapse rule.
|
|
14
|
+
*/
|
|
15
|
+
declare function getParagraphSpacingAfter(block: ParagraphBlock): number;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/layout-engine/paragraphSpacing.ts
|
|
2
|
+
/** Whether a paragraph has no visible run content. */
|
|
3
|
+
function isEmptyParagraph(block) {
|
|
4
|
+
if (block.runs.length === 0) return true;
|
|
5
|
+
if (block.runs.length !== 1) return false;
|
|
6
|
+
const run = block.runs.at(0);
|
|
7
|
+
return run?.kind === "text" && run.text === "";
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Resolve leading paragraph spacing after applying the empty-paragraph
|
|
11
|
+
* inherited-spacing collapse rule.
|
|
12
|
+
*/
|
|
13
|
+
function getParagraphSpacingBefore(block) {
|
|
14
|
+
const value = block.attrs?.spacing?.before ?? 0;
|
|
15
|
+
if (value === 0) return 0;
|
|
16
|
+
if (isEmptyParagraph(block) && !block.attrs?.styleId && !block.attrs?.hasDirectParagraphFormatting && !block.attrs?.hasDirectParagraphMarkFormatting && !block.attrs?.spacingExplicit?.before) return 0;
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve trailing paragraph spacing after applying the empty-paragraph
|
|
21
|
+
* inherited-spacing collapse rule.
|
|
22
|
+
*/
|
|
23
|
+
function getParagraphSpacingAfter(block) {
|
|
24
|
+
const value = block.attrs?.spacing?.after ?? 0;
|
|
25
|
+
if (value === 0) return 0;
|
|
26
|
+
if (isEmptyParagraph(block) && !block.attrs?.styleId && !block.attrs?.hasDirectParagraphFormatting && !block.attrs?.hasDirectParagraphMarkFormatting && !block.attrs?.spacingExplicit?.after) return 0;
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { FlowBlock, Page } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/layout-engine/renderedBreakReconciliation.d.ts
|
|
4
|
+
type RenderedBreakState = {
|
|
5
|
+
type: "noPageAdvance";
|
|
6
|
+
} | {
|
|
7
|
+
type: "pageAdvance";
|
|
8
|
+
reason: "ordinary" | "reflowBoundary";
|
|
9
|
+
};
|
|
10
|
+
declare const INITIAL_RENDERED_BREAK_STATE: RenderedBreakState;
|
|
11
|
+
type ReconcileBeforeBlockOptions = {
|
|
12
|
+
state: RenderedBreakState;
|
|
13
|
+
block: FlowBlock;
|
|
14
|
+
previousBlock: FlowBlock | undefined;
|
|
15
|
+
page: Page;
|
|
16
|
+
blocksById: ReadonlyMap<string, FlowBlock>;
|
|
17
|
+
hasExplicitPageBreak: boolean;
|
|
18
|
+
renderedBreakNeedsSnap: boolean;
|
|
19
|
+
};
|
|
20
|
+
type BreakDecision = {
|
|
21
|
+
forcePageBreak: boolean;
|
|
22
|
+
state: RenderedBreakState;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Reconcile an authored or cached break before laying out one block.
|
|
26
|
+
*
|
|
27
|
+
* Cached markers describe a boundary at a paragraph, not an ordinal page.
|
|
28
|
+
* Natural reflow may therefore satisfy the marker before layout reaches it.
|
|
29
|
+
* Every marker consumes the accumulated advance state so later markers make
|
|
30
|
+
* independent decisions.
|
|
31
|
+
*/
|
|
32
|
+
declare const reconcileBreakBeforeBlock: ({
|
|
33
|
+
state,
|
|
34
|
+
block,
|
|
35
|
+
previousBlock,
|
|
36
|
+
page,
|
|
37
|
+
blocksById,
|
|
38
|
+
hasExplicitPageBreak,
|
|
39
|
+
renderedBreakNeedsSnap
|
|
40
|
+
}: ReconcileBeforeBlockOptions) => BreakDecision;
|
|
41
|
+
declare const recordReflowBoundary: (state: RenderedBreakState, crossedPageBoundary: boolean) => RenderedBreakState;
|
|
42
|
+
type ReconcileAfterBlockOptions = {
|
|
43
|
+
state: RenderedBreakState;
|
|
44
|
+
block: FlowBlock;
|
|
45
|
+
pageNumberBefore: number;
|
|
46
|
+
pageNumberAfter: number;
|
|
47
|
+
previousPage: Page | undefined;
|
|
48
|
+
};
|
|
49
|
+
/** Record page movement caused by laying out a block, or reset at a structural break. */
|
|
50
|
+
declare const reconcileAfterBlock: ({
|
|
51
|
+
state,
|
|
52
|
+
block,
|
|
53
|
+
pageNumberBefore,
|
|
54
|
+
pageNumberAfter,
|
|
55
|
+
previousPage
|
|
56
|
+
}: ReconcileAfterBlockOptions) => RenderedBreakState;
|
|
57
|
+
//#endregion
|
|
58
|
+
export { INITIAL_RENDERED_BREAK_STATE, RenderedBreakState, reconcileAfterBlock, reconcileBreakBeforeBlock, recordReflowBoundary };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { isEmptyParagraph } from "./paragraphSpacing.js";
|
|
2
|
+
import { continuesNumberedSequence, continuesTabbedParagraphSequence } from "./paragraphSequence.js";
|
|
3
|
+
//#region src/layout-engine/renderedBreakReconciliation.ts
|
|
4
|
+
const INITIAL_RENDERED_BREAK_STATE = { type: "noPageAdvance" };
|
|
5
|
+
/**
|
|
6
|
+
* Reconcile an authored or cached break before laying out one block.
|
|
7
|
+
*
|
|
8
|
+
* Cached markers describe a boundary at a paragraph, not an ordinal page.
|
|
9
|
+
* Natural reflow may therefore satisfy the marker before layout reaches it.
|
|
10
|
+
* Every marker consumes the accumulated advance state so later markers make
|
|
11
|
+
* independent decisions.
|
|
12
|
+
*/
|
|
13
|
+
const reconcileBreakBeforeBlock = ({ state, block, previousBlock, page, blocksById, hasExplicitPageBreak, renderedBreakNeedsSnap }) => {
|
|
14
|
+
if (hasExplicitPageBreak) return {
|
|
15
|
+
forcePageBreak: true,
|
|
16
|
+
state: INITIAL_RENDERED_BREAK_STATE
|
|
17
|
+
};
|
|
18
|
+
if (block.kind !== "paragraph" || block.attrs?.renderedPageBreakBefore !== true) return {
|
|
19
|
+
forcePageBreak: false,
|
|
20
|
+
state
|
|
21
|
+
};
|
|
22
|
+
const markerAlreadySatisfied = pageStartsWithPreviousParagraphContinuation(page, previousBlock) || state.type === "pageAdvance" && state.reason === "reflowBoundary" || state.type === "pageAdvance" && isPaginationEmptyParagraph(block) || state.type === "pageAdvance" && (continuesNumberedSequence(previousBlock, block) || continuesTabbedParagraphSequence(previousBlock, block));
|
|
23
|
+
return {
|
|
24
|
+
forcePageBreak: renderedBreakNeedsSnap && !markerAlreadySatisfied && pageHasVisibleBodyContent(page, blocksById),
|
|
25
|
+
state: INITIAL_RENDERED_BREAK_STATE
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
const recordReflowBoundary = (state, crossedPageBoundary) => crossedPageBoundary ? {
|
|
29
|
+
type: "pageAdvance",
|
|
30
|
+
reason: "reflowBoundary"
|
|
31
|
+
} : state;
|
|
32
|
+
/** Record page movement caused by laying out a block, or reset at a structural break. */
|
|
33
|
+
const reconcileAfterBlock = ({ state, block, pageNumberBefore, pageNumberAfter, previousPage }) => {
|
|
34
|
+
if (isStructuralBreak(block)) return INITIAL_RENDERED_BREAK_STATE;
|
|
35
|
+
if (!isVisibleBodyBlock(block) || pageNumberAfter <= pageNumberBefore) return state;
|
|
36
|
+
return {
|
|
37
|
+
type: "pageAdvance",
|
|
38
|
+
reason: previousPage?.fragments.some(({ blockId }) => blockId === block.id) ? "reflowBoundary" : "ordinary"
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
const isPaginationEmptyParagraph = (block) => block.runs.every((run) => {
|
|
42
|
+
if (run.kind === "text") return run.text.trim().length === 0;
|
|
43
|
+
return run.kind === "tab" || run.kind === "lineBreak";
|
|
44
|
+
});
|
|
45
|
+
const pageHasVisibleBodyContent = (page, blocksById) => {
|
|
46
|
+
for (const fragment of page.fragments) {
|
|
47
|
+
if (fragment.kind !== "paragraph") return true;
|
|
48
|
+
const block = blocksById.get(String(fragment.blockId));
|
|
49
|
+
if (block?.kind !== "paragraph" || !isPaginationEmptyParagraph(block)) return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
};
|
|
53
|
+
const pageStartsWithPreviousParagraphContinuation = (page, previousBlock) => {
|
|
54
|
+
if (previousBlock?.kind !== "paragraph") return false;
|
|
55
|
+
return page.fragments.some((fragment) => fragment.kind === "paragraph" && String(fragment.blockId) === String(previousBlock.id) && fragment.continuesFromPrev === true);
|
|
56
|
+
};
|
|
57
|
+
const isStructuralBreak = (block) => block.kind === "pageBreak" || block.kind === "columnBreak" || block.kind === "sectionBreak";
|
|
58
|
+
const isVisibleBodyBlock = (block) => {
|
|
59
|
+
if (block.kind === "paragraph") return !isEmptyParagraph(block);
|
|
60
|
+
return !isStructuralBreak(block);
|
|
61
|
+
};
|
|
62
|
+
//#endregion
|
|
63
|
+
export { INITIAL_RENDERED_BREAK_STATE, reconcileAfterBlock, reconcileBreakBeforeBlock, recordReflowBoundary };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { OutlineStyleAttr } from "../types/documentEnumValues.js";
|
|
2
|
-
import { ImagePosition, ImageWrap, SdtProperties, SdtType, TableWidthType } from "@stll/docx-core/model";
|
|
2
|
+
import { ImagePosition, ImageWrap, SdtProperties, SdtType, ShapeTextBody, TableWidthType } from "@stll/docx-core/model";
|
|
3
3
|
|
|
4
4
|
//#region src/layout-engine/types.d.ts
|
|
5
5
|
declare namespace types_d_exports {
|
|
6
|
-
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, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned };
|
|
6
|
+
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, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned };
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* Unique identifier for a block in the document.
|
|
@@ -310,7 +310,8 @@ type ParagraphAttrs = {
|
|
|
310
310
|
* source `w:pPr`. Word treats such a blank as authored and keeps its
|
|
311
311
|
* inherited spacing; a bare empty paragraph still collapses that spacing.
|
|
312
312
|
*/
|
|
313
|
-
hasDirectParagraphFormatting?: boolean;
|
|
313
|
+
hasDirectParagraphFormatting?: boolean; /** Whether an empty paragraph carries direct formatting on its paragraph mark. */
|
|
314
|
+
hasDirectParagraphMarkFormatting?: boolean;
|
|
314
315
|
indent?: ParagraphIndent;
|
|
315
316
|
keepNext?: boolean;
|
|
316
317
|
keepLines?: boolean;
|
|
@@ -337,7 +338,8 @@ type ParagraphAttrs = {
|
|
|
337
338
|
listIsBullet?: boolean;
|
|
338
339
|
listMarkerHidden?: boolean;
|
|
339
340
|
listMarkerFontFamily?: string;
|
|
340
|
-
listMarkerFontSize?: number;
|
|
341
|
+
listMarkerFontSize?: number; /** Horizontal alignment of the marker around the paragraph's list anchor. */
|
|
342
|
+
listMarkerAlignment?: "left" | "center" | "right";
|
|
341
343
|
/**
|
|
342
344
|
* `w:suff` (§17.9.25) — what follows the marker before body text.
|
|
343
345
|
* `tab` (default) grows the marker to the next tab stop; `space` adds
|
|
@@ -443,6 +445,8 @@ type TableCell = {
|
|
|
443
445
|
type TableRow = {
|
|
444
446
|
id: BlockId;
|
|
445
447
|
cells: TableCell[];
|
|
448
|
+
gridBefore?: number;
|
|
449
|
+
gridAfter?: number;
|
|
446
450
|
height?: number;
|
|
447
451
|
heightRule?: "auto" | "atLeast" | "exact";
|
|
448
452
|
isHeader?: boolean;
|
|
@@ -582,7 +586,8 @@ type TextBoxBlock = {
|
|
|
582
586
|
kind: "textBox";
|
|
583
587
|
id: BlockId; /** Width in pixels */
|
|
584
588
|
width: number; /** Height in pixels (may be auto-calculated) */
|
|
585
|
-
height?: number; /**
|
|
589
|
+
height?: number; /** Text fitting behavior */
|
|
590
|
+
autoFit?: ShapeTextBody["autoFit"]; /** Fill/background color */
|
|
586
591
|
fillColor?: string; /** Border width in pixels */
|
|
587
592
|
outlineWidth?: number; /** Border color */
|
|
588
593
|
outlineColor?: string; /** Outline dash style, or `"none"` for an explicit no-outline. */
|
|
@@ -913,7 +918,8 @@ type LayoutOptions = {
|
|
|
913
918
|
* overflowing first-page header without forcing pages 2+ to inherit the
|
|
914
919
|
* same extension.
|
|
915
920
|
*/
|
|
916
|
-
firstPageMargins?: PageMargins; /**
|
|
921
|
+
firstPageMargins?: PageMargins; /** Per-section body margins used on even section pages. */
|
|
922
|
+
sectionEvenPageMargins?: (PageMargins | undefined)[]; /** Body-level final section page size. */
|
|
917
923
|
finalPageSize?: {
|
|
918
924
|
w: number;
|
|
919
925
|
h: number;
|
|
@@ -1016,7 +1022,8 @@ type HeaderFooterContent = {
|
|
|
1016
1022
|
/**
|
|
1017
1023
|
* `true` when the image run is anchored at page-level coordinates instead of
|
|
1018
1024
|
* participating in inline flow. Covers the OOXML floating wrap types
|
|
1019
|
-
* (`square`, `tight`, `through`, `behind`, `inFront`)
|
|
1025
|
+
* (`square`, `tight`, `through`, `behind`, `inFront`), explicitly positioned
|
|
1026
|
+
* `topAndBottom` artwork, and the legacy
|
|
1020
1027
|
* `displayMode === "float"` escape hatch used by ProseMirror nodes that
|
|
1021
1028
|
* pre-date the wrap-type round-trip.
|
|
1022
1029
|
*
|
|
@@ -1067,5 +1074,7 @@ declare function floatingTextBoxReservesBand(block: TextBoxFlowAttrs): boolean;
|
|
|
1067
1074
|
* and the painter both consult this so their line count and `white-space` agree.
|
|
1068
1075
|
*/
|
|
1069
1076
|
declare function tableColumnsArePinned(table: TableBlock): boolean;
|
|
1077
|
+
/** Return the leading visual offset for a row with omitted grid columns. */
|
|
1078
|
+
declare const getTableRowLeadingWidth: (row: TableRow, columnWidths: readonly number[]) => number;
|
|
1070
1079
|
//#endregion
|
|
1071
|
-
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, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned, types_d_exports };
|
|
1080
|
+
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, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned, types_d_exports };
|
|
@@ -9,6 +9,7 @@ var types_exports = /* @__PURE__ */ __exportAll({
|
|
|
9
9
|
FOOTNOTE_SEPARATOR_HEIGHT: () => 12,
|
|
10
10
|
floatingTextBoxReservesBand: () => floatingTextBoxReservesBand,
|
|
11
11
|
floatingTextBoxWrapsText: () => floatingTextBoxWrapsText,
|
|
12
|
+
getTableRowLeadingWidth: () => getTableRowLeadingWidth,
|
|
12
13
|
isFloatingImageRun: () => isFloatingImageRun,
|
|
13
14
|
isFloatingTextBoxBlock: () => isFloatingTextBoxBlock,
|
|
14
15
|
isTextWrappingFloatingImageRun: () => isTextWrappingFloatingImageRun,
|
|
@@ -46,7 +47,8 @@ const FOOTNOTE_FALLBACK_LINE_HEIGHT = 13;
|
|
|
46
47
|
/**
|
|
47
48
|
* `true` when the image run is anchored at page-level coordinates instead of
|
|
48
49
|
* participating in inline flow. Covers the OOXML floating wrap types
|
|
49
|
-
* (`square`, `tight`, `through`, `behind`, `inFront`)
|
|
50
|
+
* (`square`, `tight`, `through`, `behind`, `inFront`), explicitly positioned
|
|
51
|
+
* `topAndBottom` artwork, and the legacy
|
|
50
52
|
* `displayMode === "float"` escape hatch used by ProseMirror nodes that
|
|
51
53
|
* pre-date the wrap-type round-trip.
|
|
52
54
|
*
|
|
@@ -55,6 +57,7 @@ const FOOTNOTE_FALLBACK_LINE_HEIGHT = 13;
|
|
|
55
57
|
*/
|
|
56
58
|
const isFloatingImageRun = (run) => {
|
|
57
59
|
const wrapType = run.wrapType;
|
|
60
|
+
if (wrapType === "topAndBottom" && run.position !== void 0) return true;
|
|
58
61
|
if (wrapType === "square" || wrapType === "tight" || wrapType === "through" || wrapType === "behind" || wrapType === "inFront") return true;
|
|
59
62
|
return run.displayMode === "float";
|
|
60
63
|
};
|
|
@@ -114,5 +117,15 @@ function tableColumnsArePinned(table) {
|
|
|
114
117
|
if (table.layout === "fixed") return true;
|
|
115
118
|
return table.widthType === "dxa" || table.widthType === "pct";
|
|
116
119
|
}
|
|
120
|
+
const tableRowOwnsNestedGrid = (row) => {
|
|
121
|
+
const cell = row.cells.length === 1 ? row.cells.at(0) : void 0;
|
|
122
|
+
if (!cell?.blocks.some((block) => block.kind === "table")) return false;
|
|
123
|
+
return cell.blocks.every((block) => block.kind === "table" || block.kind === "paragraph" && block.runs.length === 0);
|
|
124
|
+
};
|
|
125
|
+
/** Return the leading visual offset for a row with omitted grid columns. */
|
|
126
|
+
const getTableRowLeadingWidth = (row, columnWidths) => {
|
|
127
|
+
if (tableRowOwnsNestedGrid(row)) return 0;
|
|
128
|
+
return columnWidths.slice(0, row.gridBefore ?? 0).reduce((sum, columnWidth) => sum + columnWidth, 0);
|
|
129
|
+
};
|
|
117
130
|
//#endregion
|
|
118
|
-
export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned, types_exports };
|
|
131
|
+
export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned, types_exports };
|
|
@@ -1188,12 +1188,12 @@ function applySectionHeaderFooterOptions(page, pageOptions, options) {
|
|
|
1188
1188
|
const useEven = refs.evenAndOddHeaders === true && sectionPageNumber % 2 === 0;
|
|
1189
1189
|
const headerRId = (() => {
|
|
1190
1190
|
if (useFirst) return refs.headerFirst;
|
|
1191
|
-
if (useEven
|
|
1191
|
+
if (useEven) return refs.headerEven;
|
|
1192
1192
|
return refs.headerDefault;
|
|
1193
1193
|
})();
|
|
1194
1194
|
const footerRId = (() => {
|
|
1195
1195
|
if (useFirst) return refs.footerFirst;
|
|
1196
|
-
if (useEven
|
|
1196
|
+
if (useEven) return refs.footerEven;
|
|
1197
1197
|
return refs.footerDefault;
|
|
1198
1198
|
})();
|
|
1199
1199
|
if (headerRId) {
|
|
@@ -5,7 +5,7 @@ import { calculateTabWidth } from "../prosemirror/utils/tabCalculator.js";
|
|
|
5
5
|
import { inlineImageBoundingBox, parseRotationDegrees, rotatedBoundingBox } from "../utils/rotationBoundingBox.js";
|
|
6
6
|
import { hasCjk, segmentByScript } from "../utils/scriptSegments.js";
|
|
7
7
|
import { isFloatingImageRun } from "../layout-engine/types.js";
|
|
8
|
-
import { getListMarkerInlineWidth } from "../layout-engine/measure/listMarkerWidth.js";
|
|
8
|
+
import { getListMarkerInlineWidth, getListMarkerVisualOffset } from "../layout-engine/measure/listMarkerWidth.js";
|
|
9
9
|
import { applySdtDataAttrs } from "./sdtBoundary.js";
|
|
10
10
|
import { applyImageVisualAttrs, hasImageVisualAttrs, wrapImageWithCrop } from "./renderImage.js";
|
|
11
11
|
import { ommlToMathml } from "../docx/mathToMathml.js";
|
|
@@ -1351,7 +1351,7 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1351
1351
|
}
|
|
1352
1352
|
const markerFontFamily = block.attrs.listMarkerFontFamily ?? firstTextRun?.fontFamily ?? block.attrs.defaultFontFamily;
|
|
1353
1353
|
const markerFontSize = block.attrs.listMarkerFontSize ?? firstTextRun?.fontSize ?? block.attrs.defaultFontSize;
|
|
1354
|
-
const marker = renderListMarker(block.attrs.listMarker, getListMarkerInlineWidth(block), doc, markerFontFamily, markerFontSize, block.attrs.listMarkerRevision, block.attrs.listMarkerSecondSlotOffsetTwips);
|
|
1354
|
+
const marker = renderListMarker(block.attrs.listMarker, getListMarkerInlineWidth(block), getListMarkerVisualOffset(block), doc, markerFontFamily, markerFontSize, block.attrs.listMarkerRevision, block.attrs.listMarkerSecondSlotOffsetTwips);
|
|
1355
1355
|
const markerMarginLeft = markerStart - Math.min(indentLeft, 0);
|
|
1356
1356
|
if (markerMarginLeft < 0) marker.style.marginLeft = `${markerMarginLeft}px`;
|
|
1357
1357
|
lineEl.prepend(marker);
|
|
@@ -1362,13 +1362,13 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1362
1362
|
}
|
|
1363
1363
|
/**
|
|
1364
1364
|
* Render a list marker element as an inline-block at the start of the first
|
|
1365
|
-
* body line. `
|
|
1365
|
+
* body line. `inlineWidth` (from `getListMarkerInlineWidth`) sizes the marker
|
|
1366
1366
|
* so the body text aligns at the next tab stop per ECMA-376 §17.9.25 —
|
|
1367
1367
|
* this honours `w:suff` (`tab` / `space` / `nothing`) and the document's
|
|
1368
1368
|
* tab grid. Long markers like "1.1.1." therefore grow to the next stop
|
|
1369
1369
|
* instead of butting against the body text.
|
|
1370
1370
|
*/
|
|
1371
|
-
function renderListMarker(marker,
|
|
1371
|
+
function renderListMarker(marker, inlineWidth, visualOffset, doc, fontFamily, fontSize, revision, secondSlotOffsetTwips) {
|
|
1372
1372
|
const span = doc.createElement("span");
|
|
1373
1373
|
span.className = "layout-list-marker";
|
|
1374
1374
|
span.style.display = "inline-block";
|
|
@@ -1377,7 +1377,8 @@ function renderListMarker(marker, minWidth, doc, fontFamily, fontSize, revision,
|
|
|
1377
1377
|
span.style.textAlign = "left";
|
|
1378
1378
|
span.style.textAlignLast = "left";
|
|
1379
1379
|
span.style.boxSizing = "border-box";
|
|
1380
|
-
|
|
1380
|
+
span.style.width = `${inlineWidth}px`;
|
|
1381
|
+
if (visualOffset !== 0) span.style.transform = `translateX(${visualOffset}px)`;
|
|
1381
1382
|
if (revision) {
|
|
1382
1383
|
const authorIdx = getAuthorColorIdx(revision.author ?? "");
|
|
1383
1384
|
const authorColor = AUTHOR_COLORS[authorIdx];
|