@stll/folio-core 0.29.0 → 0.30.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/controller/folioEditor.d.ts +28 -1
- package/dist/controller/folioEditor.js +37 -26
- package/dist/controller/fontReadiness.js +28 -24
- package/dist/controller/hiddenEditorManager.d.ts +2 -7
- package/dist/controller/layoutPipeline.js +33 -3
- package/dist/controller/noteEditorManager.d.ts +3 -6
- package/dist/docx/metafileSvg.d.ts +12 -0
- package/dist/docx/metafileSvg.js +462 -0
- package/dist/docx/paragraphTextBoxEnrichment.js +55 -4
- package/dist/docx/parser.d.ts +2 -2
- package/dist/docx/parser.js +26 -0
- package/dist/docx/runParser.js +2 -1
- package/dist/docx/serializer/runSerializer.js +14 -2
- package/dist/docx/styleParser.js +2 -1
- package/dist/docx/textBoxParser.js +14 -0
- package/dist/docx/vmlImageParser.js +72 -152
- package/dist/docx/vmlPreview.d.ts +31 -0
- package/dist/docx/vmlPreview.js +535 -0
- package/dist/docx/wrapTypes.d.ts +2 -36
- package/dist/docx/wrapTypes.js +1 -39
- package/dist/fonts/fontAlternates.d.ts +8 -0
- package/dist/fonts/fontAlternates.js +16 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +34 -3
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +5 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -0
- package/dist/layout-bridge/convert/toFlowBlocks.js +78 -28
- package/dist/layout-bridge/dom/noteStoryDom.d.ts +1 -1
- package/dist/layout-bridge/engine/selectionRects.js +3 -1
- package/dist/layout-engine/index.js +65 -32
- package/dist/layout-engine/justifiedLineFit.d.ts +7 -0
- package/dist/layout-engine/justifiedLineFit.js +6 -0
- package/dist/layout-engine/keep-together.js +15 -10
- package/dist/layout-engine/measure/complexScriptFormatting.d.ts +6 -1
- package/dist/layout-engine/measure/complexScriptFormatting.js +11 -2
- package/dist/layout-engine/measure/floatingTablePosition.d.ts +16 -1
- package/dist/layout-engine/measure/floatingTablePosition.js +29 -6
- package/dist/layout-engine/measure/lineBreakProvider.js +67 -8
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +1 -0
- package/dist/layout-engine/measure/listMarkerWidth.js +23 -10
- package/dist/layout-engine/measure/measureBlocks.d.ts +15 -0
- package/dist/layout-engine/measure/measureBlocks.js +197 -30
- package/dist/layout-engine/measure/measureContainer.js +20 -24
- package/dist/layout-engine/measure/measureHelpers.d.ts +1 -1
- package/dist/layout-engine/measure/measureHelpers.js +14 -8
- package/dist/layout-engine/measure/measureParagraph.js +111 -37
- package/dist/layout-engine/measure/measureTypes.d.ts +3 -0
- package/dist/layout-engine/measure/tabCalculator.d.ts +123 -0
- package/dist/layout-engine/measure/tabCalculator.js +144 -0
- package/dist/layout-engine/measure/tableCellFlow.d.ts +21 -1
- package/dist/layout-engine/measure/tableCellFlow.js +45 -8
- package/dist/layout-engine/measure/tableInlinePlacement.d.ts +10 -2
- package/dist/layout-engine/measure/tableInlinePlacement.js +17 -15
- package/dist/layout-engine/paginator.d.ts +26 -2
- package/dist/layout-engine/paginator.js +76 -14
- package/dist/layout-engine/paragraphSpacing.d.ts +3 -1
- package/dist/layout-engine/paragraphSpacing.js +4 -3
- package/dist/layout-engine/renderedBreakReconciliation.d.ts +14 -3
- package/dist/layout-engine/renderedBreakReconciliation.js +45 -14
- package/dist/layout-engine/section-breaks.d.ts +3 -1
- package/dist/layout-engine/section-breaks.js +6 -2
- package/dist/layout-engine/types.d.ts +18 -1
- package/dist/layout-engine/types.js +1 -1
- package/dist/layout-painter/imageLayout.d.ts +1 -1
- package/dist/layout-painter/renderPage.js +6 -1
- package/dist/layout-painter/renderParagraph.js +169 -82
- package/dist/layout-painter/renderTable.d.ts +1 -1
- package/dist/layout-painter/renderTable.js +18 -7
- package/dist/layout-painter/renderTextBox.d.ts +1 -1
- package/dist/layout-painter/renderTextBox.js +6 -1
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/paged-layout/sectionBlockWidths.d.ts +14 -0
- package/dist/paged-layout/sectionBlockWidths.js +103 -14
- package/dist/prosemirror/attrs/index.js +12 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +6 -2
- package/dist/prosemirror/conversion/toProseDoc.js +10 -3
- package/dist/prosemirror/extensions/marks/CharacterSpacingExtension.js +9 -5
- package/dist/prosemirror/extensions/nodes/TableExtension.js +2 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +6 -2
- package/dist/prosemirror/schema/nodes.d.ts +4 -0
- package/dist/prosemirror/utils/tabCalculator.d.ts +2 -123
- package/dist/prosemirror/utils/tabCalculator.js +1 -140
- package/dist/render-dom/RemoteSelectionOverlay.d.ts +4 -4
- package/dist/types/documentEnumValues.d.ts +4 -1
- package/dist/types/documentEnumValues.js +12 -1
- package/dist/types/editor-story.d.ts +8 -0
- package/dist/types/editor-story.js +0 -0
- package/dist/types/remote-selection.d.ts +11 -0
- package/dist/types/remote-selection.js +0 -0
- package/dist/types/wrap.d.ts +36 -0
- package/dist/types/wrap.js +40 -0
- package/dist/utils/fontResolver.d.ts +1 -1
- package/dist/utils/fontResolver.js +42 -2
- package/dist/utils/formatToStyle.js +4 -2
- package/dist/utils/horizontalScale.d.ts +28 -0
- package/dist/utils/horizontalScale.js +42 -0
- package/package.json +2 -2
|
@@ -2,12 +2,33 @@ import { measuredLineRangeHeight } from "../lineFlow.js";
|
|
|
2
2
|
import { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph } from "../paragraphSpacing.js";
|
|
3
3
|
import { isFloatingTextBoxBlock } from "../types.js";
|
|
4
4
|
//#region src/layout-engine/measure/tableCellFlow.ts
|
|
5
|
+
const TABLE_CELL_FLOW_POSITION = {
|
|
6
|
+
start: "start",
|
|
7
|
+
interior: "interior"
|
|
8
|
+
};
|
|
9
|
+
const TABLE_CELL_TRAILING_SPACING_TYPE = {
|
|
10
|
+
none: "none",
|
|
11
|
+
authored: "authored",
|
|
12
|
+
automatic: "automatic"
|
|
13
|
+
};
|
|
5
14
|
const createTableCellFlowState = () => ({
|
|
6
15
|
height: 0,
|
|
16
|
+
position: TABLE_CELL_FLOW_POSITION.start,
|
|
7
17
|
previousParagraphWasEmpty: false,
|
|
8
|
-
trailingSpacing:
|
|
18
|
+
trailingSpacing: { type: TABLE_CELL_TRAILING_SPACING_TYPE.none }
|
|
9
19
|
});
|
|
10
|
-
const
|
|
20
|
+
const trailingSpacingValue = (spacing) => {
|
|
21
|
+
switch (spacing.type) {
|
|
22
|
+
case TABLE_CELL_TRAILING_SPACING_TYPE.none: return 0;
|
|
23
|
+
case TABLE_CELL_TRAILING_SPACING_TYPE.authored:
|
|
24
|
+
case TABLE_CELL_TRAILING_SPACING_TYPE.automatic: return spacing.value;
|
|
25
|
+
default: return spacing;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const finishTableCellFlow = (state) => {
|
|
29
|
+
if (state.trailingSpacing.type === TABLE_CELL_TRAILING_SPACING_TYPE.automatic) return state.height;
|
|
30
|
+
return state.height + trailingSpacingValue(state.trailingSpacing);
|
|
31
|
+
};
|
|
11
32
|
const isSuppressedParagraphMeasure = (measure) => measure.totalHeight === 0 && measure.lines.every(({ lineHeight }) => lineHeight === 0);
|
|
12
33
|
const paragraphSpacing = (block, measure) => {
|
|
13
34
|
if (isSuppressedParagraphMeasure(measure)) return {
|
|
@@ -42,12 +63,19 @@ const placeTableCellBlock = (state, block, measure) => {
|
|
|
42
63
|
contentHeight: 0,
|
|
43
64
|
leadingSpacing: 0
|
|
44
65
|
};
|
|
66
|
+
if (block.kind === "textBox" && isFloatingTextBoxBlock(block)) return {
|
|
67
|
+
top,
|
|
68
|
+
contentTop: top,
|
|
69
|
+
contentHeight: 0,
|
|
70
|
+
leadingSpacing: 0
|
|
71
|
+
};
|
|
45
72
|
if (block.kind !== "paragraph" || measure.kind !== "paragraph") {
|
|
46
|
-
const leadingSpacing = state.trailingSpacing;
|
|
73
|
+
const leadingSpacing = trailingSpacingValue(state.trailingSpacing);
|
|
47
74
|
const contentTop = top + leadingSpacing;
|
|
48
75
|
state.height = contentTop + contentHeight;
|
|
76
|
+
state.position = TABLE_CELL_FLOW_POSITION.interior;
|
|
49
77
|
state.previousParagraphWasEmpty = false;
|
|
50
|
-
state.trailingSpacing =
|
|
78
|
+
state.trailingSpacing = { type: TABLE_CELL_TRAILING_SPACING_TYPE.none };
|
|
51
79
|
return {
|
|
52
80
|
top,
|
|
53
81
|
contentTop,
|
|
@@ -57,14 +85,23 @@ const placeTableCellBlock = (state, block, measure) => {
|
|
|
57
85
|
}
|
|
58
86
|
const spacing = paragraphSpacing(block, measure);
|
|
59
87
|
const empty = isEmptyParagraph(block);
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
88
|
+
const before = state.position === TABLE_CELL_FLOW_POSITION.start && block.attrs?.automaticSpacing?.before === true ? 0 : spacing.before;
|
|
89
|
+
const trailingSpacing = trailingSpacingValue(state.trailingSpacing);
|
|
90
|
+
const leadingSpacing = empty && state.previousParagraphWasEmpty ? before + trailingSpacing : collapseParagraphSpacing({
|
|
91
|
+
before,
|
|
92
|
+
after: trailingSpacing
|
|
63
93
|
});
|
|
64
94
|
const contentTop = top + leadingSpacing;
|
|
65
95
|
state.height = contentTop + contentHeight;
|
|
96
|
+
state.position = TABLE_CELL_FLOW_POSITION.interior;
|
|
66
97
|
state.previousParagraphWasEmpty = empty;
|
|
67
|
-
state.trailingSpacing =
|
|
98
|
+
state.trailingSpacing = block.attrs?.automaticSpacing?.after === true ? {
|
|
99
|
+
type: TABLE_CELL_TRAILING_SPACING_TYPE.automatic,
|
|
100
|
+
value: spacing.after
|
|
101
|
+
} : {
|
|
102
|
+
type: TABLE_CELL_TRAILING_SPACING_TYPE.authored,
|
|
103
|
+
value: spacing.after
|
|
104
|
+
};
|
|
68
105
|
return {
|
|
69
106
|
top,
|
|
70
107
|
contentTop,
|
|
@@ -7,6 +7,14 @@ type TableInlinePlacement = {
|
|
|
7
7
|
offset: number;
|
|
8
8
|
};
|
|
9
9
|
/** Resolve an inline table's horizontal anchor without losing RTL leading-edge semantics. */
|
|
10
|
-
declare const resolveTableInlinePlacement: (table: Pick<TableBlock, "bidi" | "indent" | "justification"
|
|
10
|
+
declare const resolveTableInlinePlacement: (table: Pick<TableBlock, "bidi" | "indent" | "justification">, rowJustification?: TableBlock["justification"]) => TableInlinePlacement;
|
|
11
|
+
type ResolveTableInlineOffsetOptions = {
|
|
12
|
+
table: Pick<TableBlock, "bidi" | "indent" | "justification">;
|
|
13
|
+
rowJustification?: TableBlock["justification"];
|
|
14
|
+
frameWidth: number;
|
|
15
|
+
tableWidth: number;
|
|
16
|
+
};
|
|
17
|
+
/** Resolve a table's physical inline offset within its current content frame. */
|
|
18
|
+
declare const resolveTableInlineOffset: ({ table, rowJustification, frameWidth, tableWidth }: ResolveTableInlineOffsetOptions) => number;
|
|
11
19
|
//#endregion
|
|
12
|
-
export { resolveTableInlinePlacement };
|
|
20
|
+
export { resolveTableInlineOffset, resolveTableInlinePlacement };
|
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
import { resolveTableCellPadding } from "../types.js";
|
|
2
1
|
//#region src/layout-engine/measure/tableInlinePlacement.ts
|
|
3
2
|
/** Resolve an inline table's horizontal anchor without losing RTL leading-edge semantics. */
|
|
4
|
-
const resolveTableInlinePlacement = (table) => {
|
|
5
|
-
|
|
6
|
-
if (
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const firstCellPadding = firstCell ? resolveTableCellPadding(firstCell) : void 0;
|
|
12
|
-
if (table.justification === "left" || table.bidi !== true) return {
|
|
13
|
-
alignment: "left",
|
|
14
|
-
offset: table.indent ?? -(firstCellPadding?.left ?? 0)
|
|
3
|
+
const resolveTableInlinePlacement = (table, rowJustification) => {
|
|
4
|
+
const logicalJustification = rowJustification ?? table.justification ?? "left";
|
|
5
|
+
if (logicalJustification === "center") return { alignment: "center" };
|
|
6
|
+
const offset = logicalJustification === "left" ? table.indent ?? 0 : 0;
|
|
7
|
+
if (table.bidi !== true) return {
|
|
8
|
+
alignment: logicalJustification,
|
|
9
|
+
offset
|
|
15
10
|
};
|
|
16
11
|
return {
|
|
17
|
-
alignment: "right",
|
|
18
|
-
offset
|
|
12
|
+
alignment: logicalJustification === "left" ? "right" : "left",
|
|
13
|
+
offset
|
|
19
14
|
};
|
|
20
15
|
};
|
|
16
|
+
/** Resolve a table's physical inline offset within its current content frame. */
|
|
17
|
+
const resolveTableInlineOffset = ({ table, rowJustification, frameWidth, tableWidth }) => {
|
|
18
|
+
const placement = resolveTableInlinePlacement(table, rowJustification);
|
|
19
|
+
if (placement.alignment === "center") return (frameWidth - tableWidth) / 2;
|
|
20
|
+
if (placement.alignment === "right") return frameWidth - tableWidth - placement.offset;
|
|
21
|
+
return placement.offset;
|
|
22
|
+
};
|
|
21
23
|
//#endregion
|
|
22
|
-
export { resolveTableInlinePlacement };
|
|
24
|
+
export { resolveTableInlineOffset, resolveTableInlinePlacement };
|
|
@@ -65,6 +65,28 @@ type PaginatorOptions = {
|
|
|
65
65
|
type ForcePageBreakOptions = {
|
|
66
66
|
coalesceBlankPage?: boolean;
|
|
67
67
|
};
|
|
68
|
+
declare const SECTION_START_PLACEMENT: {
|
|
69
|
+
readonly CONTINUOUS: "continuous";
|
|
70
|
+
readonly NEXT_PAGE: "nextPage";
|
|
71
|
+
};
|
|
72
|
+
type SectionStartPlacement = (typeof SECTION_START_PLACEMENT)[keyof typeof SECTION_START_PLACEMENT];
|
|
73
|
+
type StartSectionOptions = {
|
|
74
|
+
sectionIndex: number;
|
|
75
|
+
pageNumbering?: SectionPageNumbering;
|
|
76
|
+
placement?: SectionStartPlacement;
|
|
77
|
+
};
|
|
78
|
+
/** Calculate active column widths, preferring authored unequal widths. */
|
|
79
|
+
declare function calculateColumnWidths(pageWidth: number, leftMargin: number, rightMargin: number, columns: ColumnLayout): number[];
|
|
80
|
+
/** Resolve every column's absolute page X in one pass. */
|
|
81
|
+
declare function calculateColumnLefts(leftMargin: number, columnWidths: readonly number[], columns: ColumnLayout): number[];
|
|
82
|
+
type ResolveColumnLeftOptions = {
|
|
83
|
+
leftMargin: number;
|
|
84
|
+
columnWidths: readonly number[];
|
|
85
|
+
columns: ColumnLayout;
|
|
86
|
+
columnIndex: number;
|
|
87
|
+
};
|
|
88
|
+
/** Resolve an active column's absolute page X from the shared width/gap model. */
|
|
89
|
+
declare function resolveColumnLeft({ leftMargin, columnWidths, columns, columnIndex }: ResolveColumnLeftOptions): number;
|
|
68
90
|
/**
|
|
69
91
|
* Creates a paginator for managing page layout state.
|
|
70
92
|
*/
|
|
@@ -108,6 +130,8 @@ declare function createPaginator(options: PaginatorOptions): {
|
|
|
108
130
|
addFootnoteHeight: (additionalHeight: number, footnoteIds?: number[]) => void;
|
|
109
131
|
/** Force a page break. */
|
|
110
132
|
forcePageBreak: (breakOptions?: ForcePageBreakOptions) => PageState;
|
|
133
|
+
/** Reuse the current sheet while advancing authored page ordinals. */
|
|
134
|
+
coalescePageBreak: () => PageState;
|
|
111
135
|
/** Reuse an already blank current page for the active section/layout. */
|
|
112
136
|
retargetCurrentBlankPage: () => boolean;
|
|
113
137
|
/** Force a column break. */
|
|
@@ -122,8 +146,8 @@ declare function createPaginator(options: PaginatorOptions): {
|
|
|
122
146
|
h: number;
|
|
123
147
|
}, newMargins?: PageMargins, applyImmediately?: boolean) => void;
|
|
124
148
|
/** Mark the next created page as the first page of a new section. */
|
|
125
|
-
startSection: (sectionIndex
|
|
149
|
+
startSection: ({ sectionIndex, pageNumbering, placement }: StartSectionOptions) => void;
|
|
126
150
|
};
|
|
127
151
|
type Paginator = ReturnType<typeof createPaginator>;
|
|
128
152
|
//#endregion
|
|
129
|
-
export { FOOTNOTE_SEPARATOR_HEIGHT, PageState, Paginator, PaginatorOptions, createPaginator };
|
|
153
|
+
export { FOOTNOTE_SEPARATOR_HEIGHT, PageState, Paginator, PaginatorOptions, SECTION_START_PLACEMENT, calculateColumnLefts, calculateColumnWidths, createPaginator, resolveColumnLeft };
|
|
@@ -8,6 +8,10 @@ import { panic } from "better-result";
|
|
|
8
8
|
* Tracks the current page, cursor position, and available space.
|
|
9
9
|
* Creates new pages when content doesn't fit.
|
|
10
10
|
*/
|
|
11
|
+
const SECTION_START_PLACEMENT = {
|
|
12
|
+
CONTINUOUS: "continuous",
|
|
13
|
+
NEXT_PAGE: "nextPage"
|
|
14
|
+
};
|
|
11
15
|
/** Calculate active column widths, preferring authored unequal widths. */
|
|
12
16
|
function calculateColumnWidths(pageWidth, leftMargin, rightMargin, columns) {
|
|
13
17
|
if (columns.widths?.length === columns.count && columns.widths.every((width) => Number.isFinite(width) && width > 0)) return [...columns.widths];
|
|
@@ -15,6 +19,22 @@ function calculateColumnWidths(pageWidth, leftMargin, rightMargin, columns) {
|
|
|
15
19
|
return Array.from({ length: columns.count }, () => equalWidth);
|
|
16
20
|
}
|
|
17
21
|
const gapAfterColumn = (columns, columnIndex) => columns.gaps?.[columnIndex] ?? columns.gap;
|
|
22
|
+
/** Resolve every column's absolute page X in one pass. */
|
|
23
|
+
function calculateColumnLefts(leftMargin, columnWidths, columns) {
|
|
24
|
+
const lefts = [];
|
|
25
|
+
let x = leftMargin;
|
|
26
|
+
for (let index = 0; index < columnWidths.length; index++) {
|
|
27
|
+
lefts.push(x);
|
|
28
|
+
x += (columnWidths[index] ?? 0) + gapAfterColumn(columns, index);
|
|
29
|
+
}
|
|
30
|
+
return lefts;
|
|
31
|
+
}
|
|
32
|
+
/** Resolve an active column's absolute page X from the shared width/gap model. */
|
|
33
|
+
function resolveColumnLeft({ leftMargin, columnWidths, columns, columnIndex }) {
|
|
34
|
+
let x = leftMargin;
|
|
35
|
+
for (let index = 0; index < columnIndex; index++) x += (columnWidths[index] ?? columnWidths[0] ?? 0) + gapAfterColumn(columns, index);
|
|
36
|
+
return x;
|
|
37
|
+
}
|
|
18
38
|
function arePageSizesEqual(left, right) {
|
|
19
39
|
return left.w === right.w && left.h === right.h;
|
|
20
40
|
}
|
|
@@ -37,7 +57,10 @@ function createPaginator(options) {
|
|
|
37
57
|
let currentSectionPageNumber = 0;
|
|
38
58
|
let currentPageNumbering = options.pageNumbering ?? { type: "continue" };
|
|
39
59
|
let nextLogicalPageNumber = currentPageNumbering.type === "restart" ? currentPageNumbering.start : 1;
|
|
40
|
-
let
|
|
60
|
+
let sectionStart = {
|
|
61
|
+
type: "pending",
|
|
62
|
+
placement: "nextPage"
|
|
63
|
+
};
|
|
41
64
|
const pages = [];
|
|
42
65
|
const states = [];
|
|
43
66
|
function getContentBottom() {
|
|
@@ -82,9 +105,12 @@ function createPaginator(options) {
|
|
|
82
105
|
* Get X position for a given column index.
|
|
83
106
|
*/
|
|
84
107
|
function getColumnX(columnIndex) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
108
|
+
return resolveColumnLeft({
|
|
109
|
+
leftMargin: states.at(-1)?.page.margins.left ?? getPageMargins(1, nextLogicalPageNumber).left,
|
|
110
|
+
columnWidths,
|
|
111
|
+
columns,
|
|
112
|
+
columnIndex
|
|
113
|
+
});
|
|
88
114
|
}
|
|
89
115
|
/**
|
|
90
116
|
* Create a new page and add it to the list.
|
|
@@ -94,7 +120,7 @@ function createPaginator(options) {
|
|
|
94
120
|
const pageNumber = pages.length + 1;
|
|
95
121
|
const logicalNumber = nextLogicalPageNumber;
|
|
96
122
|
nextLogicalPageNumber += 1;
|
|
97
|
-
|
|
123
|
+
sectionStart = { type: "placed" };
|
|
98
124
|
currentSectionPageNumber += 1;
|
|
99
125
|
const pageMargins = getPageMargins(pageNumber, logicalNumber);
|
|
100
126
|
const topMargin = pageMargins.top;
|
|
@@ -208,11 +234,14 @@ function createPaginator(options) {
|
|
|
208
234
|
y
|
|
209
235
|
};
|
|
210
236
|
}
|
|
237
|
+
function consumeSharedSectionPage(state) {
|
|
238
|
+
if (sectionStart.type !== "pending" || sectionStart.placement !== "continuous" || state.page.sectionIndex === currentSectionIndex) return;
|
|
239
|
+
if (currentPageNumbering.type === "restart") nextLogicalPageNumber = currentPageNumbering.start + 1;
|
|
240
|
+
currentSectionPageNumber = 1;
|
|
241
|
+
sectionStart = { type: "placed" };
|
|
242
|
+
}
|
|
211
243
|
function commitFragment(state, fragment) {
|
|
212
|
-
|
|
213
|
-
if (currentPageNumbering.type === "restart") nextLogicalPageNumber = currentPageNumbering.start + 1;
|
|
214
|
-
sectionStartPending = false;
|
|
215
|
-
}
|
|
244
|
+
consumeSharedSectionPage(state);
|
|
216
245
|
state.page.fragments.push(fragment);
|
|
217
246
|
}
|
|
218
247
|
function addUnflowedFragment(fragment) {
|
|
@@ -259,6 +288,7 @@ function createPaginator(options) {
|
|
|
259
288
|
*/
|
|
260
289
|
function forcePageBreak(breakOptions = {}) {
|
|
261
290
|
const current = states.at(-1);
|
|
291
|
+
if (current?.page.fragments.length) consumeSharedSectionPage(current);
|
|
262
292
|
if (breakOptions.coalesceBlankPage && current && current.page.fragments.length === 0 && current.cursorY === current.topMargin) {
|
|
263
293
|
if (current.page.sectionIndex !== currentSectionIndex) {
|
|
264
294
|
retargetCurrentBlankPage();
|
|
@@ -268,6 +298,33 @@ function createPaginator(options) {
|
|
|
268
298
|
}
|
|
269
299
|
return createNewPage();
|
|
270
300
|
}
|
|
301
|
+
function coalescePageBreak() {
|
|
302
|
+
const state = getCurrentState();
|
|
303
|
+
consumeSharedSectionPage(state);
|
|
304
|
+
const previousMargins = state.page.margins;
|
|
305
|
+
const logicalNumber = nextLogicalPageNumber;
|
|
306
|
+
nextLogicalPageNumber += 1;
|
|
307
|
+
currentSectionPageNumber += 1;
|
|
308
|
+
state.page.logicalNumber = logicalNumber;
|
|
309
|
+
if (currentPageNumbering.format === void 0) delete state.page.logicalNumberFormat;
|
|
310
|
+
else state.page.logicalNumberFormat = currentPageNumbering.format;
|
|
311
|
+
applySectionMetadata(state.page);
|
|
312
|
+
const pageMargins = getPageMargins(state.page.number, logicalNumber);
|
|
313
|
+
const xDelta = pageMargins.left - previousMargins.left;
|
|
314
|
+
const yDelta = pageMargins.top - previousMargins.top;
|
|
315
|
+
for (const fragment of state.page.fragments) {
|
|
316
|
+
fragment.x += xDelta;
|
|
317
|
+
fragment.y += yDelta;
|
|
318
|
+
}
|
|
319
|
+
state.page.margins = pageMargins;
|
|
320
|
+
state.topMargin = pageMargins.top;
|
|
321
|
+
state.cursorY += yDelta;
|
|
322
|
+
state.rawContentBottom = pageSize.h - pageMargins.bottom;
|
|
323
|
+
state.contentBottom = state.rawContentBottom - state.footnoteHeight;
|
|
324
|
+
columnRegionTop += yDelta;
|
|
325
|
+
columnRegionMaxBottom += yDelta;
|
|
326
|
+
return state;
|
|
327
|
+
}
|
|
271
328
|
function retargetCurrentBlankPage() {
|
|
272
329
|
const current = states.at(-1);
|
|
273
330
|
if (!current || current.page.fragments.length > 0 || current.cursorY !== current.topMargin) return false;
|
|
@@ -334,20 +391,23 @@ function createPaginator(options) {
|
|
|
334
391
|
pendingPageSize = void 0;
|
|
335
392
|
pendingMargins = void 0;
|
|
336
393
|
}
|
|
337
|
-
function startSection(sectionIndex, pageNumbering = { type: "continue" }) {
|
|
394
|
+
function startSection({ sectionIndex, pageNumbering = { type: "continue" }, placement = SECTION_START_PLACEMENT.NEXT_PAGE }) {
|
|
338
395
|
currentSectionIndex = sectionIndex;
|
|
339
396
|
currentSectionPageNumber = 0;
|
|
340
397
|
currentPageNumbering = pageNumbering;
|
|
341
|
-
|
|
398
|
+
sectionStart = {
|
|
399
|
+
type: "pending",
|
|
400
|
+
placement
|
|
401
|
+
};
|
|
342
402
|
if (pageNumbering.type === "restart") nextLogicalPageNumber = pageNumbering.start;
|
|
343
403
|
}
|
|
344
404
|
function retargetSectionMetadata(page) {
|
|
345
405
|
currentSectionPageNumber = 1;
|
|
346
|
-
if (
|
|
406
|
+
if (sectionStart.type === "pending" && currentPageNumbering.type === "restart") {
|
|
347
407
|
page.logicalNumber = currentPageNumbering.start;
|
|
348
408
|
nextLogicalPageNumber = currentPageNumbering.start + 1;
|
|
349
409
|
}
|
|
350
|
-
|
|
410
|
+
sectionStart = { type: "placed" };
|
|
351
411
|
if (currentPageNumbering.format === void 0) delete page.logicalNumberFormat;
|
|
352
412
|
else page.logicalNumberFormat = currentPageNumbering.format;
|
|
353
413
|
applySectionMetadata(page);
|
|
@@ -393,6 +453,8 @@ function createPaginator(options) {
|
|
|
393
453
|
addFootnoteHeight,
|
|
394
454
|
/** Force a page break. */
|
|
395
455
|
forcePageBreak,
|
|
456
|
+
/** Reuse the current sheet while advancing authored page ordinals. */
|
|
457
|
+
coalescePageBreak,
|
|
396
458
|
/** Reuse an already blank current page for the active section/layout. */
|
|
397
459
|
retargetCurrentBlankPage,
|
|
398
460
|
/** Force a column break. */
|
|
@@ -408,4 +470,4 @@ function createPaginator(options) {
|
|
|
408
470
|
};
|
|
409
471
|
}
|
|
410
472
|
//#endregion
|
|
411
|
-
export { FOOTNOTE_SEPARATOR_HEIGHT, createPaginator };
|
|
473
|
+
export { FOOTNOTE_SEPARATOR_HEIGHT, SECTION_START_PLACEMENT, calculateColumnLefts, calculateColumnWidths, createPaginator, resolveColumnLeft };
|
|
@@ -12,6 +12,8 @@ type CollapseParagraphSpacingOptions = {
|
|
|
12
12
|
declare const collapseParagraphSpacing: ({ before, after }: CollapseParagraphSpacingOptions) => number;
|
|
13
13
|
/** Whether a paragraph has no visible run content. */
|
|
14
14
|
declare function isEmptyParagraph(block: ParagraphBlock): boolean;
|
|
15
|
+
/** Whether a visually empty paragraph carries authored formatting semantics. */
|
|
16
|
+
declare const isAuthoredEmptyParagraph: (block: ParagraphBlock) => boolean;
|
|
15
17
|
/**
|
|
16
18
|
* Resolve leading paragraph spacing after applying the empty-paragraph
|
|
17
19
|
* inherited-spacing collapse rule.
|
|
@@ -34,4 +36,4 @@ declare const resolveEffectiveParagraphSpacing: (block: ParagraphBlock) => Effec
|
|
|
34
36
|
declare const resolveEffectiveParagraphSpacingTree: (blocks: FlowBlock[]) => FlowBlock[];
|
|
35
37
|
declare const paragraphsShareStyle: (previous: ParagraphBlock, current: ParagraphBlock) => boolean;
|
|
36
38
|
//#endregion
|
|
37
|
-
export { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph, paragraphsShareStyle, resolveEffectiveParagraphSpacing, resolveEffectiveParagraphSpacingTree };
|
|
39
|
+
export { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isAuthoredEmptyParagraph, isEmptyParagraph, paragraphsShareStyle, resolveEffectiveParagraphSpacing, resolveEffectiveParagraphSpacingTree };
|
|
@@ -9,6 +9,8 @@ function isEmptyParagraph(block) {
|
|
|
9
9
|
const run = block.runs.at(0);
|
|
10
10
|
return run?.kind === "text" && run.text === "";
|
|
11
11
|
}
|
|
12
|
+
/** Whether a visually empty paragraph carries authored formatting semantics. */
|
|
13
|
+
const isAuthoredEmptyParagraph = (block) => isEmptyParagraph(block) && (block.attrs?.styleId !== void 0 || block.attrs?.hasDirectParagraphFormatting === true || block.attrs?.hasDirectParagraphMarkFormatting === true);
|
|
12
14
|
/**
|
|
13
15
|
* Resolve leading paragraph spacing after applying the empty-paragraph
|
|
14
16
|
* inherited-spacing collapse rule.
|
|
@@ -23,7 +25,7 @@ const getParagraphSpacingAfter = (block) => resolveEffectiveParagraphSpacing(blo
|
|
|
23
25
|
const resolveEffectiveParagraphSpacing = (block) => {
|
|
24
26
|
const before = block.attrs?.spacing?.before ?? 0;
|
|
25
27
|
const after = block.attrs?.spacing?.after ?? 0;
|
|
26
|
-
if (!isEmptyParagraph(block) ||
|
|
28
|
+
if (!isEmptyParagraph(block) || isAuthoredEmptyParagraph(block)) return {
|
|
27
29
|
before,
|
|
28
30
|
after
|
|
29
31
|
};
|
|
@@ -49,7 +51,6 @@ const resolveEffectiveParagraphSpacingTree = (blocks) => {
|
|
|
49
51
|
return changed ? resolved : blocks;
|
|
50
52
|
};
|
|
51
53
|
const paragraphsShareStyle = (previous, current) => previous.attrs?.styleId === current.attrs?.styleId;
|
|
52
|
-
const preservesInheritedEmptySpacing = (block) => Boolean(block.attrs?.styleId || block.attrs?.hasDirectParagraphFormatting || block.attrs?.hasDirectParagraphMarkFormatting);
|
|
53
54
|
const resolveBlockSpacing = (blocks, index) => {
|
|
54
55
|
const block = blocks[index];
|
|
55
56
|
if (block.kind === "paragraph") return resolveParagraphSequenceSpacing(blocks, index, block);
|
|
@@ -121,4 +122,4 @@ const resolveTextBoxSpacing = (block) => {
|
|
|
121
122
|
};
|
|
122
123
|
};
|
|
123
124
|
//#endregion
|
|
124
|
-
export { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph, paragraphsShareStyle, resolveEffectiveParagraphSpacing, resolveEffectiveParagraphSpacingTree };
|
|
125
|
+
export { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isAuthoredEmptyParagraph, isEmptyParagraph, paragraphsShareStyle, resolveEffectiveParagraphSpacing, resolveEffectiveParagraphSpacingTree };
|
|
@@ -8,7 +8,12 @@ type RenderedBreakState = {
|
|
|
8
8
|
} | {
|
|
9
9
|
type: "pageAdvance";
|
|
10
10
|
reason: "authoredBoundary";
|
|
11
|
-
boundary: "hardBreak"
|
|
11
|
+
boundary: "hardBreak";
|
|
12
|
+
} | {
|
|
13
|
+
type: "pageAdvance";
|
|
14
|
+
reason: "authoredBoundary";
|
|
15
|
+
boundary: "sectionBreak";
|
|
16
|
+
nextHardBreak: "coalesce" | "advance";
|
|
12
17
|
};
|
|
13
18
|
declare const INITIAL_RENDERED_BREAK_STATE: RenderedBreakState;
|
|
14
19
|
type ReconcileBeforeBlockOptions = {
|
|
@@ -20,8 +25,14 @@ type ReconcileBeforeBlockOptions = {
|
|
|
20
25
|
hasExplicitPageBreak: boolean;
|
|
21
26
|
renderedBreakNeedsSnap: boolean;
|
|
22
27
|
};
|
|
28
|
+
declare const PAGE_ADVANCE: {
|
|
29
|
+
readonly COALESCED: "coalesced";
|
|
30
|
+
readonly NONE: "none";
|
|
31
|
+
readonly PHYSICAL: "physical";
|
|
32
|
+
};
|
|
33
|
+
type PageAdvance = (typeof PAGE_ADVANCE)[keyof typeof PAGE_ADVANCE];
|
|
23
34
|
type BreakDecision = {
|
|
24
|
-
|
|
35
|
+
pageAdvance: PageAdvance;
|
|
25
36
|
suppressSpaceBefore: boolean;
|
|
26
37
|
state: RenderedBreakState;
|
|
27
38
|
};
|
|
@@ -45,4 +56,4 @@ type ReconcileAfterBlockOptions = {
|
|
|
45
56
|
/** Record page movement and carry authored boundaries across pagination-empty paragraphs. */
|
|
46
57
|
declare const reconcileAfterBlock: ({ state, block, pageNumberBefore, pageNumberAfter, previousPage }: ReconcileAfterBlockOptions) => RenderedBreakState;
|
|
47
58
|
//#endregion
|
|
48
|
-
export { INITIAL_RENDERED_BREAK_STATE, RenderedBreakState, reconcileAfterBlock, reconcileBreakBeforeBlock, recordReflowBoundary };
|
|
59
|
+
export { INITIAL_RENDERED_BREAK_STATE, PAGE_ADVANCE, RenderedBreakState, reconcileAfterBlock, reconcileBreakBeforeBlock, recordReflowBoundary };
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { continuesNumberedSequence, continuesTabbedParagraphSequence } from "./paragraphSequence.js";
|
|
2
2
|
//#region src/layout-engine/renderedBreakReconciliation.ts
|
|
3
3
|
const INITIAL_RENDERED_BREAK_STATE = { type: "noPageAdvance" };
|
|
4
|
+
const PAGE_ADVANCE = {
|
|
5
|
+
COALESCED: "coalesced",
|
|
6
|
+
NONE: "none",
|
|
7
|
+
PHYSICAL: "physical"
|
|
8
|
+
};
|
|
4
9
|
/**
|
|
5
10
|
* Reconcile an authored or cached break before laying out one block.
|
|
6
11
|
*
|
|
@@ -10,13 +15,21 @@ const INITIAL_RENDERED_BREAK_STATE = { type: "noPageAdvance" };
|
|
|
10
15
|
* independent decisions.
|
|
11
16
|
*/
|
|
12
17
|
const reconcileBreakBeforeBlock = ({ state, block, previousBlock, page, blocksById, hasExplicitPageBreak, renderedBreakNeedsSnap }) => {
|
|
13
|
-
if (hasExplicitPageBreak)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
if (hasExplicitPageBreak) {
|
|
19
|
+
const sectionBoundaryAlreadyOpenedPage = state.type === "pageAdvance" && state.reason === "authoredBoundary" && state.boundary === "sectionBreak" && state.nextHardBreak === "coalesce" && !pageHasVisibleBodyContent(page, blocksById);
|
|
20
|
+
return {
|
|
21
|
+
pageAdvance: sectionBoundaryAlreadyOpenedPage ? PAGE_ADVANCE.COALESCED : PAGE_ADVANCE.PHYSICAL,
|
|
22
|
+
suppressSpaceBefore: false,
|
|
23
|
+
state: sectionBoundaryAlreadyOpenedPage ? {
|
|
24
|
+
type: "pageAdvance",
|
|
25
|
+
reason: "authoredBoundary",
|
|
26
|
+
boundary: "sectionBreak",
|
|
27
|
+
nextHardBreak: "advance"
|
|
28
|
+
} : INITIAL_RENDERED_BREAK_STATE
|
|
29
|
+
};
|
|
30
|
+
}
|
|
18
31
|
if (block.kind !== "paragraph" || block.attrs?.renderedPageBreakBefore !== true) return {
|
|
19
|
-
|
|
32
|
+
pageAdvance: PAGE_ADVANCE.NONE,
|
|
20
33
|
suppressSpaceBefore: false,
|
|
21
34
|
state
|
|
22
35
|
};
|
|
@@ -27,9 +40,10 @@ const reconcileBreakBeforeBlock = ({ state, block, previousBlock, page, blocksBy
|
|
|
27
40
|
const followsPageBreakingSection = followsSectionBreak && previousBlock.type !== "continuous";
|
|
28
41
|
const followsCarriedSectionBoundary = state.type === "pageAdvance" && state.reason === "authoredBoundary" && state.boundary === "sectionBreak";
|
|
29
42
|
const preserveSectionLeadingSpacing = (followsSectionBreak || followsCarriedSectionBoundary) && block.attrs?.spacingExplicit?.before === true;
|
|
43
|
+
const markerAccountsForBoundary = forcePageBreak || markerAlreadySatisfied || followsAuthoredPageBreak || followsPageBreakingSection;
|
|
30
44
|
return {
|
|
31
|
-
forcePageBreak,
|
|
32
|
-
suppressSpaceBefore:
|
|
45
|
+
pageAdvance: forcePageBreak ? PAGE_ADVANCE.PHYSICAL : PAGE_ADVANCE.NONE,
|
|
46
|
+
suppressSpaceBefore: markerAccountsForBoundary && !preserveSectionLeadingSpacing,
|
|
33
47
|
state: INITIAL_RENDERED_BREAK_STATE
|
|
34
48
|
};
|
|
35
49
|
};
|
|
@@ -41,16 +55,33 @@ const recordReflowBoundary = (state, crossedPageBoundary) => crossedPageBoundary
|
|
|
41
55
|
const reconcileAfterBlock = ({ state, block, pageNumberBefore, pageNumberAfter, previousPage }) => {
|
|
42
56
|
if (isStructuralBreak(block)) {
|
|
43
57
|
if (pageNumberAfter <= pageNumberBefore) {
|
|
44
|
-
if (state.type === "pageAdvance" && state.reason === "authoredBoundary")
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
if (state.type === "pageAdvance" && state.reason === "authoredBoundary") {
|
|
59
|
+
if (block.kind !== "sectionBreak") return state.boundary === "sectionBreak" ? {
|
|
60
|
+
type: "pageAdvance",
|
|
61
|
+
reason: "authoredBoundary",
|
|
62
|
+
boundary: "sectionBreak",
|
|
63
|
+
nextHardBreak: "advance"
|
|
64
|
+
} : state;
|
|
65
|
+
if (state.boundary === "sectionBreak") return state;
|
|
66
|
+
return {
|
|
67
|
+
type: "pageAdvance",
|
|
68
|
+
reason: "authoredBoundary",
|
|
69
|
+
boundary: "sectionBreak",
|
|
70
|
+
nextHardBreak: "advance"
|
|
71
|
+
};
|
|
72
|
+
}
|
|
48
73
|
return INITIAL_RENDERED_BREAK_STATE;
|
|
49
74
|
}
|
|
75
|
+
if (block.kind === "sectionBreak") return {
|
|
76
|
+
type: "pageAdvance",
|
|
77
|
+
reason: "authoredBoundary",
|
|
78
|
+
boundary: "sectionBreak",
|
|
79
|
+
nextHardBreak: "coalesce"
|
|
80
|
+
};
|
|
50
81
|
return {
|
|
51
82
|
type: "pageAdvance",
|
|
52
83
|
reason: "authoredBoundary",
|
|
53
|
-
boundary:
|
|
84
|
+
boundary: "hardBreak"
|
|
54
85
|
};
|
|
55
86
|
}
|
|
56
87
|
if (block.kind === "paragraph" && isPaginationEmptyParagraph(block)) return state;
|
|
@@ -83,4 +114,4 @@ const pageStartsWithPreviousParagraphContinuation = (page, previousBlock) => {
|
|
|
83
114
|
};
|
|
84
115
|
const isStructuralBreak = (block) => block.kind === "pageBreak" || block.kind === "columnBreak" || block.kind === "sectionBreak";
|
|
85
116
|
//#endregion
|
|
86
|
-
export { INITIAL_RENDERED_BREAK_STATE, reconcileAfterBlock, reconcileBreakBeforeBlock, recordReflowBoundary };
|
|
117
|
+
export { INITIAL_RENDERED_BREAK_STATE, PAGE_ADVANCE, reconcileAfterBlock, reconcileBreakBeforeBlock, recordReflowBoundary };
|
|
@@ -53,6 +53,8 @@ type BreakDecision = {
|
|
|
53
53
|
/** Required page parity (even or odd). */
|
|
54
54
|
requiredParity?: "even" | "odd";
|
|
55
55
|
};
|
|
56
|
+
declare const DEFAULT_SECTION_BREAK_TYPE = "nextPage";
|
|
57
|
+
declare function normalizeSectionBreakType(type: SectionBreakBlock["type"]): "continuous" | "nextPage" | "evenPage" | "oddPage";
|
|
56
58
|
/**
|
|
57
59
|
* Create initial section state from default options.
|
|
58
60
|
*/
|
|
@@ -92,4 +94,4 @@ declare function getEffectivePageSize(state: SectionState): {
|
|
|
92
94
|
*/
|
|
93
95
|
declare function getEffectiveColumns(state: SectionState): ColumnLayout;
|
|
94
96
|
//#endregion
|
|
95
|
-
export { BreakDecision, SectionState, applyPendingToActive, createInitialSectionState, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, scheduleSectionBreak };
|
|
97
|
+
export { BreakDecision, DEFAULT_SECTION_BREAK_TYPE, SectionState, applyPendingToActive, createInitialSectionState, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, normalizeSectionBreakType, scheduleSectionBreak };
|
|
@@ -6,6 +6,10 @@ const DEFAULT_COLUMNS = {
|
|
|
6
6
|
count: 1,
|
|
7
7
|
gap: 0
|
|
8
8
|
};
|
|
9
|
+
const DEFAULT_SECTION_BREAK_TYPE = "nextPage";
|
|
10
|
+
function normalizeSectionBreakType(type) {
|
|
11
|
+
return type ?? "nextPage";
|
|
12
|
+
}
|
|
9
13
|
/**
|
|
10
14
|
* Create initial section state from default options.
|
|
11
15
|
*/
|
|
@@ -52,7 +56,7 @@ function getColumnConfig(columns) {
|
|
|
52
56
|
*/
|
|
53
57
|
function scheduleSectionBreak(block, state, _baseMargins) {
|
|
54
58
|
const next = { ...state };
|
|
55
|
-
const sectionType = block.type
|
|
59
|
+
const sectionType = normalizeSectionBreakType(block.type);
|
|
56
60
|
const sectionMargins = block.margins;
|
|
57
61
|
const sectionPageSize = block.pageSize;
|
|
58
62
|
const sectionOrientation = block.orientation;
|
|
@@ -179,4 +183,4 @@ function getEffectiveColumns(state) {
|
|
|
179
183
|
return state.pendingColumns ?? state.activeColumns;
|
|
180
184
|
}
|
|
181
185
|
//#endregion
|
|
182
|
-
export { applyPendingToActive, createInitialSectionState, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, scheduleSectionBreak };
|
|
186
|
+
export { DEFAULT_SECTION_BREAK_TYPE, applyPendingToActive, createInitialSectionState, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, normalizeSectionBreakType, scheduleSectionBreak };
|