@2018kguo/recto 0.0.0-stage → 0.3.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 (53) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +25 -2
  3. package/dist/assets.d.ts +19 -0
  4. package/dist/assets.js +250 -0
  5. package/dist/document.d.ts +307 -0
  6. package/dist/document.js +405 -0
  7. package/dist/engine.d.ts +62 -0
  8. package/dist/engine.js +383 -0
  9. package/dist/errors.d.ts +36 -0
  10. package/dist/errors.js +14 -0
  11. package/dist/index.d.ts +18 -0
  12. package/dist/index.js +23 -0
  13. package/dist/page-measures.d.ts +34 -0
  14. package/dist/page-measures.js +166 -0
  15. package/dist/pool-worker.d.ts +14 -0
  16. package/dist/pool-worker.js +65 -0
  17. package/dist/pool.d.ts +36 -0
  18. package/dist/pool.js +287 -0
  19. package/dist/ranged.d.ts +98 -0
  20. package/dist/ranged.js +497 -0
  21. package/dist/reading-order.d.ts +14 -0
  22. package/dist/reading-order.js +173 -0
  23. package/dist/sha256.d.ts +1 -0
  24. package/dist/sha256.js +78 -0
  25. package/dist/text-items.d.ts +49 -0
  26. package/dist/text-items.js +272 -0
  27. package/package.json +48 -4
  28. package/wasm/FoxitDingbats.pfb +0 -0
  29. package/wasm/FoxitFixed.pfb +0 -0
  30. package/wasm/FoxitFixedBold.pfb +0 -0
  31. package/wasm/FoxitFixedBoldItalic.pfb +0 -0
  32. package/wasm/FoxitFixedItalic.pfb +0 -0
  33. package/wasm/FoxitSans.pfb +0 -0
  34. package/wasm/FoxitSansBold.pfb +0 -0
  35. package/wasm/FoxitSansBoldItalic.pfb +0 -0
  36. package/wasm/FoxitSansItalic.pfb +0 -0
  37. package/wasm/FoxitSerif.pfb +0 -0
  38. package/wasm/FoxitSerifBold.pfb +0 -0
  39. package/wasm/FoxitSerifBoldItalic.pfb +0 -0
  40. package/wasm/FoxitSerifItalic.pfb +0 -0
  41. package/wasm/FoxitSymbol.pfb +0 -0
  42. package/wasm/THIRD_PARTY_NOTICES +2362 -0
  43. package/wasm/cjk-fallback.ttf +0 -0
  44. package/wasm/cmaps.deflate +0 -0
  45. package/wasm/pins.d.mts +1 -0
  46. package/wasm/pins.mjs +23 -0
  47. package/wasm/recto-more.wasm +0 -0
  48. package/wasm/recto-rare.wasm +0 -0
  49. package/wasm/recto-render.wasm +0 -0
  50. package/wasm/recto.gen.d.mts +3 -0
  51. package/wasm/recto.gen.mjs +1203 -0
  52. package/wasm/recto.types.d.mts +295 -0
  53. package/wasm/recto.wasm +0 -0
@@ -0,0 +1,14 @@
1
+ export type PoolRequest = {
2
+ id: number;
3
+ doc: number;
4
+ /** Only until this thread has the document. */
5
+ open?: {
6
+ bytes: Uint8Array;
7
+ password: string | undefined;
8
+ };
9
+ method: string;
10
+ args: unknown[];
11
+ };
12
+ export type PoolStart = {
13
+ assetBase: string | undefined;
14
+ };
@@ -0,0 +1,65 @@
1
+ // One thread of a pool: opens what it's sent on demand, and answers each call with its result.
2
+ import { createPdfEngine } from "./engine.js";
3
+ const parent = globalThis.process?.getBuiltinModule?.("node:worker_threads")?.parentPort;
4
+ const port = (parent ?? self);
5
+ const docs = new Map();
6
+ let engine;
7
+ /** Every column in a result, so it moves rather than being copied. */
8
+ function buffersIn(value, found) {
9
+ if (ArrayBuffer.isView(value)) {
10
+ found.add(value.buffer);
11
+ }
12
+ else if (typeof value === "object" && value !== null) {
13
+ for (const inner of Object.values(value)) {
14
+ buffersIn(inner, found);
15
+ }
16
+ }
17
+ }
18
+ async function answer({ doc: id, open, method, args }) {
19
+ let doc = docs.get(id);
20
+ if (!doc) {
21
+ doc = await engine.open(open.bytes, open.password === undefined ? {} : { password: open.password });
22
+ docs.set(id, doc);
23
+ }
24
+ if (method === "pageCount") {
25
+ return doc.pageCount;
26
+ }
27
+ return doc[method](...args);
28
+ }
29
+ function failureOf(error) {
30
+ const { name, code, message } = error;
31
+ return { name, code, message: message ?? String(error) };
32
+ }
33
+ async function handle(message) {
34
+ if (!("id" in message)) {
35
+ const { assetBase } = message;
36
+ try {
37
+ engine = await createPdfEngine(assetBase ? { assetBase } : {});
38
+ port.postMessage({ ready: true }, []);
39
+ }
40
+ catch (error) {
41
+ port.postMessage({ startFailed: true, failure: failureOf(error) }, []);
42
+ }
43
+ return;
44
+ }
45
+ if (message.method === "close") {
46
+ docs.get(message.doc)?.close();
47
+ docs.delete(message.doc);
48
+ return;
49
+ }
50
+ try {
51
+ const result = await answer(message);
52
+ const buffers = new Set();
53
+ buffersIn(result, buffers);
54
+ port.postMessage({ result, opened: docs.has(message.doc) }, [...buffers]);
55
+ }
56
+ catch (error) {
57
+ port.postMessage({ failure: failureOf(error), opened: docs.has(message.doc) }, []);
58
+ }
59
+ }
60
+ if (parent) {
61
+ parent.on("message", (message) => void handle(message));
62
+ }
63
+ else {
64
+ self.onmessage = ({ data }) => void handle(data);
65
+ }
package/dist/pool.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ import type { PdfAnnotation, PdfBitmap, PdfOutlineItem, PdfPageInfo, PdfPageObjects, PdfPageText, PdfRenderOptions } from "./document.ts";
2
+ export type PdfPoolOptions = {
3
+ /** At most this many threads, up to 64. Default: one less than the machine's cores, up to 8. */
4
+ threads?: number;
5
+ /** As for `createPdfEngine`. A string: it crosses to the threads. */
6
+ assetBase?: string;
7
+ };
8
+ export type PdfPoolOpenOptions = {
9
+ password?: string;
10
+ };
11
+ /** A `PdfDocument` whose calls run on the pool's threads, each a promise. */
12
+ export interface PdfPoolDocument {
13
+ readonly pageCount: number;
14
+ pageInfo(index: number): Promise<PdfPageInfo>;
15
+ text(index: number): Promise<PdfPageText>;
16
+ objects(index: number): Promise<PdfPageObjects>;
17
+ annotations(index: number): Promise<PdfAnnotation[]>;
18
+ outline(): Promise<PdfOutlineItem[]>;
19
+ /** Takes no `signal`: a call that's running can't be stopped. */
20
+ render(index: number, options: PdfRenderOptions): Promise<PdfBitmap>;
21
+ /** Frees it on every thread, and fails its calls still waiting with `closed`. */
22
+ close(): Promise<void>;
23
+ [Symbol.asyncDispose](): Promise<void>;
24
+ }
25
+ export interface PdfPool {
26
+ open(bytes: Uint8Array | ArrayBuffer, options?: PdfPoolOpenOptions): Promise<PdfPoolDocument>;
27
+ /** Threads started so far, and calls waiting for one. */
28
+ stats(): {
29
+ threads: number;
30
+ queued: number;
31
+ };
32
+ /** Stops every thread now: anything unfinished fails with `closed`. */
33
+ close(): void;
34
+ [Symbol.dispose](): void;
35
+ }
36
+ export declare function startPool(options: PdfPoolOptions): PdfPool;
package/dist/pool.js ADDED
@@ -0,0 +1,287 @@
1
+ // Pages read and drawn across worker threads, from one call per page. Each thread has its own
2
+ // engine and, the first time it's given a page of a document, its own copy of the file; a thread
3
+ // starts only when there's work and none is free, so calls made one at a time stay on one thread.
4
+ // No special headers: SharedArrayBuffer only saves a copy, where it's allowed.
5
+ //
6
+ // No call waits for ever on a thread that's gone: a thread reports a failed start, its death fails its
7
+ // call, and one that has said nothing by START_TIMEOUT_MS counts as dead. A call that's running can't be
8
+ // stopped, only the page budgets end it, or `close()`.
9
+ import { PdfError } from "./errors.js";
10
+ /**
11
+ * The pool's copy of a file. Shared memory where it's allowed (Node, or a page with COOP and COEP), so that
12
+ * telling each thread about the file doesn't copy it again before the thread copies it into its wasm.
13
+ */
14
+ function privateCopy(view) {
15
+ if (typeof SharedArrayBuffer !== "function") {
16
+ return view.slice();
17
+ }
18
+ const copy = new Uint8Array(new SharedArrayBuffer(view.byteLength));
19
+ copy.set(view);
20
+ return copy;
21
+ }
22
+ /** Longer than a thread's slowest fetch of its wasm (ASSET_TIMEOUT_MS), and its compile. */
23
+ const START_TIMEOUT_MS = 90_000;
24
+ const MAX_THREADS = 64;
25
+ function defaultThreads() {
26
+ const cores = globalThis.process?.getBuiltinModule?.("node:os")?.availableParallelism() ?? navigator.hardwareConcurrency ?? 2;
27
+ return Math.max(1, Math.min(8, cores - 1));
28
+ }
29
+ function failureOf(failure) {
30
+ if (failure.code) {
31
+ return new PdfError(failure.code, failure.message);
32
+ }
33
+ // A bad argument stays what it is on a single thread.
34
+ const type = failure.name === "RangeError" ? RangeError : failure.name === "TypeError" ? TypeError : Error;
35
+ return new type(failure.message);
36
+ }
37
+ const closedError = () => new PdfError("closed", "this document or pool is closed");
38
+ class Pool {
39
+ #limit;
40
+ #ceiling;
41
+ #assetBase;
42
+ #threads = [];
43
+ /** Started and not yet ready. */
44
+ #startingThreads = new Set();
45
+ #queue = [];
46
+ #nextFile = 0;
47
+ #nextId = 0;
48
+ #closed = false;
49
+ constructor(limit, assetBase) {
50
+ this.#limit = limit;
51
+ this.#ceiling = limit;
52
+ this.#assetBase = assetBase;
53
+ }
54
+ #spawn() {
55
+ const thread = { worker: null, running: null };
56
+ this.#startingThreads.add(thread);
57
+ let dead = false;
58
+ let waiting;
59
+ const onMessage = (message) => {
60
+ if (this.#closed) {
61
+ void thread.worker.terminate();
62
+ return;
63
+ }
64
+ if (message.startFailed) {
65
+ onDeath(message.failure.message, failureOf(message.failure));
66
+ return;
67
+ }
68
+ if (message.ready) {
69
+ clearTimeout(waiting);
70
+ this.#startingThreads.delete(thread);
71
+ this.#threads.push(thread);
72
+ // Idle, a thread doesn't keep a Node process alive; a call under way does.
73
+ thread.worker.unref?.();
74
+ }
75
+ else {
76
+ const task = thread.running;
77
+ thread.running = null;
78
+ thread.worker.unref?.();
79
+ // It'll need the file again: this was the call that was to open it.
80
+ if (!message.opened) {
81
+ task.file.on.delete(thread);
82
+ }
83
+ if (message.failure) {
84
+ task.reject(failureOf(message.failure));
85
+ }
86
+ else {
87
+ task.resolve(message.result);
88
+ }
89
+ }
90
+ this.#pump();
91
+ };
92
+ // In Node a failure is an `error` and then an `exit`.
93
+ const onDeath = (reason, typed) => {
94
+ if (dead || this.#closed) {
95
+ return;
96
+ }
97
+ dead = true;
98
+ clearTimeout(waiting);
99
+ void thread.worker?.terminate();
100
+ const failure = typed ?? new PdfError("crash", `a pool thread stopped: ${String(reason)}`, reason);
101
+ if (this.#startingThreads.delete(thread)) {
102
+ // It couldn't start, so another wouldn't either: run on what's left, or fail what's waiting.
103
+ this.#limit = this.#threads.length + this.#startingThreads.size;
104
+ if (this.#limit === 0) {
105
+ // The next call tries again: what stopped it, a missing file say, may be back by then.
106
+ this.#limit = this.#ceiling;
107
+ for (const task of this.#queue.splice(0)) {
108
+ task.reject(failure);
109
+ }
110
+ }
111
+ }
112
+ else {
113
+ this.#threads.splice(this.#threads.indexOf(thread), 1);
114
+ }
115
+ thread.running?.file.on.delete(thread);
116
+ thread.running?.reject(failure);
117
+ thread.running = null;
118
+ this.#pump();
119
+ };
120
+ waiting = setTimeout(() => onDeath(`it hadn't started after ${START_TIMEOUT_MS} ms`), START_TIMEOUT_MS);
121
+ waiting.unref?.();
122
+ try {
123
+ const threads = globalThis.process?.getBuiltinModule?.("node:worker_threads");
124
+ if (threads) {
125
+ const worker = new threads.Worker(new URL("./pool-worker.js", import.meta.url), { execArgv: [] });
126
+ worker.on("message", onMessage);
127
+ worker.on("messageerror", onDeath);
128
+ worker.on("error", onDeath);
129
+ worker.on("exit", onDeath);
130
+ thread.worker = worker;
131
+ }
132
+ else {
133
+ // Not `new URL("…", import.meta.url)`: a bundler would emit the file as it stands for that.
134
+ const here = import.meta.url;
135
+ const worker = new URL(here).origin === location.origin
136
+ ? // Written out in full: this is the form bundlers recognise, and they emit the worker for it.
137
+ new Worker(new URL("./pool-worker.js", import.meta.url), { type: "module" })
138
+ : // A worker's script has to be of this page's origin, so from a CDN it's one line that imports the
139
+ // real one, which the CDN has to allow with CORS, as it does the wasm.
140
+ // ponytail: the blob's URL lives as long as the page; one per thread started.
141
+ new Worker(URL.createObjectURL(new Blob([`import ${JSON.stringify(new URL("./pool-worker.js", here).href)};`], { type: "text/javascript" })), { type: "module" });
142
+ worker.onmessage = ({ data }) => onMessage(data);
143
+ worker.onmessageerror = () => onDeath("a message from it could not be read");
144
+ worker.onerror = (event) => {
145
+ event.preventDefault();
146
+ onDeath(event.message || "its script did not load");
147
+ };
148
+ thread.worker = worker;
149
+ }
150
+ const start = { assetBase: this.#assetBase };
151
+ thread.worker.postMessage(start);
152
+ }
153
+ catch (error) {
154
+ onDeath(error);
155
+ }
156
+ }
157
+ /** Gives each free thread work, the oldest call on a document it has if there is one. */
158
+ #pump() {
159
+ for (const thread of this.#threads) {
160
+ if (thread.running || this.#queue.length === 0) {
161
+ continue;
162
+ }
163
+ let at = this.#queue.findIndex((task) => task.file.on.has(thread));
164
+ if (at < 0) {
165
+ // Not a copy of the file for a call that a thread already holding it will take next.
166
+ const free = this.#threads.filter((other) => !other.running);
167
+ at = this.#queue.findIndex((task) => !free.some((other) => task.file.on.has(other)));
168
+ }
169
+ if (at < 0) {
170
+ continue;
171
+ }
172
+ const [task] = this.#queue.splice(at, 1);
173
+ thread.running = task;
174
+ thread.worker.ref?.();
175
+ const { file } = task;
176
+ const had = file.on.has(thread);
177
+ const open = had ? {} : { open: { bytes: file.bytes, password: file.password } };
178
+ file.on.add(thread);
179
+ try {
180
+ thread.worker.postMessage({ ...task.request, id: this.#nextId++, ...open });
181
+ }
182
+ catch (error) {
183
+ // An argument that can't cross. Nothing reached the thread.
184
+ thread.running = null;
185
+ thread.worker.unref?.();
186
+ if (!had) {
187
+ file.on.delete(thread);
188
+ }
189
+ task.reject(error);
190
+ // Nothing will come back from the thread to take the next call.
191
+ this.#pump();
192
+ return;
193
+ }
194
+ }
195
+ const wanted = Math.min(this.#limit - this.#threads.length, this.#queue.length) - this.#startingThreads.size;
196
+ for (let i = 0; i < wanted; i++) {
197
+ this.#spawn();
198
+ }
199
+ }
200
+ #call(file, method, args) {
201
+ if (file.closed || this.#closed) {
202
+ return Promise.reject(closedError());
203
+ }
204
+ return new Promise((resolve, reject) => {
205
+ this.#queue.push({ request: { doc: file.id, method, args }, file, resolve, reject });
206
+ this.#pump();
207
+ });
208
+ }
209
+ async open(input, options = {}) {
210
+ const view = ArrayBuffer.isView(input) ? input : new Uint8Array(input);
211
+ const file = {
212
+ id: this.#nextFile++,
213
+ bytes: privateCopy(view),
214
+ password: options.password,
215
+ closed: false,
216
+ on: new Set(),
217
+ };
218
+ const call = (method, ...args) => this.#call(file, method, args);
219
+ const pageCount = (await call("pageCount"));
220
+ const close = async () => {
221
+ if (file.closed) {
222
+ return;
223
+ }
224
+ file.closed = true;
225
+ this.#queue = this.#queue.filter((task) => {
226
+ if (task.file === file) {
227
+ task.reject(closedError());
228
+ }
229
+ return task.file !== file;
230
+ });
231
+ // Each thread frees it when it gets there, past any call of it under way, which still answers.
232
+ for (const thread of file.on) {
233
+ thread.worker.postMessage({ id: this.#nextId++, doc: file.id, method: "close", args: [] });
234
+ }
235
+ file.on.clear();
236
+ };
237
+ return {
238
+ pageCount,
239
+ pageInfo: (index) => call("pageInfo", index),
240
+ text: (index) => call("text", index),
241
+ objects: (index) => call("objects", index),
242
+ annotations: (index) => call("annotations", index),
243
+ outline: () => call("outline"),
244
+ render: (index, renderOptions) => {
245
+ if (renderOptions?.signal) {
246
+ return Promise.reject(new TypeError("a pool's render takes no signal: a call that's running can't be stopped. pool.close() ends all of them."));
247
+ }
248
+ return call("render", index, { width: renderOptions?.width, maxHeight: renderOptions?.maxHeight });
249
+ },
250
+ close,
251
+ [Symbol.asyncDispose]: close,
252
+ };
253
+ }
254
+ stats() {
255
+ return { threads: this.#threads.length, queued: this.#queue.length };
256
+ }
257
+ close() {
258
+ if (this.#closed) {
259
+ return;
260
+ }
261
+ this.#closed = true;
262
+ for (const thread of [...this.#threads, ...this.#startingThreads]) {
263
+ thread.running?.reject(closedError());
264
+ void thread.worker?.terminate();
265
+ }
266
+ for (const task of this.#queue.splice(0)) {
267
+ task.reject(closedError());
268
+ }
269
+ }
270
+ [Symbol.dispose]() {
271
+ this.close();
272
+ }
273
+ }
274
+ export function startPool(options) {
275
+ const threads = options.threads ?? defaultThreads();
276
+ if (!(Number.isInteger(threads) && threads >= 1 && threads <= MAX_THREADS)) {
277
+ throw new RangeError(`threads must be a whole number from 1 to ${MAX_THREADS}, not ${threads}`);
278
+ }
279
+ if (!globalThis.process?.getBuiltinModule?.("node:worker_threads") && typeof Worker === "undefined") {
280
+ throw new PdfError("wrong-thread", "a pool needs worker threads, which this thread can't start");
281
+ }
282
+ // A thread's location isn't this one's, so a relative URL has to be resolved here.
283
+ const assetBase = options.assetBase !== undefined && typeof location !== "undefined"
284
+ ? new URL(options.assetBase, location.href).href
285
+ : options.assetBase;
286
+ return new Pool(threads, assetBase);
287
+ }
@@ -0,0 +1,98 @@
1
+ import { PdfError } from "./errors.ts";
2
+ import type { PdfDocument } from "./document.ts";
3
+ /**
4
+ * Where a ranged document's bytes come from: an HTTP(S) URL, or bytes in memory (copied, so
5
+ * the caller may reuse the buffer).
6
+ */
7
+ export type PdfRangeSource = string | URL | Uint8Array;
8
+ export type PdfRangedOpenOptions = {
9
+ password?: string;
10
+ /** Smallest fetch and the cache's unit, above 0. Default 64 KiB. */
11
+ chunkBytes?: number;
12
+ /** Fail (code "too-large") rather than fetch more than this in total. Default 64 MiB. */
13
+ maxBytes?: number;
14
+ /**
15
+ * Checked before the open and before every fetch. The thread is blocked during a fetch, so
16
+ * an abort cannot interrupt one; `timeoutMs` bounds that.
17
+ */
18
+ signal?: AbortSignal;
19
+ /** How long one fetch round may block the thread (code "timeout"). Default 30 s. */
20
+ timeoutMs?: number;
21
+ /**
22
+ * Wall time for the whole read, from the first fetch on, in place of counting rounds (code "timeout").
23
+ * A fetch in flight is cut when it runs out, and none starts after; that bounds the network waits, not
24
+ * the parsing between them. What's already fetched reads until a read fails, after which every call
25
+ * on the document throws that failure: reopen to go on. In WebKit a response that keeps trickling is
26
+ * not cut. Default: no limit.
27
+ */
28
+ totalTimeoutMs?: number;
29
+ };
30
+ export type PdfRangedStats = {
31
+ /** Bytes received from the source, across every request. */
32
+ bytesFetched: number;
33
+ requests: number;
34
+ /** Fetch rounds (round trips); a round's requests run in parallel. */
35
+ rounds: number;
36
+ /** The engine's reads, and the bytes they asked for: the parse work. */
37
+ reads: number;
38
+ readBytes: number;
39
+ /** The file's full size, from the first response. */
40
+ fileBytes: number;
41
+ };
42
+ /** A document opened over byte ranges. Use and close it like any other. */
43
+ export type PdfRangedDocument = PdfDocument & {
44
+ stats(): PdfRangedStats;
45
+ };
46
+ type Range = {
47
+ start: number;
48
+ end: number;
49
+ };
50
+ type Answer = {
51
+ start: number;
52
+ bytes: Uint8Array;
53
+ length: number;
54
+ etag: string | null;
55
+ status: number;
56
+ };
57
+ /** Fetches `ranges`; `limit` is how many bytes may still arrive. */
58
+ type Fetch = (ranges: Range[], timeoutMs: number, limit: number) => Answer[];
59
+ /** Fetched chunks of one file and the policy for fetching more. */
60
+ declare class RangeLoader {
61
+ #private;
62
+ length: number;
63
+ readonly chunkBytes: number;
64
+ /** Set once a fetch failed; every later read fails and every call throws it. */
65
+ failure: PdfError | null;
66
+ constructor(fetch: Fetch, options: {
67
+ chunkBytes: number;
68
+ maxBytes: number;
69
+ timeoutMs: number;
70
+ totalTimeoutMs: number;
71
+ signal: AbortSignal | undefined;
72
+ });
73
+ stats(): PdfRangedStats;
74
+ /** Fetches chunks `firstChunk..endChunk`, keeping only those not held yet. */
75
+ fetchChunks(firstChunk: number, endChunk: number): void;
76
+ /**
77
+ * The `startxref` offset in the fetched last chunk, if it names one. Null for a linearized
78
+ * file, whose trailer points at the first-page section near the head.
79
+ */
80
+ startxref(): number | null;
81
+ /**
82
+ * Copies [offset, offset + out.length) into `out`, fetching what is missing first. On a
83
+ * failed fetch it records the failure and fails the read; the call that made it then
84
+ * throws the failure.
85
+ */
86
+ read(offset: number, out: Uint8Array): boolean;
87
+ }
88
+ /**
89
+ * Starts a ranged read of `source`: the head, then (for a larger file) the tail and its last
90
+ * cross-reference section. Blocks while it fetches.
91
+ */
92
+ export declare function startRangeLoader(source: string | Uint8Array, options?: PdfRangedOpenOptions): RangeLoader;
93
+ /**
94
+ * The caller's source in a form that stays valid across an await: bytes copied, a URL as an
95
+ * absolute http(s) string. Runs before anything asynchronous.
96
+ */
97
+ export declare function ownedSource(source: PdfRangeSource): string | Uint8Array;
98
+ export {};