@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.
- package/LICENSE +26 -0
- package/README.md +25 -2
- package/dist/assets.d.ts +19 -0
- package/dist/assets.js +250 -0
- package/dist/document.d.ts +307 -0
- package/dist/document.js +405 -0
- package/dist/engine.d.ts +62 -0
- package/dist/engine.js +383 -0
- package/dist/errors.d.ts +36 -0
- package/dist/errors.js +14 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +23 -0
- package/dist/page-measures.d.ts +34 -0
- package/dist/page-measures.js +166 -0
- package/dist/pool-worker.d.ts +14 -0
- package/dist/pool-worker.js +65 -0
- package/dist/pool.d.ts +36 -0
- package/dist/pool.js +287 -0
- package/dist/ranged.d.ts +98 -0
- package/dist/ranged.js +497 -0
- package/dist/reading-order.d.ts +14 -0
- package/dist/reading-order.js +173 -0
- package/dist/sha256.d.ts +1 -0
- package/dist/sha256.js +78 -0
- package/dist/text-items.d.ts +49 -0
- package/dist/text-items.js +272 -0
- package/package.json +48 -4
- 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 +2362 -0
- package/wasm/cjk-fallback.ttf +0 -0
- package/wasm/cmaps.deflate +0 -0
- package/wasm/pins.d.mts +1 -0
- package/wasm/pins.mjs +23 -0
- 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 +3 -0
- package/wasm/recto.gen.mjs +1203 -0
- package/wasm/recto.types.d.mts +295 -0
- package/wasm/recto.wasm +0 -0
package/dist/document.js
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
// One open PDF. Every coordinate this returns is in the viewer's frame: PDF points, top-left
|
|
2
|
+
// origin, with /Rotate and the crop box applied.
|
|
3
|
+
import { PdfError } from "./errors.js";
|
|
4
|
+
/** `PdfPageText.flags` bit: a space or line break the engine inferred; not in the content stream. */
|
|
5
|
+
export const CHAR_GENERATED = 1;
|
|
6
|
+
/** `PdfPageText.flags` bit: the glyph has no Unicode mapping, so its text is unreadable. */
|
|
7
|
+
export const CHAR_UNMAPPED = 2;
|
|
8
|
+
/** Entry `i` of a `boxes` column. */
|
|
9
|
+
export function boxAt(boxes, i) {
|
|
10
|
+
return {
|
|
11
|
+
x: boxes[4 * i] ?? 0,
|
|
12
|
+
y: boxes[4 * i + 1] ?? 0,
|
|
13
|
+
width: boxes[4 * i + 2] ?? 0,
|
|
14
|
+
height: boxes[4 * i + 3] ?? 0,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** The text of chars `first..last` (inclusive). */
|
|
18
|
+
export function charsText(page, first, last) {
|
|
19
|
+
let out = "";
|
|
20
|
+
for (let i = first; i <= last; i++) {
|
|
21
|
+
out += String.fromCodePoint(page.codepoints[i] ?? 0xff_fd);
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
/** Segment kinds, in the low bits of `PdfPathSegments.kinds`. */
|
|
26
|
+
export const SEGMENT_LINE = 0;
|
|
27
|
+
export const SEGMENT_BEZIER = 1;
|
|
28
|
+
export const SEGMENT_MOVE = 2;
|
|
29
|
+
/** `PdfPathSegments.kinds` bit: the segment closes its subpath. */
|
|
30
|
+
export const SEGMENT_CLOSE = 4;
|
|
31
|
+
/**
|
|
32
|
+
* `PdfPathSegments.kinds` bit on a move: its subpath is a rectangle drawn by
|
|
33
|
+
* `re` (pdf.js counts only those as rectangles).
|
|
34
|
+
*/
|
|
35
|
+
export const SEGMENT_RECT = 8;
|
|
36
|
+
/** `paths.flags` bits. */
|
|
37
|
+
export const PATH_FILL = 1;
|
|
38
|
+
export const PATH_STROKE = 2;
|
|
39
|
+
export const ANNOT_SUBTYPE = {
|
|
40
|
+
text: 1,
|
|
41
|
+
link: 2,
|
|
42
|
+
freeText: 3,
|
|
43
|
+
stamp: 13,
|
|
44
|
+
widget: 20,
|
|
45
|
+
};
|
|
46
|
+
const DEFAULT_MAX_HEIGHT = 4096;
|
|
47
|
+
const MAX_DIMENSION = 16_384;
|
|
48
|
+
/** What a call into the instance threw, as what the caller should see. */
|
|
49
|
+
export function engineFailure(instance, error) {
|
|
50
|
+
if (error instanceof PdfError) {
|
|
51
|
+
return error;
|
|
52
|
+
}
|
|
53
|
+
// An exhausted call stack in WebAssembly is a RangeError, and leaves the instance in the
|
|
54
|
+
// same unknown state as a trap. The argument checks that throw RangeError run before any call.
|
|
55
|
+
if (error instanceof WebAssembly.RuntimeError || error instanceof RangeError) {
|
|
56
|
+
instance.dead = true;
|
|
57
|
+
return new PdfError("crash", `the engine crashed: ${error.message}`, error);
|
|
58
|
+
}
|
|
59
|
+
const code = error?.code;
|
|
60
|
+
if (typeof code === "string") {
|
|
61
|
+
return new PdfError(code, error.message);
|
|
62
|
+
}
|
|
63
|
+
return error;
|
|
64
|
+
}
|
|
65
|
+
/** Runs `read` over an object wasm-bindgen allocated, freeing it even if `read` throws. */
|
|
66
|
+
function consume(owned, read) {
|
|
67
|
+
try {
|
|
68
|
+
return read(owned);
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
owned.free();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function toText(t) {
|
|
75
|
+
return consume(t, () => {
|
|
76
|
+
const fontFlags = t.fontFlags;
|
|
77
|
+
const fontWeights = t.fontWeights;
|
|
78
|
+
const fontEmbedded = t.fontEmbedded;
|
|
79
|
+
const fontAscents = t.fontAscents;
|
|
80
|
+
const fontDescents = t.fontDescents;
|
|
81
|
+
const fonts = t.fontNames.map((name, i) => ({
|
|
82
|
+
name,
|
|
83
|
+
flags: fontFlags[i] ?? 0,
|
|
84
|
+
weight: fontWeights[i] ?? 0,
|
|
85
|
+
embedded: fontEmbedded[i] === 1,
|
|
86
|
+
ascent: fontAscents[i] ?? 0,
|
|
87
|
+
descent: fontDescents[i] ?? 0,
|
|
88
|
+
}));
|
|
89
|
+
const wordFirstChar = t.wordFirstChar;
|
|
90
|
+
const runEms = t.runEms;
|
|
91
|
+
return {
|
|
92
|
+
text: t.text,
|
|
93
|
+
count: t.count,
|
|
94
|
+
codepoints: t.codepoints,
|
|
95
|
+
boxes: t.boxes,
|
|
96
|
+
fontSizes: t.fontSizes,
|
|
97
|
+
tfSizes: t.tfSizes,
|
|
98
|
+
truncated: t.truncated,
|
|
99
|
+
angles: t.angles,
|
|
100
|
+
fills: t.fills,
|
|
101
|
+
fontIds: t.fontIds,
|
|
102
|
+
flags: t.flags,
|
|
103
|
+
fonts,
|
|
104
|
+
words: {
|
|
105
|
+
count: wordFirstChar.length,
|
|
106
|
+
firstChar: wordFirstChar,
|
|
107
|
+
lastChar: t.wordLastChar,
|
|
108
|
+
boxes: t.wordBoxes,
|
|
109
|
+
},
|
|
110
|
+
runIds: t.runIds,
|
|
111
|
+
runs: { count: runEms.length, origins: t.runOrigins, ems: runEms },
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function toObjects(o) {
|
|
116
|
+
return consume(o, () => {
|
|
117
|
+
const imageBoxes = o.imageBoxes;
|
|
118
|
+
const pathFlags = o.pathFlags;
|
|
119
|
+
const segmentKinds = o.segmentKinds;
|
|
120
|
+
return {
|
|
121
|
+
textObjects: o.textObjects,
|
|
122
|
+
truncated: o.truncated,
|
|
123
|
+
images: {
|
|
124
|
+
count: imageBoxes.length / 4,
|
|
125
|
+
boxes: imageBoxes,
|
|
126
|
+
pixels: o.imagePixels,
|
|
127
|
+
},
|
|
128
|
+
paths: {
|
|
129
|
+
count: pathFlags.length,
|
|
130
|
+
boxes: o.pathBoxes,
|
|
131
|
+
flags: pathFlags,
|
|
132
|
+
start: o.pathStart,
|
|
133
|
+
},
|
|
134
|
+
segments: {
|
|
135
|
+
count: segmentKinds.length,
|
|
136
|
+
points: o.segmentPoints,
|
|
137
|
+
kinds: segmentKinds,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function toAnnotations(a) {
|
|
143
|
+
return consume(a, () => {
|
|
144
|
+
const subtypes = a.subtypes;
|
|
145
|
+
const boxes = a.boxes;
|
|
146
|
+
const contents = a.contents;
|
|
147
|
+
const hasField = a.hasField;
|
|
148
|
+
const fieldTypes = a.fieldTypes;
|
|
149
|
+
const fieldValues = a.fieldValues;
|
|
150
|
+
const objects = a.appearanceObjects;
|
|
151
|
+
const textObjects = a.appearanceTextObjects;
|
|
152
|
+
const text = a.appearanceText;
|
|
153
|
+
const out = [];
|
|
154
|
+
for (let i = 0; i < a.count; i++) {
|
|
155
|
+
out.push({
|
|
156
|
+
subtype: subtypes[i] ?? 0,
|
|
157
|
+
box: boxAt(boxes, i),
|
|
158
|
+
contents: contents[i] ?? "",
|
|
159
|
+
field: hasField[i] === 1
|
|
160
|
+
? { type: fieldTypes[i] ?? "", value: fieldValues[i] ?? "" }
|
|
161
|
+
: null,
|
|
162
|
+
appearanceObjects: objects[i] ?? 0,
|
|
163
|
+
appearanceTextObjects: textObjects[i] ?? 0,
|
|
164
|
+
appearanceText: text[i] ?? "",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
const DEFAULT_SLICE_MS = 8;
|
|
171
|
+
/** Back to the event loop, without the 4 ms a nested timer waits in a browser. */
|
|
172
|
+
function nextTask() {
|
|
173
|
+
return new Promise((resolve) => {
|
|
174
|
+
const { port1, port2 } = new MessageChannel();
|
|
175
|
+
port1.onmessage = () => {
|
|
176
|
+
port1.close();
|
|
177
|
+
resolve();
|
|
178
|
+
};
|
|
179
|
+
port2.postMessage(0);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
export class RectoDocument {
|
|
183
|
+
pageCount;
|
|
184
|
+
#closed = false;
|
|
185
|
+
#doc;
|
|
186
|
+
#instance;
|
|
187
|
+
/** The same file on another instance. */
|
|
188
|
+
#reopen;
|
|
189
|
+
/** A ranged read's failure, thrown by the call whose fetch it failed. */
|
|
190
|
+
#fetchFailure;
|
|
191
|
+
constructor(instance, doc, reopen, fetchFailure = () => null) {
|
|
192
|
+
this.#instance = instance;
|
|
193
|
+
this.#doc = doc;
|
|
194
|
+
this.#reopen = reopen;
|
|
195
|
+
this.#fetchFailure = fetchFailure;
|
|
196
|
+
this.pageCount = doc.pageCount;
|
|
197
|
+
instance.openDocuments += 1;
|
|
198
|
+
instance.documents.open += 1;
|
|
199
|
+
}
|
|
200
|
+
#check(index) {
|
|
201
|
+
if (this.#closed) {
|
|
202
|
+
throw new PdfError("closed", "this document is closed");
|
|
203
|
+
}
|
|
204
|
+
if (!(Number.isInteger(index) && index >= 0 && index < this.pageCount)) {
|
|
205
|
+
throw new RangeError(`page ${index} is out of range (0..${this.pageCount - 1})`);
|
|
206
|
+
}
|
|
207
|
+
this.#checkInstance();
|
|
208
|
+
}
|
|
209
|
+
#checkInstance() {
|
|
210
|
+
if (this.#instance.abandoned) {
|
|
211
|
+
this.#moveOffAbandoned();
|
|
212
|
+
}
|
|
213
|
+
if (this.#instance.dead) {
|
|
214
|
+
throw new PdfError("crash", "the engine crashed earlier on this thread; open the document again");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
#call(fn) {
|
|
218
|
+
let result;
|
|
219
|
+
try {
|
|
220
|
+
result = fn();
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
// A failed fetch or asset surfaces inside the engine as some other error.
|
|
224
|
+
const failure = this.#instance.failure ?? this.#fetchFailure();
|
|
225
|
+
if (failure && !(error instanceof WebAssembly.RuntimeError)) {
|
|
226
|
+
throw failure;
|
|
227
|
+
}
|
|
228
|
+
throw engineFailure(this.#instance, error);
|
|
229
|
+
}
|
|
230
|
+
// Or as a page that reads short: blank text must not pass for the page's text.
|
|
231
|
+
const failure = this.#instance.failure ?? this.#fetchFailure();
|
|
232
|
+
if (failure) {
|
|
233
|
+
throw failure;
|
|
234
|
+
}
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
/** The call that failed told the caller; this one is a fresh start. */
|
|
238
|
+
#moveOffAbandoned() {
|
|
239
|
+
const old = this.#instance;
|
|
240
|
+
let moved;
|
|
241
|
+
try {
|
|
242
|
+
moved = this.#reopen(this.#doc, old);
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
throw engineFailure(old, error);
|
|
246
|
+
}
|
|
247
|
+
old.openDocuments -= 1;
|
|
248
|
+
old.onClose?.();
|
|
249
|
+
this.#instance = moved.instance;
|
|
250
|
+
this.#doc = moved.doc;
|
|
251
|
+
moved.instance.openDocuments += 1;
|
|
252
|
+
}
|
|
253
|
+
/** Loaded ahead of the call, so that failing to get it leaves nothing half-done. */
|
|
254
|
+
#needsRender() {
|
|
255
|
+
this.#call(() => this.#instance.loadPart("recto-render.wasm"));
|
|
256
|
+
}
|
|
257
|
+
pageInfo(index) {
|
|
258
|
+
this.#check(index);
|
|
259
|
+
return this.#call(() => {
|
|
260
|
+
const [width = 0, height = 0, rotation = 0] = this.#doc.pageInfo(index);
|
|
261
|
+
return { width, height, rotation: rotation };
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
text(index) {
|
|
265
|
+
this.#check(index);
|
|
266
|
+
return this.#call(() => toText(this.#doc.pageText(index)));
|
|
267
|
+
}
|
|
268
|
+
objects(index) {
|
|
269
|
+
this.#check(index);
|
|
270
|
+
this.#needsRender();
|
|
271
|
+
return this.#call(() => toObjects(this.#doc.objects(index)));
|
|
272
|
+
}
|
|
273
|
+
pageLabels() {
|
|
274
|
+
if (this.#closed) {
|
|
275
|
+
throw new PdfError("closed", "this document is closed");
|
|
276
|
+
}
|
|
277
|
+
this.#checkInstance();
|
|
278
|
+
return this.#call(() => this.#doc.pageLabels());
|
|
279
|
+
}
|
|
280
|
+
structureTruncated() {
|
|
281
|
+
if (this.#closed) {
|
|
282
|
+
throw new PdfError("closed", "this document is closed");
|
|
283
|
+
}
|
|
284
|
+
this.#checkInstance();
|
|
285
|
+
const [pages, pageLabels, outline] = this.#call(() => this.#doc.structureTruncated());
|
|
286
|
+
return { pages: pages === 1, pageLabels: pageLabels === 1, outline: outline === 1 };
|
|
287
|
+
}
|
|
288
|
+
outline() {
|
|
289
|
+
if (this.#closed) {
|
|
290
|
+
throw new PdfError("closed", "this document is closed");
|
|
291
|
+
}
|
|
292
|
+
this.#checkInstance();
|
|
293
|
+
return this.#call(() => {
|
|
294
|
+
const top = [];
|
|
295
|
+
// The open item at each level; an item's parent is the last one a level up.
|
|
296
|
+
const open = [];
|
|
297
|
+
for (const [title, url, level, page] of this.#doc.outline()) {
|
|
298
|
+
const item = { title, page, url: url || null, children: [] };
|
|
299
|
+
(level === 0 ? top : (open[level - 1]?.children ?? top)).push(item);
|
|
300
|
+
open[level] = item;
|
|
301
|
+
}
|
|
302
|
+
return top;
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
annotations(index) {
|
|
306
|
+
this.#check(index);
|
|
307
|
+
return this.#call(() => toAnnotations(this.#doc.annotations(index)));
|
|
308
|
+
}
|
|
309
|
+
scan(index) {
|
|
310
|
+
this.#check(index);
|
|
311
|
+
this.#needsRender();
|
|
312
|
+
return this.#call(() => consume(this.#doc.scan(index), (scan) => ({
|
|
313
|
+
text: toText(scan.takeText()),
|
|
314
|
+
objects: toObjects(scan.takeObjects()),
|
|
315
|
+
annotations: toAnnotations(scan.takeAnnotations()),
|
|
316
|
+
})));
|
|
317
|
+
}
|
|
318
|
+
textAndAnnotations(index) {
|
|
319
|
+
this.#check(index);
|
|
320
|
+
return this.#call(() => consume(this.#doc.textAndAnnotations(index), (scan) => ({
|
|
321
|
+
text: toText(scan.takeText()),
|
|
322
|
+
annotations: toAnnotations(scan.takeAnnotations()),
|
|
323
|
+
})));
|
|
324
|
+
}
|
|
325
|
+
render(index, options) {
|
|
326
|
+
this.#check(index);
|
|
327
|
+
const maxHeight = options.maxHeight ?? DEFAULT_MAX_HEIGHT;
|
|
328
|
+
if (!(options.width > 0 && options.width <= MAX_DIMENSION) ||
|
|
329
|
+
!(maxHeight > 0)) {
|
|
330
|
+
throw new RangeError(`render width must be in 1..${MAX_DIMENSION} and maxHeight above 0`);
|
|
331
|
+
}
|
|
332
|
+
this.#needsRender();
|
|
333
|
+
return this.#call(() => consume(this.#doc.render(index, options.width, maxHeight), (page) => ({
|
|
334
|
+
width: page.width,
|
|
335
|
+
height: page.height,
|
|
336
|
+
truncated: page.truncated,
|
|
337
|
+
rgba: page.takeRgba(),
|
|
338
|
+
})));
|
|
339
|
+
}
|
|
340
|
+
async renderAsync(index, options) {
|
|
341
|
+
this.#check(index);
|
|
342
|
+
const maxHeight = options.maxHeight ?? DEFAULT_MAX_HEIGHT;
|
|
343
|
+
if (!(options.width > 0 && options.width <= MAX_DIMENSION) ||
|
|
344
|
+
!(maxHeight > 0)) {
|
|
345
|
+
throw new RangeError(`render width must be in 1..${MAX_DIMENSION} and maxHeight above 0`);
|
|
346
|
+
}
|
|
347
|
+
this.#needsRender();
|
|
348
|
+
const instance = this.#instance;
|
|
349
|
+
const sliceMs = options.sliceMs ?? DEFAULT_SLICE_MS;
|
|
350
|
+
const task = this.#call(() => this.#doc.renderTask(index, options.width, maxHeight));
|
|
351
|
+
const stopIfAsked = () => {
|
|
352
|
+
if (options.signal?.aborted) {
|
|
353
|
+
throw new PdfError("aborted", "the signal was aborted", options.signal.reason);
|
|
354
|
+
}
|
|
355
|
+
if (this.#closed) {
|
|
356
|
+
throw new PdfError("closed", "this document is closed");
|
|
357
|
+
}
|
|
358
|
+
if (instance.dead) {
|
|
359
|
+
throw new PdfError("crash", "the engine crashed while this render was paused; open the document again");
|
|
360
|
+
}
|
|
361
|
+
if (instance.abandoned) {
|
|
362
|
+
throw instance.failure ?? new PdfError("missing-asset", "a file the engine needed failed to load while this render was paused; render again");
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
try {
|
|
366
|
+
stopIfAsked();
|
|
367
|
+
while (!this.#call(() => task.step(sliceMs))) {
|
|
368
|
+
await nextTask();
|
|
369
|
+
stopIfAsked();
|
|
370
|
+
}
|
|
371
|
+
await nextTask();
|
|
372
|
+
stopIfAsked();
|
|
373
|
+
return this.#call(() => consume(task.finish(), (page) => ({
|
|
374
|
+
width: page.width,
|
|
375
|
+
height: page.height,
|
|
376
|
+
truncated: page.truncated,
|
|
377
|
+
rgba: page.takeRgba(),
|
|
378
|
+
})));
|
|
379
|
+
}
|
|
380
|
+
finally {
|
|
381
|
+
if (!instance.dead && !instance.abandoned) {
|
|
382
|
+
task.free();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
close() {
|
|
387
|
+
if (this.#closed) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
this.#closed = true;
|
|
391
|
+
this.#instance.openDocuments -= 1;
|
|
392
|
+
this.#instance.documents.open -= 1;
|
|
393
|
+
if (!this.#instance.dead && !this.#instance.abandoned) {
|
|
394
|
+
this.#doc.free();
|
|
395
|
+
}
|
|
396
|
+
this.#instance.onClose?.();
|
|
397
|
+
}
|
|
398
|
+
[Symbol.dispose]() {
|
|
399
|
+
this.close();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// A browser without Symbol.dispose yet: tools that lower `using` look for this one instead.
|
|
403
|
+
Object.defineProperty(RectoDocument.prototype, Symbol.for("Symbol.dispose"), {
|
|
404
|
+
value: RectoDocument.prototype.close,
|
|
405
|
+
});
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type AssetName, type SyncHash } from "./assets.ts";
|
|
2
|
+
import { type Instance, type PdfDocument } from "./document.ts";
|
|
3
|
+
import { type PdfRangedDocument, type PdfRangedOpenOptions, type PdfRangeSource } from "./ranged.ts";
|
|
4
|
+
export type PdfEngineOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* The directory holding the package's wasm/ files, for setups that move the package
|
|
7
|
+
* without them (a bundler that does not rewrite `new URL`, a CDN). A URL in a browser; a
|
|
8
|
+
* path or file: URL in Node. Defaults to the package's own wasm/ directory.
|
|
9
|
+
*/
|
|
10
|
+
assetBase?: string | URL;
|
|
11
|
+
/**
|
|
12
|
+
* Past this much linear memory, the instance is dropped when its last document closes, or replaced
|
|
13
|
+
* at the next open if one is still open. By default it's dropped a second after the last close, once
|
|
14
|
+
* anything has used it, and an open before then reuses it; past 512 MiB, at the close; and past 64 MiB
|
|
15
|
+
* with a document still open, the next open gets a new one.
|
|
16
|
+
*/
|
|
17
|
+
recycleAboveBytes?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Files to fetch and verify while the engine is created, so that a missing one fails here and no later
|
|
20
|
+
* call waits for it. `recto-render.wasm` saves the first `render` that wait; all three parts check the
|
|
21
|
+
* whole engine at startup. The default is none, except where a thread can't read a file when it's needed
|
|
22
|
+
* (a page's main thread, a service worker): there it's all of them, about 9.6 MB. Name fewer to fetch less, and
|
|
23
|
+
* a call that needs a font or CMaps you left out fails with `missing-asset`. The parts are fetched there
|
|
24
|
+
* whatever you name, so it can always draw.
|
|
25
|
+
*/
|
|
26
|
+
preloadAssets?: (AssetName | "cmaps.brotli" | "fonts.bin")[];
|
|
27
|
+
};
|
|
28
|
+
export type PdfOpenOptions = {
|
|
29
|
+
password?: string;
|
|
30
|
+
/** Checked before the open. */
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
};
|
|
33
|
+
export type PdfEngineStats = {
|
|
34
|
+
heapBytes: number;
|
|
35
|
+
/** Increments every time the instance is replaced. heapBytes is 0 while there is none. */
|
|
36
|
+
generation: number;
|
|
37
|
+
openDocuments: number;
|
|
38
|
+
};
|
|
39
|
+
/** One instance per thread is enough; several documents can be open on it. */
|
|
40
|
+
export interface PdfEngine {
|
|
41
|
+
/**
|
|
42
|
+
* Opens a PDF held in memory. The bytes are copied before this returns its promise, so the
|
|
43
|
+
* caller may reuse, change or transfer its buffer straight away, without waiting for it.
|
|
44
|
+
*/
|
|
45
|
+
open(bytes: Uint8Array | ArrayBuffer, options?: PdfOpenOptions): Promise<PdfDocument>;
|
|
46
|
+
/**
|
|
47
|
+
* Opens a PDF read by byte range, fetching only what the engine parses. Every read of it
|
|
48
|
+
* can block this thread for a network round trip; see `timeoutMs`.
|
|
49
|
+
*/
|
|
50
|
+
openRanged(source: PdfRangeSource, options?: PdfRangedOpenOptions): Promise<PdfRangedDocument>;
|
|
51
|
+
stats(): PdfEngineStats;
|
|
52
|
+
}
|
|
53
|
+
/** An instance's own SHA-256, for an asset it needs: faster than JavaScript's in a cold browser. */
|
|
54
|
+
export declare function hashOn(instance: Pick<Instance, "dead" | "glue">): SyncHash;
|
|
55
|
+
export declare function createPdfEngine(options?: PdfEngineOptions): Promise<PdfEngine>;
|
|
56
|
+
/**
|
|
57
|
+
* Opens a PDF on this thread's shared engine:
|
|
58
|
+
*
|
|
59
|
+
* await using doc = await openPdf(bytes);
|
|
60
|
+
* doc.pageCount; doc.text(0); doc.render(0, { width: 1024 });
|
|
61
|
+
*/
|
|
62
|
+
export declare function openPdf(bytes: Uint8Array | ArrayBuffer, options?: PdfOpenOptions): Promise<PdfDocument>;
|