@2018kguo/recto 0.1.0 → 0.5.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.
- package/dist/assets.d.ts +11 -6
- package/dist/assets.js +123 -42
- package/dist/document.d.ts +144 -2
- package/dist/document.js +163 -14
- package/dist/engine.d.ts +24 -9
- package/dist/engine.js +236 -49
- package/dist/errors.d.ts +4 -2
- package/dist/errors.js +4 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +13 -0
- package/dist/pool-worker.d.ts +15 -0
- package/dist/pool-worker.js +65 -0
- package/dist/pool.d.ts +42 -0
- package/dist/pool.js +295 -0
- package/dist/ranged.d.ts +44 -9
- package/dist/ranged.js +571 -86
- package/package.json +2 -1
- package/wasm/FoxitDingbats.pfb +0 -0
- package/wasm/FoxitFixed.pfb +0 -0
- package/wasm/FoxitFixedBold.pfb +0 -0
- package/wasm/FoxitFixedBoldItalic.pfb +0 -0
- package/wasm/FoxitFixedItalic.pfb +0 -0
- package/wasm/FoxitSans.pfb +0 -0
- package/wasm/FoxitSansBold.pfb +0 -0
- package/wasm/FoxitSansBoldItalic.pfb +0 -0
- package/wasm/FoxitSansItalic.pfb +0 -0
- package/wasm/FoxitSerif.pfb +0 -0
- package/wasm/FoxitSerifBold.pfb +0 -0
- package/wasm/FoxitSerifBoldItalic.pfb +0 -0
- package/wasm/FoxitSerifItalic.pfb +0 -0
- package/wasm/FoxitSymbol.pfb +0 -0
- package/wasm/THIRD_PARTY_NOTICES +2 -158
- package/wasm/cmaps.deflate +0 -0
- package/wasm/pins.d.mts +1 -1
- package/wasm/pins.mjs +19 -3
- package/wasm/recto-more.wasm +0 -0
- package/wasm/recto-rare.wasm +0 -0
- package/wasm/recto-render.wasm +0 -0
- package/wasm/recto.gen.d.mts +2 -1
- package/wasm/recto.gen.mjs +213 -19
- package/wasm/recto.types.d.mts +83 -4
- package/wasm/recto.wasm +0 -0
- package/wasm/cmaps.brotli +0 -0
- package/wasm/fonts.bin +0 -0
package/dist/pool.js
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
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
|
+
#maxDecodedBytes;
|
|
43
|
+
#threads = [];
|
|
44
|
+
/** Started and not yet ready. */
|
|
45
|
+
#startingThreads = new Set();
|
|
46
|
+
#queue = [];
|
|
47
|
+
#nextFile = 0;
|
|
48
|
+
#nextId = 0;
|
|
49
|
+
#closed = false;
|
|
50
|
+
constructor(limit, assetBase, maxDecodedBytes) {
|
|
51
|
+
this.#limit = limit;
|
|
52
|
+
this.#ceiling = limit;
|
|
53
|
+
this.#assetBase = assetBase;
|
|
54
|
+
this.#maxDecodedBytes = maxDecodedBytes;
|
|
55
|
+
}
|
|
56
|
+
#spawn() {
|
|
57
|
+
const thread = { worker: null, running: null };
|
|
58
|
+
this.#startingThreads.add(thread);
|
|
59
|
+
let dead = false;
|
|
60
|
+
let waiting;
|
|
61
|
+
const onMessage = (message) => {
|
|
62
|
+
if (this.#closed) {
|
|
63
|
+
void thread.worker.terminate();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (message.startFailed) {
|
|
67
|
+
onDeath(message.failure.message, failureOf(message.failure));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (message.ready) {
|
|
71
|
+
clearTimeout(waiting);
|
|
72
|
+
this.#startingThreads.delete(thread);
|
|
73
|
+
this.#threads.push(thread);
|
|
74
|
+
// Idle, a thread doesn't keep a Node process alive; a call under way does.
|
|
75
|
+
thread.worker.unref?.();
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const task = thread.running;
|
|
79
|
+
thread.running = null;
|
|
80
|
+
thread.worker.unref?.();
|
|
81
|
+
// It'll need the file again: this was the call that was to open it.
|
|
82
|
+
if (!message.opened) {
|
|
83
|
+
task.file.on.delete(thread);
|
|
84
|
+
}
|
|
85
|
+
if (message.failure) {
|
|
86
|
+
task.reject(failureOf(message.failure));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
task.resolve(message.result);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
this.#pump();
|
|
93
|
+
};
|
|
94
|
+
// In Node a failure is an `error` and then an `exit`.
|
|
95
|
+
const onDeath = (reason, typed) => {
|
|
96
|
+
if (dead || this.#closed) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
dead = true;
|
|
100
|
+
clearTimeout(waiting);
|
|
101
|
+
void thread.worker?.terminate();
|
|
102
|
+
const failure = typed ?? new PdfError("crash", `a pool thread stopped: ${String(reason)}`, reason);
|
|
103
|
+
if (this.#startingThreads.delete(thread)) {
|
|
104
|
+
// It couldn't start, so another wouldn't either: run on what's left, or fail what's waiting.
|
|
105
|
+
this.#limit = this.#threads.length + this.#startingThreads.size;
|
|
106
|
+
if (this.#limit === 0) {
|
|
107
|
+
// The next call tries again: what stopped it, a missing file say, may be back by then.
|
|
108
|
+
this.#limit = this.#ceiling;
|
|
109
|
+
for (const task of this.#queue.splice(0)) {
|
|
110
|
+
task.reject(failure);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
this.#threads.splice(this.#threads.indexOf(thread), 1);
|
|
116
|
+
}
|
|
117
|
+
thread.running?.file.on.delete(thread);
|
|
118
|
+
thread.running?.reject(failure);
|
|
119
|
+
thread.running = null;
|
|
120
|
+
this.#pump();
|
|
121
|
+
};
|
|
122
|
+
waiting = setTimeout(() => onDeath(`it hadn't started after ${START_TIMEOUT_MS} ms`), START_TIMEOUT_MS);
|
|
123
|
+
waiting.unref?.();
|
|
124
|
+
try {
|
|
125
|
+
const threads = globalThis.process?.getBuiltinModule?.("node:worker_threads");
|
|
126
|
+
if (threads) {
|
|
127
|
+
const worker = new threads.Worker(new URL("./pool-worker.js", import.meta.url), { execArgv: [] });
|
|
128
|
+
worker.on("message", onMessage);
|
|
129
|
+
worker.on("messageerror", onDeath);
|
|
130
|
+
worker.on("error", onDeath);
|
|
131
|
+
worker.on("exit", onDeath);
|
|
132
|
+
thread.worker = worker;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// Not `new URL("…", import.meta.url)`: a bundler would emit the file as it stands for that.
|
|
136
|
+
const here = import.meta.url;
|
|
137
|
+
const worker = new URL(here).origin === location.origin
|
|
138
|
+
? // Written out in full: this is the form bundlers recognise, and they emit the worker for it.
|
|
139
|
+
new Worker(new URL("./pool-worker.js", import.meta.url), { type: "module" })
|
|
140
|
+
: // A worker's script has to be of this page's origin, so from a CDN it's one line that imports the
|
|
141
|
+
// real one, which the CDN has to allow with CORS, as it does the wasm.
|
|
142
|
+
// ponytail: the blob's URL lives as long as the page; one per thread started.
|
|
143
|
+
new Worker(URL.createObjectURL(new Blob([`import ${JSON.stringify(new URL("./pool-worker.js", here).href)};`], { type: "text/javascript" })), { type: "module" });
|
|
144
|
+
worker.onmessage = ({ data }) => onMessage(data);
|
|
145
|
+
worker.onmessageerror = () => onDeath("a message from it could not be read");
|
|
146
|
+
worker.onerror = (event) => {
|
|
147
|
+
event.preventDefault();
|
|
148
|
+
onDeath(event.message || "its script did not load");
|
|
149
|
+
};
|
|
150
|
+
thread.worker = worker;
|
|
151
|
+
}
|
|
152
|
+
const start = { assetBase: this.#assetBase, maxDecodedBytes: this.#maxDecodedBytes };
|
|
153
|
+
thread.worker.postMessage(start);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
onDeath(error);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** Gives each free thread work, the oldest call on a document it has if there is one. */
|
|
160
|
+
#pump() {
|
|
161
|
+
for (const thread of this.#threads) {
|
|
162
|
+
if (thread.running || this.#queue.length === 0) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
let at = this.#queue.findIndex((task) => task.file.on.has(thread));
|
|
166
|
+
if (at < 0) {
|
|
167
|
+
// Not a copy of the file for a call that a thread already holding it will take next.
|
|
168
|
+
const free = this.#threads.filter((other) => !other.running);
|
|
169
|
+
at = this.#queue.findIndex((task) => !free.some((other) => task.file.on.has(other)));
|
|
170
|
+
}
|
|
171
|
+
if (at < 0) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const [task] = this.#queue.splice(at, 1);
|
|
175
|
+
thread.running = task;
|
|
176
|
+
thread.worker.ref?.();
|
|
177
|
+
const { file } = task;
|
|
178
|
+
const had = file.on.has(thread);
|
|
179
|
+
const open = had ? {} : { open: { bytes: file.bytes, password: file.password } };
|
|
180
|
+
file.on.add(thread);
|
|
181
|
+
try {
|
|
182
|
+
thread.worker.postMessage({ ...task.request, id: this.#nextId++, ...open });
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
// An argument that can't cross. Nothing reached the thread.
|
|
186
|
+
thread.running = null;
|
|
187
|
+
thread.worker.unref?.();
|
|
188
|
+
if (!had) {
|
|
189
|
+
file.on.delete(thread);
|
|
190
|
+
}
|
|
191
|
+
task.reject(error);
|
|
192
|
+
// Nothing will come back from the thread to take the next call.
|
|
193
|
+
this.#pump();
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const wanted = Math.min(this.#limit - this.#threads.length, this.#queue.length) - this.#startingThreads.size;
|
|
198
|
+
for (let i = 0; i < wanted; i++) {
|
|
199
|
+
this.#spawn();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
#call(file, method, args) {
|
|
203
|
+
if (file.closed || this.#closed) {
|
|
204
|
+
return Promise.reject(closedError());
|
|
205
|
+
}
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
this.#queue.push({ request: { doc: file.id, method, args }, file, resolve, reject });
|
|
208
|
+
this.#pump();
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
async open(input, options = {}) {
|
|
212
|
+
const view = ArrayBuffer.isView(input) ? input : new Uint8Array(input);
|
|
213
|
+
const file = {
|
|
214
|
+
id: this.#nextFile++,
|
|
215
|
+
bytes: privateCopy(view),
|
|
216
|
+
password: options.password,
|
|
217
|
+
closed: false,
|
|
218
|
+
on: new Set(),
|
|
219
|
+
};
|
|
220
|
+
const call = (method, ...args) => this.#call(file, method, args);
|
|
221
|
+
const pageCount = (await call("pageCount"));
|
|
222
|
+
const close = async () => {
|
|
223
|
+
if (file.closed) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
file.closed = true;
|
|
227
|
+
this.#queue = this.#queue.filter((task) => {
|
|
228
|
+
if (task.file === file) {
|
|
229
|
+
task.reject(closedError());
|
|
230
|
+
}
|
|
231
|
+
return task.file !== file;
|
|
232
|
+
});
|
|
233
|
+
// Each thread frees it when it gets there, past any call of it under way, which still answers.
|
|
234
|
+
for (const thread of file.on) {
|
|
235
|
+
thread.worker.postMessage({ id: this.#nextId++, doc: file.id, method: "close", args: [] });
|
|
236
|
+
}
|
|
237
|
+
file.on.clear();
|
|
238
|
+
};
|
|
239
|
+
return {
|
|
240
|
+
pageCount,
|
|
241
|
+
pageInfo: (index) => call("pageInfo", index),
|
|
242
|
+
text: (index) => call("text", index),
|
|
243
|
+
objects: (index) => call("objects", index),
|
|
244
|
+
annotations: (index) => call("annotations", index),
|
|
245
|
+
links: (index) => call("links", index),
|
|
246
|
+
outline: () => call("outline"),
|
|
247
|
+
render: ((index, renderOptions) => {
|
|
248
|
+
if (renderOptions?.signal) {
|
|
249
|
+
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."));
|
|
250
|
+
}
|
|
251
|
+
const { width, maxHeight, region } = renderOptions ?? {};
|
|
252
|
+
return call("render", index, region === undefined ? { width, maxHeight } : { width, maxHeight, region });
|
|
253
|
+
}),
|
|
254
|
+
close,
|
|
255
|
+
[Symbol.asyncDispose]: close,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
stats() {
|
|
259
|
+
return { threads: this.#threads.length, queued: this.#queue.length };
|
|
260
|
+
}
|
|
261
|
+
close() {
|
|
262
|
+
if (this.#closed) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
this.#closed = true;
|
|
266
|
+
for (const thread of [...this.#threads, ...this.#startingThreads]) {
|
|
267
|
+
thread.running?.reject(closedError());
|
|
268
|
+
void thread.worker?.terminate();
|
|
269
|
+
}
|
|
270
|
+
for (const task of this.#queue.splice(0)) {
|
|
271
|
+
task.reject(closedError());
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
[Symbol.dispose]() {
|
|
275
|
+
this.close();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
export function startPool(options) {
|
|
279
|
+
const threads = options.threads ?? defaultThreads();
|
|
280
|
+
if (!(Number.isInteger(threads) && threads >= 1 && threads <= MAX_THREADS)) {
|
|
281
|
+
throw new RangeError(`threads must be a whole number from 1 to ${MAX_THREADS}, not ${threads}`);
|
|
282
|
+
}
|
|
283
|
+
if (!globalThis.process?.getBuiltinModule?.("node:worker_threads") && typeof Worker === "undefined") {
|
|
284
|
+
throw new PdfError("wrong-thread", "a pool needs worker threads, which this thread can't start");
|
|
285
|
+
}
|
|
286
|
+
// A thread's location isn't this one's, so a relative URL has to be resolved here.
|
|
287
|
+
const assetBase = options.assetBase !== undefined && typeof location !== "undefined"
|
|
288
|
+
? new URL(options.assetBase, location.href).href
|
|
289
|
+
: options.assetBase;
|
|
290
|
+
const maxDecodedBytes = options.maxDecodedBytes;
|
|
291
|
+
if (maxDecodedBytes !== undefined && !(typeof maxDecodedBytes === "number" && maxDecodedBytes >= 0)) {
|
|
292
|
+
throw new RangeError("maxDecodedBytes must be a number of bytes, 0 or more");
|
|
293
|
+
}
|
|
294
|
+
return new Pool(threads, assetBase, maxDecodedBytes);
|
|
295
|
+
}
|
package/dist/ranged.d.ts
CHANGED
|
@@ -18,6 +18,14 @@ export type PdfRangedOpenOptions = {
|
|
|
18
18
|
signal?: AbortSignal;
|
|
19
19
|
/** How long one fetch round may block the thread (code "timeout"). Default 30 s. */
|
|
20
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;
|
|
21
29
|
};
|
|
22
30
|
export type PdfRangedStats = {
|
|
23
31
|
/** Bytes received from the source, across every request. */
|
|
@@ -35,18 +43,36 @@ export type PdfRangedStats = {
|
|
|
35
43
|
export type PdfRangedDocument = PdfDocument & {
|
|
36
44
|
stats(): PdfRangedStats;
|
|
37
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Bytes `start..end`. With `yieldTail`, read no further than that many bytes before the file's
|
|
48
|
+
* end, where a suffix asked for beside it starts, so no byte comes twice.
|
|
49
|
+
*/
|
|
38
50
|
type Range = {
|
|
39
51
|
start: number;
|
|
40
52
|
end: number;
|
|
53
|
+
yieldTail?: number;
|
|
54
|
+
};
|
|
55
|
+
/** The file's last `suffix` bytes (`bytes=-n`), asked for before its size is known. */
|
|
56
|
+
type Suffix = {
|
|
57
|
+
suffix: number;
|
|
41
58
|
};
|
|
59
|
+
type Request = Range | Suffix;
|
|
60
|
+
/** `received`: the bytes read off the response, `bytes` and any read past it. */
|
|
42
61
|
type Answer = {
|
|
43
62
|
start: number;
|
|
44
63
|
bytes: Uint8Array;
|
|
45
64
|
length: number;
|
|
46
65
|
etag: string | null;
|
|
66
|
+
status: number;
|
|
67
|
+
received: number;
|
|
47
68
|
};
|
|
48
|
-
/**
|
|
49
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Fetches `requests` in parallel; `limit` is how many bytes may still arrive. A range that
|
|
71
|
+
* fails throws. A suffix that isn't answered as one (a server that ignores or refuses it)
|
|
72
|
+
* gives null, and isn't counted: the caller asks for the tail by its offset instead.
|
|
73
|
+
*/
|
|
74
|
+
type Fetch = (requests: Request[], timeoutMs: number, limit: number) => (Answer | null)[];
|
|
75
|
+
type FetchAsync = (requests: Request[], timeoutMs: number, limit: number) => Promise<(Answer | null)[]>;
|
|
50
76
|
/** Fetched chunks of one file and the policy for fetching more. */
|
|
51
77
|
declare class RangeLoader {
|
|
52
78
|
#private;
|
|
@@ -58,28 +84,37 @@ declare class RangeLoader {
|
|
|
58
84
|
chunkBytes: number;
|
|
59
85
|
maxBytes: number;
|
|
60
86
|
timeoutMs: number;
|
|
87
|
+
totalTimeoutMs: number;
|
|
61
88
|
signal: AbortSignal | undefined;
|
|
62
89
|
});
|
|
63
90
|
stats(): PdfRangedStats;
|
|
64
91
|
/** Fetches chunks `firstChunk..endChunk`, keeping only those not held yet. */
|
|
65
92
|
fetchChunks(firstChunk: number, endChunk: number): void;
|
|
93
|
+
/**
|
|
94
|
+
* The first round: the head and, in parallel, the tail as a suffix, then the last
|
|
95
|
+
* cross-reference section if the tail didn't hold it. A file the head covers needs no more;
|
|
96
|
+
* a server that doesn't answer the suffix gets the tail by offset in a round of its own.
|
|
97
|
+
*/
|
|
98
|
+
begin(fetchAsync: FetchAsync | null): Promise<void>;
|
|
66
99
|
/**
|
|
67
100
|
* The `startxref` offset in the fetched last chunk, if it names one. Null for a linearized
|
|
68
101
|
* file, whose trailer points at the first-page section near the head.
|
|
69
102
|
*/
|
|
70
103
|
startxref(): number | null;
|
|
71
104
|
/**
|
|
72
|
-
* Copies [offset, offset + out.length) into `out`, fetching what is missing first
|
|
73
|
-
*
|
|
74
|
-
*
|
|
105
|
+
* Copies [offset, offset + out.length) into `out`, fetching what is missing first, and if it
|
|
106
|
+
* fetches, `ahead` bytes past it too, as far as the budget allows: the engine's guess at what
|
|
107
|
+
* it reads next, which then costs no round trip of its own. On a failed fetch it records the
|
|
108
|
+
* failure and fails the read; the call that made it then throws the failure.
|
|
75
109
|
*/
|
|
76
|
-
read(offset: number, out: Uint8Array): boolean;
|
|
110
|
+
read(offset: number, out: Uint8Array, ahead?: number): boolean;
|
|
77
111
|
}
|
|
78
112
|
/**
|
|
79
|
-
* Starts a ranged read of `source`: the head, then
|
|
80
|
-
*
|
|
113
|
+
* Starts a ranged read of `source`: the head and tail, then the last cross-reference section
|
|
114
|
+
* if the tail didn't hold it. Blocks while it fetches, except in a browser, where these
|
|
115
|
+
* rounds are `fetch` calls.
|
|
81
116
|
*/
|
|
82
|
-
export declare function startRangeLoader(source: string | Uint8Array, options?: PdfRangedOpenOptions): RangeLoader
|
|
117
|
+
export declare function startRangeLoader(source: string | Uint8Array, options?: PdfRangedOpenOptions): Promise<RangeLoader>;
|
|
83
118
|
/**
|
|
84
119
|
* The caller's source in a form that stays valid across an await: bytes copied, a URL as an
|
|
85
120
|
* absolute http(s) string. Runs before anything asynchronous.
|