@portone/docx-editor 0.2.1 → 0.3.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 (225) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/CONTRIBUTING.md +5 -1
  3. package/dist/DocxEditor.d.ts +6 -0
  4. package/dist/DocxEditor.js +12 -29
  5. package/dist/core.d.ts +23 -8
  6. package/dist/core.js +9 -4
  7. package/dist/docx/cloning.d.ts +38 -0
  8. package/dist/docx/cloning.js +74 -0
  9. package/dist/docx/commentOnlyChange.d.ts +12 -7
  10. package/dist/docx/commentOnlyChange.js +9 -156
  11. package/dist/docx/comments/constants.d.ts +0 -1
  12. package/dist/docx/comments/constants.js +0 -2
  13. package/dist/docx/comments/grammar.d.ts +10 -4
  14. package/dist/docx/comments/grammar.js +12 -2
  15. package/dist/docx/comments/{verifying.d.ts → parts.d.ts} +26 -18
  16. package/dist/docx/comments/parts.js +279 -0
  17. package/dist/docx/comments/people.d.ts +11 -2
  18. package/dist/docx/comments/people.js +16 -92
  19. package/dist/docx/comments/policy.d.ts +7 -0
  20. package/dist/docx/comments/policy.js +43 -0
  21. package/dist/docx/comments/reading.js +8 -10
  22. package/dist/docx/comments/writing.d.ts +19 -7
  23. package/dist/docx/comments/writing.js +52 -111
  24. package/dist/docx/documentSettings.d.ts +7 -0
  25. package/dist/docx/documentSettings.js +10 -2
  26. package/dist/docx/exportDocx.d.ts +40 -5
  27. package/dist/docx/exportDocx.js +69 -71
  28. package/dist/docx/exportRefs.d.ts +5 -2
  29. package/dist/docx/exportRefs.js +3 -1
  30. package/dist/docx/fidelity.d.ts +43 -0
  31. package/dist/docx/fidelity.js +64 -0
  32. package/dist/docx/formatting/attrs.d.ts +27 -0
  33. package/dist/docx/formatting/attrs.js +31 -0
  34. package/dist/docx/formatting/context.d.ts +31 -0
  35. package/dist/docx/formatting/context.js +42 -0
  36. package/dist/docx/formatting/direct.d.ts +6 -5
  37. package/dist/docx/formatting/direct.js +52 -78
  38. package/dist/docx/formatting/resolve.d.ts +45 -0
  39. package/dist/docx/formatting/resolve.js +140 -0
  40. package/dist/docx/formatting/runProperties.d.ts +93 -0
  41. package/dist/docx/formatting/runProperties.js +316 -0
  42. package/dist/docx/formatting/styles.js +3 -3
  43. package/dist/docx/formatting/tabStops.js +7 -22
  44. package/dist/docx/formatting.d.ts +4 -1
  45. package/dist/docx/formatting.js +4 -1
  46. package/dist/docx/headersFooters.js +6 -13
  47. package/dist/docx/identities.d.ts +67 -0
  48. package/dist/docx/identities.js +174 -0
  49. package/dist/docx/importDocx.d.ts +12 -1
  50. package/dist/docx/importDocx.js +72 -79
  51. package/dist/docx/importParagraph.d.ts +1 -1
  52. package/dist/docx/importParagraph.js +4 -3
  53. package/dist/docx/importTable.d.ts +1 -1
  54. package/dist/docx/importTable.js +17 -1
  55. package/dist/docx/invariants.d.ts +33 -0
  56. package/dist/docx/invariants.js +256 -0
  57. package/dist/docx/media.d.ts +6 -4
  58. package/dist/docx/media.js +6 -37
  59. package/dist/docx/newLists.d.ts +20 -0
  60. package/dist/docx/newLists.js +36 -0
  61. package/dist/docx/notes.js +3 -7
  62. package/dist/docx/numberingPlanner.d.ts +8 -0
  63. package/dist/docx/numberingPlanner.js +19 -0
  64. package/dist/docx/packageParts.d.ts +42 -0
  65. package/dist/docx/packageParts.js +135 -0
  66. package/dist/docx/pageGeometry.d.ts +2 -0
  67. package/dist/docx/pageGeometry.js +18 -9
  68. package/dist/docx/paraProps.d.ts +10 -22
  69. package/dist/docx/paraProps.js +47 -76
  70. package/dist/docx/partPlan.d.ts +36 -0
  71. package/dist/docx/partPlan.js +59 -0
  72. package/dist/docx/protectionPolicy.d.ts +127 -0
  73. package/dist/docx/protectionPolicy.js +169 -0
  74. package/dist/docx/relationships.d.ts +1 -1
  75. package/dist/docx/relationships.js +8 -13
  76. package/dist/docx/runProps.d.ts +9 -22
  77. package/dist/docx/runProps.js +15 -168
  78. package/dist/docx/scan.js +20 -52
  79. package/dist/docx/sdt.js +9 -34
  80. package/dist/docx/sdtProps.d.ts +8 -1
  81. package/dist/docx/sdtProps.js +10 -0
  82. package/dist/docx/serializeBlock.d.ts +2 -0
  83. package/dist/docx/serializeBlock.js +8 -5
  84. package/dist/docx/serializeTable.js +32 -15
  85. package/dist/docx/session.d.ts +42 -14
  86. package/dist/docx/session.js +43 -17
  87. package/dist/docx/tableFormatting/editing.js +108 -139
  88. package/dist/docx/tableFormatting/reading.d.ts +16 -6
  89. package/dist/docx/tableFormatting/reading.js +40 -20
  90. package/dist/docx/tableTemplate.js +18 -7
  91. package/dist/download.d.ts +8 -5
  92. package/dist/download.js +2 -0
  93. package/dist/editor/clipboard/inlineFormatting.js +19 -30
  94. package/dist/editor/commands/comments/editing.d.ts +6 -2
  95. package/dist/editor/commands/comments/editing.js +19 -31
  96. package/dist/editor/commands/exportQueries.d.ts +15 -0
  97. package/dist/editor/commands/exportQueries.js +14 -0
  98. package/dist/editor/commands/fidelityQueries.d.ts +12 -0
  99. package/dist/editor/commands/fidelityQueries.js +8 -0
  100. package/dist/editor/commands/formatting/editing.d.ts +2 -2
  101. package/dist/editor/commands/formatting/editing.js +26 -108
  102. package/dist/editor/commands/formatting/propertyCommands.d.ts +21 -0
  103. package/dist/editor/commands/formatting/propertyCommands.js +103 -0
  104. package/dist/editor/commands/formatting/shared.d.ts +3 -3
  105. package/dist/editor/commands/formatting/shared.js +5 -2
  106. package/dist/editor/commands/indentCommands.js +5 -4
  107. package/dist/editor/commands/index.d.ts +11 -0
  108. package/dist/editor/commands/index.js +5 -0
  109. package/dist/editor/commands/linkCommands.js +5 -6
  110. package/dist/editor/commands/listCommands.js +8 -10
  111. package/dist/editor/commands/lockCommands.d.ts +7 -1
  112. package/dist/editor/commands/lockCommands.js +35 -46
  113. package/dist/editor/commands/paragraphCommands.js +28 -31
  114. package/dist/editor/commands/spacingCommands.js +1 -1
  115. package/dist/editor/createEditor.d.ts +17 -29
  116. package/dist/editor/createEditor.js +58 -58
  117. package/dist/editor/documentStyles.d.ts +11 -35
  118. package/dist/editor/documentStyles.js +9 -52
  119. package/dist/editor/editorDocument.d.ts +56 -0
  120. package/dist/editor/editorDocument.js +74 -0
  121. package/dist/editor/externalClipboard.js +16 -45
  122. package/dist/editor/insertTable.js +4 -3
  123. package/dist/editor/paragraphEdits.d.ts +11 -18
  124. package/dist/editor/paragraphEdits.js +7 -16
  125. package/dist/editor/plugins/displayDerivation.d.ts +47 -0
  126. package/dist/editor/plugins/displayDerivation.js +78 -0
  127. package/dist/editor/plugins/imagePaste.js +4 -3
  128. package/dist/editor/plugins/keymap.js +18 -3
  129. package/dist/editor/plugins/numberingDecorations.d.ts +5 -11
  130. package/dist/editor/plugins/numberingDecorations.js +7 -17
  131. package/dist/editor/plugins/paragraphDisplay.d.ts +7 -0
  132. package/dist/editor/plugins/paragraphDisplay.js +51 -0
  133. package/dist/editor/plugins/tabLayout.js +1 -1
  134. package/dist/editor/plugins/tableDisplay.d.ts +6 -0
  135. package/dist/editor/plugins/tableDisplay.js +16 -0
  136. package/dist/index.d.ts +2 -0
  137. package/dist/model/format.d.ts +30 -8
  138. package/dist/model/format.js +35 -22
  139. package/dist/model/tabStops.d.ts +9 -0
  140. package/dist/model/tabStops.js +18 -0
  141. package/dist/numbering/listTemplate.js +24 -7
  142. package/dist/numbering/parseNumbering.d.ts +14 -1
  143. package/dist/numbering/parseNumbering.js +35 -22
  144. package/dist/numbering/writeNumbering.d.ts +3 -4
  145. package/dist/numbering/writeNumbering.js +14 -25
  146. package/dist/ooxml/childOrder.d.ts +34 -0
  147. package/dist/ooxml/childOrder.js +496 -0
  148. package/dist/ooxml/element.d.ts +24 -11
  149. package/dist/ooxml/element.js +31 -12
  150. package/dist/ooxml/errors.d.ts +3 -2
  151. package/dist/ooxml/image.d.ts +4 -0
  152. package/dist/ooxml/image.js +2 -5
  153. package/dist/ooxml/partSplice.d.ts +67 -0
  154. package/dist/ooxml/partSplice.js +169 -0
  155. package/dist/ooxml/props.d.ts +112 -0
  156. package/dist/{docx/propsXml.js → ooxml/props.js} +72 -200
  157. package/dist/ooxml/simpleTypes.d.ts +103 -0
  158. package/dist/ooxml/simpleTypes.js +182 -0
  159. package/dist/ooxml/tabStops.js +8 -27
  160. package/dist/ooxml/tagScan.d.ts +34 -0
  161. package/dist/ooxml/tagScan.js +108 -0
  162. package/dist/ooxml/units.d.ts +25 -13
  163. package/dist/ooxml/units.js +65 -31
  164. package/dist/ooxml/xml.d.ts +27 -6
  165. package/dist/ooxml/xml.js +37 -5
  166. package/dist/page/blockKinds.d.ts +61 -0
  167. package/dist/page/blockKinds.js +11 -0
  168. package/dist/page/kinds/index.d.ts +6 -0
  169. package/dist/page/kinds/index.js +10 -0
  170. package/dist/page/kinds/paragraphKind.d.ts +10 -0
  171. package/dist/page/kinds/paragraphKind.js +71 -0
  172. package/dist/page/kinds/tableKind.d.ts +10 -0
  173. package/dist/page/kinds/tableKind.js +178 -0
  174. package/dist/page/measureBlocks.d.ts +3 -14
  175. package/dist/page/measureBlocks.js +24 -45
  176. package/dist/page/pageDecorations.d.ts +27 -44
  177. package/dist/page/pageDecorations.js +70 -152
  178. package/dist/page/pageLayout.d.ts +2 -19
  179. package/dist/page/pageLayout.js +36 -6
  180. package/dist/page/usePageLayout.d.ts +2 -17
  181. package/dist/page/usePageLayout.js +3 -39
  182. package/dist/schema/attrRoles.d.ts +25 -18
  183. package/dist/schema/attrRoles.js +102 -67
  184. package/dist/schema/displayDerivation.d.ts +82 -0
  185. package/dist/schema/displayDerivation.js +130 -0
  186. package/dist/schema/docxSchema.d.ts +3 -0
  187. package/dist/schema/docxSchema.js +26 -14
  188. package/dist/schema/editGuard.d.ts +1 -1
  189. package/dist/schema/guards.d.ts +37 -14
  190. package/dist/schema/guards.js +29 -3
  191. package/dist/schema/index.d.ts +2 -0
  192. package/dist/schema/index.js +2 -0
  193. package/dist/schema/locks.d.ts +11 -44
  194. package/dist/schema/locks.js +0 -9
  195. package/dist/schema/preservedGuards.d.ts +26 -6
  196. package/dist/schema/preservedGuards.js +31 -1
  197. package/dist/schema/protection.d.ts +7 -1
  198. package/dist/schema/protection.js +2 -1
  199. package/dist/schema/sourceEquality.d.ts +1 -9
  200. package/dist/schema/sourceEquality.js +1 -27
  201. package/dist/styles/inlineStyle.js +15 -6
  202. package/dist/table/cellFormatting.d.ts +8 -1
  203. package/dist/table/cellFormatting.js +10 -18
  204. package/dist/table/commands.js +20 -18
  205. package/dist/table/format.d.ts +3 -0
  206. package/dist/table/format.js +3 -9
  207. package/dist/table/gridBorders.d.ts +14 -16
  208. package/dist/table/gridBorders.js +3 -41
  209. package/dist/table/merge.d.ts +2 -6
  210. package/dist/table/merge.js +5 -5
  211. package/package.json +7 -3
  212. package/dist/docx/comments/contentTypes.d.ts +0 -7
  213. package/dist/docx/comments/contentTypes.js +0 -38
  214. package/dist/docx/comments/verifying.js +0 -206
  215. package/dist/docx/formatting/effectiveParagraph.d.ts +0 -15
  216. package/dist/docx/formatting/effectiveParagraph.js +0 -81
  217. package/dist/docx/propsXml.d.ts +0 -67
  218. package/dist/docx/uniqueControls.d.ts +0 -14
  219. package/dist/docx/uniqueControls.js +0 -62
  220. package/dist/editor/plugins/commentReservations.d.ts +0 -5
  221. package/dist/editor/plugins/commentReservations.js +0 -26
  222. package/dist/editor/plugins/styledParagraphs.d.ts +0 -15
  223. package/dist/editor/plugins/styledParagraphs.js +0 -65
  224. package/dist/page/tableMeasurements.d.ts +0 -18
  225. package/dist/page/tableMeasurements.js +0 -119
@@ -0,0 +1,108 @@
1
+ // src/ooxml/tagScan.ts
2
+ function skipPast(source, from, marker) {
3
+ const at = source.indexOf(marker, from);
4
+ return at === -1 ? -1 : at + marker.length;
5
+ }
6
+ function otherTag(end) {
7
+ return end === -1 ? null : { kind: "other", name: "", nameEnd: end, end };
8
+ }
9
+ function readTag(source, lt) {
10
+ if (source.startsWith("<?", lt)) return otherTag(skipPast(source, lt, "?>"));
11
+ if (source.startsWith("<!--", lt))
12
+ return otherTag(skipPast(source, lt, "-->"));
13
+ if (source.startsWith("<![CDATA[", lt))
14
+ return otherTag(skipPast(source, lt, "]]>"));
15
+ if (source.startsWith("<!", lt)) return otherTag(skipPast(source, lt, ">"));
16
+ const closing = source.startsWith("</", lt);
17
+ let i = lt + (closing ? 2 : 1);
18
+ const nameStart = i;
19
+ while (i < source.length && !" \r\n/>".includes(source[i])) i += 1;
20
+ const name = source.slice(nameStart, i);
21
+ if (!name) return null;
22
+ const nameEnd = i;
23
+ let quote = null;
24
+ while (i < source.length) {
25
+ const ch = source[i];
26
+ if (quote !== null) {
27
+ if (ch === quote) quote = null;
28
+ } else if (ch === '"' || ch === "'") {
29
+ quote = ch;
30
+ } else if (ch === ">") {
31
+ const selfClosing = source[i - 1] === "/";
32
+ const kind = closing ? "close" : selfClosing ? "empty" : "open";
33
+ return { kind, name, nameEnd, end: i + 1 };
34
+ }
35
+ i += 1;
36
+ }
37
+ return null;
38
+ }
39
+ function rootTagAt(xml) {
40
+ let at = 0;
41
+ for (; ; ) {
42
+ const lt = xml.indexOf("<", at);
43
+ if (lt === -1) return -1;
44
+ const tag = readTag(xml, lt);
45
+ if (!tag) return -1;
46
+ if (tag.kind !== "other") return lt;
47
+ at = tag.end;
48
+ }
49
+ }
50
+ var ENTITIES = {
51
+ amp: "&",
52
+ lt: "<",
53
+ gt: ">",
54
+ quot: '"',
55
+ apos: "'"
56
+ };
57
+ var HIGHEST_CODE_POINT = 1114111;
58
+ function referenceText(name) {
59
+ const named = ENTITIES[name];
60
+ if (named !== void 0) return named;
61
+ const numeric = /^#(?:[xX]([0-9a-fA-F]+)|(\d+))$/.exec(name);
62
+ if (!numeric) return null;
63
+ const [, hex, decimal] = numeric;
64
+ const code = Number.parseInt(hex ?? decimal, hex === void 0 ? 10 : 16);
65
+ if (code > HIGHEST_CODE_POINT || code >= 55296 && code <= 57343) {
66
+ return null;
67
+ }
68
+ return String.fromCodePoint(code);
69
+ }
70
+ function decodeReferences(value) {
71
+ if (!value.includes("&")) return value;
72
+ let decoded = "";
73
+ let at = 0;
74
+ for (; ; ) {
75
+ const amp = value.indexOf("&", at);
76
+ if (amp === -1) return decoded + value.slice(at);
77
+ const semicolon = value.indexOf(";", amp);
78
+ if (semicolon === -1) return null;
79
+ const text = referenceText(value.slice(amp + 1, semicolon));
80
+ if (text === null) return null;
81
+ decoded += value.slice(at, amp) + text;
82
+ at = semicolon + 1;
83
+ }
84
+ }
85
+ var ATTRIBUTE = /(\s*)([^\s=/><"']+)\s*=\s*(?:"([^"<]*)"|'([^'<]*)')/y;
86
+ function parseAttrs(raw) {
87
+ const attrs = [];
88
+ const names = /* @__PURE__ */ new Set();
89
+ let at = 0;
90
+ for (; ; ) {
91
+ ATTRIBUTE.lastIndex = at;
92
+ const matched = ATTRIBUTE.exec(raw);
93
+ if (!matched) return raw.slice(at).trim() === "" ? attrs : null;
94
+ const [, separator, name, doubled, singled] = matched;
95
+ if (at > 0 && separator === "") return null;
96
+ if (names.has(name)) return null;
97
+ names.add(name);
98
+ const value = decodeReferences(doubled ?? singled);
99
+ if (value === null) return null;
100
+ attrs.push([name, value]);
101
+ at = ATTRIBUTE.lastIndex;
102
+ }
103
+ }
104
+ export {
105
+ parseAttrs,
106
+ readTag,
107
+ rootTagAt
108
+ };
@@ -1,30 +1,42 @@
1
1
  import { type ParagraphAlign } from "../model/format";
2
+ import { type HexColor } from "./simpleTypes";
2
3
  /** The alignment `w:jc` writes down, both for a paragraph and for a table */
3
4
  export declare const ALIGN_BY_JC: Record<string, ParagraphAlign>;
4
5
  /** A color as the document writes it, six hex digits and no `#`. Null for anything else */
5
6
  export declare function normalizeHex(value: string): string | null;
6
7
  export declare function wAttr(el: Element, name: string): string | null;
8
+ /**
9
+ * Whether a boolean property element states on (§17.17.4).
10
+ *
11
+ * An element carrying no `w:val` states on, and one that is not there at all states nothing, which
12
+ * a caller asking "is this on" reads as off. For malformed values, this project uses the same
13
+ * fallback as an absent attribute. That recovery policy is not a schema or Word guarantee.
14
+ */
15
+ export declare function isOnElement(el: Element | null): boolean;
7
16
  /** `<w:b/>` means on, `<w:b w:val="0"/>` means off */
8
17
  export declare function isOn(parent: Element, name: string): boolean;
9
18
  export declare function childValue(parent: Element, name: string): string | null;
10
- export declare function toNumber(value: string | null): number | null;
11
19
  /** Cuts off the messy digits after the decimal point so the same input always yields the same value */
12
20
  export declare function round(value: number): number;
13
21
  /** twip (1/20 of a point) */
14
- export declare function twipsToPt(value: string | null): number | null;
22
+ export declare function twipsToPt(twips: number | null): number | null;
15
23
  /** half-point (w:sz 20 = 10pt) */
16
- export declare function halfPointsToPt(value: string | null): number | null;
17
- /** 1/8 of a point */
18
- export declare function eighthsToPt(value: string | null): number | null;
24
+ export declare function halfPointsToPt(half: number | null): number | null;
25
+ /** The color as CSS spells it. `auto` is not a color to paint with, so it reads as none */
19
26
  export declare function toHexColor(value: string | null): string | null;
20
- /**
21
- * Moves one border side into a CSS value.
22
- *
23
- * A side the document has pinned down as "draw no line here" becomes `none`.
24
- * A kind we do not know, or a side that is not there at all, becomes null, so the caller can pick a default.
25
- * A side that draws a line but writes down no thickness gets the default one: reading it as "no
26
- * line" would take away a line the style laid down.
27
- */
27
+ /** One border side as `CT_Border` records it (§17.3.4) */
28
+ export interface BorderLine {
29
+ /** ST_Border (§17.18.2). `none` states that this side draws nothing */
30
+ val: string;
31
+ /** The thickness, in eighths of a point */
32
+ eighths: number;
33
+ color: HexColor;
34
+ }
35
+ /** The CSS declaration one line is drawn with, and null for a line there is none of */
36
+ export declare function borderLineCss(line: BorderLine | null): string | null;
37
+ /** The line a CSS declaration this module wrote stands for, so no caller reads one back its own way */
38
+ export declare function borderLineOfCss(css: string | null): BorderLine | null;
39
+ /** Moves one border side into a CSS value */
28
40
  export declare function borderCss(el: Element | null): string | null;
29
41
  /** One side inside `w:tblBorders` or `w:tcBorders` */
30
42
  export declare function borderSide(borders: Element | null, side: string): string | null;
@@ -1,5 +1,13 @@
1
1
  // src/ooxml/units.ts
2
2
  import { NO_FILL } from "../model/format.js";
3
+ import {
4
+ EIGHTHS_PER_PT,
5
+ HALF_POINTS_PER_PT,
6
+ ST_EighthPointMeasure,
7
+ ST_HexColor,
8
+ ST_OnOff,
9
+ TWIPS_PER_PT
10
+ } from "./simpleTypes.js";
3
11
  import { childByLocalName, W_NS } from "./xml.js";
4
12
  var ALIGN_BY_JC = {
5
13
  left: "left",
@@ -17,39 +25,29 @@ function normalizeHex(value) {
17
25
  function wAttr(el, name) {
18
26
  return el.getAttributeNS(W_NS, name) ?? el.getAttribute(name);
19
27
  }
20
- function isOn(parent, name) {
21
- const el = childByLocalName(parent, name);
28
+ function isOnElement(el) {
22
29
  if (!el) return false;
23
- const value = wAttr(el, "val");
24
- return value === null || value === "1" || value === "true" || value === "on";
30
+ return ST_OnOff.parse(wAttr(el, "val")) ?? true;
31
+ }
32
+ function isOn(parent, name) {
33
+ return isOnElement(childByLocalName(parent, name));
25
34
  }
26
35
  function childValue(parent, name) {
27
36
  const el = childByLocalName(parent, name);
28
37
  return el ? wAttr(el, "val") : null;
29
38
  }
30
- function toNumber(value) {
31
- if (value === null) return null;
32
- const parsed = Number.parseFloat(value);
33
- return Number.isFinite(parsed) ? parsed : null;
34
- }
35
39
  function round(value) {
36
40
  return Math.round(value * 100) / 100;
37
41
  }
38
- function twipsToPt(value) {
39
- const twips = toNumber(value);
40
- return twips === null ? null : round(twips / 20);
41
- }
42
- function halfPointsToPt(value) {
43
- const half = toNumber(value);
44
- return half === null ? null : round(half / 2);
42
+ function twipsToPt(twips) {
43
+ return twips === null ? null : round(twips / TWIPS_PER_PT);
45
44
  }
46
- function eighthsToPt(value) {
47
- const eighths = toNumber(value);
48
- return eighths === null ? null : round(eighths / 8);
45
+ function halfPointsToPt(half) {
46
+ return half === null ? null : round(half / HALF_POINTS_PER_PT);
49
47
  }
50
48
  function toHexColor(value) {
51
- if (value === null || value === "auto") return null;
52
- return /^[0-9a-fA-F]{6}$/.test(value) ? `#${value}` : null;
49
+ const color = ST_HexColor.parse(value);
50
+ return color?.kind === "rgb" ? `#${color.hex}` : null;
53
51
  }
54
52
  var BORDER_STYLE_BY_VAL = {
55
53
  single: "solid",
@@ -78,17 +76,52 @@ var BORDER_STYLE_BY_VAL = {
78
76
  dotDotDash: "dashed",
79
77
  dashDotStroked: "dashed"
80
78
  };
81
- var FALLBACK_BORDER_PT = 0.5;
82
- function borderCss(el) {
79
+ var BORDER_VAL_BY_STYLE = {
80
+ solid: "single",
81
+ double: "double",
82
+ dashed: "dashed",
83
+ dotted: "dotted"
84
+ };
85
+ var FALLBACK_BORDER_EIGHTHS = 4;
86
+ var DEFAULT_BORDER_CSS_COLOR = "#000000";
87
+ var NO_LINE = {
88
+ val: "none",
89
+ eighths: 0,
90
+ color: { kind: "auto" }
91
+ };
92
+ function borderLineOf(el) {
83
93
  if (!el) return null;
84
94
  const val = wAttr(el, "val") ?? "";
85
- if (val === "nil" || val === "none") return "none";
86
- const style = BORDER_STYLE_BY_VAL[val];
95
+ if (val === "nil" || val === "none") return NO_LINE;
96
+ if (!BORDER_STYLE_BY_VAL[val]) return null;
97
+ const written = ST_EighthPointMeasure.parse(wAttr(el, "sz"));
98
+ return {
99
+ val,
100
+ eighths: written === null || written === 0 ? FALLBACK_BORDER_EIGHTHS : written,
101
+ color: ST_HexColor.parse(wAttr(el, "color")) ?? { kind: "auto" }
102
+ };
103
+ }
104
+ function borderLineCss(line) {
105
+ if (!line) return null;
106
+ if (line.val === "none") return "none";
107
+ const style = BORDER_STYLE_BY_VAL[line.val];
87
108
  if (!style) return null;
88
- const written = eighthsToPt(wAttr(el, "sz"));
89
- const widthPt = written === null || written === 0 ? FALLBACK_BORDER_PT : written;
90
- const color = toHexColor(wAttr(el, "color")) ?? "#000000";
91
- return `${widthPt}pt ${style} ${color}`;
109
+ const color = line.color.kind === "rgb" ? `#${line.color.hex}` : DEFAULT_BORDER_CSS_COLOR;
110
+ return `${round(line.eighths / EIGHTHS_PER_PT)}pt ${style} ${color}`;
111
+ }
112
+ var BORDER_CSS = /^(\d+(?:\.\d+)?)pt (solid|double|dashed|dotted) (#[0-9a-f]{6})$/i;
113
+ function borderLineOfCss(css) {
114
+ if (css === null) return null;
115
+ if (css === "none") return NO_LINE;
116
+ const matched = BORDER_CSS.exec(css);
117
+ if (!matched) return null;
118
+ const eighths = Math.round(Number(matched[1]) * EIGHTHS_PER_PT);
119
+ const color = ST_HexColor.parse(matched[3].slice(1).toUpperCase());
120
+ if (eighths <= 0 || color === null) return null;
121
+ return { val: BORDER_VAL_BY_STYLE[matched[2].toLowerCase()], eighths, color };
122
+ }
123
+ function borderCss(el) {
124
+ return borderLineCss(borderLineOf(el));
92
125
  }
93
126
  function borderSide(borders, side) {
94
127
  return borders ? borderCss(childByLocalName(borders, side)) : null;
@@ -101,16 +134,17 @@ function shadingOf(parent) {
101
134
  export {
102
135
  ALIGN_BY_JC,
103
136
  borderCss,
137
+ borderLineCss,
138
+ borderLineOfCss,
104
139
  borderSide,
105
140
  childValue,
106
- eighthsToPt,
107
141
  halfPointsToPt,
108
142
  isOn,
143
+ isOnElement,
109
144
  normalizeHex,
110
145
  round,
111
146
  shadingOf,
112
147
  toHexColor,
113
- toNumber,
114
148
  twipsToPt,
115
149
  wAttr
116
150
  };
@@ -29,6 +29,32 @@ export declare function decodeUtf8(bytes: Uint8Array): {
29
29
  hadBom: boolean;
30
30
  };
31
31
  export declare function encodeUtf8(text: string, withBom: boolean): Uint8Array;
32
+ /**
33
+ * What reading a package part asks of a runtime: an XML string in, a document out.
34
+ *
35
+ * A browser's `DOMParser` is one, and so is a `DOMParser` from jsdom. The package ships none of
36
+ * them.
37
+ *
38
+ * Markup it cannot read may be answered either way a parser answers one: by handing back a
39
+ * document holding a `parsererror` element, or by throwing. Both are read as `malformed-xml`.
40
+ */
41
+ export interface XmlParser {
42
+ parseFromString(source: string, type: "application/xml"): Document;
43
+ }
44
+ /**
45
+ * Runs `work` with every `parseXml` inside it reading through the one parser settled on here.
46
+ *
47
+ * Settling it at the boundary rather than at each read is what lets an entry point turn a runtime
48
+ * holding no parser down before it has read anything, instead of wherever the first part happens
49
+ * to be parsed; it also means one parser serves the whole call rather than a fresh `DOMParser`
50
+ * being built for every part.
51
+ *
52
+ * The public entry points are synchronous, so the scope covers exactly the work one of them does
53
+ * and nothing that runs after it. `undefined` keeps the enclosing scope's parser rather than
54
+ * clearing it, which is what lets an entry point opening a file through another one - the verifier
55
+ * running two imports - hand its own parser down without every inner call having to carry it.
56
+ */
57
+ export declare function withXmlParser<T>(parser: XmlParser | undefined, work: () => T): T;
32
58
  export declare function parseXml(source: string): Document;
33
59
  /**
34
60
  * Gathers the namespace prefixes used in the fragment and declares them.
@@ -50,12 +76,7 @@ export declare function attributeByLocalName(el: Element, localName: string): st
50
76
  export declare function childByLocalName(el: Element, name: string): Element | null;
51
77
  /** Turns an opening tag's attributes into an `a="b" c="d"` string */
52
78
  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
- */
79
+ /** Whether this node is an element */
59
80
  export declare function isElement(node: Node): node is Element;
60
81
  /**
61
82
  * Turns a single element back into an XML string.
package/dist/ooxml/xml.js CHANGED
@@ -36,11 +36,38 @@ function declaresDtd(source) {
36
36
  at = closes;
37
37
  }
38
38
  }
39
+ var scopedParser;
40
+ function resolveParser(parser) {
41
+ const named = parser ?? scopedParser;
42
+ if (named) return named;
43
+ if (typeof DOMParser === "function") return new DOMParser();
44
+ throw new DocxImportError(
45
+ "no-xml-parser",
46
+ "no XML parser: pass `xmlParser` or install a DOMParser global"
47
+ );
48
+ }
49
+ function withXmlParser(parser, work) {
50
+ const enclosing = scopedParser;
51
+ scopedParser = resolveParser(parser);
52
+ try {
53
+ return work();
54
+ } finally {
55
+ scopedParser = enclosing;
56
+ }
57
+ }
39
58
  function parseXml(source) {
40
59
  if (declaresDtd(source)) {
41
60
  throw new DocxImportError("malformed-xml", "the XML declares a DTD");
42
61
  }
43
- const doc = new DOMParser().parseFromString(source, "application/xml");
62
+ let doc;
63
+ try {
64
+ doc = resolveParser(void 0).parseFromString(source, "application/xml");
65
+ } catch (cause) {
66
+ if (cause instanceof DocxImportError) throw cause;
67
+ throw new DocxImportError("malformed-xml", "could not parse the XML", {
68
+ cause
69
+ });
70
+ }
44
71
  if (doc.getElementsByTagName("parsererror").length > 0) {
45
72
  throw new DocxImportError("malformed-xml", "could not parse the XML");
46
73
  }
@@ -75,15 +102,19 @@ function attrString(el) {
75
102
  if (el.attributes.length === 0) return null;
76
103
  return Array.from(el.attributes).map((attr) => `${attr.name}="${escapeXml(attr.value)}"`).join(" ");
77
104
  }
105
+ var ELEMENT_NODE = 1;
106
+ var TEXT_NODE = 3;
107
+ var CDATA_SECTION_NODE = 4;
108
+ var COMMENT_NODE = 8;
78
109
  function isElement(node) {
79
- return node.nodeType === Node.ELEMENT_NODE;
110
+ return node.nodeType === ELEMENT_NODE;
80
111
  }
81
112
  function serializeChildNode(node) {
82
113
  if (isElement(node)) return serializeXml(node);
83
- if (node.nodeType === Node.TEXT_NODE || node.nodeType === Node.CDATA_SECTION_NODE) {
114
+ if (node.nodeType === TEXT_NODE || node.nodeType === CDATA_SECTION_NODE) {
84
115
  return escapeXml(node.nodeValue ?? "");
85
116
  }
86
- if (node.nodeType === Node.COMMENT_NODE) {
117
+ if (node.nodeType === COMMENT_NODE) {
87
118
  return `<!--${node.nodeValue ?? ""}-->`;
88
119
  }
89
120
  throw new DocxImportError(
@@ -112,5 +143,6 @@ export {
112
143
  localPart,
113
144
  namespaceDecls,
114
145
  parseXml,
115
- serializeXml
146
+ serializeXml,
147
+ withXmlParser
116
148
  };
@@ -4,7 +4,12 @@
4
4
  * A measurer says where a block may be parted and how much of it has to fit on the page it
5
5
  * starts on; the layout (`page/pageLayout`) answers with the places it decided to part it at.
6
6
  * Neither side names a paragraph or a table.
7
+ *
8
+ * Which measurer a block gets, and what draws the cuts it was given, is the block's kind
9
+ * (`page/kinds`).
7
10
  */
11
+ import type { Node as PMNode } from "prosemirror-model";
12
+ import type { Decoration, EditorView } from "prosemirror-view";
8
13
  /** A place inside a block where the next page may, or must, start */
9
14
  export interface BreakCandidate {
10
15
  /**
@@ -34,9 +39,65 @@ export interface MeasuredBlock {
34
39
  candidates: readonly BreakCandidate[];
35
40
  /** The smallest piece that has to fit on the page the block starts on */
36
41
  minFirstPiece: number;
42
+ /** The document asks for this block to stand on the same page as the start of the block after it */
43
+ keepWithNext: boolean;
37
44
  }
38
45
  /** A page cut the layout decided on: the space opened before the continued piece */
39
46
  export interface PageCut {
40
47
  at: number;
41
48
  height: number;
42
49
  }
50
+ /** One block as it stands on the sheet, handed to the kind about to measure it */
51
+ export interface MeasureTarget {
52
+ view: EditorView;
53
+ node: PMNode;
54
+ pos: number;
55
+ dom: HTMLElement;
56
+ /**
57
+ * Sheet coordinates from viewport coordinates: the visual scale taken out, and everything the
58
+ * engine has opened up above this block taken back off
59
+ */
60
+ sheetY(viewportY: number): number;
61
+ /** The block's natural top: pushes and spaces opened above it already taken back off */
62
+ top: number;
63
+ /** The visual scale the sheet is drawn at, which a length read off the screen is divided by */
64
+ scale: number;
65
+ }
66
+ /** What a kind has to say about the block it measured */
67
+ export interface KindMeasure {
68
+ candidates: readonly BreakCandidate[];
69
+ /** The smallest piece that has to fit on the page the block starts on */
70
+ minFirstPiece: number;
71
+ /** The block holds a break it could not open a space at, so the next block starts a page */
72
+ breakAfter: boolean;
73
+ /** Height the engine's own marks add inside this block, taken off its measured bottom */
74
+ appliedHeight: number;
75
+ /**
76
+ * Whether the document asks for this block to stand on the same page as the start of the block
77
+ * after it. A kind whose blocks never do leaves it out
78
+ */
79
+ keepWithNext?: boolean;
80
+ }
81
+ /**
82
+ * Everything the engine does with one shape of breakable block: how one is measured, and how the
83
+ * cuts the layout gave it are drawn.
84
+ *
85
+ * A shape the engine learns later - a footnote area, a paragraph with a floating object hanging
86
+ * out of it - is one more kind rather than another branch in the measurer, in the decorations
87
+ * and in the plugin state at once.
88
+ */
89
+ export interface BlockKind {
90
+ readonly name: string;
91
+ matches(node: PMNode): boolean;
92
+ measure(target: MeasureTarget): KindMeasure;
93
+ /** Whether `at` still names a place this kind can cut at in `doc`; a false drops the cut */
94
+ holdsCut(doc: PMNode, at: number): boolean;
95
+ /**
96
+ * The decorations one block of this kind carries: the cuts the layout gave it, plus whatever it
97
+ * needs before any measurement (a paragraph puts an empty space on every page `br`, so the
98
+ * measurement has an element to read it off).
99
+ */
100
+ decorate(pos: number, node: PMNode, cuts: readonly PageCut[], into: Decoration[]): void;
101
+ }
102
+ /** The first kind whose `matches` answers. The kind registered last has to match every block */
103
+ export declare function blockKindFor(kinds: readonly BlockKind[], node: PMNode): BlockKind;
@@ -0,0 +1,11 @@
1
+ // src/page/blockKinds.ts
2
+ function blockKindFor(kinds, node) {
3
+ const found = kinds.find((kind) => kind.matches(node));
4
+ if (!found) {
5
+ throw new Error(`no block kind matches a ${node.type.name} block`);
6
+ }
7
+ return found;
8
+ }
9
+ export {
10
+ blockKindFor
11
+ };
@@ -0,0 +1,6 @@
1
+ import type { BlockKind } from "../blockKinds";
2
+ /**
3
+ * The block shapes the editor pages, in the order they are asked. A new kind goes ahead of the
4
+ * kinds it is narrower than, and the last one claims every block none of the others did.
5
+ */
6
+ export declare const DEFAULT_BLOCK_KINDS: readonly BlockKind[];
@@ -0,0 +1,10 @@
1
+ // src/page/kinds/index.ts
2
+ import { paragraphKind } from "./paragraphKind.js";
3
+ import { tableKind } from "./tableKind.js";
4
+ var DEFAULT_BLOCK_KINDS = [
5
+ tableKind,
6
+ paragraphKind
7
+ ];
8
+ export {
9
+ DEFAULT_BLOCK_KINDS
10
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The kind every block falls back to: a paragraph, and any other shape no kind ahead of it claims.
3
+ *
4
+ * A page `br` is the one place a paragraph is parted at, and the space opened at it is what the
5
+ * measurement reads the break's place off, so every `br` carries a space of its own before
6
+ * anything is measured. A break standing where no space can be opened - inside a table cell,
7
+ * where it would grow the cell rather than the page - is answered by the whole block instead.
8
+ */
9
+ import type { BlockKind } from "../blockKinds";
10
+ export declare const paragraphKind: BlockKind;
@@ -0,0 +1,71 @@
1
+ // src/page/kinds/paragraphKind.ts
2
+ import { Decoration } from "prosemirror-view";
3
+ import { toParagraphFormat } from "../../model/format.js";
4
+ import { docxSchema, isPageBreak } from "../../schema/index.js";
5
+ import { editorAttributes } from "../../styles/classNames.js";
6
+ var PAGE_BREAK_BR = `br[${editorAttributes.breakType}="page"]`;
7
+ var BREAK_SPACE = `[${editorAttributes.pageBreakSpace}]`;
8
+ function spaceStyle(height) {
9
+ return `display:block;height:${height}px`;
10
+ }
11
+ function isPageBreakNode(node) {
12
+ return node?.type === docxSchema.nodes.hardBreak && isPageBreak(node.attrs.brAttrs);
13
+ }
14
+ function pageBreaksIn(block, blockPos) {
15
+ const found = [];
16
+ if (block.type !== docxSchema.nodes.paragraph) return found;
17
+ block.forEach((child, offset) => {
18
+ if (isPageBreakNode(child)) {
19
+ found.push({ at: blockPos + 1 + offset, size: child.nodeSize });
20
+ }
21
+ });
22
+ return found;
23
+ }
24
+ var paragraphKind = {
25
+ name: "paragraph",
26
+ matches: () => true,
27
+ measure({ node, pos, dom, sheetY, top, scale }) {
28
+ const breaks = pageBreaksIn(node, pos);
29
+ const candidates = [];
30
+ let appliedHeight = 0;
31
+ dom.querySelectorAll(BREAK_SPACE).forEach((space, index) => {
32
+ const box = space.getBoundingClientRect();
33
+ const found = breaks.at(index);
34
+ if (found) {
35
+ candidates.push({
36
+ at: found.at,
37
+ offset: sheetY(box.top) - appliedHeight - top,
38
+ forced: true,
39
+ repeatHeight: 0
40
+ });
41
+ }
42
+ appliedHeight += box.height / scale;
43
+ });
44
+ return {
45
+ candidates,
46
+ minFirstPiece: candidates[0]?.offset ?? dom.getBoundingClientRect().height / scale - appliedHeight,
47
+ // A break with no space of its own sits somewhere the space could not be opened. Those
48
+ // still start a new page, but only after the whole block
49
+ breakAfter: dom.querySelectorAll(PAGE_BREAK_BR).length > candidates.length,
50
+ appliedHeight,
51
+ keepWithNext: toParagraphFormat(node.attrs.format)?.keepNext === true
52
+ };
53
+ },
54
+ holdsCut: (doc, at) => isPageBreakNode(doc.nodeAt(at)),
55
+ decorate(pos, node, cuts, into) {
56
+ const heights = new Map(cuts.map((cut) => [cut.at, cut.height]));
57
+ for (const { at, size } of pageBreaksIn(node, pos)) {
58
+ const height = heights.get(at) ?? 0;
59
+ into.push(
60
+ Decoration.inline(at, at + size, {
61
+ nodeName: "span",
62
+ style: spaceStyle(height),
63
+ [editorAttributes.pageBreakSpace]: `${height}`
64
+ })
65
+ );
66
+ }
67
+ }
68
+ };
69
+ export {
70
+ paragraphKind
71
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The kind of a table: it is parted between rows, and a continued page opens a spacer row and
3
+ * repeats the headers the table opens with.
4
+ *
5
+ * Both sides read the table as it stands on screen without treating the rows this very engine
6
+ * drew as content: the measurement takes their heights back off, and the projection is derived
7
+ * again from the document, so an edit to a header row redraws it with nothing measured anew.
8
+ */
9
+ import type { BlockKind } from "../blockKinds";
10
+ export declare const tableKind: BlockKind;