@portone/docx-editor 0.5.1 → 0.6.1
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/CHANGELOG.md +47 -0
- package/CONTRIBUTING.md +8 -2
- package/README.md +1 -1
- package/dist/DocxEditor.js +87 -29
- package/dist/docx/exportDocx.d.ts +2 -1
- package/dist/docx/exportDocx.js +3 -9
- package/dist/docx/headersFooters.js +35 -23
- package/dist/docx/identities.d.ts +28 -0
- package/dist/docx/identities.js +42 -10
- package/dist/docx/importDocx.js +25 -26
- package/dist/docx/importParagraph.d.ts +1 -4
- package/dist/docx/importParagraph.js +4 -18
- package/dist/docx/invariants.d.ts +1 -1
- package/dist/docx/invariants.js +45 -4
- package/dist/docx/notes/newNote.d.ts +39 -0
- package/dist/docx/notes/newNote.js +122 -0
- package/dist/docx/notes/numbering.d.ts +27 -0
- package/dist/docx/notes/numbering.js +120 -0
- package/dist/docx/notes/reading.d.ts +58 -0
- package/dist/docx/notes/reading.js +98 -0
- package/dist/docx/notes/writing.d.ts +19 -0
- package/dist/docx/notes/writing.js +96 -0
- package/dist/docx/partPlan.d.ts +3 -0
- package/dist/docx/partPlanners.d.ts +13 -0
- package/dist/docx/partPlanners.js +48 -0
- package/dist/docx/sections.d.ts +5 -0
- package/dist/docx/sections.js +7 -2
- package/dist/docx/serializeParagraph.js +7 -2
- package/dist/docx/session.d.ts +5 -0
- package/dist/docx/session.js +6 -0
- package/dist/docx/storyParts.d.ts +112 -0
- package/dist/docx/storyParts.js +289 -0
- package/dist/editor/clipboard/htmlReader.d.ts +24 -4
- package/dist/editor/clipboard/htmlReader.js +39 -4
- package/dist/editor/clipboard/internalChannel.d.ts +30 -3
- package/dist/editor/clipboard/internalChannel.js +43 -3
- package/dist/editor/clipboard/normalizers.d.ts +35 -8
- package/dist/editor/clipboard/normalizers.js +60 -11
- package/dist/editor/clipboard/plugin.js +42 -22
- package/dist/editor/commands/comments/editing.d.ts +4 -1
- package/dist/editor/commands/comments/editing.js +1 -3
- package/dist/editor/commands/comments/model.d.ts +7 -0
- package/dist/editor/commands/index.d.ts +1 -0
- package/dist/editor/commands/index.js +18 -0
- package/dist/editor/commands/noteCommands.d.ts +47 -0
- package/dist/editor/commands/noteCommands.js +95 -0
- package/dist/editor/commands/noteQueries.d.ts +22 -3
- package/dist/editor/commands/noteQueries.js +21 -13
- package/dist/editor/createEditor.js +25 -3
- package/dist/editor/documentStyles.d.ts +3 -0
- package/dist/editor/documentStyles.js +2 -1
- package/dist/editor/editorDocument.d.ts +24 -0
- package/dist/editor/editorDocument.js +34 -4
- package/dist/editor/notes/noteSurface.d.ts +53 -0
- package/dist/editor/notes/noteSurface.js +246 -0
- package/dist/editor/plugins/commentDecorations.js +5 -4
- package/dist/editor/plugins/commentRestoration.d.ts +20 -0
- package/dist/editor/plugins/commentRestoration.js +168 -0
- package/dist/editor/plugins/compositionSelection.d.ts +20 -0
- package/dist/editor/plugins/compositionSelection.js +19 -0
- package/dist/editor/plugins/keymap.d.ts +14 -0
- package/dist/editor/plugins/keymap.js +13 -1
- package/dist/editor/plugins/noteLifecycle.d.ts +18 -0
- package/dist/editor/plugins/noteLifecycle.js +117 -0
- package/dist/editor/plugins/noteNavigation.d.ts +45 -0
- package/dist/editor/plugins/noteNavigation.js +86 -0
- package/dist/editor/plugins/noteNumbering.d.ts +13 -0
- package/dist/editor/plugins/noteNumbering.js +58 -0
- package/dist/editor/plugins/rowResize.js +2 -7
- package/dist/editor/plugins/tabLayout.js +2 -7
- package/dist/editor/stories/storyMarkup.d.ts +24 -0
- package/dist/editor/stories/storyMarkup.js +35 -0
- package/dist/editor/stories/storyState.d.ts +34 -0
- package/dist/editor/stories/storyState.js +74 -0
- package/dist/editor/stories/storyView.d.ts +115 -0
- package/dist/editor/stories/storyView.js +144 -0
- package/dist/numbering/spellers.d.ts +1 -1
- package/dist/numbering/spellers.js +6 -1
- package/dist/page/blockKinds.d.ts +23 -4
- package/dist/page/demands/footnoteDemands.d.ts +18 -0
- package/dist/page/demands/footnoteDemands.js +51 -0
- package/dist/page/demands/index.d.ts +43 -0
- package/dist/page/demands/index.js +8 -0
- package/dist/page/kinds/paragraphKind.js +7 -1
- package/dist/page/kinds/tableKind.js +9 -1
- package/dist/page/measureBlocks.d.ts +2 -0
- package/dist/page/measureBlocks.js +31 -10
- package/dist/page/pageDecorations.d.ts +9 -2
- package/dist/page/pageDecorations.js +18 -5
- package/dist/page/pageLayout.d.ts +77 -1
- package/dist/page/pageLayout.js +186 -9
- package/dist/page/usePageLayout.d.ts +57 -2
- package/dist/page/usePageLayout.js +50 -5
- package/dist/schema/attrRoles.js +3 -2
- package/dist/schema/docxSchema.js +5 -2
- package/dist/schema/editGuard.d.ts +1 -1
- package/dist/schema/guards.js +1 -2
- package/dist/schema/preservedGuards.d.ts +4 -6
- package/dist/schema/preservedGuards.js +0 -9
- package/dist/schema/stories.d.ts +34 -2
- package/dist/schema/stories.js +22 -5
- package/dist/styles/classNames.d.ts +23 -6
- package/dist/styles/classNames.js +23 -6
- package/dist/styles/inlineStyle.d.ts +7 -0
- package/dist/styles/inlineStyle.js +11 -5
- package/dist/styles/visualScale.d.ts +19 -0
- package/dist/styles/visualScale.js +26 -0
- package/dist/styles.css +96 -36
- package/dist/ui/CommentsPanel.d.ts +6 -1
- package/dist/ui/CommentsPanel.js +26 -8
- package/dist/ui/InsertImageButton.d.ts +3 -1
- package/dist/ui/InsertImageButton.js +3 -2
- package/dist/ui/TableMenu.d.ts +5 -2
- package/dist/ui/TableMenu.js +18 -1
- package/dist/ui/TextMenu.d.ts +5 -2
- package/dist/ui/TextMenu.js +21 -6
- package/dist/ui/Toolbar.d.ts +17 -2
- package/dist/ui/Toolbar.js +23 -10
- package/dist/ui/comments/useCommentRailLayout.js +2 -4
- package/dist/ui/noteItems.d.ts +21 -0
- package/dist/ui/noteItems.js +30 -0
- package/dist/ui/notes/NoteAreas.d.ts +73 -0
- package/dist/ui/notes/NoteAreas.js +131 -0
- package/dist/ui/notes/NoteList.d.ts +25 -0
- package/dist/ui/notes/NoteList.js +58 -0
- package/dist/ui/notes/StoryRow.d.ts +75 -0
- package/dist/ui/notes/StoryRow.js +122 -0
- package/dist/ui/notes/noteBands.d.ts +100 -0
- package/dist/ui/notes/noteBands.js +142 -0
- package/dist/ui/notes/noteHeights.d.ts +10 -0
- package/dist/ui/notes/noteHeights.js +24 -0
- package/dist/ui/notes/noteSeparator.d.ts +12 -0
- package/dist/ui/notes/noteSeparator.js +9 -0
- package/dist/ui/notes/useStorySurface.d.ts +68 -0
- package/dist/ui/notes/useStorySurface.js +95 -0
- package/dist/ui/runCommand.d.ts +8 -1
- package/dist/ui/runCommand.js +2 -2
- package/dist/ui/shortcutLabels.d.ts +16 -0
- package/dist/ui/shortcutLabels.js +9 -0
- package/dist/ui/usePageRoom.d.ts +13 -0
- package/dist/ui/usePageRoom.js +31 -0
- package/package.json +2 -1
- package/dist/docx/notes.d.ts +0 -27
- package/dist/docx/notes.js +0 -55
- package/dist/ui/NotesPanel.d.ts +0 -6
- package/dist/ui/NotesPanel.js +0 -33
|
@@ -28,9 +28,11 @@ var paragraphKind = {
|
|
|
28
28
|
const breaks = pageBreaksIn(node, pos);
|
|
29
29
|
const candidates = [];
|
|
30
30
|
let appliedHeight = 0;
|
|
31
|
+
const opened = /* @__PURE__ */ new Map();
|
|
31
32
|
dom.querySelectorAll(BREAK_SPACE).forEach((space, index) => {
|
|
32
33
|
const box = space.getBoundingClientRect();
|
|
33
34
|
const found = breaks.at(index);
|
|
35
|
+
const drawn = box.height / scale;
|
|
34
36
|
if (found) {
|
|
35
37
|
candidates.push({
|
|
36
38
|
at: found.at,
|
|
@@ -38,8 +40,9 @@ var paragraphKind = {
|
|
|
38
40
|
forced: true,
|
|
39
41
|
repeatHeight: 0
|
|
40
42
|
});
|
|
43
|
+
if (drawn > 0) opened.set(found.at, drawn);
|
|
41
44
|
}
|
|
42
|
-
appliedHeight +=
|
|
45
|
+
appliedHeight += drawn;
|
|
43
46
|
});
|
|
44
47
|
return {
|
|
45
48
|
candidates,
|
|
@@ -48,6 +51,7 @@ var paragraphKind = {
|
|
|
48
51
|
// still start a new page, but only after the whole block
|
|
49
52
|
breakAfter: dom.querySelectorAll(PAGE_BREAK_BR).length > candidates.length,
|
|
50
53
|
appliedHeight,
|
|
54
|
+
opened,
|
|
51
55
|
keepWithNext: toParagraphFormat(node.attrs.format)?.keepNext === true
|
|
52
56
|
};
|
|
53
57
|
},
|
|
@@ -59,6 +63,8 @@ var paragraphKind = {
|
|
|
59
63
|
into.push(
|
|
60
64
|
Decoration.inline(at, at + size, {
|
|
61
65
|
nodeName: "span",
|
|
66
|
+
// Chrome can enter the enlarged break and insert text outside the document model.
|
|
67
|
+
contenteditable: "false",
|
|
62
68
|
style: spaceStyle(height),
|
|
63
69
|
[editorAttributes.pageBreakSpace]: `${height}`
|
|
64
70
|
})
|
|
@@ -34,7 +34,8 @@ function rowEntries(view, tableNode, tablePos, tableDom, appliedRows, scale) {
|
|
|
34
34
|
node: row,
|
|
35
35
|
pos,
|
|
36
36
|
top: (rect.top - tableTop) / scale - appliedBefore,
|
|
37
|
-
height: rect.height / scale
|
|
37
|
+
height: rect.height / scale,
|
|
38
|
+
appliedAbove: appliedBefore
|
|
38
39
|
});
|
|
39
40
|
});
|
|
40
41
|
return entries;
|
|
@@ -132,6 +133,12 @@ var tableKind = {
|
|
|
132
133
|
});
|
|
133
134
|
}
|
|
134
135
|
}
|
|
136
|
+
const opened = /* @__PURE__ */ new Map();
|
|
137
|
+
rows.forEach((row, index) => {
|
|
138
|
+
const above = index === 0 ? 0 : rows[index - 1]?.appliedAbove ?? 0;
|
|
139
|
+
const here = row.appliedAbove - above;
|
|
140
|
+
if (here > 0) opened.set(row.pos, here);
|
|
141
|
+
});
|
|
135
142
|
const firstBodyBoundary = candidates.find(
|
|
136
143
|
(candidate) => candidate.offset > repeatHeaderHeight + 0.5
|
|
137
144
|
);
|
|
@@ -141,6 +148,7 @@ var tableKind = {
|
|
|
141
148
|
);
|
|
142
149
|
return {
|
|
143
150
|
candidates,
|
|
151
|
+
opened,
|
|
144
152
|
// The smallest useful first piece: the headers followed by one body row group
|
|
145
153
|
minFirstPiece: firstBodyBoundary?.offset ?? dom.getBoundingClientRect().height / scale - appliedHeight,
|
|
146
154
|
// A space cannot be opened inside a cell, so a break standing in one is answered after the
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* The engine's own marks are taken back off as they are read, so a block already pushed and a
|
|
8
8
|
* break already given its space read as they would with neither applied. A measurement that read
|
|
9
9
|
* them in would add to what is already there, and the layout would creep on every pass.
|
|
10
|
+
* Every demand source (`page/demands`) is asked about every block as well, after its kind, and the
|
|
11
|
+
* places it reads have the spaces the engine opened inside the block taken back off the same way.
|
|
10
12
|
* Where there is no layout (in tests) everything is 0, so the result is a single page.
|
|
11
13
|
*/
|
|
12
14
|
import type { EditorView } from "prosemirror-view";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// src/page/measureBlocks.ts
|
|
2
2
|
import { editorAttributes } from "../styles/classNames.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { visualScaleOf } from "../styles/visualScale.js";
|
|
4
|
+
import {
|
|
5
|
+
blockKindFor
|
|
6
|
+
} from "./blockKinds.js";
|
|
7
|
+
import { blockKindsOf, demandSourcesOf } from "./pageDecorations.js";
|
|
8
|
+
var NOTHING_OPENED = /* @__PURE__ */ new Map();
|
|
5
9
|
function pixels(value) {
|
|
6
10
|
const parsed = Number.parseFloat(value);
|
|
7
11
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
8
12
|
}
|
|
9
|
-
function visualScale(element) {
|
|
10
|
-
const scale = pixels(getComputedStyle(element).zoom);
|
|
11
|
-
return scale > 0 ? scale : 1;
|
|
12
|
-
}
|
|
13
13
|
function appliedPush(element) {
|
|
14
14
|
return pixels(element.getAttribute(editorAttributes.pagePush) ?? "");
|
|
15
15
|
}
|
|
@@ -23,16 +23,28 @@ function drawnBlocks(view) {
|
|
|
23
23
|
});
|
|
24
24
|
return found;
|
|
25
25
|
}
|
|
26
|
+
function naturalOffset(candidates, opened, drawn) {
|
|
27
|
+
let shift = 0;
|
|
28
|
+
for (const candidate of candidates) {
|
|
29
|
+
const space = opened.get(candidate.at);
|
|
30
|
+
if (space === void 0) continue;
|
|
31
|
+
const start = candidate.offset + shift;
|
|
32
|
+
if (drawn < start) break;
|
|
33
|
+
shift += Math.min(space, drawn - start);
|
|
34
|
+
}
|
|
35
|
+
return drawn - shift;
|
|
36
|
+
}
|
|
26
37
|
function measureSheet(view, layer) {
|
|
27
38
|
const sheet = view.dom;
|
|
28
39
|
const sheetRect = sheet.getBoundingClientRect();
|
|
29
40
|
const layerRect = layer.getBoundingClientRect();
|
|
30
41
|
const style = getComputedStyle(sheet);
|
|
31
|
-
const scale =
|
|
42
|
+
const scale = visualScaleOf(layer);
|
|
32
43
|
const contentTop = pixels(style.paddingTop);
|
|
33
44
|
const contentBottom = pixels(style.paddingBottom);
|
|
34
45
|
const sheetY = (viewportY) => (viewportY - sheetRect.top) / scale;
|
|
35
46
|
const kinds = blockKindsOf(view.state);
|
|
47
|
+
const sources = demandSourcesOf(view.state);
|
|
36
48
|
const blocks = [];
|
|
37
49
|
let previousBottom = contentTop;
|
|
38
50
|
let applied = 0;
|
|
@@ -42,7 +54,7 @@ function measureSheet(view, layer) {
|
|
|
42
54
|
const above = applied;
|
|
43
55
|
const blockY = (viewportY) => sheetY(viewportY) - above;
|
|
44
56
|
const top = blockY(rect.top);
|
|
45
|
-
const
|
|
57
|
+
const target = {
|
|
46
58
|
view,
|
|
47
59
|
node,
|
|
48
60
|
pos,
|
|
@@ -50,7 +62,8 @@ function measureSheet(view, layer) {
|
|
|
50
62
|
sheetY: blockY,
|
|
51
63
|
top,
|
|
52
64
|
scale
|
|
53
|
-
}
|
|
65
|
+
};
|
|
66
|
+
const measured = blockKindFor(kinds, node).measure(target);
|
|
54
67
|
applied += measured.appliedHeight;
|
|
55
68
|
const bottom = sheetY(rect.bottom) - applied;
|
|
56
69
|
blocks.push({
|
|
@@ -61,7 +74,15 @@ function measureSheet(view, layer) {
|
|
|
61
74
|
breakAfter: measured.breakAfter,
|
|
62
75
|
candidates: measured.candidates,
|
|
63
76
|
minFirstPiece: measured.minFirstPiece,
|
|
64
|
-
keepWithNext: measured.keepWithNext ?? false
|
|
77
|
+
keepWithNext: measured.keepWithNext ?? false,
|
|
78
|
+
demands: sources.flatMap((source) => source.demandsIn(target)).map((demand) => ({
|
|
79
|
+
...demand,
|
|
80
|
+
offset: naturalOffset(
|
|
81
|
+
measured.candidates,
|
|
82
|
+
measured.opened ?? NOTHING_OPENED,
|
|
83
|
+
demand.offset
|
|
84
|
+
)
|
|
85
|
+
}))
|
|
65
86
|
});
|
|
66
87
|
previousBottom = bottom;
|
|
67
88
|
}
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
* own kind draws the cuts inside it as (`page/kinds`).
|
|
4
4
|
*
|
|
5
5
|
* The kinds the editor was built with are held here, so the measurement and the decorations read
|
|
6
|
-
* one registry and a block is measured by the same kind that draws it.
|
|
6
|
+
* one registry and a block is measured by the same kind that draws it. The demand sources
|
|
7
|
+
* (`page/demands`) the measurement asks about every block are held beside them.
|
|
7
8
|
* The document model is left untouched, so no trace of any of it is left in the exported XML or
|
|
8
9
|
* in the edit history.
|
|
9
10
|
*/
|
|
10
11
|
import { type EditorState, Plugin } from "prosemirror-state";
|
|
11
12
|
import { DecorationSet, type EditorView } from "prosemirror-view";
|
|
12
13
|
import { type BlockKind, type PageCut } from "./blockKinds";
|
|
14
|
+
import { type DemandSource } from "./demands";
|
|
13
15
|
import type { BlockPush } from "./pageLayout";
|
|
14
16
|
/** Everything one measurement has to say about the page (`page/pageLayout`) */
|
|
15
17
|
export interface PageMarksInput {
|
|
@@ -25,11 +27,16 @@ export interface PageMarksInput {
|
|
|
25
27
|
}
|
|
26
28
|
interface PageMarks extends PageMarksInput {
|
|
27
29
|
kinds: readonly BlockKind[];
|
|
30
|
+
sources: readonly DemandSource[];
|
|
28
31
|
decorations: DecorationSet;
|
|
29
32
|
}
|
|
30
|
-
export declare function pageDecorations(kinds?: readonly BlockKind[]): Plugin<PageMarks>;
|
|
33
|
+
export declare function pageDecorations(kinds?: readonly BlockKind[], sources?: readonly DemandSource[]): Plugin<PageMarks>;
|
|
31
34
|
/** The kinds the editor was built with, which the measurement reads to match them */
|
|
32
35
|
export declare function blockKindsOf(state: EditorState): readonly BlockKind[];
|
|
36
|
+
/** The demand sources the editor was built with, which the measurement asks about every block */
|
|
37
|
+
export declare function demandSourcesOf(state: EditorState): readonly DemandSource[];
|
|
38
|
+
/** The cuts the sheet is drawn with, whose spaces the measurement takes back off a demand's place */
|
|
39
|
+
export declare function pageCutsOf(state: EditorState): readonly PageCut[];
|
|
33
40
|
/** One transaction, or none when the same marks are already applied */
|
|
34
41
|
export declare function setPageMarks(view: EditorView, next: PageMarksInput): void;
|
|
35
42
|
export {};
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
7
7
|
import { editorAttributes } from "../styles/classNames.js";
|
|
8
8
|
import { blockKindFor } from "./blockKinds.js";
|
|
9
|
+
import { DEFAULT_DEMAND_SOURCES } from "./demands/index.js";
|
|
9
10
|
import { DEFAULT_BLOCK_KINDS } from "./kinds/index.js";
|
|
10
11
|
var marksKey = new PluginKey("docxPageDecorations");
|
|
11
12
|
function pushStyle(marginTop) {
|
|
@@ -46,9 +47,10 @@ function decorationsFor(doc, kinds, pushes, cuts) {
|
|
|
46
47
|
});
|
|
47
48
|
return DecorationSet.create(doc, decorations);
|
|
48
49
|
}
|
|
49
|
-
function marksFor(doc, kinds, pushes, cuts) {
|
|
50
|
+
function marksFor(doc, { kinds, sources }, pushes, cuts) {
|
|
50
51
|
return {
|
|
51
52
|
kinds,
|
|
53
|
+
sources,
|
|
52
54
|
pushes,
|
|
53
55
|
cuts,
|
|
54
56
|
decorations: decorationsFor(doc, kinds, pushes, cuts)
|
|
@@ -75,18 +77,18 @@ function samePageMarks(a, b) {
|
|
|
75
77
|
return other !== void 0 && other.at === cut.at && other.height === cut.height;
|
|
76
78
|
});
|
|
77
79
|
}
|
|
78
|
-
function pageDecorations(kinds = DEFAULT_BLOCK_KINDS) {
|
|
80
|
+
function pageDecorations(kinds = DEFAULT_BLOCK_KINDS, sources = DEFAULT_DEMAND_SOURCES) {
|
|
79
81
|
return new Plugin({
|
|
80
82
|
key: marksKey,
|
|
81
83
|
state: {
|
|
82
|
-
init: (_config, state) => marksFor(state.doc, kinds, [], []),
|
|
84
|
+
init: (_config, state) => marksFor(state.doc, { kinds, sources }, [], []),
|
|
83
85
|
apply(tr, value) {
|
|
84
86
|
const next = tr.getMeta(marksKey);
|
|
85
87
|
if (next) return next;
|
|
86
88
|
if (!tr.docChanged) return value;
|
|
87
89
|
return marksFor(
|
|
88
90
|
tr.doc,
|
|
89
|
-
value
|
|
91
|
+
value,
|
|
90
92
|
value.pushes.map((push) => ({
|
|
91
93
|
...push,
|
|
92
94
|
pos: tr.mapping.map(push.pos)
|
|
@@ -106,6 +108,12 @@ function pageDecorations(kinds = DEFAULT_BLOCK_KINDS) {
|
|
|
106
108
|
function blockKindsOf(state) {
|
|
107
109
|
return marksKey.getState(state)?.kinds ?? DEFAULT_BLOCK_KINDS;
|
|
108
110
|
}
|
|
111
|
+
function demandSourcesOf(state) {
|
|
112
|
+
return marksKey.getState(state)?.sources ?? DEFAULT_DEMAND_SOURCES;
|
|
113
|
+
}
|
|
114
|
+
function pageCutsOf(state) {
|
|
115
|
+
return marksKey.getState(state)?.cuts ?? [];
|
|
116
|
+
}
|
|
109
117
|
function current(view) {
|
|
110
118
|
return marksKey.getState(view.state) ?? { pushes: [], cuts: [] };
|
|
111
119
|
}
|
|
@@ -116,7 +124,10 @@ function setPageMarks(view, next) {
|
|
|
116
124
|
marksKey,
|
|
117
125
|
marksFor(
|
|
118
126
|
view.state.doc,
|
|
119
|
-
|
|
127
|
+
{
|
|
128
|
+
kinds: blockKindsOf(view.state),
|
|
129
|
+
sources: demandSourcesOf(view.state)
|
|
130
|
+
},
|
|
120
131
|
next.pushes,
|
|
121
132
|
next.cuts
|
|
122
133
|
)
|
|
@@ -125,6 +136,8 @@ function setPageMarks(view, next) {
|
|
|
125
136
|
}
|
|
126
137
|
export {
|
|
127
138
|
blockKindsOf,
|
|
139
|
+
demandSourcesOf,
|
|
140
|
+
pageCutsOf,
|
|
128
141
|
pageDecorations,
|
|
129
142
|
setPageMarks
|
|
130
143
|
};
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { type PageGeometry } from "../docx/pageGeometry";
|
|
20
20
|
import type { DocumentSection, SectionProperties } from "../docx/sections";
|
|
21
21
|
import type { MeasuredBlock, PageCut } from "./blockKinds";
|
|
22
|
+
import type { DemandBand } from "./demands";
|
|
22
23
|
export type { MeasuredBlock };
|
|
23
24
|
/** The paper measured in the pixels the sheet is drawn with */
|
|
24
25
|
export interface PagePixels {
|
|
@@ -128,6 +129,51 @@ export interface PageStart {
|
|
|
128
129
|
*/
|
|
129
130
|
crossed: boolean;
|
|
130
131
|
}
|
|
132
|
+
/** The room one page keeps at the foot of its body for one band (`page/demands`) */
|
|
133
|
+
export interface PageReservation {
|
|
134
|
+
page: number;
|
|
135
|
+
band: string;
|
|
136
|
+
/** The ids the page keeps, in the order it took them: those carried over from the page before first */
|
|
137
|
+
ids: readonly string[];
|
|
138
|
+
/**
|
|
139
|
+
* Where the band starts, measured from the top of the body like a split. The bands a page keeps
|
|
140
|
+
* stand one under the next in the order it took them, from the end of its body above the room.
|
|
141
|
+
* A page that could not give its bands all the room they ask for gives them what lies under the
|
|
142
|
+
* text that has to stay on it, so a band starts no higher than that text ends
|
|
143
|
+
*/
|
|
144
|
+
top: number;
|
|
145
|
+
/**
|
|
146
|
+
* The band's overhead and the height of each id, which the page's body ends above. A demand
|
|
147
|
+
* taller than an empty page's body is counted whole, though the page has less to give it
|
|
148
|
+
*/
|
|
149
|
+
height: number;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Rows laid after the last block of the document rather than beside any place in it
|
|
153
|
+
* (`page/demands`), which is where the endnotes stand.
|
|
154
|
+
*
|
|
155
|
+
* They are given whatever the last page has left below its text, and go on down pages of their
|
|
156
|
+
* own from there. Nothing in the layout knows what they hold: a row is an id and a height, and
|
|
157
|
+
* what draws them is told back which page each landed on.
|
|
158
|
+
*/
|
|
159
|
+
export interface TrailingRows {
|
|
160
|
+
/** Height set aside once, above the first row, wherever the rows begin */
|
|
161
|
+
readonly overhead: number;
|
|
162
|
+
readonly rows: readonly {
|
|
163
|
+
readonly id: string;
|
|
164
|
+
readonly height: number;
|
|
165
|
+
}[];
|
|
166
|
+
}
|
|
167
|
+
/** The trailing rows one page holds, top to bottom */
|
|
168
|
+
export interface TrailingPlacement {
|
|
169
|
+
page: number;
|
|
170
|
+
/** Where the rows begin on this page, measured from the top of the body like a split */
|
|
171
|
+
top: number;
|
|
172
|
+
/** The rows this page holds, in the order they are laid */
|
|
173
|
+
ids: readonly string[];
|
|
174
|
+
/** The rows' heights, and the overhead on the page the rows begin on */
|
|
175
|
+
height: number;
|
|
176
|
+
}
|
|
131
177
|
export interface PageLayout {
|
|
132
178
|
/** Only the blocks to be moved down to the next page */
|
|
133
179
|
pushes: BlockPush[];
|
|
@@ -144,6 +190,10 @@ export interface PageLayout {
|
|
|
144
190
|
* margin needs the sheet drawn down to this.
|
|
145
191
|
*/
|
|
146
192
|
marginBottom: number;
|
|
193
|
+
/** What each page keeps at its foot, page by page, one entry per band a page holds */
|
|
194
|
+
reserved: readonly PageReservation[];
|
|
195
|
+
/** Where the rows laid after the last block landed, one entry per page holding any */
|
|
196
|
+
trailing: readonly TrailingPlacement[];
|
|
147
197
|
}
|
|
148
198
|
export interface PageLayoutInput {
|
|
149
199
|
blocks: readonly MeasuredBlock[];
|
|
@@ -153,6 +203,16 @@ export interface PageLayoutInput {
|
|
|
153
203
|
* unless that section is `continuous`.
|
|
154
204
|
*/
|
|
155
205
|
sections: readonly SectionPixels[];
|
|
206
|
+
/**
|
|
207
|
+
* The bands the blocks' demands are kept in, by name. A demand naming a band not given here is
|
|
208
|
+
* kept nowhere, so with none given the pages are the ones the blocks alone come to
|
|
209
|
+
*/
|
|
210
|
+
bands?: ReadonlyMap<string, DemandBand>;
|
|
211
|
+
/**
|
|
212
|
+
* The rows laid after the last block, which open pages of their own where the last page has no
|
|
213
|
+
* room left for them. Left out, the pages end with the blocks
|
|
214
|
+
*/
|
|
215
|
+
trailing?: TrailingRows;
|
|
156
216
|
}
|
|
157
217
|
/**
|
|
158
218
|
* Distributes the blocks across pages.
|
|
@@ -169,5 +229,21 @@ export interface PageLayoutInput {
|
|
|
169
229
|
* after it and the first piece of the block the keeps end at.
|
|
170
230
|
* Each block is measured against the paper of its own section, and the first block of a section
|
|
171
231
|
* opens a page of its own unless the section it opens declares `continuous`.
|
|
232
|
+
*
|
|
233
|
+
* A demand (`page/demands`) asks the page its place lands on for room at the foot, and that page's
|
|
234
|
+
* body ends above the room: whatever would stand in it is pushed, parted or crossed onto the next
|
|
235
|
+
* page exactly as at the end of the paper, the demand's own piece included when the room it asks
|
|
236
|
+
* for does not fit beneath that piece. The page ends and page tops stay where the paper puts them,
|
|
237
|
+
* except the place a block taller than a page crosses, which is the end of the body above the room.
|
|
238
|
+
* A demand is never parted. Where the content that has to stay on a page leaves no room for it, it
|
|
239
|
+
* is carried whole to the top of the next page's band with every demand after it on that page, and
|
|
240
|
+
* past the last block a page is opened for them; one taller than an empty page's body could fit
|
|
241
|
+
* nowhere, so it stays where it landed. A block that opens its page is never pushed off it to make
|
|
242
|
+
* room, which would leave a page holding nothing but what was carried onto it.
|
|
243
|
+
*
|
|
244
|
+
* The trailing rows are laid last, under the last block on the page it ends on, and go on down
|
|
245
|
+
* pages of their own where that page has no room left. They stand above whatever that page keeps
|
|
246
|
+
* at its foot, the way the text does, and a page opened for them belongs to the section the last
|
|
247
|
+
* block belongs to, so it carries that section's header and footer.
|
|
172
248
|
*/
|
|
173
|
-
export declare function pageLayout({ blocks, sections }: PageLayoutInput): PageLayout;
|
|
249
|
+
export declare function pageLayout({ blocks, sections, bands, trailing: trailingRows, }: PageLayoutInput): PageLayout;
|
package/dist/page/pageLayout.js
CHANGED
|
@@ -58,6 +58,60 @@ var TOLERANCE_PX = 0.5;
|
|
|
58
58
|
function round(value) {
|
|
59
59
|
return Math.round(value * 100) / 100;
|
|
60
60
|
}
|
|
61
|
+
var NO_BANDS = /* @__PURE__ */ new Map();
|
|
62
|
+
var NO_DEMANDS = [];
|
|
63
|
+
function emptyRoom() {
|
|
64
|
+
return { bands: /* @__PURE__ */ new Map(), kept: [], height: 0 };
|
|
65
|
+
}
|
|
66
|
+
function keep(bands, room, taken) {
|
|
67
|
+
const { demand } = taken;
|
|
68
|
+
const band = bands.get(demand.band);
|
|
69
|
+
if (!band || room.bands.get(demand.band)?.has(demand.id)) return;
|
|
70
|
+
const ids = room.bands.get(demand.band);
|
|
71
|
+
if (ids) {
|
|
72
|
+
ids.add(demand.id);
|
|
73
|
+
} else {
|
|
74
|
+
room.bands.set(demand.band, /* @__PURE__ */ new Set([demand.id]));
|
|
75
|
+
room.height += band.overhead;
|
|
76
|
+
}
|
|
77
|
+
room.kept.push(taken);
|
|
78
|
+
room.height += band.heights.get(demand.id) ?? 0;
|
|
79
|
+
}
|
|
80
|
+
function letGo(bands, room) {
|
|
81
|
+
const taken = room.kept.pop();
|
|
82
|
+
const band = taken && bands.get(taken.demand.band);
|
|
83
|
+
const ids = taken && room.bands.get(taken.demand.band);
|
|
84
|
+
if (!taken || !band || !ids) return void 0;
|
|
85
|
+
ids.delete(taken.demand.id);
|
|
86
|
+
room.height -= band.heights.get(taken.demand.id) ?? 0;
|
|
87
|
+
if (ids.size === 0) {
|
|
88
|
+
room.bands.delete(taken.demand.band);
|
|
89
|
+
room.height -= band.overhead;
|
|
90
|
+
}
|
|
91
|
+
if (room.kept.length === 0) room.height = 0;
|
|
92
|
+
return taken;
|
|
93
|
+
}
|
|
94
|
+
function bandHeight(band, ids) {
|
|
95
|
+
let height = band.overhead;
|
|
96
|
+
for (const id of ids) height += band.heights.get(id) ?? 0;
|
|
97
|
+
return height;
|
|
98
|
+
}
|
|
99
|
+
function roomAdded(bands, room, demands) {
|
|
100
|
+
if (demands.length === 0) return 0;
|
|
101
|
+
const trial = {
|
|
102
|
+
bands: new Map(
|
|
103
|
+
Array.from(room.bands, ([name, ids]) => [name, new Set(ids)])
|
|
104
|
+
),
|
|
105
|
+
kept: [...room.kept],
|
|
106
|
+
height: room.height
|
|
107
|
+
};
|
|
108
|
+
for (const demand of demands) keep(bands, trial, { demand, order: 0 });
|
|
109
|
+
return trial.height - room.height;
|
|
110
|
+
}
|
|
111
|
+
function demandsInOrder(block) {
|
|
112
|
+
const demands = block.demands ?? NO_DEMANDS;
|
|
113
|
+
return demands.length < 2 ? demands : [...demands].sort((a, b) => a.offset - b.offset);
|
|
114
|
+
}
|
|
61
115
|
function withinSections(pages, sections) {
|
|
62
116
|
let section = null;
|
|
63
117
|
let within = 0;
|
|
@@ -101,10 +155,17 @@ function keptExtents(blocks, laid) {
|
|
|
101
155
|
settle();
|
|
102
156
|
return extents;
|
|
103
157
|
}
|
|
104
|
-
function pageLayout({
|
|
158
|
+
function pageLayout({
|
|
159
|
+
blocks,
|
|
160
|
+
sections,
|
|
161
|
+
bands = NO_BANDS,
|
|
162
|
+
trailing: trailingRows
|
|
163
|
+
}) {
|
|
105
164
|
const pushes = [];
|
|
106
165
|
const cuts = [];
|
|
107
166
|
const splits = [];
|
|
167
|
+
const reserved = [];
|
|
168
|
+
const trailing = [];
|
|
108
169
|
let blockPos = blocks[0]?.pos ?? 0;
|
|
109
170
|
const opened = [
|
|
110
171
|
{ page: 1, bodyStart: 0, pos: blockPos, crossed: false }
|
|
@@ -119,7 +180,9 @@ function pageLayout({ blocks, sections }) {
|
|
|
119
180
|
splits,
|
|
120
181
|
pages: withinSections(opened, sections),
|
|
121
182
|
bodyHeight: 0,
|
|
122
|
-
marginBottom: 0
|
|
183
|
+
marginBottom: 0,
|
|
184
|
+
reserved,
|
|
185
|
+
trailing
|
|
123
186
|
};
|
|
124
187
|
}
|
|
125
188
|
const blockSections = blocks.map(
|
|
@@ -136,16 +199,82 @@ function pageLayout({ blocks, sections }) {
|
|
|
136
199
|
let cursor = 0;
|
|
137
200
|
let paper = laid[0]?.paper ?? roomy;
|
|
138
201
|
const stepTo = (opening) => paper.pageStep - paper.marginTop + opening.marginTop;
|
|
202
|
+
let room = emptyRoom();
|
|
203
|
+
let carried = [];
|
|
204
|
+
let reached = 0;
|
|
205
|
+
let held = 0;
|
|
206
|
+
const reservedHere = () => Math.min(room.height, Math.max(0, pageStart + paper.bodyHeight - held));
|
|
207
|
+
const bodyEnd = () => pageStart + paper.bodyHeight - reservedHere();
|
|
208
|
+
const endsOnPage = (bottom) => bottom <= pageStart + paper.bodyHeight + TOLERANCE_PX;
|
|
209
|
+
const settle = (floor, onPaper = paper) => {
|
|
210
|
+
held = Math.max(held, floor);
|
|
211
|
+
while (held + room.height > pageStart + onPaper.bodyHeight + TOLERANCE_PX) {
|
|
212
|
+
const last = room.kept.at(-1);
|
|
213
|
+
if (last === void 0 || roomAdded(bands, emptyRoom(), [last.demand]) > onPaper.bodyHeight + TOLERANCE_PX) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
letGo(bands, room);
|
|
217
|
+
carried.push(last);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const recordRoom = (page) => {
|
|
221
|
+
const held2 = [...room.bands].flatMap(([name, ids]) => {
|
|
222
|
+
const band = bands.get(name);
|
|
223
|
+
return band ? [{ name, ids, band }] : [];
|
|
224
|
+
}).sort((a, b) => b.band.order - a.band.order);
|
|
225
|
+
let top = bodyEnd();
|
|
226
|
+
for (const { name, ids, band } of held2) {
|
|
227
|
+
const height = bandHeight(band, ids);
|
|
228
|
+
reserved.push({
|
|
229
|
+
page,
|
|
230
|
+
band: name,
|
|
231
|
+
ids: [...ids],
|
|
232
|
+
top: round(top),
|
|
233
|
+
height: round(height)
|
|
234
|
+
});
|
|
235
|
+
top += height;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
139
238
|
const split = (y, crossed, forced, opening = paper) => {
|
|
140
239
|
const page = splits.length + 2;
|
|
240
|
+
recordRoom(page - 1);
|
|
141
241
|
splits.push({ y: round(y), page, forced, crossed });
|
|
142
242
|
pageStart = crossed ? y : y + stepTo(opening);
|
|
143
243
|
opened.push({ page, bodyStart: round(pageStart), pos: blockPos, crossed });
|
|
244
|
+
room = emptyRoom();
|
|
245
|
+
held = pageStart;
|
|
246
|
+
const waiting = carried;
|
|
247
|
+
carried = [];
|
|
248
|
+
waiting.sort((a, b) => a.order - b.order);
|
|
249
|
+
for (const taken of waiting) keep(bands, room, taken);
|
|
250
|
+
settle(pageStart, opening);
|
|
144
251
|
};
|
|
145
252
|
const crossTo = (y) => {
|
|
146
|
-
while (y >
|
|
147
|
-
|
|
253
|
+
while (y > bodyEnd() + TOLERANCE_PX) {
|
|
254
|
+
const end = bodyEnd();
|
|
255
|
+
split(
|
|
256
|
+
end > pageStart + TOLERANCE_PX ? end : pageStart + paper.bodyHeight,
|
|
257
|
+
true,
|
|
258
|
+
false
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
const place = (bottom, demands, placeOf) => {
|
|
263
|
+
for (const demand of demands) {
|
|
264
|
+
if (!bands.has(demand.band)) continue;
|
|
265
|
+
if (!endsOnPage(bottom)) crossTo(placeOf(demand));
|
|
266
|
+
const taken = { demand, order: reached };
|
|
267
|
+
reached += 1;
|
|
268
|
+
const alreadyKept = room.bands.get(demand.band)?.has(demand.id) === true;
|
|
269
|
+
if (carried.length > 0 && !alreadyKept) {
|
|
270
|
+
carried.push(taken);
|
|
271
|
+
} else {
|
|
272
|
+
keep(bands, room, taken);
|
|
273
|
+
settle(placeOf(demand));
|
|
274
|
+
}
|
|
148
275
|
}
|
|
276
|
+
if (endsOnPage(bottom)) settle(bottom);
|
|
277
|
+
crossTo(bottom);
|
|
149
278
|
};
|
|
150
279
|
const kept = keptExtents(blocks, laid);
|
|
151
280
|
let breakAfterPrevious = false;
|
|
@@ -159,7 +288,10 @@ function pageLayout({ blocks, sections }) {
|
|
|
159
288
|
const first = kept.get(index) ?? block.minFirstPiece;
|
|
160
289
|
const overflows = top + first > pageEnd + TOLERANCE_PX;
|
|
161
290
|
const fits = first <= opening.bodyHeight + TOLERANCE_PX;
|
|
162
|
-
const
|
|
291
|
+
const demands = demandsInOrder(block);
|
|
292
|
+
const firstDemands = demands.length === 0 ? NO_DEMANDS : demands.filter((demand) => demand.offset < first);
|
|
293
|
+
const crowded = top > pageStart + TOLERANCE_PX && top + first + roomAdded(bands, room, firstDemands) > bodyEnd() + TOLERANCE_PX && first + roomAdded(bands, emptyRoom(), firstDemands) <= opening.bodyHeight + TOLERANCE_PX;
|
|
294
|
+
const push = startsPage || overflows && fits || crowded ? Math.max(0, pageEnd + stepTo(opening) - top) : 0;
|
|
163
295
|
if (push > 0 || startsPage) split(pageEnd, false, startsPage, opening);
|
|
164
296
|
paper = opening;
|
|
165
297
|
if (push > 0) {
|
|
@@ -173,15 +305,23 @@ function pageLayout({ blocks, sections }) {
|
|
|
173
305
|
const pageBodyHeight = paper.bodyHeight;
|
|
174
306
|
let added = 0;
|
|
175
307
|
let pieceStart = 0;
|
|
308
|
+
let untaken = 0;
|
|
176
309
|
for (let index2 = 0; index2 <= block.candidates.length; index2 += 1) {
|
|
177
310
|
const cut = index2 === 0 ? null : block.candidates[index2 - 1];
|
|
178
|
-
const
|
|
311
|
+
const next = block.candidates[index2];
|
|
312
|
+
const pieceEnd = next?.offset ?? block.height;
|
|
313
|
+
const taken = untaken;
|
|
314
|
+
while (untaken < demands.length && (next === void 0 || (demands[untaken]?.offset ?? 0) < pieceEnd)) {
|
|
315
|
+
untaken += 1;
|
|
316
|
+
}
|
|
317
|
+
const pieceDemands = taken === untaken ? NO_DEMANDS : demands.slice(taken, untaken);
|
|
179
318
|
const pieceTop = contentTop + pieceStart + added;
|
|
180
319
|
let pieceBottom = contentTop + pieceEnd + added;
|
|
181
320
|
if (cut) {
|
|
182
321
|
if (cut.forced) crossTo(pieceTop);
|
|
183
322
|
const pageBottom = pageStart + pageBodyHeight;
|
|
184
|
-
const
|
|
323
|
+
const size = pieceEnd - pieceStart;
|
|
324
|
+
const cutHere = cut.forced || pieceTop > pageStart + TOLERANCE_PX && (pieceBottom > pageBottom + TOLERANCE_PX && size <= pageBodyHeight + TOLERANCE_PX || pieceBottom + roomAdded(bands, room, pieceDemands) > bodyEnd() + TOLERANCE_PX && size + roomAdded(bands, emptyRoom(), pieceDemands) <= pageBodyHeight + TOLERANCE_PX);
|
|
185
325
|
if (cutHere) {
|
|
186
326
|
split(pageBottom, false, cut.forced);
|
|
187
327
|
const height = Math.max(0, pageStart - pieceTop);
|
|
@@ -190,12 +330,47 @@ function pageLayout({ blocks, sections }) {
|
|
|
190
330
|
pieceBottom += height + cut.repeatHeight;
|
|
191
331
|
}
|
|
192
332
|
}
|
|
193
|
-
|
|
333
|
+
const shift = contentTop + added;
|
|
334
|
+
place(pieceBottom, pieceDemands, (demand) => shift + demand.offset);
|
|
194
335
|
pieceStart = pieceEnd;
|
|
195
336
|
}
|
|
196
337
|
cursor = contentTop + block.height + added;
|
|
197
338
|
breakAfterPrevious = block.breakAfter;
|
|
198
339
|
}
|
|
340
|
+
const drainCarried = () => {
|
|
341
|
+
while (carried.length > 0) {
|
|
342
|
+
split(pageStart + paper.bodyHeight, false, false);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
drainCarried();
|
|
346
|
+
let laidHere;
|
|
347
|
+
let below = Math.max(cursor, pageStart);
|
|
348
|
+
for (const [at, row] of (trailingRows?.rows ?? []).entries()) {
|
|
349
|
+
const above = at === 0 ? trailingRows?.overhead ?? 0 : 0;
|
|
350
|
+
const opens = below + above + row.height > bodyEnd() + TOLERANCE_PX && below > pageStart + TOLERANCE_PX;
|
|
351
|
+
if (opens) {
|
|
352
|
+
laidHere = void 0;
|
|
353
|
+
split(pageStart + paper.bodyHeight, false, false);
|
|
354
|
+
below = pageStart;
|
|
355
|
+
}
|
|
356
|
+
const top = below + above;
|
|
357
|
+
if (laidHere === void 0) {
|
|
358
|
+
laidHere = {
|
|
359
|
+
page: splits.length + 1,
|
|
360
|
+
top: round(below),
|
|
361
|
+
ids: [row.id],
|
|
362
|
+
height: round(above + row.height)
|
|
363
|
+
};
|
|
364
|
+
trailing.push(laidHere);
|
|
365
|
+
} else {
|
|
366
|
+
laidHere.ids = [...laidHere.ids, row.id];
|
|
367
|
+
laidHere.height = round(laidHere.height + row.height);
|
|
368
|
+
}
|
|
369
|
+
below = top + row.height;
|
|
370
|
+
settle(below);
|
|
371
|
+
}
|
|
372
|
+
drainCarried();
|
|
373
|
+
recordRoom(splits.length + 1);
|
|
199
374
|
return {
|
|
200
375
|
pushes,
|
|
201
376
|
cuts,
|
|
@@ -203,7 +378,9 @@ function pageLayout({ blocks, sections }) {
|
|
|
203
378
|
pages: withinSections(opened, sections),
|
|
204
379
|
// The last page is filled out in full on the paper of the section it opens with
|
|
205
380
|
bodyHeight: round(pageStart + paper.bodyHeight),
|
|
206
|
-
marginBottom: paper.marginBottom
|
|
381
|
+
marginBottom: paper.marginBottom,
|
|
382
|
+
reserved,
|
|
383
|
+
trailing
|
|
207
384
|
};
|
|
208
385
|
}
|
|
209
386
|
export {
|