@stll/folio-core 0.37.1 → 0.37.3
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/headless.d.ts +24 -1
- package/dist/ai-edits/headless.js +108 -71
- package/dist/ai-edits/read.d.ts +9 -1
- package/dist/ai-edits/read.js +34 -16
- package/dist/ai-edits/snapshot.d.ts +7 -5
- package/dist/ai-edits/snapshot.js +39 -12
- package/dist/ai-edits/table-cell-mutations.js +10 -5
- package/dist/ai-edits/table-template.js +19 -5
- package/dist/compare/compare.d.ts +2 -3
- package/dist/compare/compare.js +41 -64
- package/dist/compare/content-alignment.js +0 -1
- package/dist/compare/scenario.d.ts +11 -2
- package/dist/compare/scenario.js +6 -2
- package/dist/controller/hiddenEditorManager.js +11 -0
- package/dist/controller/layoutPipeline.js +1 -1
- package/dist/display-list/build/textBoxPrimitives.js +34 -1
- package/dist/display-list/dom/renderDisplayListToDom.js +5 -2
- package/dist/display-list/primitives.d.ts +1 -1
- package/dist/display-list/types.d.ts +8 -4
- package/dist/docx/paragraphPropertySource.d.ts +82 -2
- package/dist/docx/paragraphPropertySource.js +569 -3
- package/dist/docx/parser.js +9 -0
- package/dist/docx/server/materializeYjsDocx.d.ts +1 -1
- package/dist/docx/server/materializeYjsDocx.js +21 -2
- package/dist/headless-layout.js +1 -1
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +7 -1
- package/dist/layout-bridge/convert/headerFooterLayout.js +20 -3
- package/dist/layout-bridge/convert/toFlowBlocks.js +43 -12
- package/dist/layout-engine/index.js +20 -10
- package/dist/layout-engine/measure/measureBlocks.d.ts +7 -3
- package/dist/layout-engine/measure/measureBlocks.js +10 -7
- package/dist/layout-engine/measure/measureParagraph.d.ts +2 -0
- package/dist/layout-engine/measure/measureParagraph.js +1 -1
- package/dist/layout-engine/paginator.d.ts +1 -1
- package/dist/layout-engine/paginator.js +41 -4
- package/dist/layout-engine/textBoxFlow.d.ts +2 -0
- package/dist/layout-engine/textBoxFlow.js +9 -5
- package/dist/layout-engine/types.d.ts +6 -0
- package/dist/layout-painter/documentColors.d.ts +10 -1
- package/dist/layout-painter/documentColors.js +16 -1
- package/dist/layout-painter/renderParagraph.js +9 -24
- package/dist/layout-painter/renderTable.js +3 -3
- package/dist/layout-painter/renderTextBox.js +2 -1
- package/dist/pdf/pageSpace.d.ts +12 -1
- package/dist/pdf/pageSpace.js +24 -1
- package/dist/pdf/paint.js +2 -2
- package/dist/prosemirror/attrs/index.js +19 -8
- package/dist/prosemirror/commands/comments.js +12 -3
- package/dist/prosemirror/commands/tableCellMergeResolution.js +18 -11
- package/dist/prosemirror/conversion/fromProseDoc.js +94 -21
- package/dist/prosemirror/conversion/toProseDoc.js +100 -8
- package/dist/prosemirror/extensions/core/DocExtension.js +2 -0
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +5 -1
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +132 -41
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +7 -0
- package/dist/prosemirror/schema/nodes.d.ts +3 -1
- package/dist/prosemirror/yjsParagraphSourceContract.d.ts +9 -0
- package/dist/prosemirror/yjsParagraphSourceContract.js +26 -0
- package/dist/utils/rotationBoundingBox.d.ts +5 -1
- package/dist/utils/rotationBoundingBox.js +9 -1
- package/package.json +2 -2
|
@@ -4,7 +4,7 @@ declare const FOLIO_YJS_PROSEMIRROR_FRAGMENT_NAME = "prosemirror";
|
|
|
4
4
|
/** Maximum accepted size of a complete Yjs collaboration state update. */
|
|
5
5
|
declare const FOLIO_YJS_UPDATE_MAX_BYTES: number;
|
|
6
6
|
/** Stable failure codes returned by server-side Yjs-to-DOCX materialization. */
|
|
7
|
-
declare const FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES: readonly ["empty_update", "invalid_update", "missing_document", "update_too_large"];
|
|
7
|
+
declare const FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES: readonly ["empty_update", "invalid_update", "missing_document", "source_mismatch", "update_too_large"];
|
|
8
8
|
/** Failure code for a rejected Yjs-to-DOCX materialization request. */
|
|
9
9
|
type FolioYjsDocxMaterializationErrorCode = (typeof FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES)[number];
|
|
10
10
|
declare const FolioYjsDocxMaterializationError_base: import("better-result").TaggedErrorClass<"FolioYjsDocxMaterializationError">;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { fromProseDoc } from "../../prosemirror/conversion/fromProseDoc.js";
|
|
2
2
|
import { schema } from "../../prosemirror/schema/index.js";
|
|
3
|
+
import { readYjsParagraphSourceContract, withParagraphSourceContract } from "../../prosemirror/yjsParagraphSourceContract.js";
|
|
4
|
+
import { ParagraphPropertySourceValidationError } from "../paragraphPropertySource.js";
|
|
3
5
|
import { parseDocx } from "../parser.js";
|
|
4
6
|
import { repackDocx } from "../rezip.js";
|
|
5
7
|
import { Result, TaggedError } from "better-result";
|
|
@@ -15,6 +17,7 @@ const FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES = [
|
|
|
15
17
|
"empty_update",
|
|
16
18
|
"invalid_update",
|
|
17
19
|
"missing_document",
|
|
20
|
+
"source_mismatch",
|
|
18
21
|
"update_too_large"
|
|
19
22
|
];
|
|
20
23
|
/** Typed failure raised when a collaboration snapshot cannot be materialized. */
|
|
@@ -37,7 +40,12 @@ const readProseMirrorDocument = (yjsUpdate) => {
|
|
|
37
40
|
code: "missing_document",
|
|
38
41
|
message: "Yjs update does not contain a Folio document."
|
|
39
42
|
});
|
|
40
|
-
|
|
43
|
+
const contract = readYjsParagraphSourceContract(ydoc);
|
|
44
|
+
if (!contract) throw new FolioYjsDocxMaterializationError({
|
|
45
|
+
code: "source_mismatch",
|
|
46
|
+
message: "Yjs update does not identify its paragraph-property source document."
|
|
47
|
+
});
|
|
48
|
+
return withParagraphSourceContract(initProseMirrorDoc(fragment, schema).doc, contract);
|
|
41
49
|
},
|
|
42
50
|
catch: (cause) => cause instanceof FolioYjsDocxMaterializationError ? cause : new FolioYjsDocxMaterializationError({
|
|
43
51
|
code: "invalid_update",
|
|
@@ -55,7 +63,18 @@ const readProseMirrorDocument = (yjsUpdate) => {
|
|
|
55
63
|
* of the browser editor's full save path for the main document story.
|
|
56
64
|
*/
|
|
57
65
|
const materializeYjsDocx = async ({ sourceDocx, yjsUpdate }) => {
|
|
58
|
-
|
|
66
|
+
const proseMirrorDocument = readProseMirrorDocument(yjsUpdate);
|
|
67
|
+
const baseDocument = await parseDocx(sourceDocx, { preloadFonts: false });
|
|
68
|
+
const converted = Result.try({
|
|
69
|
+
try: () => fromProseDoc(proseMirrorDocument, baseDocument),
|
|
70
|
+
catch: (cause) => cause instanceof ParagraphPropertySourceValidationError ? new FolioYjsDocxMaterializationError({
|
|
71
|
+
code: "source_mismatch",
|
|
72
|
+
message: "The collaboration snapshot belongs to a different source document.",
|
|
73
|
+
cause
|
|
74
|
+
}) : cause
|
|
75
|
+
});
|
|
76
|
+
if (converted.isErr()) throw converted.error;
|
|
77
|
+
return await repackDocx(converted.value);
|
|
59
78
|
};
|
|
60
79
|
//#endregion
|
|
61
80
|
export { FOLIO_YJS_DOCX_MATERIALIZATION_ERROR_CODES, FOLIO_YJS_PROSEMIRROR_FRAGMENT_NAME, FOLIO_YJS_UPDATE_MAX_BYTES, FolioYjsDocxMaterializationError, materializeYjsDocx };
|
package/dist/headless-layout.js
CHANGED
|
@@ -132,7 +132,7 @@ const convertStories = ({ parts, contentWidth, metrics, storyOptions, pageCount,
|
|
|
132
132
|
for (const [rId, part] of parts ?? []) {
|
|
133
133
|
const content = convertHeaderFooterToContent(part, contentWidth, metrics, {
|
|
134
134
|
...storyOptions,
|
|
135
|
-
measureBlocks: (blocks, width) => measureBlocks(blocks, width, void 0, void 0, buildHeaderFooterFieldValues(blocks, pageCount, now)),
|
|
135
|
+
measureBlocks: (blocks, width) => measureBlocks(blocks, width, void 0, void 0, buildHeaderFooterFieldValues(blocks, pageCount, now), { allowEndTabOverflow: true }),
|
|
136
136
|
rId
|
|
137
137
|
});
|
|
138
138
|
if (content) contentByRId.set(rId, content);
|
|
@@ -13,6 +13,12 @@ type HeaderFooterMetrics = {
|
|
|
13
13
|
margins: PageMargins;
|
|
14
14
|
};
|
|
15
15
|
declare function normalizeHeaderFooterMeasureBlocks(blocks: FlowBlock[], section?: HeaderFooterMetrics["section"]): FlowBlock[];
|
|
16
|
+
/**
|
|
17
|
+
* Header/footer auto-fit tables use `w:tblGrid` as a provisional ratio, not a
|
|
18
|
+
* license to paint beyond the page furniture frame. Scale an oversized grid
|
|
19
|
+
* for measurement while leaving the authored block untouched for round trips.
|
|
20
|
+
*/
|
|
21
|
+
declare function fitHeaderFooterTablesToContentWidth(blocks: FlowBlock[], contentWidth: number): FlowBlock[];
|
|
16
22
|
declare function resolveHeaderFooterPositionedVisualTop(position: ImageRunPosition | undefined, elementHeight: number, sourceY: number, flowHeight: number, metrics: HeaderFooterMetrics): number;
|
|
17
23
|
declare function resolveHeaderFooterVisualTop(run: ImageRun, paragraphY: number, flowHeight: number, metrics: HeaderFooterMetrics): number;
|
|
18
24
|
declare function calculateHeaderFooterVisualBounds(blocks: FlowBlock[], measures: Measure[], flowHeight: number, metrics: HeaderFooterMetrics): {
|
|
@@ -96,4 +102,4 @@ type ReserveHeaderFooterFullWidthWrapBandsOptions = {
|
|
|
96
102
|
};
|
|
97
103
|
declare function reserveHeaderFooterFullWidthWrapBands({ blocks, measures, contentWidth, metrics }: ReserveHeaderFooterFullWidthWrapBandsOptions): Measure[];
|
|
98
104
|
//#endregion
|
|
99
|
-
export { ConvertHeaderFooterOptions, HeaderFooterMetrics, calculateHeaderFooterBodyTopClearance, calculateHeaderFooterMarginPushBounds, calculateHeaderFooterVisualBounds, convertHeaderFooterPmDocToContent, convertHeaderFooterToContent, normalizeHeaderFooterMeasureBlocks, reserveHeaderFooterFullWidthWrapBands, resolveHeaderFooterPositionedVisualTop, resolveHeaderFooterVisualTop };
|
|
105
|
+
export { ConvertHeaderFooterOptions, HeaderFooterMetrics, calculateHeaderFooterBodyTopClearance, calculateHeaderFooterMarginPushBounds, calculateHeaderFooterVisualBounds, convertHeaderFooterPmDocToContent, convertHeaderFooterToContent, fitHeaderFooterTablesToContentWidth, normalizeHeaderFooterMeasureBlocks, reserveHeaderFooterFullWidthWrapBands, resolveHeaderFooterPositionedVisualTop, resolveHeaderFooterVisualTop };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cloneParagraphWithPropertySource } from "../../docx/paragraphPropertySource.js";
|
|
2
|
-
import { isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun } from "../../layout-engine/types.js";
|
|
2
|
+
import { isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned } from "../../layout-engine/types.js";
|
|
3
3
|
import { headerFooterToProseDoc } from "../../prosemirror/conversion/toProseDoc.js";
|
|
4
4
|
import { emuToPixels } from "../../utils/units.js";
|
|
5
5
|
import { toFlowBlocks } from "./toFlowBlocks.js";
|
|
@@ -33,6 +33,23 @@ function preservesInheritedSpacing(block) {
|
|
|
33
33
|
function normalizeHeaderFooterMeasureBlocks(blocks, section = "header") {
|
|
34
34
|
return normalizeFlowBlockArray(blocks, { suppressTrailingEmptyAfterTable: section === "header" });
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Header/footer auto-fit tables use `w:tblGrid` as a provisional ratio, not a
|
|
38
|
+
* license to paint beyond the page furniture frame. Scale an oversized grid
|
|
39
|
+
* for measurement while leaving the authored block untouched for round trips.
|
|
40
|
+
*/
|
|
41
|
+
function fitHeaderFooterTablesToContentWidth(blocks, contentWidth) {
|
|
42
|
+
return blocks.map((block) => {
|
|
43
|
+
if (block.kind !== "table" || block.floating !== void 0 || tableColumnsArePinned(block) || block.columnWidths === void 0) return block;
|
|
44
|
+
const totalWidth = block.columnWidths.reduce((sum, width) => sum + width, 0);
|
|
45
|
+
if (totalWidth <= contentWidth || totalWidth <= 0) return block;
|
|
46
|
+
const scale = contentWidth / totalWidth;
|
|
47
|
+
return {
|
|
48
|
+
...block,
|
|
49
|
+
columnWidths: block.columnWidths.map((width) => width * scale)
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
36
53
|
function normalizeFlowBlockArray(blocks, normalization) {
|
|
37
54
|
const trailingEmptyAfterTable = /* @__PURE__ */ new Set();
|
|
38
55
|
const lastIndex = blocks.length - 1;
|
|
@@ -414,7 +431,7 @@ function reserveHeaderFooterFullWidthWrapBands({ blocks, measures, contentWidth,
|
|
|
414
431
|
}
|
|
415
432
|
function finalizeHeaderFooterContent(blocks, contentWidth, metrics, options) {
|
|
416
433
|
if (blocks.length === 0) return;
|
|
417
|
-
const blocksForMeasure = normalizeHeaderFooterMeasureBlocks(blocks, metrics.section);
|
|
434
|
+
const blocksForMeasure = fitHeaderFooterTablesToContentWidth(normalizeHeaderFooterMeasureBlocks(blocks, metrics.section), contentWidth);
|
|
418
435
|
const measures = reserveHeaderFooterFullWidthWrapBands({
|
|
419
436
|
blocks,
|
|
420
437
|
measures: options.measureBlocks(blocksForMeasure, contentWidth),
|
|
@@ -575,4 +592,4 @@ function serializeRunFmt(run) {
|
|
|
575
592
|
return JSON.stringify(out);
|
|
576
593
|
}
|
|
577
594
|
//#endregion
|
|
578
|
-
export { calculateHeaderFooterBodyTopClearance, calculateHeaderFooterMarginPushBounds, calculateHeaderFooterVisualBounds, convertHeaderFooterPmDocToContent, convertHeaderFooterToContent, normalizeHeaderFooterMeasureBlocks, reserveHeaderFooterFullWidthWrapBands, resolveHeaderFooterPositionedVisualTop, resolveHeaderFooterVisualTop };
|
|
595
|
+
export { calculateHeaderFooterBodyTopClearance, calculateHeaderFooterMarginPushBounds, calculateHeaderFooterVisualBounds, convertHeaderFooterPmDocToContent, convertHeaderFooterToContent, fitHeaderFooterTablesToContentWidth, normalizeHeaderFooterMeasureBlocks, reserveHeaderFooterFullWidthWrapBands, resolveHeaderFooterPositionedVisualTop, resolveHeaderFooterVisualTop };
|
|
@@ -1162,6 +1162,25 @@ function paragraphFragmentAttrs(source, index, count) {
|
|
|
1162
1162
|
}
|
|
1163
1163
|
return attrs;
|
|
1164
1164
|
}
|
|
1165
|
+
const runIsZeroWidthBoundaryMarker = (run) => {
|
|
1166
|
+
switch (run.kind) {
|
|
1167
|
+
case "text": return run.text.length === 0;
|
|
1168
|
+
case "field": return (run.fallback ?? "").length === 0;
|
|
1169
|
+
case "renderedPageBreak": return true;
|
|
1170
|
+
case "image":
|
|
1171
|
+
case "lineBreak":
|
|
1172
|
+
case "math":
|
|
1173
|
+
case "tab": return false;
|
|
1174
|
+
default: return run;
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
/** Decide leading-break eligibility from the exact projected runs consumed by layout. */
|
|
1178
|
+
const hasSingleLeadingProjectedPageBreak = (runs, pageBreaks) => {
|
|
1179
|
+
if (pageBreaks.length !== 1) return false;
|
|
1180
|
+
const partitioned = partitionRunsAtPageBreaks(runs, pageBreaks);
|
|
1181
|
+
if (partitioned.type === "overlap") return false;
|
|
1182
|
+
return partitioned.partitions[0]?.before.every(runIsZeroWidthBoundaryMarker) === true;
|
|
1183
|
+
};
|
|
1165
1184
|
function splitParagraphAtPageBreaks({ pageBreaks, paragraph, splitPageBreakAndParagraphMark }) {
|
|
1166
1185
|
if (pageBreaks.length === 0) return [paragraph];
|
|
1167
1186
|
const result = [];
|
|
@@ -1324,17 +1343,16 @@ function extractCellBorders(borders, theme) {
|
|
|
1324
1343
|
}
|
|
1325
1344
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
1326
1345
|
}
|
|
1327
|
-
/**
|
|
1328
|
-
* Convert a table cell node.
|
|
1329
|
-
*/
|
|
1330
1346
|
function convertTableCell(node, startPos, options, tableCellMargins) {
|
|
1331
|
-
const pageBreakPosition = options.firstPageBreakRunPosition(node);
|
|
1332
|
-
if (pageBreakPosition !== void 0) panic(`An explicit page-break run at ${String(pageBreakPosition)} cannot be projected inside a table cell`);
|
|
1333
1347
|
const blocks = [];
|
|
1334
1348
|
let offset = startPos + 1;
|
|
1349
|
+
const authoredPageBreakPosition = options.firstPageBreakRunPosition(node);
|
|
1350
|
+
const singleParagraph = node.childCount === 1 && node.firstChild?.type.name === "paragraph" ? node.firstChild : void 0;
|
|
1351
|
+
if (authoredPageBreakPosition !== void 0 && singleParagraph === void 0) panic(`An explicit page-break run at ${String(authoredPageBreakPosition)} cannot be projected inside a table cell`);
|
|
1352
|
+
const pageBreaks = [];
|
|
1335
1353
|
node.forEach((child) => {
|
|
1336
1354
|
if (child.type.name === "paragraph") {
|
|
1337
|
-
const block = convertParagraph(child, offset, options);
|
|
1355
|
+
const block = convertParagraph(child, offset, options, child === singleParagraph ? pageBreaks : void 0);
|
|
1338
1356
|
blocks.push(block);
|
|
1339
1357
|
} else if (child.type.name === "table") blocks.push(convertTable(child, offset, options));
|
|
1340
1358
|
else if (child.type.name === "textBox") blocks.push(convertTextBoxNode(child, offset, options));
|
|
@@ -1376,7 +1394,13 @@ function convertTableCell(node, startPos, options, tableCellMargins) {
|
|
|
1376
1394
|
const cellBorders = extractCellBorders(attrs.borders, options.theme);
|
|
1377
1395
|
if (cellBorders) cell.borders = cellBorders;
|
|
1378
1396
|
if (attrs.noWrap) cell.noWrap = true;
|
|
1379
|
-
return cell;
|
|
1397
|
+
if (authoredPageBreakPosition === void 0 || pageBreaks.length === 0) return { cell };
|
|
1398
|
+
const paragraph = blocks.at(0);
|
|
1399
|
+
if (paragraph?.kind !== "paragraph" || !hasSingleLeadingProjectedPageBreak(paragraph.runs, pageBreaks)) panic(`An explicit page-break run at ${String(authoredPageBreakPosition)} cannot be projected inside a table cell`);
|
|
1400
|
+
return {
|
|
1401
|
+
cell,
|
|
1402
|
+
breakBefore: "page"
|
|
1403
|
+
};
|
|
1380
1404
|
}
|
|
1381
1405
|
/**
|
|
1382
1406
|
* Convert a table row node.
|
|
@@ -1384,8 +1408,13 @@ function convertTableCell(node, startPos, options, tableCellMargins) {
|
|
|
1384
1408
|
function convertTableRow(node, startPos, options, tableCellMargins) {
|
|
1385
1409
|
const cells = [];
|
|
1386
1410
|
let offset = startPos + 1;
|
|
1411
|
+
let breakBefore;
|
|
1387
1412
|
node.forEach((child) => {
|
|
1388
|
-
if (child.type.name === "tableCell" || child.type.name === "tableHeader")
|
|
1413
|
+
if (child.type.name === "tableCell" || child.type.name === "tableHeader") {
|
|
1414
|
+
const converted = convertTableCell(child, offset, options, tableCellMargins);
|
|
1415
|
+
if (converted.breakBefore !== void 0) breakBefore = converted.breakBefore;
|
|
1416
|
+
cells.push(converted.cell);
|
|
1417
|
+
}
|
|
1389
1418
|
offset += child.nodeSize;
|
|
1390
1419
|
});
|
|
1391
1420
|
const attrs = expectTableRowAttrs(node);
|
|
@@ -1399,6 +1428,7 @@ function convertTableRow(node, startPos, options, tableCellMargins) {
|
|
|
1399
1428
|
if (attrs.heightRule) row.heightRule = attrs.heightRule;
|
|
1400
1429
|
if (attrs.isHeader) row.isHeader = attrs.isHeader;
|
|
1401
1430
|
if (attrs._originalFormatting?.cantSplit) row.cantSplit = true;
|
|
1431
|
+
if (breakBefore !== void 0) row.breakBefore = breakBefore;
|
|
1402
1432
|
if (attrs.hidden) row.hidden = attrs.hidden;
|
|
1403
1433
|
const effectiveJustification = attrs._originalFormatting?.justification ?? attrs._resolvedJustification;
|
|
1404
1434
|
if (effectiveJustification) row.justification = effectiveJustification;
|
|
@@ -1539,6 +1569,7 @@ function convertTextBoxNode(node, startPos, opts) {
|
|
|
1539
1569
|
if (attrs.outlineWidth !== void 0) textBox.outlineWidth = attrs.outlineWidth;
|
|
1540
1570
|
if (attrs.outlineColor !== void 0) textBox.outlineColor = attrs.outlineColor;
|
|
1541
1571
|
if (attrs.outlineStyle !== void 0) textBox.outlineStyle = attrs.outlineStyle;
|
|
1572
|
+
if (attrs.transform !== void 0) textBox.transform = attrs.transform;
|
|
1542
1573
|
if (attrs.displayMode !== void 0) textBox.displayMode = attrs.displayMode;
|
|
1543
1574
|
if (attrs.cssFloat !== void 0) textBox.cssFloat = attrs.cssFloat;
|
|
1544
1575
|
if (attrs.wrapType !== void 0) textBox.wrapType = attrs.wrapType;
|
|
@@ -1703,12 +1734,12 @@ function toFlowBlocks(doc, options = {}) {
|
|
|
1703
1734
|
blocks.push(block);
|
|
1704
1735
|
};
|
|
1705
1736
|
const pushParagraphProjection = (node, pos, stripLeadingLineBreak = false) => {
|
|
1706
|
-
if (opts.firstPageBreakRunPosition(node) !== void 0) {
|
|
1707
|
-
const disposition = pageBreakRunParagraphProjectionDisposition(node);
|
|
1708
|
-
if (disposition.status === "unsupported") panic(disposition.message);
|
|
1709
|
-
}
|
|
1710
1737
|
const pageBreaks = [];
|
|
1711
1738
|
const paragraph = convertParagraph(node, pos, opts, pageBreaks);
|
|
1739
|
+
if (pageBreaks.length > 0) {
|
|
1740
|
+
const disposition = pageBreakRunParagraphProjectionDisposition(node);
|
|
1741
|
+
if (disposition.status === "unsupported" && (disposition.reason === "textBoxAnchor" || !hasSingleLeadingProjectedPageBreak(paragraph.runs, pageBreaks))) panic(disposition.message);
|
|
1742
|
+
}
|
|
1712
1743
|
if (stripLeadingLineBreak && paragraph.runs.at(0)?.kind === "lineBreak") paragraph.runs.shift();
|
|
1713
1744
|
for (const block of splitParagraphAtPageBreaks({
|
|
1714
1745
|
pageBreaks,
|
|
@@ -303,10 +303,10 @@ function layoutDocumentPass(blocks, measures, options) {
|
|
|
303
303
|
block,
|
|
304
304
|
pageNumberBefore: pageBeforeBlockLayout,
|
|
305
305
|
pageNumberAfter: paginator.getCurrentState().page.number,
|
|
306
|
-
previousPage: paginator.
|
|
306
|
+
previousPage: paginator.states[pageBeforeBlockLayout - 1]?.page
|
|
307
307
|
});
|
|
308
308
|
}
|
|
309
|
-
if (paginator.
|
|
309
|
+
if (paginator.states.length === 0) paginator.getCurrentState();
|
|
310
310
|
return {
|
|
311
311
|
pageSize,
|
|
312
312
|
pages: paginator.pages,
|
|
@@ -359,6 +359,7 @@ function layoutParagraph({ block, measure, paginator, contentWidth, footnoteHeig
|
|
|
359
359
|
const state = paginator.getCurrentState();
|
|
360
360
|
const fragment = {
|
|
361
361
|
kind: "paragraph",
|
|
362
|
+
...block.attrs?.suppressEmptyParagraphHeight === true ? { paginationRole: "empty-carrier" } : {},
|
|
362
363
|
blockId: block.id,
|
|
363
364
|
x: paginator.getColumnX(state.columnIndex),
|
|
364
365
|
y: state.cursorY + spaceBefore,
|
|
@@ -454,6 +455,7 @@ function layoutParagraph({ block, measure, paginator, contentWidth, footnoteHeig
|
|
|
454
455
|
const pmRange = getParagraphFragmentPmRange(block, measure, currentLineIndex, currentLineIndex + fittingLines);
|
|
455
456
|
const fragment = {
|
|
456
457
|
kind: "paragraph",
|
|
458
|
+
...block.attrs?.suppressEmptyParagraphHeight === true ? { paginationRole: "empty-carrier" } : {},
|
|
457
459
|
blockId: block.id,
|
|
458
460
|
x: paginator.getColumnX(state.columnIndex),
|
|
459
461
|
y: 0,
|
|
@@ -575,7 +577,12 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
|
|
|
575
577
|
const rowState = paginator.getCurrentState();
|
|
576
578
|
const rowHeaderOverhead = shouldRepeatHeaderRows(currentRowIndex, 0, rowState) ? headerRowsHeight : 0;
|
|
577
579
|
const rowAvailableHeight = paginator.getAvailableHeight() - rowHeaderOverhead - rowState.trailingSpacing;
|
|
578
|
-
|
|
580
|
+
const rowStartsFreshPage = rowState.cursorY === rowState.topMargin && rowState.page.fragments.length === 0;
|
|
581
|
+
if (block.rows[currentRowIndex]?.breakBefore === "page" && !rowStartsFreshPage) {
|
|
582
|
+
paginator.forcePageBreak();
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
if (!rowStartsFreshPage && tableRowStartsWithRenderedPageBreak(block, currentRowIndex) && !tableRowHasTrackedChanges(block, currentRowIndex) && rows[currentRowIndex].height > rowAvailableHeight) {
|
|
579
586
|
paginator.forcePageBreak();
|
|
580
587
|
continue;
|
|
581
588
|
}
|
|
@@ -676,6 +683,7 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
|
|
|
676
683
|
rowIndex: currentRowIndex
|
|
677
684
|
});
|
|
678
685
|
for (let j = currentRowIndex; j < rows.length; j++) {
|
|
686
|
+
if (j > currentRowIndex && block.rows[j]?.breakBefore === "page") break;
|
|
679
687
|
if (j > currentRowIndex && computeTableX({
|
|
680
688
|
columnIndex: state.columnIndex,
|
|
681
689
|
rowIndex: j
|
|
@@ -883,13 +891,14 @@ function layoutTextBox(block, measure, { paginator, sectionMarginTop, sectionPag
|
|
|
883
891
|
marginBottom: sectionMarginBottom,
|
|
884
892
|
boxHeight: measure.height
|
|
885
893
|
});
|
|
886
|
-
const
|
|
887
|
-
const x = horizontal ? bandFragmentX(horizontal, {
|
|
894
|
+
const x = bandFragmentX(block.position?.horizontal, {
|
|
888
895
|
pageWidth: state.page.size.w,
|
|
889
896
|
marginLeft: state.page.margins.left,
|
|
890
897
|
marginRight: state.page.margins.right,
|
|
898
|
+
activeColumnLeft: paginator.getColumnX(state.columnIndex),
|
|
899
|
+
activeColumnWidth: paginator.columnWidth,
|
|
891
900
|
boxWidth: measure.width
|
|
892
|
-
})
|
|
901
|
+
});
|
|
893
902
|
const fragment = {
|
|
894
903
|
kind: "textBox",
|
|
895
904
|
blockId: block.id,
|
|
@@ -906,13 +915,14 @@ function layoutTextBox(block, measure, { paginator, sectionMarginTop, sectionPag
|
|
|
906
915
|
}
|
|
907
916
|
if (block.position !== void 0) {
|
|
908
917
|
const state = paginator.getCurrentState();
|
|
909
|
-
const
|
|
910
|
-
const x = horizontal ? bandFragmentX(horizontal, {
|
|
918
|
+
const x = bandFragmentX(block.position.horizontal, {
|
|
911
919
|
pageWidth: state.page.size.w,
|
|
912
920
|
marginLeft: state.page.margins.left,
|
|
913
921
|
marginRight: state.page.margins.right,
|
|
922
|
+
activeColumnLeft: paginator.getColumnX(state.columnIndex),
|
|
923
|
+
activeColumnWidth: paginator.columnWidth,
|
|
914
924
|
boxWidth: measure.width
|
|
915
|
-
})
|
|
925
|
+
});
|
|
916
926
|
const vertical = block.position.vertical;
|
|
917
927
|
const anchorBlockId = readTextBoxAnchorBlockId(block);
|
|
918
928
|
const anchorParagraph = vertical?.relativeTo === "paragraph" && typeof anchorBlockId === "string" ? state.page.fragments.find((fragment) => fragment.kind === "paragraph" && fragment.blockId === anchorBlockId) : void 0;
|
|
@@ -1004,7 +1014,7 @@ function handleSectionBreak(_block, paginator, nextSectionConfig, nextSectionTyp
|
|
|
1004
1014
|
if (!paginator.retargetCurrentBlankPage()) panic("Odd-page section target must be blank");
|
|
1005
1015
|
break;
|
|
1006
1016
|
case "continuous": {
|
|
1007
|
-
const currentPage = paginator.
|
|
1017
|
+
const currentPage = paginator.states.at(-1)?.page;
|
|
1008
1018
|
const nextSize = nextSectionConfig.pageSize;
|
|
1009
1019
|
const pageSizeChanges = currentPage != null && (Math.round(nextSize.w) !== Math.round(currentPage.size.w) || Math.round(nextSize.h) !== Math.round(currentPage.size.h));
|
|
1010
1020
|
if (nextSectionIndex !== void 0) paginator.startSection({
|
|
@@ -2,6 +2,10 @@ import { FlowBlock, Measure, TableBlock, TableMeasure, TextBoxBlock, TextBoxMeas
|
|
|
2
2
|
import { FloatingImageZone } from "./floatingZones.js";
|
|
3
3
|
import "./measureParagraph.js";
|
|
4
4
|
//#region src/layout-engine/measure/measureBlocks.d.ts
|
|
5
|
+
type MeasureBlocksOptions = {
|
|
6
|
+
/** Header/footer tabs may be authored in the page margin, beyond body width. */
|
|
7
|
+
allowEndTabOverflow?: boolean;
|
|
8
|
+
};
|
|
5
9
|
declare function measureTableBlock(tableBlock: TableBlock, contentWidth: number, fieldValues?: ReadonlyMap<number, string>): TableMeasure;
|
|
6
10
|
type PhysicalBandPageGeometry = {
|
|
7
11
|
pageWidth: number | number[];
|
|
@@ -28,7 +32,7 @@ type BandPageGeometry = {
|
|
|
28
32
|
/**
|
|
29
33
|
* Measure a block based on its type.
|
|
30
34
|
*/
|
|
31
|
-
declare function measureBlock(block: FlowBlock, contentWidth: number, floatingZones?: FloatingImageZone[], cumulativeY?: number, fieldValues?: ReadonlyMap<number, string
|
|
35
|
+
declare function measureBlock(block: FlowBlock, contentWidth: number, floatingZones?: FloatingImageZone[], cumulativeY?: number, fieldValues?: ReadonlyMap<number, string>, options?: MeasureBlocksOptions): Measure;
|
|
32
36
|
declare function measureTextBoxBlock(tb: TextBoxBlock, fieldValues?: ReadonlyMap<number, string>): TextBoxMeasure;
|
|
33
37
|
/**
|
|
34
38
|
* Measure all blocks with floating image support.
|
|
@@ -37,7 +41,7 @@ declare function measureTextBoxBlock(tb: TextBoxBlock, fieldValues?: ReadonlyMap
|
|
|
37
41
|
* Then measures each block, passing the zones so paragraphs can calculate
|
|
38
42
|
* per-line widths based on vertical overlap with floating images.
|
|
39
43
|
*/
|
|
40
|
-
declare function measureBlocks(blocks: FlowBlock[], contentWidth: number | number[], marginTop?: number | number[], pageGeometry?: BandPageGeometry, fieldValues?: ReadonlyMap<number, string
|
|
44
|
+
declare function measureBlocks(blocks: FlowBlock[], contentWidth: number | number[], marginTop?: number | number[], pageGeometry?: BandPageGeometry, fieldValues?: ReadonlyMap<number, string>, options?: MeasureBlocksOptions): Measure[];
|
|
41
45
|
declare function measureSingleBlockWithoutFloatingZones(block: FlowBlock, blockWidth: number, blockIndex: number): Measure;
|
|
42
46
|
//#endregion
|
|
43
|
-
export { measureBlock, measureBlocks, measureSingleBlockWithoutFloatingZones, measureTableBlock, measureTextBoxBlock };
|
|
47
|
+
export { MeasureBlocksOptions, measureBlock, measureBlocks, measureSingleBlockWithoutFloatingZones, measureTableBlock, measureTextBoxBlock };
|
|
@@ -385,6 +385,7 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
|
|
|
385
385
|
const blockMarginRight = perBlockNumberValue(marginRightInput, blockIndex, defaultMarginRight);
|
|
386
386
|
const blockPageWidth = perBlockNumberValue(pageWidthInput, blockIndex, defaultPageWidth);
|
|
387
387
|
const blockContentWidth = perBlockNumberValue(contentWidth, blockIndex, defaultContentWidth);
|
|
388
|
+
const blockContentLeft = perBlockNumberValue(contentLeftInput, blockIndex, defaultContentLeft);
|
|
388
389
|
const vertical = tb.position.vertical;
|
|
389
390
|
const pageFrameRelative = isPageFrameRelativeAnchor(vertical?.relativeTo);
|
|
390
391
|
const topY = pageFrameRelative ? bandTopContentY(vertical, {
|
|
@@ -408,13 +409,14 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
|
|
|
408
409
|
});
|
|
409
410
|
continue;
|
|
410
411
|
}
|
|
411
|
-
const
|
|
412
|
-
const contentX = (horizontal ? bandFragmentX(horizontal, {
|
|
412
|
+
const contentX = bandFragmentX(tb.position.horizontal, {
|
|
413
413
|
pageWidth: blockPageWidth,
|
|
414
414
|
marginLeft: blockMarginLeft,
|
|
415
415
|
marginRight: blockMarginRight,
|
|
416
|
+
activeColumnLeft: blockContentLeft,
|
|
417
|
+
activeColumnWidth: blockContentWidth,
|
|
416
418
|
boxWidth: measure.width
|
|
417
|
-
})
|
|
419
|
+
}) - blockContentLeft;
|
|
418
420
|
const wrapSide = textBoxWrapSide({
|
|
419
421
|
box: tb,
|
|
420
422
|
contentX,
|
|
@@ -518,12 +520,12 @@ function activateFloatingTablePageRect(template, anchorPageY) {
|
|
|
518
520
|
/**
|
|
519
521
|
* Measure a block based on its type.
|
|
520
522
|
*/
|
|
521
|
-
function measureBlock(block, contentWidth, floatingZones, cumulativeY, fieldValues) {
|
|
523
|
+
function measureBlock(block, contentWidth, floatingZones, cumulativeY, fieldValues, options) {
|
|
522
524
|
switch (block.kind) {
|
|
523
525
|
case "paragraph": {
|
|
524
526
|
const pBlock = block;
|
|
525
527
|
const hasFieldRuns = pBlock.runs.some((run) => run.kind === "field");
|
|
526
|
-
const cacheable = (!floatingZones || floatingZones.length === 0) && (!fieldValues || !hasFieldRuns);
|
|
528
|
+
const cacheable = (!floatingZones || floatingZones.length === 0) && (!fieldValues || !hasFieldRuns) && options?.allowEndTabOverflow !== true;
|
|
527
529
|
if (cacheable) {
|
|
528
530
|
const cached = getCachedParagraphMeasure(pBlock, contentWidth);
|
|
529
531
|
if (cached) return cached;
|
|
@@ -531,6 +533,7 @@ function measureBlock(block, contentWidth, floatingZones, cumulativeY, fieldValu
|
|
|
531
533
|
const measureOpts = { paragraphYOffset: cumulativeY ?? 0 };
|
|
532
534
|
if (floatingZones) measureOpts.floatingZones = floatingZones;
|
|
533
535
|
if (fieldValues) measureOpts.fieldValues = fieldValues;
|
|
536
|
+
if (options?.allowEndTabOverflow === true) measureOpts.allowEndTabOverflow = true;
|
|
534
537
|
const result = measureParagraph(pBlock, contentWidth, measureOpts);
|
|
535
538
|
if (cacheable) setCachedParagraphMeasure(pBlock, contentWidth, result);
|
|
536
539
|
return result;
|
|
@@ -604,7 +607,7 @@ function isContinuousSectionBreak(block) {
|
|
|
604
607
|
* Then measures each block, passing the zones so paragraphs can calculate
|
|
605
608
|
* per-line widths based on vertical overlap with floating images.
|
|
606
609
|
*/
|
|
607
|
-
function measureBlocks(blocks, contentWidth, marginTop = 0, pageGeometry, fieldValues) {
|
|
610
|
+
function measureBlocks(blocks, contentWidth, marginTop = 0, pageGeometry, fieldValues, options) {
|
|
608
611
|
const defaultWidth = Array.isArray(contentWidth) ? contentWidth[0] ?? 0 : contentWidth;
|
|
609
612
|
const extractedZones = extractFloatingZones(blocks, contentWidth, marginTop, pageGeometry);
|
|
610
613
|
const floatingZonesWithAnchors = [];
|
|
@@ -694,7 +697,7 @@ function measureBlocks(blocks, contentWidth, marginTop = 0, pageGeometry, fieldV
|
|
|
694
697
|
}));
|
|
695
698
|
const zones = activeZones.length > 0 || projectedTableZones.length > 0 ? [...activeZones, ...projectedTableZones] : void 0;
|
|
696
699
|
try {
|
|
697
|
-
const measure = measureBlock(block, blockWidth, zones, cumulativeY, fieldValues);
|
|
700
|
+
const measure = measureBlock(block, blockWidth, zones, cumulativeY, fieldValues, options);
|
|
698
701
|
const clearingZones = measure.kind === "table" ? zones : zones?.filter((zone) => zone.fullWidthBlock);
|
|
699
702
|
if (clearingZones?.length && (measure.kind === "image" || measure.kind === "table" && !block.floating)) {
|
|
700
703
|
const blockHeight = measure.kind === "image" ? measure.height : measure.totalHeight;
|
|
@@ -13,6 +13,8 @@ type MeasureParagraphOptions = {
|
|
|
13
13
|
/** Field run `pmStart` -> resolved display text, so a field measures at its
|
|
14
14
|
* painted width instead of the cached fallback. */
|
|
15
15
|
fieldValues?: ReadonlyMap<number, string>;
|
|
16
|
+
/** Header/footer tabs may be authored in the page margin, beyond body width. */
|
|
17
|
+
allowEndTabOverflow?: boolean;
|
|
16
18
|
};
|
|
17
19
|
/**
|
|
18
20
|
* Minimum horizontal room a line must offer before we treat it as usable for
|
|
@@ -987,7 +987,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
987
987
|
let tabWidth = tabResult.width;
|
|
988
988
|
const authoredEndpoint = contentX + tabWidth + followingWidth;
|
|
989
989
|
const activeContentRightEdge = maxWidth - currentLine.rightOffset;
|
|
990
|
-
const preservesAuthoredEndStop = tabResult.alignment === "end" && authoredEndpoint <= activeContentRightEdge + WIDTH_TOLERANCE;
|
|
990
|
+
const preservesAuthoredEndStop = tabResult.alignment === "end" && (options?.allowEndTabOverflow === true || authoredEndpoint <= activeContentRightEdge + WIDTH_TOLERANCE);
|
|
991
991
|
const landsOnLeftIndent = tabResult.alignment === "start" && indentLeft > 0 && Math.abs(contentX + tabWidth - indentLeft) <= WIDTH_TOLERANCE;
|
|
992
992
|
const lineRightEdgeX = indentLeft + (isFirstLine ? firstLineOffset + markerInlineWidth : 0) + currentLine.availableWidth + currentLine.leftOffset;
|
|
993
993
|
if (!preservesAuthoredEndStop && !landsOnLeftIndent && !hasFollowingTabOnLine(runs, runIndex) && canClampTabToRightEdge(tabResult.alignment, currentLine.width, hasPriorTabOnLine(runs, runIndex), followingWidth, currentLine.availableWidth) && (tabWidth > 0 || followingWidth > 0) && contentX + tabWidth + followingWidth > lineRightEdgeX + WIDTH_TOLERANCE) tabWidth = Math.max(1, lineRightEdgeX - contentX - followingWidth);
|
|
@@ -92,7 +92,7 @@ declare function resolveColumnLeft({ leftMargin, columnWidths, columns, columnIn
|
|
|
92
92
|
*/
|
|
93
93
|
declare function createPaginator(options: PaginatorOptions): {
|
|
94
94
|
/** All pages created so far. */
|
|
95
|
-
pages: Page[];
|
|
95
|
+
readonly pages: Page[];
|
|
96
96
|
/** All page states. */
|
|
97
97
|
states: PageState[];
|
|
98
98
|
/** Column width in pixels (use getColumnWidth() for current value after updates). */
|
|
@@ -12,6 +12,7 @@ const SECTION_START_PLACEMENT = {
|
|
|
12
12
|
CONTINUOUS: "continuous",
|
|
13
13
|
NEXT_PAGE: "nextPage"
|
|
14
14
|
};
|
|
15
|
+
const isEmptyCarrier = (fragment) => fragment.kind === "paragraph" && fragment.paginationRole === "empty-carrier";
|
|
15
16
|
/** Calculate active column widths, preferring authored unequal widths. */
|
|
16
17
|
function calculateColumnWidths(pageWidth, leftMargin, rightMargin, columns) {
|
|
17
18
|
if (columns.widths?.length === columns.count && columns.widths.every((width) => Number.isFinite(width) && width > 0)) return [...columns.widths];
|
|
@@ -63,6 +64,7 @@ function createPaginator(options) {
|
|
|
63
64
|
};
|
|
64
65
|
const pages = [];
|
|
65
66
|
const states = [];
|
|
67
|
+
const paginationStateByPage = /* @__PURE__ */ new WeakMap();
|
|
66
68
|
function getContentBottom() {
|
|
67
69
|
return pageSize.h - margins.bottom;
|
|
68
70
|
}
|
|
@@ -112,10 +114,30 @@ function createPaginator(options) {
|
|
|
112
114
|
columnIndex
|
|
113
115
|
});
|
|
114
116
|
}
|
|
117
|
+
function getPagePaginationState(page) {
|
|
118
|
+
const state = paginationStateByPage.get(page);
|
|
119
|
+
if (!state) panic("Paginator: page visibility state is missing");
|
|
120
|
+
return state;
|
|
121
|
+
}
|
|
122
|
+
function materializeCarrierGeometry(state) {
|
|
123
|
+
if (!state) return;
|
|
124
|
+
const paginationState = getPagePaginationState(state.page);
|
|
125
|
+
if (!paginationState.carrierGeometryNeedsMaterialization) return;
|
|
126
|
+
const x = getColumnX(0);
|
|
127
|
+
const width = columnWidths[0] ?? getContentWidth();
|
|
128
|
+
for (const fragment of state.page.fragments) {
|
|
129
|
+
if (!isEmptyCarrier(fragment)) continue;
|
|
130
|
+
fragment.x = x;
|
|
131
|
+
fragment.y = state.topMargin;
|
|
132
|
+
fragment.width = width;
|
|
133
|
+
}
|
|
134
|
+
paginationState.carrierGeometryNeedsMaterialization = false;
|
|
135
|
+
}
|
|
115
136
|
/**
|
|
116
137
|
* Create a new page and add it to the list.
|
|
117
138
|
*/
|
|
118
139
|
function createNewPage() {
|
|
140
|
+
materializeCarrierGeometry(states.at(-1));
|
|
119
141
|
if (pendingPageSize || pendingMargins) applyPendingLayout();
|
|
120
142
|
const pageNumber = pages.length + 1;
|
|
121
143
|
const logicalNumber = nextLogicalPageNumber;
|
|
@@ -150,6 +172,10 @@ function createPaginator(options) {
|
|
|
150
172
|
footnoteDemandHeight: 0,
|
|
151
173
|
trailingSpacing: 0
|
|
152
174
|
};
|
|
175
|
+
paginationStateByPage.set(page, {
|
|
176
|
+
visibleFragmentCount: 0,
|
|
177
|
+
carrierGeometryNeedsMaterialization: false
|
|
178
|
+
});
|
|
153
179
|
pages.push(page);
|
|
154
180
|
states.push(state);
|
|
155
181
|
columnRegionTop = topMargin;
|
|
@@ -242,6 +268,11 @@ function createPaginator(options) {
|
|
|
242
268
|
}
|
|
243
269
|
function commitFragment(state, fragment) {
|
|
244
270
|
consumeSharedSectionPage(state);
|
|
271
|
+
const paginationState = getPagePaginationState(state.page);
|
|
272
|
+
if (!isEmptyCarrier(fragment)) {
|
|
273
|
+
materializeCarrierGeometry(state);
|
|
274
|
+
paginationState.visibleFragmentCount += 1;
|
|
275
|
+
}
|
|
245
276
|
state.page.fragments.push(fragment);
|
|
246
277
|
}
|
|
247
278
|
function addUnflowedFragment(fragment) {
|
|
@@ -289,7 +320,7 @@ function createPaginator(options) {
|
|
|
289
320
|
function forcePageBreak(breakOptions = {}) {
|
|
290
321
|
const current = states.at(-1);
|
|
291
322
|
if (current?.page.fragments.length) consumeSharedSectionPage(current);
|
|
292
|
-
if (breakOptions.coalesceBlankPage && current && current.page.
|
|
323
|
+
if (breakOptions.coalesceBlankPage && current && getPagePaginationState(current.page).visibleFragmentCount === 0 && current.cursorY === current.topMargin) {
|
|
293
324
|
if (current.page.sectionIndex !== currentSectionIndex) {
|
|
294
325
|
retargetCurrentBlankPage();
|
|
295
326
|
return current;
|
|
@@ -312,7 +343,9 @@ function createPaginator(options) {
|
|
|
312
343
|
const pageMargins = getPageMargins(state.page.number, logicalNumber);
|
|
313
344
|
const xDelta = pageMargins.left - previousMargins.left;
|
|
314
345
|
const yDelta = pageMargins.top - previousMargins.top;
|
|
315
|
-
|
|
346
|
+
const paginationState = getPagePaginationState(state.page);
|
|
347
|
+
if (paginationState.visibleFragmentCount === 0) paginationState.carrierGeometryNeedsMaterialization = state.page.fragments.length > 0;
|
|
348
|
+
else for (const fragment of state.page.fragments) {
|
|
316
349
|
fragment.x += xDelta;
|
|
317
350
|
fragment.y += yDelta;
|
|
318
351
|
}
|
|
@@ -327,7 +360,7 @@ function createPaginator(options) {
|
|
|
327
360
|
}
|
|
328
361
|
function retargetCurrentBlankPage() {
|
|
329
362
|
const current = states.at(-1);
|
|
330
|
-
if (!current || current.page.
|
|
363
|
+
if (!current || getPagePaginationState(current.page).visibleFragmentCount > 0 || current.cursorY !== current.topMargin) return false;
|
|
331
364
|
if (pendingPageSize || pendingMargins) applyPendingLayout();
|
|
332
365
|
retargetSectionMetadata(current.page);
|
|
333
366
|
const pageMargins = getPageMargins(current.page.number, current.page.logicalNumber);
|
|
@@ -342,6 +375,7 @@ function createPaginator(options) {
|
|
|
342
375
|
else delete current.page.columns;
|
|
343
376
|
current.topMargin = topMargin;
|
|
344
377
|
current.cursorY = topMargin;
|
|
378
|
+
getPagePaginationState(current.page).carrierGeometryNeedsMaterialization = current.page.fragments.length > 0;
|
|
345
379
|
current.columnIndex = 0;
|
|
346
380
|
current.rawContentBottom = rawContentBottom;
|
|
347
381
|
current.footnoteHeight = footnoteHeightFloor;
|
|
@@ -421,7 +455,10 @@ function createPaginator(options) {
|
|
|
421
455
|
}
|
|
422
456
|
return {
|
|
423
457
|
/** All pages created so far. */
|
|
424
|
-
pages
|
|
458
|
+
get pages() {
|
|
459
|
+
materializeCarrierGeometry(states.at(-1));
|
|
460
|
+
return pages;
|
|
461
|
+
},
|
|
425
462
|
/** All page states. */
|
|
426
463
|
states,
|
|
427
464
|
/** Column width in pixels (use getColumnWidth() for current value after updates). */
|
|
@@ -97,11 +97,12 @@ function bandTopContentY(vertical, geometry) {
|
|
|
97
97
|
/**
|
|
98
98
|
* Page-absolute `[left, right]` (px) of the frame a horizontal anchor positions
|
|
99
99
|
* within. `inside`/`outsideMargin` map to the left/right margin strips (page
|
|
100
|
-
* parity is not modelled); `column
|
|
101
|
-
*
|
|
100
|
+
* parity is not modelled); `column` and an omitted horizontal anchor use the
|
|
101
|
+
* active flow-column frame, while `character` falls back to the content box
|
|
102
|
+
* because character X is unavailable. eigenpal #694.
|
|
102
103
|
*/
|
|
103
104
|
function bandHorizontalFrame(relativeTo, geometry) {
|
|
104
|
-
const { pageWidth, marginLeft, marginRight } = geometry;
|
|
105
|
+
const { pageWidth, marginLeft, marginRight, activeColumnLeft, activeColumnWidth } = geometry;
|
|
105
106
|
switch (relativeTo) {
|
|
106
107
|
case "page": return {
|
|
107
108
|
left: 0,
|
|
@@ -117,10 +118,13 @@ function bandHorizontalFrame(relativeTo, geometry) {
|
|
|
117
118
|
left: pageWidth - marginRight,
|
|
118
119
|
right: pageWidth
|
|
119
120
|
};
|
|
120
|
-
case "margin":
|
|
121
121
|
case "column":
|
|
122
|
-
case "character":
|
|
123
122
|
case void 0: return {
|
|
123
|
+
left: activeColumnLeft,
|
|
124
|
+
right: activeColumnLeft + activeColumnWidth
|
|
125
|
+
};
|
|
126
|
+
case "margin":
|
|
127
|
+
case "character": return {
|
|
124
128
|
left: marginLeft,
|
|
125
129
|
right: pageWidth - marginRight
|
|
126
130
|
};
|