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,193 @@
1
+ /*
2
+ * DrawingML geometry: EMU, 60,000ths of a degree, and the affine transforms
3
+ * of groups. Slide space is CSS pixels at 96 dpi.
4
+ */
5
+ export const EMU_PER_PX = 9525;
6
+ export const EMU_PER_PT = 12700;
7
+ export function emuToPx(emu) {
8
+ return emu / EMU_PER_PX;
9
+ }
10
+ export function pxToEmu(px) {
11
+ return Math.round(px * EMU_PER_PX);
12
+ }
13
+ /** Reads `a:xfrm` (or `p:xfrm` of a graphic frame); undefined without an offset and an extent. */
14
+ export function readXfrm(part, node) {
15
+ if (!node)
16
+ return undefined;
17
+ const off = node.children.find((child) => child.local === "off");
18
+ const ext = node.children.find((child) => child.local === "ext");
19
+ if (!off || !ext)
20
+ return undefined;
21
+ const x = integer(part.attribute(off, "x"));
22
+ const y = integer(part.attribute(off, "y"));
23
+ const cx = integer(part.attribute(ext, "cx"));
24
+ const cy = integer(part.attribute(ext, "cy"));
25
+ if ([x, y, cx, cy].some((value) => value === undefined))
26
+ return undefined;
27
+ const chOff = node.children.find((child) => child.local === "chOff");
28
+ const chExt = node.children.find((child) => child.local === "chExt");
29
+ const child = chOff && chExt
30
+ ? {
31
+ x: integer(part.attribute(chOff, "x")) ?? 0,
32
+ y: integer(part.attribute(chOff, "y")) ?? 0,
33
+ cx: integer(part.attribute(chExt, "cx")) ?? 0,
34
+ cy: integer(part.attribute(chExt, "cy")) ?? 0,
35
+ }
36
+ : undefined;
37
+ return {
38
+ x: x,
39
+ y: y,
40
+ cx: cx,
41
+ cy: cy,
42
+ rotation: (integer(part.attribute(node, "rot")) ?? 0) / 60000,
43
+ flipH: part.attribute(node, "flipH") === "1",
44
+ flipV: part.attribute(node, "flipV") === "1",
45
+ ...(child ? { child } : {}),
46
+ };
47
+ }
48
+ function integer(value) {
49
+ if (value === undefined)
50
+ return undefined;
51
+ const number = Number(value);
52
+ return Number.isFinite(number) ? Math.trunc(number) : undefined;
53
+ }
54
+ export const IDENTITY = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
55
+ export function multiply(m, n) {
56
+ // m ∘ n: apply n first, then m.
57
+ return {
58
+ a: m.a * n.a + m.c * n.b,
59
+ b: m.b * n.a + m.d * n.b,
60
+ c: m.a * n.c + m.c * n.d,
61
+ d: m.b * n.c + m.d * n.d,
62
+ e: m.a * n.e + m.c * n.f + m.e,
63
+ f: m.b * n.e + m.d * n.f + m.f,
64
+ };
65
+ }
66
+ export function translate(tx, ty) {
67
+ return { a: 1, b: 0, c: 0, d: 1, e: tx, f: ty };
68
+ }
69
+ export function scale(sx, sy) {
70
+ return { a: sx, b: 0, c: 0, d: sy, e: 0, f: 0 };
71
+ }
72
+ function rotate(degrees) {
73
+ const radians = (degrees * Math.PI) / 180;
74
+ const cos = Math.cos(radians);
75
+ const sin = Math.sin(radians);
76
+ return { a: cos, b: sin, c: -sin, d: cos, e: 0, f: 0 };
77
+ }
78
+ export function apply(m, point) {
79
+ return {
80
+ x: m.a * point.x + m.c * point.y + m.e,
81
+ y: m.b * point.x + m.d * point.y + m.f,
82
+ };
83
+ }
84
+ export function invert(m) {
85
+ const det = m.a * m.d - m.b * m.c;
86
+ if (det === 0)
87
+ return IDENTITY;
88
+ return {
89
+ a: m.d / det,
90
+ b: -m.b / det,
91
+ c: -m.c / det,
92
+ d: m.a / det,
93
+ e: (m.c * m.f - m.d * m.e) / det,
94
+ f: (m.b * m.e - m.a * m.f) / det,
95
+ };
96
+ }
97
+ /** Rotation and flips of a frame about its centre, in the frame's own space. */
98
+ function frameTransform(frame) {
99
+ const centre = translate(frame.x + frame.cx / 2, frame.y + frame.cy / 2);
100
+ const back = translate(-(frame.x + frame.cx / 2), -(frame.y + frame.cy / 2));
101
+ const flip = scale(frame.flipH ? -1 : 1, frame.flipV ? -1 : 1);
102
+ return multiply(centre, multiply(rotate(frame.rotation), multiply(flip, back)));
103
+ }
104
+ /**
105
+ * The transform from a group's child space to its parent space: child
106
+ * coordinates are scaled from `chOff`/`chExt` into `off`/`ext`, then the
107
+ * group's rotation and flips apply about its centre.
108
+ */
109
+ export function groupMatrix(group) {
110
+ const child = group.child ?? { x: 0, y: 0, cx: group.cx, cy: group.cy };
111
+ const sx = child.cx === 0 ? 1 : group.cx / child.cx;
112
+ const sy = child.cy === 0 ? 1 : group.cy / child.cy;
113
+ const place = multiply(translate(group.x, group.y), multiply(scale(sx, sy), translate(-child.x, -child.y)));
114
+ return multiply(frameTransform(group), place);
115
+ }
116
+ /**
117
+ * Places a frame given in its own space (EMU) through the transform of the
118
+ * groups above it. The reported frame keeps the element's box scaled and
119
+ * positioned by the groups, with the rotations and flips combined; the
120
+ * bounds are the axis-aligned box of its four transformed corners.
121
+ */
122
+ export function placeFrame(frame, parents) {
123
+ const own = frameTransform(frame);
124
+ const total = multiply(parents, own);
125
+ const corners = [
126
+ { x: frame.x, y: frame.y },
127
+ { x: frame.x + frame.cx, y: frame.y },
128
+ { x: frame.x + frame.cx, y: frame.y + frame.cy },
129
+ { x: frame.x, y: frame.y + frame.cy },
130
+ ].map((corner) => apply(total, corner));
131
+ const xs = corners.map((corner) => corner.x);
132
+ const ys = corners.map((corner) => corner.y);
133
+ const left = Math.min(...xs);
134
+ const top = Math.min(...ys);
135
+ const bounds = {
136
+ x: emuToPx(left),
137
+ y: emuToPx(top),
138
+ width: emuToPx(Math.max(...xs) - left),
139
+ height: emuToPx(Math.max(...ys) - top),
140
+ };
141
+ // The unrotated box in slide space: the parents' transform applied to the
142
+ // element's box, measured by its centre and its scaled extent.
143
+ const centre = apply(parents, {
144
+ x: frame.x + frame.cx / 2,
145
+ y: frame.y + frame.cy / 2,
146
+ });
147
+ const scaleX = Math.hypot(parents.a, parents.b);
148
+ const scaleY = Math.hypot(parents.c, parents.d);
149
+ const width = frame.cx * scaleX;
150
+ const height = frame.cy * scaleY;
151
+ const parentFlip = parents.a * parents.d - parents.b * parents.c < 0;
152
+ // A reflecting parent is reported as a rotation followed by flipH, so its
153
+ // angle comes from the matrix with that flip taken out.
154
+ const parentRotation = parentFlip
155
+ ? (Math.atan2(-parents.b, -parents.a) * 180) / Math.PI
156
+ : (Math.atan2(parents.b, parents.a) * 180) / Math.PI;
157
+ const rotation = normalizeDegrees(parentRotation + (parentFlip ? -frame.rotation : frame.rotation));
158
+ return {
159
+ frame: {
160
+ x: emuToPx(centre.x - width / 2),
161
+ y: emuToPx(centre.y - height / 2),
162
+ width: emuToPx(width),
163
+ height: emuToPx(height),
164
+ rotation,
165
+ flipH: frame.flipH !== parentFlip,
166
+ flipV: frame.flipV,
167
+ },
168
+ bounds,
169
+ };
170
+ }
171
+ function normalizeDegrees(degrees) {
172
+ const value = ((degrees % 360) + 360) % 360;
173
+ return Math.abs(value) < 1e-9 ? 0 : Number(value.toFixed(6));
174
+ }
175
+ /** Whether a slide-space point lies inside a placed frame, rotation and flips included. */
176
+ export function frameContains(placed, point) {
177
+ const { frame } = placed;
178
+ const cx = frame.x + frame.width / 2;
179
+ const cy = frame.y + frame.height / 2;
180
+ const radians = (-frame.rotation * Math.PI) / 180;
181
+ const dx = point.x - cx;
182
+ const dy = point.y - cy;
183
+ const localX = dx * Math.cos(radians) - dy * Math.sin(radians);
184
+ const localY = dx * Math.sin(radians) + dy * Math.cos(radians);
185
+ return (Math.abs(localX) <= frame.width / 2 + 1e-6 &&
186
+ Math.abs(localY) <= frame.height / 2 + 1e-6);
187
+ }
188
+ export function rectsIntersect(a, b) {
189
+ return (a.x < b.x + b.width &&
190
+ b.x < a.x + a.width &&
191
+ a.y < b.y + b.height &&
192
+ b.y < a.y + a.height);
193
+ }
@@ -0,0 +1,7 @@
1
+ import type { WorkerOperationHandler } from "../../worker-endpoint.js";
2
+ /**
3
+ * Serves the edit worker protocol for one OOXML document. It runs inside
4
+ * the OOXML edit worker in the browser and directly in Node tests. The open
5
+ * payload names the format: PPTX (the default) or DOCX.
6
+ */
7
+ export declare function createOoxmlEditHandler(): WorkerOperationHandler;
@@ -0,0 +1,91 @@
1
+ import { ViewerError } from "../../errors.js";
2
+ import { DocxEditEngine } from "../docx/engine.js";
3
+ import { PptxEditEngine } from "./engine.js";
4
+ /**
5
+ * Serves the edit worker protocol for one OOXML document. It runs inside
6
+ * the OOXML edit worker in the browser and directly in Node tests. The open
7
+ * payload names the format: PPTX (the default) or DOCX.
8
+ */
9
+ export function createOoxmlEditHandler() {
10
+ let state;
11
+ const engine = () => {
12
+ if (!state)
13
+ throw new ViewerError("lifecycle-error", "No package is open for editing");
14
+ return state;
15
+ };
16
+ const pptx = () => {
17
+ const current = engine();
18
+ if (!(current instanceof PptxEditEngine))
19
+ throw new ViewerError("edit-unsupported", "The open document is not a presentation", { details: { format: "docx", reason: "no-reads" } });
20
+ return current;
21
+ };
22
+ return async (operation, payload, context) => {
23
+ const { signal } = context;
24
+ switch (operation) {
25
+ case "edit-init":
26
+ return undefined;
27
+ case "edit-open": {
28
+ const open = payload;
29
+ const format = open.format ?? "pptx";
30
+ if (format !== "pptx" && format !== "docx")
31
+ throw new ViewerError("edit-unsupported", `The OOXML edit worker does not serve ${open.format} yet`, { details: { format: open.format, reason: "no-engine" } });
32
+ // The live engine stays until the next one opened, so a failed or
33
+ // aborted open leaves the worker serving what it served before.
34
+ const bytes = new Uint8Array(open.data);
35
+ const next = format === "docx"
36
+ ? await DocxEditEngine.open(bytes, open.limits, signal)
37
+ : await PptxEditEngine.open(bytes, open.limits, signal);
38
+ await state?.dispose();
39
+ state = next;
40
+ const result = { pageCount: state.pageCount };
41
+ return result;
42
+ }
43
+ case "edit-validate": {
44
+ const { operations } = payload;
45
+ return engine().validate(operations, signal);
46
+ }
47
+ case "edit-apply": {
48
+ const { batch } = payload;
49
+ return engine().apply(batch, signal);
50
+ }
51
+ case "edit-put-asset": {
52
+ const { id, data } = payload;
53
+ await engine().putAsset(id, new Uint8Array(data), signal);
54
+ return undefined;
55
+ }
56
+ case "edit-materialize": {
57
+ const { purpose, options } = (payload ?? {});
58
+ const { bytes, warnings } = await engine().materializeDocument(purpose ?? "show", options ?? {}, signal);
59
+ const data = bytes.slice().buffer;
60
+ return { data, warnings };
61
+ }
62
+ case "edit-restore": {
63
+ const { batches, base } = payload;
64
+ await engine().restore({ batches, ...(base ? { base: new Uint8Array(base) } : {}) }, signal);
65
+ return undefined;
66
+ }
67
+ case "edit-elements":
68
+ return engine().getElements(payload.query, signal);
69
+ case "edit-element":
70
+ return engine().getElement(payload.id, signal);
71
+ case "edit-elements-at": {
72
+ const { pageIndex, point } = payload;
73
+ return engine().elementsAt(pageIndex, point, signal);
74
+ }
75
+ case "edit-find-text": {
76
+ const { query, options } = payload;
77
+ return engine().findText(query, options, signal);
78
+ }
79
+ case "edit-pptx-slides":
80
+ return pptx().slides(signal);
81
+ case "edit-pptx-layouts":
82
+ return pptx().layouts(signal);
83
+ case "edit-dispose":
84
+ await state?.dispose();
85
+ state = undefined;
86
+ return undefined;
87
+ default:
88
+ throw new ViewerError("internal", `The OOXML edit worker does not handle ${operation}`);
89
+ }
90
+ };
91
+ }
@@ -0,0 +1,3 @@
1
+ import type { PptxOperationHandler } from "./operations.js";
2
+ /** Handlers by operation name; `IMPLEMENTED_OPERATIONS` in schemas.ts lists the same names. */
3
+ export declare const pptxHandlers: ReadonlyMap<string, PptxOperationHandler>;
@@ -0,0 +1,21 @@
1
+ import { deleteElementHandler, insertTextBoxHandler, moveElementHandler, resizeElementHandler, setShapeStyleHandler, } from "./shape-ops.js";
2
+ import { insertImageHandler, insertTableHandler, setTableCellHandler, } from "./image-table-ops.js";
3
+ import { deleteSlideHandler, duplicateSlideHandler, insertSlideHandler, moveSlideHandler, } from "./slide-ops.js";
4
+ import { replaceTextHandler, setTextStyleHandler } from "./text-ops.js";
5
+ /** Handlers by operation name; `IMPLEMENTED_OPERATIONS` in schemas.ts lists the same names. */
6
+ export const pptxHandlers = new Map([
7
+ ["replaceText", replaceTextHandler],
8
+ ["setTextStyle", setTextStyleHandler],
9
+ ["setShapeStyle", setShapeStyleHandler],
10
+ ["moveElement", moveElementHandler],
11
+ ["resizeElement", resizeElementHandler],
12
+ ["deleteElement", deleteElementHandler],
13
+ ["insertTextBox", insertTextBoxHandler],
14
+ ["insertImage", insertImageHandler],
15
+ ["insertTable", insertTableHandler],
16
+ ["setTableCell", setTableCellHandler],
17
+ ["insertSlide", insertSlideHandler],
18
+ ["duplicateSlide", duplicateSlideHandler],
19
+ ["deleteSlide", deleteSlideHandler],
20
+ ["moveSlide", moveSlideHandler],
21
+ ]);
@@ -0,0 +1,5 @@
1
+ import { type PptxOperationHandler } from "./operations.js";
2
+ import type { PptxInsertImageOperation, PptxInsertTableOperation, PptxSetTableCellOperation } from "./types.js";
3
+ export declare const insertImageHandler: PptxOperationHandler<PptxInsertImageOperation>;
4
+ export declare const insertTableHandler: PptxOperationHandler<PptxInsertTableOperation>;
5
+ export declare const setTableCellHandler: PptxOperationHandler<PptxSetTableCellOperation>;
@@ -0,0 +1,233 @@
1
+ import { resolveBinary } from "../assets.js";
2
+ import { patches } from "../ooxml/patch.js";
3
+ import { escapeText } from "../ooxml/xml.js";
4
+ import { extensionForMime } from "../ooxml/opc.js";
5
+ import { relativeTarget } from "../ooxml/transaction.js";
6
+ import { pxToEmu } from "./geometry.js";
7
+ import { RELATIONSHIP_TYPES } from "./model.js";
8
+ import { committedParts, } from "./operations.js";
9
+ import { paragraphsXml } from "./shape-ops.js";
10
+ import { readTextModel } from "./text.js";
11
+ import { replacedBodyContent } from "./text-ops.js";
12
+ import { normalizeText, textProblem } from "./text-write.js";
13
+ /*
14
+ * Pictures and tables: a picture is a media part related from the slide
15
+ * and a p:pic that embeds it; a table is a p:graphicFrame holding a:tbl
16
+ * with a grid, rows of equal height and one paragraph per cell; a cell's
17
+ * text is replaced like a shape's.
18
+ */
19
+ const MEDIA_FOLDER = "/ppt/media";
20
+ const TABLE_URI = "http://schemas.openxmlformats.org/drawingml/2006/table";
21
+ function checkSlide(pageIndex, context, issue) {
22
+ if (pageIndex < 0 || pageIndex >= context.pageCount) {
23
+ issue("/pageIndex", "unknown-target", `No slide ${pageIndex}`);
24
+ return false;
25
+ }
26
+ return true;
27
+ }
28
+ export const insertImageHandler = {
29
+ async validate(operation, context, issue) {
30
+ checkSlide(operation.pageIndex, context, issue);
31
+ try {
32
+ const bytes = resolveBinary(operation.data, context.assets);
33
+ if (bytes.length === 0)
34
+ issue("/data", "invalid-value", "The image has no bytes");
35
+ else if (!matchesMime(bytes, operation.mimeType))
36
+ issue("/data", "invalid-value", `The bytes are not a ${operation.mimeType} image`);
37
+ }
38
+ catch {
39
+ issue("/data", "unknown-asset", "The asset is not known to the session");
40
+ }
41
+ },
42
+ async apply(operation, context) {
43
+ const elements = await context.elements(operation.pageIndex);
44
+ const { part, slide } = elements;
45
+ const tree = part.find("spTree");
46
+ const id = context.allocateShapeId(elements);
47
+ const bytes = resolveBinary(operation.data, context.assets);
48
+ const transaction = context.pkg.transaction();
49
+ // The same bytes already in the package are related, not stored again.
50
+ const existing = await existingMedia(context, bytes, operation.mimeType);
51
+ const rId = existing
52
+ ? await transaction.addRelationship(slide.part, RELATIONSHIP_TYPES.image, relativeTarget(slide.part, existing))
53
+ : (await transaction.addMedia(slide.part, MEDIA_FOLDER, bytes, operation.mimeType)).rId;
54
+ const { rect } = operation;
55
+ const xml = `<p:pic><p:nvPicPr><p:cNvPr id="${id}" name="Picture ${id - 1}"/><p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr><p:nvPr/></p:nvPicPr>` +
56
+ `<p:blipFill><a:blip r:embed="${rId}"/><a:stretch><a:fillRect/></a:stretch></p:blipFill>` +
57
+ `<p:spPr><a:xfrm><a:off x="${pxToEmu(rect.x)}" y="${pxToEmu(rect.y)}"/><a:ext cx="${pxToEmu(rect.width)}" cy="${pxToEmu(rect.height)}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr></p:pic>`;
58
+ transaction.patch(part, [patches.appendChild(part, tree, xml)]);
59
+ const change = await transaction.commit();
60
+ return {
61
+ createdIds: [`${slide.key}:${id}`],
62
+ changedPages: [operation.pageIndex],
63
+ warnings: change.warnings,
64
+ parts: committedParts(change),
65
+ };
66
+ },
67
+ };
68
+ /** A media part of the package with these bytes and this type's extension, if any. */
69
+ async function existingMedia(context, bytes, mimeType) {
70
+ const extension = `.${extensionForMime(mimeType)}`;
71
+ for (const name of context.pkg.currentPartNames) {
72
+ if (!name.startsWith(`${MEDIA_FOLDER}/`) || !name.endsWith(extension))
73
+ continue;
74
+ const entry = context.pkg.entry(name);
75
+ if (entry && entry.uncompressedSize !== bytes.length)
76
+ continue;
77
+ const candidate = await context.pkg.part(name);
78
+ if (candidate.length !== bytes.length)
79
+ continue;
80
+ let same = true;
81
+ for (let index = 0; index < bytes.length; index += 1)
82
+ if (candidate[index] !== bytes[index]) {
83
+ same = false;
84
+ break;
85
+ }
86
+ if (same)
87
+ return name;
88
+ }
89
+ return undefined;
90
+ }
91
+ /** Whether the bytes carry the signature of the declared image type. */
92
+ function matchesMime(bytes, mimeType) {
93
+ if (mimeType === "image/png")
94
+ return (bytes.length >= 8 &&
95
+ bytes[0] === 0x89 &&
96
+ bytes[1] === 0x50 &&
97
+ bytes[2] === 0x4e &&
98
+ bytes[3] === 0x47);
99
+ return (bytes.length >= 3 &&
100
+ bytes[0] === 0xff &&
101
+ bytes[1] === 0xd8 &&
102
+ bytes[2] === 0xff);
103
+ }
104
+ /** The default table style id the deck names, when it has a style list. */
105
+ async function defaultTableStyle(context) {
106
+ const name = "/ppt/tableStyles.xml";
107
+ if (!context.pkg.has(name))
108
+ return undefined;
109
+ const part = await context.pkg.xml(name);
110
+ return part.attribute(part.root, "def");
111
+ }
112
+ export const insertTableHandler = {
113
+ async validate(operation, context, issue) {
114
+ checkSlide(operation.pageIndex, context, issue);
115
+ const columns = operation.rows[0]?.length ?? 0;
116
+ if (operation.rows.some((row) => row.length !== columns))
117
+ issue("/rows", "invalid-value", "Every row needs the same number of cells");
118
+ if (operation.columnWidths && operation.columnWidths.length !== columns)
119
+ issue("/columnWidths", "invalid-value", `Expected ${columns} column weights`);
120
+ operation.rows.forEach((row, rowIndex) => row.forEach((cell, columnIndex) => {
121
+ const problem = textProblem(normalizeText(cell));
122
+ if (problem)
123
+ issue(`/rows/${rowIndex}/${columnIndex}`, "invalid-text", `The text holds ${problem}`);
124
+ }));
125
+ },
126
+ async apply(operation, context) {
127
+ const elements = await context.elements(operation.pageIndex);
128
+ const { part, slide } = elements;
129
+ const tree = part.find("spTree");
130
+ const id = context.allocateShapeId(elements);
131
+ const { rect, rows } = operation;
132
+ const columns = rows[0].length;
133
+ const weights = operation.columnWidths ?? Array.from({ length: columns }, () => 1);
134
+ const total = weights.reduce((sum, weight) => sum + weight, 0);
135
+ const width = pxToEmu(rect.width);
136
+ const gridWidths = [];
137
+ let used = 0;
138
+ weights.forEach((weight, index) => {
139
+ const value = index === weights.length - 1
140
+ ? width - used
141
+ : Math.round((width * weight) / total);
142
+ gridWidths.push(value);
143
+ used += value;
144
+ });
145
+ const height = pxToEmu(rect.height);
146
+ const rowHeights = rows.map((_, index) => index === rows.length - 1
147
+ ? height - Math.round(height / rows.length) * (rows.length - 1)
148
+ : Math.round(height / rows.length));
149
+ const style = operation.style ?? {};
150
+ const styleId = await defaultTableStyle(context);
151
+ const tblPr = `<a:tblPr firstRow="${style.firstRow === false ? 0 : 1}" bandRow="${style.bandRow === false ? 0 : 1}"${styleId
152
+ ? `><a:tableStyleId>${escapeText(styleId)}</a:tableStyleId></a:tblPr>`
153
+ : "/>"}`;
154
+ const grid = `<a:tblGrid>${gridWidths.map((w) => `<a:gridCol w="${w}"/>`).join("")}</a:tblGrid>`;
155
+ const body = rows
156
+ .map((row, rowIndex) => `<a:tr h="${rowHeights[rowIndex]}">${row
157
+ .map((cell) => `<a:tc><a:txBody><a:bodyPr/><a:lstStyle/>${paragraphsXml(part, normalizeText(cell), undefined)}</a:txBody><a:tcPr/></a:tc>`)
158
+ .join("")}</a:tr>`)
159
+ .join("");
160
+ const xml = `<p:graphicFrame><p:nvGraphicFramePr><p:cNvPr id="${id}" name="Table ${id - 1}"/><p:cNvGraphicFramePr><a:graphicFrameLocks noGrp="1"/></p:cNvGraphicFramePr><p:nvPr/></p:nvGraphicFramePr>` +
161
+ `<p:xfrm><a:off x="${pxToEmu(rect.x)}" y="${pxToEmu(rect.y)}"/><a:ext cx="${width}" cy="${height}"/></p:xfrm>` +
162
+ `<a:graphic><a:graphicData uri="${TABLE_URI}"><a:tbl>${tblPr}${grid}${body}</a:tbl></a:graphicData></a:graphic></p:graphicFrame>`;
163
+ const transaction = context.pkg.transaction();
164
+ transaction.patch(part, [patches.appendChild(part, tree, xml)]);
165
+ const change = await transaction.commit();
166
+ return {
167
+ createdIds: [`${slide.key}:${id}`],
168
+ changedPages: [operation.pageIndex],
169
+ warnings: change.warnings,
170
+ parts: committedParts(change),
171
+ };
172
+ },
173
+ };
174
+ async function cellTarget(operation, context, issue) {
175
+ const record = await context.locate(operation.target);
176
+ if (!record) {
177
+ issue("/target", "unknown-target", `No element ${operation.target}`);
178
+ return undefined;
179
+ }
180
+ const tbl = record.part.find("tbl", record.node);
181
+ if (record.readOnly || record.element.kind !== "table" || !tbl) {
182
+ issue("/target", "invalid-target", `Element ${operation.target} is not an editable table`);
183
+ return undefined;
184
+ }
185
+ const rows = tbl.children.filter((child) => child.local === "tr");
186
+ const row = rows[operation.row];
187
+ const cell = row?.children.filter((child) => child.local === "tc")[operation.column];
188
+ if (!row) {
189
+ issue("/row", "range", `The table has ${rows.length} rows`);
190
+ return undefined;
191
+ }
192
+ if (!cell) {
193
+ issue("/column", "range", `Row ${operation.row} has ${row.children.filter((child) => child.local === "tc").length} cells`);
194
+ return undefined;
195
+ }
196
+ return { record, part: record.part, cell };
197
+ }
198
+ export const setTableCellHandler = {
199
+ async validate(operation, context, issue) {
200
+ const problem = textProblem(normalizeText(operation.text));
201
+ if (problem)
202
+ issue("/text", "invalid-text", `The text holds ${problem}`);
203
+ await cellTarget(operation, context, issue);
204
+ },
205
+ async apply(operation, context) {
206
+ const { record, part, cell } = (await cellTarget(operation, context, () => { }));
207
+ const txBody = cell.children.find((child) => child.local === "txBody");
208
+ const transaction = context.pkg.transaction();
209
+ if (txBody) {
210
+ const model = readTextModel(part, txBody);
211
+ transaction.patch(part, [
212
+ patches.replaceContent(part, txBody, replacedBodyContent(part, txBody, model, normalizeText(operation.text), 0, model.text.length, true)),
213
+ ]);
214
+ }
215
+ else {
216
+ const xml = `<a:txBody><a:bodyPr/><a:lstStyle/>${paragraphsXml(part, normalizeText(operation.text), undefined)}</a:txBody>`;
217
+ const tcPr = cell.children.find((child) => child.local === "tcPr");
218
+ transaction.patch(part, [
219
+ tcPr
220
+ ? patches.insertBefore(part, tcPr, xml)
221
+ : patches.appendChild(part, cell, xml),
222
+ ]);
223
+ }
224
+ const change = await transaction.commit();
225
+ const result = {
226
+ createdIds: [],
227
+ changedPages: [record.element.pageIndex],
228
+ warnings: change.warnings,
229
+ parts: committedParts(change),
230
+ };
231
+ return result;
232
+ },
233
+ };
@@ -0,0 +1,69 @@
1
+ import type { OoxmlPackage } from "../ooxml/package.js";
2
+ import type { XmlElement, XmlPart } from "../ooxml/xml.js";
3
+ export declare const PRESENTATION_NS = "http://schemas.openxmlformats.org/presentationml/2006/main";
4
+ export declare const RELATIONSHIP_TYPES: {
5
+ readonly officeDocument: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
6
+ readonly slide: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide";
7
+ readonly slideLayout: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout";
8
+ readonly slideMaster: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster";
9
+ readonly notesSlide: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide";
10
+ readonly theme: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme";
11
+ readonly image: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
12
+ };
13
+ export interface MasterRecord {
14
+ /** "master1", from the part's number. */
15
+ readonly id: string;
16
+ readonly part: string;
17
+ readonly theme?: string;
18
+ readonly layouts: readonly string[];
19
+ }
20
+ export interface LayoutRecord {
21
+ /** "layout2", from the part's number. */
22
+ readonly id: string;
23
+ readonly part: string;
24
+ readonly name: string;
25
+ readonly type?: string;
26
+ readonly master: MasterRecord;
27
+ }
28
+ export interface SlideRecord {
29
+ /** "sld3", from the part's number; the slide key of element ids. */
30
+ readonly key: string;
31
+ readonly number: number;
32
+ readonly part: string;
33
+ /** `p:sldId/@id`. */
34
+ readonly id: string;
35
+ /** The presentation relationship that names the part. */
36
+ readonly rId: string;
37
+ readonly node: XmlElement;
38
+ readonly layout?: LayoutRecord;
39
+ }
40
+ export interface ThemeFonts {
41
+ readonly major: string;
42
+ readonly minor: string;
43
+ }
44
+ /** The number in a part name like `/ppt/slides/slide12.xml`. */
45
+ export declare function partNumber(part: string): number;
46
+ export declare class DeckModel {
47
+ #private;
48
+ readonly revision: number;
49
+ readonly presentationPart: string;
50
+ readonly presentation: XmlPart;
51
+ /** EMU. */
52
+ readonly slideSize: {
53
+ readonly cx: number;
54
+ readonly cy: number;
55
+ };
56
+ readonly slides: readonly SlideRecord[];
57
+ readonly layouts: readonly LayoutRecord[];
58
+ readonly masters: readonly MasterRecord[];
59
+ private constructor();
60
+ static load(pkg: OoxmlPackage, signal?: AbortSignal): Promise<DeckModel>;
61
+ get pageCount(): number;
62
+ slideAt(pageIndex: number): SlideRecord;
63
+ slideByKey(key: string): SlideRecord | undefined;
64
+ layoutById(id: string): LayoutRecord | undefined;
65
+ /** Whether a slide is hidden (`p:sld/@show="0"`). */
66
+ hidden(slide: SlideRecord, signal?: AbortSignal): Promise<boolean>;
67
+ /** The major and minor Latin faces of a master's theme. */
68
+ themeFonts(master: MasterRecord | undefined): Promise<ThemeFonts>;
69
+ }