@portone/docx-editor 0.4.0 → 0.5.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.
Files changed (155) hide show
  1. package/CHANGELOG.md +159 -0
  2. package/assets/editor.png +0 -0
  3. package/dist/DocxEditor.d.ts +1 -1
  4. package/dist/DocxEditor.js +44 -25
  5. package/dist/docx/cloning.js +4 -9
  6. package/dist/docx/commentOnlyChange.d.ts +5 -3
  7. package/dist/docx/comments/grammar.d.ts +27 -10
  8. package/dist/docx/comments/grammar.js +18 -67
  9. package/dist/docx/comments/model.d.ts +5 -6
  10. package/dist/docx/comments/model.js +0 -9
  11. package/dist/docx/comments/parts.d.ts +16 -10
  12. package/dist/docx/comments/parts.js +65 -13
  13. package/dist/docx/comments/people.d.ts +11 -3
  14. package/dist/docx/comments/people.js +20 -6
  15. package/dist/docx/comments/policy.js +7 -1
  16. package/dist/docx/comments/reading.d.ts +4 -2
  17. package/dist/docx/comments/reading.js +1 -14
  18. package/dist/docx/comments/writing.js +65 -28
  19. package/dist/docx/exportDocx.js +10 -14
  20. package/dist/docx/exportRefs.d.ts +8 -3
  21. package/dist/docx/exportRefs.js +2 -1
  22. package/dist/docx/fidelity.js +24 -10
  23. package/dist/docx/fields.d.ts +42 -0
  24. package/dist/docx/fields.js +76 -0
  25. package/dist/docx/headersFooters.d.ts +89 -20
  26. package/dist/docx/headersFooters.js +163 -172
  27. package/dist/docx/importDocx.d.ts +1 -1
  28. package/dist/docx/importDocx.js +100 -48
  29. package/dist/docx/importParagraph.d.ts +16 -4
  30. package/dist/docx/importParagraph.js +87 -93
  31. package/dist/docx/importPolicy.d.ts +70 -0
  32. package/dist/docx/importPolicy.js +190 -0
  33. package/dist/docx/importPreserved.d.ts +30 -0
  34. package/dist/docx/importPreserved.js +54 -0
  35. package/dist/docx/importTable.js +56 -12
  36. package/dist/docx/invariants.js +16 -18
  37. package/dist/docx/notes.d.ts +6 -1
  38. package/dist/docx/notes.js +6 -16
  39. package/dist/docx/pageGeometry.d.ts +5 -14
  40. package/dist/docx/pageGeometry.js +0 -12
  41. package/dist/docx/protectionPolicy.d.ts +13 -5
  42. package/dist/docx/protectionPolicy.js +32 -22
  43. package/dist/docx/scan.d.ts +18 -8
  44. package/dist/docx/scan.js +17 -11
  45. package/dist/docx/sections.d.ts +126 -0
  46. package/dist/docx/sections.js +207 -0
  47. package/dist/docx/serializeBlock.d.ts +13 -4
  48. package/dist/docx/serializeBlock.js +16 -18
  49. package/dist/docx/serializeParagraph.d.ts +4 -0
  50. package/dist/docx/serializeParagraph.js +1 -0
  51. package/dist/docx/serializePreserved.d.ts +14 -0
  52. package/dist/docx/serializePreserved.js +24 -0
  53. package/dist/docx/serializeStory.d.ts +17 -0
  54. package/dist/docx/serializeStory.js +17 -0
  55. package/dist/docx/serializeTable.js +10 -13
  56. package/dist/docx/session.d.ts +23 -6
  57. package/dist/docx/session.js +26 -5
  58. package/dist/docx/story.d.ts +140 -0
  59. package/dist/docx/story.js +237 -0
  60. package/dist/docx/storyProjection.js +6 -1
  61. package/dist/editor/clipboard/htmlReader.d.ts +30 -0
  62. package/dist/editor/{externalClipboard.js → clipboard/htmlReader.js} +39 -223
  63. package/dist/editor/clipboard/internalChannel.d.ts +57 -0
  64. package/dist/editor/clipboard/internalChannel.js +58 -0
  65. package/dist/editor/clipboard/normalizers.d.ts +99 -0
  66. package/dist/editor/clipboard/normalizers.js +199 -0
  67. package/dist/editor/clipboard/parser.d.ts +34 -0
  68. package/dist/editor/clipboard/parser.js +58 -0
  69. package/dist/editor/clipboard/plugin.d.ts +21 -0
  70. package/dist/editor/clipboard/plugin.js +320 -0
  71. package/dist/editor/clipboard/readContext.d.ts +24 -0
  72. package/dist/editor/clipboard/readContext.js +19 -0
  73. package/dist/editor/clipboard/readers.d.ts +33 -0
  74. package/dist/editor/clipboard/readers.js +37 -0
  75. package/dist/editor/commands/comments/editing.d.ts +26 -6
  76. package/dist/editor/commands/comments/editing.js +175 -105
  77. package/dist/editor/commands/comments/model.d.ts +34 -17
  78. package/dist/editor/commands/comments/model.js +11 -0
  79. package/dist/editor/commands/comments/reading.d.ts +5 -3
  80. package/dist/editor/commands/comments/reading.js +8 -58
  81. package/dist/editor/commands/index.d.ts +2 -2
  82. package/dist/editor/commands/index.js +2 -0
  83. package/dist/editor/commands/listCommands.d.ts +6 -0
  84. package/dist/editor/commands/listCommands.js +3 -2
  85. package/dist/editor/commands/noteQueries.d.ts +22 -5
  86. package/dist/editor/commands/noteQueries.js +34 -6
  87. package/dist/editor/createEditor.js +13 -3
  88. package/dist/editor/documentStyles.d.ts +26 -4
  89. package/dist/editor/documentStyles.js +9 -4
  90. package/dist/editor/imageFiles.d.ts +2 -2
  91. package/dist/editor/imageFiles.js +2 -0
  92. package/dist/editor/insertTable.d.ts +1 -1
  93. package/dist/editor/insertTable.js +2 -2
  94. package/dist/editor/plainText.d.ts +12 -0
  95. package/dist/editor/plainText.js +2 -1
  96. package/dist/editor/plugins/columnResize.js +6 -3
  97. package/dist/editor/plugins/commentComposer.d.ts +28 -0
  98. package/dist/editor/plugins/commentComposer.js +46 -0
  99. package/dist/editor/plugins/commentDecorations.d.ts +29 -2
  100. package/dist/editor/plugins/commentDecorations.js +89 -15
  101. package/dist/editor/plugins/documentProjection.d.ts +31 -0
  102. package/dist/editor/plugins/documentProjection.js +22 -0
  103. package/dist/editor/plugins/imagePaste.js +15 -8
  104. package/dist/editor/plugins/linkPanel.d.ts +7 -3
  105. package/dist/editor/plugins/linkPanel.js +13 -27
  106. package/dist/editor/plugins/panelState.d.ts +67 -0
  107. package/dist/editor/plugins/panelState.js +54 -0
  108. package/dist/editor/plugins/tableContextMenu.d.ts +1 -1
  109. package/dist/editor/plugins/tableContextMenu.js +30 -47
  110. package/dist/editor/plugins/textContextMenu.d.ts +1 -1
  111. package/dist/editor/plugins/textContextMenu.js +30 -47
  112. package/dist/ooxml/fragment.d.ts +11 -0
  113. package/dist/ooxml/fragment.js +14 -4
  114. package/dist/ooxml/image.d.ts +3 -4
  115. package/dist/ooxml/image.js +50 -5
  116. package/dist/ooxml/names.d.ts +6 -0
  117. package/dist/ooxml/names.js +2 -0
  118. package/dist/ooxml/props.d.ts +8 -0
  119. package/dist/ooxml/props.js +14 -4
  120. package/dist/ooxml/rangeMarkers.d.ts +5 -0
  121. package/dist/ooxml/rangeMarkers.js +24 -0
  122. package/dist/page/PageGuides.d.ts +7 -3
  123. package/dist/page/PageGuides.js +13 -24
  124. package/dist/page/pageLayout.d.ts +64 -5
  125. package/dist/page/pageLayout.js +90 -29
  126. package/dist/page/usePageLayout.d.ts +14 -4
  127. package/dist/page/usePageLayout.js +19 -20
  128. package/dist/schema/attrRoles.js +38 -20
  129. package/dist/schema/docxSchema.d.ts +9 -1
  130. package/dist/schema/docxSchema.js +191 -139
  131. package/dist/schema/editGuard.d.ts +1 -1
  132. package/dist/schema/guards.js +2 -2
  133. package/dist/schema/index.d.ts +1 -0
  134. package/dist/schema/preservedFragments.d.ts +12 -0
  135. package/dist/schema/preservedFragments.js +38 -0
  136. package/dist/schema/preservedGuards.d.ts +9 -7
  137. package/dist/schema/preservedGuards.js +112 -11
  138. package/dist/schema/protection.d.ts +34 -5
  139. package/dist/schema/protection.js +42 -14
  140. package/dist/schema/rendering.js +3 -1
  141. package/dist/schema/stories.d.ts +49 -0
  142. package/dist/schema/stories.js +78 -0
  143. package/dist/styles/classNames.d.ts +3 -5
  144. package/dist/styles/classNames.js +3 -5
  145. package/dist/styles.css +74 -12
  146. package/dist/table/resize.d.ts +5 -3
  147. package/dist/ui/CommentsPanel.d.ts +1 -3
  148. package/dist/ui/CommentsPanel.js +24 -15
  149. package/dist/ui/NotesPanel.js +2 -2
  150. package/dist/ui/TextMenu.d.ts +1 -3
  151. package/dist/ui/TextMenu.js +10 -6
  152. package/dist/ui/comments/CommentComposer.d.ts +6 -1
  153. package/dist/ui/comments/CommentComposer.js +26 -3
  154. package/package.json +1 -1
  155. package/dist/editor/externalClipboard.d.ts +0 -22
@@ -1,6 +1,7 @@
1
1
  // src/ooxml/image.ts
2
2
  import { NAMESPACES } from "./names.js";
3
- import { childByLocalName, escapeXml, R_NS } from "./xml.js";
3
+ import { parseAttrs, readTag } from "./tagScan.js";
4
+ import { childByLocalName, escapeXml, localPart, R_NS } from "./xml.js";
4
5
  var EMU_PER_PX = 9525;
5
6
  function emuToPx(emu) {
6
7
  return emu / EMU_PER_PX;
@@ -76,11 +77,55 @@ function readDrawingPicture(drawing) {
76
77
  const descr = childOf(inline, "docPr")?.getAttribute("descr") ?? null;
77
78
  return { relId, extent, alt: descr === "" ? null : descr };
78
79
  }
79
- var EXTENT_TAG = /<([\w.-]+:)?extent\b[^>]*\/>/g;
80
- var EXT_TAG = /<([\w.-]+:)?ext\b[^>]*\/>/g;
80
+ function isPictureSize(path) {
81
+ const names = path.join("/");
82
+ return names === "drawing/inline/extent" || names === "drawing/inline/graphic/graphicData/pic/spPr/xfrm/ext";
83
+ }
84
+ function resizeTag(open, extent) {
85
+ const tag = readTag(open, 0);
86
+ if (!tag) return open;
87
+ const attrs = new Map(
88
+ parseAttrs(
89
+ open.slice(tag.nameEnd, tag.end - (tag.kind === "empty" ? 2 : 1))
90
+ ) ?? []
91
+ );
92
+ return open.replace(
93
+ /(\s+)([^\s=/>]+)(\s*=\s*)(["'])([\s\S]*?)\4/g,
94
+ (whole, gap, name, equals, quote) => {
95
+ if (name !== "cx" && name !== "cy") return whole;
96
+ const size = extent[name];
97
+ if (Number(attrs.get(name)) === size) return whole;
98
+ return `${gap}${name}${equals}${quote}${size}${quote}`;
99
+ }
100
+ );
101
+ }
81
102
  function withExtent(xml, extent) {
82
- const size = `cx="${extent.cx}" cy="${extent.cy}"`;
83
- return xml.replace(EXTENT_TAG, (_match, prefix) => `<${prefix ?? ""}extent ${size}/>`).replace(EXT_TAG, (_match, prefix) => `<${prefix ?? ""}ext ${size}/>`);
103
+ const path = [];
104
+ const parts = [];
105
+ let at = 0;
106
+ let kept = 0;
107
+ for (; ; ) {
108
+ const lt = xml.indexOf("<", at);
109
+ if (lt === -1) break;
110
+ const tag = readTag(xml, lt);
111
+ if (!tag) return xml;
112
+ if (tag.kind === "close") {
113
+ path.pop();
114
+ } else if (tag.kind !== "other") {
115
+ path.push(localPart(tag.name));
116
+ if (isPictureSize(path)) {
117
+ parts.push(
118
+ xml.slice(kept, lt),
119
+ resizeTag(xml.slice(lt, tag.end), extent)
120
+ );
121
+ kept = tag.end;
122
+ }
123
+ if (tag.kind === "empty") path.pop();
124
+ }
125
+ at = tag.end;
126
+ }
127
+ parts.push(xml.slice(kept));
128
+ return parts.join("");
84
129
  }
85
130
  function imageDrawingXml(image) {
86
131
  const size = `cx="${image.extent.cx}" cy="${image.extent.cy}"`;
@@ -13,6 +13,12 @@ export declare const W_NS = "http://schemas.openxmlformats.org/wordprocessingml/
13
13
  * every relationship type name is built on
14
14
  */
15
15
  export declare const R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
16
+ /**
17
+ * The math namespace. Nothing here is written under a prefix of ours - a `m:oMath` travels as the
18
+ * XML it arrived as - so it names the namespace alone and stays out of `NAMESPACES`, which is the
19
+ * list of prefixes the writer spells out.
20
+ */
21
+ export declare const M_NS = "http://schemas.openxmlformats.org/officeDocument/2006/math";
16
22
  /** The prefix every WordprocessingML element and attribute this package writes carries */
17
23
  export declare const W_PREFIX = "w";
18
24
  export declare const NAMESPACES: {
@@ -1,6 +1,7 @@
1
1
  // src/ooxml/names.ts
2
2
  var W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
3
3
  var R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
4
+ var M_NS = "http://schemas.openxmlformats.org/officeDocument/2006/math";
4
5
  var W_PREFIX = "w";
5
6
  var NAMESPACES = {
6
7
  w: W_NS,
@@ -25,6 +26,7 @@ function xmlnsDecl(prefix) {
25
26
  return `xmlns:${prefix}="${NAMESPACES[prefix]}"`;
26
27
  }
27
28
  export {
29
+ M_NS,
28
30
  NAMESPACES,
29
31
  R_NS,
30
32
  W_NS,
@@ -52,6 +52,14 @@ export declare function renderProps(props: Props): string;
52
52
  * carries children in a different order under a different parent (`pPr/rPr`).
53
53
  */
54
54
  export declare function setChild(props: Props, name: string, xml: string | null, parent?: string): Props;
55
+ /**
56
+ * Replaces every child of one name with this list of them, in the order given.
57
+ *
58
+ * `CT_SectPr` lets a section name a header and a footer once per variant, so a name standing more
59
+ * than once is a list rather than a single child, and writing one of them is writing the list. An
60
+ * empty list takes them all away.
61
+ */
62
+ export declare function setChildren(props: Props, name: string, xmls: readonly string[], parent?: string): Props;
55
63
  export declare function propsChild(children: readonly PropsChild[], name: string): PropsChild | undefined;
56
64
  /**
57
65
  * The opening tag's attributes as pairs, and null when their shape cannot be made out.
@@ -115,28 +115,37 @@ function tailWith(props, carried) {
115
115
  return tail === "" ? {} : { tail };
116
116
  }
117
117
  function setChild(props, name, xml, parent) {
118
+ return setChildren(props, name, xml === null ? [] : [xml], parent);
119
+ }
120
+ function setChildren(props, name, xmls, parent) {
118
121
  const order = childOrderOf(localPart(props.tag), parent);
119
122
  const at = props.children.findIndex((entry) => entry.name === name);
120
- if (at !== -1 && xml !== null) {
123
+ if (at !== -1 && xmls.length > 0) {
121
124
  const rest = dropChildren(props.children.slice(at + 1), name);
122
125
  return {
123
126
  ...props,
124
127
  children: [
125
128
  ...props.children.slice(0, at),
126
- { ...props.children[at], xml },
129
+ ...xmls.map(
130
+ (xml, index2) => index2 === 0 ? { ...props.children[at], xml } : { name, xml }
131
+ ),
127
132
  ...rest.kept
128
133
  ],
129
134
  ...tailWith(props, rest.carried)
130
135
  };
131
136
  }
132
137
  const { kept, carried } = dropChildren(props.children, name);
133
- if (xml === null) {
138
+ if (xmls.length === 0) {
134
139
  return { ...props, children: kept, ...tailWith(props, carried) };
135
140
  }
136
141
  const index = insertIndex(kept, name, order, props.tag);
137
142
  return {
138
143
  ...props,
139
- children: [...kept.slice(0, index), { name, xml }, ...kept.slice(index)],
144
+ children: [
145
+ ...kept.slice(0, index),
146
+ ...xmls.map((xml) => ({ name, xml })),
147
+ ...kept.slice(index)
148
+ ],
140
149
  ...tailWith(props, carried)
141
150
  };
142
151
  }
@@ -210,5 +219,6 @@ export {
210
219
  renderElement,
211
220
  renderProps,
212
221
  setChild,
222
+ setChildren,
213
223
  withAttrs
214
224
  };
@@ -0,0 +1,5 @@
1
+ /** `EG_RangeMarkupElements` less the comment markers modelled inside paragraphs and wrappers */
2
+ export declare const RANGE_MARKERS: string[];
3
+ /** The markers of `EG_RunLevelElts` that stand outside `EG_RangeMarkupElements` */
4
+ export declare const PERMISSION_MARKERS: string[];
5
+ export declare const COMMENT_RANGE_MARKERS: string[];
@@ -0,0 +1,24 @@
1
+ // src/ooxml/rangeMarkers.ts
2
+ var RANGE_MARKERS = [
3
+ "bookmarkStart",
4
+ "bookmarkEnd",
5
+ "moveFromRangeStart",
6
+ "moveFromRangeEnd",
7
+ "moveToRangeStart",
8
+ "moveToRangeEnd",
9
+ "customXmlInsRangeStart",
10
+ "customXmlInsRangeEnd",
11
+ "customXmlDelRangeStart",
12
+ "customXmlDelRangeEnd",
13
+ "customXmlMoveFromRangeStart",
14
+ "customXmlMoveFromRangeEnd",
15
+ "customXmlMoveToRangeStart",
16
+ "customXmlMoveToRangeEnd"
17
+ ];
18
+ var PERMISSION_MARKERS = ["permStart", "permEnd"];
19
+ var COMMENT_RANGE_MARKERS = ["commentRangeStart", "commentRangeEnd"];
20
+ export {
21
+ COMMENT_RANGE_MARKERS,
22
+ PERMISSION_MARKERS,
23
+ RANGE_MARKERS
24
+ };
@@ -6,11 +6,15 @@
6
6
  * Only the places crossed by a block taller than one page are marked over the text, with
7
7
  * a faint dotted line.
8
8
  * The box only paints, so the mouse still reaches the text beneath it.
9
+ *
10
+ * Each page draws the header and footer of the section it opens with, since a document names its
11
+ * headers section by section (`docx/headersFooters`).
9
12
  */
10
13
  import type { ReactElement } from "react";
11
14
  import { type HeadersFooters } from "../docx/headersFooters";
12
- import type { PageOverlay } from "./usePageLayout";
13
- export declare function PageGuides({ overlay, headersFooters, }: {
15
+ import type { PageFace, PageOverlay } from "./usePageLayout";
16
+ export declare function PageGuides({ overlay, headersFootersFor, }: {
14
17
  overlay: PageOverlay;
15
- headersFooters?: HeadersFooters;
18
+ /** The stories the section this page belongs to shows. Left out while no document is open */
19
+ headersFootersFor?: (page: PageFace) => HeadersFooters | null;
16
20
  }): ReactElement;
@@ -1,14 +1,14 @@
1
1
  // src/page/PageGuides.tsx
2
2
  import {
3
3
  displayPageNumber,
4
- headerFooterAlign,
4
+ headerFooterOn,
5
5
  headerFooterText
6
6
  } from "../docx/headersFooters.js";
7
7
  import { editorClassNames } from "../styles/classNames.js";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
9
9
  function PageGuides({
10
10
  overlay,
11
- headersFooters
11
+ headersFootersFor
12
12
  }) {
13
13
  const totalPages = overlay.pages.length;
14
14
  return /* @__PURE__ */ jsxs(
@@ -34,30 +34,19 @@ function PageGuides({
34
34
  },
35
35
  mark.page
36
36
  )),
37
- headersFooters && overlay.pages.flatMap((page) => {
38
- if (page.crossed) return [];
37
+ headersFootersFor && overlay.pages.flatMap((page) => {
38
+ const headersFooters = page.crossed ? null : headersFootersFor(page);
39
+ if (headersFooters === null) return [];
39
40
  const number = displayPageNumber(headersFooters, page.page);
40
- const header = headerFooterText(
41
+ const header = headerFooterOn(
41
42
  headersFooters.headers,
42
43
  headersFooters,
43
- page.page,
44
- totalPages
44
+ page.pageInSection
45
45
  );
46
- const footer = headerFooterText(
46
+ const footer = headerFooterOn(
47
47
  headersFooters.footers,
48
48
  headersFooters,
49
- page.page,
50
- totalPages
51
- );
52
- const headerAlign = headerFooterAlign(
53
- headersFooters.headers,
54
- headersFooters,
55
- page.page
56
- );
57
- const footerAlign = headerFooterAlign(
58
- headersFooters.footers,
59
- headersFooters,
60
- page.page
49
+ page.pageInSection
61
50
  );
62
51
  return [
63
52
  header === null ? null : /* @__PURE__ */ jsx(
@@ -68,10 +57,10 @@ function PageGuides({
68
57
  top: `${page.headerTop}px`,
69
58
  left: `${page.left}px`,
70
59
  width: `${page.width}px`,
71
- textAlign: headerAlign ?? void 0
60
+ textAlign: header.align ?? void 0
72
61
  },
73
62
  title: `Page ${number} header`,
74
- children: header
63
+ children: headerFooterText(header.story, number, totalPages)
75
64
  },
76
65
  `header-${page.page}`
77
66
  ),
@@ -83,10 +72,10 @@ function PageGuides({
83
72
  top: `${page.footerTop}px`,
84
73
  left: `${page.left}px`,
85
74
  width: `${page.width}px`,
86
- textAlign: footerAlign ?? void 0
75
+ textAlign: footer.align ?? void 0
87
76
  },
88
77
  title: `Page ${number} footer`,
89
- children: footer
78
+ children: headerFooterText(footer.story, number, totalPages)
90
79
  },
91
80
  `footer-${page.page}`
92
81
  )
@@ -7,9 +7,17 @@
7
7
  * the places where Word actually breaks may differ.
8
8
  *
9
9
  * The size of a page is the document's own (`docx/pageGeometry`), not a fixed A4: a Letter
10
- * document breaks at Letter's height and fits its tables to Letter's width.
10
+ * document breaks at Letter's height and fits its tables to Letter's width. A document written in
11
+ * several sections breaks each block at the height of the section that block sits in
12
+ * (`docx/sections`), and a section opens a page of its own unless it declares `continuous`, which
13
+ * carries it on down the page the section before it ends on.
14
+ *
15
+ * The width is the exception: one sheet is drawn at one width (`styles/editor.css`), which is the
16
+ * first section's, so a section on wider paper is paginated on its own height but drawn on the
17
+ * paper the sheet holds.
11
18
  */
12
19
  import { type PageGeometry } from "../docx/pageGeometry";
20
+ import type { DocumentSection, SectionProperties } from "../docx/sections";
13
21
  import type { MeasuredBlock, PageCut } from "./blockKinds";
14
22
  export type { MeasuredBlock };
15
23
  /** The paper measured in the pixels the sheet is drawn with */
@@ -39,6 +47,35 @@ export declare const PAGE_SPLIT_PX = 24;
39
47
  export declare function pagePixels(geometry: PageGeometry): PagePixels;
40
48
  /** The paper a document that names none is drawn on */
41
49
  export declare const A4_PAGE_PIXELS: PagePixels;
50
+ /** The paper one section of a document is laid out on */
51
+ export interface SectionPixels {
52
+ /**
53
+ * The last position this section covers. A section is closed by the paragraph carrying its
54
+ * `w:sectPr` (§17.6.17), so the block after that one opens the section that follows, and the
55
+ * section the body itself closes reaches past every block.
56
+ */
57
+ untilPos: number;
58
+ pixels: PagePixels;
59
+ /**
60
+ * How this section starts (§17.6.22). `continuous` carries it on down the page the section
61
+ * before it ends on; every other kind opens a page.
62
+ */
63
+ type: SectionProperties["type"];
64
+ }
65
+ /** The one section a document naming none is laid out on */
66
+ export declare const A4_SECTION_PIXELS: readonly SectionPixels[];
67
+ /**
68
+ * The paper of every section of a document, in the pixels the sheet is drawn with.
69
+ *
70
+ * Reading the sections walks every block (`docx/sections`), so this runs once per document change
71
+ * rather than once per measurement.
72
+ */
73
+ export declare function sectionPixels(sections: readonly DocumentSection[]): SectionPixels[];
74
+ /**
75
+ * The paper the block at this position is laid out on, which is the paper of every page that
76
+ * block stands on. A4 where the list names no section at all.
77
+ */
78
+ export declare function sectionPaperAt(sections: readonly SectionPixels[], pos: number): PagePixels;
42
79
  /**
43
80
  * The paper the document names, as the CSS variables the sheet is drawn from.
44
81
  *
@@ -74,6 +111,17 @@ export interface PageStart {
74
111
  page: number;
75
112
  /** Where this page's body starts, measured from the top of the body */
76
113
  bodyStart: number;
114
+ /**
115
+ * The position of the block this page opens with, which is the block the text crossing into it
116
+ * belongs to. 0 for a document holding no block at all
117
+ */
118
+ pos: number;
119
+ /**
120
+ * The place this page takes within its own section, counted from 1 again at every section it
121
+ * opens. A document read with no section table at all is one section, so this counts the whole
122
+ * of it
123
+ */
124
+ pageInSection: number;
77
125
  /**
78
126
  * Whether text crosses over from the previous page, so this page continues with no top
79
127
  * margin
@@ -90,12 +138,21 @@ export interface PageLayout {
90
138
  pages: PageStart[];
91
139
  /** The body height with the last page filled out in full */
92
140
  bodyHeight: number;
141
+ /**
142
+ * The margin under that last page, which is its own section's. The sheet is padded from the
143
+ * first section's paper alone (`editor/createEditor`), so a document ending on a deeper bottom
144
+ * margin needs the sheet drawn down to this.
145
+ */
146
+ marginBottom: number;
93
147
  }
94
148
  export interface PageLayoutInput {
95
149
  blocks: readonly MeasuredBlock[];
96
- pageBodyHeight: number;
97
- /** From the end of the previous page's body to the top of the next page's body */
98
- pageStep: number;
150
+ /**
151
+ * The paper of each section in document order (`sectionPixels`). A block is laid out on the
152
+ * paper of the section it sits in, and the section a block opens starts a page of its own
153
+ * unless that section is `continuous`.
154
+ */
155
+ sections: readonly SectionPixels[];
99
156
  }
100
157
  /**
101
158
  * Distributes the blocks across pages.
@@ -110,5 +167,7 @@ export interface PageLayoutInput {
110
167
  * a row boundary in a table - is taken only where the piece after it would run off the page.
111
168
  * A block kept with the next one is pushed together with what it is kept with: the blocks kept
112
169
  * after it and the first piece of the block the keeps end at.
170
+ * Each block is measured against the paper of its own section, and the first block of a section
171
+ * opens a page of its own unless the section it opens declares `continuous`.
113
172
  */
114
- export declare function pageLayout({ blocks, pageBodyHeight, pageStep, }: PageLayoutInput): PageLayout;
173
+ export declare function pageLayout({ blocks, sections }: PageLayoutInput): PageLayout;
@@ -24,6 +24,23 @@ function pagePixels(geometry) {
24
24
  };
25
25
  }
26
26
  var A4_PAGE_PIXELS = pagePixels(A4_PORTRAIT);
27
+ var A4_SECTION_PIXELS = [
28
+ { untilPos: Number.POSITIVE_INFINITY, pixels: A4_PAGE_PIXELS, type: null }
29
+ ];
30
+ function sectionPixels(sections) {
31
+ return sections.map(({ anchor, props }) => ({
32
+ untilPos: anchor.kind === "paragraph" ? anchor.pos : Number.POSITIVE_INFINITY,
33
+ pixels: pagePixels(props.geometry),
34
+ type: props.type
35
+ }));
36
+ }
37
+ function sectionIndexAt(sections, pos) {
38
+ const reaching = sections.findIndex((section) => pos <= section.untilPos);
39
+ return reaching === -1 ? sections.length - 1 : reaching;
40
+ }
41
+ function sectionPaperAt(sections, pos) {
42
+ return sections[sectionIndexAt(sections, pos)]?.pixels ?? A4_PAGE_PIXELS;
43
+ }
27
44
  function px(value) {
28
45
  return `${Math.round(value * 100) / 100}px`;
29
46
  }
@@ -41,15 +58,29 @@ var TOLERANCE_PX = 0.5;
41
58
  function round(value) {
42
59
  return Math.round(value * 100) / 100;
43
60
  }
44
- function keepsWithNext(block, next) {
45
- return block.keepWithNext && block.candidates.length === 0 && !block.breakAfter && !next.breakBefore;
61
+ function withinSections(pages, sections) {
62
+ let section = null;
63
+ let within = 0;
64
+ return pages.map((page) => {
65
+ const index = sectionIndexAt(sections, page.pos);
66
+ within = index === section ? within + 1 : 1;
67
+ section = index;
68
+ return { ...page, pageInSection: within };
69
+ });
46
70
  }
47
- function keptExtents(blocks, fitsPage) {
71
+ function keepsWithNext(block, next, opensPage) {
72
+ return block.keepWithNext && !opensPage && block.candidates.length === 0 && !block.breakAfter && !next.breakBefore;
73
+ }
74
+ function keptExtents(blocks, laid) {
75
+ const fitsPage = (extent, index) => {
76
+ const height = laid[index]?.paper.bodyHeight ?? 0;
77
+ return extent <= height + TOLERANCE_PX;
78
+ };
48
79
  const extents = /* @__PURE__ */ new Map();
49
80
  let run = [];
50
81
  const settle = () => {
51
82
  const head = run.at(-1);
52
- if (head && fitsPage(head.extent)) {
83
+ if (head && fitsPage(head.extent, head.index)) {
53
84
  for (const { index, extent } of run) {
54
85
  extents.set(index, extent);
55
86
  }
@@ -59,7 +90,8 @@ function keptExtents(blocks, fitsPage) {
59
90
  for (let index = blocks.length - 2; index >= 0; index -= 1) {
60
91
  const block = blocks[index];
61
92
  const next = blocks[index + 1];
62
- if (block && next && keepsWithNext(block, next)) {
93
+ const opensPage = laid[index + 1]?.opensPage === true;
94
+ if (block && next && keepsWithNext(block, next, opensPage)) {
63
95
  const below = run.at(-1)?.extent ?? next.minFirstPiece;
64
96
  run.push({ index, extent: block.height + next.gap + below });
65
97
  } else {
@@ -69,44 +101,67 @@ function keptExtents(blocks, fitsPage) {
69
101
  settle();
70
102
  return extents;
71
103
  }
72
- function pageLayout({
73
- blocks,
74
- pageBodyHeight,
75
- pageStep
76
- }) {
104
+ function pageLayout({ blocks, sections }) {
77
105
  const pushes = [];
78
106
  const cuts = [];
79
107
  const splits = [];
80
- const firstPage = { page: 1, bodyStart: 0, crossed: false };
81
- const pages = [firstPage];
82
- if (!(pageBodyHeight > 0)) {
83
- return { pushes, cuts, splits, pages, bodyHeight: 0 };
108
+ let blockPos = blocks[0]?.pos ?? 0;
109
+ const opened = [
110
+ { page: 1, bodyStart: 0, pos: blockPos, crossed: false }
111
+ ];
112
+ const roomy = sections.find(
113
+ (section) => section.pixels.bodyHeight > 0
114
+ )?.pixels;
115
+ if (roomy === void 0) {
116
+ return {
117
+ pushes,
118
+ cuts,
119
+ splits,
120
+ pages: withinSections(opened, sections),
121
+ bodyHeight: 0,
122
+ marginBottom: 0
123
+ };
84
124
  }
125
+ const blockSections = blocks.map(
126
+ (block) => sectionIndexAt(sections, block.pos)
127
+ );
128
+ const laid = blockSections.map((section, index) => {
129
+ const pixels = sections[section]?.pixels;
130
+ return {
131
+ paper: pixels && pixels.bodyHeight > 0 ? pixels : roomy,
132
+ opensPage: index > 0 && section !== blockSections[index - 1] && sections[section]?.type !== "continuous"
133
+ };
134
+ });
85
135
  let pageStart = 0;
86
136
  let cursor = 0;
87
- const split = (y, crossed, forced) => {
137
+ let paper = laid[0]?.paper ?? roomy;
138
+ const stepTo = (opening) => paper.pageStep - paper.marginTop + opening.marginTop;
139
+ const split = (y, crossed, forced, opening = paper) => {
88
140
  const page = splits.length + 2;
89
141
  splits.push({ y: round(y), page, forced, crossed });
90
- pageStart = crossed ? y : y + pageStep;
91
- pages.push({ page, bodyStart: round(pageStart), crossed });
142
+ pageStart = crossed ? y : y + stepTo(opening);
143
+ opened.push({ page, bodyStart: round(pageStart), pos: blockPos, crossed });
92
144
  };
93
145
  const crossTo = (y) => {
94
- while (y > pageStart + pageBodyHeight + TOLERANCE_PX) {
95
- split(pageStart + pageBodyHeight, true, false);
146
+ while (y > pageStart + paper.bodyHeight + TOLERANCE_PX) {
147
+ split(pageStart + paper.bodyHeight, true, false);
96
148
  }
97
149
  };
98
- const fitsPage = (extent) => extent <= pageBodyHeight + TOLERANCE_PX;
99
- const kept = keptExtents(blocks, fitsPage);
150
+ const kept = keptExtents(blocks, laid);
100
151
  let breakAfterPrevious = false;
101
152
  for (const [index, block] of blocks.entries()) {
102
- const pageEnd = pageStart + pageBodyHeight;
153
+ blockPos = block.pos;
154
+ const opening = laid[index]?.paper ?? paper;
155
+ const startsSection = laid[index]?.opensPage === true;
156
+ const pageEnd = pageStart + paper.bodyHeight;
103
157
  const top = cursor + block.gap;
104
- const startsPage = (block.breakBefore || breakAfterPrevious) && top > pageStart + TOLERANCE_PX;
158
+ const startsPage = (block.breakBefore || breakAfterPrevious || startsSection) && top > pageStart + TOLERANCE_PX;
105
159
  const first = kept.get(index) ?? block.minFirstPiece;
106
160
  const overflows = top + first > pageEnd + TOLERANCE_PX;
107
- const fits = fitsPage(first);
108
- const push = startsPage || overflows && fits ? Math.max(0, pageEnd + pageStep - top) : 0;
109
- if (push > 0 || startsPage) split(pageEnd, false, startsPage);
161
+ const fits = first <= opening.bodyHeight + TOLERANCE_PX;
162
+ const push = startsPage || overflows && fits ? Math.max(0, pageEnd + stepTo(opening) - top) : 0;
163
+ if (push > 0 || startsPage) split(pageEnd, false, startsPage, opening);
164
+ paper = opening;
110
165
  if (push > 0) {
111
166
  pushes.push({
112
167
  pos: block.pos,
@@ -115,6 +170,7 @@ function pageLayout({
115
170
  });
116
171
  }
117
172
  const contentTop = top + push;
173
+ const pageBodyHeight = paper.bodyHeight;
118
174
  let added = 0;
119
175
  let pieceStart = 0;
120
176
  for (let index2 = 0; index2 <= block.candidates.length; index2 += 1) {
@@ -144,14 +200,19 @@ function pageLayout({
144
200
  pushes,
145
201
  cuts,
146
202
  splits,
147
- pages,
148
- bodyHeight: round(pageStart + pageBodyHeight)
203
+ pages: withinSections(opened, sections),
204
+ // The last page is filled out in full on the paper of the section it opens with
205
+ bodyHeight: round(pageStart + paper.bodyHeight),
206
+ marginBottom: paper.marginBottom
149
207
  };
150
208
  }
151
209
  export {
152
210
  A4_PAGE_PIXELS,
211
+ A4_SECTION_PIXELS,
153
212
  PAGE_SPLIT_PX,
154
213
  pageGeometryStyle,
155
214
  pageLayout,
156
- pagePixels
215
+ pagePixels,
216
+ sectionPaperAt,
217
+ sectionPixels
157
218
  };
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import type { EditorView } from "prosemirror-view";
12
12
  import { type RefObject } from "react";
13
- import type { PageGeometry } from "../docx/pageGeometry";
13
+ import { type SectionPixels } from "./pageLayout";
14
14
  /** One place where a page parts from the next. The position is measured on the sheet */
15
15
  export interface PageMark {
16
16
  page: number;
@@ -22,6 +22,13 @@ export interface PageMark {
22
22
  /** The paper area of one visual page, used to place its header and footer stories. */
23
23
  export interface PageFace {
24
24
  page: number;
25
+ /** The position of the block this page opens with, which says which section it belongs to */
26
+ pos: number;
27
+ /**
28
+ * The place this page takes within that section, counted from 1 again at every section, which is
29
+ * what its header and footer variant is chosen by (`docx/headersFooters`)
30
+ */
31
+ pageInSection: number;
25
32
  headerTop: number;
26
33
  footerTop: number;
27
34
  left: number;
@@ -43,8 +50,11 @@ interface PageLayoutOptions {
43
50
  enabled: boolean;
44
51
  /** A value that differs every time the text changes */
45
52
  revision: unknown;
46
- /** The paper the open document names. A4 where a document names none */
47
- geometry?: PageGeometry;
53
+ /**
54
+ * The paper of each section of the open document (`page/pageLayout`), read once per document
55
+ * change by the caller. A4 where a document names none, and where none is open
56
+ */
57
+ sections?: readonly SectionPixels[];
48
58
  }
49
- export declare function usePageLayout({ view, layer, enabled, revision, geometry, }: PageLayoutOptions): PageOverlay | null;
59
+ export declare function usePageLayout({ view, layer, enabled, revision, sections, }: PageLayoutOptions): PageOverlay | null;
50
60
  export {};