@portone/docx-editor 0.1.1 → 0.2.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.
Files changed (138) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/CONTRIBUTING.md +4 -0
  3. package/README.md +17 -5
  4. package/dist/DocxEditor.d.ts +34 -10
  5. package/dist/DocxEditor.js +69 -24
  6. package/dist/core.d.ts +2 -0
  7. package/dist/core.js +2 -0
  8. package/dist/docx/commentOnlyChange.d.ts +47 -0
  9. package/dist/docx/commentOnlyChange.js +162 -0
  10. package/dist/docx/comments/constants.d.ts +11 -3
  11. package/dist/docx/comments/constants.js +10 -3
  12. package/dist/docx/comments/contentTypes.d.ts +7 -0
  13. package/dist/docx/comments/contentTypes.js +38 -0
  14. package/dist/docx/comments/grammar.d.ts +80 -0
  15. package/dist/docx/comments/grammar.js +213 -0
  16. package/dist/docx/comments/model.d.ts +2 -0
  17. package/dist/docx/comments/model.js +3 -0
  18. package/dist/docx/comments/people.d.ts +39 -0
  19. package/dist/docx/comments/people.js +196 -0
  20. package/dist/docx/comments/reading.d.ts +18 -1
  21. package/dist/docx/comments/reading.js +33 -20
  22. package/dist/docx/comments/verifying.d.ts +44 -0
  23. package/dist/docx/comments/verifying.js +206 -0
  24. package/dist/docx/comments/writing.d.ts +4 -1
  25. package/dist/docx/comments/writing.js +84 -77
  26. package/dist/docx/exportDocx.js +2 -1
  27. package/dist/docx/formatting/direct.js +3 -6
  28. package/dist/docx/headersFooters.js +8 -10
  29. package/dist/docx/importParagraph.js +1 -0
  30. package/dist/docx/notes.js +3 -5
  31. package/dist/docx/paraProps.js +51 -46
  32. package/dist/docx/propsXml.d.ts +20 -2
  33. package/dist/docx/propsXml.js +72 -32
  34. package/dist/docx/relationships.js +8 -3
  35. package/dist/docx/runProps.js +34 -34
  36. package/dist/docx/sdt.js +16 -14
  37. package/dist/docx/serializeBlock.js +1 -2
  38. package/dist/docx/serializeParagraph.d.ts +2 -1
  39. package/dist/docx/serializeParagraph.js +25 -14
  40. package/dist/docx/serializeTable.js +38 -24
  41. package/dist/docx/storyProjection.d.ts +36 -0
  42. package/dist/docx/storyProjection.js +23 -0
  43. package/dist/docx/tableFormatting/editing.js +49 -73
  44. package/dist/docx/tableTemplate.js +24 -3
  45. package/dist/docx/theme.d.ts +0 -6
  46. package/dist/docx/theme.js +0 -8
  47. package/dist/editor/clipboard/images.js +17 -3
  48. package/dist/editor/clipboard/inlineFormatting.d.ts +7 -0
  49. package/dist/editor/clipboard/inlineFormatting.js +1 -0
  50. package/dist/editor/commands/breakCommands.js +6 -2
  51. package/dist/editor/commands/canRunCommand.d.ts +3 -2
  52. package/dist/editor/commands/canRunCommand.js +2 -2
  53. package/dist/editor/commands/comments/editing.js +9 -10
  54. package/dist/editor/commands/comments/model.d.ts +9 -0
  55. package/dist/editor/commands/comments/reading.d.ts +7 -0
  56. package/dist/editor/commands/comments/reading.js +14 -0
  57. package/dist/editor/commands/formatting/editing.js +6 -1
  58. package/dist/editor/commands/historyCommands.js +14 -5
  59. package/dist/editor/commands/index.d.ts +7 -1
  60. package/dist/editor/commands/index.js +4 -0
  61. package/dist/editor/commands/linkCommands.js +2 -0
  62. package/dist/editor/commands/lockCommands.js +12 -1
  63. package/dist/editor/commands/tabCommands.js +4 -2
  64. package/dist/editor/createEditor.d.ts +13 -3
  65. package/dist/editor/createEditor.js +23 -15
  66. package/dist/editor/externalClipboard.js +166 -4
  67. package/dist/editor/insertImage.js +6 -2
  68. package/dist/editor/insertTable.js +2 -1
  69. package/dist/editor/paragraphEdits.d.ts +2 -0
  70. package/dist/editor/paragraphEdits.js +2 -0
  71. package/dist/editor/plugins/documentProtection.d.ts +21 -0
  72. package/dist/editor/plugins/documentProtection.js +44 -0
  73. package/dist/editor/plugins/imagePaste.js +4 -1
  74. package/dist/editor/plugins/keymap.d.ts +11 -1
  75. package/dist/editor/plugins/keymap.js +24 -4
  76. package/dist/editor/plugins/lockedContent.d.ts +4 -2
  77. package/dist/editor/plugins/lockedContent.js +2 -2
  78. package/dist/editor/plugins/tableContextMenu.js +2 -1
  79. package/dist/editor/plugins/textContextMenu.js +6 -1
  80. package/dist/index.d.ts +2 -0
  81. package/dist/numbering/listTemplate.js +32 -10
  82. package/dist/ooxml/element.d.ts +52 -0
  83. package/dist/ooxml/element.js +49 -0
  84. package/dist/ooxml/fragment.d.ts +53 -0
  85. package/dist/ooxml/fragment.js +76 -0
  86. package/dist/ooxml/image.js +4 -3
  87. package/dist/ooxml/names.d.ts +27 -0
  88. package/dist/ooxml/names.js +29 -0
  89. package/dist/ooxml/precedence.d.ts +31 -0
  90. package/dist/ooxml/precedence.js +51 -0
  91. package/dist/ooxml/xml.d.ts +31 -0
  92. package/dist/ooxml/xml.js +27 -0
  93. package/dist/page/blockKinds.d.ts +42 -0
  94. package/dist/page/blockKinds.js +0 -0
  95. package/dist/page/measureBlocks.d.ts +14 -1
  96. package/dist/page/measureBlocks.js +31 -11
  97. package/dist/page/pageDecorations.d.ts +16 -0
  98. package/dist/page/pageDecorations.js +1 -0
  99. package/dist/page/pageLayout.d.ts +8 -43
  100. package/dist/page/pageLayout.js +28 -51
  101. package/dist/page/tableMeasurements.d.ts +10 -2
  102. package/dist/page/tableMeasurements.js +20 -16
  103. package/dist/page/usePageLayout.d.ts +15 -0
  104. package/dist/page/usePageLayout.js +30 -2
  105. package/dist/schema/attrRoles.d.ts +35 -0
  106. package/dist/schema/attrRoles.js +106 -0
  107. package/dist/schema/docxSchema.d.ts +6 -0
  108. package/dist/schema/docxSchema.js +216 -100
  109. package/dist/schema/editGuard.d.ts +99 -0
  110. package/dist/schema/editGuard.js +43 -0
  111. package/dist/schema/guards.d.ts +68 -0
  112. package/dist/schema/guards.js +82 -0
  113. package/dist/schema/locks.d.ts +9 -8
  114. package/dist/schema/locks.js +32 -16
  115. package/dist/schema/preservedGuards.d.ts +30 -0
  116. package/dist/schema/preservedGuards.js +50 -0
  117. package/dist/schema/protection.d.ts +84 -0
  118. package/dist/schema/protection.js +171 -0
  119. package/dist/schema/protectionState.d.ts +20 -0
  120. package/dist/schema/protectionState.js +37 -0
  121. package/dist/schema/sourceEquality.d.ts +27 -0
  122. package/dist/schema/sourceEquality.js +65 -0
  123. package/dist/table/cellFormatting.js +5 -2
  124. package/dist/table/commands.d.ts +2 -2
  125. package/dist/table/commands.js +2 -2
  126. package/dist/table/index.d.ts +2 -2
  127. package/dist/table/merge.d.ts +2 -2
  128. package/dist/table/merge.js +3 -3
  129. package/dist/ui/CommentsPanel.d.ts +3 -3
  130. package/dist/ui/CommentsPanel.js +26 -18
  131. package/dist/ui/LinkCard.d.ts +1 -2
  132. package/dist/ui/LinkCard.js +3 -4
  133. package/dist/ui/TextMenu.js +15 -10
  134. package/package.json +5 -2
  135. package/dist/editor/plugins/bookmarkProtection.d.ts +0 -3
  136. package/dist/editor/plugins/bookmarkProtection.js +0 -28
  137. package/dist/editor/plugins/noteProtection.d.ts +0 -3
  138. package/dist/editor/plugins/noteProtection.js +0 -25
@@ -5,6 +5,14 @@ export declare const W_NS = "http://schemas.openxmlformats.org/wordprocessingml/
5
5
  * every relationship type name is built on
6
6
  */
7
7
  export declare const R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
8
+ /**
9
+ * The prefixes whose meaning the editor depends on, and the namespace each one has to carry.
10
+ *
11
+ * Every element the writer spells out is a `w:` one, and the relationship a link or an image points
12
+ * at is named by `r:id` or `r:embed`, so a fragment that bound either prefix elsewhere would leave
13
+ * the writer's own markup meaning something else.
14
+ */
15
+ export declare const RESERVED_PREFIXES: ReadonlyMap<string, string>;
8
16
  /** The name with its namespace prefix stripped off (`w:ascii` -> `ascii`) */
9
17
  export declare function localPart(name: string): string;
10
18
  /**
@@ -22,10 +30,33 @@ export declare function decodeUtf8(bytes: Uint8Array): {
22
30
  };
23
31
  export declare function encodeUtf8(text: string, withBom: boolean): Uint8Array;
24
32
  export declare function parseXml(source: string): Document;
33
+ /**
34
+ * Gathers the namespace prefixes used in the fragment and declares them.
35
+ * Only `w` carries real meaning; the rest are placeholders that keep the parser from stopping.
36
+ * All we read are element names and `w:` attributes, so placeholders still let the values be read as they are.
37
+ *
38
+ * An attribute standing at the very start of the string counts too, since a fragment may be an
39
+ * attribute list of its own (`attrString`) rather than an element.
40
+ */
41
+ export declare function namespaceDecls(xml: string): string;
25
42
  export declare function elementChildren(el: Element): Element[];
43
+ /**
44
+ * The value of the attribute with this local name, whatever prefix it was written under.
45
+ *
46
+ * A document is free to bind the WordprocessingML namespace to a prefix of its own, so an
47
+ * attribute is looked up by the name it carries rather than by the spelling a producer chose.
48
+ */
49
+ export declare function attributeByLocalName(el: Element, localName: string): string | null;
26
50
  export declare function childByLocalName(el: Element, name: string): Element | null;
27
51
  /** Turns an opening tag's attributes into an `a="b" c="d"` string */
28
52
  export declare function attrString(el: Element): string | null;
53
+ /**
54
+ * Whether this node is an element.
55
+ *
56
+ * `Node` is a global a server is asked to install (`site/content/docs/core.mdx`); `Element` is
57
+ * not, so nothing here may reach for it at run time.
58
+ */
59
+ export declare function isElement(node: Node): node is Element;
29
60
  /**
30
61
  * Turns a single element back into an XML string.
31
62
  *
package/dist/ooxml/xml.js CHANGED
@@ -2,6 +2,10 @@
2
2
  import { DocxImportError } from "./errors.js";
3
3
  var W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
4
4
  var R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
5
+ var RESERVED_PREFIXES = /* @__PURE__ */ new Map([
6
+ ["w", W_NS],
7
+ ["r", R_NS]
8
+ ]);
5
9
  function localPart(name) {
6
10
  const colon = name.indexOf(":");
7
11
  return colon === -1 ? name : name.slice(colon + 1);
@@ -42,9 +46,28 @@ function parseXml(source) {
42
46
  }
43
47
  return doc;
44
48
  }
49
+ function namespaceDecls(xml) {
50
+ const prefixes = /* @__PURE__ */ new Set(["w"]);
51
+ for (const [, prefix] of xml.matchAll(/<\/?([A-Za-z_][\w.-]*):/g)) {
52
+ prefixes.add(prefix);
53
+ }
54
+ for (const [, prefix] of xml.matchAll(
55
+ /(?:^|[\s"'])([A-Za-z_][\w.-]*):[\w.-]+=/g
56
+ )) {
57
+ prefixes.add(prefix);
58
+ }
59
+ prefixes.delete("xml");
60
+ prefixes.delete("xmlns");
61
+ return Array.from(prefixes).map(
62
+ (prefix) => `xmlns:${prefix}="${prefix === "w" ? W_NS : `urn:docx-editor:${prefix}`}"`
63
+ ).join(" ");
64
+ }
45
65
  function elementChildren(el) {
46
66
  return Array.from(el.children);
47
67
  }
68
+ function attributeByLocalName(el, localName) {
69
+ return Array.from(el.attributes).find((entry) => entry.localName === localName)?.value ?? null;
70
+ }
48
71
  function childByLocalName(el, name) {
49
72
  return elementChildren(el).find((child) => child.localName === name) ?? null;
50
73
  }
@@ -75,15 +98,19 @@ function serializeXml(el) {
75
98
  return inner ? `${open}>${inner}</${el.nodeName}>` : `${open}/>`;
76
99
  }
77
100
  export {
101
+ RESERVED_PREFIXES,
78
102
  R_NS,
79
103
  W_NS,
80
104
  attrString,
105
+ attributeByLocalName,
81
106
  childByLocalName,
82
107
  decodeUtf8,
83
108
  elementChildren,
84
109
  encodeUtf8,
85
110
  escapeXml,
111
+ isElement,
86
112
  localPart,
113
+ namespaceDecls,
87
114
  parseXml,
88
115
  serializeXml
89
116
  };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * What the page engine knows about a block, whatever shape of block it is.
3
+ *
4
+ * A measurer says where a block may be parted and how much of it has to fit on the page it
5
+ * starts on; the layout (`page/pageLayout`) answers with the places it decided to part it at.
6
+ * Neither side names a paragraph or a table.
7
+ */
8
+ /** A place inside a block where the next page may, or must, start */
9
+ export interface BreakCandidate {
10
+ /**
11
+ * The document position the continued piece starts at: a page `br` for a paragraph, a row for a
12
+ * table. A position maps through an edit, which is why the marks are kept by position and not
13
+ * by ordinal (see `page/pageDecorations`)
14
+ */
15
+ at: number;
16
+ /** The top of the continued piece, measured from the block's natural top with no space in it */
17
+ offset: number;
18
+ /** A forced candidate always cuts; an optional one cuts only when the piece after it would overflow */
19
+ forced: boolean;
20
+ /** Height the continued piece carries onto the next page ahead of its own content (a repeated header) */
21
+ repeatHeight: number;
22
+ }
23
+ /** One body block as drawn on screen */
24
+ export interface MeasuredBlock {
25
+ /** The position where this block starts in the document */
26
+ pos: number;
27
+ /** The height that naturally opens up between the previous block and this one */
28
+ gap: number;
29
+ height: number;
30
+ /** The document records that a new page starts at this block */
31
+ breakBefore: boolean;
32
+ /** The block holds a break it cannot open a space at (inside a cell), so the next block starts a page */
33
+ breakAfter: boolean;
34
+ candidates: readonly BreakCandidate[];
35
+ /** The smallest piece that has to fit on the page the block starts on */
36
+ minFirstPiece: number;
37
+ }
38
+ /** A page cut the layout decided on: the space opened before the continued piece */
39
+ export interface PageCut {
40
+ at: number;
41
+ height: number;
42
+ }
File without changes
@@ -2,13 +2,21 @@
2
2
  * Measures the sheet as drawn on screen: the height of each body block, the gap above it, and
3
3
  * where the page breaks inside it stand, with nothing computed line by line.
4
4
  *
5
+ * Every place a block may be parted at is read as a break candidate (`page/blockKinds`), whatever
6
+ * shape of block it is: a page break for a paragraph, a safe row boundary for a table.
5
7
  * The engine's own marks are taken back off as they are read, so a block already pushed and a
6
8
  * break already given its space read as they would with neither applied. A measurement that read
7
9
  * them in would add to what is already there, and the layout would creep on every pass.
8
10
  * Where there is no layout (in tests) everything is 0, so the result is a single page.
9
11
  */
10
12
  import type { EditorView } from "prosemirror-view";
11
- import type { MeasuredBlock } from "./pageLayout";
13
+ import type { MeasuredBlock } from "./blockKinds";
14
+ /** What a table continuation still needs from the measurement to be drawn */
15
+ export interface TableHeaderProjection {
16
+ headerRows: readonly number[];
17
+ headerSignature: string;
18
+ columns: number;
19
+ }
12
20
  /**
13
21
  * The measurements taken in order to draw the page overlay. Positions are relative to
14
22
  * the overlay box
@@ -21,5 +29,10 @@ export interface SheetMeasure {
21
29
  contentTop: number;
22
30
  contentBottom: number;
23
31
  blocks: MeasuredBlock[];
32
+ /**
33
+ * The header facts of every table block, by the table's position, for the adapter that turns
34
+ * cuts back into table continuations (`page/usePageLayout`)
35
+ */
36
+ tables: ReadonlyMap<number, TableHeaderProjection>;
24
37
  }
25
38
  export declare function measureSheet(view: EditorView, layer: HTMLElement): SheetMeasure;
@@ -1,5 +1,6 @@
1
1
  // src/page/measureBlocks.ts
2
2
  import { editorAttributes } from "../styles/classNames.js";
3
+ import { pageBreaksIn } from "./pageDecorations.js";
3
4
  import { measureTable } from "./tableMeasurements.js";
4
5
  var PAGE_BREAK_BR = `br[${editorAttributes.breakType}="page"]`;
5
6
  var BREAK_SPACE = `[${editorAttributes.pageBreakSpace}]`;
@@ -37,34 +38,52 @@ function measureSheet(view, layer) {
37
38
  const contentBottom = pixels(style.paddingBottom);
38
39
  const sheetY = (viewportY) => (viewportY - sheetRect.top) / scale;
39
40
  const blocks = [];
41
+ const tables = /* @__PURE__ */ new Map();
40
42
  let previousBottom = contentTop;
41
43
  let applied = 0;
42
- let breakAfterPrevious = false;
43
44
  for (const { pos, dom } of drawnBlocks(view)) {
44
45
  const node = view.state.doc.nodeAt(pos);
45
46
  const rect = dom.getBoundingClientRect();
46
47
  applied += appliedPush(dom);
47
48
  const top = sheetY(rect.top) - applied;
48
49
  const measuredTable = node ? measureTable(view, node, pos, dom, scale) : null;
49
- const breaks = [];
50
+ const breaks = node ? pageBreaksIn(node, pos) : [];
50
51
  const spaces = Array.from(dom.querySelectorAll(BREAK_SPACE));
51
- for (const space of spaces) {
52
+ const forced = [];
53
+ spaces.forEach((space, index) => {
52
54
  const box = space.getBoundingClientRect();
53
- breaks.push(sheetY(box.top) - applied - top);
55
+ const found = breaks.at(index);
56
+ if (found) {
57
+ forced.push({
58
+ at: found.at,
59
+ offset: sheetY(box.top) - applied - top,
60
+ forced: true,
61
+ repeatHeight: 0
62
+ });
63
+ }
54
64
  applied += box.height / scale;
55
- }
65
+ });
56
66
  applied += measuredTable?.appliedHeight ?? 0;
57
67
  const bottom = sheetY(rect.bottom) - applied;
68
+ const height = bottom - top;
69
+ if (measuredTable) {
70
+ tables.set(pos, {
71
+ headerRows: measuredTable.headerRows,
72
+ headerSignature: measuredTable.headerSignature,
73
+ columns: measuredTable.columns
74
+ });
75
+ }
58
76
  blocks.push({
59
77
  pos,
60
78
  gap: top - previousBottom,
61
- height: bottom - top,
62
- breakBefore: breakAfterPrevious || dom.hasAttribute(editorAttributes.pageBreakBefore),
63
- breaks,
64
- ...measuredTable ? { table: measuredTable.table } : {}
79
+ height,
80
+ breakBefore: dom.hasAttribute(editorAttributes.pageBreakBefore),
81
+ breakAfter: breakWithoutSpace(dom, spaces.length),
82
+ // A space is never opened inside a table, so at most one of the two lists holds anything
83
+ candidates: [...forced, ...measuredTable?.candidates ?? []],
84
+ minFirstPiece: measuredTable?.minFirstPiece ?? forced[0]?.offset ?? height
65
85
  });
66
86
  previousBottom = bottom;
67
- breakAfterPrevious = breakWithoutSpace(dom, spaces.length);
68
87
  }
69
88
  return {
70
89
  left: (sheetRect.left - layerRect.left) / scale,
@@ -72,7 +91,8 @@ function measureSheet(view, layer) {
72
91
  width: sheetRect.width / scale,
73
92
  contentTop,
74
93
  contentBottom,
75
- blocks
94
+ blocks,
95
+ tables
76
96
  };
77
97
  }
78
98
  export {
@@ -5,6 +5,7 @@
5
5
  * The document model is left untouched, so no trace of them is left in
6
6
  * the exported XML or in the edit history.
7
7
  */
8
+ import type { Node as PMNode } from "prosemirror-model";
8
9
  import { Plugin } from "prosemirror-state";
9
10
  import { DecorationSet, type EditorView } from "prosemirror-view";
10
11
  import type { BlockPush, BreakSpace, TableContinuation } from "./pageLayout";
@@ -26,6 +27,21 @@ interface PageMarks {
26
27
  tableContinuations: readonly TableContinuation[];
27
28
  decorations: DecorationSet;
28
29
  }
30
+ /** One page break inside a block, where it stands and how much room the `br` itself takes */
31
+ interface BreakAt {
32
+ at: number;
33
+ size: number;
34
+ }
35
+ /**
36
+ * Every page break inside this block, in document order.
37
+ * A space is put on the breaks of a top-level paragraph alone: inside a table cell it would grow
38
+ * the cell rather than the page, so a break there is left to the whole-block rule in
39
+ * `page/measureBlocks`, and none is found here.
40
+ *
41
+ * The measurement reads the same list to pair each space element it finds with the break it
42
+ * belongs to (`page/measureBlocks`).
43
+ */
44
+ export declare function pageBreaksIn(block: PMNode, blockPos: number): BreakAt[];
29
45
  export declare function pageDecorations(): Plugin<PageMarks>;
30
46
  /** Does nothing when the same values are already applied */
31
47
  export declare function setPagePushes(view: EditorView, pushes: readonly BlockPush[]): void;
@@ -204,6 +204,7 @@ function setTableContinuations(view, continuations) {
204
204
  );
205
205
  }
206
206
  export {
207
+ pageBreaksIn,
207
208
  pageDecorations,
208
209
  setPageBreakSpaces,
209
210
  setPagePushes,
@@ -10,6 +10,8 @@
10
10
  * document breaks at Letter's height and fits its tables to Letter's width.
11
11
  */
12
12
  import { type PageGeometry } from "../docx/pageGeometry";
13
+ import type { MeasuredBlock, PageCut } from "./blockKinds";
14
+ export type { MeasuredBlock };
13
15
  /** The paper measured in the pixels the sheet is drawn with */
14
16
  export interface PagePixels {
15
17
  /** The whole sheet, padding included */
@@ -45,44 +47,6 @@ export declare const A4_PAGE_PIXELS: PagePixels;
45
47
  * moment before a document is opened.
46
48
  */
47
49
  export declare function pageGeometryStyle(page: PagePixels): string;
48
- /** One body block as drawn on screen */
49
- export interface MeasuredBlock {
50
- /** The position where this block starts in the document */
51
- pos: number;
52
- /** The height that naturally opens up between the previous block and this one */
53
- gap: number;
54
- height: number;
55
- /** Set when the document records that a new page starts at this block */
56
- breakBefore: boolean;
57
- /**
58
- * Every page break inside the block, in document order, each measured from the block's own top
59
- * so that the block can be laid out wherever it lands.
60
- * That top is the one the block would be drawn at with no space in it, so a space opened at one
61
- * break shifts the ones after it.
62
- */
63
- breaks: readonly number[];
64
- /** Row boundaries available when this block is an editable table */
65
- table?: MeasuredTable;
66
- }
67
- /** A row that may start the continued part of a table */
68
- export interface TableBoundary {
69
- /** The row's document position */
70
- pos: number;
71
- /** Its top measured from the table's natural top */
72
- offset: number;
73
- }
74
- /** The measurements needed to continue a table without changing the document */
75
- export interface MeasuredTable {
76
- boundaries: readonly TableBoundary[];
77
- /** The smallest useful first piece: headers followed by one body row group */
78
- firstPageMinimum: number;
79
- repeatHeaderHeight: number;
80
- /** Document positions of the contiguous header rows at the start of the table */
81
- headerRows: readonly number[];
82
- /** Changes when any projected header content or formatting changes */
83
- headerSignature: string;
84
- columns: number;
85
- }
86
50
  /** The space opened up at one page break, so that what follows it starts the next page */
87
51
  export interface BreakSpace {
88
52
  /** The position where the block holding the break starts */
@@ -148,8 +112,8 @@ export interface PageStart {
148
112
  export interface PageLayout {
149
113
  /** Only the blocks to be moved down to the next page */
150
114
  pushes: BlockPush[];
151
- spaces: BreakSpace[];
152
- tableContinuations: TableContinuation[];
115
+ /** Where the layout parted a block, and the space it opened before the continued piece */
116
+ cuts: PageCut[];
153
117
  splits: PageSplit[];
154
118
  /** One per page, the first page included */
155
119
  pages: PageStart[];
@@ -169,8 +133,9 @@ export interface PageLayoutInput {
169
133
  * blocks below it move down by the same amount.
170
134
  * A block taller than one page cannot be pushed, so it is left where it is and only the
171
135
  * places it crosses are reported.
172
- * A page break inside a block is filled out to the end of the page it falls on, which carries
173
- * the rest of that block - the rest of the sentence, the rest of the list item - to the top of
174
- * the next page while the block itself stays whole.
136
+ * A block is parted only where its measurer offered a candidate. A forced one - a page break
137
+ * written into the text - is filled out to the end of the page it falls on, which carries the rest
138
+ * of that block to the top of the next page while the block itself stays whole. An optional one -
139
+ * a row boundary in a table - is taken only where the piece after it would run off the page.
175
140
  */
176
141
  export declare function pageLayout({ blocks, pageBodyHeight, pageStep, }: PageLayoutInput): PageLayout;
@@ -47,8 +47,7 @@ function pageLayout({
47
47
  pageStep
48
48
  }) {
49
49
  const pushes = [];
50
- const spaces = [];
51
- const tableContinuations = [];
50
+ const cuts = [];
52
51
  const splits = [];
53
52
  const firstPage = {
54
53
  page: 1,
@@ -58,14 +57,7 @@ function pageLayout({
58
57
  };
59
58
  const pages = [firstPage];
60
59
  if (!(pageBodyHeight > 0)) {
61
- return {
62
- pushes,
63
- spaces,
64
- tableContinuations,
65
- splits,
66
- pages,
67
- bodyHeight: 0
68
- };
60
+ return { pushes, cuts, splits, pages, bodyHeight: 0 };
69
61
  }
70
62
  let pageStart = 0;
71
63
  let cursor = 0;
@@ -82,11 +74,12 @@ function pageLayout({
82
74
  split(pageStart + pageBodyHeight, true, false);
83
75
  }
84
76
  };
77
+ let breakAfterPrevious = false;
85
78
  for (const block of blocks) {
86
79
  const pageEnd = pageStart + pageBodyHeight;
87
80
  const top = cursor + block.gap;
88
- const startsPage = block.breakBefore && top > pageStart + TOLERANCE_PX;
89
- const first = block.breaks[0] ?? block.table?.firstPageMinimum ?? block.height;
81
+ const startsPage = (block.breakBefore || breakAfterPrevious) && top > pageStart + TOLERANCE_PX;
82
+ const first = block.minFirstPiece;
90
83
  const overflows = top + first > pageEnd + TOLERANCE_PX;
91
84
  const fits = first <= pageBodyHeight + TOLERANCE_PX;
92
85
  const push = startsPage || overflows && fits ? Math.max(0, pageEnd + pageStep - top) : 0;
@@ -98,51 +91,35 @@ function pageLayout({
98
91
  push: round(push)
99
92
  });
100
93
  }
101
- let contentTop = top + push;
102
- for (const [index, offset] of block.breaks.entries()) {
103
- const breakY = contentTop + offset;
104
- crossTo(breakY);
105
- split(pageStart + pageBodyHeight, false, true);
106
- const height = Math.max(0, pageStart - breakY);
107
- spaces.push({ pos: block.pos, index, height: round(height) });
108
- contentTop += height;
109
- }
110
- let tableAdded = 0;
111
- if (block.table) {
112
- let segmentStart = 0;
113
- for (let index = 0; index <= block.table.boundaries.length; index += 1) {
114
- const segmentEnd = block.table.boundaries[index]?.offset ?? block.height;
115
- const segmentTop = contentTop + segmentStart + tableAdded;
116
- let segmentBottom = contentTop + segmentEnd + tableAdded;
117
- const pageEnd2 = pageStart + pageBodyHeight;
118
- const overflows2 = segmentBottom > pageEnd2 + TOLERANCE_PX;
119
- const boundary = index === 0 ? null : block.table.boundaries[index - 1];
120
- const segmentHeight = segmentEnd - segmentStart;
121
- if (overflows2 && boundary && segmentTop > pageStart + TOLERANCE_PX && segmentHeight <= pageBodyHeight + TOLERANCE_PX) {
122
- split(pageEnd2, false, false);
123
- const height = Math.max(0, pageStart - segmentTop);
124
- tableContinuations.push({
125
- pos: boundary.pos,
126
- height: round(height),
127
- headerRows: block.table.headerRows,
128
- headerSignature: block.table.headerSignature,
129
- columns: block.table.columns
130
- });
131
- tableAdded += height + block.table.repeatHeaderHeight;
132
- segmentBottom = contentTop + segmentEnd + tableAdded;
94
+ const contentTop = top + push;
95
+ let added = 0;
96
+ let pieceStart = 0;
97
+ for (let index = 0; index <= block.candidates.length; index += 1) {
98
+ const cut = index === 0 ? null : block.candidates[index - 1];
99
+ const pieceEnd = block.candidates[index]?.offset ?? block.height;
100
+ const pieceTop = contentTop + pieceStart + added;
101
+ let pieceBottom = contentTop + pieceEnd + added;
102
+ if (cut) {
103
+ if (cut.forced) crossTo(pieceTop);
104
+ const pageBottom = pageStart + pageBodyHeight;
105
+ const cutHere = cut.forced || pieceBottom > pageBottom + TOLERANCE_PX && pieceTop > pageStart + TOLERANCE_PX && pieceEnd - pieceStart <= pageBodyHeight + TOLERANCE_PX;
106
+ if (cutHere) {
107
+ split(pageBottom, false, cut.forced);
108
+ const height = Math.max(0, pageStart - pieceTop);
109
+ cuts.push({ at: cut.at, height: round(height) });
110
+ added += height + cut.repeatHeight;
111
+ pieceBottom += height + cut.repeatHeight;
133
112
  }
134
- crossTo(segmentBottom);
135
- segmentStart = segmentEnd;
136
113
  }
114
+ crossTo(pieceBottom);
115
+ pieceStart = pieceEnd;
137
116
  }
138
- const bottom = contentTop + block.height + tableAdded;
139
- crossTo(bottom);
140
- cursor = bottom;
117
+ cursor = contentTop + block.height + added;
118
+ breakAfterPrevious = block.breakAfter;
141
119
  }
142
120
  return {
143
121
  pushes,
144
- spaces,
145
- tableContinuations,
122
+ cuts,
146
123
  splits,
147
124
  pages,
148
125
  bodyHeight: round(pageStart + pageBodyHeight)
@@ -1,10 +1,18 @@
1
1
  /** Measures safe row boundaries without treating pagination decorations as document content. */
2
2
  import type { Node as PMNode } from "prosemirror-model";
3
3
  import type { EditorView } from "prosemirror-view";
4
- import type { MeasuredTable } from "./pageLayout";
4
+ import type { BreakCandidate } from "./blockKinds";
5
5
  export interface TableMeasure {
6
- table: MeasuredTable;
6
+ /** Every row that may start the continued part of the table */
7
+ candidates: readonly BreakCandidate[];
8
+ /** The smallest useful first piece: headers followed by one body row group */
9
+ minFirstPiece: number;
7
10
  /** Height already added by the previous pagination pass */
8
11
  appliedHeight: number;
12
+ /** Document positions of the contiguous header rows at the start of the table */
13
+ headerRows: readonly number[];
14
+ /** Changes when any projected header content or formatting changes */
15
+ headerSignature: string;
16
+ columns: number;
9
17
  }
10
18
  export declare function measureTable(view: EditorView, tableNode: PMNode, tablePos: number, tableDom: HTMLElement, scale?: number): TableMeasure | null;
@@ -81,33 +81,37 @@ function measureTable(view, tableNode, tablePos, tableDom, scale = 1) {
81
81
  if (rows.length === 0) return null;
82
82
  const headers = headerCount(rows);
83
83
  const unsafe = unsafeBoundaries(rows);
84
- const boundaries = [];
84
+ const headerRows = rows.slice(0, headers);
85
+ const repeatHeaderHeight = headerRows.reduce(
86
+ (total, row) => total + row.height,
87
+ 0
88
+ );
89
+ const candidates = [];
85
90
  for (let rowIndex = Math.max(1, headers); rowIndex < rows.length; rowIndex += 1) {
86
91
  const row = rows[rowIndex];
87
92
  if (row && !unsafe.has(rowIndex)) {
88
- boundaries.push({ pos: row.pos, offset: row.top });
93
+ candidates.push({
94
+ at: row.pos,
95
+ offset: row.top,
96
+ forced: false,
97
+ repeatHeight: repeatHeaderHeight
98
+ });
89
99
  }
90
100
  }
91
- const repeatHeaderHeight = rows.slice(0, headers).reduce((total, row) => total + row.height, 0);
92
- const firstBodyBoundary = boundaries.find(
93
- (boundary) => boundary.offset > repeatHeaderHeight + 0.5
101
+ const firstBodyBoundary = candidates.find(
102
+ (candidate) => candidate.offset > repeatHeaderHeight + 0.5
94
103
  );
95
104
  const appliedHeight = appliedRows.reduce(
96
105
  (total, row) => total + row.getBoundingClientRect().height / scale,
97
106
  0
98
107
  );
99
108
  return {
100
- table: {
101
- boundaries,
102
- firstPageMinimum: firstBodyBoundary?.offset ?? tableDom.getBoundingClientRect().height / scale - appliedHeight,
103
- repeatHeaderHeight,
104
- headerRows: rows.slice(0, headers).map((row) => row.pos),
105
- headerSignature: JSON.stringify(
106
- rows.slice(0, headers).map((row) => row.node.toJSON())
107
- ),
108
- columns: columnCount(tableNode)
109
- },
110
- appliedHeight
109
+ candidates,
110
+ minFirstPiece: firstBodyBoundary?.offset ?? tableDom.getBoundingClientRect().height / scale - appliedHeight,
111
+ appliedHeight,
112
+ headerRows: headerRows.map((row) => row.pos),
113
+ headerSignature: JSON.stringify(headerRows.map((row) => row.node.toJSON())),
114
+ columns: columnCount(tableNode)
111
115
  };
112
116
  }
113
117
  export {
@@ -8,9 +8,13 @@
8
8
  * in Japanese or Chinese stays open across a whole clause while the text grows line by line. So no
9
9
  * measurement is taken while a composition is open: the frame is taken again until it is over.
10
10
  */
11
+ import type { Node as PMNode } from "prosemirror-model";
11
12
  import type { EditorView } from "prosemirror-view";
12
13
  import { type RefObject } from "react";
13
14
  import type { PageGeometry } from "../docx/pageGeometry";
15
+ import type { PageCut } from "./blockKinds";
16
+ import { type TableHeaderProjection } from "./measureBlocks";
17
+ import { type BreakSpace, type TableContinuation } from "./pageLayout";
14
18
  /** One place where a page parts from the next. The position is measured on the sheet */
15
19
  export interface PageMark {
16
20
  page: number;
@@ -53,5 +57,16 @@ interface PageLayoutOptions {
53
57
  /** The paper the open document names. A4 where a document names none */
54
58
  geometry?: PageGeometry;
55
59
  }
60
+ /**
61
+ * The layout's cuts as the two kinds of mark the decorations still take.
62
+ *
63
+ * A cut names the position the continued piece starts at, and which of the two it is follows from
64
+ * what stands there: a page `br` the block it sits in counted, or a table row. This stands in
65
+ * until the decorations take the cuts themselves.
66
+ */
67
+ export declare function cutsToLegacyMarks(cuts: readonly PageCut[], tables: ReadonlyMap<number, TableHeaderProjection>, doc: PMNode): {
68
+ spaces: BreakSpace[];
69
+ tableContinuations: TableContinuation[];
70
+ };
56
71
  export declare function usePageLayout({ view, layer, enabled, revision, geometry, }: PageLayoutOptions): PageOverlay | null;
57
72
  export {};