@laminapdf/sdk 0.0.0-stage → 0.2.0-alpha.2

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 (139) hide show
  1. package/README.md +208 -2
  2. package/dist/api.d.ts +23 -0
  3. package/dist/api.d.ts.map +1 -0
  4. package/dist/api.js +11 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/builder.d.ts +319 -0
  7. package/dist/builder.d.ts.map +1 -0
  8. package/dist/builder.js +540 -0
  9. package/dist/builder.js.map +1 -0
  10. package/dist/defined.d.ts +5 -0
  11. package/dist/defined.d.ts.map +1 -0
  12. package/dist/defined.js +7 -0
  13. package/dist/defined.js.map +1 -0
  14. package/dist/document.d.ts +241 -0
  15. package/dist/document.d.ts.map +1 -0
  16. package/dist/document.js +427 -0
  17. package/dist/document.js.map +1 -0
  18. package/dist/entry/node.d.ts +30 -0
  19. package/dist/entry/node.d.ts.map +1 -0
  20. package/dist/entry/node.js +37 -0
  21. package/dist/entry/node.js.map +1 -0
  22. package/dist/entry/workerd.d.ts +30 -0
  23. package/dist/entry/workerd.d.ts.map +1 -0
  24. package/dist/entry/workerd.js +37 -0
  25. package/dist/entry/workerd.js.map +1 -0
  26. package/dist/errors.d.ts +102 -0
  27. package/dist/errors.d.ts.map +1 -0
  28. package/dist/errors.js +137 -0
  29. package/dist/errors.js.map +1 -0
  30. package/dist/explain.d.ts +20 -0
  31. package/dist/explain.d.ts.map +1 -0
  32. package/dist/explain.js +14 -0
  33. package/dist/explain.js.map +1 -0
  34. package/dist/font.d.ts +66 -0
  35. package/dist/font.d.ts.map +1 -0
  36. package/dist/font.js +103 -0
  37. package/dist/font.js.map +1 -0
  38. package/dist/generated/builder-enums.d.ts +29 -0
  39. package/dist/generated/builder-enums.d.ts.map +1 -0
  40. package/dist/generated/builder-enums.js +80 -0
  41. package/dist/generated/builder-enums.js.map +1 -0
  42. package/dist/generated/codes.d.ts +97 -0
  43. package/dist/generated/codes.d.ts.map +1 -0
  44. package/dist/generated/codes.js +453 -0
  45. package/dist/generated/codes.js.map +1 -0
  46. package/dist/generated/explain.d.ts +6 -0
  47. package/dist/generated/explain.d.ts.map +1 -0
  48. package/dist/generated/explain.js +138 -0
  49. package/dist/generated/explain.js.map +1 -0
  50. package/dist/index.d.ts +30 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +37 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/init.d.ts +80 -0
  55. package/dist/init.d.ts.map +1 -0
  56. package/dist/init.js +127 -0
  57. package/dist/init.js.map +1 -0
  58. package/dist/loader/fetch.d.ts +3 -0
  59. package/dist/loader/fetch.d.ts.map +1 -0
  60. package/dist/loader/fetch.js +2 -0
  61. package/dist/loader/fetch.js.map +1 -0
  62. package/dist/loader/node.d.ts +3 -0
  63. package/dist/loader/node.d.ts.map +1 -0
  64. package/dist/loader/node.js +9 -0
  65. package/dist/loader/node.js.map +1 -0
  66. package/dist/loader/workerd.d.ts +3 -0
  67. package/dist/loader/workerd.d.ts.map +1 -0
  68. package/dist/loader/workerd.js +15 -0
  69. package/dist/loader/workerd.js.map +1 -0
  70. package/dist/matrix.d.ts +16 -0
  71. package/dist/matrix.d.ts.map +1 -0
  72. package/dist/matrix.js +36 -0
  73. package/dist/matrix.js.map +1 -0
  74. package/dist/path.d.ts +34 -0
  75. package/dist/path.d.ts.map +1 -0
  76. package/dist/path.js +49 -0
  77. package/dist/path.js.map +1 -0
  78. package/dist/source/http.d.ts +36 -0
  79. package/dist/source/http.d.ts.map +1 -0
  80. package/dist/source/http.js +211 -0
  81. package/dist/source/http.js.map +1 -0
  82. package/dist/source/range.d.ts +70 -0
  83. package/dist/source/range.d.ts.map +1 -0
  84. package/dist/source/range.js +96 -0
  85. package/dist/source/range.js.map +1 -0
  86. package/dist/text.d.ts +90 -0
  87. package/dist/text.d.ts.map +1 -0
  88. package/dist/text.js +127 -0
  89. package/dist/text.js.map +1 -0
  90. package/dist/transaction.d.ts +36 -0
  91. package/dist/transaction.d.ts.map +1 -0
  92. package/dist/transaction.js +47 -0
  93. package/dist/transaction.js.map +1 -0
  94. package/dist/types.d.ts +79 -0
  95. package/dist/types.d.ts.map +1 -0
  96. package/dist/types.js +2 -0
  97. package/dist/types.js.map +1 -0
  98. package/dist/version.d.ts +3 -0
  99. package/dist/version.d.ts.map +1 -0
  100. package/dist/version.js +6 -0
  101. package/dist/version.js.map +1 -0
  102. package/dist/worker/client.d.ts +87 -0
  103. package/dist/worker/client.d.ts.map +1 -0
  104. package/dist/worker/client.js +405 -0
  105. package/dist/worker/client.js.map +1 -0
  106. package/dist/worker/emitter.d.ts +12 -0
  107. package/dist/worker/emitter.d.ts.map +1 -0
  108. package/dist/worker/emitter.js +34 -0
  109. package/dist/worker/emitter.js.map +1 -0
  110. package/dist/worker/entry.d.ts +2 -0
  111. package/dist/worker/entry.d.ts.map +1 -0
  112. package/dist/worker/entry.js +4 -0
  113. package/dist/worker/entry.js.map +1 -0
  114. package/dist/worker/index.d.ts +7 -0
  115. package/dist/worker/index.d.ts.map +1 -0
  116. package/dist/worker/index.js +4 -0
  117. package/dist/worker/index.js.map +1 -0
  118. package/dist/worker/protocol.d.ts +82 -0
  119. package/dist/worker/protocol.d.ts.map +1 -0
  120. package/dist/worker/protocol.js +11 -0
  121. package/dist/worker/protocol.js.map +1 -0
  122. package/dist/worker/server.d.ts +51 -0
  123. package/dist/worker/server.d.ts.map +1 -0
  124. package/dist/worker/server.js +460 -0
  125. package/dist/worker/server.js.map +1 -0
  126. package/dist/worker/support.d.ts +39 -0
  127. package/dist/worker/support.d.ts.map +1 -0
  128. package/dist/worker/support.js +25 -0
  129. package/dist/worker/support.js.map +1 -0
  130. package/dist/worker/transfer.d.ts +3 -0
  131. package/dist/worker/transfer.d.ts.map +1 -0
  132. package/dist/worker/transfer.js +13 -0
  133. package/dist/worker/transfer.js.map +1 -0
  134. package/dist/worker/types.d.ts +43 -0
  135. package/dist/worker/types.d.ts.map +1 -0
  136. package/dist/worker/types.js +2 -0
  137. package/dist/worker/types.js.map +1 -0
  138. package/package.json +91 -3
  139. package/schemas/error.schema.json +288 -0
@@ -0,0 +1,241 @@
1
+ import type { WasmDocument, WasmPage, WasmSaveJob } from "@laminapdf/wasm/base";
2
+ import { type LaminaPdfWarning } from "./errors.js";
3
+ import { type RangeOptions, type RangeSource, type RangeState } from "./source/range.js";
4
+ import { PageText, type TextOptions } from "./text.js";
5
+ import { Transaction } from "./transaction.js";
6
+ import type { DocumentInfo, Edited, Op, OpenOptions, OpLog, PageInfo, Progress, SaveOptions, SaveReport, UndoOutcome } from "./types.js";
7
+ /** A page of a {@link Document}, held by its ID: moving pages doesn't retarget it (ADR 0028 F2). */
8
+ export declare class PageRef {
9
+ #private;
10
+ /** @internal */
11
+ constructor(page: WasmPage);
12
+ /** The page's stable ID (`p12.0`). */
13
+ get id(): string;
14
+ /** The page's current 0-based index. @throws {@link LaminaPdfError} `unknown-page` when it is gone. */
15
+ get index(): number;
16
+ /** The page's facts now. @throws {@link LaminaPdfError} `unknown-page` when it is gone. */
17
+ get info(): PageInfo;
18
+ /**
19
+ * The page's text: lines in reading order, runs, and every glyph's quad in page space.
20
+ * Needs a document opened while the extract or text module was loaded.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * await init({ extract: true });
25
+ * const doc = Document.open(bytes);
26
+ * const text = doc.page(0).text().text();
27
+ * ```
28
+ * @param options - `skipInvisible` leaves out invisible text (OCR layers).
29
+ * @throws {@link LaminaPdfError} `extract-module-not-loaded` for a document opened in the base
30
+ * module; `unknown-page`; reading errors (`data-pending` for a range document: use `ensure`).
31
+ */
32
+ text(options?: TextOptions): PageText;
33
+ }
34
+ /** A save in progress, stepped by the caller with a budget of work units (ADR 0028 §3). */
35
+ export declare class SaveJob {
36
+ #private;
37
+ /** @internal */
38
+ constructor(job: WasmSaveJob);
39
+ /**
40
+ * Runs at most `budget` work units.
41
+ *
42
+ * @returns `{ state: "progress", … }`, or `{ state: "done", bytes, report }` at the end.
43
+ * @throws {@link LaminaPdfError} `cancelled`, `document-changed`, `job-finished`, or the save's errors.
44
+ */
45
+ step(budget: number): ({
46
+ readonly state: "progress";
47
+ } & Progress) | {
48
+ readonly state: "done";
49
+ readonly bytes: Uint8Array<ArrayBuffer>;
50
+ readonly report: SaveReport;
51
+ };
52
+ /** Stops the job: its next step fails with `cancelled`. */
53
+ cancel(): void;
54
+ /** Frees the job's memory now (otherwise when it is garbage-collected). */
55
+ free(): void;
56
+ }
57
+ /**
58
+ * An open PDF: read it, edit it with ops (atomic transactions with undo), save it.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * await init();
63
+ * const doc = Document.open(bytes);
64
+ * doc.transact((tx) => tx.setInfo({ title: "Final" }));
65
+ * const updated = doc.save({ mode: "incremental" });
66
+ * doc.free();
67
+ * ```
68
+ */
69
+ export declare class Document {
70
+ #private;
71
+ /** @internal Wraps a raw document (from `open`, `openRange` or `Builder.finish`). */
72
+ constructor(doc: WasmDocument, range?: RangeState);
73
+ /**
74
+ * Opens a document whose bytes are fetched range by range ({@link httpRangeSource},
75
+ * {@link callbackSource}): only what the open needs is fetched (the end of the file, the xref,
76
+ * the catalog). Calls on the document stay synchronous; one that needs bytes not fetched yet
77
+ * throws `data-pending` with `ranges`, so wrap later reads in {@link Document.ensure}.
78
+ *
79
+ * @param source - the file (or a promise of it, as `httpRangeSource` returns).
80
+ * @param options - open options (password, strict, limits) and fetch options.
81
+ * @returns the document, once open has everything it needs.
82
+ * @throws {@link LaminaPdfError} as {@link Document.open}; `data-pending` when fetching made no
83
+ * progress or `maxRounds` ran out; `source-budget-exceeded`; the source's own errors
84
+ * (`fetch-failed`, `source-changed`); the signal's reason when aborted.
85
+ */
86
+ static openRange(source: RangeSource | Promise<RangeSource>, options?: OpenOptions & RangeOptions): Promise<Document>;
87
+ /**
88
+ * Runs a synchronous call on this document, fetching what it needs first: for a document
89
+ * from {@link Document.openRange}, a `data-pending` makes it fetch the ranges asked for and
90
+ * call `f` again. For other documents it just returns `f()`.
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * const doc = await Document.openRange(httpRangeSource(url));
95
+ * const pages = await doc.ensure(() => doc.pages);
96
+ * ```
97
+ * @param f - the call (it may run several times; keep it free of side effects). An async
98
+ * function works too: its `data-pending` rejections are caught the same way.
99
+ * @param options - `signal` aborts this call's fetches (the open's signal applies to the open
100
+ * only).
101
+ * @returns what `f` returns.
102
+ * @throws what `f` throws, and as {@link Document.openRange}.
103
+ */
104
+ ensure<T>(f: () => T | Promise<T>, options?: {
105
+ readonly signal?: AbortSignal;
106
+ }): Promise<T>;
107
+ /**
108
+ * Opens a document from its bytes (the bytes are copied into the WASM memory).
109
+ *
110
+ * The password is tried as given. When that fails with `wrong-password` and its NFKC form
111
+ * differs, the NFKC form is tried too: R5/R6 passwords are prepared with SASLprep, whose
112
+ * normalisation the WASM modules leave out (ADR 0016 §7), while R2–R4 passwords must reach
113
+ * the core unchanged.
114
+ *
115
+ * @param bytes - the PDF file.
116
+ * @param options - password, strict mode, limits.
117
+ * @returns the document.
118
+ * @throws {@link LaminaPdfError} `not-pdf`, `password-required`, `wrong-password`, `limit-exceeded`,
119
+ * `strict`, …; `unknown-option` / `invalid-argument` for bad options; `not-initialized`.
120
+ */
121
+ static open(bytes: Uint8Array | ArrayBuffer, options?: OpenOptions): Document;
122
+ /**
123
+ * Whether `page(i).text()` works on this document: it was opened or built while the extract or
124
+ * the text module was loaded. A document opened before the module loaded stays in the base
125
+ * module; save and reopen it to move it.
126
+ */
127
+ get canExtractText(): boolean;
128
+ /** The number of pages. @throws {@link LaminaPdfError} reading errors. */
129
+ get pageCount(): number;
130
+ /** Every page's facts, in order (one page-tree walk). @throws {@link LaminaPdfError} reading errors. */
131
+ get pages(): readonly PageInfo[];
132
+ /**
133
+ * Page `index`'s facts.
134
+ *
135
+ * @throws {@link LaminaPdfError} `page-index-out-of-range`.
136
+ */
137
+ pageInfo(index: number): PageInfo;
138
+ /**
139
+ * A handle on page `index` that follows the page by its ID.
140
+ *
141
+ * @throws {@link LaminaPdfError} `page-index-out-of-range`.
142
+ */
143
+ page(index: number): PageRef;
144
+ /**
145
+ * A handle on the page with `id` (`p12.0`).
146
+ *
147
+ * @throws {@link LaminaPdfError} `invalid-argument`, `unknown-page`.
148
+ */
149
+ pageById(id: string): PageRef;
150
+ /** The Info dictionary's text entries (document text). @throws {@link LaminaPdfError} reading errors. */
151
+ get info(): DocumentInfo;
152
+ /** Every warning so far (build warnings first), each the ADR 0026 §1 object. */
153
+ get warnings(): readonly LaminaPdfWarning[];
154
+ /** Whether there are changes no save has written yet. */
155
+ get hasUnsavedChanges(): boolean;
156
+ /**
157
+ * Applies `ops` in order as one transaction: all of them, or none.
158
+ *
159
+ * @returns the pages the edit touched.
160
+ * @throws {@link LaminaPdfError} `unknown-option` / `invalid-argument` for a value that isn't an op;
161
+ * the first failing op's error (`unknown-page`, …), with nothing applied.
162
+ */
163
+ apply(ops: readonly Op[]): Edited;
164
+ /**
165
+ * Applies an op log (`{ v: 1, ops }`, as JSON text or an object) as one transaction.
166
+ *
167
+ * @throws {@link LaminaPdfError} `invalid-op-log`, `unsupported-version`; as {@link Document.apply}.
168
+ */
169
+ applyLog(log: string | OpLog): Edited;
170
+ /**
171
+ * Runs `edit` with a transaction handle and applies what it collected as one transaction
172
+ * (ADR 0006 §2). When `edit` throws, nothing is applied and the error propagates. The ops are
173
+ * applied when `edit` returns: reads inside `edit` see the document before the transaction.
174
+ *
175
+ * @param edit - collects ops on `tx`; synchronous only (a callback returning a promise is
176
+ * refused with `invalid-argument` and nothing is applied).
177
+ * @returns what `edit` returned.
178
+ * @throws {@link LaminaPdfError} `nested-transaction` when called (or when `apply`, `undo`, `redo`,
179
+ * `save`… are called) inside a `transact` of the same document; `invalid-argument` for an
180
+ * async callback; as {@link Document.apply}.
181
+ * @example
182
+ * ```ts
183
+ * doc.transact((tx) => {
184
+ * tx.setInfo({ title: "Report" });
185
+ * tx.rotatePage(doc.page(0).id, 90);
186
+ * });
187
+ * ```
188
+ */
189
+ transact<T>(edit: (tx: Transaction) => T): T;
190
+ /**
191
+ * Undoes the newest transaction.
192
+ *
193
+ * @returns how it went, or `undefined` when there is nothing to undo.
194
+ * @throws {@link LaminaPdfError} `saved-not-rebased`; reading errors.
195
+ */
196
+ undo(): UndoOutcome | undefined;
197
+ /**
198
+ * Redoes the newest undone transaction.
199
+ *
200
+ * @returns how it went, or `undefined` when there is nothing to redo.
201
+ * @throws {@link LaminaPdfError} as {@link Document.undo}.
202
+ */
203
+ redo(): UndoOutcome | undefined;
204
+ /**
205
+ * Saves and rebases onto the output (later edits and saves build on it).
206
+ *
207
+ * @param options - `mode` (`auto`, `incremental`, `fullRewrite`), overrides, output form.
208
+ * @returns the file's bytes.
209
+ * @throws {@link LaminaPdfError} `would-invalidate-signatures`, `requires-rewrite`, `encrypted-base`, …
210
+ */
211
+ save(options?: SaveOptions): Uint8Array<ArrayBuffer>;
212
+ /**
213
+ * As {@link Document.save}, with the save's report (mode used, bytes written, renumbering, …).
214
+ *
215
+ * @throws {@link LaminaPdfError} as {@link Document.save}.
216
+ */
217
+ saveWithReport(options?: SaveOptions): {
218
+ readonly bytes: Uint8Array<ArrayBuffer>;
219
+ readonly report: SaveReport;
220
+ };
221
+ /**
222
+ * A save as a job stepped by the caller (to keep a UI responsive).
223
+ *
224
+ * @throws {@link LaminaPdfError} `unknown-option` / `invalid-argument` for bad options.
225
+ */
226
+ saveJob(options?: SaveOptions): SaveJob;
227
+ /**
228
+ * Rebases onto `bytes`, the output of a save whose rebase failed (`not-rebased`).
229
+ *
230
+ * @throws {@link LaminaPdfError} `no-pending-save`; reading errors in `bytes`.
231
+ */
232
+ rebase(bytes: Uint8Array): void;
233
+ /**
234
+ * Frees the document's WASM memory now; without it, it is freed when garbage-collected.
235
+ * Calling it twice is harmless; using the document afterwards is not (L2b adds `disposed`).
236
+ */
237
+ free(): void;
238
+ /** Frees the document at the end of a `using` block. */
239
+ [Symbol.dispose](): void;
240
+ }
241
+ //# sourceMappingURL=document.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,427 @@
1
+ import { guard, isLaminaPdfError, packageError } from "./errors.js";
2
+ import { extractsText, readingModule } from "./init.js";
3
+ import { settle, wholeBytes } from "./source/range.js";
4
+ import { PageText } from "./text.js";
5
+ import { Transaction } from "./transaction.js";
6
+ function isThenable(value) {
7
+ return ((typeof value === "object" || typeof value === "function") &&
8
+ value !== null &&
9
+ typeof value.then === "function");
10
+ }
11
+ /** How many warnings one cursor call fetches. */
12
+ const WARNING_BATCH = 256;
13
+ function bytesOf(input) {
14
+ return input instanceof Uint8Array ? input : new Uint8Array(input);
15
+ }
16
+ /**
17
+ * The open options to try, in order: as given, then, when the password's NFKC form differs, with
18
+ * that form (tried only after `wrong-password`). R5/R6 passwords are prepared with SASLprep,
19
+ * whose normalisation the WASM modules leave out (ADR 0016 §7), while R2–R4 passwords must reach
20
+ * the core unchanged.
21
+ */
22
+ function passwordAttempts(options) {
23
+ const password = options.password;
24
+ const nfkc = password?.normalize("NFKC");
25
+ return password === undefined || nfkc === undefined || nfkc === password
26
+ ? [options]
27
+ : [options, { ...options, password: nfkc }];
28
+ }
29
+ /** Whether a failed attempt should be followed by the next one. */
30
+ function tryNext(e, attempt, attempts) {
31
+ return attempt + 1 < attempts && isLaminaPdfError(e, "wrong-password");
32
+ }
33
+ /** A page of a {@link Document}, held by its ID: moving pages doesn't retarget it (ADR 0028 F2). */
34
+ export class PageRef {
35
+ #page;
36
+ /** @internal */
37
+ constructor(page) {
38
+ this.#page = page;
39
+ }
40
+ /** The page's stable ID (`p12.0`). */
41
+ get id() {
42
+ return this.#page.id;
43
+ }
44
+ /** The page's current 0-based index. @throws {@link LaminaPdfError} `unknown-page` when it is gone. */
45
+ get index() {
46
+ return guard(() => this.#page.index());
47
+ }
48
+ /** The page's facts now. @throws {@link LaminaPdfError} `unknown-page` when it is gone. */
49
+ get info() {
50
+ return guard(() => this.#page.info());
51
+ }
52
+ /**
53
+ * The page's text: lines in reading order, runs, and every glyph's quad in page space.
54
+ * Needs a document opened while the extract or text module was loaded.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * await init({ extract: true });
59
+ * const doc = Document.open(bytes);
60
+ * const text = doc.page(0).text().text();
61
+ * ```
62
+ * @param options - `skipInvisible` leaves out invisible text (OCR layers).
63
+ * @throws {@link LaminaPdfError} `extract-module-not-loaded` for a document opened in the base
64
+ * module; `unknown-page`; reading errors (`data-pending` for a range document: use `ensure`).
65
+ */
66
+ text(options) {
67
+ const page = this.#page;
68
+ if (typeof page.text !== "function") {
69
+ throw packageError("extract-module-not-loaded", "page.text() needs a document opened while the extract or text module was loaded");
70
+ }
71
+ const extract = page.text.bind(page);
72
+ return new PageText(guard(() => extract(options)));
73
+ }
74
+ }
75
+ /** A save in progress, stepped by the caller with a budget of work units (ADR 0028 §3). */
76
+ export class SaveJob {
77
+ #job;
78
+ /** @internal */
79
+ constructor(job) {
80
+ this.#job = job;
81
+ }
82
+ /**
83
+ * Runs at most `budget` work units.
84
+ *
85
+ * @returns `{ state: "progress", … }`, or `{ state: "done", bytes, report }` at the end.
86
+ * @throws {@link LaminaPdfError} `cancelled`, `document-changed`, `job-finished`, or the save's errors.
87
+ */
88
+ step(budget) {
89
+ return guard(() => this.#job.step(budget));
90
+ }
91
+ /** Stops the job: its next step fails with `cancelled`. */
92
+ cancel() {
93
+ this.#job.cancel();
94
+ }
95
+ /** Frees the job's memory now (otherwise when it is garbage-collected). */
96
+ free() {
97
+ this.#job.free();
98
+ }
99
+ }
100
+ /**
101
+ * An open PDF: read it, edit it with ops (atomic transactions with undo), save it.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * await init();
106
+ * const doc = Document.open(bytes);
107
+ * doc.transact((tx) => tx.setInfo({ title: "Final" }));
108
+ * const updated = doc.save({ mode: "incremental" });
109
+ * doc.free();
110
+ * ```
111
+ */
112
+ export class Document {
113
+ #doc;
114
+ /** Set for a document opened with {@link Document.openRange}: where more bytes come from. */
115
+ #range;
116
+ #freed = false;
117
+ /** Whether a `transact` callback of this document is running. */
118
+ #transacting = false;
119
+ /** @internal Wraps a raw document (from `open`, `openRange` or `Builder.finish`). */
120
+ constructor(doc, range) {
121
+ this.#doc = doc;
122
+ this.#range = range;
123
+ }
124
+ /**
125
+ * Opens a document whose bytes are fetched range by range ({@link httpRangeSource},
126
+ * {@link callbackSource}): only what the open needs is fetched (the end of the file, the xref,
127
+ * the catalog). Calls on the document stay synchronous; one that needs bytes not fetched yet
128
+ * throws `data-pending` with `ranges`, so wrap later reads in {@link Document.ensure}.
129
+ *
130
+ * @param source - the file (or a promise of it, as `httpRangeSource` returns).
131
+ * @param options - open options (password, strict, limits) and fetch options.
132
+ * @returns the document, once open has everything it needs.
133
+ * @throws {@link LaminaPdfError} as {@link Document.open}; `data-pending` when fetching made no
134
+ * progress or `maxRounds` ran out; `source-budget-exceeded`; the source's own errors
135
+ * (`fetch-failed`, `source-changed`); the signal's reason when aborted.
136
+ */
137
+ static async openRange(source, options = {}) {
138
+ // The source first: a rejected source promise must not go unhandled behind not-initialized.
139
+ const src = await source;
140
+ const { signal, blockSize, readahead, maxRounds, budget, ...open } = options;
141
+ // A server that ignored Range from the start sent the whole file: open those bytes as they
142
+ // are, rather than copying them into a sparse source as well.
143
+ const whole = wholeBytes(src);
144
+ if (whole !== undefined)
145
+ return Document.open(whole, open);
146
+ const wasm = readingModule();
147
+ const sparse = guard(() => new wasm.WasmSparseSource(src.size, budget));
148
+ const state = {
149
+ sparse,
150
+ source: src,
151
+ blockSize: blockSize ?? src.blockSize ?? 4096,
152
+ readahead: readahead ?? 0,
153
+ maxRounds: maxRounds ?? 1024,
154
+ };
155
+ const attempts = passwordAttempts(open);
156
+ try {
157
+ for (let i = 0;; i++) {
158
+ try {
159
+ return await settle(state, () => new Document(guard(() => wasm.WasmDocument.openSource(sparse, attempts[i])), state), signal);
160
+ }
161
+ catch (e) {
162
+ if (!tryNext(e, i, attempts.length))
163
+ throw e;
164
+ }
165
+ }
166
+ }
167
+ catch (e) {
168
+ // Nothing holds the fetched bytes now: free them at once rather than at collection.
169
+ sparse.free();
170
+ throw e;
171
+ }
172
+ }
173
+ /**
174
+ * Runs a synchronous call on this document, fetching what it needs first: for a document
175
+ * from {@link Document.openRange}, a `data-pending` makes it fetch the ranges asked for and
176
+ * call `f` again. For other documents it just returns `f()`.
177
+ *
178
+ * @example
179
+ * ```ts
180
+ * const doc = await Document.openRange(httpRangeSource(url));
181
+ * const pages = await doc.ensure(() => doc.pages);
182
+ * ```
183
+ * @param f - the call (it may run several times; keep it free of side effects). An async
184
+ * function works too: its `data-pending` rejections are caught the same way.
185
+ * @param options - `signal` aborts this call's fetches (the open's signal applies to the open
186
+ * only).
187
+ * @returns what `f` returns.
188
+ * @throws what `f` throws, and as {@link Document.openRange}.
189
+ */
190
+ async ensure(f, options = {}) {
191
+ return this.#range === undefined ? f() : settle(this.#range, f, options.signal);
192
+ }
193
+ /**
194
+ * Opens a document from its bytes (the bytes are copied into the WASM memory).
195
+ *
196
+ * The password is tried as given. When that fails with `wrong-password` and its NFKC form
197
+ * differs, the NFKC form is tried too: R5/R6 passwords are prepared with SASLprep, whose
198
+ * normalisation the WASM modules leave out (ADR 0016 §7), while R2–R4 passwords must reach
199
+ * the core unchanged.
200
+ *
201
+ * @param bytes - the PDF file.
202
+ * @param options - password, strict mode, limits.
203
+ * @returns the document.
204
+ * @throws {@link LaminaPdfError} `not-pdf`, `password-required`, `wrong-password`, `limit-exceeded`,
205
+ * `strict`, …; `unknown-option` / `invalid-argument` for bad options; `not-initialized`.
206
+ */
207
+ static open(bytes, options) {
208
+ const wasm = readingModule();
209
+ const data = bytesOf(bytes);
210
+ const attempts = passwordAttempts(options ?? {});
211
+ for (let i = 0;; i++) {
212
+ try {
213
+ return new Document(guard(() => wasm.WasmDocument.openBytes(data, attempts[i])));
214
+ }
215
+ catch (e) {
216
+ if (!tryNext(e, i, attempts.length))
217
+ throw e;
218
+ }
219
+ }
220
+ }
221
+ /**
222
+ * Whether `page(i).text()` works on this document: it was opened or built while the extract or
223
+ * the text module was loaded. A document opened before the module loaded stays in the base
224
+ * module; save and reopen it to move it.
225
+ */
226
+ get canExtractText() {
227
+ return extractsText(this.#doc);
228
+ }
229
+ /** The number of pages. @throws {@link LaminaPdfError} reading errors. */
230
+ get pageCount() {
231
+ return guard(() => this.#doc.pageCount());
232
+ }
233
+ /** Every page's facts, in order (one page-tree walk). @throws {@link LaminaPdfError} reading errors. */
234
+ get pages() {
235
+ return guard(() => this.#doc.pageInfos());
236
+ }
237
+ /**
238
+ * Page `index`'s facts.
239
+ *
240
+ * @throws {@link LaminaPdfError} `page-index-out-of-range`.
241
+ */
242
+ pageInfo(index) {
243
+ return guard(() => this.#doc.pageInfo(index));
244
+ }
245
+ /**
246
+ * A handle on page `index` that follows the page by its ID.
247
+ *
248
+ * @throws {@link LaminaPdfError} `page-index-out-of-range`.
249
+ */
250
+ page(index) {
251
+ return new PageRef(guard(() => this.#doc.page(index)));
252
+ }
253
+ /**
254
+ * A handle on the page with `id` (`p12.0`).
255
+ *
256
+ * @throws {@link LaminaPdfError} `invalid-argument`, `unknown-page`.
257
+ */
258
+ pageById(id) {
259
+ return new PageRef(guard(() => this.#doc.pageById(id)));
260
+ }
261
+ /** The Info dictionary's text entries (document text). @throws {@link LaminaPdfError} reading errors. */
262
+ get info() {
263
+ return guard(() => this.#doc.info());
264
+ }
265
+ /** Every warning so far (build warnings first), each the ADR 0026 §1 object. */
266
+ get warnings() {
267
+ const cursor = this.#doc.warnings();
268
+ try {
269
+ const out = [];
270
+ for (;;) {
271
+ const batch = cursor.nextBatch(WARNING_BATCH);
272
+ if (batch.length === 0)
273
+ return out;
274
+ out.push(...batch);
275
+ }
276
+ }
277
+ finally {
278
+ cursor.free();
279
+ }
280
+ }
281
+ /** Whether there are changes no save has written yet. */
282
+ get hasUnsavedChanges() {
283
+ return this.#doc.hasUnsavedChanges();
284
+ }
285
+ /**
286
+ * Applies `ops` in order as one transaction: all of them, or none.
287
+ *
288
+ * @returns the pages the edit touched.
289
+ * @throws {@link LaminaPdfError} `unknown-option` / `invalid-argument` for a value that isn't an op;
290
+ * the first failing op's error (`unknown-page`, …), with nothing applied.
291
+ */
292
+ apply(ops) {
293
+ this.#notInTransaction("apply");
294
+ return guard(() => this.#doc.apply(ops));
295
+ }
296
+ /**
297
+ * Applies an op log (`{ v: 1, ops }`, as JSON text or an object) as one transaction.
298
+ *
299
+ * @throws {@link LaminaPdfError} `invalid-op-log`, `unsupported-version`; as {@link Document.apply}.
300
+ */
301
+ applyLog(log) {
302
+ this.#notInTransaction("applyLog");
303
+ const json = typeof log === "string" ? log : JSON.stringify(log);
304
+ return guard(() => this.#doc.applyLog(json));
305
+ }
306
+ /**
307
+ * Runs `edit` with a transaction handle and applies what it collected as one transaction
308
+ * (ADR 0006 §2). When `edit` throws, nothing is applied and the error propagates. The ops are
309
+ * applied when `edit` returns: reads inside `edit` see the document before the transaction.
310
+ *
311
+ * @param edit - collects ops on `tx`; synchronous only (a callback returning a promise is
312
+ * refused with `invalid-argument` and nothing is applied).
313
+ * @returns what `edit` returned.
314
+ * @throws {@link LaminaPdfError} `nested-transaction` when called (or when `apply`, `undo`, `redo`,
315
+ * `save`… are called) inside a `transact` of the same document; `invalid-argument` for an
316
+ * async callback; as {@link Document.apply}.
317
+ * @example
318
+ * ```ts
319
+ * doc.transact((tx) => {
320
+ * tx.setInfo({ title: "Report" });
321
+ * tx.rotatePage(doc.page(0).id, 90);
322
+ * });
323
+ * ```
324
+ */
325
+ transact(edit) {
326
+ this.#notInTransaction("transact");
327
+ this.#transacting = true;
328
+ const tx = new Transaction();
329
+ let result;
330
+ try {
331
+ result = edit(tx);
332
+ }
333
+ finally {
334
+ this.#transacting = false;
335
+ tx.close();
336
+ }
337
+ if (isThenable(result)) {
338
+ // The ops of an async callback would be applied before its awaits finish: refuse it.
339
+ throw packageError("invalid-argument", "transact takes a synchronous callback; it returned a promise", {
340
+ data: { argument: "edit", detail: "the callback returned a promise; nothing was applied" },
341
+ });
342
+ }
343
+ if (tx.ops.length > 0)
344
+ this.apply(tx.ops);
345
+ return result;
346
+ }
347
+ /** Throws `nested-transaction` while a `transact` callback of this document runs. */
348
+ #notInTransaction(call) {
349
+ if (this.#transacting) {
350
+ throw packageError("nested-transaction", `${call} was called inside a transaction of the same document`);
351
+ }
352
+ }
353
+ /**
354
+ * Undoes the newest transaction.
355
+ *
356
+ * @returns how it went, or `undefined` when there is nothing to undo.
357
+ * @throws {@link LaminaPdfError} `saved-not-rebased`; reading errors.
358
+ */
359
+ undo() {
360
+ this.#notInTransaction("undo");
361
+ return guard(() => this.#doc.undo());
362
+ }
363
+ /**
364
+ * Redoes the newest undone transaction.
365
+ *
366
+ * @returns how it went, or `undefined` when there is nothing to redo.
367
+ * @throws {@link LaminaPdfError} as {@link Document.undo}.
368
+ */
369
+ redo() {
370
+ this.#notInTransaction("redo");
371
+ return guard(() => this.#doc.redo());
372
+ }
373
+ /**
374
+ * Saves and rebases onto the output (later edits and saves build on it).
375
+ *
376
+ * @param options - `mode` (`auto`, `incremental`, `fullRewrite`), overrides, output form.
377
+ * @returns the file's bytes.
378
+ * @throws {@link LaminaPdfError} `would-invalidate-signatures`, `requires-rewrite`, `encrypted-base`, …
379
+ */
380
+ save(options) {
381
+ return this.saveWithReport(options).bytes;
382
+ }
383
+ /**
384
+ * As {@link Document.save}, with the save's report (mode used, bytes written, renumbering, …).
385
+ *
386
+ * @throws {@link LaminaPdfError} as {@link Document.save}.
387
+ */
388
+ saveWithReport(options) {
389
+ this.#notInTransaction("save");
390
+ return guard(() => this.#doc.save(options));
391
+ }
392
+ /**
393
+ * A save as a job stepped by the caller (to keep a UI responsive).
394
+ *
395
+ * @throws {@link LaminaPdfError} `unknown-option` / `invalid-argument` for bad options.
396
+ */
397
+ saveJob(options) {
398
+ this.#notInTransaction("saveJob");
399
+ return new SaveJob(guard(() => this.#doc.saveJob(options)));
400
+ }
401
+ /**
402
+ * Rebases onto `bytes`, the output of a save whose rebase failed (`not-rebased`).
403
+ *
404
+ * @throws {@link LaminaPdfError} `no-pending-save`; reading errors in `bytes`.
405
+ */
406
+ rebase(bytes) {
407
+ this.#notInTransaction("rebase");
408
+ guard(() => this.#doc.rebase(bytes));
409
+ }
410
+ /**
411
+ * Frees the document's WASM memory now; without it, it is freed when garbage-collected.
412
+ * Calling it twice is harmless; using the document afterwards is not (L2b adds `disposed`).
413
+ */
414
+ free() {
415
+ if (this.#freed)
416
+ return;
417
+ this.#freed = true;
418
+ this.#doc.free();
419
+ // A range document's fetched bytes live in its sparse source.
420
+ this.#range?.sparse.free();
421
+ }
422
+ /** Frees the document at the end of a `using` block. */
423
+ [Symbol.dispose]() {
424
+ this.free();
425
+ }
426
+ }
427
+ //# sourceMappingURL=document.js.map