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,352 @@
1
+ import { patches } from "../ooxml/patch.js";
2
+ import { escapeText } from "../ooxml/xml.js";
3
+ import { W14_NS, W_NS } from "./ids.js";
4
+ import { isThemeColorName } from "./style.js";
5
+ import { LINE_BREAK, PAGE_BREAK, TAB } from "./text.js";
6
+ /*
7
+ * Writing WordprocessingML: runs re-serialized from text and their
8
+ * properties, run and paragraph properties changed in schema order with
9
+ * every other child kept as its bytes, paragraph start tags carrying their
10
+ * `w14:paraId`, and the namespace declarations that attribute needs.
11
+ */
12
+ const MC_NS = "http://schemas.openxmlformats.org/markup-compatibility/2006";
13
+ /** Children of `w:rPr` in schema order (CT_RPr). */
14
+ const RPR_ORDER = [
15
+ "rStyle",
16
+ "rFonts",
17
+ "b",
18
+ "bCs",
19
+ "i",
20
+ "iCs",
21
+ "caps",
22
+ "smallCaps",
23
+ "strike",
24
+ "dstrike",
25
+ "outline",
26
+ "shadow",
27
+ "emboss",
28
+ "imprint",
29
+ "noProof",
30
+ "snapToGrid",
31
+ "vanish",
32
+ "webHidden",
33
+ "color",
34
+ "spacing",
35
+ "w",
36
+ "kern",
37
+ "position",
38
+ "sz",
39
+ "szCs",
40
+ "highlight",
41
+ "u",
42
+ "effect",
43
+ "bdr",
44
+ "shd",
45
+ "fitText",
46
+ "vertAlign",
47
+ "rtl",
48
+ "cs",
49
+ "em",
50
+ "lang",
51
+ "eastAsianLayout",
52
+ "specVanish",
53
+ "oMath",
54
+ "rPrChange",
55
+ ];
56
+ /** Children of `w:pPr` in schema order (CT_PPr). */
57
+ const PPR_ORDER = [
58
+ "pStyle",
59
+ "keepNext",
60
+ "keepLines",
61
+ "pageBreakBefore",
62
+ "framePr",
63
+ "widowControl",
64
+ "numPr",
65
+ "suppressLineNumbers",
66
+ "pBdr",
67
+ "shd",
68
+ "tabs",
69
+ "suppressAutoHyphens",
70
+ "kinsoku",
71
+ "wordWrap",
72
+ "overflowPunct",
73
+ "topLinePunct",
74
+ "autoSpaceDE",
75
+ "autoSpaceDN",
76
+ "bidi",
77
+ "adjustRightInd",
78
+ "snapToGrid",
79
+ "spacing",
80
+ "ind",
81
+ "contextualSpacing",
82
+ "mirrorIndents",
83
+ "suppressOverlap",
84
+ "jc",
85
+ "textDirection",
86
+ "textAlignment",
87
+ "textboxTightWrap",
88
+ "outlineLvl",
89
+ "divId",
90
+ "cnfStyle",
91
+ "rPr",
92
+ "sectPr",
93
+ "pPrChange",
94
+ ];
95
+ const FORBIDDEN = /[\u0000-\u0008\u000e-\u001f￾￿]/;
96
+ const LONE_SURROGATE = /[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;
97
+ /** Why a text cannot be written, or undefined when it can. */
98
+ export function textProblem(text) {
99
+ if (FORBIDDEN.test(text))
100
+ return "control characters XML cannot carry";
101
+ if (LONE_SURROGATE.test(text))
102
+ return "a lone surrogate";
103
+ return undefined;
104
+ }
105
+ /** Why a value cannot be written as an attribute: text problems plus the breaks text may hold. */
106
+ export function attributeProblem(value) {
107
+ if (/[\u000b\u000c]/.test(value))
108
+ return "line or page breaks an attribute cannot carry";
109
+ return textProblem(value);
110
+ }
111
+ export function colorProblem(color) {
112
+ if (typeof color === "string")
113
+ return color === "auto" || /^#[0-9A-Fa-f]{6}$/.test(color)
114
+ ? undefined
115
+ : "not #RRGGBB or auto";
116
+ if (!color || typeof color !== "object")
117
+ return "not a colour";
118
+ const { theme } = color;
119
+ return typeof theme === "string" && isThemeColorName(theme)
120
+ ? undefined
121
+ : "not a theme colour name";
122
+ }
123
+ /** Text as the engine stores it: Windows and classic Mac line ends become "\n". */
124
+ export function normalizeText(text) {
125
+ return text.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
126
+ }
127
+ export function sliceOf(part, node) {
128
+ return node ? part.text.slice(node.start, node.end) : "";
129
+ }
130
+ export function escapeAttributeValue(value) {
131
+ return value
132
+ .replaceAll("&", "&amp;")
133
+ .replaceAll("<", "&lt;")
134
+ .replaceAll('"', "&quot;")
135
+ .replaceAll("\t", "&#9;")
136
+ .replaceAll("\n", "&#10;")
137
+ .replaceAll("\r", "&#13;");
138
+ }
139
+ /** The content elements of a run for `text`: `w:t`, `w:tab`, `w:br`. */
140
+ export function runContentXml(text) {
141
+ let out = "";
142
+ let pending = "";
143
+ const flush = () => {
144
+ if (pending.length === 0)
145
+ return;
146
+ const preserve = /^\s|\s$/.test(pending) ? ' xml:space="preserve"' : "";
147
+ out += `<w:t${preserve}>${escapeText(pending)}</w:t>`;
148
+ pending = "";
149
+ };
150
+ for (const char of text) {
151
+ if (char === TAB) {
152
+ flush();
153
+ out += "<w:tab/>";
154
+ }
155
+ else if (char === LINE_BREAK) {
156
+ flush();
157
+ out += "<w:br/>";
158
+ }
159
+ else if (char === PAGE_BREAK) {
160
+ flush();
161
+ out += '<w:br w:type="page"/>';
162
+ }
163
+ else
164
+ pending += char;
165
+ }
166
+ flush();
167
+ return out;
168
+ }
169
+ /** A run with `rPr` bytes (or "") and the given content; nothing for empty content. */
170
+ export function runXml(rPr, content) {
171
+ return content.length === 0 ? "" : `<w:r>${rPr}${content}</w:r>`;
172
+ }
173
+ /**
174
+ * Properties with changes merged in schema order: every child of `source`
175
+ * keeps its bytes unless `set` names its local name, in which case the
176
+ * replacement (or nothing, for `null`) takes its place; new children go
177
+ * where the schema puts them. Returns "" for no properties at all.
178
+ */
179
+ export function mergedProperties(part, source, tag, order, set) {
180
+ const children = source
181
+ ? source.children.map((child) => ({
182
+ local: child.local,
183
+ xml: sliceOf(part, child),
184
+ }))
185
+ : [];
186
+ for (const [local, xml] of set) {
187
+ const index = children.findIndex((child) => child.local === local);
188
+ for (let at = children.length - 1; at >= 0; at -= 1)
189
+ if (children[at].local === local)
190
+ children.splice(at, 1);
191
+ if (xml === null)
192
+ continue;
193
+ let at = index;
194
+ if (at < 0) {
195
+ // Before the first child the schema places later; a child the
196
+ // schema list does not name (an extension) counts as later too.
197
+ const rank = order.indexOf(local);
198
+ at = children.findIndex((child) => {
199
+ const other = order.indexOf(child.local);
200
+ return other < 0 || other > rank;
201
+ });
202
+ if (at < 0)
203
+ at = children.length;
204
+ }
205
+ children.splice(at, 0, { local, xml });
206
+ }
207
+ const attributes = source
208
+ ? source.attributes.map((a) => ` ${a.name}="${a.rawValue}"`).join("")
209
+ : "";
210
+ const content = children.map((child) => child.xml).join("");
211
+ if (!content)
212
+ return source ? `<${tag}${attributes}/>` : "";
213
+ return `<${tag}${attributes}>${content}</${tag}>`;
214
+ }
215
+ function toggleXml(local, on) {
216
+ return on ? `<w:${local}/>` : `<w:${local} w:val="0"/>`;
217
+ }
218
+ /** `w:rPr` bytes with a text style change applied. */
219
+ export function changedRunProperties(part, rPr, change, styles) {
220
+ const set = new Map();
221
+ if (change.bold !== undefined) {
222
+ set.set("b", toggleXml("b", change.bold));
223
+ set.set("bCs", toggleXml("bCs", change.bold));
224
+ }
225
+ if (change.italic !== undefined) {
226
+ set.set("i", toggleXml("i", change.italic));
227
+ set.set("iCs", toggleXml("iCs", change.italic));
228
+ }
229
+ if (change.underline !== undefined)
230
+ set.set("u", `<w:u w:val="${change.underline ? "single" : "none"}"/>`);
231
+ if (change.fontSize !== undefined) {
232
+ const half = String(Math.round(change.fontSize * 2));
233
+ set.set("sz", `<w:sz w:val="${half}"/>`);
234
+ set.set("szCs", `<w:szCs w:val="${half}"/>`);
235
+ }
236
+ if (change.color !== undefined)
237
+ set.set("color", colorXml(change.color, styles));
238
+ if (change.highlight !== undefined)
239
+ set.set("highlight", change.highlight === "none"
240
+ ? null
241
+ : `<w:highlight w:val="${change.highlight}"/>`);
242
+ if (change.fontFamily !== undefined)
243
+ set.set("rFonts", fontsXml(part, rPr, change.fontFamily));
244
+ return mergedProperties(part, rPr, "w:rPr", RPR_ORDER, set);
245
+ }
246
+ /** `w:color` for a colour: RGB, automatic, or a theme name with the theme's value. */
247
+ export function colorXml(color, styles) {
248
+ if (typeof color === "string") {
249
+ if (color === "auto")
250
+ return '<w:color w:val="auto"/>';
251
+ return `<w:color w:val="${color.slice(1).toUpperCase()}"/>`;
252
+ }
253
+ const value = styles.themeColor(color.theme) ?? "auto";
254
+ return `<w:color w:val="${value}" w:themeColor="${escapeAttributeValue(color.theme)}"/>`;
255
+ }
256
+ /** `w:rFonts` with the ASCII and high-ANSI faces set and their theme overrides dropped. */
257
+ function fontsXml(part, rPr, family) {
258
+ const existing = rPr?.children.find((child) => child.local === "rFonts" && child.namespace === W_NS);
259
+ const kept = (existing?.attributes ?? []).filter((attribute) => !["w:ascii", "w:hAnsi", "w:asciiTheme", "w:hAnsiTheme"].includes(attribute.name));
260
+ const face = escapeAttributeValue(family);
261
+ const rest = kept.map((a) => ` ${a.name}="${a.rawValue}"`).join("");
262
+ return `<w:rFonts w:ascii="${face}" w:hAnsi="${face}"${rest}/>`;
263
+ }
264
+ /** `w:pPr` bytes with alignment and spacing changed, other children kept. */
265
+ export function changedParagraphProperties(part, pPr, change) {
266
+ const set = new Map();
267
+ if (change.align !== undefined)
268
+ set.set("jc", `<w:jc w:val="${alignValue(change.align)}"/>`);
269
+ if (change.spacing !== undefined) {
270
+ const existing = pPr?.children.find((child) => child.local === "spacing" && child.namespace === W_NS);
271
+ const attributes = new Map();
272
+ for (const attribute of existing?.attributes ?? [])
273
+ attributes.set(attribute.name, attribute.rawValue);
274
+ // Word prefers the line-based and automatic forms over w:before and
275
+ // w:after, so a set value drops them.
276
+ if (change.spacing.before !== undefined) {
277
+ attributes.set("w:before", String(Math.round(change.spacing.before * 20)));
278
+ attributes.delete("w:beforeLines");
279
+ attributes.delete("w:beforeAutospacing");
280
+ }
281
+ if (change.spacing.after !== undefined) {
282
+ attributes.set("w:after", String(Math.round(change.spacing.after * 20)));
283
+ attributes.delete("w:afterLines");
284
+ attributes.delete("w:afterAutospacing");
285
+ }
286
+ if (change.spacing.line !== undefined) {
287
+ attributes.set("w:line", String(Math.round(change.spacing.line * 240)));
288
+ attributes.set("w:lineRule", "auto");
289
+ }
290
+ set.set("spacing", `<w:spacing${[...attributes].map(([name, raw]) => ` ${name}="${raw}"`).join("")}/>`);
291
+ }
292
+ return mergedProperties(part, pPr, "w:pPr", PPR_ORDER, set);
293
+ }
294
+ export function alignValue(align) {
295
+ switch (align) {
296
+ case "center":
297
+ return "center";
298
+ case "right":
299
+ return "right";
300
+ case "justify":
301
+ return "both";
302
+ default:
303
+ return "left";
304
+ }
305
+ }
306
+ /** `w:pPr` bytes with the paragraph mark's `w:rPr` changed (created when absent). */
307
+ export function paragraphMarkProperties(part, pPr, change, styles) {
308
+ const rPr = pPr?.children.find((child) => child.local === "rPr" && child.namespace === W_NS);
309
+ const changed = changedRunProperties(part, rPr, change, styles);
310
+ return mergedProperties(part, pPr, "w:pPr", PPR_ORDER, new Map([["rPr", changed || null]]));
311
+ }
312
+ /** `w:pPr` bytes without the section properties, for a paragraph copied next to one that ends a section. */
313
+ export function paragraphPropertiesWithoutSection(part, pPr) {
314
+ if (!pPr)
315
+ return "";
316
+ return mergedProperties(part, pPr, "w:pPr", PPR_ORDER, new Map([["sectPr", null]]));
317
+ }
318
+ /**
319
+ * The start tag of a `w:p` with its id written: the paragraph's own
320
+ * attributes, `w14:paraId` and `w14:textId` replaced.
321
+ */
322
+ export function paragraphStartTag(source, id) {
323
+ const kept = (source?.attributes ?? []).filter((attribute) => attribute.name !== "w14:paraId" && attribute.name !== "w14:textId");
324
+ const rest = kept.map((a) => ` ${a.name}="${a.rawValue}"`).join("");
325
+ return `<w:p${rest} w14:paraId="${id}">`;
326
+ }
327
+ /** A whole paragraph from its start tag, property bytes and content. */
328
+ export function paragraphXml(source, id, pPr, content) {
329
+ return `${paragraphStartTag(source, id)}${pPr}${content}</w:p>`;
330
+ }
331
+ /**
332
+ * Patches that declare the `w14` namespace on the document root and list
333
+ * it as ignorable, when the root does not yet; nothing when it does.
334
+ */
335
+ export function namespacePatches(part) {
336
+ const root = part.root;
337
+ const items = [];
338
+ const has = (name) => root.attributes.find((attribute) => attribute.name === name)?.value;
339
+ if (has("xmlns:w14") === undefined)
340
+ items.push(patches.setAttribute(part, root, "xmlns:w14", W14_NS));
341
+ if (has("xmlns:mc") === undefined)
342
+ items.push(patches.setAttribute(part, root, "xmlns:mc", MC_NS));
343
+ const ignorable = has("mc:Ignorable");
344
+ const listed = ignorable ? ignorable.split(/\s+/).filter(Boolean) : [];
345
+ if (!listed.includes("w14"))
346
+ items.push(patches.setAttribute(part, root, "mc:Ignorable", [...listed, "w14"].join(" ")));
347
+ return items;
348
+ }
349
+ /** Whether the paragraph already carries a `w14:paraId`. */
350
+ export function isAuthored(part, paragraph) {
351
+ return part.attribute(paragraph, "w14:paraId") !== undefined;
352
+ }
@@ -0,0 +1,117 @@
1
+ import type { DocumentFormat, RegisteredFont, ResourceLimits, TextRun, ViewerWarning } from "../contracts.js";
2
+ import type { EditSession } from "./sessions.js";
3
+ import type { EditableFormat, EditElement, EditFindOptions, EditOperation, EditSessionBase, ElementQuery, OperationIssue, OperationSchemaSet, PagePoint, ReadItem, ReadOptions, ReadResult, TextTarget } from "./types.js";
4
+ export interface EditEngineContext {
5
+ readonly format: EditableFormat;
6
+ readonly fileName?: string;
7
+ readonly limits: ResourceLimits;
8
+ readonly assetBaseUrl?: URL;
9
+ /** Fonts the host registered with the client, for text the engine writes. */
10
+ readonly fonts?: readonly RegisteredFont[];
11
+ readonly signal: AbortSignal;
12
+ }
13
+ /**
14
+ * The format-independent session a provider wraps into its typed session.
15
+ * The read hooks let a typed session add engine reads that queue behind
16
+ * earlier calls and carry the same envelope as the core's own reads.
17
+ */
18
+ export interface EditSessionCore extends EditSessionBase<EditOperation, EditElement> {
19
+ readItem<T>(options: ReadOptions | undefined, task: (engine: EditEngine, signal: AbortSignal) => Promise<T | undefined>): Promise<ReadItem<T>>;
20
+ readItems<T>(options: ReadOptions | undefined, task: (engine: EditEngine, signal: AbortSignal) => Promise<readonly T[]>): Promise<ReadResult<T>>;
21
+ }
22
+ /**
23
+ * What the viewer lends a typed session beyond the core: the text runs of
24
+ * the shown document, for formats whose engine never lays out (DOCX joins
25
+ * its elements with the renderer's runs on the main thread).
26
+ */
27
+ export interface EditSessionAccess {
28
+ /** Text runs of a page of the shown document; computed on demand and cached by the viewer. */
29
+ getTextRuns(pageIndex: number, signal?: AbortSignal): Promise<readonly TextRun[]>;
30
+ /** Pages whose text runs are already known, in no particular order. */
31
+ cachedPages(): readonly number[];
32
+ }
33
+ /** Advertised by a `DocumentAdapter` that can edit some of its formats. */
34
+ export interface EditEngineProvider {
35
+ /** Document formats the provider edits; variants map to a session format (pptm → pptx). */
36
+ readonly formats: readonly DocumentFormat[];
37
+ load(original: Uint8Array, context: EditEngineContext): Promise<EditEngine>;
38
+ /** Adds the format's typed methods on top of the core session. */
39
+ createSession(core: EditSessionCore, access: EditSessionAccess): EditSession;
40
+ }
41
+ /** Bytes of a state, with what the engine could not guarantee about them. */
42
+ export interface MaterializedDocument {
43
+ readonly bytes: Uint8Array;
44
+ /** For example `privacy-not-guaranteed` when a PDF full save could not be compacted. */
45
+ readonly warnings: readonly ViewerWarning[];
46
+ }
47
+ /** A batch with the identity the core assigned to the state after it. */
48
+ export interface EngineBatch {
49
+ /** Unique within the session and never reused; engines derive created ids from it. */
50
+ readonly stateId: number;
51
+ readonly operations: readonly EditOperation[];
52
+ }
53
+ /** Format-specific save fields, as the session's `save()` received them without the signal. */
54
+ export type MaterializeOptions = Readonly<Record<string, unknown>>;
55
+ /** What `restore` rebuilds: a base document (a checkpoint, else the original) plus batches. */
56
+ export interface RestoreTarget {
57
+ readonly base?: Uint8Array;
58
+ readonly batches: readonly EngineBatch[];
59
+ }
60
+ export interface EngineChange {
61
+ readonly createdIds: readonly string[];
62
+ /** Ids that no longer exist after the batch, including every element of a deleted page. */
63
+ readonly removedIds: readonly string[];
64
+ /** Old id → new id, when the format had to rename an element. */
65
+ readonly remappedIds?: Readonly<Record<string, string>>;
66
+ /** A superset of the pages whose content changed. */
67
+ readonly changedPages: readonly number[];
68
+ /**
69
+ * Flow formats: the `paragraphId` of the text runs where the document
70
+ * reflows; the host turns it into every page from that paragraph's first
71
+ * page to the end, and `changedPages` is ignored.
72
+ */
73
+ readonly reflowFrom?: string;
74
+ /** Optional: the core takes the count from the renderer and only cross-checks this. */
75
+ readonly pageCount?: number;
76
+ readonly warnings: readonly ViewerWarning[];
77
+ }
78
+ /**
79
+ * A mutable working copy of one document. The core serializes every call, so
80
+ * an engine never sees two calls at once.
81
+ */
82
+ export interface EditEngine {
83
+ readonly schemas: OperationSchemaSet;
84
+ /** Validates against the current state; an empty result means the batch can be applied. */
85
+ validate(operations: readonly EditOperation[], signal: AbortSignal): Promise<readonly OperationIssue[]>;
86
+ /**
87
+ * Applies an already validated batch to the working copy. Same-batch
88
+ * references (`"$<n>"` targets) are resolved here; one that resolves to an
89
+ * element the operation cannot act on rejects with `invalid-operation`.
90
+ */
91
+ apply(batch: EngineBatch, signal: AbortSignal): Promise<EngineChange>;
92
+ /**
93
+ * Bytes of the current state; the original bytes when nothing changed.
94
+ * `show` asks for what the viewer reopens, `save` for what the host keeps;
95
+ * formats read their own fields from `options` (the PDF save mode).
96
+ */
97
+ materialize(purpose: "show" | "save", options: MaterializeOptions, signal: AbortSignal): Promise<Uint8Array>;
98
+ /**
99
+ * `materialize` with what the bytes do not guarantee; an engine without it
100
+ * is taken to guarantee everything.
101
+ */
102
+ materializeDocument?(purpose: "show" | "save", options: MaterializeOptions, signal: AbortSignal): Promise<MaterializedDocument>;
103
+ /**
104
+ * Rebuilds a state: the base document (a checkpoint, else the original)
105
+ * with the batches applied in order. Used by undo, redo, reset, dry runs
106
+ * and recovery.
107
+ */
108
+ restore(target: RestoreTarget, signal: AbortSignal): Promise<void>;
109
+ /** Keeps asset bytes for the session; operations refer to them by id. */
110
+ putAsset(id: string, data: Uint8Array, signal: AbortSignal): Promise<void>;
111
+ getElements(query: ElementQuery, signal: AbortSignal): Promise<readonly EditElement[]>;
112
+ /** Direct lookup; without it the core scans `getElements`. */
113
+ getElement?(id: string, signal: AbortSignal): Promise<EditElement | undefined>;
114
+ elementsAt(pageIndex: number, point: PagePoint, signal: AbortSignal): Promise<readonly EditElement[]>;
115
+ findText(query: string, options: EditFindOptions, signal: AbortSignal): Promise<readonly TextTarget[]>;
116
+ dispose(): Promise<void>;
117
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,53 @@
1
+ import type { EngineBatch } from "./engine.js";
2
+ import type { EditOperation } from "./types.js";
3
+ export interface HistoryEntry {
4
+ readonly operations: readonly EditOperation[];
5
+ readonly label?: string;
6
+ /** Ids the batch created; an undo removes them again. */
7
+ readonly createdIds: readonly string[];
8
+ /** Ids the batch removed; a redo removes them again. */
9
+ readonly removedIds: readonly string[];
10
+ /** Page indexes the batch changed, in the document after it. */
11
+ readonly changedPages: readonly number[];
12
+ /** Flow formats: the paragraph the document reflows from, for undo and redo. */
13
+ readonly reflowFrom?: string;
14
+ readonly pageCountBefore: number;
15
+ readonly pageCountAfter: number;
16
+ /** Identifies the content after this batch; equal ids mean equal content. */
17
+ readonly stateId: number;
18
+ }
19
+ /**
20
+ * Linear undo history over an immutable original. Entries beyond the limit are
21
+ * folded into the starting point: they stay applied but can no longer be
22
+ * undone. Every reachable content state has a numeric id, so "unchanged since
23
+ * the last save" is an id comparison.
24
+ */
25
+ export declare class EditHistory {
26
+ #private;
27
+ constructor(limit: number, originalPageCount: number);
28
+ get canUndo(): boolean;
29
+ get canRedo(): boolean;
30
+ get isPristine(): boolean;
31
+ /** The id the next pushed entry will get; engines derive created ids from it. */
32
+ get nextStateId(): number;
33
+ /** Content id of the current state; 0 is the original document. */
34
+ get stateId(): number;
35
+ get pageCount(): number;
36
+ /** State ids of every entry still in the history, folded and redo tail included. */
37
+ get stateIds(): readonly number[];
38
+ /** The entry `undo()` would revert, if any. */
39
+ get undoEntry(): HistoryEntry | undefined;
40
+ /** The entry `redo()` would re-apply, if any. */
41
+ get redoEntry(): HistoryEntry | undefined;
42
+ /** Batches applied to the original in the current state. */
43
+ applied(): readonly EngineBatch[];
44
+ /** Batches applied to the original when `position` undoable entries are applied. */
45
+ batchesAt(position: number): readonly EngineBatch[];
46
+ /** Entries applied when `position` undoable entries are applied, folded ones first. */
47
+ entriesAt(position: number): readonly HistoryEntry[];
48
+ get position(): number;
49
+ push(entry: Omit<HistoryEntry, "stateId">): HistoryEntry;
50
+ undo(): void;
51
+ redo(): void;
52
+ clear(): void;
53
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Linear undo history over an immutable original. Entries beyond the limit are
3
+ * folded into the starting point: they stay applied but can no longer be
4
+ * undone. Every reachable content state has a numeric id, so "unchanged since
5
+ * the last save" is an id comparison.
6
+ */
7
+ export class EditHistory {
8
+ #limit;
9
+ #originalPageCount;
10
+ /** Folded batches, always applied before the undoable entries. */
11
+ #folded = [];
12
+ #entries = [];
13
+ #position = 0;
14
+ #nextStateId = 1;
15
+ constructor(limit, originalPageCount) {
16
+ this.#limit = limit;
17
+ this.#originalPageCount = originalPageCount;
18
+ }
19
+ get canUndo() {
20
+ return this.#position > 0;
21
+ }
22
+ get canRedo() {
23
+ return this.#position < this.#entries.length;
24
+ }
25
+ get isPristine() {
26
+ return this.#folded.length === 0 && this.#entries.length === 0;
27
+ }
28
+ /** The id the next pushed entry will get; engines derive created ids from it. */
29
+ get nextStateId() {
30
+ return this.#nextStateId;
31
+ }
32
+ /** Content id of the current state; 0 is the original document. */
33
+ get stateId() {
34
+ return this.#position > 0
35
+ ? this.#entries[this.#position - 1].stateId
36
+ : (this.#folded.at(-1)?.stateId ?? 0);
37
+ }
38
+ get pageCount() {
39
+ return this.#position > 0
40
+ ? this.#entries[this.#position - 1].pageCountAfter
41
+ : (this.#folded.at(-1)?.pageCountAfter ?? this.#originalPageCount);
42
+ }
43
+ /** State ids of every entry still in the history, folded and redo tail included. */
44
+ get stateIds() {
45
+ return [...this.#folded, ...this.#entries].map((entry) => entry.stateId);
46
+ }
47
+ /** The entry `undo()` would revert, if any. */
48
+ get undoEntry() {
49
+ return this.#position > 0 ? this.#entries[this.#position - 1] : undefined;
50
+ }
51
+ /** The entry `redo()` would re-apply, if any. */
52
+ get redoEntry() {
53
+ return this.#entries[this.#position];
54
+ }
55
+ /** Batches applied to the original in the current state. */
56
+ applied() {
57
+ return this.batchesAt(this.#position);
58
+ }
59
+ /** Batches applied to the original when `position` undoable entries are applied. */
60
+ batchesAt(position) {
61
+ return this.entriesAt(position).map((entry) => ({
62
+ stateId: entry.stateId,
63
+ operations: entry.operations,
64
+ }));
65
+ }
66
+ /** Entries applied when `position` undoable entries are applied, folded ones first. */
67
+ entriesAt(position) {
68
+ return [...this.#folded, ...this.#entries.slice(0, position)];
69
+ }
70
+ get position() {
71
+ return this.#position;
72
+ }
73
+ push(entry) {
74
+ const stored = Object.freeze({
75
+ ...entry,
76
+ stateId: this.#nextStateId++,
77
+ });
78
+ // A new change after an undo drops the redo tail.
79
+ this.#entries.length = this.#position;
80
+ this.#entries.push(stored);
81
+ this.#position = this.#entries.length;
82
+ while (this.#entries.length > this.#limit) {
83
+ this.#folded.push(this.#entries.shift());
84
+ this.#position -= 1;
85
+ }
86
+ return stored;
87
+ }
88
+ undo() {
89
+ if (this.canUndo)
90
+ this.#position -= 1;
91
+ }
92
+ redo() {
93
+ if (this.canRedo)
94
+ this.#position += 1;
95
+ }
96
+ clear() {
97
+ this.#folded = [];
98
+ this.#entries = [];
99
+ this.#position = 0;
100
+ }
101
+ }
@@ -0,0 +1,20 @@
1
+ /** "/ppt/slides/slide1.xml" for a ZIP entry name or a loosely written name. */
2
+ export declare function partNameOf(entryName: string): string;
3
+ /** The ZIP entry name of a part: no leading slash. */
4
+ export declare function entryNameOf(partName: string): string;
5
+ /** The key two part names are compared by. */
6
+ export declare function partKey(name: string): string;
7
+ /** Whether a ZIP entry is a directory marker rather than a part. */
8
+ export declare function isDirectoryEntry(entryName: string): boolean;
9
+ /** "/ppt/slides/" for "/ppt/slides/slide1.xml"; "/" at the root. */
10
+ export declare function folderOf(partName: string): string;
11
+ /** The extension without the dot, lower-case; "" when there is none. */
12
+ export declare function extensionOf(partName: string): string;
13
+ /** The relationships part of a part ("/" for the package): "/ppt/slides/_rels/slide1.xml.rels". */
14
+ export declare function relationshipsPartOf(partName: string): string;
15
+ /**
16
+ * Resolves a relationship target against its source part: relative targets
17
+ * against the source's folder, absolute ones as they are; "." and ".."
18
+ * segments collapse. The result is an absolute part name.
19
+ */
20
+ export declare function resolveTarget(sourcePart: string, target: string): string;