web-doc 0.6.2 → 0.8.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 (201) hide show
  1. package/THIRD_PARTY_NOTICES.md +33 -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 +53 -2
  14. package/dist/edit/ai/outline.d.ts +47 -0
  15. package/dist/edit/ai/outline.js +338 -0
  16. package/dist/edit/ai/targets.d.ts +16 -0
  17. package/dist/edit/ai/targets.js +309 -0
  18. package/dist/edit/ai/tools.d.ts +28 -0
  19. package/dist/edit/ai/tools.js +605 -0
  20. package/dist/edit/ai/types.d.ts +175 -0
  21. package/dist/edit/ai/types.js +1 -0
  22. package/dist/edit/assets.d.ts +23 -0
  23. package/dist/edit/assets.js +75 -0
  24. package/dist/edit/docx/elements.d.ts +6 -0
  25. package/dist/edit/docx/elements.js +90 -0
  26. package/dist/edit/docx/engine.d.ts +57 -0
  27. package/dist/edit/docx/engine.js +547 -0
  28. package/dist/edit/docx/handlers.d.ts +3 -0
  29. package/dist/edit/docx/handlers.js +15 -0
  30. package/dist/edit/docx/ids.d.ts +45 -0
  31. package/dist/edit/docx/ids.js +101 -0
  32. package/dist/edit/docx/model.d.ts +94 -0
  33. package/dist/edit/docx/model.js +350 -0
  34. package/dist/edit/docx/operations.d.ts +47 -0
  35. package/dist/edit/docx/operations.js +3 -0
  36. package/dist/edit/docx/provider.d.ts +16 -0
  37. package/dist/edit/docx/provider.js +37 -0
  38. package/dist/edit/docx/schemas.d.ts +6 -0
  39. package/dist/edit/docx/schemas.js +192 -0
  40. package/dist/edit/docx/session.d.ts +48 -0
  41. package/dist/edit/docx/session.js +467 -0
  42. package/dist/edit/docx/structure-ops.d.ts +6 -0
  43. package/dist/edit/docx/structure-ops.js +529 -0
  44. package/dist/edit/docx/style.d.ts +45 -0
  45. package/dist/edit/docx/style.js +375 -0
  46. package/dist/edit/docx/table-ops.d.ts +4 -0
  47. package/dist/edit/docx/table-ops.js +241 -0
  48. package/dist/edit/docx/text-ops.d.ts +40 -0
  49. package/dist/edit/docx/text-ops.js +491 -0
  50. package/dist/edit/docx/text.d.ts +34 -0
  51. package/dist/edit/docx/text.js +284 -0
  52. package/dist/edit/docx/tracked.d.ts +52 -0
  53. package/dist/edit/docx/tracked.js +347 -0
  54. package/dist/edit/docx/types.d.ts +206 -0
  55. package/dist/edit/docx/types.js +1 -0
  56. package/dist/edit/docx/write.d.ts +64 -0
  57. package/dist/edit/docx/write.js +375 -0
  58. package/dist/edit/engine.d.ts +128 -0
  59. package/dist/edit/engine.js +1 -0
  60. package/dist/edit/history.d.ts +77 -0
  61. package/dist/edit/history.js +123 -0
  62. package/dist/edit/ooxml/names.d.ts +20 -0
  63. package/dist/edit/ooxml/names.js +61 -0
  64. package/dist/edit/ooxml/opc.d.ts +50 -0
  65. package/dist/edit/ooxml/opc.js +150 -0
  66. package/dist/edit/ooxml/package.d.ts +82 -0
  67. package/dist/edit/ooxml/package.js +233 -0
  68. package/dist/edit/ooxml/patch.d.ts +51 -0
  69. package/dist/edit/ooxml/patch.js +250 -0
  70. package/dist/edit/ooxml/transaction.d.ts +39 -0
  71. package/dist/edit/ooxml/transaction.js +316 -0
  72. package/dist/edit/ooxml/worker.d.ts +8 -0
  73. package/dist/edit/ooxml/worker.js +12 -0
  74. package/dist/edit/ooxml/writer.d.ts +21 -0
  75. package/dist/edit/ooxml/writer.js +187 -0
  76. package/dist/edit/ooxml/xml.d.ts +74 -0
  77. package/dist/edit/ooxml/xml.js +451 -0
  78. package/dist/edit/ooxml/zip.d.ts +54 -0
  79. package/dist/edit/ooxml/zip.js +280 -0
  80. package/dist/edit/operations.d.ts +19 -0
  81. package/dist/edit/operations.js +137 -0
  82. package/dist/edit/pdf/engine/compact.d.ts +6 -0
  83. package/dist/edit/pdf/engine/compact.js +442 -0
  84. package/dist/edit/pdf/engine/document.d.ts +95 -0
  85. package/dist/edit/pdf/engine/document.js +868 -0
  86. package/dist/edit/pdf/engine/elements.d.ts +45 -0
  87. package/dist/edit/pdf/engine/elements.js +313 -0
  88. package/dist/edit/pdf/engine/existing-text.d.ts +4 -0
  89. package/dist/edit/pdf/engine/existing-text.js +424 -0
  90. package/dist/edit/pdf/engine/fonts.d.ts +78 -0
  91. package/dist/edit/pdf/engine/fonts.js +466 -0
  92. package/dist/edit/pdf/engine/geometry.d.ts +36 -0
  93. package/dist/edit/pdf/engine/geometry.js +97 -0
  94. package/dist/edit/pdf/engine/handler.d.ts +21 -0
  95. package/dist/edit/pdf/engine/handler.js +108 -0
  96. package/dist/edit/pdf/engine/images.d.ts +33 -0
  97. package/dist/edit/pdf/engine/images.js +188 -0
  98. package/dist/edit/pdf/engine/layout.d.ts +23 -0
  99. package/dist/edit/pdf/engine/layout.js +177 -0
  100. package/dist/edit/pdf/engine/operations.d.ts +67 -0
  101. package/dist/edit/pdf/engine/operations.js +3 -0
  102. package/dist/edit/pdf/engine/pages.d.ts +6 -0
  103. package/dist/edit/pdf/engine/pages.js +98 -0
  104. package/dist/edit/pdf/engine/pdfium.d.ts +195 -0
  105. package/dist/edit/pdf/engine/pdfium.js +249 -0
  106. package/dist/edit/pdf/engine/shapes.d.ts +4 -0
  107. package/dist/edit/pdf/engine/shapes.js +183 -0
  108. package/dist/edit/pdf/engine/tables.d.ts +44 -0
  109. package/dist/edit/pdf/engine/tables.js +315 -0
  110. package/dist/edit/pdf/engine/text-box.d.ts +71 -0
  111. package/dist/edit/pdf/engine/text-box.js +317 -0
  112. package/dist/edit/pdf/engine/text-layout.d.ts +28 -0
  113. package/dist/edit/pdf/engine/text-layout.js +67 -0
  114. package/dist/edit/pdf/engine/transform.d.ts +5 -0
  115. package/dist/edit/pdf/engine/transform.js +137 -0
  116. package/dist/edit/pdf/provider.d.ts +35 -0
  117. package/dist/edit/pdf/provider.js +92 -0
  118. package/dist/edit/pdf/range-map.d.ts +16 -0
  119. package/dist/edit/pdf/range-map.js +107 -0
  120. package/dist/edit/pdf/schemas.d.ts +11 -0
  121. package/dist/edit/pdf/schemas.js +291 -0
  122. package/dist/edit/pdf/selection.d.ts +5 -0
  123. package/dist/edit/pdf/selection.js +152 -0
  124. package/dist/edit/pdf/session.d.ts +63 -0
  125. package/dist/edit/pdf/session.js +312 -0
  126. package/dist/edit/pdf/types.d.ts +336 -0
  127. package/dist/edit/pdf/types.js +1 -0
  128. package/dist/edit/pptx/elements.d.ts +74 -0
  129. package/dist/edit/pptx/elements.js +301 -0
  130. package/dist/edit/pptx/engine.d.ts +37 -0
  131. package/dist/edit/pptx/engine.js +466 -0
  132. package/dist/edit/pptx/geometry.d.ts +72 -0
  133. package/dist/edit/pptx/geometry.js +193 -0
  134. package/dist/edit/pptx/handler.d.ts +7 -0
  135. package/dist/edit/pptx/handler.js +99 -0
  136. package/dist/edit/pptx/handlers.d.ts +3 -0
  137. package/dist/edit/pptx/handlers.js +21 -0
  138. package/dist/edit/pptx/image-table-ops.d.ts +5 -0
  139. package/dist/edit/pptx/image-table-ops.js +233 -0
  140. package/dist/edit/pptx/model.d.ts +69 -0
  141. package/dist/edit/pptx/model.js +170 -0
  142. package/dist/edit/pptx/operations.d.ts +50 -0
  143. package/dist/edit/pptx/operations.js +11 -0
  144. package/dist/edit/pptx/provider.d.ts +17 -0
  145. package/dist/edit/pptx/provider.js +40 -0
  146. package/dist/edit/pptx/schemas.d.ts +6 -0
  147. package/dist/edit/pptx/schemas.js +220 -0
  148. package/dist/edit/pptx/session.d.ts +54 -0
  149. package/dist/edit/pptx/session.js +145 -0
  150. package/dist/edit/pptx/shape-ops.d.ts +10 -0
  151. package/dist/edit/pptx/shape-ops.js +486 -0
  152. package/dist/edit/pptx/slide-ops.d.ts +6 -0
  153. package/dist/edit/pptx/slide-ops.js +353 -0
  154. package/dist/edit/pptx/style.d.ts +31 -0
  155. package/dist/edit/pptx/style.js +181 -0
  156. package/dist/edit/pptx/text-ops.d.ts +13 -0
  157. package/dist/edit/pptx/text-ops.js +267 -0
  158. package/dist/edit/pptx/text-write.d.ts +67 -0
  159. package/dist/edit/pptx/text-write.js +293 -0
  160. package/dist/edit/pptx/text.d.ts +41 -0
  161. package/dist/edit/pptx/text.js +99 -0
  162. package/dist/edit/pptx/types.d.ts +232 -0
  163. package/dist/edit/pptx/types.js +1 -0
  164. package/dist/edit/schema.d.ts +11 -0
  165. package/dist/edit/schema.js +275 -0
  166. package/dist/edit/session.d.ts +77 -0
  167. package/dist/edit/session.js +943 -0
  168. package/dist/edit/sessions.d.ts +8 -0
  169. package/dist/edit/sessions.js +1 -0
  170. package/dist/edit/types.d.ts +290 -0
  171. package/dist/edit/types.js +1 -0
  172. package/dist/edit/worker-engine.d.ts +31 -0
  173. package/dist/edit/worker-engine.js +91 -0
  174. package/dist/fonts/THIRD_PARTY_NOTICES.md +3 -0
  175. package/dist/fonts/manifest.json +6 -1
  176. package/dist/fonts/noto-sans-latin-cyrillic.ttf +0 -0
  177. package/dist/fonts.d.ts +2 -0
  178. package/dist/fonts.js +4 -0
  179. package/dist/fuzzy-alignment.d.ts +11 -4
  180. package/dist/fuzzy-alignment.js +3 -9
  181. package/dist/headless.d.ts +5 -1
  182. package/dist/headless.js +8 -1
  183. package/dist/index.d.ts +10 -2
  184. package/dist/index.js +16 -2
  185. package/dist/limits.js +6 -0
  186. package/dist/ooxml-edit-worker.d.ts +1 -0
  187. package/dist/ooxml-edit-worker.js +8 -0
  188. package/dist/pdf-edit-worker.d.ts +1 -0
  189. package/dist/pdf-edit-worker.js +35 -0
  190. package/dist/spreadsheet-viewport.d.ts +5 -0
  191. package/dist/spreadsheet-viewport.js +11 -0
  192. package/dist/ui.js +7 -0
  193. package/dist/viewer.d.ts +6 -0
  194. package/dist/viewer.js +293 -19
  195. package/dist/viewport.d.ts +13 -0
  196. package/dist/viewport.js +126 -1
  197. package/dist/worker-protocol.d.ts +39 -2
  198. package/dist/workers/fuzzy-search-worker.js +1 -1
  199. package/dist/workers/ooxml-edit-worker.js +9212 -0
  200. package/dist/workers/pdf-edit-worker.js +10847 -0
  201. package/package.json +3 -3
@@ -0,0 +1,467 @@
1
+ import { readDescription, readOutline } from "../ai/outline.js";
2
+ import { resolveTargets } from "../ai/targets.js";
3
+ import { buildToolSet, callTool as runTool } from "../ai/tools.js";
4
+ import { ViewerError } from "../../errors.js";
5
+ export class DocxSession {
6
+ format = "docx";
7
+ #core;
8
+ #tools;
9
+ #access;
10
+ constructor(core, access) {
11
+ this.#core = core;
12
+ this.#access = access;
13
+ }
14
+ get sessionId() {
15
+ return this.#core.sessionId;
16
+ }
17
+ get state() {
18
+ return this.#core.state;
19
+ }
20
+ get schemas() {
21
+ return this.#core.schemas;
22
+ }
23
+ apply(operations, options) {
24
+ return this.#core.apply(operations, options);
25
+ }
26
+ applyJson(operations, options) {
27
+ return this.#core.applyJson(operations, options);
28
+ }
29
+ undo(options) {
30
+ return this.#core.undo(options);
31
+ }
32
+ redo(options) {
33
+ return this.#core.redo(options);
34
+ }
35
+ reset(options) {
36
+ return this.#core.reset(options);
37
+ }
38
+ save(options) {
39
+ return this.#core.save(options);
40
+ }
41
+ markSaved(stateToken) {
42
+ this.#core.markSaved(stateToken);
43
+ }
44
+ addAsset(data, options) {
45
+ return this.#core.addAsset(data, options);
46
+ }
47
+ getElements(query = {}, options) {
48
+ return this.#core.readItems(options, async (engine, signal) => {
49
+ const elements = (await engine.getElements({}, signal));
50
+ const pages = query.pageIndex === undefined
51
+ ? this.#access.cachedPages()
52
+ : [query.pageIndex];
53
+ const placement = await this.#placement(pages, signal);
54
+ return placeElements(elements, placement).filter((element) => matchesQuery(element, query));
55
+ });
56
+ }
57
+ getElement(id, options) {
58
+ return this.#core.readItem(options, async (engine, signal) => {
59
+ // A table is placed through its cell paragraphs, so every element is
60
+ // read and the one asked for picked out of the joined list.
61
+ const elements = (await engine.getElements({}, signal));
62
+ if (!elements.some((element) => element.id === id))
63
+ return undefined;
64
+ const placement = await this.#placement(this.#access.cachedPages(), signal);
65
+ return placeElements(elements, placement).find((element) => element.id === id);
66
+ });
67
+ }
68
+ elementsAt(pageIndex, point, options) {
69
+ return this.#core.readItems(options, async (engine, signal) => {
70
+ const runs = await this.#access.getTextRuns(pageIndex, signal);
71
+ const hit = new Set();
72
+ for (const run of runs)
73
+ if (run.paragraphId && rectContains(runRect(run), point))
74
+ hit.add(`p:${run.paragraphId}`);
75
+ if (hit.size === 0)
76
+ return [];
77
+ const elements = (await engine.getElements({}, signal));
78
+ const placed = placeElements(elements, await this.#placement([pageIndex], signal));
79
+ const paragraphs = placed.filter((element) => hit.has(element.id));
80
+ const tables = new Set(paragraphs
81
+ .map((element) => element.parentId)
82
+ .filter((id) => id !== undefined));
83
+ return [
84
+ ...paragraphs,
85
+ ...placed.filter((element) => tables.has(element.id)),
86
+ ];
87
+ });
88
+ }
89
+ async findText(query, options = {}) {
90
+ // A page range bounds the matches by where they are placed, which
91
+ // only the geometry knows: the engine then searches without a limit
92
+ // and the limit applies to the matches on those pages.
93
+ const { pageRange } = options;
94
+ const engineOptions = { ...options };
95
+ if (pageRange)
96
+ delete engineOptions.maxResults;
97
+ const found = await this.#core.findText(query, engineOptions);
98
+ const placed = await this.#placeTargets(found.items, options);
99
+ const items = pageRange
100
+ ? placed
101
+ .filter((target) => target.pageIndex >= pageRange[0] &&
102
+ target.pageIndex <= pageRange[1])
103
+ .slice(0, options.maxResults ?? placed.length)
104
+ : placed;
105
+ return Object.freeze({ ...found, items });
106
+ }
107
+ getOutline(options) {
108
+ return readOutline(this, this.#core.limits, options);
109
+ }
110
+ describe(options) {
111
+ return readDescription(this, this.#core.limits, options);
112
+ }
113
+ resolveTargets(query, options) {
114
+ return resolveTargets(this, query, options);
115
+ }
116
+ createCheckpoint(label) {
117
+ return this.#core.createCheckpoint(label);
118
+ }
119
+ listCheckpoints() {
120
+ return this.#core.listCheckpoints();
121
+ }
122
+ restoreCheckpoint(id, options) {
123
+ return this.#core.restoreCheckpoint(id, options);
124
+ }
125
+ dropCheckpoint(id) {
126
+ this.#core.dropCheckpoint(id);
127
+ }
128
+ get tools() {
129
+ return (this.#tools ??= buildToolSet(this.format, this.schemas));
130
+ }
131
+ callTool(call, options) {
132
+ return runTool(this, this.tools, call, options);
133
+ }
134
+ getRevisions(elementId, options) {
135
+ return this.#core.readItems(options, (engine, signal) => docxReads(engine).revisions(elementId, signal));
136
+ }
137
+ replaceText(fields, options) {
138
+ return this.apply([{ op: "replaceText", ...fields }], options);
139
+ }
140
+ setTextStyle(fields, options) {
141
+ return this.apply([{ op: "setTextStyle", ...fields }], options);
142
+ }
143
+ setParagraphStyle(fields, options) {
144
+ return this.apply([{ op: "setParagraphStyle", ...fields }], options);
145
+ }
146
+ insertParagraph(fields, options) {
147
+ return this.apply([{ op: "insertParagraph", ...fields }], options);
148
+ }
149
+ deleteElement(fields, options) {
150
+ return this.apply([{ op: "deleteElement", ...fields }], options);
151
+ }
152
+ moveElement(fields, options) {
153
+ return this.apply([{ op: "moveElement", ...fields }], options);
154
+ }
155
+ insertTable(fields, options) {
156
+ return this.apply([{ op: "insertTable", ...fields }], options);
157
+ }
158
+ setTableCell(fields, options) {
159
+ return this.apply([{ op: "setTableCell", ...fields }], options);
160
+ }
161
+ insertImage(fields, options) {
162
+ return this.apply([{ op: "insertImage", ...fields }], options);
163
+ }
164
+ /** The union of every paragraph's runs on the given pages. */
165
+ async #placement(pages, signal) {
166
+ const placement = new Map();
167
+ for (const pageIndex of [...new Set(pages)].sort((a, b) => a - b)) {
168
+ const runs = await this.#access.getTextRuns(pageIndex, signal);
169
+ for (const run of runs) {
170
+ if (!run.paragraphId)
171
+ continue;
172
+ let pages = placement.get(run.paragraphId);
173
+ if (!pages) {
174
+ pages = new Map();
175
+ placement.set(run.paragraphId, pages);
176
+ }
177
+ const current = pages.get(pageIndex);
178
+ pages.set(pageIndex, current ? union(current, runRect(run)) : runRect(run));
179
+ }
180
+ }
181
+ return placement;
182
+ }
183
+ /**
184
+ * Rectangles and pages for text matches: the runs of the match's
185
+ * paragraph on the page that holds it, cut to the matched characters
186
+ * when the run text can be aligned with the paragraph text, else the
187
+ * paragraph's runs on its first page. Pages are read from the viewer's
188
+ * cache first, then from the requested range, then in order.
189
+ */
190
+ async #placeTargets(targets, options) {
191
+ if (targets.length === 0)
192
+ return targets;
193
+ const signal = options.signal;
194
+ const runsByPage = new Map();
195
+ const load = async (pageIndex) => {
196
+ let runs = runsByPage.get(pageIndex);
197
+ if (!runs) {
198
+ runs = await this.#access.getTextRuns(pageIndex, signal);
199
+ runsByPage.set(pageIndex, runs);
200
+ }
201
+ return runs;
202
+ };
203
+ const pageCount = this.#core.state.pageCount;
204
+ const cached = this.#access.cachedPages();
205
+ const scan = options.pageRange
206
+ ? range(options.pageRange[0], options.pageRange[1], pageCount)
207
+ : range(0, pageCount - 1, pageCount);
208
+ /** Pages that hold runs of a paragraph, found so far. */
209
+ const pagesOf = new Map();
210
+ const holds = async (pageIndex, paragraphId) => (await load(pageIndex)).some((run) => run.paragraphId === paragraphId);
211
+ // A paragraph's pages are contiguous: the first page found to hold
212
+ // it, among the cached pages and then the scanned ones, is grown
213
+ // in both directions until a page without it.
214
+ const locate = async (paragraphId) => {
215
+ const known = pagesOf.get(paragraphId);
216
+ if (known)
217
+ return known;
218
+ let seed;
219
+ for (const pageIndex of [...cached, ...scan]) {
220
+ if (await holds(pageIndex, paragraphId)) {
221
+ seed = pageIndex;
222
+ break;
223
+ }
224
+ }
225
+ const pages = [];
226
+ if (seed !== undefined) {
227
+ pages.push(seed);
228
+ for (let pageIndex = seed - 1; pageIndex >= 0 && (await holds(pageIndex, paragraphId)); pageIndex -= 1)
229
+ pages.unshift(pageIndex);
230
+ for (let pageIndex = seed + 1; pageIndex < pageCount && (await holds(pageIndex, paragraphId)); pageIndex += 1)
231
+ pages.push(pageIndex);
232
+ }
233
+ pagesOf.set(paragraphId, pages);
234
+ return pages;
235
+ };
236
+ const fold = (value) => options.caseSensitive ? value : value.toLocaleLowerCase();
237
+ const occurrences = new Map();
238
+ const out = [];
239
+ for (const target of targets) {
240
+ const elementId = target.elementIds[0];
241
+ const paragraphId = elementId?.startsWith("p:")
242
+ ? elementId.slice(2)
243
+ : undefined;
244
+ const pages = paragraphId ? await locate(paragraphId) : [];
245
+ if (!paragraphId || pages.length === 0) {
246
+ out.push(target);
247
+ continue;
248
+ }
249
+ const runs = [];
250
+ for (const pageIndex of pages)
251
+ for (const run of await load(pageIndex))
252
+ if (run.paragraphId === paragraphId)
253
+ runs.push({ run, pageIndex });
254
+ const key = `${paragraphId}\u0000${fold(target.text)}`;
255
+ const occurrence = occurrences.get(key) ?? 0;
256
+ occurrences.set(key, occurrence + 1);
257
+ const rects = matchRects(runs, fold(target.text), occurrence, fold);
258
+ const placed = rects.length > 0
259
+ ? rects
260
+ : runs
261
+ .filter((entry) => entry.pageIndex === pages[0])
262
+ .map((entry) => ({
263
+ pageIndex: entry.pageIndex,
264
+ rect: runRect(entry.run),
265
+ }));
266
+ out.push({
267
+ ...target,
268
+ pageIndex: placed[0]?.pageIndex ?? target.pageIndex,
269
+ rects: placed.map((entry) => entry.rect),
270
+ });
271
+ }
272
+ return out;
273
+ }
274
+ }
275
+ function range(first, last, pageCount) {
276
+ const out = [];
277
+ for (let index = Math.max(0, first); index <= Math.min(last, pageCount - 1); index += 1)
278
+ out.push(index);
279
+ return out;
280
+ }
281
+ /** The `occurrence`-th match of `needle` in the runs' text, cut to run boxes. */
282
+ function matchRects(runs, needle, occurrence, fold) {
283
+ if (needle.length === 0)
284
+ return [];
285
+ const text = fold(runs.map((entry) => entry.run.text).join(""));
286
+ let at = -1;
287
+ for (let found = 0; found <= occurrence; found += 1) {
288
+ at = text.indexOf(needle, at + 1);
289
+ if (at < 0)
290
+ return [];
291
+ }
292
+ const end = at + needle.length;
293
+ const out = [];
294
+ let offset = 0;
295
+ for (const { run, pageIndex } of runs) {
296
+ const runStart = offset;
297
+ const runEnd = offset + run.text.length;
298
+ offset = runEnd;
299
+ if (runEnd <= at || runStart >= end || run.text.length === 0)
300
+ continue;
301
+ const from = Math.max(at, runStart) - runStart;
302
+ const to = Math.min(end, runEnd) - runStart;
303
+ const box = runRect(run);
304
+ const scale = box.width / run.text.length;
305
+ out.push({
306
+ pageIndex,
307
+ rect: {
308
+ x: box.x + from * scale,
309
+ y: box.y,
310
+ width: (to - from) * scale,
311
+ height: box.height,
312
+ },
313
+ });
314
+ }
315
+ return out;
316
+ }
317
+ function runRect(run) {
318
+ return { x: run.x, y: run.y, width: run.width, height: run.height };
319
+ }
320
+ function union(a, b) {
321
+ const x = Math.min(a.x, b.x);
322
+ const y = Math.min(a.y, b.y);
323
+ return {
324
+ x,
325
+ y,
326
+ width: Math.max(a.x + a.width, b.x + b.width) - x,
327
+ height: Math.max(a.y + a.height, b.y + b.height) - y,
328
+ };
329
+ }
330
+ function rectContains(rect, point) {
331
+ return (point.x >= rect.x &&
332
+ point.x <= rect.x + rect.width &&
333
+ point.y >= rect.y &&
334
+ point.y <= rect.y + rect.height);
335
+ }
336
+ function rectsIntersect(a, b) {
337
+ return (a.x < b.x + b.width &&
338
+ b.x < a.x + a.width &&
339
+ a.y < b.y + b.height &&
340
+ b.y < a.y + a.height);
341
+ }
342
+ function fragmentsOf(pages) {
343
+ if (!pages)
344
+ return [];
345
+ return [...pages.entries()]
346
+ .sort((a, b) => a[0] - b[0])
347
+ .map(([pageIndex, bounds]) => ({ pageIndex, bounds }));
348
+ }
349
+ function withFragments(element, fragments) {
350
+ const first = fragments[0];
351
+ return {
352
+ ...element,
353
+ pageIndex: first ? first.pageIndex : -1,
354
+ bounds: first ? first.bounds : { x: 0, y: 0, width: 0, height: 0 },
355
+ fragments,
356
+ };
357
+ }
358
+ /** Characters a paragraph may hold without the renderer drawing a text run for it. */
359
+ const INVISIBLE = /^[\ufffc\s]*$/;
360
+ /**
361
+ * A paragraph that draws no run (empty, or holding only pictures) is
362
+ * placed by estimate right after the placed paragraph before it in the
363
+ * same body or cell, else right before the one after it: a picture
364
+ * paragraph takes its largest picture's extent, an empty one a line of
365
+ * its neighbour's height. Nothing is estimated for a paragraph with text,
366
+ * whose runs simply lie on a page not read yet.
367
+ */
368
+ function estimateRunless(elements, placed) {
369
+ const paragraphs = elements.filter((element) => element.kind === "paragraph");
370
+ const sizeOf = (paragraph) => {
371
+ const pictures = elements.filter((candidate) => candidate.kind === "image" &&
372
+ candidate.parentId === paragraph.id &&
373
+ candidate.imageSize);
374
+ if (pictures.length === 0)
375
+ return undefined;
376
+ return {
377
+ width: Math.max(...pictures.map((picture) => picture.imageSize.width)),
378
+ height: Math.max(...pictures.map((picture) => picture.imageSize.height)),
379
+ };
380
+ };
381
+ for (const [index, paragraph] of paragraphs.entries()) {
382
+ const current = placed.get(paragraph.id);
383
+ if (current.pageIndex >= 0 || !INVISIBLE.test(paragraph.text ?? ""))
384
+ continue;
385
+ const sibling = (step) => {
386
+ for (let at = index + step; at >= 0 && at < paragraphs.length; at += step) {
387
+ const other = paragraphs[at];
388
+ if (other.parentId !== paragraph.parentId)
389
+ continue;
390
+ const fragments = placed.get(other.id)?.fragments ?? [];
391
+ const fragment = step < 0 ? fragments.at(-1) : fragments[0];
392
+ if (fragment)
393
+ return fragment;
394
+ if (!INVISIBLE.test(other.text ?? ""))
395
+ return undefined;
396
+ }
397
+ return undefined;
398
+ };
399
+ const before = sibling(-1);
400
+ const after = before ? undefined : sibling(1);
401
+ const anchor = before ?? after;
402
+ if (!anchor)
403
+ continue;
404
+ const size = sizeOf(paragraph) ?? {
405
+ width: anchor.bounds.width,
406
+ height: anchor.bounds.height,
407
+ };
408
+ const bounds = before
409
+ ? {
410
+ x: before.bounds.x,
411
+ y: before.bounds.y + before.bounds.height,
412
+ ...size,
413
+ }
414
+ : {
415
+ x: after.bounds.x,
416
+ y: Math.max(0, after.bounds.y - size.height),
417
+ ...size,
418
+ };
419
+ placed.set(paragraph.id, withFragments(paragraph, [{ pageIndex: anchor.pageIndex, bounds }]));
420
+ }
421
+ }
422
+ /** Joins the engine's elements with the runs' placement. */
423
+ export function placeElements(elements, placement) {
424
+ const placed = new Map();
425
+ // Paragraphs first; tables and inline objects derive from them.
426
+ for (const element of elements)
427
+ if (element.kind === "paragraph")
428
+ placed.set(element.id, withFragments(element, fragmentsOf(placement.get(element.id.slice(2)))));
429
+ estimateRunless(elements, placed);
430
+ for (const element of elements) {
431
+ if (element.kind === "table") {
432
+ const pages = new Map();
433
+ for (const candidate of elements) {
434
+ if (candidate.kind !== "paragraph" || candidate.parentId !== element.id)
435
+ continue;
436
+ for (const fragment of placed.get(candidate.id)?.fragments ?? []) {
437
+ const current = pages.get(fragment.pageIndex);
438
+ pages.set(fragment.pageIndex, current ? union(current, fragment.bounds) : fragment.bounds);
439
+ }
440
+ }
441
+ placed.set(element.id, withFragments(element, fragmentsOf(pages)));
442
+ }
443
+ else if (element.kind !== "paragraph") {
444
+ const parent = element.parentId
445
+ ? placed.get(element.parentId)
446
+ : undefined;
447
+ placed.set(element.id, withFragments(element, parent?.fragments ?? []));
448
+ }
449
+ }
450
+ return elements.map((element) => placed.get(element.id) ?? element);
451
+ }
452
+ function matchesQuery(element, query) {
453
+ if (query.kinds && !query.kinds.includes(element.kind))
454
+ return false;
455
+ if (query.pageIndex === undefined)
456
+ return true;
457
+ const fragment = element.fragments?.find((candidate) => candidate.pageIndex === query.pageIndex);
458
+ if (!fragment)
459
+ return false;
460
+ return !query.intersects || rectsIntersect(fragment.bounds, query.intersects);
461
+ }
462
+ function docxReads(engine) {
463
+ const candidate = engine;
464
+ if (typeof candidate.revisions !== "function")
465
+ throw new ViewerError("edit-unsupported", "The engine does not provide the DOCX reads", { details: { format: "docx", reason: "no-reads" } });
466
+ return candidate;
467
+ }
@@ -0,0 +1,6 @@
1
+ import type { DocxOperationHandler } from "./operations.js";
2
+ import type { DocxDeleteElementOperation, DocxInsertImageOperation, DocxInsertParagraphOperation, DocxMoveElementOperation } from "./types.js";
3
+ export declare const insertParagraphHandler: DocxOperationHandler<DocxInsertParagraphOperation>;
4
+ export declare const deleteElementHandler: DocxOperationHandler<DocxDeleteElementOperation>;
5
+ export declare const moveElementHandler: DocxOperationHandler<DocxMoveElementOperation>;
6
+ export declare const insertImageHandler: DocxOperationHandler<DocxInsertImageOperation>;