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,192 @@
1
+ /*
2
+ * JSON Schemas of the DOCX operations, shared by the worker (engine
3
+ * validation), the main thread (shape checks) and clients
4
+ * (`session.schemas`). Only keywords the in-repo validator implements may
5
+ * appear here. The exported set lists the operations that have an engine
6
+ * handler.
7
+ */
8
+ const HIGHLIGHTS = [
9
+ "yellow",
10
+ "green",
11
+ "cyan",
12
+ "magenta",
13
+ "blue",
14
+ "red",
15
+ "darkBlue",
16
+ "darkCyan",
17
+ "darkGreen",
18
+ "darkMagenta",
19
+ "darkRed",
20
+ "darkYellow",
21
+ "darkGray",
22
+ "lightGray",
23
+ "black",
24
+ "white",
25
+ ];
26
+ const definitions = {
27
+ target: { type: "string", minLength: 1, maxLength: 200 },
28
+ textPosition: {
29
+ type: "object",
30
+ required: ["elementId", "offset"],
31
+ additionalProperties: false,
32
+ properties: {
33
+ elementId: { $ref: "#/$defs/target" },
34
+ offset: { type: "integer", minimum: 0 },
35
+ },
36
+ },
37
+ textRange: {
38
+ type: "object",
39
+ required: ["start", "end"],
40
+ additionalProperties: false,
41
+ properties: {
42
+ start: { $ref: "#/$defs/textPosition" },
43
+ end: { $ref: "#/$defs/textPosition" },
44
+ },
45
+ },
46
+ text: { type: "string", maxLength: 100_000 },
47
+ rgb: { type: "string", pattern: "^#[0-9A-Fa-f]{6}$" },
48
+ themeColor: {
49
+ type: "object",
50
+ required: ["theme"],
51
+ additionalProperties: false,
52
+ properties: {
53
+ theme: { type: "string", minLength: 1, maxLength: 20 },
54
+ mods: { type: "object" },
55
+ },
56
+ },
57
+ color: {
58
+ oneOf: [
59
+ { $ref: "#/$defs/rgb" },
60
+ { $ref: "#/$defs/themeColor" },
61
+ { const: "auto" },
62
+ ],
63
+ },
64
+ textStyle: {
65
+ type: "object",
66
+ additionalProperties: false,
67
+ properties: {
68
+ fontFamily: { type: "string", minLength: 1, maxLength: 100 },
69
+ fontSize: { type: "number", minimum: 1, maximum: 400 },
70
+ bold: { type: "boolean" },
71
+ italic: { type: "boolean" },
72
+ underline: { type: "boolean" },
73
+ color: { $ref: "#/$defs/color" },
74
+ highlight: { enum: [...HIGHLIGHTS, "none"] },
75
+ },
76
+ },
77
+ paragraphStyle: {
78
+ type: "object",
79
+ additionalProperties: false,
80
+ properties: {
81
+ align: { enum: ["left", "center", "right", "justify"] },
82
+ spacing: {
83
+ type: "object",
84
+ additionalProperties: false,
85
+ properties: {
86
+ before: { type: "number", minimum: 0, maximum: 1584 },
87
+ after: { type: "number", minimum: 0, maximum: 1584 },
88
+ line: { type: "number", minimum: 0.06, maximum: 132 },
89
+ },
90
+ },
91
+ },
92
+ },
93
+ tableRows: {
94
+ type: "array",
95
+ minItems: 1,
96
+ maxItems: 100,
97
+ items: {
98
+ type: "array",
99
+ minItems: 1,
100
+ maxItems: 20,
101
+ items: { type: "string", maxLength: 10_000 },
102
+ },
103
+ },
104
+ index: { type: "integer", minimum: 0, maximum: 100_000 },
105
+ size: {
106
+ type: "object",
107
+ required: ["width", "height"],
108
+ additionalProperties: false,
109
+ properties: {
110
+ width: { type: "number", exclusiveMinimum: 0, maximum: 1584 },
111
+ height: { type: "number", exclusiveMinimum: 0, maximum: 1584 },
112
+ },
113
+ },
114
+ };
115
+ function operation(name, properties, required) {
116
+ return {
117
+ type: "object",
118
+ required: ["op", ...required],
119
+ additionalProperties: false,
120
+ properties: { op: { const: name }, ...properties },
121
+ $defs: definitions,
122
+ };
123
+ }
124
+ /** `before` or `after`: the element the operation places next to. */
125
+ const placement = {
126
+ before: { $ref: "#/$defs/target" },
127
+ after: { $ref: "#/$defs/target" },
128
+ };
129
+ const all = {
130
+ replaceText: operation("replaceText", {
131
+ target: { $ref: "#/$defs/target" },
132
+ text: { $ref: "#/$defs/text" },
133
+ range: { $ref: "#/$defs/textRange" },
134
+ }, ["target", "text"]),
135
+ setTextStyle: operation("setTextStyle", {
136
+ target: { $ref: "#/$defs/target" },
137
+ range: { $ref: "#/$defs/textRange" },
138
+ style: { $ref: "#/$defs/textStyle" },
139
+ }, ["target", "style"]),
140
+ setParagraphStyle: operation("setParagraphStyle", {
141
+ target: { $ref: "#/$defs/target" },
142
+ style: { $ref: "#/$defs/paragraphStyle" },
143
+ }, ["target", "style"]),
144
+ insertParagraph: operation("insertParagraph", {
145
+ ...placement,
146
+ text: { $ref: "#/$defs/text" },
147
+ style: { $ref: "#/$defs/textStyle" },
148
+ }, ["text"]),
149
+ deleteElement: operation("deleteElement", { target: { $ref: "#/$defs/target" } }, ["target"]),
150
+ moveElement: operation("moveElement", { target: { $ref: "#/$defs/target" }, ...placement }, ["target"]),
151
+ insertTable: operation("insertTable", {
152
+ ...placement,
153
+ rows: { $ref: "#/$defs/tableRows" },
154
+ columnWidths: {
155
+ type: "array",
156
+ minItems: 1,
157
+ maxItems: 20,
158
+ items: { type: "number", exclusiveMinimum: 0 },
159
+ },
160
+ }, ["rows"]),
161
+ setTableCell: operation("setTableCell", {
162
+ target: { $ref: "#/$defs/target" },
163
+ row: { $ref: "#/$defs/index" },
164
+ column: { $ref: "#/$defs/index" },
165
+ text: { $ref: "#/$defs/text" },
166
+ }, ["target", "row", "column", "text"]),
167
+ insertImage: operation("insertImage", {
168
+ ...placement,
169
+ data: { "x-binary": true, type: "string", contentEncoding: "base64" },
170
+ mimeType: { enum: ["image/png", "image/jpeg"] },
171
+ size: { $ref: "#/$defs/size" },
172
+ }, ["data", "mimeType", "size"]),
173
+ };
174
+ /** Operations with an engine handler; grows as the tasks land. */
175
+ export const IMPLEMENTED_OPERATIONS = [
176
+ "replaceText",
177
+ "setTextStyle",
178
+ "setParagraphStyle",
179
+ "insertParagraph",
180
+ "deleteElement",
181
+ "moveElement",
182
+ "insertImage",
183
+ "insertTable",
184
+ "setTableCell",
185
+ ];
186
+ export const docxOperationSchemas = Object.freeze({
187
+ format: "docx",
188
+ version: 1,
189
+ operations: Object.freeze(Object.fromEntries(IMPLEMENTED_OPERATIONS.map((name) => [name, all[name]]))),
190
+ });
191
+ /** Every schema, implemented or not, for the conformance test. */
192
+ export const docxOperationSchemaDrafts = all;
@@ -0,0 +1,37 @@
1
+ import type { EditSessionAccess, EditSessionCore } from "../engine.js";
2
+ import type { ApplyOptions, AssetOptions, EditFindOptions, EditOperation, EditReceipt, EditState, ElementQuery, HistoryOptions, OperationSchemaSet, PagePoint, PageRect, ReadItem, ReadOptions, ReadResult, SavedDocument, TextTarget } from "../types.js";
3
+ import type { DocxDeleteElementOperation, DocxEditSession, DocxElement, DocxFields, DocxInsertImageOperation, DocxInsertParagraphOperation, DocxInsertTableOperation, DocxMoveElementOperation, DocxOperation, DocxReplaceTextOperation, DocxSaveOptions, DocxSetParagraphStyleOperation, DocxSetTableCellOperation, DocxSetTextStyleOperation } from "./types.js";
4
+ /** Per paragraph id (eight hex digits), the union of its runs per page. */
5
+ type Placement = ReadonlyMap<string, ReadonlyMap<number, PageRect>>;
6
+ export declare class DocxSession implements DocxEditSession {
7
+ #private;
8
+ readonly format: "docx";
9
+ constructor(core: EditSessionCore, access: EditSessionAccess);
10
+ get sessionId(): string;
11
+ get state(): EditState;
12
+ get schemas(): OperationSchemaSet;
13
+ apply(operations: readonly DocxOperation[], options?: ApplyOptions): Promise<EditReceipt>;
14
+ applyJson(operations: readonly EditOperation[], options?: ApplyOptions): Promise<EditReceipt>;
15
+ undo(options?: HistoryOptions): Promise<EditReceipt>;
16
+ redo(options?: HistoryOptions): Promise<EditReceipt>;
17
+ reset(options?: HistoryOptions): Promise<EditReceipt>;
18
+ save(options?: DocxSaveOptions): Promise<SavedDocument>;
19
+ markSaved(stateToken: string): void;
20
+ addAsset(data: Uint8Array, options?: AssetOptions): Promise<string>;
21
+ getElements(query?: ElementQuery, options?: ReadOptions): Promise<ReadResult<DocxElement>>;
22
+ getElement(id: string, options?: ReadOptions): Promise<ReadItem<DocxElement>>;
23
+ elementsAt(pageIndex: number, point: PagePoint, options?: ReadOptions): Promise<ReadResult<DocxElement>>;
24
+ findText(query: string, options?: EditFindOptions): Promise<ReadResult<TextTarget>>;
25
+ replaceText(fields: DocxFields<DocxReplaceTextOperation>, options?: ApplyOptions): Promise<EditReceipt>;
26
+ setTextStyle(fields: DocxFields<DocxSetTextStyleOperation>, options?: ApplyOptions): Promise<EditReceipt>;
27
+ setParagraphStyle(fields: DocxFields<DocxSetParagraphStyleOperation>, options?: ApplyOptions): Promise<EditReceipt>;
28
+ insertParagraph(fields: DocxFields<DocxInsertParagraphOperation>, options?: ApplyOptions): Promise<EditReceipt>;
29
+ deleteElement(fields: DocxFields<DocxDeleteElementOperation>, options?: ApplyOptions): Promise<EditReceipt>;
30
+ moveElement(fields: DocxFields<DocxMoveElementOperation>, options?: ApplyOptions): Promise<EditReceipt>;
31
+ insertTable(fields: DocxFields<DocxInsertTableOperation>, options?: ApplyOptions): Promise<EditReceipt>;
32
+ setTableCell(fields: DocxFields<DocxSetTableCellOperation>, options?: ApplyOptions): Promise<EditReceipt>;
33
+ insertImage(fields: DocxFields<DocxInsertImageOperation>, options?: ApplyOptions): Promise<EditReceipt>;
34
+ }
35
+ /** Joins the engine's elements with the runs' placement. */
36
+ export declare function placeElements(elements: readonly DocxElement[], placement: Placement): DocxElement[];
37
+ export {};
@@ -0,0 +1,426 @@
1
+ export class DocxSession {
2
+ format = "docx";
3
+ #core;
4
+ #access;
5
+ constructor(core, access) {
6
+ this.#core = core;
7
+ this.#access = access;
8
+ }
9
+ get sessionId() {
10
+ return this.#core.sessionId;
11
+ }
12
+ get state() {
13
+ return this.#core.state;
14
+ }
15
+ get schemas() {
16
+ return this.#core.schemas;
17
+ }
18
+ apply(operations, options) {
19
+ return this.#core.apply(operations, options);
20
+ }
21
+ applyJson(operations, options) {
22
+ return this.#core.applyJson(operations, options);
23
+ }
24
+ undo(options) {
25
+ return this.#core.undo(options);
26
+ }
27
+ redo(options) {
28
+ return this.#core.redo(options);
29
+ }
30
+ reset(options) {
31
+ return this.#core.reset(options);
32
+ }
33
+ save(options) {
34
+ return this.#core.save(options);
35
+ }
36
+ markSaved(stateToken) {
37
+ this.#core.markSaved(stateToken);
38
+ }
39
+ addAsset(data, options) {
40
+ return this.#core.addAsset(data, options);
41
+ }
42
+ getElements(query = {}, options) {
43
+ return this.#core.readItems(options, async (engine, signal) => {
44
+ const elements = (await engine.getElements({}, signal));
45
+ const pages = query.pageIndex === undefined
46
+ ? this.#access.cachedPages()
47
+ : [query.pageIndex];
48
+ const placement = await this.#placement(pages, signal);
49
+ return placeElements(elements, placement).filter((element) => matchesQuery(element, query));
50
+ });
51
+ }
52
+ getElement(id, options) {
53
+ return this.#core.readItem(options, async (engine, signal) => {
54
+ // A table is placed through its cell paragraphs, so every element is
55
+ // read and the one asked for picked out of the joined list.
56
+ const elements = (await engine.getElements({}, signal));
57
+ if (!elements.some((element) => element.id === id))
58
+ return undefined;
59
+ const placement = await this.#placement(this.#access.cachedPages(), signal);
60
+ return placeElements(elements, placement).find((element) => element.id === id);
61
+ });
62
+ }
63
+ elementsAt(pageIndex, point, options) {
64
+ return this.#core.readItems(options, async (engine, signal) => {
65
+ const runs = await this.#access.getTextRuns(pageIndex, signal);
66
+ const hit = new Set();
67
+ for (const run of runs)
68
+ if (run.paragraphId && rectContains(runRect(run), point))
69
+ hit.add(`p:${run.paragraphId}`);
70
+ if (hit.size === 0)
71
+ return [];
72
+ const elements = (await engine.getElements({}, signal));
73
+ const placed = placeElements(elements, await this.#placement([pageIndex], signal));
74
+ const paragraphs = placed.filter((element) => hit.has(element.id));
75
+ const tables = new Set(paragraphs
76
+ .map((element) => element.parentId)
77
+ .filter((id) => id !== undefined));
78
+ return [
79
+ ...paragraphs,
80
+ ...placed.filter((element) => tables.has(element.id)),
81
+ ];
82
+ });
83
+ }
84
+ async findText(query, options = {}) {
85
+ // A page range bounds the matches by where they are placed, which
86
+ // only the geometry knows: the engine then searches without a limit
87
+ // and the limit applies to the matches on those pages.
88
+ const { pageRange } = options;
89
+ const engineOptions = { ...options };
90
+ if (pageRange)
91
+ delete engineOptions.maxResults;
92
+ const found = await this.#core.findText(query, engineOptions);
93
+ const placed = await this.#placeTargets(found.items, options);
94
+ const items = pageRange
95
+ ? placed
96
+ .filter((target) => target.pageIndex >= pageRange[0] &&
97
+ target.pageIndex <= pageRange[1])
98
+ .slice(0, options.maxResults ?? placed.length)
99
+ : placed;
100
+ return Object.freeze({ ...found, items });
101
+ }
102
+ replaceText(fields, options) {
103
+ return this.apply([{ op: "replaceText", ...fields }], options);
104
+ }
105
+ setTextStyle(fields, options) {
106
+ return this.apply([{ op: "setTextStyle", ...fields }], options);
107
+ }
108
+ setParagraphStyle(fields, options) {
109
+ return this.apply([{ op: "setParagraphStyle", ...fields }], options);
110
+ }
111
+ insertParagraph(fields, options) {
112
+ return this.apply([{ op: "insertParagraph", ...fields }], options);
113
+ }
114
+ deleteElement(fields, options) {
115
+ return this.apply([{ op: "deleteElement", ...fields }], options);
116
+ }
117
+ moveElement(fields, options) {
118
+ return this.apply([{ op: "moveElement", ...fields }], options);
119
+ }
120
+ insertTable(fields, options) {
121
+ return this.apply([{ op: "insertTable", ...fields }], options);
122
+ }
123
+ setTableCell(fields, options) {
124
+ return this.apply([{ op: "setTableCell", ...fields }], options);
125
+ }
126
+ insertImage(fields, options) {
127
+ return this.apply([{ op: "insertImage", ...fields }], options);
128
+ }
129
+ /** The union of every paragraph's runs on the given pages. */
130
+ async #placement(pages, signal) {
131
+ const placement = new Map();
132
+ for (const pageIndex of [...new Set(pages)].sort((a, b) => a - b)) {
133
+ const runs = await this.#access.getTextRuns(pageIndex, signal);
134
+ for (const run of runs) {
135
+ if (!run.paragraphId)
136
+ continue;
137
+ let pages = placement.get(run.paragraphId);
138
+ if (!pages) {
139
+ pages = new Map();
140
+ placement.set(run.paragraphId, pages);
141
+ }
142
+ const current = pages.get(pageIndex);
143
+ pages.set(pageIndex, current ? union(current, runRect(run)) : runRect(run));
144
+ }
145
+ }
146
+ return placement;
147
+ }
148
+ /**
149
+ * Rectangles and pages for text matches: the runs of the match's
150
+ * paragraph on the page that holds it, cut to the matched characters
151
+ * when the run text can be aligned with the paragraph text, else the
152
+ * paragraph's runs on its first page. Pages are read from the viewer's
153
+ * cache first, then from the requested range, then in order.
154
+ */
155
+ async #placeTargets(targets, options) {
156
+ if (targets.length === 0)
157
+ return targets;
158
+ const signal = options.signal;
159
+ const runsByPage = new Map();
160
+ const load = async (pageIndex) => {
161
+ let runs = runsByPage.get(pageIndex);
162
+ if (!runs) {
163
+ runs = await this.#access.getTextRuns(pageIndex, signal);
164
+ runsByPage.set(pageIndex, runs);
165
+ }
166
+ return runs;
167
+ };
168
+ const pageCount = this.#core.state.pageCount;
169
+ const cached = this.#access.cachedPages();
170
+ const scan = options.pageRange
171
+ ? range(options.pageRange[0], options.pageRange[1], pageCount)
172
+ : range(0, pageCount - 1, pageCount);
173
+ /** Pages that hold runs of a paragraph, found so far. */
174
+ const pagesOf = new Map();
175
+ const holds = async (pageIndex, paragraphId) => (await load(pageIndex)).some((run) => run.paragraphId === paragraphId);
176
+ // A paragraph's pages are contiguous: the first page found to hold
177
+ // it, among the cached pages and then the scanned ones, is grown
178
+ // in both directions until a page without it.
179
+ const locate = async (paragraphId) => {
180
+ const known = pagesOf.get(paragraphId);
181
+ if (known)
182
+ return known;
183
+ let seed;
184
+ for (const pageIndex of [...cached, ...scan]) {
185
+ if (await holds(pageIndex, paragraphId)) {
186
+ seed = pageIndex;
187
+ break;
188
+ }
189
+ }
190
+ const pages = [];
191
+ if (seed !== undefined) {
192
+ pages.push(seed);
193
+ for (let pageIndex = seed - 1; pageIndex >= 0 && (await holds(pageIndex, paragraphId)); pageIndex -= 1)
194
+ pages.unshift(pageIndex);
195
+ for (let pageIndex = seed + 1; pageIndex < pageCount && (await holds(pageIndex, paragraphId)); pageIndex += 1)
196
+ pages.push(pageIndex);
197
+ }
198
+ pagesOf.set(paragraphId, pages);
199
+ return pages;
200
+ };
201
+ const fold = (value) => options.caseSensitive ? value : value.toLocaleLowerCase();
202
+ const occurrences = new Map();
203
+ const out = [];
204
+ for (const target of targets) {
205
+ const elementId = target.elementIds[0];
206
+ const paragraphId = elementId?.startsWith("p:")
207
+ ? elementId.slice(2)
208
+ : undefined;
209
+ const pages = paragraphId ? await locate(paragraphId) : [];
210
+ if (!paragraphId || pages.length === 0) {
211
+ out.push(target);
212
+ continue;
213
+ }
214
+ const runs = [];
215
+ for (const pageIndex of pages)
216
+ for (const run of await load(pageIndex))
217
+ if (run.paragraphId === paragraphId)
218
+ runs.push({ run, pageIndex });
219
+ const key = `${paragraphId}\u0000${fold(target.text)}`;
220
+ const occurrence = occurrences.get(key) ?? 0;
221
+ occurrences.set(key, occurrence + 1);
222
+ const rects = matchRects(runs, fold(target.text), occurrence, fold);
223
+ const placed = rects.length > 0
224
+ ? rects
225
+ : runs
226
+ .filter((entry) => entry.pageIndex === pages[0])
227
+ .map((entry) => ({
228
+ pageIndex: entry.pageIndex,
229
+ rect: runRect(entry.run),
230
+ }));
231
+ out.push({
232
+ ...target,
233
+ pageIndex: placed[0]?.pageIndex ?? target.pageIndex,
234
+ rects: placed.map((entry) => entry.rect),
235
+ });
236
+ }
237
+ return out;
238
+ }
239
+ }
240
+ function range(first, last, pageCount) {
241
+ const out = [];
242
+ for (let index = Math.max(0, first); index <= Math.min(last, pageCount - 1); index += 1)
243
+ out.push(index);
244
+ return out;
245
+ }
246
+ /** The `occurrence`-th match of `needle` in the runs' text, cut to run boxes. */
247
+ function matchRects(runs, needle, occurrence, fold) {
248
+ if (needle.length === 0)
249
+ return [];
250
+ const text = fold(runs.map((entry) => entry.run.text).join(""));
251
+ let at = -1;
252
+ for (let found = 0; found <= occurrence; found += 1) {
253
+ at = text.indexOf(needle, at + 1);
254
+ if (at < 0)
255
+ return [];
256
+ }
257
+ const end = at + needle.length;
258
+ const out = [];
259
+ let offset = 0;
260
+ for (const { run, pageIndex } of runs) {
261
+ const runStart = offset;
262
+ const runEnd = offset + run.text.length;
263
+ offset = runEnd;
264
+ if (runEnd <= at || runStart >= end || run.text.length === 0)
265
+ continue;
266
+ const from = Math.max(at, runStart) - runStart;
267
+ const to = Math.min(end, runEnd) - runStart;
268
+ const box = runRect(run);
269
+ const scale = box.width / run.text.length;
270
+ out.push({
271
+ pageIndex,
272
+ rect: {
273
+ x: box.x + from * scale,
274
+ y: box.y,
275
+ width: (to - from) * scale,
276
+ height: box.height,
277
+ },
278
+ });
279
+ }
280
+ return out;
281
+ }
282
+ function runRect(run) {
283
+ return { x: run.x, y: run.y, width: run.width, height: run.height };
284
+ }
285
+ function union(a, b) {
286
+ const x = Math.min(a.x, b.x);
287
+ const y = Math.min(a.y, b.y);
288
+ return {
289
+ x,
290
+ y,
291
+ width: Math.max(a.x + a.width, b.x + b.width) - x,
292
+ height: Math.max(a.y + a.height, b.y + b.height) - y,
293
+ };
294
+ }
295
+ function rectContains(rect, point) {
296
+ return (point.x >= rect.x &&
297
+ point.x <= rect.x + rect.width &&
298
+ point.y >= rect.y &&
299
+ point.y <= rect.y + rect.height);
300
+ }
301
+ function rectsIntersect(a, b) {
302
+ return (a.x < b.x + b.width &&
303
+ b.x < a.x + a.width &&
304
+ a.y < b.y + b.height &&
305
+ b.y < a.y + a.height);
306
+ }
307
+ function fragmentsOf(pages) {
308
+ if (!pages)
309
+ return [];
310
+ return [...pages.entries()]
311
+ .sort((a, b) => a[0] - b[0])
312
+ .map(([pageIndex, bounds]) => ({ pageIndex, bounds }));
313
+ }
314
+ function withFragments(element, fragments) {
315
+ const first = fragments[0];
316
+ return {
317
+ ...element,
318
+ pageIndex: first ? first.pageIndex : -1,
319
+ bounds: first ? first.bounds : { x: 0, y: 0, width: 0, height: 0 },
320
+ fragments,
321
+ };
322
+ }
323
+ /** Characters a paragraph may hold without the renderer drawing a text run for it. */
324
+ const INVISIBLE = /^[\ufffc\s]*$/;
325
+ /**
326
+ * A paragraph that draws no run (empty, or holding only pictures) is
327
+ * placed by estimate right after the placed paragraph before it in the
328
+ * same body or cell, else right before the one after it: a picture
329
+ * paragraph takes its largest picture's extent, an empty one a line of
330
+ * its neighbour's height. Nothing is estimated for a paragraph with text,
331
+ * whose runs simply lie on a page not read yet.
332
+ */
333
+ function estimateRunless(elements, placed) {
334
+ const paragraphs = elements.filter((element) => element.kind === "paragraph");
335
+ const sizeOf = (paragraph) => {
336
+ const pictures = elements.filter((candidate) => candidate.kind === "image" &&
337
+ candidate.parentId === paragraph.id &&
338
+ candidate.imageSize);
339
+ if (pictures.length === 0)
340
+ return undefined;
341
+ return {
342
+ width: Math.max(...pictures.map((picture) => picture.imageSize.width)),
343
+ height: Math.max(...pictures.map((picture) => picture.imageSize.height)),
344
+ };
345
+ };
346
+ for (const [index, paragraph] of paragraphs.entries()) {
347
+ const current = placed.get(paragraph.id);
348
+ if (current.pageIndex >= 0 || !INVISIBLE.test(paragraph.text ?? ""))
349
+ continue;
350
+ const sibling = (step) => {
351
+ for (let at = index + step; at >= 0 && at < paragraphs.length; at += step) {
352
+ const other = paragraphs[at];
353
+ if (other.parentId !== paragraph.parentId)
354
+ continue;
355
+ const fragments = placed.get(other.id)?.fragments ?? [];
356
+ const fragment = step < 0 ? fragments.at(-1) : fragments[0];
357
+ if (fragment)
358
+ return fragment;
359
+ if (!INVISIBLE.test(other.text ?? ""))
360
+ return undefined;
361
+ }
362
+ return undefined;
363
+ };
364
+ const before = sibling(-1);
365
+ const after = before ? undefined : sibling(1);
366
+ const anchor = before ?? after;
367
+ if (!anchor)
368
+ continue;
369
+ const size = sizeOf(paragraph) ?? {
370
+ width: anchor.bounds.width,
371
+ height: anchor.bounds.height,
372
+ };
373
+ const bounds = before
374
+ ? {
375
+ x: before.bounds.x,
376
+ y: before.bounds.y + before.bounds.height,
377
+ ...size,
378
+ }
379
+ : {
380
+ x: after.bounds.x,
381
+ y: Math.max(0, after.bounds.y - size.height),
382
+ ...size,
383
+ };
384
+ placed.set(paragraph.id, withFragments(paragraph, [{ pageIndex: anchor.pageIndex, bounds }]));
385
+ }
386
+ }
387
+ /** Joins the engine's elements with the runs' placement. */
388
+ export function placeElements(elements, placement) {
389
+ const placed = new Map();
390
+ // Paragraphs first; tables and inline objects derive from them.
391
+ for (const element of elements)
392
+ if (element.kind === "paragraph")
393
+ placed.set(element.id, withFragments(element, fragmentsOf(placement.get(element.id.slice(2)))));
394
+ estimateRunless(elements, placed);
395
+ for (const element of elements) {
396
+ if (element.kind === "table") {
397
+ const pages = new Map();
398
+ for (const candidate of elements) {
399
+ if (candidate.kind !== "paragraph" || candidate.parentId !== element.id)
400
+ continue;
401
+ for (const fragment of placed.get(candidate.id)?.fragments ?? []) {
402
+ const current = pages.get(fragment.pageIndex);
403
+ pages.set(fragment.pageIndex, current ? union(current, fragment.bounds) : fragment.bounds);
404
+ }
405
+ }
406
+ placed.set(element.id, withFragments(element, fragmentsOf(pages)));
407
+ }
408
+ else if (element.kind !== "paragraph") {
409
+ const parent = element.parentId
410
+ ? placed.get(element.parentId)
411
+ : undefined;
412
+ placed.set(element.id, withFragments(element, parent?.fragments ?? []));
413
+ }
414
+ }
415
+ return elements.map((element) => placed.get(element.id) ?? element);
416
+ }
417
+ function matchesQuery(element, query) {
418
+ if (query.kinds && !query.kinds.includes(element.kind))
419
+ return false;
420
+ if (query.pageIndex === undefined)
421
+ return true;
422
+ const fragment = element.fragments?.find((candidate) => candidate.pageIndex === query.pageIndex);
423
+ if (!fragment)
424
+ return false;
425
+ return !query.intersects || rectsIntersect(fragment.bounds, query.intersects);
426
+ }
@@ -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>;