web-doc 0.6.1 → 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 +59 -28
  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 -2
@@ -0,0 +1,8 @@
1
+ import type { DocxEditSession } from "./docx/types.js";
2
+ import type { PdfEditSession } from "./pdf/types.js";
3
+ import type { PptxEditSession } from "./pptx/types.js";
4
+ /**
5
+ * The session of the loaded document, discriminated by `format`. Format
6
+ * modules add their member as they ship.
7
+ */
8
+ export type EditSession = PdfEditSession | PptxEditSession | DocxEditSession;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,276 @@
1
+ import type { ViewerWarning } from "../contracts.js";
2
+ /** Formats an edit session can be started for. */
3
+ export type EditableFormat = "pdf" | "pptx" | "docx";
4
+ export interface EditOptions {
5
+ readonly signal?: AbortSignal;
6
+ /** Reserved: recorded with changes where a format keeps authorship (tracked changes). */
7
+ readonly author?: string;
8
+ }
9
+ /** A plain JSON object; each format defines its concrete operation union. */
10
+ export interface EditOperation {
11
+ readonly op: string;
12
+ }
13
+ /**
14
+ * Binary payload: bytes, a base64 string for pure-JSON transports, or an
15
+ * `asset:` reference returned by `addAsset()`.
16
+ */
17
+ export type BinaryData = Uint8Array | string;
18
+ /** A colour as a format accepts it; PDF takes the string form only. */
19
+ export type EditColor = ColorString | ThemeColor;
20
+ /** `#RRGGBB` or `#RRGGBBAA`, or "auto" where a format has automatic colours. */
21
+ export type ColorString = string;
22
+ /** A theme slot with optional modifiers, for OOXML; keeps the theme link. */
23
+ export interface ThemeColor {
24
+ readonly theme: string;
25
+ readonly mods?: Readonly<Record<string, number>>;
26
+ }
27
+ export type JsonSchema = Readonly<Record<string, unknown>>;
28
+ export interface OperationSchemaSet {
29
+ readonly format: EditableFormat;
30
+ /** Raised whenever an operation's shape changes incompatibly. */
31
+ readonly version: number;
32
+ /** One JSON Schema (draft 2020-12) per operation name. */
33
+ readonly operations: Readonly<Record<string, JsonSchema>>;
34
+ }
35
+ /**
36
+ * A point in page space: the units of `DocumentInfo.pageSizes` at zoom 1
37
+ * (points for PDF, CSS pixels for DOCX and PPTX), origin at the top-left of
38
+ * the page as displayed, `y` growing downwards, page rotation applied.
39
+ */
40
+ export interface PagePoint {
41
+ readonly x: number;
42
+ readonly y: number;
43
+ }
44
+ /** A rectangle in page space; see `PagePoint`. */
45
+ export interface PageRect {
46
+ readonly x: number;
47
+ readonly y: number;
48
+ readonly width: number;
49
+ readonly height: number;
50
+ }
51
+ export interface PageHit {
52
+ readonly pageIndex: number;
53
+ readonly point: PagePoint;
54
+ }
55
+ /** Rectangle in client (CSS pixel) coordinates, like `getBoundingClientRect()`. */
56
+ export interface ViewportRect {
57
+ readonly left: number;
58
+ readonly top: number;
59
+ readonly width: number;
60
+ readonly height: number;
61
+ }
62
+ export interface EditElement {
63
+ /** Opaque, stable for the lifetime of the session, never reused for another element. */
64
+ readonly id: string;
65
+ /** Format-specific kind, for example "text", "image", "shape", "table". */
66
+ readonly kind: string;
67
+ /** Page of the element, or of its first fragment. */
68
+ readonly pageIndex: number;
69
+ /** Axis-aligned bounds in page space, or those of the first fragment. */
70
+ readonly bounds: PageRect;
71
+ /** Clockwise rotation in degrees, when the element is rotated. */
72
+ readonly rotation?: number;
73
+ /** Untransformed frame for formats that keep one (OOXML shapes); `bounds` stays the AABB. */
74
+ readonly frame?: ElementFrame;
75
+ /** Every piece of an element that spans pages; absent when it has one. */
76
+ readonly fragments?: readonly ElementFragment[];
77
+ /** Where the element lives in a flow document; absent for the page body. */
78
+ readonly story?: ElementStory;
79
+ readonly text?: string;
80
+ readonly parentId?: string;
81
+ /** Names of the operations that accept this element as their target. */
82
+ readonly operations: readonly string[];
83
+ }
84
+ export interface ElementFragment {
85
+ readonly pageIndex: number;
86
+ readonly bounds: PageRect;
87
+ }
88
+ export interface ElementFrame extends PageRect {
89
+ readonly rotation: number;
90
+ readonly flipH: boolean;
91
+ readonly flipV: boolean;
92
+ }
93
+ export type ElementStory = {
94
+ readonly kind: "body";
95
+ } | {
96
+ readonly kind: "header" | "footer";
97
+ readonly scope: string;
98
+ } | {
99
+ readonly kind: "footnote" | "endnote" | "comment";
100
+ readonly id: string;
101
+ } | {
102
+ readonly kind: "notes" | "layout" | "master";
103
+ readonly id: string;
104
+ };
105
+ /**
106
+ * A place in an element's text, in UTF-16 code units of `EditElement.text`.
107
+ * A tab, a break, an inline image and a field each count as one placeholder
108
+ * character.
109
+ */
110
+ export interface TextPosition {
111
+ readonly elementId: string;
112
+ readonly offset: number;
113
+ }
114
+ /** Half-open; may span elements in reading order. */
115
+ export interface TextRange {
116
+ readonly start: TextPosition;
117
+ readonly end: TextPosition;
118
+ }
119
+ export interface ElementQuery {
120
+ readonly pageIndex?: number;
121
+ readonly kinds?: readonly string[];
122
+ /** Only elements with a fragment whose bounds intersect this rectangle; requires `pageIndex`. */
123
+ readonly intersects?: PageRect;
124
+ }
125
+ export interface ReadOptions {
126
+ readonly signal?: AbortSignal;
127
+ }
128
+ export interface EditFindOptions extends ReadOptions {
129
+ readonly caseSensitive?: boolean;
130
+ /** Inclusive 0-based page range. */
131
+ readonly pageRange?: readonly [number, number];
132
+ readonly maxResults?: number;
133
+ }
134
+ export interface TextTarget {
135
+ /** Page of the first rectangle. */
136
+ readonly pageIndex: number;
137
+ readonly text: string;
138
+ readonly rects: readonly PageRect[];
139
+ /** Elements that contain the matched text, in reading order. */
140
+ readonly elementIds: readonly string[];
141
+ /** The match as text ranges, one per element it touches, in reading order. */
142
+ readonly ranges: readonly TextRange[];
143
+ }
144
+ /** Every read says which state it describes. */
145
+ export interface ReadEnvelope {
146
+ readonly sessionId: string;
147
+ readonly revision: number;
148
+ }
149
+ export interface ReadResult<T> extends ReadEnvelope {
150
+ readonly items: readonly T[];
151
+ }
152
+ export interface ReadItem<T> extends ReadEnvelope {
153
+ readonly item: T | undefined;
154
+ }
155
+ export interface EditState {
156
+ /** Unique per session; stamped on receipts, read results and events. */
157
+ readonly sessionId: string;
158
+ /** Starts at 0 and grows by one with every applied change (apply, undo, redo, reset). */
159
+ readonly revision: number;
160
+ /** True while the content differs from the state last given to `markSaved()`, or from the original. */
161
+ readonly dirty: boolean;
162
+ readonly canUndo: boolean;
163
+ readonly canRedo: boolean;
164
+ readonly pageCount: number;
165
+ }
166
+ export interface ApplyOptions {
167
+ /** Rejects the call with `edit-conflict` unless `state.revision` equals this value. */
168
+ readonly expectedRevision?: number;
169
+ /** Rejects with `edit-conflict` unless it equals `sessionId`; for callers that outlive a session. */
170
+ readonly expectedSessionId?: string;
171
+ /** Validates and simulates the batch without changing anything. */
172
+ readonly dryRun?: boolean;
173
+ /** Free text stored with the history entry, for host UIs and audit logs. */
174
+ readonly label?: string;
175
+ /** Reserved: ISO 8601 time a format records with the change; never generated by web-doc. */
176
+ readonly timestamp?: string;
177
+ readonly signal?: AbortSignal;
178
+ }
179
+ export interface HistoryOptions {
180
+ readonly expectedRevision?: number;
181
+ readonly expectedSessionId?: string;
182
+ readonly signal?: AbortSignal;
183
+ }
184
+ export interface SaveOptions {
185
+ readonly signal?: AbortSignal;
186
+ }
187
+ /** What `save()` returns: the bytes and the token that names their state. */
188
+ export interface SavedDocument {
189
+ readonly bytes: Uint8Array;
190
+ /** Pass it to `markSaved()` once the bytes are persisted. */
191
+ readonly stateToken: string;
192
+ readonly sessionId: string;
193
+ readonly revision: number;
194
+ /**
195
+ * What the bytes do not guarantee: `privacy-not-guaranteed` when a PDF full
196
+ * save could not be compacted and deleted content may remain recoverable.
197
+ * Empty for a save that holds every guarantee.
198
+ */
199
+ readonly warnings: readonly ViewerWarning[];
200
+ }
201
+ export interface AssetOptions {
202
+ readonly mimeType?: string;
203
+ readonly signal?: AbortSignal;
204
+ }
205
+ export interface EditReceipt {
206
+ readonly sessionId: string;
207
+ /** `state.revision` after the call; unchanged for a dry run or a no-op. */
208
+ readonly revision: number;
209
+ readonly dryRun: boolean;
210
+ readonly operationCount: number;
211
+ /** Ids of the elements the batch created, in operation order. */
212
+ readonly createdIds: readonly string[];
213
+ /** Ids that no longer exist after the call, including every element of a deleted page. */
214
+ readonly removedIds: readonly string[];
215
+ /** Old id → new id, when a format has to rename an element; absent for formats that never do. */
216
+ readonly remappedIds?: Readonly<Record<string, string>>;
217
+ /** Page indexes in the resulting document whose content may have changed; a superset. */
218
+ readonly changedPages: readonly number[];
219
+ readonly pageCount: number;
220
+ readonly warnings: readonly ViewerWarning[];
221
+ }
222
+ /** One problem found while validating a batch. */
223
+ export interface OperationIssue {
224
+ readonly operationIndex: number;
225
+ /** JSON Pointer into the operation, for example "/style/color". */
226
+ readonly path: string;
227
+ /** Stable machine code, for example "required", "type", "range", "unknown-operation", "unknown-target". */
228
+ readonly code: string;
229
+ readonly message: string;
230
+ }
231
+ export interface EditSessionBase<TOperation extends EditOperation, TElement extends EditElement> {
232
+ readonly format: EditableFormat;
233
+ /** Unique per session; stamped on state, receipts, read results and events. */
234
+ readonly sessionId: string;
235
+ /** Frozen snapshot, replaced (never mutated) on every state change. */
236
+ readonly state: EditState;
237
+ /** JSON Schemas of every operation this session accepts. */
238
+ readonly schemas: OperationSchemaSet;
239
+ apply(operations: readonly TOperation[], options?: ApplyOptions): Promise<EditReceipt>;
240
+ /** `apply()` for callers that hold operations as plain JSON; identical behaviour. */
241
+ applyJson(operations: readonly EditOperation[], options?: ApplyOptions): Promise<EditReceipt>;
242
+ undo(options?: HistoryOptions): Promise<EditReceipt>;
243
+ redo(options?: HistoryOptions): Promise<EditReceipt>;
244
+ /** Drops every change and returns to the original bytes. Cannot be undone. */
245
+ reset(options?: HistoryOptions): Promise<EditReceipt>;
246
+ /** Bytes of the current state. Pure: changes neither the state nor `dirty`. */
247
+ save(options?: SaveOptions): Promise<SavedDocument>;
248
+ /** Tells the session the host has persisted the state named by `stateToken`. */
249
+ markSaved(stateToken: string): void;
250
+ /** Registers binary data once; returns an `asset:` reference usable in operations. */
251
+ addAsset(data: Uint8Array, options?: AssetOptions): Promise<string>;
252
+ getElements(query?: ElementQuery, options?: ReadOptions): Promise<ReadResult<TElement>>;
253
+ getElement(id: string, options?: ReadOptions): Promise<ReadItem<TElement>>;
254
+ elementsAt(pageIndex: number, point: PagePoint, options?: ReadOptions): Promise<ReadResult<TElement>>;
255
+ findText(query: string, options?: EditFindOptions): Promise<ReadResult<TextTarget>>;
256
+ }
257
+ export interface EditStateChange extends EditState {
258
+ readonly active: boolean;
259
+ readonly format?: EditableFormat;
260
+ }
261
+ export type DocumentChangeReason = "apply" | "undo" | "redo" | "reset";
262
+ export interface DocumentChange {
263
+ readonly sessionId: string;
264
+ readonly revision: number;
265
+ readonly reason: DocumentChangeReason;
266
+ /** May over-approximate; for flow formats every page from the first affected one. */
267
+ readonly changedPages: readonly number[];
268
+ readonly pageCount: number;
269
+ }
270
+ /** The viewport has laid out and painted the pages of `revision`; geometry helpers are current. */
271
+ export interface LayoutChange {
272
+ readonly sessionId: string;
273
+ readonly revision: number;
274
+ /** Pages painted since the previous `layoutchange`. */
275
+ readonly pages: readonly number[];
276
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ import type { WorkerRpcClient } from "../worker-client.js";
2
+ import type { EditWorkerOperation } from "../worker-protocol.js";
3
+ import type { EditEngine, EditEngineContext, EngineBatch, EngineChange, MaterializedDocument, MaterializeOptions, RestoreTarget } from "./engine.js";
4
+ import type { EditElement, EditFindOptions, EditOperation, ElementQuery, OperationIssue, OperationSchemaSet, PagePoint, TextTarget } from "./types.js";
5
+ export declare abstract class WorkerEngineClient implements EditEngine {
6
+ #private;
7
+ abstract readonly schemas: OperationSchemaSet;
8
+ protected readonly rpc: WorkerRpcClient;
9
+ protected readonly context: EditEngineContext;
10
+ constructor(rpc: WorkerRpcClient, context: EditEngineContext);
11
+ validate(operations: readonly EditOperation[], signal: AbortSignal): Promise<readonly OperationIssue[]>;
12
+ /** Plain operation arrays, as the unit tests pass them, become the next batch. */
13
+ apply(input: EngineBatch | readonly EditOperation[], signal: AbortSignal): Promise<EngineChange>;
14
+ /** A bare signal, as the unit tests pass it, asks for the shown form. */
15
+ materialize(purposeOrSignal?: "show" | "save" | AbortSignal, options?: MaterializeOptions, signal?: AbortSignal): Promise<Uint8Array>;
16
+ materializeDocument(purposeOrSignal?: "show" | "save" | AbortSignal, options?: MaterializeOptions, signal?: AbortSignal): Promise<MaterializedDocument>;
17
+ restore(input: RestoreTarget | readonly (readonly EditOperation[])[], signal: AbortSignal): Promise<void>;
18
+ putAsset(id: string, data: Uint8Array, signal: AbortSignal): Promise<void>;
19
+ getElements(query: ElementQuery, signal: AbortSignal): Promise<readonly EditElement[]>;
20
+ getElement(id: string, signal: AbortSignal): Promise<EditElement | undefined>;
21
+ elementsAt(pageIndex: number, point: PagePoint, signal: AbortSignal): Promise<readonly EditElement[]>;
22
+ findText(query: string, options: EditFindOptions, signal: AbortSignal): Promise<readonly TextTarget[]>;
23
+ dispose(): Promise<void>;
24
+ protected request<T>(operation: EditWorkerOperation, payload: unknown, signal: AbortSignal | undefined, transfer?: Transferable[]): Promise<T>;
25
+ }
26
+ /** How long disposal waits for the worker's answer before terminating it. */
27
+ export declare const DISPOSE_GRACE_MS = 1000;
28
+ /** `value` resolved against `base`, else the packaged fallback. */
29
+ export declare function resolveAssetUrl(value: string | URL | undefined, base: URL | undefined, fallback: URL): URL;
30
+ /** A URL next to this module; data-driven so bundlers leave the package-owned directory alone. */
31
+ export declare function packageRelativeUrl(path: string, from: string): URL;
@@ -0,0 +1,91 @@
1
+ /*
2
+ * The main-thread side of an edit engine that lives in a worker: every core
3
+ * engine call is one request over the worker RPC. Format clients extend it
4
+ * with how their worker starts and with their own reads.
5
+ */
6
+ export class WorkerEngineClient {
7
+ rpc;
8
+ context;
9
+ /** Highest state id seen, for batches passed as plain arrays. */
10
+ #stateId = 0;
11
+ constructor(rpc, context) {
12
+ this.rpc = rpc;
13
+ this.context = context;
14
+ }
15
+ validate(operations, signal) {
16
+ return this.request("edit-validate", { operations }, signal);
17
+ }
18
+ /** Plain operation arrays, as the unit tests pass them, become the next batch. */
19
+ apply(input, signal) {
20
+ const batch = Array.isArray(input)
21
+ ? { stateId: ++this.#stateId, operations: input }
22
+ : input;
23
+ this.#stateId = Math.max(this.#stateId, batch.stateId);
24
+ return this.request("edit-apply", { batch }, signal);
25
+ }
26
+ /** A bare signal, as the unit tests pass it, asks for the shown form. */
27
+ async materialize(purposeOrSignal = "show", options = {}, signal = new AbortController().signal) {
28
+ return (await this.materializeDocument(purposeOrSignal, options, signal))
29
+ .bytes;
30
+ }
31
+ async materializeDocument(purposeOrSignal = "show", options = {}, signal = new AbortController().signal) {
32
+ const purpose = purposeOrSignal instanceof AbortSignal ? "show" : purposeOrSignal;
33
+ const own = purposeOrSignal instanceof AbortSignal ? purposeOrSignal : signal;
34
+ const result = await this.request("edit-materialize", { purpose, options }, own);
35
+ return { bytes: new Uint8Array(result.data), warnings: result.warnings };
36
+ }
37
+ restore(input, signal) {
38
+ const target = Array.isArray(input)
39
+ ? {
40
+ batches: input.map((operations, index) => ({ stateId: index + 1, operations })),
41
+ }
42
+ : input;
43
+ const base = target.base?.slice().buffer;
44
+ return this.request("edit-restore", { batches: target.batches, ...(base ? { base } : {}) }, signal, base ? [base] : undefined);
45
+ }
46
+ putAsset(id, data, signal) {
47
+ const buffer = data.slice().buffer;
48
+ return this.request("edit-put-asset", { id, data: buffer }, signal, [
49
+ buffer,
50
+ ]);
51
+ }
52
+ getElements(query, signal) {
53
+ return this.request("edit-elements", { query }, signal);
54
+ }
55
+ getElement(id, signal) {
56
+ return this.request("edit-element", { id }, signal);
57
+ }
58
+ elementsAt(pageIndex, point, signal) {
59
+ return this.request("edit-elements-at", { pageIndex, point }, signal);
60
+ }
61
+ findText(query, options, signal) {
62
+ return this.request("edit-find-text", { query, options }, signal);
63
+ }
64
+ async dispose() {
65
+ // A healthy worker answers at once; a dead one never would, and nothing
66
+ // is lost by terminating it without an answer.
67
+ const request = this.request("edit-dispose", undefined, undefined).catch(() => undefined);
68
+ await Promise.race([
69
+ request,
70
+ new Promise((resolve) => setTimeout(resolve, DISPOSE_GRACE_MS)),
71
+ ]);
72
+ this.rpc.destroy();
73
+ }
74
+ request(operation, payload, signal, transfer) {
75
+ return this.rpc.request(operation, payload, {
76
+ ...(signal ? { signal } : {}),
77
+ ...(transfer ? { transfer } : {}),
78
+ timeoutMs: this.context.limits.maxOperationMs,
79
+ });
80
+ }
81
+ }
82
+ /** How long disposal waits for the worker's answer before terminating it. */
83
+ export const DISPOSE_GRACE_MS = 1000;
84
+ /** `value` resolved against `base`, else the packaged fallback. */
85
+ export function resolveAssetUrl(value, base, fallback) {
86
+ return value === undefined ? fallback : new URL(value, base);
87
+ }
88
+ /** A URL next to this module; data-driven so bundlers leave the package-owned directory alone. */
89
+ export function packageRelativeUrl(path, from) {
90
+ return new URL(path, from);
91
+ }
@@ -6,5 +6,8 @@ The WOFF2 files in this directory are modified/subset builds of Noto Sans and No
6
6
  - Noto Sans CJK sources: `notofonts/noto-cjk` commit `f8d157532fbfaeda587e826d4cd5b21a49186f7c`.
7
7
  - License: SIL Open Font License 1.1; see `OFL-1.1.txt`.
8
8
  - Modified font family used by the viewer: `Zrimo Noto`.
9
+ - `noto-sans-latin-cyrillic.ttf` is the same Latin/Cyrillic subset saved as an
10
+ uncompressed TrueType file; the PDF editor embeds it into documents whose
11
+ text the standard fonts cannot encode.
9
12
 
10
13
  No proprietary Microsoft font is included.
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "family": "Zrimo Noto",
4
4
  "generated": "2026-07-16",
5
- "tooling": "fonttools pyftsubset with all layout features, then woff2_compress",
5
+ "tooling": "fonttools pyftsubset with all layout features, then woff2_compress; the .ttf is the Latin/Cyrillic subset saved uncompressed with fonttools for embedding into PDFs",
6
6
  "license": "OFL-1.1",
7
7
  "sources": {
8
8
  "noto-fonts": "https://github.com/notofonts/noto-fonts/tree/ffebf8c1ee449e544955a7e813c54f9b73848eac",
@@ -78,6 +78,11 @@
78
78
  "file": "noto-sans-korean.woff2",
79
79
  "bytes": 1110144,
80
80
  "sha256": "077e4178230f7cc0e7bd9a5ccdb5ad24f89692bb64f5d421b2095a69b4b9243a"
81
+ },
82
+ {
83
+ "file": "noto-sans-latin-cyrillic.ttf",
84
+ "bytes": 218796,
85
+ "sha256": "6a23e5413988b4b523ceb4aa81a1de85dad21336868997f52eb6d42159345262"
81
86
  }
82
87
  ]
83
88
  }
package/dist/fonts.d.ts CHANGED
@@ -13,6 +13,8 @@ export interface FontManagerOptions {
13
13
  export declare class FontManager {
14
14
  #private;
15
15
  constructor(options: FontManagerOptions);
16
+ /** Fonts the host registered, as given. */
17
+ get registered(): readonly RegisteredFont[];
16
18
  ensureRuns(runs: readonly TextRun[], reportWarning: (warning: ViewerWarning) => void, signal?: AbortSignal): Promise<void>;
17
19
  destroy(): Promise<void>;
18
20
  }
package/dist/fonts.js CHANGED
@@ -20,6 +20,10 @@ export class FontManager {
20
20
  fonts: globalThis.document?.fonts,
21
21
  };
22
22
  }
23
+ /** Fonts the host registered, as given. */
24
+ get registered() {
25
+ return this.#registered;
26
+ }
23
27
  async ensureRuns(runs, reportWarning, signal) {
24
28
  // PDF.js owns embedded/subset/standard-font loading for both its canvas and
25
29
  // text layer. Resolving those generated families through the viewer's Noto
@@ -1,4 +1,8 @@
1
1
  export * from "./contracts.js";
2
+ export * from "./edit/types.js";
3
+ export * from "./edit/sessions.js";
4
+ export * from "./edit/pdf/types.js";
5
+ export * from "./edit/pptx/types.js";
2
6
  export * from "./client.js";
3
7
  export * from "./viewer.js";
4
8
  export * from "./detect.js";
package/dist/headless.js CHANGED
@@ -1,4 +1,8 @@
1
1
  export * from "./contracts.js";
2
+ export * from "./edit/types.js";
3
+ export * from "./edit/sessions.js";
4
+ export * from "./edit/pdf/types.js";
5
+ export * from "./edit/pptx/types.js";
2
6
  export * from "./client.js";
3
7
  export * from "./viewer.js";
4
8
  export * from "./detect.js";
package/dist/index.d.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  export * from "./contracts.js";
2
+ export * from "./edit/types.js";
3
+ export * from "./edit/sessions.js";
4
+ export * from "./edit/pdf/types.js";
5
+ export * from "./edit/pptx/types.js";
6
+ export * from "./edit/docx/types.js";
2
7
  export * from "./client.js";
3
8
  export * from "./detect.js";
4
9
  export * from "./errors.js";
package/dist/index.js CHANGED
@@ -1,4 +1,9 @@
1
1
  export * from "./contracts.js";
2
+ export * from "./edit/types.js";
3
+ export * from "./edit/sessions.js";
4
+ export * from "./edit/pdf/types.js";
5
+ export * from "./edit/pptx/types.js";
6
+ export * from "./edit/docx/types.js";
2
7
  export * from "./client.js";
3
8
  export * from "./detect.js";
4
9
  export * from "./errors.js";
package/dist/limits.js CHANGED
@@ -10,6 +10,9 @@ export const defaultResourceLimits = Object.freeze({
10
10
  maxDocumentUnits: 100_000,
11
11
  maxConcurrentRenders: 2,
12
12
  maxOperationMs: 30_000,
13
+ maxEditOperations: 500,
14
+ maxEditHistory: 200,
15
+ maxEditCheckpointBytes: 64 * 1024 * 1024,
13
16
  });
14
17
  export function resolveLimits(base = {}, override = {}) {
15
18
  const result = { ...defaultResourceLimits, ...base, ...override };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import { createOoxmlEditHandler } from "./edit/pptx/handler.js";
2
+ import { attachWorkerEndpoint } from "./worker-endpoint.js";
3
+ /*
4
+ * Module worker that owns one OOXML package for one edit session: the ZIP
5
+ * container, the scanned parts and the overlay of changes. It needs no
6
+ * WebAssembly; inflation uses the platform DecompressionStream.
7
+ */
8
+ attachWorkerEndpoint(self, createOoxmlEditHandler());
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,35 @@
1
+ import { createPdfEditHandler } from "./edit/pdf/engine/handler.js";
2
+ import { Pdfium } from "./edit/pdf/engine/pdfium.js";
3
+ import { attachWorkerEndpoint } from "./worker-endpoint.js";
4
+ /*
5
+ * Module worker that owns a PDFium instance for one edit session. The WASM is
6
+ * fetched from the URL the main thread resolved, so hosting the assets
7
+ * elsewhere (assetBaseUrl) needs no change here.
8
+ */
9
+ async function fetchBytes(url) {
10
+ const response = await fetch(url);
11
+ if (!response.ok)
12
+ throw new Error(`Fetching ${url} failed with HTTP ${response.status}`);
13
+ return new Uint8Array(await response.arrayBuffer());
14
+ }
15
+ attachWorkerEndpoint(self, createPdfEditHandler({
16
+ loadPdfium: async (wasmUrl) => Pdfium.load(await fetchBytes(wasmUrl)),
17
+ fetchBytes,
18
+ decodeImage: async (bytes, mimeType) => {
19
+ const bitmap = await createImageBitmap(new Blob([new Uint8Array(bytes)], { type: mimeType }));
20
+ try {
21
+ const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
22
+ const context = canvas.getContext("2d");
23
+ context.drawImage(bitmap, 0, 0);
24
+ const { data } = context.getImageData(0, 0, bitmap.width, bitmap.height);
25
+ return {
26
+ width: bitmap.width,
27
+ height: bitmap.height,
28
+ rgba: new Uint8Array(data.buffer),
29
+ };
30
+ }
31
+ finally {
32
+ bitmap.close();
33
+ }
34
+ },
35
+ }));
@@ -22,6 +22,11 @@ export declare class SpreadsheetViewport {
22
22
  #private;
23
23
  constructor(container: HTMLElement, host: ViewportHost);
24
24
  setDocument(info: DocumentInfo | undefined): void;
25
+ /** Sheets are not editable yet, so new content is treated as a new document. */
26
+ replaceDocument(info: DocumentInfo, _changedPages?: readonly number[], _revision?: number): void;
27
+ /** Sheet geometry is cell-addressed; page-space helpers do not apply. */
28
+ pageToClient(): undefined;
29
+ clientToPage(): undefined;
25
30
  update(): void;
26
31
  panBy(deltaX: number, deltaY: number): void;
27
32
  /** Sheet matches are cell-addressed; the sheet viewport scrolls per cell already. */
@@ -203,6 +203,17 @@ export class SpreadsheetViewport {
203
203
  this.#root.scrollTo({ left: 0, top: 0 });
204
204
  this.schedule();
205
205
  }
206
+ /** Sheets are not editable yet, so new content is treated as a new document. */
207
+ replaceDocument(info, _changedPages, _revision) {
208
+ this.setDocument(info);
209
+ }
210
+ /** Sheet geometry is cell-addressed; page-space helpers do not apply. */
211
+ pageToClient() {
212
+ return undefined;
213
+ }
214
+ clientToPage() {
215
+ return undefined;
216
+ }
206
217
  update() {
207
218
  if (!this.#info)
208
219
  return;
package/dist/ui.js CHANGED
@@ -120,6 +120,13 @@ export class BasicViewerUi {
120
120
  if (!this.#thumbnailPanel.hidden)
121
121
  void this.#renderThumbnails();
122
122
  this.#renderSheetTabs();
123
+ }), viewer.on("documentchange", () => {
124
+ // Edited content: counters, tabs and thumbnails follow the new document.
125
+ this.#info = viewer.getDocumentInfo();
126
+ this.#renderSheetTabs();
127
+ this.#syncState(viewer.state);
128
+ if (!this.#thumbnailPanel.hidden)
129
+ void this.#renderThumbnails();
123
130
  }));
124
131
  this.root.addEventListener("keydown", this.#onKeyDown);
125
132
  document.addEventListener("fullscreenchange", this.#onFullscreenChange);
package/dist/viewer.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import type { CellRange, CellSelection, DocumentInfo, DocumentSource, FitMode, HeadlessRenderOptions, OpenDocumentOptions, ResourceLimits, SearchOptions, SearchResult, SpreadsheetViewportRange, TextSelection, TextSelectionRange, ThumbnailRenderOptions, ViewerApi, ViewerEventListener, ViewerEventMap, ViewerFetch, ViewerLogger, ViewerOptions, ViewerState } from "./contracts.js";
2
+ import type { EditOptions, PageHit, PageRect, ViewportRect } from "./edit/types.js";
3
+ import type { EditSession } from "./edit/sessions.js";
2
4
  import type { AdapterRegistry } from "./registry.js";
3
5
  import type { FontManager } from "./fonts.js";
4
6
  import { RenderScheduler } from "./render-scheduler.js";
@@ -47,6 +49,10 @@ export declare class DocumentViewer implements ViewerApi {
47
49
  copySelection(): Promise<string>;
48
50
  getOriginalBytes(): Uint8Array | undefined;
49
51
  downloadOriginal(fileName?: string): Blob;
52
+ edit(options?: EditOptions): Promise<EditSession>;
53
+ getEditSession(): EditSession | undefined;
54
+ pageToClient(pageIndex: number, rect: PageRect): ViewportRect | undefined;
55
+ clientToPage(clientX: number, clientY: number): PageHit | undefined;
50
56
  on<K extends keyof ViewerEventMap>(type: K, listener: ViewerEventListener<K>): () => void;
51
57
  destroy(): Promise<void>;
52
58
  }