web-doc 0.6.2 → 0.7.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 (188) hide show
  1. package/THIRD_PARTY_NOTICES.md +26 -1
  2. package/dist/adapters/docx-images.d.ts +13 -4
  3. package/dist/adapters/docx-images.js +3 -0
  4. package/dist/adapters/docx-paragraphs.d.ts +65 -0
  5. package/dist/adapters/docx-paragraphs.js +165 -0
  6. package/dist/adapters/docx-prepass.d.ts +17 -0
  7. package/dist/adapters/docx-prepass.js +230 -0
  8. package/dist/adapters/office.d.ts +31 -5
  9. package/dist/adapters/office.js +60 -29
  10. package/dist/adapters/pdf.d.ts +9 -0
  11. package/dist/adapters/pdf.js +10 -0
  12. package/dist/assets/pdfium/pdfium.wasm +0 -0
  13. package/dist/contracts.d.ts +47 -2
  14. package/dist/edit/assets.d.ts +23 -0
  15. package/dist/edit/assets.js +75 -0
  16. package/dist/edit/docx/elements.d.ts +6 -0
  17. package/dist/edit/docx/elements.js +90 -0
  18. package/dist/edit/docx/engine.d.ts +51 -0
  19. package/dist/edit/docx/engine.js +473 -0
  20. package/dist/edit/docx/handlers.d.ts +3 -0
  21. package/dist/edit/docx/handlers.js +15 -0
  22. package/dist/edit/docx/ids.d.ts +45 -0
  23. package/dist/edit/docx/ids.js +101 -0
  24. package/dist/edit/docx/model.d.ts +92 -0
  25. package/dist/edit/docx/model.js +339 -0
  26. package/dist/edit/docx/operations.d.ts +37 -0
  27. package/dist/edit/docx/operations.js +3 -0
  28. package/dist/edit/docx/provider.d.ts +13 -0
  29. package/dist/edit/docx/provider.js +34 -0
  30. package/dist/edit/docx/schemas.d.ts +6 -0
  31. package/dist/edit/docx/schemas.js +192 -0
  32. package/dist/edit/docx/session.d.ts +37 -0
  33. package/dist/edit/docx/session.js +426 -0
  34. package/dist/edit/docx/structure-ops.d.ts +6 -0
  35. package/dist/edit/docx/structure-ops.js +494 -0
  36. package/dist/edit/docx/style.d.ts +45 -0
  37. package/dist/edit/docx/style.js +375 -0
  38. package/dist/edit/docx/table-ops.d.ts +4 -0
  39. package/dist/edit/docx/table-ops.js +219 -0
  40. package/dist/edit/docx/text-ops.d.ts +18 -0
  41. package/dist/edit/docx/text-ops.js +458 -0
  42. package/dist/edit/docx/text.d.ts +34 -0
  43. package/dist/edit/docx/text.js +263 -0
  44. package/dist/edit/docx/types.d.ts +191 -0
  45. package/dist/edit/docx/types.js +1 -0
  46. package/dist/edit/docx/write.d.ts +50 -0
  47. package/dist/edit/docx/write.js +352 -0
  48. package/dist/edit/engine.d.ts +117 -0
  49. package/dist/edit/engine.js +1 -0
  50. package/dist/edit/history.d.ts +53 -0
  51. package/dist/edit/history.js +101 -0
  52. package/dist/edit/ooxml/names.d.ts +20 -0
  53. package/dist/edit/ooxml/names.js +61 -0
  54. package/dist/edit/ooxml/opc.d.ts +50 -0
  55. package/dist/edit/ooxml/opc.js +150 -0
  56. package/dist/edit/ooxml/package.d.ts +82 -0
  57. package/dist/edit/ooxml/package.js +233 -0
  58. package/dist/edit/ooxml/patch.d.ts +51 -0
  59. package/dist/edit/ooxml/patch.js +250 -0
  60. package/dist/edit/ooxml/transaction.d.ts +39 -0
  61. package/dist/edit/ooxml/transaction.js +316 -0
  62. package/dist/edit/ooxml/worker.d.ts +8 -0
  63. package/dist/edit/ooxml/worker.js +12 -0
  64. package/dist/edit/ooxml/writer.d.ts +21 -0
  65. package/dist/edit/ooxml/writer.js +187 -0
  66. package/dist/edit/ooxml/xml.d.ts +74 -0
  67. package/dist/edit/ooxml/xml.js +451 -0
  68. package/dist/edit/ooxml/zip.d.ts +54 -0
  69. package/dist/edit/ooxml/zip.js +280 -0
  70. package/dist/edit/operations.d.ts +19 -0
  71. package/dist/edit/operations.js +137 -0
  72. package/dist/edit/pdf/engine/compact.d.ts +6 -0
  73. package/dist/edit/pdf/engine/compact.js +442 -0
  74. package/dist/edit/pdf/engine/document.d.ts +95 -0
  75. package/dist/edit/pdf/engine/document.js +868 -0
  76. package/dist/edit/pdf/engine/elements.d.ts +45 -0
  77. package/dist/edit/pdf/engine/elements.js +313 -0
  78. package/dist/edit/pdf/engine/existing-text.d.ts +4 -0
  79. package/dist/edit/pdf/engine/existing-text.js +424 -0
  80. package/dist/edit/pdf/engine/fonts.d.ts +78 -0
  81. package/dist/edit/pdf/engine/fonts.js +466 -0
  82. package/dist/edit/pdf/engine/geometry.d.ts +36 -0
  83. package/dist/edit/pdf/engine/geometry.js +97 -0
  84. package/dist/edit/pdf/engine/handler.d.ts +21 -0
  85. package/dist/edit/pdf/engine/handler.js +108 -0
  86. package/dist/edit/pdf/engine/images.d.ts +33 -0
  87. package/dist/edit/pdf/engine/images.js +188 -0
  88. package/dist/edit/pdf/engine/layout.d.ts +23 -0
  89. package/dist/edit/pdf/engine/layout.js +177 -0
  90. package/dist/edit/pdf/engine/operations.d.ts +67 -0
  91. package/dist/edit/pdf/engine/operations.js +3 -0
  92. package/dist/edit/pdf/engine/pages.d.ts +6 -0
  93. package/dist/edit/pdf/engine/pages.js +98 -0
  94. package/dist/edit/pdf/engine/pdfium.d.ts +195 -0
  95. package/dist/edit/pdf/engine/pdfium.js +249 -0
  96. package/dist/edit/pdf/engine/shapes.d.ts +4 -0
  97. package/dist/edit/pdf/engine/shapes.js +183 -0
  98. package/dist/edit/pdf/engine/tables.d.ts +44 -0
  99. package/dist/edit/pdf/engine/tables.js +315 -0
  100. package/dist/edit/pdf/engine/text-box.d.ts +71 -0
  101. package/dist/edit/pdf/engine/text-box.js +317 -0
  102. package/dist/edit/pdf/engine/text-layout.d.ts +28 -0
  103. package/dist/edit/pdf/engine/text-layout.js +67 -0
  104. package/dist/edit/pdf/engine/transform.d.ts +5 -0
  105. package/dist/edit/pdf/engine/transform.js +137 -0
  106. package/dist/edit/pdf/provider.d.ts +35 -0
  107. package/dist/edit/pdf/provider.js +92 -0
  108. package/dist/edit/pdf/range-map.d.ts +14 -0
  109. package/dist/edit/pdf/range-map.js +107 -0
  110. package/dist/edit/pdf/schemas.d.ts +11 -0
  111. package/dist/edit/pdf/schemas.js +291 -0
  112. package/dist/edit/pdf/selection.d.ts +5 -0
  113. package/dist/edit/pdf/selection.js +152 -0
  114. package/dist/edit/pdf/session.d.ts +53 -0
  115. package/dist/edit/pdf/session.js +273 -0
  116. package/dist/edit/pdf/types.d.ts +336 -0
  117. package/dist/edit/pdf/types.js +1 -0
  118. package/dist/edit/pptx/elements.d.ts +74 -0
  119. package/dist/edit/pptx/elements.js +301 -0
  120. package/dist/edit/pptx/engine.d.ts +37 -0
  121. package/dist/edit/pptx/engine.js +466 -0
  122. package/dist/edit/pptx/geometry.d.ts +72 -0
  123. package/dist/edit/pptx/geometry.js +193 -0
  124. package/dist/edit/pptx/handler.d.ts +7 -0
  125. package/dist/edit/pptx/handler.js +91 -0
  126. package/dist/edit/pptx/handlers.d.ts +3 -0
  127. package/dist/edit/pptx/handlers.js +21 -0
  128. package/dist/edit/pptx/image-table-ops.d.ts +5 -0
  129. package/dist/edit/pptx/image-table-ops.js +233 -0
  130. package/dist/edit/pptx/model.d.ts +69 -0
  131. package/dist/edit/pptx/model.js +170 -0
  132. package/dist/edit/pptx/operations.d.ts +50 -0
  133. package/dist/edit/pptx/operations.js +11 -0
  134. package/dist/edit/pptx/provider.d.ts +17 -0
  135. package/dist/edit/pptx/provider.js +40 -0
  136. package/dist/edit/pptx/schemas.d.ts +6 -0
  137. package/dist/edit/pptx/schemas.js +220 -0
  138. package/dist/edit/pptx/session.d.ts +44 -0
  139. package/dist/edit/pptx/session.js +114 -0
  140. package/dist/edit/pptx/shape-ops.d.ts +10 -0
  141. package/dist/edit/pptx/shape-ops.js +486 -0
  142. package/dist/edit/pptx/slide-ops.d.ts +6 -0
  143. package/dist/edit/pptx/slide-ops.js +353 -0
  144. package/dist/edit/pptx/style.d.ts +31 -0
  145. package/dist/edit/pptx/style.js +181 -0
  146. package/dist/edit/pptx/text-ops.d.ts +13 -0
  147. package/dist/edit/pptx/text-ops.js +267 -0
  148. package/dist/edit/pptx/text-write.d.ts +67 -0
  149. package/dist/edit/pptx/text-write.js +293 -0
  150. package/dist/edit/pptx/text.d.ts +41 -0
  151. package/dist/edit/pptx/text.js +99 -0
  152. package/dist/edit/pptx/types.d.ts +232 -0
  153. package/dist/edit/pptx/types.js +1 -0
  154. package/dist/edit/schema.d.ts +11 -0
  155. package/dist/edit/schema.js +275 -0
  156. package/dist/edit/session.d.ts +66 -0
  157. package/dist/edit/session.js +701 -0
  158. package/dist/edit/sessions.d.ts +8 -0
  159. package/dist/edit/sessions.js +1 -0
  160. package/dist/edit/types.d.ts +276 -0
  161. package/dist/edit/types.js +1 -0
  162. package/dist/edit/worker-engine.d.ts +31 -0
  163. package/dist/edit/worker-engine.js +91 -0
  164. package/dist/fonts/THIRD_PARTY_NOTICES.md +3 -0
  165. package/dist/fonts/manifest.json +6 -1
  166. package/dist/fonts/noto-sans-latin-cyrillic.ttf +0 -0
  167. package/dist/fonts.d.ts +2 -0
  168. package/dist/fonts.js +4 -0
  169. package/dist/headless.d.ts +4 -0
  170. package/dist/headless.js +4 -0
  171. package/dist/index.d.ts +5 -0
  172. package/dist/index.js +5 -0
  173. package/dist/limits.js +3 -0
  174. package/dist/ooxml-edit-worker.d.ts +1 -0
  175. package/dist/ooxml-edit-worker.js +8 -0
  176. package/dist/pdf-edit-worker.d.ts +1 -0
  177. package/dist/pdf-edit-worker.js +35 -0
  178. package/dist/spreadsheet-viewport.d.ts +5 -0
  179. package/dist/spreadsheet-viewport.js +11 -0
  180. package/dist/ui.js +7 -0
  181. package/dist/viewer.d.ts +6 -0
  182. package/dist/viewer.js +293 -19
  183. package/dist/viewport.d.ts +13 -0
  184. package/dist/viewport.js +126 -1
  185. package/dist/worker-protocol.d.ts +39 -2
  186. package/dist/workers/ooxml-edit-worker.js +8671 -0
  187. package/dist/workers/pdf-edit-worker.js +10844 -0
  188. package/package.json +3 -3
@@ -0,0 +1,458 @@
1
+ import { patches } from "../ooxml/patch.js";
2
+ import { W_NS } from "./ids.js";
3
+ import { changedParagraphProperties, changedRunProperties, attributeProblem, colorProblem, namespacePatches, normalizeText, paragraphMarkProperties, paragraphPropertiesWithoutSection, paragraphXml, runContentXml, runXml, sliceOf, textProblem, } from "./write.js";
4
+ /** The child of `container` an item sits under. */
5
+ function topOf(node, container) {
6
+ let current = node;
7
+ while (current.parent && current.parent !== container)
8
+ current = current.parent;
9
+ return current;
10
+ }
11
+ /** The units of a container's children for the items it holds. */
12
+ function unitsOf(container, items) {
13
+ const byTop = new Map();
14
+ for (const item of items) {
15
+ const top = topOf(item.run, container);
16
+ const list = byTop.get(top) ?? [];
17
+ list.push(item);
18
+ byTop.set(top, list);
19
+ }
20
+ // A complex field spans consecutive children: from its begin run's top
21
+ // to its end run's top.
22
+ const groupEnd = new Map();
23
+ for (const item of items)
24
+ if (item.fieldRuns && item.fieldRuns.length > 1) {
25
+ const first = topOf(item.fieldRuns[0], container);
26
+ const last = topOf(item.fieldRuns.at(-1), container);
27
+ if (first !== last)
28
+ groupEnd.set(first, last);
29
+ }
30
+ const units = [];
31
+ let position = 0;
32
+ const children = container.children.filter((child) => !(child.local === "pPr" && child.namespace === W_NS));
33
+ for (let index = 0; index < children.length; index += 1) {
34
+ const child = children[index];
35
+ const end = groupEnd.get(child);
36
+ const nodes = [child];
37
+ if (end) {
38
+ let cursor = index + 1;
39
+ while (cursor < children.length && children[cursor - 1] !== end)
40
+ nodes.push(children[cursor++]);
41
+ index = cursor - 1;
42
+ }
43
+ const own = nodes.flatMap((node) => byTop.get(node) ?? []);
44
+ const kind = own.length === 0
45
+ ? "zero"
46
+ : end ||
47
+ child.local === "fldSimple" ||
48
+ own.some((item) => item.fieldRuns)
49
+ ? "field"
50
+ : child.local === "r"
51
+ ? "run"
52
+ : "wrapper";
53
+ const start = own.length > 0 ? own[0].start : position;
54
+ const stop = own.length > 0 ? own.at(-1).end : position;
55
+ units.push({ nodes, kind, start, end: stop, items: own });
56
+ position = stop;
57
+ }
58
+ return units;
59
+ }
60
+ function rPrOf(part, item) {
61
+ return item ? sliceOf(part, item.rPr) : "";
62
+ }
63
+ function startTagOf(part, node) {
64
+ return part.text.slice(node.start, node.contentStart);
65
+ }
66
+ function endTagOf(part, node) {
67
+ return part.text.slice(node.contentEnd, node.end);
68
+ }
69
+ /**
70
+ * The content of a run's items in `[from, to]`: `w:t` text sliced, every
71
+ * other child (tab, break, picture, symbol, note reference) as its bytes
72
+ * when it lies inside. A zero-width item at `from` belongs to the slice
73
+ * only when `includeStart` says so, which is how a caret or a range edge
74
+ * hands such an item to exactly one side.
75
+ */
76
+ function contentOf(part, items, from, to, includeStart) {
77
+ let out = "";
78
+ for (const item of items) {
79
+ if (item.start === item.end) {
80
+ const after = includeStart ? item.start >= from : item.start > from;
81
+ if (after && item.start <= to)
82
+ out += sliceOf(part, item.child);
83
+ continue;
84
+ }
85
+ const begin = Math.max(item.start, from);
86
+ const stop = Math.min(item.end, to);
87
+ if (stop <= begin)
88
+ continue;
89
+ if (item.kind === "text" && item.child.local === "t")
90
+ out += runContentXml(item.text.slice(begin - item.start, stop - item.start));
91
+ else if (begin === item.start && stop === item.end)
92
+ out += sliceOf(part, item.child);
93
+ }
94
+ return out;
95
+ }
96
+ /**
97
+ * Inline objects a replacement of `[from, to)` drops: pictures, objects
98
+ * and anchors inside the range (a zero-width one only strictly inside),
99
+ * by element id, with the ids of every paragraph nested in them (a text
100
+ * box) so the engine's id list follows.
101
+ */
102
+ function droppedInlines(model, record, items, from, to) {
103
+ const removedIds = [];
104
+ const removedParagraphIds = [];
105
+ for (const item of items) {
106
+ if (item.kind !== "picture" &&
107
+ item.kind !== "object" &&
108
+ item.kind !== "anchor")
109
+ continue;
110
+ const inside = item.start === item.end
111
+ ? from < item.start && item.start < to
112
+ : item.start >= from && item.end <= to;
113
+ if (!inside)
114
+ continue;
115
+ const inline = record.inlines.find((candidate) => candidate.node === item.child);
116
+ if (inline)
117
+ removedIds.push(inline.elementId);
118
+ for (const paragraph of model.document.findAll("p", item.child)) {
119
+ const id = model.paragraphIds.get(paragraph);
120
+ if (id && paragraph.namespace === W_NS)
121
+ removedParagraphIds.push(id);
122
+ }
123
+ }
124
+ return { removedIds, removedParagraphIds };
125
+ }
126
+ /**
127
+ * The container's content with `[start, end)` removed and `insert(rPr)`
128
+ * written at `start`: `before` holds everything up to and including the
129
+ * inserted text, `after` what follows. New text is styled like the run
130
+ * that held the first replaced character, like the run before a caret,
131
+ * else like the run after it.
132
+ */
133
+ function splitAt(part, container, items, start, end, insert, fallbackRPr) {
134
+ let before = "";
135
+ let after = "";
136
+ let inserted = false;
137
+ let nested = false;
138
+ let rPr = fallbackRPr;
139
+ let lastRPr;
140
+ const raw = (unit) => unit.nodes.map((node) => sliceOf(part, node)).join("");
141
+ const place = (style) => {
142
+ rPr = style ?? lastRPr ?? fallbackRPr;
143
+ before += insert(rPr);
144
+ inserted = true;
145
+ };
146
+ for (const unit of unitsOf(container, items)) {
147
+ if (unit.kind === "zero") {
148
+ if (inserted)
149
+ after += raw(unit);
150
+ else
151
+ before += raw(unit);
152
+ continue;
153
+ }
154
+ if (unit.end <= start) {
155
+ before += raw(unit);
156
+ lastRPr = rPrOf(part, unit.items.at(-1));
157
+ if (!inserted && start === end && unit.end === start)
158
+ place(lastRPr);
159
+ continue;
160
+ }
161
+ if (unit.start >= end) {
162
+ if (!inserted)
163
+ place(lastRPr ?? rPrOf(part, unit.items[0]));
164
+ after += raw(unit);
165
+ continue;
166
+ }
167
+ if (unit.start >= start && unit.end <= end) {
168
+ // Covered whole: dropped; the first such unit styles the new text.
169
+ if (!inserted)
170
+ place(rPrOf(part, unit.items[0]));
171
+ continue;
172
+ }
173
+ if (unit.kind === "run") {
174
+ const style = rPrOf(part, unit.items[0]);
175
+ // The head keeps a zero-width item at `start`; the tail takes one at
176
+ // `end` only when the range is not a caret (the head has it then).
177
+ const head = runXml(style, contentOf(part, unit.items, unit.start, start, true));
178
+ const tail = runXml(style, contentOf(part, unit.items, end, unit.end, start < end));
179
+ if (!inserted) {
180
+ before += head;
181
+ place(style);
182
+ after += tail;
183
+ }
184
+ else
185
+ after += tail;
186
+ continue;
187
+ }
188
+ if (unit.kind === "wrapper" && unit.start < start && end < unit.end) {
189
+ // Both ends inside: the wrapper stays and its runs change.
190
+ const wrapper = unit.nodes[0];
191
+ const inner = splitAt(part, wrapper, unit.items, start, end, insert, fallbackRPr);
192
+ before +=
193
+ startTagOf(part, wrapper) +
194
+ inner.before +
195
+ inner.after +
196
+ endTagOf(part, wrapper);
197
+ rPr = inner.rPr;
198
+ inserted = true;
199
+ nested = true;
200
+ continue;
201
+ }
202
+ // A field cut, a wrapper edge cut: validation refuses these.
203
+ throw new Error("A range cannot cut a field or a wrapper");
204
+ }
205
+ if (!inserted)
206
+ place(lastRPr);
207
+ return { before, after, rPr, nested };
208
+ }
209
+ async function textTarget(id, context, issue) {
210
+ const record = context.model.byId.get(id);
211
+ if (!record || record.kind !== "paragraph") {
212
+ issue("/target", record ? "invalid-target" : "unknown-target", record ? `Element ${id} is not a paragraph` : `No element ${id}`);
213
+ return undefined;
214
+ }
215
+ if (record.readOnlyReason) {
216
+ issue("/target", "invalid-target", `Paragraph ${id} is read-only (${record.readOnlyReason})`);
217
+ return undefined;
218
+ }
219
+ return { record, part: context.model.document };
220
+ }
221
+ /** Checks a range against its target; returns the clamped offsets. */
222
+ function checkRange(target, id, range, issue, structural) {
223
+ const text = target.record.text.text;
224
+ const length = text.length;
225
+ if (!range)
226
+ return { start: 0, end: length };
227
+ if (range.start.elementId !== id || range.end.elementId !== id) {
228
+ issue("/range", "invalid-range", "Both ends must lie on the target");
229
+ return undefined;
230
+ }
231
+ const { start, end } = { start: range.start.offset, end: range.end.offset };
232
+ if (start > length || end > length || start > end) {
233
+ issue("/range", "invalid-range", `Offsets must satisfy 0 ≤ start ≤ end ≤ ${length}`);
234
+ return undefined;
235
+ }
236
+ for (const offset of [start, end])
237
+ if (offset > 0 &&
238
+ offset < length &&
239
+ (text.charCodeAt(offset) & 0xfc00) === 0xdc00 &&
240
+ (text.charCodeAt(offset - 1) & 0xfc00) === 0xd800) {
241
+ issue("/range", "invalid-range", "A range cannot split a surrogate pair");
242
+ return undefined;
243
+ }
244
+ if (!structural)
245
+ return { start, end };
246
+ const inside = (unit, offset) => unit.start < offset && offset < unit.end;
247
+ const check = (container, items) => {
248
+ for (const unit of unitsOf(container, items)) {
249
+ if (unit.kind === "field" && (inside(unit, start) || inside(unit, end))) {
250
+ issue("/range", "invalid-range", "A range cannot start or end inside a field");
251
+ return false;
252
+ }
253
+ if (unit.kind !== "wrapper")
254
+ continue;
255
+ const startIn = inside(unit, start);
256
+ const endIn = inside(unit, end);
257
+ if (startIn !== endIn) {
258
+ issue("/range", "invalid-range", "A range cannot cut the edge of a hyperlink or content control");
259
+ return false;
260
+ }
261
+ if (startIn && !check(unit.nodes[0], unit.items))
262
+ return false;
263
+ }
264
+ return true;
265
+ };
266
+ return check(target.record.node, target.record.text.items)
267
+ ? { start, end }
268
+ : undefined;
269
+ }
270
+ function commitParagraph(context, target, items, extra = {}) {
271
+ const { part, record } = target;
272
+ const transaction = context.pkg.transaction();
273
+ transaction.patch(part, [...namespacePatches(part), ...items]);
274
+ return transaction.commit().then((change) => ({
275
+ createdIds: [],
276
+ warnings: change.warnings,
277
+ ...(context.model.unauthoredSet.has(record.id)
278
+ ? { stamped: [record.id] }
279
+ : {}),
280
+ reflowFrom: record.id,
281
+ ...extra,
282
+ }));
283
+ }
284
+ export const replaceTextHandler = {
285
+ async validate(operation, context, issue) {
286
+ const text = normalizeText(operation.text);
287
+ const problem = textProblem(text);
288
+ if (problem)
289
+ issue("/text", "invalid-text", `The text holds ${problem}`);
290
+ const target = await textTarget(operation.target, context, issue);
291
+ if (!target)
292
+ return;
293
+ const range = checkRange(target, operation.target, operation.range, issue, true);
294
+ if (!range || !text.includes("\n"))
295
+ return;
296
+ // A paragraph cannot be split inside a hyperlink or content control.
297
+ const probe = splitAt(target.part, target.record.node, target.record.text.items, range.start, range.end, () => "", "");
298
+ if (probe.nested)
299
+ issue("/text", "invalid-range", "A paragraph break cannot be inserted inside a hyperlink or content control");
300
+ },
301
+ async apply(operation, context) {
302
+ const target = (await textTarget(operation.target, context, () => { }));
303
+ const { start, end } = checkRange(target, operation.target, operation.range, () => { }, true);
304
+ const { items, createdIds, removedIds, removedParagraphIds } = replacedParagraph(context, target.record, start, end, normalizeText(operation.text));
305
+ return commitParagraph(context, target, items, {
306
+ createdIds,
307
+ ...(removedIds.length > 0 ? { removedIds } : {}),
308
+ ...(removedParagraphIds.length > 0 ? { removedParagraphIds } : {}),
309
+ });
310
+ },
311
+ };
312
+ /**
313
+ * The patches that replace `[start, end)` of a paragraph's text with
314
+ * `text`, newlines splitting it into new paragraphs after it: the first
315
+ * paragraph rebuilt in place, the others inserted with fresh ids.
316
+ */
317
+ export function replacedParagraph(context, record, start, end, text) {
318
+ const part = context.model.document;
319
+ const segments = text.split("\n");
320
+ const split = splitAt(part, record.node, record.text.items, start, end, (rPr) => runXml(rPr, runContentXml(segments[0])), paragraphMarkRPr(part, record));
321
+ const pPr = sliceOf(part, record.pPr);
322
+ const single = segments.length === 1;
323
+ const first = paragraphXml(record.node, record.id, pPr, split.before + (single ? split.after : ""));
324
+ const items = [patches.replaceElement(part, record.node, first)];
325
+ const createdIds = [];
326
+ const copiedPPr = paragraphPropertiesWithoutSection(part, record.pPr);
327
+ segments.slice(1).forEach((segment, index) => {
328
+ const id = context.freshParagraphId();
329
+ createdIds.push(`p:${id}`);
330
+ const last = index === segments.length - 2;
331
+ items.push(patches.insertAfter(part, record.node, paragraphXml(record.node, id, copiedPPr, runXml(split.rPr, runContentXml(segment)) + (last ? split.after : ""))));
332
+ });
333
+ const dropped = droppedInlines(context.model, record, record.text.items, start, end);
334
+ const removedIds = dropped.removedIds;
335
+ // Inline objects that moved to a new paragraph change their id.
336
+ if (!single)
337
+ for (const inline of record.inlines) {
338
+ const item = record.text.items.find((candidate) => candidate.child === inline.node);
339
+ if (item && item.start >= end && !removedIds.includes(inline.elementId))
340
+ removedIds.push(inline.elementId);
341
+ }
342
+ return {
343
+ items,
344
+ createdIds,
345
+ removedIds,
346
+ removedParagraphIds: dropped.removedParagraphIds,
347
+ };
348
+ }
349
+ /** The paragraph mark's `w:rPr`, the style of text in an empty paragraph. */
350
+ function paragraphMarkRPr(part, record) {
351
+ const rPr = record.pPr?.children.find((child) => child.local === "rPr" && child.namespace === W_NS);
352
+ return sliceOf(part, rPr);
353
+ }
354
+ /** A run re-serialized with new properties around its original content. */
355
+ function restyledRun(part, run, rPr) {
356
+ const content = run.children
357
+ .filter((child) => !(child.local === "rPr" && child.namespace === W_NS))
358
+ .map((child) => sliceOf(part, child))
359
+ .join("");
360
+ return `<w:r>${rPr}${content}</w:r>`;
361
+ }
362
+ /** The container's content with the runs in `[start, end)` restyled. */
363
+ function restyled(part, container, items, start, end, change, context) {
364
+ let out = "";
365
+ const changed = (rPr) => changedRunProperties(part, rPr, change, context.model.styles);
366
+ for (const unit of unitsOf(container, items)) {
367
+ const raw = unit.nodes.map((node) => sliceOf(part, node)).join("");
368
+ const overlaps = unit.start < end && start < unit.end
369
+ ? true
370
+ : unit.start === unit.end && start < unit.start && unit.start < end;
371
+ if (unit.kind === "zero" || !overlaps) {
372
+ out += raw;
373
+ continue;
374
+ }
375
+ if (unit.kind === "run") {
376
+ const run = unit.nodes[0];
377
+ const own = rPrOf(part, unit.items[0]);
378
+ const rPr = unit.items[0]?.rPr;
379
+ out += runXml(own, contentOf(part, unit.items, unit.start, Math.min(start, unit.end), true));
380
+ out += runXml(changed(rPr), contentOf(part, unit.items, Math.max(unit.start, start), Math.min(unit.end, end), false));
381
+ out += runXml(own, contentOf(part, unit.items, Math.max(end, unit.start), unit.end, false));
382
+ continue;
383
+ }
384
+ if (unit.kind === "field") {
385
+ // A field is styled whole: every run of it takes the change.
386
+ for (const node of unit.nodes)
387
+ if (node.local === "r" && node.namespace === W_NS)
388
+ out += restyledRun(part, node, changed(node.children.find((child) => child.local === "rPr" && child.namespace === W_NS)));
389
+ else if (node.local === "fldSimple")
390
+ out +=
391
+ startTagOf(part, node) +
392
+ restyled(part, node, unit.items, start, end, change, context) +
393
+ endTagOf(part, node);
394
+ else
395
+ out += sliceOf(part, node);
396
+ continue;
397
+ }
398
+ const wrapper = unit.nodes[0];
399
+ out +=
400
+ startTagOf(part, wrapper) +
401
+ restyled(part, wrapper, unit.items, start, end, change, context) +
402
+ endTagOf(part, wrapper);
403
+ }
404
+ return out;
405
+ }
406
+ export const setTextStyleHandler = {
407
+ async validate(operation, context, issue) {
408
+ const { style } = operation;
409
+ if (style.color !== undefined) {
410
+ const problem = colorProblem(style.color);
411
+ if (problem)
412
+ issue("/style/color", "invalid-value", `Colour: ${problem}`);
413
+ }
414
+ if (style.fontFamily !== undefined) {
415
+ const problem = attributeProblem(style.fontFamily);
416
+ if (problem)
417
+ issue("/style/fontFamily", "invalid-value", `Font: ${problem}`);
418
+ }
419
+ const target = await textTarget(operation.target, context, issue);
420
+ if (target)
421
+ checkRange(target, operation.target, operation.range, issue, false);
422
+ },
423
+ async apply(operation, context) {
424
+ const target = (await textTarget(operation.target, context, () => { }));
425
+ const { part, record } = target;
426
+ const { start, end } = checkRange(target, operation.target, operation.range, () => { }, false);
427
+ if (Object.keys(operation.style).length === 0)
428
+ return { createdIds: [], warnings: [] };
429
+ const content = restyled(part, record.node, record.text.items, start, end, operation.style, context);
430
+ const pPr = end >= record.text.text.length
431
+ ? paragraphMarkProperties(part, record.pPr, operation.style, context.model.styles)
432
+ : sliceOf(part, record.pPr);
433
+ return commitParagraph(context, target, [
434
+ patches.replaceElement(part, record.node, paragraphXml(record.node, record.id, pPr, content)),
435
+ ]);
436
+ },
437
+ };
438
+ export const setParagraphStyleHandler = {
439
+ async validate(operation, context, issue) {
440
+ await textTarget(operation.target, context, issue);
441
+ },
442
+ async apply(operation, context) {
443
+ const target = (await textTarget(operation.target, context, () => { }));
444
+ const { part, record } = target;
445
+ const { style } = operation;
446
+ if (style.align === undefined &&
447
+ (style.spacing === undefined || Object.keys(style.spacing).length === 0))
448
+ return { createdIds: [], warnings: [] };
449
+ const pPr = changedParagraphProperties(part, record.pPr, style);
450
+ const node = record.node;
451
+ const content = node.selfClosing
452
+ ? ""
453
+ : part.text.slice(record.pPr ? record.pPr.end : node.contentStart, node.contentEnd);
454
+ return commitParagraph(context, target, [
455
+ patches.replaceElement(part, node, paragraphXml(node, record.id, pPr, content)),
456
+ ]);
457
+ },
458
+ };
@@ -0,0 +1,34 @@
1
+ import type { XmlElement, XmlPart } from "../ooxml/xml.js";
2
+ export declare const TAB = "\t";
3
+ export declare const LINE_BREAK = "\v";
4
+ export declare const PAGE_BREAK = "\f";
5
+ /** The object replacement character: an inline picture or an embedded object. */
6
+ export declare const OBJECT = "\uFFFC";
7
+ export type RunItemKind = "text" | "tab" | "break" | "pageBreak" | "picture" | "anchor" | "object" | "field" | "note";
8
+ export interface RunItem {
9
+ readonly kind: RunItemKind;
10
+ /** Half-open offsets in the paragraph's text. */
11
+ readonly start: number;
12
+ readonly end: number;
13
+ readonly text: string;
14
+ /** The `w:r` holding the item, or the `w:fldSimple` of a simple field. */
15
+ readonly run: XmlElement;
16
+ /** The child of the run the item comes from (`w:t`, `w:tab`, `w:drawing`, …). */
17
+ readonly child: XmlElement;
18
+ /** `w:rPr` of the run, when present. */
19
+ readonly rPr?: XmlElement;
20
+ /** The hyperlink, inline sdt, smart tag or tracked-change wrapper the run sits in, when any. */
21
+ readonly wrapper?: XmlElement;
22
+ /** For a complex field: every run from `begin` to `end`, in order. */
23
+ readonly fieldRuns?: readonly XmlElement[];
24
+ }
25
+ export interface ParagraphText {
26
+ readonly text: string;
27
+ readonly items: readonly RunItem[];
28
+ /** The paragraph holds tracked insertions, deletions or moves. */
29
+ readonly tracked: boolean;
30
+ }
31
+ /** Reads the text of a `w:p`. */
32
+ export declare function readParagraphText(part: XmlPart, paragraph: XmlElement): ParagraphText;
33
+ /** The first item that draws text, for the resolved style of a paragraph. */
34
+ export declare function firstTextItem(model: ParagraphText): RunItem | undefined;