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,466 @@
1
+ import { ViewerError } from "../../errors.js";
2
+ import { AssetStore } from "../assets.js";
3
+ import { invalidOperationError, parseReference } from "../operations.js";
4
+ import { OoxmlPackage } from "../ooxml/package.js";
5
+ import { readPlaceholders, readSlideElements, } from "./elements.js";
6
+ import { frameContains, rectsIntersect } from "./geometry.js";
7
+ import { DeckModel } from "./model.js";
8
+ import { pptxHandlers } from "./handlers.js";
9
+ import { issueCollector, } from "./operations.js";
10
+ import { pptxOperationSchemas } from "./schemas.js";
11
+ const SLIDE_PART = /^\/ppt\/slides\/slide(\d+)\.xml$/i;
12
+ const SLIDE_RELS = /^\/ppt\/slides\/_rels\/slide(\d+)\.xml\.rels$/i;
13
+ export class PptxEditEngine {
14
+ schemas = pptxOperationSchemas;
15
+ #original;
16
+ #limits;
17
+ #assets = new AssetStore();
18
+ #pkg;
19
+ #inspection;
20
+ #disposed = false;
21
+ constructor(original, pkg, limits) {
22
+ this.#original = original;
23
+ this.#pkg = pkg;
24
+ this.#limits = limits;
25
+ }
26
+ /** Opens the package and reads the deck index, so a broken deck fails here. */
27
+ static async open(bytes, limits, signal) {
28
+ const pkg = await OoxmlPackage.open(bytes, {
29
+ limits,
30
+ ...(signal ? { signal } : {}),
31
+ });
32
+ const engine = new PptxEditEngine(bytes, pkg, limits);
33
+ await engine.model(signal);
34
+ return engine;
35
+ }
36
+ /** The package behind the engine, for tests and the operations. */
37
+ get package() {
38
+ return this.#pkg;
39
+ }
40
+ get pageCount() {
41
+ return this.#modelSync?.pageCount ?? 0;
42
+ }
43
+ #modelSync;
44
+ /** The deck index at the current revision. */
45
+ async model(signal) {
46
+ return (await this.#inspect(signal)).model;
47
+ }
48
+ #inspect(signal) {
49
+ this.#assertAlive();
50
+ const revision = this.#pkg.revision;
51
+ if (!this.#inspection)
52
+ return this.#startInspection(revision, signal);
53
+ return this.#inspection.then((current) => current.revision === revision
54
+ ? current
55
+ : this.#startInspection(revision, signal));
56
+ }
57
+ /** Builds the index; a failed or aborted build is not kept. */
58
+ #startInspection(revision, signal) {
59
+ const pending = this.#buildInspection(revision, signal);
60
+ this.#inspection = pending;
61
+ pending.catch(() => {
62
+ if (this.#inspection === pending)
63
+ this.#inspection = undefined;
64
+ });
65
+ return pending;
66
+ }
67
+ /**
68
+ * After a commit: a change confined to slide parts keeps the deck index
69
+ * (the presentation, every layout and master, every slide's
70
+ * relationships); anything else — the presentation, parts added or
71
+ * removed other than media — rebuilds it on the next read.
72
+ */
73
+ async #afterCommit(parts) {
74
+ const current = this.#inspection
75
+ ? await this.#inspection.catch(() => undefined)
76
+ : undefined;
77
+ if (!parts || !current) {
78
+ this.#inspection = undefined;
79
+ return;
80
+ }
81
+ for (const name of parts.changed)
82
+ if (!SLIDE_PART.test(name) && !SLIDE_RELS.test(name)) {
83
+ this.#inspection = undefined;
84
+ return;
85
+ }
86
+ if (parts.added.length > 0 || parts.removed.length > 0) {
87
+ const structural = [...parts.added, ...parts.removed].some((name) => !/^\/ppt\/media\//i.test(name));
88
+ if (structural) {
89
+ this.#inspection = undefined;
90
+ return;
91
+ }
92
+ }
93
+ // Every cached slide was scanned at the old revision; a patch on such a
94
+ // scan is refused, so the slides are read again on demand while the
95
+ // deck index (presentation, layouts, masters) is kept.
96
+ current.slides.clear();
97
+ current.revision = this.#pkg.revision;
98
+ }
99
+ async #buildInspection(revision, signal) {
100
+ const model = await DeckModel.load(this.#pkg, signal);
101
+ this.#modelSync = model;
102
+ return { revision, model, placeholders: new Map(), slides: new Map() };
103
+ }
104
+ async validate(operations, signal) {
105
+ const issues = [];
106
+ const base = await this.#context(0, signal);
107
+ // Slide operations change the count the later operations see.
108
+ let pageCount = base.pageCount;
109
+ for (const [index, operation] of operations.entries()) {
110
+ const context = { ...base, pageCount, operationIndex: index };
111
+ switch (operation.op) {
112
+ case "insertSlide":
113
+ case "duplicateSlide":
114
+ pageCount += 1;
115
+ break;
116
+ case "deleteSlide":
117
+ pageCount = Math.max(1, pageCount - 1);
118
+ break;
119
+ default:
120
+ break;
121
+ }
122
+ const handler = pptxHandlers.get(operation.op);
123
+ if (!handler) {
124
+ issues.push({
125
+ operationIndex: index,
126
+ path: "/op",
127
+ code: "unknown-operation",
128
+ message: `Unknown operation ${operation.op}`,
129
+ });
130
+ continue;
131
+ }
132
+ // A same-batch reference names an element that does not exist yet:
133
+ // its target is checked when the batch is applied, the rest now.
134
+ const target = operation.target;
135
+ const reference = typeof target === "string" ? parseReference(target) : undefined;
136
+ if (reference !== undefined && reference >= index) {
137
+ issues.push({
138
+ operationIndex: index,
139
+ path: "/target",
140
+ code: "unknown-target",
141
+ message: `"${target}" must refer to an earlier operation`,
142
+ });
143
+ continue;
144
+ }
145
+ const collect = issueCollector(index, issues);
146
+ await handler.validate(operation, context, reference === undefined
147
+ ? collect
148
+ : (path, code, message) => {
149
+ if (!path.startsWith("/target") && !path.startsWith("/range"))
150
+ collect(path, code, message);
151
+ });
152
+ }
153
+ return issues;
154
+ }
155
+ /** Plain operation arrays, as the unit tests pass them, become the next batch. */
156
+ async apply(input, signal) {
157
+ const batch = Array.isArray(input)
158
+ ? { stateId: this.#nextStateId(), operations: input }
159
+ : input;
160
+ this.#stateId = Math.max(this.#stateId, batch.stateId);
161
+ const snapshot = this.#pkg.snapshot();
162
+ const issuedIds = new Map(this.#issuedIds);
163
+ const createdIds = [];
164
+ const removedIds = [];
165
+ const changedPages = new Set();
166
+ const warnings = [];
167
+ const createdByOperation = [];
168
+ try {
169
+ for (const [index, raw] of batch.operations.entries()) {
170
+ throwIfAborted(signal);
171
+ const operation = resolveReferences(raw, createdByOperation, index);
172
+ const handler = pptxHandlers.get(operation.op);
173
+ if (!handler)
174
+ throw new ViewerError("invalid-operation", `Unknown operation ${operation.op}`);
175
+ const context = await this.#context(batch.stateId, signal, index);
176
+ const issues = [];
177
+ await handler.validate(operation, context, issueCollector(index, issues));
178
+ if (issues.length > 0)
179
+ throw invalidOperationError(issues);
180
+ const result = await handler.apply(operation, context);
181
+ await this.#afterCommit(result.parts);
182
+ createdByOperation.push([...result.createdIds]);
183
+ createdIds.push(...result.createdIds);
184
+ removedIds.push(...(result.removedIds ?? []));
185
+ // A removed id is never issued again on its slide.
186
+ for (const id of result.removedIds ?? []) {
187
+ const match = /^(sld\d+):(\d+)/.exec(id);
188
+ if (!match)
189
+ continue;
190
+ const key = match[1];
191
+ this.#issuedIds.set(key, Math.max(this.#issuedIds.get(key) ?? 0, Number(match[2])));
192
+ }
193
+ for (const page of result.changedPages)
194
+ changedPages.add(page);
195
+ warnings.push(...result.warnings);
196
+ }
197
+ }
198
+ catch (error) {
199
+ // Everything the batch did, including the ids it issued, is undone.
200
+ this.#pkg.restore(snapshot);
201
+ this.#issuedIds = issuedIds;
202
+ this.#inspection = undefined;
203
+ this.#modelSync = undefined;
204
+ throw error;
205
+ }
206
+ this.#pkg.release(snapshot);
207
+ const model = await this.model(signal);
208
+ return {
209
+ createdIds,
210
+ removedIds,
211
+ changedPages: [...changedPages].sort((a, b) => a - b),
212
+ pageCount: model.pageCount,
213
+ warnings,
214
+ };
215
+ }
216
+ #stateId = 0;
217
+ /** Highest `p:cNvPr` id issued per slide key in this session; replays rebuild it. */
218
+ #issuedIds = new Map();
219
+ #nextStateId() {
220
+ this.#stateId += 1;
221
+ return this.#stateId;
222
+ }
223
+ async #context(stateId, signal, operationIndex = 0) {
224
+ const model = await this.model(signal);
225
+ return {
226
+ pkg: this.#pkg,
227
+ model,
228
+ limits: this.#limits,
229
+ assets: this.#assets,
230
+ pageCount: model.pageCount,
231
+ stateId,
232
+ operationIndex,
233
+ elements: (pageIndex) => this.#slideElements(pageIndex, signal),
234
+ locate: (id) => this.#locate(id, signal),
235
+ allocateShapeId: (elements) => this.#allocateShapeId(elements),
236
+ };
237
+ }
238
+ async materialize(purposeOrSignal = "show", options = {}, signal = new AbortController().signal) {
239
+ return (await this.materializeDocument(purposeOrSignal, options, signal))
240
+ .bytes;
241
+ }
242
+ async materializeDocument(purposeOrSignal = "show", _options = {}, signal = new AbortController().signal) {
243
+ this.#assertAlive();
244
+ const own = purposeOrSignal instanceof AbortSignal ? purposeOrSignal : signal;
245
+ return { bytes: await this.#pkg.save({}, own), warnings: [] };
246
+ }
247
+ async restore(input, signal) {
248
+ const target = Array.isArray(input)
249
+ ? {
250
+ batches: input.map((operations, index) => ({ stateId: index + 1, operations })),
251
+ }
252
+ : input;
253
+ this.#assertAlive();
254
+ this.#pkg = await OoxmlPackage.open(target.base ?? this.#original, {
255
+ limits: this.#limits,
256
+ signal,
257
+ });
258
+ this.#inspection = undefined;
259
+ this.#modelSync = undefined;
260
+ this.#issuedIds = new Map();
261
+ await this.model(signal);
262
+ for (const batch of target.batches)
263
+ await this.apply(batch, signal);
264
+ }
265
+ async putAsset(id, data, _signal) {
266
+ this.#assets.set(id, data);
267
+ }
268
+ async getElements(query, signal) {
269
+ const model = await this.model(signal);
270
+ const pages = query.pageIndex === undefined
271
+ ? model.slides.map((_, index) => index)
272
+ : [query.pageIndex];
273
+ const out = [];
274
+ for (const pageIndex of pages) {
275
+ if (pageIndex < 0 || pageIndex >= model.pageCount)
276
+ continue;
277
+ const elements = await this.#slideElements(pageIndex, signal);
278
+ for (const record of elements.records) {
279
+ const element = record.element;
280
+ if (query.kinds && !query.kinds.includes(element.kind))
281
+ continue;
282
+ if (query.intersects &&
283
+ !rectsIntersect(element.bounds, query.intersects))
284
+ continue;
285
+ out.push(element);
286
+ }
287
+ }
288
+ return out;
289
+ }
290
+ async getElement(id, signal) {
291
+ return (await this.#locate(id, signal))?.element;
292
+ }
293
+ async elementsAt(pageIndex, point, signal) {
294
+ const model = await this.model(signal);
295
+ if (pageIndex < 0 || pageIndex >= model.pageCount)
296
+ return [];
297
+ const elements = await this.#slideElements(pageIndex, signal);
298
+ const hits = [];
299
+ for (let index = elements.records.length - 1; index >= 0; index -= 1) {
300
+ const record = elements.records[index];
301
+ if (record.placed && frameContains(record.placed, point))
302
+ hits.push(record.element);
303
+ }
304
+ return hits;
305
+ }
306
+ async findText(query, options, signal) {
307
+ if (query.length === 0)
308
+ return [];
309
+ const model = await this.model(signal);
310
+ const [first, last] = options.pageRange ?? [0, model.pageCount - 1];
311
+ const fold = (value) => options.caseSensitive ? value : value.toLocaleLowerCase();
312
+ const needle = fold(query);
313
+ const targets = [];
314
+ const limit = options.maxResults ?? Number.POSITIVE_INFINITY;
315
+ for (let pageIndex = Math.max(0, first); pageIndex <= Math.min(last, model.pageCount - 1); pageIndex += 1) {
316
+ const elements = await this.#slideElements(pageIndex, signal);
317
+ for (const record of elements.records) {
318
+ const text = record.element.text;
319
+ if (!text)
320
+ continue;
321
+ const haystack = fold(text);
322
+ let from = 0;
323
+ while (targets.length < limit) {
324
+ const at = haystack.indexOf(needle, from);
325
+ if (at < 0)
326
+ break;
327
+ const end = at + query.length;
328
+ targets.push({
329
+ pageIndex,
330
+ text: text.slice(at, end),
331
+ rects: [record.element.bounds],
332
+ elementIds: [record.element.id],
333
+ ranges: [
334
+ {
335
+ start: { elementId: record.element.id, offset: at },
336
+ end: { elementId: record.element.id, offset: end },
337
+ },
338
+ ],
339
+ });
340
+ from = end;
341
+ }
342
+ if (targets.length >= limit)
343
+ return targets;
344
+ }
345
+ }
346
+ return targets;
347
+ }
348
+ async slides(signal) {
349
+ const model = await this.model(signal);
350
+ const out = [];
351
+ for (const [pageIndex, slide] of model.slides.entries())
352
+ out.push({
353
+ pageIndex,
354
+ key: slide.key,
355
+ layout: slide.layout?.id ?? "",
356
+ hidden: await model.hidden(slide, signal),
357
+ });
358
+ return out;
359
+ }
360
+ async layouts(signal) {
361
+ const model = await this.model(signal);
362
+ return model.layouts.map((layout) => ({
363
+ id: layout.id,
364
+ name: layout.name,
365
+ ...(layout.type ? { type: layout.type } : {}),
366
+ master: layout.master.id,
367
+ }));
368
+ }
369
+ async dispose() {
370
+ this.#disposed = true;
371
+ this.#inspection = undefined;
372
+ }
373
+ async #slideElements(pageIndex, signal) {
374
+ const inspection = await this.#inspect(signal);
375
+ const slide = inspection.model.slideAt(pageIndex);
376
+ let pending = inspection.slides.get(slide.key);
377
+ if (!pending) {
378
+ const started = this.#readSlide(inspection, slide, pageIndex, signal);
379
+ pending = started;
380
+ inspection.slides.set(slide.key, started);
381
+ // An aborted or failed read must not stand in for the slide.
382
+ started.catch(() => {
383
+ if (inspection.slides.get(slide.key) === started)
384
+ inspection.slides.delete(slide.key);
385
+ });
386
+ }
387
+ return pending;
388
+ }
389
+ /** One above every id on the slide and every id issued there this session. */
390
+ #allocateShapeId(elements) {
391
+ let max = 1;
392
+ for (const record of elements.records)
393
+ max = Math.max(max, record.cNvPrId);
394
+ const tree = elements.part.find("spTree");
395
+ if (tree)
396
+ for (const node of elements.part.findAll("cNvPr", tree)) {
397
+ const id = Number(elements.part.attribute(node, "id") ?? NaN);
398
+ if (Number.isInteger(id))
399
+ max = Math.max(max, id);
400
+ }
401
+ const issued = this.#issuedIds.get(elements.slide.key) ?? 0;
402
+ const next = Math.max(max, issued) + 1;
403
+ this.#issuedIds.set(elements.slide.key, next);
404
+ return next;
405
+ }
406
+ async #readSlide(inspection, slide, pageIndex, signal) {
407
+ const table = (part) => {
408
+ if (!part)
409
+ return undefined;
410
+ let pending = inspection.placeholders.get(part);
411
+ if (!pending) {
412
+ const started = readPlaceholders(this.#pkg, part, signal);
413
+ pending = started;
414
+ inspection.placeholders.set(part, started);
415
+ started.catch(() => {
416
+ if (inspection.placeholders.get(part) === started)
417
+ inspection.placeholders.delete(part);
418
+ });
419
+ }
420
+ return pending;
421
+ };
422
+ const layout = await table(slide.layout?.part);
423
+ const master = await table(slide.layout?.master.part);
424
+ const presentation = inspection.model.presentation;
425
+ const defaultsNode = presentation.find("defaultTextStyle");
426
+ const sources = {
427
+ ...(layout ? { layout } : {}),
428
+ ...(master ? { master } : {}),
429
+ ...(defaultsNode
430
+ ? { defaults: { part: presentation, node: defaultsNode } }
431
+ : {}),
432
+ };
433
+ return readSlideElements(this.#pkg, inspection.model, slide, pageIndex, sources, signal);
434
+ }
435
+ async #locate(id, signal) {
436
+ const key = id.split(":")[0];
437
+ if (!key)
438
+ return undefined;
439
+ const model = await this.model(signal);
440
+ const pageIndex = model.slides.findIndex((slide) => slide.key === key);
441
+ if (pageIndex < 0)
442
+ return undefined;
443
+ return (await this.#slideElements(pageIndex, signal)).byId(id);
444
+ }
445
+ #assertAlive() {
446
+ if (this.#disposed)
447
+ throw new ViewerError("lifecycle-error", "The PPTX engine was disposed");
448
+ }
449
+ }
450
+ /** Replaces `"$<n>"` targets with the first id operation `n` created. */
451
+ function resolveReferences(operation, created, index) {
452
+ const target = operation.target;
453
+ if (typeof target !== "string")
454
+ return operation;
455
+ const reference = parseReference(target);
456
+ if (reference === undefined)
457
+ return operation;
458
+ const id = reference < index ? created[reference]?.[0] : undefined;
459
+ if (!id)
460
+ throw new ViewerError("invalid-operation", `Operation ${index} refers to "${target}", which created nothing`, { details: { operationIndex: index, target } });
461
+ return { ...operation, target: id };
462
+ }
463
+ function throwIfAborted(signal) {
464
+ if (signal?.aborted)
465
+ throw new ViewerError("aborted", "The operation was aborted");
466
+ }
@@ -0,0 +1,72 @@
1
+ import type { PageRect } from "../types.js";
2
+ import type { ElementFrame } from "../types.js";
3
+ import type { XmlElement, XmlPart } from "../ooxml/xml.js";
4
+ export declare const EMU_PER_PX = 9525;
5
+ export declare const EMU_PER_PT = 12700;
6
+ export declare function emuToPx(emu: number): number;
7
+ export declare function pxToEmu(px: number): number;
8
+ /** An `a:xfrm` as the file stores it, in EMU and degrees. */
9
+ export interface Xfrm {
10
+ readonly x: number;
11
+ readonly y: number;
12
+ readonly cx: number;
13
+ readonly cy: number;
14
+ /** Clockwise degrees. */
15
+ readonly rotation: number;
16
+ readonly flipH: boolean;
17
+ readonly flipV: boolean;
18
+ /** Child space of a group. */
19
+ readonly child?: {
20
+ readonly x: number;
21
+ readonly y: number;
22
+ readonly cx: number;
23
+ readonly cy: number;
24
+ };
25
+ }
26
+ /** Reads `a:xfrm` (or `p:xfrm` of a graphic frame); undefined without an offset and an extent. */
27
+ export declare function readXfrm(part: XmlPart, node: XmlElement | undefined): Xfrm | undefined;
28
+ /** A 2D affine transform: x' = a·x + c·y + e, y' = b·x + d·y + f. */
29
+ export interface Matrix {
30
+ readonly a: number;
31
+ readonly b: number;
32
+ readonly c: number;
33
+ readonly d: number;
34
+ readonly e: number;
35
+ readonly f: number;
36
+ }
37
+ export declare const IDENTITY: Matrix;
38
+ export declare function multiply(m: Matrix, n: Matrix): Matrix;
39
+ export declare function translate(tx: number, ty: number): Matrix;
40
+ export declare function scale(sx: number, sy: number): Matrix;
41
+ export declare function apply(m: Matrix, point: {
42
+ readonly x: number;
43
+ readonly y: number;
44
+ }): {
45
+ x: number;
46
+ y: number;
47
+ };
48
+ export declare function invert(m: Matrix): Matrix;
49
+ /**
50
+ * The transform from a group's child space to its parent space: child
51
+ * coordinates are scaled from `chOff`/`chExt` into `off`/`ext`, then the
52
+ * group's rotation and flips apply about its centre.
53
+ */
54
+ export declare function groupMatrix(group: Xfrm): Matrix;
55
+ /** A frame in slide space (px) with its axis-aligned bounds. */
56
+ export interface PlacedFrame {
57
+ readonly frame: ElementFrame;
58
+ readonly bounds: PageRect;
59
+ }
60
+ /**
61
+ * Places a frame given in its own space (EMU) through the transform of the
62
+ * groups above it. The reported frame keeps the element's box scaled and
63
+ * positioned by the groups, with the rotations and flips combined; the
64
+ * bounds are the axis-aligned box of its four transformed corners.
65
+ */
66
+ export declare function placeFrame(frame: Xfrm, parents: Matrix): PlacedFrame;
67
+ /** Whether a slide-space point lies inside a placed frame, rotation and flips included. */
68
+ export declare function frameContains(placed: PlacedFrame, point: {
69
+ readonly x: number;
70
+ readonly y: number;
71
+ }): boolean;
72
+ export declare function rectsIntersect(a: PageRect, b: PageRect): boolean;