@doxi/core 0.11.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/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/collab/port.d.ts +46 -0
- package/dist/collab/port.d.ts.map +1 -0
- package/dist/collab/port.js +11 -0
- package/dist/collab/port.js.map +1 -0
- package/dist/commands/block-commands.d.ts +62 -0
- package/dist/commands/block-commands.d.ts.map +1 -0
- package/dist/commands/block-commands.js +208 -0
- package/dist/commands/block-commands.js.map +1 -0
- package/dist/commands/command.d.ts +13 -0
- package/dist/commands/command.d.ts.map +1 -0
- package/dist/commands/command.js +13 -0
- package/dist/commands/command.js.map +1 -0
- package/dist/commands/edit-commands.d.ts +5 -0
- package/dist/commands/edit-commands.d.ts.map +1 -0
- package/dist/commands/edit-commands.js +147 -0
- package/dist/commands/edit-commands.js.map +1 -0
- package/dist/commands/image-commands.d.ts +31 -0
- package/dist/commands/image-commands.d.ts.map +1 -0
- package/dist/commands/image-commands.js +130 -0
- package/dist/commands/image-commands.js.map +1 -0
- package/dist/commands/index.d.ts +11 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +11 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/keymap.d.ts +34 -0
- package/dist/commands/keymap.d.ts.map +1 -0
- package/dist/commands/keymap.js +84 -0
- package/dist/commands/keymap.js.map +1 -0
- package/dist/commands/link-commands.d.ts +54 -0
- package/dist/commands/link-commands.d.ts.map +1 -0
- package/dist/commands/link-commands.js +151 -0
- package/dist/commands/link-commands.js.map +1 -0
- package/dist/commands/list-commands.d.ts +42 -0
- package/dist/commands/list-commands.d.ts.map +1 -0
- package/dist/commands/list-commands.js +316 -0
- package/dist/commands/list-commands.js.map +1 -0
- package/dist/commands/mark-commands.d.ts +53 -0
- package/dist/commands/mark-commands.d.ts.map +1 -0
- package/dist/commands/mark-commands.js +181 -0
- package/dist/commands/mark-commands.js.map +1 -0
- package/dist/commands/selection-commands.d.ts +3 -0
- package/dist/commands/selection-commands.d.ts.map +1 -0
- package/dist/commands/selection-commands.js +11 -0
- package/dist/commands/selection-commands.js.map +1 -0
- package/dist/commands/table-commands.d.ts +109 -0
- package/dist/commands/table-commands.d.ts.map +1 -0
- package/dist/commands/table-commands.js +884 -0
- package/dist/commands/table-commands.js.map +1 -0
- package/dist/history/history.d.ts +40 -0
- package/dist/history/history.d.ts.map +1 -0
- package/dist/history/history.js +139 -0
- package/dist/history/history.js.map +1 -0
- package/dist/history/index.d.ts +2 -0
- package/dist/history/index.d.ts.map +1 -0
- package/dist/history/index.js +2 -0
- package/dist/history/index.js.map +1 -0
- package/dist/html/index.d.ts +3 -0
- package/dist/html/index.d.ts.map +1 -0
- package/dist/html/index.js +3 -0
- package/dist/html/index.js.map +1 -0
- package/dist/html/parse.d.ts +4 -0
- package/dist/html/parse.d.ts.map +1 -0
- package/dist/html/parse.js +0 -0
- package/dist/html/parse.js.map +1 -0
- package/dist/html/serialize.d.ts +4 -0
- package/dist/html/serialize.d.ts.map +1 -0
- package/dist/html/serialize.js +75 -0
- package/dist/html/serialize.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/layout/index.d.ts +6 -0
- package/dist/layout/index.d.ts.map +1 -0
- package/dist/layout/index.js +6 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/layout/layout-engine.d.ts +20 -0
- package/dist/layout/layout-engine.d.ts.map +1 -0
- package/dist/layout/layout-engine.js +198 -0
- package/dist/layout/layout-engine.js.map +1 -0
- package/dist/layout/measure.d.ts +9 -0
- package/dist/layout/measure.d.ts.map +1 -0
- package/dist/layout/measure.js +37 -0
- package/dist/layout/measure.js.map +1 -0
- package/dist/layout/split-paragraph.d.ts +28 -0
- package/dist/layout/split-paragraph.d.ts.map +1 -0
- package/dist/layout/split-paragraph.js +122 -0
- package/dist/layout/split-paragraph.js.map +1 -0
- package/dist/layout/split-table.d.ts +46 -0
- package/dist/layout/split-table.d.ts.map +1 -0
- package/dist/layout/split-table.js +84 -0
- package/dist/layout/split-table.js.map +1 -0
- package/dist/layout/types.d.ts +73 -0
- package/dist/layout/types.d.ts.map +1 -0
- package/dist/layout/types.js +36 -0
- package/dist/layout/types.js.map +1 -0
- package/dist/layout/widow-orphan.d.ts +15 -0
- package/dist/layout/widow-orphan.d.ts.map +1 -0
- package/dist/layout/widow-orphan.js +14 -0
- package/dist/layout/widow-orphan.js.map +1 -0
- package/dist/model/content-expr.d.ts +32 -0
- package/dist/model/content-expr.d.ts.map +1 -0
- package/dist/model/content-expr.js +106 -0
- package/dist/model/content-expr.js.map +1 -0
- package/dist/model/fragment.d.ts +17 -0
- package/dist/model/fragment.d.ts.map +1 -0
- package/dist/model/fragment.js +44 -0
- package/dist/model/fragment.js.map +1 -0
- package/dist/model/index.d.ts +10 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/index.js +10 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/mark.d.ts +35 -0
- package/dist/model/mark.d.ts.map +1 -0
- package/dist/model/mark.js +89 -0
- package/dist/model/mark.js.map +1 -0
- package/dist/model/node-type.d.ts +36 -0
- package/dist/model/node-type.d.ts.map +1 -0
- package/dist/model/node-type.js +14 -0
- package/dist/model/node-type.js.map +1 -0
- package/dist/model/node.d.ts +36 -0
- package/dist/model/node.d.ts.map +1 -0
- package/dist/model/node.js +192 -0
- package/dist/model/node.js.map +1 -0
- package/dist/model/position.d.ts +66 -0
- package/dist/model/position.d.ts.map +1 -0
- package/dist/model/position.js +158 -0
- package/dist/model/position.js.map +1 -0
- package/dist/model/schema.d.ts +28 -0
- package/dist/model/schema.d.ts.map +1 -0
- package/dist/model/schema.js +195 -0
- package/dist/model/schema.js.map +1 -0
- package/dist/model/slice.d.ts +26 -0
- package/dist/model/slice.d.ts.map +1 -0
- package/dist/model/slice.js +56 -0
- package/dist/model/slice.js.map +1 -0
- package/dist/model/table-grid.d.ts +71 -0
- package/dist/model/table-grid.d.ts.map +1 -0
- package/dist/model/table-grid.js +130 -0
- package/dist/model/table-grid.js.map +1 -0
- package/dist/plugin/index.d.ts +3 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +3 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/plugin-key.d.ts +13 -0
- package/dist/plugin/plugin-key.d.ts.map +1 -0
- package/dist/plugin/plugin-key.js +13 -0
- package/dist/plugin/plugin-key.js.map +1 -0
- package/dist/plugin/plugin-state.d.ts +2 -0
- package/dist/plugin/plugin-state.d.ts.map +1 -0
- package/dist/plugin/plugin-state.js +3 -0
- package/dist/plugin/plugin-state.js.map +1 -0
- package/dist/plugin/plugin.d.ts +39 -0
- package/dist/plugin/plugin.d.ts.map +1 -0
- package/dist/plugin/plugin.js +10 -0
- package/dist/plugin/plugin.js.map +1 -0
- package/dist/schema/default.d.ts +163 -0
- package/dist/schema/default.d.ts.map +1 -0
- package/dist/schema/default.js +94 -0
- package/dist/schema/default.js.map +1 -0
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +2 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/serialize/index.d.ts +2 -0
- package/dist/serialize/index.d.ts.map +1 -0
- package/dist/serialize/index.js +2 -0
- package/dist/serialize/index.js.map +1 -0
- package/dist/serialize/json.d.ts +15 -0
- package/dist/serialize/json.d.ts.map +1 -0
- package/dist/serialize/json.js +23 -0
- package/dist/serialize/json.js.map +1 -0
- package/dist/state/all-selection.d.ts +11 -0
- package/dist/state/all-selection.d.ts.map +1 -0
- package/dist/state/all-selection.js +17 -0
- package/dist/state/all-selection.js.map +1 -0
- package/dist/state/cell-selection.d.ts +30 -0
- package/dist/state/cell-selection.d.ts.map +1 -0
- package/dist/state/cell-selection.js +38 -0
- package/dist/state/cell-selection.js.map +1 -0
- package/dist/state/editor-state.d.ts +46 -0
- package/dist/state/editor-state.d.ts.map +1 -0
- package/dist/state/editor-state.js +211 -0
- package/dist/state/editor-state.js.map +1 -0
- package/dist/state/index.d.ts +7 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +7 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/node-selection.d.ts +16 -0
- package/dist/state/node-selection.d.ts.map +1 -0
- package/dist/state/node-selection.js +51 -0
- package/dist/state/node-selection.js.map +1 -0
- package/dist/state/selection.d.ts +29 -0
- package/dist/state/selection.d.ts.map +1 -0
- package/dist/state/selection.js +24 -0
- package/dist/state/selection.js.map +1 -0
- package/dist/state/text-selection.d.ts +10 -0
- package/dist/state/text-selection.d.ts.map +1 -0
- package/dist/state/text-selection.js +26 -0
- package/dist/state/text-selection.js.map +1 -0
- package/dist/transform/attr-step.d.ts +16 -0
- package/dist/transform/attr-step.d.ts.map +1 -0
- package/dist/transform/attr-step.js +98 -0
- package/dist/transform/attr-step.js.map +1 -0
- package/dist/transform/index.d.ts +10 -0
- package/dist/transform/index.d.ts.map +1 -0
- package/dist/transform/index.js +10 -0
- package/dist/transform/index.js.map +1 -0
- package/dist/transform/mapping.d.ts +44 -0
- package/dist/transform/mapping.d.ts.map +1 -0
- package/dist/transform/mapping.js +101 -0
- package/dist/transform/mapping.js.map +1 -0
- package/dist/transform/mark-step.d.ts +27 -0
- package/dist/transform/mark-step.d.ts.map +1 -0
- package/dist/transform/mark-step.js +146 -0
- package/dist/transform/mark-step.js.map +1 -0
- package/dist/transform/replace-around-step.d.ts +35 -0
- package/dist/transform/replace-around-step.d.ts.map +1 -0
- package/dist/transform/replace-around-step.js +144 -0
- package/dist/transform/replace-around-step.js.map +1 -0
- package/dist/transform/replace-step.d.ts +17 -0
- package/dist/transform/replace-step.d.ts.map +1 -0
- package/dist/transform/replace-step.js +72 -0
- package/dist/transform/replace-step.js.map +1 -0
- package/dist/transform/replace.d.ts +18 -0
- package/dist/transform/replace.d.ts.map +1 -0
- package/dist/transform/replace.js +132 -0
- package/dist/transform/replace.js.map +1 -0
- package/dist/transform/set-page-meta-step.d.ts +42 -0
- package/dist/transform/set-page-meta-step.d.ts.map +1 -0
- package/dist/transform/set-page-meta-step.js +75 -0
- package/dist/transform/set-page-meta-step.js.map +1 -0
- package/dist/transform/step.d.ts +34 -0
- package/dist/transform/step.d.ts.map +1 -0
- package/dist/transform/step.js +23 -0
- package/dist/transform/step.js.map +1 -0
- package/dist/transform/transaction.d.ts +20 -0
- package/dist/transform/transaction.d.ts.map +1 -0
- package/dist/transform/transaction.js +38 -0
- package/dist/transform/transaction.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/dist/view/cell-drag.d.ts +33 -0
- package/dist/view/cell-drag.d.ts.map +1 -0
- package/dist/view/cell-drag.js +177 -0
- package/dist/view/cell-drag.js.map +1 -0
- package/dist/view/clipboard.d.ts +5 -0
- package/dist/view/clipboard.d.ts.map +1 -0
- package/dist/view/clipboard.js +97 -0
- package/dist/view/clipboard.js.map +1 -0
- package/dist/view/default-renderer.d.ts +3 -0
- package/dist/view/default-renderer.d.ts.map +1 -0
- package/dist/view/default-renderer.js +142 -0
- package/dist/view/default-renderer.js.map +1 -0
- package/dist/view/dom-spec.d.ts +11 -0
- package/dist/view/dom-spec.d.ts.map +1 -0
- package/dist/view/dom-spec.js +32 -0
- package/dist/view/dom-spec.js.map +1 -0
- package/dist/view/editor-view.d.ts +55 -0
- package/dist/view/editor-view.d.ts.map +1 -0
- package/dist/view/editor-view.js +143 -0
- package/dist/view/editor-view.js.map +1 -0
- package/dist/view/image-resize.d.ts +37 -0
- package/dist/view/image-resize.d.ts.map +1 -0
- package/dist/view/image-resize.js +191 -0
- package/dist/view/image-resize.js.map +1 -0
- package/dist/view/index.d.ts +15 -0
- package/dist/view/index.d.ts.map +1 -0
- package/dist/view/index.js +15 -0
- package/dist/view/index.js.map +1 -0
- package/dist/view/input-pipeline.d.ts +24 -0
- package/dist/view/input-pipeline.d.ts.map +1 -0
- package/dist/view/input-pipeline.js +226 -0
- package/dist/view/input-pipeline.js.map +1 -0
- package/dist/view/mutation-observer.d.ts +17 -0
- package/dist/view/mutation-observer.d.ts.map +1 -0
- package/dist/view/mutation-observer.js +62 -0
- package/dist/view/mutation-observer.js.map +1 -0
- package/dist/view/page-slots.d.ts +56 -0
- package/dist/view/page-slots.d.ts.map +1 -0
- package/dist/view/page-slots.js +230 -0
- package/dist/view/page-slots.js.map +1 -0
- package/dist/view/paginator.d.ts +17 -0
- package/dist/view/paginator.d.ts.map +1 -0
- package/dist/view/paginator.js +93 -0
- package/dist/view/paginator.js.map +1 -0
- package/dist/view/print.d.ts +42 -0
- package/dist/view/print.d.ts.map +1 -0
- package/dist/view/print.js +70 -0
- package/dist/view/print.js.map +1 -0
- package/dist/view/reconcile.d.ts +16 -0
- package/dist/view/reconcile.d.ts.map +1 -0
- package/dist/view/reconcile.js +158 -0
- package/dist/view/reconcile.js.map +1 -0
- package/dist/view/renderer.d.ts +31 -0
- package/dist/view/renderer.d.ts.map +1 -0
- package/dist/view/renderer.js +89 -0
- package/dist/view/renderer.js.map +1 -0
- package/dist/view/selection-sync.d.ts +35 -0
- package/dist/view/selection-sync.d.ts.map +1 -0
- package/dist/view/selection-sync.js +324 -0
- package/dist/view/selection-sync.js.map +1 -0
- package/dist/view/table-resize.d.ts +41 -0
- package/dist/view/table-resize.d.ts.map +1 -0
- package/dist/view/table-resize.js +216 -0
- package/dist/view/table-resize.js.map +1 -0
- package/package.json +93 -0
- package/styles/base.css +269 -0
- package/styles/dark.css +36 -0
- package/styles/light.css +13 -0
- package/styles/page.css +93 -0
- package/styles/print-a4.css +87 -0
- package/styles/print.css +88 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { renderNode } from '../view/renderer.js';
|
|
2
|
+
import { resolveDimensions, } from './types.js';
|
|
3
|
+
import { createMeasurer, destroyMeasurer, measureBlockHeight } from './measure.js';
|
|
4
|
+
import { splitParagraphByHeight } from './split-paragraph.js';
|
|
5
|
+
import { splitTableByHeight, buildPartialTable } from './split-table.js';
|
|
6
|
+
import { DEFAULT_WIDOW_ORPHAN, decideBreak } from './widow-orphan.js';
|
|
7
|
+
const SPLIT_LIMIT_PER_BLOCK = 10;
|
|
8
|
+
export class LayoutEngine {
|
|
9
|
+
dimensions;
|
|
10
|
+
doc;
|
|
11
|
+
renderer;
|
|
12
|
+
measurer;
|
|
13
|
+
widowOrphan;
|
|
14
|
+
generation = 0;
|
|
15
|
+
constructor(doc, renderer, config, options = {}) {
|
|
16
|
+
this.doc = doc;
|
|
17
|
+
this.renderer = renderer;
|
|
18
|
+
this.dimensions = resolveDimensions(config);
|
|
19
|
+
this.measurer = createMeasurer(doc, this.dimensions.contentWidthPx);
|
|
20
|
+
this.widowOrphan = options.widowOrphan ?? DEFAULT_WIDOW_ORPHAN;
|
|
21
|
+
}
|
|
22
|
+
layout(root) {
|
|
23
|
+
this.generation++;
|
|
24
|
+
const pages = [];
|
|
25
|
+
if (root.content.childCount === 0) {
|
|
26
|
+
pages.push({
|
|
27
|
+
index: 0,
|
|
28
|
+
modelFrom: 0,
|
|
29
|
+
modelTo: 0,
|
|
30
|
+
blockIndices: [],
|
|
31
|
+
contentHeightPx: 0,
|
|
32
|
+
});
|
|
33
|
+
return { pages, generation: this.generation, dimensions: this.dimensions };
|
|
34
|
+
}
|
|
35
|
+
const pageHeight = this.dimensions.contentHeightPx;
|
|
36
|
+
let pageIndex = 0;
|
|
37
|
+
let cursorHeight = 0;
|
|
38
|
+
let currentBlockIndices = [];
|
|
39
|
+
let currentModelFrom = 0;
|
|
40
|
+
let pendingSplitBefore = undefined;
|
|
41
|
+
let modelOffset = 0;
|
|
42
|
+
const flushPage = (modelTo, height, splitAfter) => {
|
|
43
|
+
const box = {
|
|
44
|
+
index: pageIndex++,
|
|
45
|
+
modelFrom: currentModelFrom,
|
|
46
|
+
modelTo,
|
|
47
|
+
blockIndices: currentBlockIndices,
|
|
48
|
+
contentHeightPx: height,
|
|
49
|
+
...(splitAfter ? { splitAfter } : {}),
|
|
50
|
+
...(pendingSplitBefore ? { splitBefore: pendingSplitBefore } : {}),
|
|
51
|
+
};
|
|
52
|
+
pages.push(box);
|
|
53
|
+
currentBlockIndices = [];
|
|
54
|
+
currentModelFrom = modelTo;
|
|
55
|
+
cursorHeight = 0;
|
|
56
|
+
pendingSplitBefore = undefined;
|
|
57
|
+
};
|
|
58
|
+
for (let i = 0; i < root.content.childCount; i++) {
|
|
59
|
+
const block = root.content.child(i);
|
|
60
|
+
let workingBlock = block;
|
|
61
|
+
let workingNodeStartOffset = modelOffset;
|
|
62
|
+
let splitsRemaining = SPLIT_LIMIT_PER_BLOCK;
|
|
63
|
+
// Table-split bookkeeping: when a table has already been partially placed
|
|
64
|
+
// on a previous page (via splitTableByHeight), `tableTail` holds the
|
|
65
|
+
// remaining body row indices to render on this/subsequent pages, and
|
|
66
|
+
// `tableHeader` holds the header row indices that repeat on every page.
|
|
67
|
+
let tableTail = null;
|
|
68
|
+
let tableHeader = [];
|
|
69
|
+
while (true) {
|
|
70
|
+
if (workingBlock.type.name === 'page_break') {
|
|
71
|
+
// Forced break: place the marker on the current page (it has near-zero
|
|
72
|
+
// visual height but a small CSS-induced height; honor whatever measure
|
|
73
|
+
// returns), then flush regardless of remaining space.
|
|
74
|
+
const h = this.measureBlock(workingBlock);
|
|
75
|
+
currentBlockIndices.push(i);
|
|
76
|
+
cursorHeight += h;
|
|
77
|
+
modelOffset = workingNodeStartOffset + workingBlock.nodeSize;
|
|
78
|
+
flushPage(modelOffset, cursorHeight);
|
|
79
|
+
break; // exits the while-true; outer for-loop moves to the next block
|
|
80
|
+
}
|
|
81
|
+
const blockHeight = this.measureBlock(workingBlock);
|
|
82
|
+
const remaining = pageHeight - cursorHeight;
|
|
83
|
+
if (blockHeight <= remaining) {
|
|
84
|
+
currentBlockIndices.push(i);
|
|
85
|
+
cursorHeight += blockHeight;
|
|
86
|
+
modelOffset = workingNodeStartOffset + workingBlock.nodeSize;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
// ---- Table row split path ------------------------------------------
|
|
90
|
+
// Try to split a table BEFORE the "accept whole when alone" fallback.
|
|
91
|
+
// A tall table on an otherwise-empty page should still be split into
|
|
92
|
+
// multiple pages; it should never be dumped whole if it has more rows
|
|
93
|
+
// than fit in the page area.
|
|
94
|
+
if (workingBlock.type.name === 'table' && splitsRemaining > 0) {
|
|
95
|
+
const tableResult = splitTableByHeight(this.measurer, this.doc, workingBlock, this.renderer, remaining);
|
|
96
|
+
if (tableResult) {
|
|
97
|
+
// Place a portion of the table on the current page; the rest
|
|
98
|
+
// becomes the next page's first block.
|
|
99
|
+
currentBlockIndices.push(i);
|
|
100
|
+
const headBodyForThisPage = tableResult.headRowIndices;
|
|
101
|
+
const splitAfter = {
|
|
102
|
+
blockIndex: i,
|
|
103
|
+
charOffset: 0,
|
|
104
|
+
tableSplit: {
|
|
105
|
+
headerRowIndices: tableResult.headerRowIndices,
|
|
106
|
+
bodyRowIndices: headBodyForThisPage,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
// The next page's splitBefore needs the tail body rows. We point
|
|
110
|
+
// modelOffset at the end of the FULL table block — once the tail
|
|
111
|
+
// is rendered we don't re-advance modelOffset.
|
|
112
|
+
const tailBody = tableResult.tailRowIndices;
|
|
113
|
+
// Flush the current page with the table-split splitAfter.
|
|
114
|
+
flushPage(workingNodeStartOffset + block.nodeSize, cursorHeight + tableResult.headHeightPx, splitAfter);
|
|
115
|
+
// Record continuation context: next page will render the tail.
|
|
116
|
+
tableHeader = tableResult.headerRowIndices.slice();
|
|
117
|
+
tableTail = tailBody.slice();
|
|
118
|
+
const tailSplitBefore = {
|
|
119
|
+
blockIndex: i,
|
|
120
|
+
charOffset: 0,
|
|
121
|
+
tableSplit: {
|
|
122
|
+
headerRowIndices: tableHeader,
|
|
123
|
+
bodyRowIndices: tableTail,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
pendingSplitBefore = tailSplitBefore;
|
|
127
|
+
// Replace workingBlock with a synthesized partial table (header
|
|
128
|
+
// rows + remaining body rows). This is what subsequent iterations
|
|
129
|
+
// measure against — it's the SHAPE of what we'll render next.
|
|
130
|
+
workingBlock = buildPartialTable(workingBlock, tableHeader, tableTail);
|
|
131
|
+
// modelOffset for the original block has already been "spent" by
|
|
132
|
+
// the flush above (we passed workingNodeStartOffset + block.nodeSize
|
|
133
|
+
// as the modelTo). Adjust workingNodeStartOffset so that when the
|
|
134
|
+
// tail finally fits, the `modelOffset = workingNodeStartOffset +
|
|
135
|
+
// workingBlock.nodeSize` line lands on the same value (no-op).
|
|
136
|
+
workingNodeStartOffset = (workingNodeStartOffset + block.nodeSize) - workingBlock.nodeSize;
|
|
137
|
+
splitsRemaining--;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
// Table split returned null — fall through to push-whole.
|
|
141
|
+
}
|
|
142
|
+
// Try in-block split. Only meaningful for inline-content blocks.
|
|
143
|
+
let split = null;
|
|
144
|
+
if (!workingBlock.isAtom &&
|
|
145
|
+
!workingBlock.isText &&
|
|
146
|
+
workingBlock.type.name !== 'table' &&
|
|
147
|
+
splitsRemaining > 0) {
|
|
148
|
+
split = splitParagraphByHeight(this.measurer, this.doc, workingBlock, this.renderer, remaining);
|
|
149
|
+
}
|
|
150
|
+
if (split && decideBreak(split.headLines, split.tailLines, this.widowOrphan) === 'split-here') {
|
|
151
|
+
// Place head on current page; flush; tail continues as next-page first block.
|
|
152
|
+
currentBlockIndices.push(i);
|
|
153
|
+
const splitAfter = { blockIndex: i, charOffset: split.charOffset };
|
|
154
|
+
const headSize = split.head.nodeSize;
|
|
155
|
+
flushPage(workingNodeStartOffset + headSize, cursorHeight + this.measureBlock(split.head), splitAfter);
|
|
156
|
+
pendingSplitBefore = { blockIndex: i, charOffset: split.charOffset };
|
|
157
|
+
workingBlock = split.tail;
|
|
158
|
+
// The next iteration places head's nodeSize remainder on the new page.
|
|
159
|
+
// Adjust workingNodeStartOffset so modelOffset accounting stays correct.
|
|
160
|
+
workingNodeStartOffset = workingNodeStartOffset + headSize - (workingBlock.nodeSize - (block.nodeSize - headSize));
|
|
161
|
+
// Simpler accounting: modelOffset becomes workingNodeStartOffset on next iteration.
|
|
162
|
+
// We need final modelOffset for THIS block = workingNodeStartOffset + workingBlock.nodeSize.
|
|
163
|
+
// The cleanest: track endOffset of the FULL block = (original modelOffset + block.nodeSize)
|
|
164
|
+
// and set modelOffset to that when we finally place the tail.
|
|
165
|
+
splitsRemaining--;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
// Can't split (or split rejected). If the page is empty, we have no
|
|
169
|
+
// choice but to accept the oversized block whole (otherwise it would
|
|
170
|
+
// loop forever). Otherwise push the whole block to the next page.
|
|
171
|
+
if (currentBlockIndices.length === 0) {
|
|
172
|
+
currentBlockIndices.push(i);
|
|
173
|
+
cursorHeight += blockHeight;
|
|
174
|
+
modelOffset = workingNodeStartOffset + workingBlock.nodeSize;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
flushPage(modelOffset, cursorHeight);
|
|
178
|
+
// workingBlock and workingNodeStartOffset unchanged; loop retries placement on the new (empty) page.
|
|
179
|
+
}
|
|
180
|
+
// Avoid unused-variable warnings on the table continuation bookkeeping
|
|
181
|
+
// (the values are consumed via pendingSplitBefore at flush time).
|
|
182
|
+
void tableTail;
|
|
183
|
+
void tableHeader;
|
|
184
|
+
}
|
|
185
|
+
if (currentBlockIndices.length > 0 || pages.length === 0) {
|
|
186
|
+
flushPage(modelOffset, cursorHeight);
|
|
187
|
+
}
|
|
188
|
+
return { pages, generation: this.generation, dimensions: this.dimensions };
|
|
189
|
+
}
|
|
190
|
+
destroy() {
|
|
191
|
+
destroyMeasurer(this.measurer);
|
|
192
|
+
}
|
|
193
|
+
measureBlock(block) {
|
|
194
|
+
const el = renderNode(this.doc, this.renderer, block, 0);
|
|
195
|
+
return measureBlockHeight(this.measurer, el);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=layout-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout-engine.js","sourceRoot":"","sources":["../../src/layout/layout-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface Measurer {
|
|
2
|
+
readonly container: HTMLElement;
|
|
3
|
+
readonly doc: Document;
|
|
4
|
+
readonly widthPx: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function createMeasurer(doc: Document, widthPx: number): Measurer;
|
|
7
|
+
export declare function measureBlockHeight(m: Measurer, el: HTMLElement): number;
|
|
8
|
+
export declare function destroyMeasurer(m: Measurer): void;
|
|
9
|
+
//# sourceMappingURL=measure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measure.d.ts","sourceRoot":"","sources":["../../src/layout/measure.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAA;IAC/B,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,QAAQ,CAWvE;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,WAAW,GAAG,MAAM,CAkBvE;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI,CAEjD"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function createMeasurer(doc, widthPx) {
|
|
2
|
+
const container = doc.createElement('div');
|
|
3
|
+
container.setAttribute('aria-hidden', 'true');
|
|
4
|
+
container.style.position = 'absolute';
|
|
5
|
+
container.style.top = '0';
|
|
6
|
+
container.style.left = '-9999px';
|
|
7
|
+
container.style.width = `${widthPx}px`;
|
|
8
|
+
container.style.visibility = 'hidden';
|
|
9
|
+
container.style.pointerEvents = 'none';
|
|
10
|
+
doc.body.appendChild(container);
|
|
11
|
+
return { container, doc, widthPx };
|
|
12
|
+
}
|
|
13
|
+
export function measureBlockHeight(m, el) {
|
|
14
|
+
m.container.appendChild(el);
|
|
15
|
+
// Force layout. getBoundingClientRect triggers a synchronous layout pass in
|
|
16
|
+
// real browsers. happy-dom does not actually compute layout — rect.height
|
|
17
|
+
// and offsetHeight stay at 0 — so we fall back to the resolved CSS height
|
|
18
|
+
// from getComputedStyle (which happy-dom does honor) for test determinism.
|
|
19
|
+
const rect = el.getBoundingClientRect();
|
|
20
|
+
let h = rect.height || el.offsetHeight || 0;
|
|
21
|
+
if (h === 0) {
|
|
22
|
+
const view = m.doc.defaultView;
|
|
23
|
+
if (view) {
|
|
24
|
+
const cs = view.getComputedStyle(el).height;
|
|
25
|
+
const parsed = parseFloat(cs);
|
|
26
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
27
|
+
h = parsed;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
m.container.removeChild(el);
|
|
31
|
+
return h;
|
|
32
|
+
}
|
|
33
|
+
export function destroyMeasurer(m) {
|
|
34
|
+
if (m.container.parentNode)
|
|
35
|
+
m.container.parentNode.removeChild(m.container);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=measure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measure.js","sourceRoot":"","sources":["../../src/layout/measure.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,cAAc,CAAC,GAAa,EAAE,OAAe;IAC3D,MAAM,SAAS,GAAG,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IAC1C,SAAS,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAA;IAC7C,SAAS,CAAC,KAAK,CAAC,QAAQ,GAAG,UAAU,CAAA;IACrC,SAAS,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAA;IACzB,SAAS,CAAC,KAAK,CAAC,IAAI,GAAG,SAAS,CAAA;IAChC,SAAS,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,OAAO,IAAI,CAAA;IACtC,SAAS,CAAC,KAAK,CAAC,UAAU,GAAG,QAAQ,CAAA;IACrC,SAAS,CAAC,KAAK,CAAC,aAAa,GAAG,MAAM,CAAA;IACtC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAA;IAC/B,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,CAAA;AACpC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,CAAW,EAAE,EAAe;IAC7D,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;IAC3B,4EAA4E;IAC5E,0EAA0E;IAC1E,0EAA0E;IAC1E,2EAA2E;IAC3E,MAAM,IAAI,GAAG,EAAE,CAAC,qBAAqB,EAAE,CAAA;IACvC,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,YAAY,IAAI,CAAC,CAAA;IAC3C,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,WAAW,CAAA;QAC9B,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,EAAE,GAAG,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,MAAM,CAAA;YAC3C,MAAM,MAAM,GAAG,UAAU,CAAC,EAAE,CAAC,CAAA;YAC7B,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC;gBAAE,CAAC,GAAG,MAAM,CAAA;QACvD,CAAC;IACH,CAAC;IACD,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;IAC3B,OAAO,CAAC,CAAA;AACV,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,CAAW;IACzC,IAAI,CAAC,CAAC,SAAS,CAAC,UAAU;QAAE,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;AAC7E,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { DxNode } from '../model/node.js';
|
|
2
|
+
import type { Renderer } from '../view/renderer.js';
|
|
3
|
+
import type { Measurer } from './measure.js';
|
|
4
|
+
export interface LineRect {
|
|
5
|
+
readonly topPx: number;
|
|
6
|
+
readonly bottomPx: number;
|
|
7
|
+
/** Inclusive character offset (in the block's content-coordinate space) where this line starts. */
|
|
8
|
+
readonly charStart: number;
|
|
9
|
+
/** Exclusive character offset where this line ends. */
|
|
10
|
+
readonly charEnd: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function measureLineRects(m: Measurer, blockEl: HTMLElement): LineRect[];
|
|
13
|
+
/**
|
|
14
|
+
* Try to split a paragraph so that the head fits within maxHeightPx and the
|
|
15
|
+
* tail contains the remainder. Returns null when no split point is acceptable
|
|
16
|
+
* (e.g., the paragraph's first line is already taller than maxHeightPx).
|
|
17
|
+
*/
|
|
18
|
+
export interface SplitResult {
|
|
19
|
+
readonly head: DxNode;
|
|
20
|
+
readonly tail: DxNode;
|
|
21
|
+
/** Char-content offset of the split point in the original paragraph. */
|
|
22
|
+
readonly charOffset: number;
|
|
23
|
+
/** Lines on the head (≥1) and tail (≥1). */
|
|
24
|
+
readonly headLines: number;
|
|
25
|
+
readonly tailLines: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function splitParagraphByHeight(m: Measurer, doc: Document, paragraph: DxNode, renderer: Renderer, maxHeightPx: number): SplitResult | null;
|
|
28
|
+
//# sourceMappingURL=split-paragraph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"split-paragraph.d.ts","sourceRoot":"","sources":["../../src/layout/split-paragraph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAE5C,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,mGAAmG;IACnG,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,GAAG,QAAQ,EAAE,CA0C9E;AA2CD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,4CAA4C;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,wBAAgB,sBAAsB,CACpC,CAAC,EAAE,QAAQ,EACX,GAAG,EAAE,QAAQ,EACb,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,MAAM,GAClB,WAAW,GAAG,IAAI,CA6BpB"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { renderNode } from '../view/renderer.js';
|
|
2
|
+
export function measureLineRects(m, blockEl) {
|
|
3
|
+
m.container.appendChild(blockEl);
|
|
4
|
+
try {
|
|
5
|
+
const lines = [];
|
|
6
|
+
const text = collectText(blockEl);
|
|
7
|
+
if (text.length === 0)
|
|
8
|
+
return lines;
|
|
9
|
+
const range = blockEl.ownerDocument.createRange();
|
|
10
|
+
let currentTop = null;
|
|
11
|
+
let currentStart = 0;
|
|
12
|
+
for (let i = 0; i < text.length; i++) {
|
|
13
|
+
const placed = placeRange(range, blockEl, i, i + 1);
|
|
14
|
+
if (!placed)
|
|
15
|
+
continue;
|
|
16
|
+
const rect = firstRect(range);
|
|
17
|
+
if (!rect)
|
|
18
|
+
continue;
|
|
19
|
+
if (currentTop === null) {
|
|
20
|
+
currentTop = rect.top;
|
|
21
|
+
currentStart = i;
|
|
22
|
+
}
|
|
23
|
+
else if (rect.top !== currentTop) {
|
|
24
|
+
lines.push({
|
|
25
|
+
topPx: currentTop,
|
|
26
|
+
bottomPx: rect.top,
|
|
27
|
+
charStart: currentStart,
|
|
28
|
+
charEnd: i,
|
|
29
|
+
});
|
|
30
|
+
currentTop = rect.top;
|
|
31
|
+
currentStart = i;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (currentTop !== null) {
|
|
35
|
+
const lastRect = firstRect(range);
|
|
36
|
+
lines.push({
|
|
37
|
+
topPx: currentTop,
|
|
38
|
+
bottomPx: lastRect ? lastRect.bottom : currentTop,
|
|
39
|
+
charStart: currentStart,
|
|
40
|
+
charEnd: text.length,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return lines;
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
if (blockEl.parentNode === m.container)
|
|
47
|
+
m.container.removeChild(blockEl);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function collectText(el) {
|
|
51
|
+
let out = '';
|
|
52
|
+
const walker = el.ownerDocument.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
53
|
+
let n;
|
|
54
|
+
while ((n = walker.nextNode())) {
|
|
55
|
+
out += n.data;
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
function placeRange(range, blockEl, charFrom, charTo) {
|
|
60
|
+
const start = locateText(blockEl, charFrom);
|
|
61
|
+
const end = locateText(blockEl, charTo);
|
|
62
|
+
if (!start || !end)
|
|
63
|
+
return false;
|
|
64
|
+
range.setStart(start.node, start.offset);
|
|
65
|
+
range.setEnd(end.node, end.offset);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
function locateText(blockEl, charOffset) {
|
|
69
|
+
const walker = blockEl.ownerDocument.createTreeWalker(blockEl, NodeFilter.SHOW_TEXT);
|
|
70
|
+
let acc = 0;
|
|
71
|
+
let n;
|
|
72
|
+
while ((n = walker.nextNode())) {
|
|
73
|
+
const text = n;
|
|
74
|
+
const len = text.data.length;
|
|
75
|
+
if (charOffset <= acc + len) {
|
|
76
|
+
return { node: text, offset: charOffset - acc };
|
|
77
|
+
}
|
|
78
|
+
acc += len;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
function firstRect(range) {
|
|
83
|
+
const rects = range.getClientRects();
|
|
84
|
+
if (rects.length === 0)
|
|
85
|
+
return null;
|
|
86
|
+
const r = rects[0];
|
|
87
|
+
return { top: r.top, bottom: r.bottom };
|
|
88
|
+
}
|
|
89
|
+
export function splitParagraphByHeight(m, doc, paragraph, renderer, maxHeightPx) {
|
|
90
|
+
const rendered = renderNode(doc, renderer, paragraph, 0);
|
|
91
|
+
const lines = measureLineRects(m, rendered);
|
|
92
|
+
if (lines.length === 0)
|
|
93
|
+
return null;
|
|
94
|
+
if (lines[0].bottomPx - lines[0].topPx > maxHeightPx) {
|
|
95
|
+
return null; // First line alone is taller than the page area.
|
|
96
|
+
}
|
|
97
|
+
// Find the last line that fits.
|
|
98
|
+
const firstTop = lines[0].topPx;
|
|
99
|
+
let lastFitIndex = -1;
|
|
100
|
+
for (let i = 0; i < lines.length; i++) {
|
|
101
|
+
const line = lines[i];
|
|
102
|
+
if (line.bottomPx - firstTop <= maxHeightPx) {
|
|
103
|
+
lastFitIndex = i;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (lastFitIndex < 0 || lastFitIndex === lines.length - 1)
|
|
110
|
+
return null;
|
|
111
|
+
const splitOffset = lines[lastFitIndex].charEnd;
|
|
112
|
+
const head = paragraph.cut(0, splitOffset);
|
|
113
|
+
const tail = paragraph.cut(splitOffset, paragraph.content.size);
|
|
114
|
+
return {
|
|
115
|
+
head,
|
|
116
|
+
tail,
|
|
117
|
+
charOffset: splitOffset,
|
|
118
|
+
headLines: lastFitIndex + 1,
|
|
119
|
+
tailLines: lines.length - 1 - lastFitIndex,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=split-paragraph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"split-paragraph.js","sourceRoot":"","sources":["../../src/layout/split-paragraph.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { DxNode } from '../model/node.js';
|
|
2
|
+
import type { Renderer } from '../view/renderer.js';
|
|
3
|
+
import { type Measurer } from './measure.js';
|
|
4
|
+
/**
|
|
5
|
+
* Result of greedy table-row pagination for the current page.
|
|
6
|
+
*
|
|
7
|
+
* The row indices are absolute (i.e. positions in `table.content`), NOT
|
|
8
|
+
* relative to the body slice — so callers can use them directly when
|
|
9
|
+
* reconstructing partial tables for rendering.
|
|
10
|
+
*/
|
|
11
|
+
export interface SplitTableResult {
|
|
12
|
+
/** Body row indices that fit on the current page (after header rows). */
|
|
13
|
+
readonly headRowIndices: number[];
|
|
14
|
+
/** Body row indices that overflow to the next page. */
|
|
15
|
+
readonly tailRowIndices: number[];
|
|
16
|
+
/** Header row indices (always repeated on continuation pages). */
|
|
17
|
+
readonly headerRowIndices: number[];
|
|
18
|
+
/** Measured height of the head portion (header + accepted body rows). */
|
|
19
|
+
readonly headHeightPx: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Greedy fit: place header rows (rows whose attrs.header === true, in the
|
|
23
|
+
* contiguous run from the top of the table) on the page, then pack body
|
|
24
|
+
* rows one-at-a-time until adding another would exceed `remainingHeightPx`.
|
|
25
|
+
*
|
|
26
|
+
* Returns null when no body rows fit (caller should push the whole table
|
|
27
|
+
* to the next page).
|
|
28
|
+
*
|
|
29
|
+
* Constraints:
|
|
30
|
+
* - Whole-row units only — no cell-content splitting within a row.
|
|
31
|
+
* - At least one body row must fit OR we return null.
|
|
32
|
+
*
|
|
33
|
+
* Measurement strategy: we render a synthetic `table` node containing
|
|
34
|
+
* the header rows + the candidate body rows, and use the same Measurer
|
|
35
|
+
* that splitParagraphByHeight uses. The synthetic table preserves the
|
|
36
|
+
* source table's attrs so styling (border-collapse, widths, etc.) matches
|
|
37
|
+
* the real one.
|
|
38
|
+
*/
|
|
39
|
+
export declare function splitTableByHeight(measurer: Measurer, doc: Document, table: DxNode, renderer: Renderer, remainingHeightPx: number): SplitTableResult | null;
|
|
40
|
+
/**
|
|
41
|
+
* Construct a table node containing only the indicated rows (header subset
|
|
42
|
+
* + body subset), preserving the source table's type/attrs. Used by the
|
|
43
|
+
* measurement path here and by the Paginator when rendering partial tables.
|
|
44
|
+
*/
|
|
45
|
+
export declare function buildPartialTable(table: DxNode, headerIdx: ReadonlyArray<number>, bodyIdx: ReadonlyArray<number>): DxNode;
|
|
46
|
+
//# sourceMappingURL=split-table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"split-table.d.ts","sourceRoot":"","sources":["../../src/layout/split-table.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAE9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,EAAsB,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAA;AAEhE;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,CAAA;IACjC,uDAAuD;IACvD,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,CAAA;IACjC,kEAAkE;IAClE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAA;IACnC,yEAAyE;IACzE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,QAAQ,EAClB,iBAAiB,EAAE,MAAM,GACxB,gBAAgB,GAAG,IAAI,CA+CzB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,EAChC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,GAC7B,MAAM,CAKR"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Fragment } from '../model/fragment.js';
|
|
2
|
+
import { renderNode } from '../view/renderer.js';
|
|
3
|
+
import { measureBlockHeight } from './measure.js';
|
|
4
|
+
/**
|
|
5
|
+
* Greedy fit: place header rows (rows whose attrs.header === true, in the
|
|
6
|
+
* contiguous run from the top of the table) on the page, then pack body
|
|
7
|
+
* rows one-at-a-time until adding another would exceed `remainingHeightPx`.
|
|
8
|
+
*
|
|
9
|
+
* Returns null when no body rows fit (caller should push the whole table
|
|
10
|
+
* to the next page).
|
|
11
|
+
*
|
|
12
|
+
* Constraints:
|
|
13
|
+
* - Whole-row units only — no cell-content splitting within a row.
|
|
14
|
+
* - At least one body row must fit OR we return null.
|
|
15
|
+
*
|
|
16
|
+
* Measurement strategy: we render a synthetic `table` node containing
|
|
17
|
+
* the header rows + the candidate body rows, and use the same Measurer
|
|
18
|
+
* that splitParagraphByHeight uses. The synthetic table preserves the
|
|
19
|
+
* source table's attrs so styling (border-collapse, widths, etc.) matches
|
|
20
|
+
* the real one.
|
|
21
|
+
*/
|
|
22
|
+
export function splitTableByHeight(measurer, doc, table, renderer, remainingHeightPx) {
|
|
23
|
+
// 1. Partition rows into header (contiguous run from the top with
|
|
24
|
+
// attrs.header === true) and body.
|
|
25
|
+
const totalRows = table.content.childCount;
|
|
26
|
+
const headerRowIndices = [];
|
|
27
|
+
for (let i = 0; i < totalRows; i++) {
|
|
28
|
+
const row = table.content.child(i);
|
|
29
|
+
if (row.attrs.header === true)
|
|
30
|
+
headerRowIndices.push(i);
|
|
31
|
+
else
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
const bodyRowIndices = [];
|
|
35
|
+
for (let i = headerRowIndices.length; i < totalRows; i++) {
|
|
36
|
+
bodyRowIndices.push(i);
|
|
37
|
+
}
|
|
38
|
+
if (bodyRowIndices.length === 0)
|
|
39
|
+
return null;
|
|
40
|
+
// 2. Greedy: extend the accepted body row count by 1 until the measured
|
|
41
|
+
// height exceeds the budget.
|
|
42
|
+
const acceptedBody = [];
|
|
43
|
+
let lastFitHeight = 0;
|
|
44
|
+
for (const bodyIdx of bodyRowIndices) {
|
|
45
|
+
const candidate = [...acceptedBody, bodyIdx];
|
|
46
|
+
const partial = buildPartialTable(table, headerRowIndices, candidate);
|
|
47
|
+
const el = renderNode(doc, renderer, partial, 0);
|
|
48
|
+
const h = measureBlockHeight(measurer, el);
|
|
49
|
+
if (h <= remainingHeightPx) {
|
|
50
|
+
acceptedBody.push(bodyIdx);
|
|
51
|
+
lastFitHeight = h;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (acceptedBody.length === 0)
|
|
58
|
+
return null;
|
|
59
|
+
const tail = [];
|
|
60
|
+
for (const idx of bodyRowIndices) {
|
|
61
|
+
if (!acceptedBody.includes(idx))
|
|
62
|
+
tail.push(idx);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
headRowIndices: acceptedBody,
|
|
66
|
+
tailRowIndices: tail,
|
|
67
|
+
headerRowIndices,
|
|
68
|
+
headHeightPx: lastFitHeight,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Construct a table node containing only the indicated rows (header subset
|
|
73
|
+
* + body subset), preserving the source table's type/attrs. Used by the
|
|
74
|
+
* measurement path here and by the Paginator when rendering partial tables.
|
|
75
|
+
*/
|
|
76
|
+
export function buildPartialTable(table, headerIdx, bodyIdx) {
|
|
77
|
+
const rows = [];
|
|
78
|
+
for (const i of headerIdx)
|
|
79
|
+
rows.push(table.content.child(i));
|
|
80
|
+
for (const i of bodyIdx)
|
|
81
|
+
rows.push(table.content.child(i));
|
|
82
|
+
return table.copy(Fragment.from(rows));
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=split-table.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"split-table.js","sourceRoot":"","sources":["../../src/layout/split-table.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @doxi/core/layout — shared types for the pagination foundation.
|
|
3
|
+
*
|
|
4
|
+
* PageBreakModel is the derived data structure produced by the layout engine
|
|
5
|
+
* (Track L) and consumed by the page renderer (Track C). It is NOT part of the
|
|
6
|
+
* document model — it is purely a view-of-state computed from the doc + a
|
|
7
|
+
* page configuration.
|
|
8
|
+
*/
|
|
9
|
+
export type PageSize = 'Letter' | 'A4' | {
|
|
10
|
+
widthPx: number;
|
|
11
|
+
heightPx: number;
|
|
12
|
+
};
|
|
13
|
+
export interface PageMargins {
|
|
14
|
+
readonly topPx: number;
|
|
15
|
+
readonly rightPx: number;
|
|
16
|
+
readonly bottomPx: number;
|
|
17
|
+
readonly leftPx: number;
|
|
18
|
+
}
|
|
19
|
+
export interface PageConfig {
|
|
20
|
+
readonly size: PageSize;
|
|
21
|
+
readonly margins: PageMargins;
|
|
22
|
+
}
|
|
23
|
+
export declare const DEFAULT_PAGE_CONFIG: PageConfig;
|
|
24
|
+
export interface ResolvedPageDimensions {
|
|
25
|
+
readonly widthPx: number;
|
|
26
|
+
readonly heightPx: number;
|
|
27
|
+
readonly contentWidthPx: number;
|
|
28
|
+
readonly contentHeightPx: number;
|
|
29
|
+
readonly margins: PageMargins;
|
|
30
|
+
}
|
|
31
|
+
export interface SplitPoint {
|
|
32
|
+
/** Index of the block (in doc.content.children) being split. */
|
|
33
|
+
readonly blockIndex: number;
|
|
34
|
+
/**
|
|
35
|
+
* For paragraph splits: char-content offset within the block where the
|
|
36
|
+
* split happens. For table splits this field is unused (zero) and the
|
|
37
|
+
* `tableSplit` payload below carries the per-row index lists.
|
|
38
|
+
*/
|
|
39
|
+
readonly charOffset: number;
|
|
40
|
+
/**
|
|
41
|
+
* Set when this SplitPoint describes a table split. The renderer (Paginator)
|
|
42
|
+
* uses `headerRowIndices` + `bodyRowIndices` to assemble a partial table
|
|
43
|
+
* containing the header rows on every page-portion plus the chosen body
|
|
44
|
+
* subset for that page.
|
|
45
|
+
*/
|
|
46
|
+
readonly tableSplit?: TableSplitInfo;
|
|
47
|
+
}
|
|
48
|
+
export interface TableSplitInfo {
|
|
49
|
+
/** Row indices (in the original table.content) that are header rows.
|
|
50
|
+
* Repeated on every page-portion of the split table. */
|
|
51
|
+
readonly headerRowIndices: ReadonlyArray<number>;
|
|
52
|
+
/** Body row indices to render on the page this SplitPoint refers to. */
|
|
53
|
+
readonly bodyRowIndices: ReadonlyArray<number>;
|
|
54
|
+
}
|
|
55
|
+
export interface PageBox {
|
|
56
|
+
readonly index: number;
|
|
57
|
+
readonly modelFrom: number;
|
|
58
|
+
readonly modelTo: number;
|
|
59
|
+
/** Doc-content indices of the blocks placed on this page. */
|
|
60
|
+
readonly blockIndices: ReadonlyArray<number>;
|
|
61
|
+
readonly contentHeightPx: number;
|
|
62
|
+
/** When set, the LAST block in blockIndices is rendered up to charOffset only. */
|
|
63
|
+
readonly splitAfter?: SplitPoint;
|
|
64
|
+
/** When set, the FIRST block in blockIndices is rendered FROM charOffset. */
|
|
65
|
+
readonly splitBefore?: SplitPoint;
|
|
66
|
+
}
|
|
67
|
+
export interface PageBreakModel {
|
|
68
|
+
readonly pages: ReadonlyArray<PageBox>;
|
|
69
|
+
readonly generation: number;
|
|
70
|
+
readonly dimensions: ResolvedPageDimensions;
|
|
71
|
+
}
|
|
72
|
+
export declare function resolveDimensions(config: PageConfig): ResolvedPageDimensions;
|
|
73
|
+
//# sourceMappingURL=types.d.ts.map
|