@substrat-run/attachment-extractors 0.1.4 → 0.2.1

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/pdf.js ADDED
@@ -0,0 +1,2011 @@
1
+ /**
2
+ * PDF → text (#1575): the text layer a PDF's pages draw, read without a dependency.
3
+ *
4
+ * **The bytes are a tenant's, so this is a hostile-input parser first and a PDF reader second.**
5
+ * Every structure a file can make large or circular has a bound of its own, and every bound
6
+ * ends the extraction legibly — `{ failed }` with a content-free reason, or the text read so
7
+ * far marked truncated — never a throw that escapes, and never a hang:
8
+ *
9
+ * - **Decompression** (`decodeStream`). Each stream's decoded size is counted as the decoder
10
+ * produces it, against `PDF_STREAM_MAX` for the stream (past it: a bomb, and the file
11
+ * fails) and against the extraction's inflate budget in total (past it: the text read so
12
+ * far is kept, truncated). A declared `/Length` or `/DL` is never trusted for either.
13
+ * - **The cross-reference chain** (`readXref`): at most `PDF_XREF_SECTIONS` sections, and a
14
+ * `/Prev` or `/XRefStm` that comes back to one already read is a loop, refused.
15
+ * - **Objects**: at most `PDF_OBJECTS_MAX` cross-reference entries, however large a `/Size`
16
+ * or a subsection count claims to be — nothing is allocated from a declared count. An object
17
+ * is parsed at most once (`objects`), and one whose resolution reaches itself — a `/Length`
18
+ * naming its own stream, a chain of references back to the start — resolves to null.
19
+ * - **Object streams** do not nest: an object stream must itself sit at a file offset, as the
20
+ * format requires, so one named as living in another object stream is refused.
21
+ * - **Nesting**: an array or dictionary at most `PDF_DEPTH_MAX` deep, a page tree at most
22
+ * `PDF_TREE_DEPTH_MAX` deep and never through one of its own ancestors, a form XObject at
23
+ * most `PDF_FORM_DEPTH_MAX` deep and never inside itself. Every walk is a loop with a depth
24
+ * counter or a recursion with one: none follows what the file says without counting.
25
+ * - **Memory** (`Retained`): what the extraction keeps — cached decoded streams, a page's
26
+ * joined content, parsed objects, cross-reference entries, CMap entries, font decoders — is
27
+ * charged before it is allocated, at most `PDF_RETAINED_FACTOR` × the inflate budget plus
28
+ * `PDF_RETAINED_BASE`. Every other allocation is bounded by a named constant: a token, a
29
+ * stream (`PDF_STREAM_MAX`), the text collected, the operand stack.
30
+ * - **Work**: a token or a direct object larger than `EXTRACTION_STRIDE` is refused, so the
31
+ * synchronous lexer never runs more than a stride between two checks of the signal, and
32
+ * every loop over the file — the lexer's, the byte searches, the decoders, the content
33
+ * interpreter — charges `Pace` and yields at a stride. Content is interpreted against a
34
+ * budget of its own (`PDF_INTERPRET_FACTOR` × the inflate budget), so a page tree that
35
+ * draws one form a thousand times cannot spend more than that.
36
+ *
37
+ * What it reads: the classic and the stream cross-reference forms, hybrid files, incremental
38
+ * updates, object streams, and — when the cross-reference data is unusable, as in a file
39
+ * edited by a tool that did not rewrite it — a scan for `N G obj` headers instead. Filters:
40
+ * Flate (with PNG predictors), LZW, ASCIIHex, ASCII85, RunLength. Image filters are not text
41
+ * and are skipped. Text is decoded through a font's `/ToUnicode` CMap where it has one, and
42
+ * through its encoding — WinAnsi, MacRoman, Standard, `/Differences` glyph names — where it
43
+ * has not. A composite font with no `/ToUnicode` names glyphs, not characters, so its text is
44
+ * unreadable and is not guessed at.
45
+ *
46
+ * What it does not: **encrypted files** are refused with that reason (their keys are derived
47
+ * with MD5 and RC4, which Web Crypto does not carry, and this package does not hand-roll a
48
+ * hash). **No OCR**: a scanned page has no text layer, so a scanned PDF extracts to nothing,
49
+ * which the kernel records `empty`.
50
+ */
51
+ import { EXTRACTION_STRIDE } from '@substrat-run/kernel';
52
+ import { CALL_COST, COLLECT_FACTOR, Retained, RetainedBoundExceeded, ExtractionBoundExceeded, MalformedInput, Pace, inflateChunks, } from './shared.js';
53
+ /** Cross-reference entries one file may declare, across every section. */
54
+ export const PDF_OBJECTS_MAX = 200_000;
55
+ /** Cross-reference sections followed through `/Prev` and `/XRefStm`. */
56
+ export const PDF_XREF_SECTIONS = 64;
57
+ /** Nesting of arrays and dictionaries inside one object. */
58
+ export const PDF_DEPTH_MAX = 32;
59
+ /** Depth of the page tree. */
60
+ export const PDF_TREE_DEPTH_MAX = 64;
61
+ /** Page-tree nodes visited, pages included. */
62
+ export const PDF_TREE_NODES_MAX = 50_000;
63
+ /** Depth of form XObjects drawn inside one another. */
64
+ export const PDF_FORM_DEPTH_MAX = 8;
65
+ /** Bytes one stream may decode to: past it, the stream is a bomb and the file fails. */
66
+ export const PDF_STREAM_MAX = 8 * 1024 * 1024;
67
+ /** Content bytes interpreted, as a multiple of the inflate budget. */
68
+ export const PDF_INTERPRET_FACTOR = 4;
69
+ /** The longest token, and the largest direct object, the lexer reads in one go. */
70
+ const TOKEN_MAX = EXTRACTION_STRIDE;
71
+ /** Operands one content-stream operator may collect before the stack is dropped as garbage. */
72
+ const OPERANDS_MAX = 256;
73
+ /** Code-space ranges one CMap may declare; a real one declares a handful. */
74
+ const CMAP_SPACES_MAX = 256;
75
+ /** The longest destination string a CMap maps a code to: the format's own limit, 512 bytes. */
76
+ const CMAP_DST_MAX = 512;
77
+ /** How far back from `obj` the scan looks for `N G `: two numbers and the space around them. */
78
+ const OBJ_HEADER_SPAN = 48;
79
+ /**
80
+ * What one PDF extraction may hold at once, as a multiple of its inflate budget (`Retained`):
81
+ * the decoded streams themselves (at most one inflate budget), and as much again for what is
82
+ * built from them — parsed objects, the cross-reference, CMaps, fonts, joined page content.
83
+ */
84
+ export const PDF_RETAINED_FACTOR = 2;
85
+ /** And a base the structures need whatever the inflate budget: fonts, the cross-reference, objects. */
86
+ export const PDF_RETAINED_BASE = 4 * 1024 * 1024;
87
+ /** What a parsed object holds per byte it was read from: values, arrays and maps cost more than their text. */
88
+ const OBJECT_COST_PER_BYTE = 4;
89
+ /** The fixed cost of an entry kept in a map or list: a cross-reference entry, a CMap entry, a parsed object. */
90
+ const ENTRY_COST = 64;
91
+ /**
92
+ * What a font's decoder holds, measured rather than guessed: 20 000 decoders built the way
93
+ * `fontDecoder` builds them and kept alive, over a collected heap (`node --expose-gc`,
94
+ * `heapUsed` before and after, divided by the count). A simple font held 2 233 bytes (its
95
+ * copied 256-entry table and its closure); each `/Differences` entry another ~78, its fresh
96
+ * glyph string included; a composite font's closure 248. Rounded up.
97
+ */
98
+ const SIMPLE_FONT_COST = 2_560;
99
+ const DIFFERENCE_COST = 64;
100
+ const COMPOSITE_FONT_COST = 256;
101
+ /** Whitespace read between a stream's declared end and its `endstream`. */
102
+ const STREAM_END_SPAN = 64;
103
+ /** A tagged value's `kind`; undefined for a number, a boolean, null, an array or a dictionary. */
104
+ const kindOf = (v) => typeof v === 'object' && v !== null && !Array.isArray(v) && !(v instanceof Map) ? v.kind : undefined;
105
+ const isName = (v) => kindOf(v) === 'name';
106
+ const isString = (v) => kindOf(v) === 'str';
107
+ const isRef = (v) => kindOf(v) === 'ref';
108
+ const isStream = (v) => kindOf(v) === 'stream';
109
+ const isDict = (v) => v instanceof Map;
110
+ const nameOf = (v) => (isName(v) ? v.name : null);
111
+ const intOf = (v) => (typeof v === 'number' && Number.isInteger(v) ? v : null);
112
+ // -- bytes ----------------------------------------------------------------------------
113
+ const isWhite = (c) => c === 0 || c === 9 || c === 10 || c === 12 || c === 13 || c === 32;
114
+ const isDelim = (c) => c === 40 || c === 41 || c === 60 || c === 62 || c === 91 || c === 93 || c === 123 || c === 125 || c === 47 || c === 37;
115
+ const isRegular = (c) => !isWhite(c) && !isDelim(c);
116
+ const ascii = (s) => Uint8Array.from(s, (ch) => ch.charCodeAt(0));
117
+ const latin1 = (b) => {
118
+ let s = '';
119
+ for (let i = 0; i < b.length; i += 1)
120
+ s += String.fromCharCode(b[i]);
121
+ return s;
122
+ };
123
+ /** The first `needle` in `buf` at or after `from`, searched a window at a time; -1 when none. */
124
+ async function findBytes(buf, needle, from, pace) {
125
+ pace.charge(CALL_COST);
126
+ const first = needle[0];
127
+ for (let at = from; at < buf.length;) {
128
+ await pace.turn();
129
+ const end = Math.min(buf.length, at + pace.room);
130
+ for (let i = at; i < end; i += 1) {
131
+ if (buf[i] !== first || i + needle.length > buf.length)
132
+ continue;
133
+ let k = 1;
134
+ while (k < needle.length && buf[i + k] === needle[k])
135
+ k += 1;
136
+ if (k === needle.length) {
137
+ pace.charge(i + 1 - at);
138
+ return i;
139
+ }
140
+ }
141
+ pace.charge(end - at);
142
+ at = end;
143
+ }
144
+ return -1;
145
+ }
146
+ /** The last `needle` in `buf`, looking back at most `span` bytes from the end, a window at a time. */
147
+ async function findLast(buf, needle, span, pace) {
148
+ pace.charge(CALL_COST);
149
+ const stop = Math.max(0, buf.length - span);
150
+ for (let at = buf.length - needle.length; at >= stop;) {
151
+ await pace.turn();
152
+ const end = Math.max(stop, at - pace.room);
153
+ for (let i = at; i >= end; i -= 1) {
154
+ let k = 0;
155
+ while (k < needle.length && buf[i + k] === needle[k])
156
+ k += 1;
157
+ if (k === needle.length) {
158
+ pace.charge(at - i + 1);
159
+ return i;
160
+ }
161
+ }
162
+ pace.charge(at - end + 1);
163
+ at = end - 1;
164
+ }
165
+ return -1;
166
+ }
167
+ /** Chunks joined into one array; a single chunk is returned as it is. */
168
+ function concatBytes(chunks) {
169
+ if (chunks.length === 1)
170
+ return chunks[0];
171
+ const out = new Uint8Array(chunks.reduce((n, c) => n + c.length, 0));
172
+ let at = 0;
173
+ for (const c of chunks) {
174
+ out.set(c, at);
175
+ at += c.length;
176
+ }
177
+ return out;
178
+ }
179
+ // -- the lexer --------------------------------------------------------------------------
180
+ /** A literal string's one-character escapes: `\n \r \t \b \f \( \) \\`. */
181
+ const ESCAPES = { 110: 10, 114: 13, 116: 9, 98: 8, 102: 12, 40: 40, 41: 41, 92: 92 };
182
+ /**
183
+ * Tokens over a byte range, synchronously. Each token — and the whitespace and comments before
184
+ * it — is at most `TOKEN_MAX` bytes, and what it consumed is charged to `pace`, so a caller's
185
+ * loop that yields whenever `pace.room` runs out never holds the thread for much past a stride.
186
+ */
187
+ class Lexer {
188
+ buf;
189
+ end;
190
+ pace;
191
+ pos;
192
+ constructor(buf, start, end, pace) {
193
+ this.buf = buf;
194
+ this.end = end;
195
+ this.pace = pace;
196
+ this.pos = start;
197
+ }
198
+ tooLong(from) {
199
+ if (this.pos - from > TOKEN_MAX) {
200
+ // What was read is charged even though the token is refused: the work was done.
201
+ this.pace.charge(this.pos - from);
202
+ throw new MalformedInput('a PDF token is longer than the extraction reads');
203
+ }
204
+ }
205
+ /** Skip whitespace and comments. */
206
+ skipSpace() {
207
+ const from = this.pos;
208
+ while (this.pos < this.end) {
209
+ const c = this.buf[this.pos];
210
+ if (isWhite(c)) {
211
+ this.pos += 1;
212
+ }
213
+ else if (c === 37) {
214
+ // To the end of the line — but never past the token bound, however long the line is.
215
+ const stop = Math.min(this.end, from + TOKEN_MAX + 1);
216
+ while (this.pos < stop && this.buf[this.pos] !== 10 && this.buf[this.pos] !== 13)
217
+ this.pos += 1;
218
+ }
219
+ else {
220
+ break;
221
+ }
222
+ this.tooLong(from);
223
+ }
224
+ this.pace.charge(this.pos - from);
225
+ }
226
+ next() {
227
+ this.skipSpace();
228
+ if (this.pos >= this.end)
229
+ return { t: 'eof' };
230
+ const from = this.pos;
231
+ const c = this.buf[this.pos];
232
+ let token;
233
+ if (c === 40)
234
+ token = { t: 'str', v: this.literal() };
235
+ else if (c === 60 && this.buf[this.pos + 1] === 60) {
236
+ this.pos += 2;
237
+ token = { t: '<<' };
238
+ }
239
+ else if (c === 60)
240
+ token = { t: 'str', v: this.hex() };
241
+ else if (c === 62) {
242
+ this.pos += this.buf[this.pos + 1] === 62 ? 2 : 1;
243
+ token = { t: '>>' };
244
+ }
245
+ else if (c === 91 || c === 93 || c === 123 || c === 125) {
246
+ this.pos += 1;
247
+ token = { t: String.fromCharCode(c) };
248
+ }
249
+ else if (c === 47)
250
+ token = { t: 'name', v: this.name() };
251
+ else if (c === 41) {
252
+ // A stray `)` is not a token anything reads; step over it rather than stall on it.
253
+ this.pos += 1;
254
+ token = { t: 'kw', v: ')' };
255
+ }
256
+ else {
257
+ // One forward pass decides whether the word is a number — `[+-]?`, digits with at most
258
+ // one `.`, at least one digit — as it finds the word's end. Never a regular expression
259
+ // over the word: an ambiguous one backtracks quadratically on a long digit run that
260
+ // ends in a letter, which a file can make 256 Ki long (Codex #2062 r1).
261
+ const start = this.pos;
262
+ let digits = 0;
263
+ let dots = 0;
264
+ let numeric = true;
265
+ while (this.pos < this.end && isRegular(this.buf[this.pos])) {
266
+ const c = this.buf[this.pos];
267
+ if (c >= 48 && c <= 57)
268
+ digits += 1;
269
+ else if (c === 46)
270
+ dots += 1;
271
+ else if (!((c === 43 || c === 45) && this.pos === start))
272
+ numeric = false;
273
+ this.pos += 1;
274
+ this.tooLong(start);
275
+ }
276
+ const word = latin1(this.buf.subarray(start, this.pos));
277
+ const n = numeric && digits > 0 && dots <= 1 ? Number(word) : Number.NaN;
278
+ token = Number.isFinite(n) ? { t: 'num', v: n } : { t: 'kw', v: word };
279
+ }
280
+ // A token's bytes, and the fixed cost of making one (`CALL_COST`): a stream of one-byte
281
+ // tokens is a call per byte.
282
+ this.pace.charge(this.pos - from + CALL_COST);
283
+ return token;
284
+ }
285
+ literal() {
286
+ const start = this.pos;
287
+ this.pos += 1;
288
+ const out = [];
289
+ let depth = 1;
290
+ while (this.pos < this.end) {
291
+ this.tooLong(start);
292
+ const c = this.buf[this.pos++];
293
+ if (c === 40)
294
+ depth += 1;
295
+ else if (c === 41 && (depth -= 1) === 0)
296
+ return Uint8Array.from(out);
297
+ if (c !== 92) {
298
+ out.push(c);
299
+ continue;
300
+ }
301
+ const e = this.buf[this.pos++];
302
+ if (e === undefined)
303
+ break;
304
+ if (ESCAPES[e] !== undefined)
305
+ out.push(ESCAPES[e]);
306
+ else if (e >= 48 && e <= 55) {
307
+ let v = e - 48;
308
+ for (let k = 0; k < 2 && this.buf[this.pos] >= 48 && this.buf[this.pos] <= 55; k += 1)
309
+ v = v * 8 + (this.buf[this.pos++] - 48);
310
+ out.push(v & 0xff);
311
+ }
312
+ else if (e === 13) {
313
+ if (this.buf[this.pos] === 10)
314
+ this.pos += 1;
315
+ }
316
+ else if (e !== 10)
317
+ out.push(e);
318
+ }
319
+ return Uint8Array.from(out);
320
+ }
321
+ hex() {
322
+ const start = this.pos;
323
+ this.pos += 1;
324
+ const out = [];
325
+ let half = -1;
326
+ while (this.pos < this.end) {
327
+ this.tooLong(start);
328
+ const c = this.buf[this.pos++];
329
+ if (c === 62)
330
+ break;
331
+ const v = c >= 48 && c <= 57 ? c - 48 : c >= 65 && c <= 70 ? c - 55 : c >= 97 && c <= 102 ? c - 87 : -1;
332
+ if (v < 0)
333
+ continue;
334
+ if (half < 0)
335
+ half = v;
336
+ else {
337
+ out.push(half * 16 + v);
338
+ half = -1;
339
+ }
340
+ }
341
+ if (half >= 0)
342
+ out.push(half * 16);
343
+ return Uint8Array.from(out);
344
+ }
345
+ name() {
346
+ const start = this.pos;
347
+ this.pos += 1;
348
+ let s = '';
349
+ while (this.pos < this.end && isRegular(this.buf[this.pos])) {
350
+ this.tooLong(start);
351
+ const c = this.buf[this.pos++];
352
+ if (c === 35 && this.pos + 1 < this.end) {
353
+ const v = Number.parseInt(String.fromCharCode(this.buf[this.pos], this.buf[this.pos + 1]), 16);
354
+ if (!Number.isNaN(v)) {
355
+ s += String.fromCharCode(v);
356
+ this.pos += 2;
357
+ continue;
358
+ }
359
+ }
360
+ s += String.fromCharCode(c);
361
+ }
362
+ return s;
363
+ }
364
+ }
365
+ /**
366
+ * One direct value, starting from a token already read. `refs` says whether `N G R` is read as
367
+ * a reference: in a content stream it never is, and the lookahead would only cost.
368
+ */
369
+ function valueFrom(tok, lex, refs, depth = 0, start = lex.pos) {
370
+ if (lex.pos - start > TOKEN_MAX)
371
+ throw new MalformedInput('a PDF object is larger than the extraction reads');
372
+ switch (tok.t) {
373
+ case 'num': {
374
+ if (refs && Number.isInteger(tok.v) && tok.v >= 0) {
375
+ const save = lex.pos;
376
+ const gen = lex.next();
377
+ if (gen.t === 'num' && Number.isInteger(gen.v) && gen.v >= 0) {
378
+ const r = lex.next();
379
+ if (r.t === 'kw' && r.v === 'R')
380
+ return { kind: 'ref', num: tok.v, gen: gen.v };
381
+ }
382
+ lex.pos = save;
383
+ }
384
+ return tok.v;
385
+ }
386
+ case 'str':
387
+ return { kind: 'str', bytes: tok.v };
388
+ case 'name':
389
+ return { kind: 'name', name: tok.v };
390
+ case 'kw':
391
+ return tok.v === 'true' ? true : tok.v === 'false' ? false : null;
392
+ case '[':
393
+ case '<<': {
394
+ if (depth >= PDF_DEPTH_MAX)
395
+ throw new MalformedInput('a PDF object is nested too deeply');
396
+ const close = tok.t === '[' ? ']' : '>>';
397
+ const items = [];
398
+ const dict = new Map();
399
+ let key = null;
400
+ for (;;) {
401
+ // Every token read counts against the object's size, a skipped key included.
402
+ if (lex.pos - start > TOKEN_MAX)
403
+ throw new MalformedInput('a PDF object is larger than the extraction reads');
404
+ const next = lex.next();
405
+ if (next.t === close)
406
+ break;
407
+ if (next.t === 'eof')
408
+ throw new MalformedInput('the PDF ends inside an object');
409
+ if (next.t === ']' || next.t === '>>')
410
+ throw new MalformedInput('a PDF object is not closed the way it was opened');
411
+ if (tok.t === '<<' && key === null) {
412
+ // A key must be a name; anything else in that place is skipped, as readers do.
413
+ if (next.t === 'name')
414
+ key = next.v;
415
+ continue;
416
+ }
417
+ const v = valueFrom(next, lex, refs, depth + 1, start);
418
+ if (tok.t === '[')
419
+ items.push(v);
420
+ else {
421
+ dict.set(key, v);
422
+ key = null;
423
+ }
424
+ }
425
+ return tok.t === '[' ? items : dict;
426
+ }
427
+ default:
428
+ return null;
429
+ }
430
+ }
431
+ /**
432
+ * Collect a decoder's output under both bounds: past `PDF_STREAM_MAX` the stream is a bomb and
433
+ * the file fails; past what the budget has left, the data ends there and says so.
434
+ */
435
+ class Sink {
436
+ budget;
437
+ chunks = [];
438
+ size = 0;
439
+ exhausted = false;
440
+ constructor(budget) {
441
+ this.budget = budget;
442
+ }
443
+ /** Add a chunk; false once the budget is spent and nothing more should be produced. */
444
+ add(chunk) {
445
+ if (this.size + chunk.length > PDF_STREAM_MAX) {
446
+ throw new ExtractionBoundExceeded('a PDF stream decodes past the extraction bound');
447
+ }
448
+ if (this.size + chunk.length > this.budget.remaining) {
449
+ this.chunks.push(chunk.subarray(0, Math.max(0, this.budget.remaining - this.size)));
450
+ this.size = this.budget.remaining;
451
+ this.exhausted = true;
452
+ return false;
453
+ }
454
+ this.chunks.push(chunk);
455
+ this.size += chunk.length;
456
+ return true;
457
+ }
458
+ done() {
459
+ this.budget.remaining -= this.size;
460
+ return { data: concatBytes(this.chunks), exhausted: this.exhausted };
461
+ }
462
+ }
463
+ /**
464
+ * Flate, through the runtime's `DecompressionStream`. A zlib header picks `deflate`; data
465
+ * without one (some writers omit it) is read as raw deflate. A stream that is damaged part way
466
+ * keeps what was inflated before the damage, as viewers do: a truncated page is still text.
467
+ */
468
+ async function inflate(raw, budget, pace) {
469
+ const zlib = raw.length >= 2 && (raw[0] & 0x0f) === 8 && ((raw[0] << 8) | raw[1]) % 31 === 0;
470
+ const sink = new Sink(budget);
471
+ await inflateChunks(raw, zlib ? 'deflate' : 'deflate-raw', pace, (chunk) => {
472
+ pace.charge(chunk.length);
473
+ return sink.add(chunk);
474
+ });
475
+ return sink.done();
476
+ }
477
+ /** LZW as PDF writes it (8-bit, early change by default). */
478
+ async function lzw(raw, early, budget, pace) {
479
+ const sink = new Sink(budget);
480
+ let dict = [];
481
+ const reset = () => {
482
+ dict = [];
483
+ for (let i = 0; i < 256; i += 1)
484
+ dict.push(Uint8Array.of(i));
485
+ dict.push(new Uint8Array(0), new Uint8Array(0));
486
+ };
487
+ reset();
488
+ let width = 9;
489
+ let bits = 0;
490
+ let acc = 0;
491
+ let prev = null;
492
+ for (let i = 0; i < raw.length; i += 1) {
493
+ if (pace.room <= 0)
494
+ await pace.turn();
495
+ pace.charge(1);
496
+ acc = (acc << 8) | raw[i];
497
+ bits += 8;
498
+ while (bits >= width) {
499
+ const code = (acc >> (bits - width)) & ((1 << width) - 1);
500
+ bits -= width;
501
+ acc &= (1 << bits) - 1;
502
+ if (code === 256) {
503
+ reset();
504
+ width = 9;
505
+ prev = null;
506
+ continue;
507
+ }
508
+ if (code === 257)
509
+ return sink.done();
510
+ let entry;
511
+ if (code < dict.length)
512
+ entry = dict[code];
513
+ else if (prev && code === dict.length) {
514
+ entry = new Uint8Array(prev.length + 1);
515
+ entry.set(prev);
516
+ entry[prev.length] = prev[0];
517
+ }
518
+ else
519
+ return sink.done();
520
+ // An entry is at most the table's 4096 bytes; its copy is work, charged as such.
521
+ pace.charge(entry.length);
522
+ if (!sink.add(entry))
523
+ return sink.done();
524
+ if (prev && dict.length < 4096) {
525
+ const grown = new Uint8Array(prev.length + 1);
526
+ grown.set(prev);
527
+ grown[prev.length] = entry[0];
528
+ dict.push(grown);
529
+ }
530
+ prev = entry;
531
+ const limit = dict.length + (early ? 1 : 0);
532
+ width = limit >= 2048 ? 12 : limit >= 1024 ? 11 : limit >= 512 ? 10 : 9;
533
+ }
534
+ }
535
+ return sink.done();
536
+ }
537
+ /** ASCIIHex, ASCII85 and RunLength: small, byte-at-a-time, paced and bounded the same way. */
538
+ /**
539
+ * Decoded bytes, one at a time, into a `Sink` a buffer at a time — so a decoder that produces
540
+ * bytes singly is held to the budgets AS it produces them, and stops the moment the budget is
541
+ * spent, rather than materialising its whole output first.
542
+ */
543
+ class ByteWriter {
544
+ sink;
545
+ buffer;
546
+ n = 0;
547
+ /** Buffered up to the budget left plus one byte, so the write that crosses it is the first one seen. */
548
+ constructor(sink, budget) {
549
+ this.sink = sink;
550
+ this.buffer = new Uint8Array(Math.max(1, Math.min(64 * 1024, budget.remaining + 1)));
551
+ }
552
+ /** Write one byte; false once the budget is spent and the decoder should stop. */
553
+ put(byte) {
554
+ this.buffer[this.n++] = byte;
555
+ return this.n < this.buffer.length || this.flush();
556
+ }
557
+ flush() {
558
+ if (this.n === 0)
559
+ return true;
560
+ const more = this.sink.add(this.buffer.slice(0, this.n));
561
+ this.n = 0;
562
+ return more;
563
+ }
564
+ }
565
+ async function asciiHex(raw, budget, pace) {
566
+ const sink = new Sink(budget);
567
+ const out = new ByteWriter(sink, budget);
568
+ let half = -1;
569
+ for (let i = 0; i < raw.length; i += 1) {
570
+ if (pace.room <= 0)
571
+ await pace.turn();
572
+ pace.charge(1);
573
+ const c = raw[i];
574
+ if (c === 62)
575
+ break;
576
+ const v = c >= 48 && c <= 57 ? c - 48 : c >= 65 && c <= 70 ? c - 55 : c >= 97 && c <= 102 ? c - 87 : -1;
577
+ if (v < 0)
578
+ continue;
579
+ if (half < 0)
580
+ half = v;
581
+ else {
582
+ if (!out.put(half * 16 + v))
583
+ return sink.done();
584
+ half = -1;
585
+ }
586
+ }
587
+ if (half >= 0)
588
+ out.put(half * 16);
589
+ out.flush();
590
+ return sink.done();
591
+ }
592
+ async function ascii85(raw, budget, pace) {
593
+ const sink = new Sink(budget);
594
+ const out = new ByteWriter(sink, budget);
595
+ const group = [];
596
+ /** The first `n` bytes of the group's value; false once the budget is spent. */
597
+ const emit = (n) => {
598
+ let v = 0;
599
+ for (let k = 0; k < 5; k += 1)
600
+ v = v * 85 + (group[k] ?? 84);
601
+ group.length = 0;
602
+ for (let k = 0; k < n; k += 1)
603
+ if (!out.put((v >>> (24 - 8 * k)) & 0xff))
604
+ return false;
605
+ return true;
606
+ };
607
+ let i = raw[0] === 60 && raw[1] === 126 ? 2 : 0;
608
+ for (; i < raw.length; i += 1) {
609
+ if (pace.room <= 0)
610
+ await pace.turn();
611
+ pace.charge(1);
612
+ const c = raw[i];
613
+ if (c === 126)
614
+ break;
615
+ if (isWhite(c))
616
+ continue;
617
+ if (c === 122 && group.length === 0) {
618
+ if (!out.put(0) || !out.put(0) || !out.put(0) || !out.put(0))
619
+ return sink.done();
620
+ continue;
621
+ }
622
+ if (c < 33 || c > 117)
623
+ continue;
624
+ group.push(c - 33);
625
+ if (group.length === 5 && !emit(4))
626
+ return sink.done();
627
+ }
628
+ if (group.length > 1)
629
+ emit(group.length - 1);
630
+ out.flush();
631
+ return sink.done();
632
+ }
633
+ async function runLength(raw, budget, pace) {
634
+ const sink = new Sink(budget);
635
+ for (let i = 0; i < raw.length;) {
636
+ if (pace.room <= 0)
637
+ await pace.turn();
638
+ const n = raw[i];
639
+ if (n === 128)
640
+ break;
641
+ const piece = n < 128 ? raw.subarray(i + 1, i + 2 + n) : new Uint8Array(257 - n).fill(raw[i + 1] ?? 0);
642
+ pace.charge(piece.length + 1);
643
+ i += n < 128 ? n + 2 : 2;
644
+ if (!sink.add(piece))
645
+ break;
646
+ }
647
+ return sink.done();
648
+ }
649
+ /** Undo a PNG predictor (`/Predictor` 10–15), row by row, with each row's own filter byte. */
650
+ async function unpredict(data, parms, pace) {
651
+ const predictor = intOf(parms?.get('Predictor')) ?? 1;
652
+ if (predictor < 10) {
653
+ if (predictor === 1)
654
+ return data;
655
+ throw new MalformedInput('a PDF stream uses a predictor that is not read');
656
+ }
657
+ const colors = Math.min(Math.max(intOf(parms?.get('Colors')) ?? 1, 1), 32);
658
+ const bpc = Math.min(Math.max(intOf(parms?.get('BitsPerComponent')) ?? 8, 1), 16);
659
+ const columns = Math.min(Math.max(intOf(parms?.get('Columns')) ?? 1, 1), 1 << 20);
660
+ const bpp = Math.max(1, Math.ceil((colors * bpc) / 8));
661
+ // The row width is the file's DECLARATION; the data is what decoding produced, already held
662
+ // to the stream budget. No allocation is sized by the declaration until the data shows a
663
+ // whole row of it: a tiny stream claiming 64 MiB rows decodes to nothing and costs nothing.
664
+ const rowLength = Math.ceil((colors * bpc * columns) / 8);
665
+ const rows = Math.floor(data.length / (rowLength + 1));
666
+ if (rows === 0)
667
+ return new Uint8Array(0);
668
+ const out = new Uint8Array(rows * rowLength); // ≤ data.length
669
+ for (let r = 0; r < rows; r += 1) {
670
+ const filter = data[r * (rowLength + 1)];
671
+ const row = data.subarray(r * (rowLength + 1) + 1, (r + 1) * (rowLength + 1));
672
+ const cur = out.subarray(r * rowLength, (r + 1) * rowLength);
673
+ // The row above; the first row's is all zeros, read as such rather than allocated.
674
+ const prior = r > 0 ? out.subarray((r - 1) * rowLength, r * rowLength) : null;
675
+ pace.charge(1);
676
+ // A row can be megabytes wide: paced inside it, a window at a time.
677
+ for (let i = 0; i < rowLength;) {
678
+ if (pace.room <= 0)
679
+ await pace.turn();
680
+ const end = Math.min(rowLength, i + pace.room);
681
+ pace.charge(end - i);
682
+ for (; i < end; i += 1) {
683
+ const a = i >= bpp ? cur[i - bpp] : 0;
684
+ const b = prior ? prior[i] : 0;
685
+ const c = prior && i >= bpp ? prior[i - bpp] : 0;
686
+ let v = row[i];
687
+ if (filter === 1)
688
+ v += a;
689
+ else if (filter === 2)
690
+ v += b;
691
+ else if (filter === 3)
692
+ v += (a + b) >> 1;
693
+ else if (filter === 4) {
694
+ const p = a + b - c;
695
+ const pa = Math.abs(p - a);
696
+ const pb = Math.abs(p - b);
697
+ const pc = Math.abs(p - c);
698
+ v += pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
699
+ }
700
+ cur[i] = v & 0xff;
701
+ }
702
+ }
703
+ }
704
+ return out;
705
+ }
706
+ /** The filters a stream names, with their parameters, in order. */
707
+ function filtersOf(dict) {
708
+ const f = dict.get('Filter') ?? dict.get('F');
709
+ const p = dict.get('DecodeParms') ?? dict.get('DP');
710
+ const names = Array.isArray(f) ? f : f === undefined || f === null ? [] : [f];
711
+ const parms = Array.isArray(p) ? p : [p];
712
+ return names.map((n, i) => ({ name: nameOf(n) ?? '', parms: isDict(parms[i]) ? parms[i] : null }));
713
+ }
714
+ const FLATE = { run: (d, _p, b, pace) => inflate(d, b, pace), predicted: true };
715
+ const LZW = { run: (d, p, b, pace) => lzw(d, intOf(p?.get('EarlyChange')) !== 0, b, pace), predicted: true };
716
+ const AHX = { run: (d, _p, b, pace) => asciiHex(d, b, pace), predicted: false };
717
+ const A85 = { run: (d, _p, b, pace) => ascii85(d, b, pace), predicted: false };
718
+ const RL = { run: (d, _p, b, pace) => runLength(d, b, pace), predicted: false };
719
+ const FILTERS = {
720
+ FlateDecode: FLATE, Fl: FLATE, LZWDecode: LZW, LZW, ASCIIHexDecode: AHX, AHx: AHX,
721
+ ASCII85Decode: A85, A85, RunLengthDecode: RL, RL,
722
+ };
723
+ /** The text read so far is enough: stop reading, and say the text was cut. */
724
+ class Enough extends Error {
725
+ }
726
+ /**
727
+ * A file that is not damaged but refused — a loop, nested object streams, encryption. Damage
728
+ * earns the scan fallback a second way in; a refusal is the file's answer and never does.
729
+ */
730
+ class Refusal extends MalformedInput {
731
+ }
732
+ const tooManyObjects = () => new ExtractionBoundExceeded(`the PDF declares more than ${PDF_OBJECTS_MAX} objects`);
733
+ class PdfDocument {
734
+ buf;
735
+ budget;
736
+ pace;
737
+ retained;
738
+ xref = new Map();
739
+ /** Each object is parsed at most once. */
740
+ objects = new Map();
741
+ /** Objects whose resolution is in progress: met again, the reference resolves to null. */
742
+ resolving = new Set();
743
+ objectStreams = new Map();
744
+ /** Decoded form XObjects and CMaps, by object number: a form drawn on every page decodes once. */
745
+ decodedByNum = new Map();
746
+ /** Parsed CMaps, by stream object number: a ToUnicode shared by a thousand fonts parses once. */
747
+ cmaps = new Map();
748
+ trailer = new Map();
749
+ constructor(buf, budget, pace, retained) {
750
+ this.buf = buf;
751
+ this.budget = budget;
752
+ this.pace = pace;
753
+ this.retained = retained;
754
+ }
755
+ /**
756
+ * Forget the cross-reference and everything read through it, for the scan to start over: an
757
+ * object a wrong offset resolved to null would otherwise stay null. The budget spent stays
758
+ * spent.
759
+ */
760
+ forgetXref() {
761
+ this.xref.clear();
762
+ this.objects.clear();
763
+ this.objectStreams.clear();
764
+ this.decodedByNum.clear();
765
+ this.cmaps.clear();
766
+ }
767
+ /**
768
+ * Record where an object is, within `PDF_OBJECTS_MAX`. Sections are read newest first, so
769
+ * by default the first entry for a number wins; the scan reads the file forwards, so it
770
+ * `replace`s — the last header for a number is the newest.
771
+ */
772
+ addEntry(num, entry, replace = false) {
773
+ if (this.xref.has(num) && !replace)
774
+ return;
775
+ if (!this.xref.has(num)) {
776
+ if (this.xref.size >= PDF_OBJECTS_MAX)
777
+ throw tooManyObjects();
778
+ this.retained.take(ENTRY_COST);
779
+ }
780
+ this.xref.set(num, entry);
781
+ }
782
+ async resolve(v) {
783
+ if (v === undefined)
784
+ return null;
785
+ return isRef(v) ? this.object(v.num) : v;
786
+ }
787
+ async resolveDict(v) {
788
+ const r = await this.resolve(v);
789
+ return isDict(r) ? r : isStream(r) ? r.dict : null;
790
+ }
791
+ async object(num) {
792
+ if (this.objects.has(num))
793
+ return this.objects.get(num);
794
+ if (this.resolving.has(num))
795
+ return null;
796
+ // A parse is synchronous and up to a token's bound long: a walk that resolves object after
797
+ // object — kids, fonts, forms — yields between them once the stride is spent.
798
+ if (this.pace.room <= 0)
799
+ await this.pace.turn();
800
+ const entry = this.xref.get(num);
801
+ if (!entry)
802
+ return null;
803
+ this.resolving.add(num);
804
+ try {
805
+ const v = entry.type === 1 ? await this.objectAt(entry.offset, num) : await this.objectInStream(entry.stream, num);
806
+ this.objects.set(num, v);
807
+ return v;
808
+ }
809
+ finally {
810
+ this.resolving.delete(num);
811
+ }
812
+ }
813
+ /** `N G obj <value> [stream … endstream] endobj` at a file offset. */
814
+ async objectAt(offset, expect) {
815
+ if (offset < 0 || offset >= this.buf.length)
816
+ return null;
817
+ const lex = new Lexer(this.buf, offset, this.buf.length, this.pace);
818
+ const n = lex.next();
819
+ const g = lex.next();
820
+ const o = lex.next();
821
+ if (n.t !== 'num' || g.t !== 'num' || o.t !== 'kw' || o.v !== 'obj')
822
+ return null;
823
+ if (expect !== null && n.v !== expect)
824
+ return null;
825
+ const from = lex.pos;
826
+ const v = valueFrom(lex.next(), lex, true);
827
+ // A parsed object is kept (`objects`): charged for what it was read from.
828
+ this.retained.take(ENTRY_COST + (lex.pos - from) * OBJECT_COST_PER_BYTE);
829
+ if (!isDict(v))
830
+ return v;
831
+ const save = lex.pos;
832
+ const kw = lex.next();
833
+ if (kw.t !== 'kw' || kw.v !== 'stream') {
834
+ lex.pos = save;
835
+ return v;
836
+ }
837
+ let start = lex.pos;
838
+ if (this.buf[start] === 13)
839
+ start += 1;
840
+ if (this.buf[start] === 10)
841
+ start += 1;
842
+ return { kind: 'stream', dict: v, start, length: await this.streamLength(v, start), num: expect };
843
+ }
844
+ /**
845
+ * A stream's data length: its `/Length` when the bytes there end with `endstream`, and the
846
+ * distance to the next `endstream` otherwise — a wrong `/Length` is common, and a `/Length`
847
+ * that names its own stream resolves to null and lands here too.
848
+ */
849
+ async streamLength(dict, start) {
850
+ const declared = intOf(await this.resolve(dict.get('Length')));
851
+ if (declared !== null && declared >= 0 && start + declared <= this.buf.length) {
852
+ let k = start + declared;
853
+ const stop = Math.min(this.buf.length, k + STREAM_END_SPAN);
854
+ while (k < stop && isWhite(this.buf[k]))
855
+ k += 1;
856
+ if (latin1(this.buf.subarray(k, k + 9)) === 'endstream')
857
+ return declared;
858
+ }
859
+ const end = await findBytes(this.buf, ENDSTREAM, start, this.pace);
860
+ if (end < 0)
861
+ throw new MalformedInput('a PDF stream is not ended');
862
+ let length = end - start;
863
+ if (this.buf[start + length - 1] === 10)
864
+ length -= 1;
865
+ if (this.buf[start + length - 1] === 13)
866
+ length -= 1;
867
+ return Math.max(0, length);
868
+ }
869
+ async objectInStream(container, num) {
870
+ const stm = await this.objectStream(container);
871
+ const at = stm?.offsets.get(num);
872
+ if (!stm || at === undefined)
873
+ return null;
874
+ const lex = new Lexer(stm.data, at, stm.data.length, this.pace);
875
+ const v = valueFrom(lex.next(), lex, true);
876
+ // Kept, like any parsed object — and many numbers may name one offset, each parsed again.
877
+ this.retained.take(ENTRY_COST + (lex.pos - at) * OBJECT_COST_PER_BYTE);
878
+ return v;
879
+ }
880
+ /** An object stream's decoded data and its header of object numbers and offsets. */
881
+ async objectStream(container) {
882
+ if (this.objectStreams.has(container))
883
+ return this.objectStreams.get(container);
884
+ const entry = this.xref.get(container);
885
+ // The format puts an object stream at a file offset; one said to sit in another object
886
+ // stream is nesting, which nothing writes and this refuses rather than follows.
887
+ if (entry?.type === 2)
888
+ throw new Refusal('the PDF nests object streams');
889
+ this.objectStreams.set(container, null);
890
+ const s = await this.object(container);
891
+ if (!isStream(s))
892
+ return null;
893
+ const decoded = await this.decode(s);
894
+ if (!decoded)
895
+ return null;
896
+ if (decoded.exhausted)
897
+ throw new ExtractionBoundExceeded('the PDF decodes past the extraction bound');
898
+ const offsets = await objectStreamOffsets(decoded.data, s.dict, this.pace);
899
+ this.retained.take(offsets.size * ENTRY_COST);
900
+ const stm = { data: decoded.data, offsets };
901
+ this.objectStreams.set(container, stm);
902
+ return stm;
903
+ }
904
+ /**
905
+ * A stream's decoded bytes, through its filters in order; null for a stream that holds an
906
+ * image (not text) or uses a filter this does not read.
907
+ */
908
+ async decode(s) {
909
+ if (s.num !== null && this.decodedByNum.has(s.num))
910
+ return this.decodedByNum.get(s.num);
911
+ let data = this.buf.subarray(s.start, Math.min(this.buf.length, s.start + s.length));
912
+ let exhausted = false;
913
+ let result = null;
914
+ try {
915
+ const filters = filtersOf(s.dict);
916
+ if (filters.length === 0) {
917
+ // Unfiltered bytes are decoded bytes too, held to both budgets like any filter's output:
918
+ // past the per-stream bound the file fails, past the total the data ends there.
919
+ const sink = new Sink(this.budget);
920
+ sink.add(data);
921
+ result = sink.done();
922
+ return result;
923
+ }
924
+ for (const { name, parms } of filters) {
925
+ // An image filter is not text, and an unknown one is not read: either way, no data.
926
+ const filter = Object.hasOwn(FILTERS, name) ? FILTERS[name] : null;
927
+ if (!filter)
928
+ return null;
929
+ const d = await filter.run(data, parms, this.budget, this.pace);
930
+ data = filter.predicted ? await unpredict(d.data, parms, this.pace) : d.data;
931
+ exhausted ||= d.exhausted;
932
+ if (exhausted)
933
+ break;
934
+ }
935
+ result = { data, exhausted };
936
+ return result;
937
+ }
938
+ finally {
939
+ if (s.num !== null) {
940
+ // Cached for the rest of the extraction: held, and charged as such.
941
+ if (result)
942
+ this.retained.take(result.data.length);
943
+ this.decodedByNum.set(s.num, result);
944
+ }
945
+ }
946
+ }
947
+ }
948
+ /** An object stream's header: each object number it holds, and where in the data it starts. */
949
+ async function objectStreamOffsets(data, dict, pace) {
950
+ const first = intOf(dict.get('First')) ?? 0;
951
+ const count = Math.min(intOf(dict.get('N')) ?? 0, PDF_OBJECTS_MAX);
952
+ const lex = new Lexer(data, 0, Math.min(first, data.length), pace);
953
+ const offsets = new Map();
954
+ for (let k = 0; k < count; k += 1) {
955
+ if (pace.room <= 0)
956
+ await pace.turn();
957
+ const a = lex.next();
958
+ const b = lex.next();
959
+ if (a.t !== 'num' || b.t !== 'num')
960
+ break;
961
+ offsets.set(a.v, first + b.v);
962
+ }
963
+ return offsets;
964
+ }
965
+ const ENDSTREAM = ascii('endstream');
966
+ const NEWLINE = Uint8Array.of(10);
967
+ const STARTXREF = ascii('startxref');
968
+ const TRAILER = ascii('trailer');
969
+ // -- cross-reference ----------------------------------------------------------------------
970
+ /**
971
+ * Read every cross-reference section, newest first: the `startxref` offset, then each
972
+ * section's `/Prev` (and a hybrid file's `/XRefStm`). At most `PDF_XREF_SECTIONS`, and an
973
+ * offset met twice is a loop, refused.
974
+ */
975
+ async function readXref(doc) {
976
+ const sx = await findLast(doc.buf, STARTXREF, 2048, doc.pace);
977
+ if (sx < 0)
978
+ throw new MalformedInput('the PDF has no startxref');
979
+ const lex = new Lexer(doc.buf, sx + STARTXREF.length, doc.buf.length, doc.pace);
980
+ const first = lex.next();
981
+ if (first.t !== 'num')
982
+ throw new MalformedInput('the PDF has no startxref');
983
+ const queue = [first.v];
984
+ const seen = new Set();
985
+ while (queue.length > 0) {
986
+ const offset = queue.shift();
987
+ if (seen.has(offset))
988
+ throw new Refusal('the PDF cross-reference chain loops');
989
+ seen.add(offset);
990
+ if (seen.size > PDF_XREF_SECTIONS) {
991
+ throw new ExtractionBoundExceeded(`the PDF has more than ${PDF_XREF_SECTIONS} cross-reference sections`);
992
+ }
993
+ const trailer = await xrefSection(doc, offset);
994
+ if (seen.size === 1)
995
+ doc.trailer = trailer; // the newest section's trailer
996
+ // The hybrid file's stream comes before its own table's `/Prev`: it is the same update.
997
+ const stm = intOf(trailer.get('XRefStm'));
998
+ if (stm !== null)
999
+ queue.unshift(stm);
1000
+ const prev = intOf(trailer.get('Prev'));
1001
+ if (prev !== null)
1002
+ queue.push(prev);
1003
+ }
1004
+ }
1005
+ /** One section at `offset` — a classic table or a cross-reference stream — and its trailer. */
1006
+ async function xrefSection(doc, offset) {
1007
+ const lex = new Lexer(doc.buf, offset, doc.buf.length, doc.pace);
1008
+ const head = lex.next();
1009
+ if (head.t === 'kw' && head.v === 'xref') {
1010
+ for (;;) {
1011
+ if (doc.pace.room <= 0)
1012
+ await doc.pace.turn();
1013
+ const save = lex.pos;
1014
+ const a = lex.next();
1015
+ if (a.t === 'kw' && a.v === 'trailer')
1016
+ break;
1017
+ const b = lex.next();
1018
+ if (a.t !== 'num' || b.t !== 'num' || a.v < 0 || b.v < 0) {
1019
+ lex.pos = save;
1020
+ throw new MalformedInput('a PDF cross-reference table is damaged');
1021
+ }
1022
+ // Checked before the loop: a subsection that claims a billion rows is refused, not read.
1023
+ if (b.v > PDF_OBJECTS_MAX)
1024
+ throw tooManyObjects();
1025
+ for (let i = 0; i < b.v; i += 1) {
1026
+ if (doc.pace.room <= 0)
1027
+ await doc.pace.turn();
1028
+ const off = lex.next();
1029
+ const gen = lex.next();
1030
+ const kind = lex.next();
1031
+ if (off.t !== 'num' || gen.t !== 'num' || kind.t !== 'kw')
1032
+ throw new MalformedInput('a PDF cross-reference table is damaged');
1033
+ if (kind.v === 'n' && off.v > 0)
1034
+ doc.addEntry(a.v + i, { type: 1, offset: off.v });
1035
+ else if (kind.v === 'f' && !doc.xref.has(a.v + i))
1036
+ doc.addEntry(a.v + i, { type: 1, offset: -1 });
1037
+ }
1038
+ }
1039
+ const trailer = valueFrom(lex.next(), lex, true);
1040
+ if (!isDict(trailer))
1041
+ throw new MalformedInput('a PDF trailer is damaged');
1042
+ return trailer;
1043
+ }
1044
+ const s = await doc.objectAt(offset, null);
1045
+ if (!isStream(s) || nameOf(s.dict.get('Type')) !== 'XRef')
1046
+ throw new MalformedInput('a PDF cross-reference section is damaged');
1047
+ const decoded = await doc.decode(s);
1048
+ if (!decoded || decoded.exhausted)
1049
+ throw new MalformedInput('a PDF cross-reference stream is unreadable');
1050
+ const w = s.dict.get('W');
1051
+ const widths = Array.isArray(w) ? w.map((x) => intOf(x) ?? -1) : [];
1052
+ if (widths.length !== 3 || widths.some((x) => x < 0 || x > 8))
1053
+ throw new MalformedInput('a PDF cross-reference stream is damaged');
1054
+ const size = intOf(s.dict.get('Size')) ?? 0;
1055
+ const index = s.dict.get('Index');
1056
+ const ranges = Array.isArray(index) ? index.map((x) => intOf(x) ?? -1) : [0, size];
1057
+ const row = widths[0] + widths[1] + widths[2];
1058
+ if (row === 0)
1059
+ throw new MalformedInput('a PDF cross-reference stream is damaged');
1060
+ const data = decoded.data;
1061
+ const field = (at, width, fallback) => {
1062
+ if (width === 0)
1063
+ return fallback;
1064
+ let v = 0;
1065
+ for (let k = 0; k < width; k += 1)
1066
+ v = v * 256 + data[at + k];
1067
+ return v;
1068
+ };
1069
+ let at = 0;
1070
+ for (let r = 0; r + 1 < ranges.length; r += 2) {
1071
+ const start = ranges[r];
1072
+ const count = ranges[r + 1];
1073
+ if (start < 0 || count < 0)
1074
+ throw new MalformedInput('a PDF cross-reference stream is damaged');
1075
+ if (count > PDF_OBJECTS_MAX)
1076
+ throw tooManyObjects();
1077
+ for (let i = 0; i < count && at + row <= data.length; i += 1) {
1078
+ if (doc.pace.room <= 0)
1079
+ await doc.pace.turn();
1080
+ doc.pace.charge(row);
1081
+ const type = field(at, widths[0], 1);
1082
+ const f2 = field(at + widths[0], widths[1], 0);
1083
+ at += row;
1084
+ if (type === 1)
1085
+ doc.addEntry(start + i, { type: 1, offset: f2 });
1086
+ else if (type === 2)
1087
+ doc.addEntry(start + i, { type: 2, stream: f2 });
1088
+ else if (type === 0 && !doc.xref.has(start + i))
1089
+ doc.addEntry(start + i, { type: 1, offset: -1 });
1090
+ }
1091
+ }
1092
+ return s.dict;
1093
+ }
1094
+ /**
1095
+ * When the cross-reference data is unusable: every `N G obj` header in the file, the last one
1096
+ * of a number winning (an incremental update appends), each object stream's contents, and the
1097
+ * last `trailer` dictionary — or, with none, the catalog found among the objects.
1098
+ */
1099
+ async function scanObjects(doc) {
1100
+ doc.forgetXref();
1101
+ const buf = doc.buf;
1102
+ const OBJ = ascii('obj');
1103
+ const streams = [];
1104
+ for (let at = await findBytes(buf, OBJ, 0, doc.pace); at >= 0; at = await findBytes(buf, OBJ, at + 3, doc.pace)) {
1105
+ // Back over `N G ` before `obj` — never further than `OBJ_HEADER_SPAN`: a header is two
1106
+ // short numbers, and a digit run longer than that is not one, however long it goes on.
1107
+ const floor = Math.max(0, at - OBJ_HEADER_SPAN);
1108
+ doc.pace.charge(OBJ_HEADER_SPAN);
1109
+ let k = at - 1;
1110
+ while (k >= floor && isWhite(buf[k]))
1111
+ k -= 1;
1112
+ const genEnd = k + 1;
1113
+ while (k >= floor && buf[k] >= 48 && buf[k] <= 57)
1114
+ k -= 1;
1115
+ if (k + 1 === genEnd)
1116
+ continue;
1117
+ while (k >= floor && isWhite(buf[k]))
1118
+ k -= 1;
1119
+ const numEnd = k + 1;
1120
+ while (k >= floor && buf[k] >= 48 && buf[k] <= 57)
1121
+ k -= 1;
1122
+ // Stopped at the floor still inside the number: not a header this scan reads.
1123
+ if (k + 1 === numEnd || k < floor || (k >= 0 && isRegular(buf[k])))
1124
+ continue;
1125
+ doc.addEntry(Number(latin1(buf.subarray(k + 1, numEnd))), { type: 1, offset: k + 1 }, true);
1126
+ }
1127
+ for (const [num] of doc.xref) {
1128
+ if (doc.pace.room <= 0)
1129
+ await doc.pace.turn();
1130
+ const v = await doc.object(num).catch(onlyDamage(null));
1131
+ if (isStream(v) && nameOf(v.dict.get('Type')) === 'ObjStm')
1132
+ streams.push(num);
1133
+ }
1134
+ for (const container of streams) {
1135
+ const s = (await doc.object(container));
1136
+ const decoded = await doc.decode(s).catch(onlyDamage(null));
1137
+ if (!decoded)
1138
+ continue;
1139
+ for (const num of (await objectStreamOffsets(decoded.data, s.dict, doc.pace)).keys()) {
1140
+ doc.addEntry(num, { type: 2, stream: container });
1141
+ }
1142
+ }
1143
+ const t = await findLast(buf, TRAILER, buf.length, doc.pace);
1144
+ if (t >= 0) {
1145
+ const lex = new Lexer(buf, t + TRAILER.length, buf.length, doc.pace);
1146
+ const trailer = valueFrom(lex.next(), lex, true);
1147
+ if (isDict(trailer) && trailer.has('Root')) {
1148
+ doc.trailer = trailer;
1149
+ return;
1150
+ }
1151
+ }
1152
+ for (const [num] of doc.xref) {
1153
+ if (doc.pace.room <= 0)
1154
+ await doc.pace.turn();
1155
+ const v = await doc.object(num).catch(onlyDamage(null));
1156
+ if (isDict(v) && nameOf(v.get('Type')) === 'Catalog') {
1157
+ doc.trailer = new Map([['Root', { kind: 'ref', num, gen: 0 }]]);
1158
+ return;
1159
+ }
1160
+ }
1161
+ }
1162
+ /** A decoder for a font this cannot read: nothing, for every code. */
1163
+ const SILENT = { decode: (bytes) => ({ text: '', next: bytes.length }) };
1164
+ /**
1165
+ * What decoding one string byte is charged: a code's place in the code space and its mapping
1166
+ * are each a binary search — over a CMap's segments, which the memory budget admits by the
1167
+ * hundred thousand — not the constant a byte scan is.
1168
+ */
1169
+ const DECODE_COST_PER_BYTE = 32;
1170
+ const ACCENTS = {
1171
+ acute: '́', grave: '̀', circumflex: '̂', dieresis: '̈', tilde: '̃', ring: '̊',
1172
+ cedilla: '̧', caron: '̌', macron: '̄', breve: '̆', ogonek: '̨', dotaccent: '̇',
1173
+ hungarumlaut: '̋', commaaccent: '̦',
1174
+ };
1175
+ const GLYPHS = {
1176
+ space: ' ', exclam: '!', quotedbl: '"', numbersign: '#', dollar: '$', percent: '%', ampersand: '&',
1177
+ quotesingle: "'", quoteright: '’', parenleft: '(', parenright: ')', asterisk: '*', plus: '+', comma: ',',
1178
+ hyphen: '-', period: '.', slash: '/', zero: '0', one: '1', two: '2', three: '3', four: '4', five: '5', six: '6',
1179
+ seven: '7', eight: '8', nine: '9', colon: ':', semicolon: ';', less: '<', equal: '=', greater: '>', question: '?',
1180
+ at: '@', bracketleft: '[', backslash: '\\', bracketright: ']', asciicircum: '^', underscore: '_', grave: '`',
1181
+ quoteleft: '‘', braceleft: '{', bar: '|', braceright: '}', asciitilde: '~', exclamdown: '¡', cent: '¢',
1182
+ sterling: '£', fraction: '⁄', yen: '¥', florin: 'ƒ', section: '§', currency: '¤', quotedblleft: '“',
1183
+ guillemotleft: '«', guilsinglleft: '‹', guilsinglright: '›', fi: 'fi', fl: 'fl', ff: 'ff', ffi: 'ffi',
1184
+ ffl: 'ffl', endash: '–', dagger: '†', daggerdbl: '‡', periodcentered: '·', paragraph: '¶',
1185
+ bullet: '•', quotesinglbase: '‚', quotedblbase: '„', quotedblright: '”', guillemotright: '»',
1186
+ ellipsis: '…', perthousand: '‰', questiondown: '¿', emdash: '—', AE: 'Æ', ae: 'æ', ordfeminine: 'ª',
1187
+ ordmasculine: 'º', Lslash: 'Ł', lslash: 'ł', Oslash: 'Ø', oslash: 'ø', OE: 'Œ', oe: 'œ', dotlessi: 'ı',
1188
+ germandbls: 'ß', Eth: 'Ð', eth: 'ð', Thorn: 'Þ', thorn: 'þ', trademark: '™', copyright: '©',
1189
+ registered: '®', degree: '°', plusminus: '±', multiply: '×', divide: '÷', minus: '−', mu: 'µ',
1190
+ onehalf: '½', onequarter: '¼', threequarters: '¾', onesuperior: '¹', twosuperior: '²', threesuperior: '³',
1191
+ logicalnot: '¬', brokenbar: '¦', Euro: '€', nbspace: ' ', nonbreakingspace: ' ', sfthyphen: '-', softhyphen: '-',
1192
+ ...ACCENTS,
1193
+ };
1194
+ /** The longest glyph name read; a writer's names are a few characters, `uni…` a few dozen. */
1195
+ const GLYPH_NAME_MAX = 64;
1196
+ /**
1197
+ * A glyph name → its text: the names writers use, `uniXXXX`, `uXXXX`, and composed accents.
1198
+ * The patterns below are anchored with one quantifier each (or fixed-width groups), so none
1199
+ * can backtrack past linear — and a name is at most `GLYPH_NAME_MAX` long besides.
1200
+ */
1201
+ function glyphText(name) {
1202
+ if (name.length > GLYPH_NAME_MAX)
1203
+ return '';
1204
+ const base = name.split('.')[0];
1205
+ if (base.includes('_'))
1206
+ return base.split('_').map(glyphText).join('');
1207
+ if (GLYPHS[base] !== undefined)
1208
+ return GLYPHS[base];
1209
+ if (/^[A-Za-z]$/.test(base))
1210
+ return base;
1211
+ const uni = /^uni((?:[0-9A-F]{4})+)$/.exec(base);
1212
+ if (uni)
1213
+ return uni[1].match(/.{4}/g).map((h) => String.fromCharCode(Number.parseInt(h, 16))).join('');
1214
+ const u = /^u([0-9A-F]{4,6})$/.exec(base);
1215
+ if (u) {
1216
+ const cp = Number.parseInt(u[1], 16);
1217
+ return cp <= 0x10ffff ? String.fromCodePoint(cp) : '';
1218
+ }
1219
+ const accented = /^([A-Za-z])([a-z]+)$/.exec(base);
1220
+ if (accented && ACCENTS[accented[2]])
1221
+ return (accented[1] + ACCENTS[accented[2]]).normalize('NFC');
1222
+ return '';
1223
+ }
1224
+ const ASCII_RANGE = () => {
1225
+ const t = new Array(256).fill(null);
1226
+ for (let c = 32; c < 127; c += 1)
1227
+ t[c] = String.fromCharCode(c);
1228
+ return t;
1229
+ };
1230
+ const WIN_ANSI = (() => {
1231
+ const t = ASCII_RANGE();
1232
+ const high = '€\u0000‚ƒ„…†‡ˆ‰Š‹Œ\u0000Ž\u0000\u0000‘’“”•–—˜™š›œ\u0000žŸ';
1233
+ for (let i = 0; i < 32; i += 1)
1234
+ t[0x80 + i] = high[i] === '\u0000' ? null : high[i];
1235
+ for (let c = 0xa0; c <= 0xff; c += 1)
1236
+ t[c] = String.fromCharCode(c);
1237
+ return t;
1238
+ })();
1239
+ const MAC_ROMAN = (() => {
1240
+ const t = ASCII_RANGE();
1241
+ const high = 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ';
1242
+ for (let i = 0; i < 128; i += 1)
1243
+ t[0x80 + i] = high[i] ?? null;
1244
+ return t;
1245
+ })();
1246
+ const STANDARD = (() => {
1247
+ const t = ASCII_RANGE();
1248
+ t[0x27] = '’';
1249
+ t[0x60] = '‘';
1250
+ const high = {
1251
+ 0xa1: '¡', 0xa2: '¢', 0xa3: '£', 0xa4: '⁄', 0xa5: '¥', 0xa6: 'ƒ', 0xa7: '§', 0xa8: '¤', 0xa9: "'",
1252
+ 0xaa: '“', 0xab: '«', 0xac: '‹', 0xad: '›', 0xae: 'fi', 0xaf: 'fl', 0xb1: '–', 0xb2: '†',
1253
+ 0xb3: '‡', 0xb4: '·', 0xb6: '¶', 0xb7: '•', 0xb8: '‚', 0xb9: '„', 0xba: '”', 0xbb: '»',
1254
+ 0xbc: '…', 0xbd: '‰', 0xbf: '¿', 0xd0: '—', 0xe1: 'Æ', 0xe3: 'ª', 0xe8: 'Ł', 0xe9: 'Ø', 0xea: 'Œ',
1255
+ 0xeb: 'º', 0xf1: 'æ', 0xf5: 'ı', 0xf8: 'ł', 0xf9: 'ø', 0xfa: 'œ', 0xfb: 'ß',
1256
+ };
1257
+ for (const [k, v] of Object.entries(high))
1258
+ t[Number(k)] = v;
1259
+ return t;
1260
+ })();
1261
+ const BASE_ENCODINGS = {
1262
+ WinAnsiEncoding: WIN_ANSI,
1263
+ MacRomanEncoding: MAC_ROMAN,
1264
+ StandardEncoding: STANDARD,
1265
+ };
1266
+ /**
1267
+ * A code space: per code length (1–4 bytes), its ranges sorted and merged, so a code is placed
1268
+ * by a binary search — never a scan over every range a file declared (Codex #2062 r2).
1269
+ */
1270
+ class CodeSpace {
1271
+ /** `byLength[n]` holds the merged `[low, high]` ranges of n-byte codes, ascending. */
1272
+ byLength;
1273
+ constructor(ranges) {
1274
+ this.byLength = [[], [], [], [], []];
1275
+ for (const [len, lo, hi] of ranges)
1276
+ if (lo <= hi)
1277
+ this.byLength[len].push([lo, hi]);
1278
+ for (const list of this.byLength) {
1279
+ list.sort((a, b) => a[0] - b[0]);
1280
+ let w = 0;
1281
+ for (const r of list) {
1282
+ if (w > 0 && r[0] <= list[w - 1][1] + 1)
1283
+ list[w - 1][1] = Math.max(list[w - 1][1], r[1]);
1284
+ else
1285
+ list[w++] = r;
1286
+ }
1287
+ list.length = w;
1288
+ }
1289
+ }
1290
+ get empty() {
1291
+ return this.byLength.every((l) => l.length === 0);
1292
+ }
1293
+ /** Whether an n-byte code falls in the space. */
1294
+ has(length, code) {
1295
+ const list = this.byLength[length];
1296
+ let lo = 0;
1297
+ let hi = list.length - 1;
1298
+ while (lo <= hi) {
1299
+ const mid = (lo + hi) >> 1;
1300
+ const [a, b] = list[mid];
1301
+ if (code < a)
1302
+ hi = mid - 1;
1303
+ else if (code > b)
1304
+ lo = mid + 1;
1305
+ else
1306
+ return true;
1307
+ }
1308
+ return false;
1309
+ }
1310
+ }
1311
+ /** What one sealed `CodeMap` segment costs: its first and last key (float64 each) and a definition index. */
1312
+ const SEGMENT_COST = 20;
1313
+ /**
1314
+ * A ToUnicode mapping that holds what the CMap WROTE, not what it implies: each `bfchar` code
1315
+ * as its string, and each `bfrange` as one definition — its first and last code and its base
1316
+ * destination — whose destination for a code is computed when that code is looked up (#2062
1317
+ * r3). Expanding a range into one string per code let a 1.5 KiB file grow the heap by half a
1318
+ * gigabyte. Each definition is charged to the extraction's `Retained` before it is kept.
1319
+ *
1320
+ * Definitions overlap, and the LATER one wins for every code it names, whichever kind either is
1321
+ * — the precedence the map had when it held a string per code. `seal` resolves them once into
1322
+ * disjoint segments, each naming the definition that wins it, so a lookup is one binary search.
1323
+ */
1324
+ class CodeMap {
1325
+ retained;
1326
+ /** Each definition's first and last key, in the order the CMap wrote them. */
1327
+ los = [];
1328
+ his = [];
1329
+ /** And what it maps to: a `bfchar`'s text, or a `bfrange`'s destination. */
1330
+ targets = [];
1331
+ /** The sealed segments: `segLo[i]`…`segHi[i]` map through `targets[segDef[i]]`. */
1332
+ segLo = new Float64Array(0);
1333
+ segHi = new Float64Array(0);
1334
+ segDef = new Int32Array(0);
1335
+ segments = 0;
1336
+ constructor(retained) {
1337
+ this.retained = retained;
1338
+ }
1339
+ setChar(length, code, text) {
1340
+ this.retained.take(ENTRY_COST + text.length * 2);
1341
+ const key = codeKey(length, code);
1342
+ this.define(key, key, text);
1343
+ }
1344
+ /** Codes `from`…`to`, mapped to `base` counting up in its last byte, as the format specifies. */
1345
+ setRange(length, from, to, base) {
1346
+ if (to < from || base.length === 0)
1347
+ return;
1348
+ this.retained.take(ENTRY_COST + base.length * 2);
1349
+ const cut = base.length % 2 === 0 ? base.length - 2 : Math.max(0, base.length - 3);
1350
+ const lo = codeKey(length, from);
1351
+ this.define(lo, codeKey(length, to), { from: lo, prefix: utf16be(base.subarray(0, cut)), tail: Uint8Array.from(base.subarray(cut)) });
1352
+ }
1353
+ define(lo, hi, target) {
1354
+ this.los.push(lo);
1355
+ this.his.push(hi);
1356
+ this.targets.push(target);
1357
+ }
1358
+ /**
1359
+ * Resolve the definitions into disjoint segments, the later definition winning each code: a
1360
+ * sweep over every definition's first code and the one past its last, holding the definitions
1361
+ * open there in a heap by order. A CMap that writes its definitions ascending and apart — as
1362
+ * real ones do — is its own segment list and skips the sweep.
1363
+ */
1364
+ async seal(pace) {
1365
+ const n = this.los.length;
1366
+ // At most one segment between each two of a definition's 2n boundaries: charged before it is made.
1367
+ this.retained.take(2 * n * SEGMENT_COST);
1368
+ const segLo = new Float64Array(2 * n);
1369
+ const segHi = new Float64Array(2 * n);
1370
+ const segDef = new Int32Array(2 * n);
1371
+ let count = 0;
1372
+ let ordered = true;
1373
+ for (let i = 1; i < n && ordered; i += 1) {
1374
+ if (pace.room <= 0)
1375
+ await pace.turn();
1376
+ pace.charge(CALL_COST);
1377
+ ordered = this.los[i] > this.his[i - 1];
1378
+ }
1379
+ if (ordered) {
1380
+ for (let i = 0; i < n; i += 1) {
1381
+ if (pace.room <= 0)
1382
+ await pace.turn();
1383
+ pace.charge(CALL_COST);
1384
+ segLo[i] = this.los[i];
1385
+ segHi[i] = this.his[i];
1386
+ segDef[i] = i;
1387
+ }
1388
+ count = n;
1389
+ }
1390
+ else {
1391
+ // What the sweep holds while it runs: two orders, a merge buffer and the heap.
1392
+ const working = 4 * n * 4;
1393
+ this.retained.take(working);
1394
+ // The definitions by first code, and by the code past their last — each stable, so a tie
1395
+ // keeps the order written.
1396
+ const byLo = await pacedOrder(this.los, n, pace);
1397
+ const byEnd = await pacedOrder(this.his, n, pace);
1398
+ // A max-heap of the open definitions by index: the latest one written wins.
1399
+ const heap = new Int32Array(n);
1400
+ let size = 0;
1401
+ const push = (d) => {
1402
+ let i = size++;
1403
+ while (i > 0 && heap[(i - 1) >> 1] < d) {
1404
+ heap[i] = heap[(i - 1) >> 1];
1405
+ i = (i - 1) >> 1;
1406
+ }
1407
+ heap[i] = d;
1408
+ };
1409
+ const pop = () => {
1410
+ const d = heap[--size];
1411
+ let i = 0;
1412
+ for (;;) {
1413
+ let c = 2 * i + 1;
1414
+ if (c >= size)
1415
+ break;
1416
+ if (c + 1 < size && heap[c + 1] > heap[c])
1417
+ c += 1;
1418
+ if (heap[c] <= d)
1419
+ break;
1420
+ heap[i] = heap[c];
1421
+ i = c;
1422
+ }
1423
+ heap[i] = d;
1424
+ };
1425
+ // Every boundary in order: a definition's first code, or the code past its last.
1426
+ let nextLo = 0;
1427
+ let nextEnd = 0;
1428
+ const boundary = () => Math.min(nextLo < n ? this.los[byLo[nextLo]] : Infinity, nextEnd < n ? this.his[byEnd[nextEnd]] + 1 : Infinity);
1429
+ // A heap step sifts through every level: charged as that many steps.
1430
+ const sift = () => CALL_COST * (32 - Math.clz32(size + 1));
1431
+ for (let at = boundary(); at !== Infinity;) {
1432
+ if (pace.room <= 0)
1433
+ await pace.turn();
1434
+ pace.charge(CALL_COST);
1435
+ // Any number of definitions may open, end or expire at one boundary — one code named
1436
+ // 250 000 times opens them all here — so each is paced on its own, not the boundary once.
1437
+ while (nextLo < n && this.los[byLo[nextLo]] === at) {
1438
+ if (pace.room <= 0)
1439
+ await pace.turn();
1440
+ pace.charge(sift());
1441
+ push(byLo[nextLo++]);
1442
+ }
1443
+ while (nextEnd < n && this.his[byEnd[nextEnd]] + 1 === at) {
1444
+ if (pace.room <= 0)
1445
+ await pace.turn();
1446
+ pace.charge(CALL_COST);
1447
+ nextEnd += 1;
1448
+ }
1449
+ while (size > 0 && this.his[heap[0]] < at) {
1450
+ if (pace.room <= 0)
1451
+ await pace.turn();
1452
+ pace.charge(sift());
1453
+ pop();
1454
+ }
1455
+ const following = boundary();
1456
+ if (size > 0) {
1457
+ // The winner here is open until the next boundary at least: its own end is one.
1458
+ const d = heap[0];
1459
+ if (count > 0 && segDef[count - 1] === d && segHi[count - 1] === at - 1)
1460
+ segHi[count - 1] = following - 1;
1461
+ else {
1462
+ segLo[count] = at;
1463
+ segHi[count] = following - 1;
1464
+ segDef[count] = d;
1465
+ count += 1;
1466
+ }
1467
+ }
1468
+ at = following;
1469
+ }
1470
+ this.retained.give(working);
1471
+ }
1472
+ this.segLo = segLo;
1473
+ this.segHi = segHi;
1474
+ this.segDef = segDef;
1475
+ this.segments = count;
1476
+ // The definitions' bounds are in the segments now.
1477
+ this.los = [];
1478
+ this.his = [];
1479
+ return this;
1480
+ }
1481
+ get(length, code) {
1482
+ const key = codeKey(length, code);
1483
+ // The last segment starting at or before the key; it maps the code if it reaches it.
1484
+ let lo = 0;
1485
+ let hi = this.segments - 1;
1486
+ let found = -1;
1487
+ while (lo <= hi) {
1488
+ const mid = (lo + hi) >> 1;
1489
+ if (this.segLo[mid] <= key) {
1490
+ found = mid;
1491
+ lo = mid + 1;
1492
+ }
1493
+ else
1494
+ hi = mid - 1;
1495
+ }
1496
+ if (found < 0 || key > this.segHi[found])
1497
+ return undefined;
1498
+ const target = this.targets[this.segDef[found]];
1499
+ return typeof target === 'string' ? target : target.prefix + countedTail(target.tail, key - target.from);
1500
+ }
1501
+ }
1502
+ /**
1503
+ * A `bfrange` destination's last one to three bytes, counted up by `offset` in the last byte —
1504
+ * carrying once into the byte before it, as the format specifies — as text, allocating nothing
1505
+ * but the string.
1506
+ */
1507
+ function countedTail(tail, offset) {
1508
+ const last = tail[tail.length - 1] + offset;
1509
+ const low = last & 0xff;
1510
+ if (tail.length === 1)
1511
+ return String.fromCharCode(low);
1512
+ const carried = (tail[tail.length - 2] + (last >> 8)) & 0xff;
1513
+ if (tail.length === 2)
1514
+ return String.fromCharCode((carried << 8) | low);
1515
+ return String.fromCharCode((tail[0] << 8) | carried) + String.fromCharCode(low);
1516
+ }
1517
+ /**
1518
+ * The indices `0…n-1` ordered by `keys`, stably: a bottom-up merge sort that charges every
1519
+ * element it moves to `pace` and yields between strides, so ordering a CMap of any size the
1520
+ * memory budget admits never holds the thread.
1521
+ */
1522
+ async function pacedOrder(keys, n, pace) {
1523
+ let from = Int32Array.from({ length: n }, (_, i) => i);
1524
+ let to = new Int32Array(n);
1525
+ for (let width = 1; width < n; width *= 2) {
1526
+ for (let start = 0; start < n; start += 2 * width) {
1527
+ const mid = Math.min(start + width, n);
1528
+ const end = Math.min(start + 2 * width, n);
1529
+ let a = start;
1530
+ let b = mid;
1531
+ for (let k = start; k < end; k += 1) {
1532
+ if (pace.room <= 0)
1533
+ await pace.turn();
1534
+ pace.charge(CALL_COST);
1535
+ to[k] = b >= end || (a < mid && keys[from[a]] <= keys[from[b]]) ? from[a++] : from[b++];
1536
+ }
1537
+ }
1538
+ [from, to] = [to, from];
1539
+ }
1540
+ return from;
1541
+ }
1542
+ const codeKey = (length, code) => length * 0x1_0000_0000 + code;
1543
+ /** Identity-H and -V: every two-byte code. */
1544
+ const IDENTITY_SPACE = new CodeSpace([[2, 0, 0xffff]]);
1545
+ const utf16be = (b) => {
1546
+ let s = '';
1547
+ for (let i = 0; i + 1 < b.length; i += 2)
1548
+ s += String.fromCharCode((b[i] << 8) | b[i + 1]);
1549
+ if (b.length % 2 === 1)
1550
+ s += String.fromCharCode(b[b.length - 1]);
1551
+ return s;
1552
+ };
1553
+ const bytesToInt = (b) => b.reduce((v, x) => v * 256 + x, 0);
1554
+ /**
1555
+ * A CMap stream's `codespacerange`, `bfchar` and `bfrange` sections, every definition charged
1556
+ * to `retained` before it is kept — parsed once per stream (`PdfDocument.cmaps`). However many
1557
+ * definitions name one code, the last one written wins it.
1558
+ */
1559
+ async function parseCMap(data, pace, retained) {
1560
+ const lex = new Lexer(data, 0, data.length, pace);
1561
+ const ranges = [];
1562
+ const map = new CodeMap(retained);
1563
+ const operands = [];
1564
+ for (;;) {
1565
+ if (pace.room <= 0)
1566
+ await pace.turn();
1567
+ const tok = lex.next();
1568
+ if (tok.t === 'eof')
1569
+ break;
1570
+ if (tok.t !== 'kw') {
1571
+ operands.push(valueFrom(tok, lex, false));
1572
+ // A section holds at most 100 entries; past that many operands it is not a CMap.
1573
+ if (operands.length > 3 * OPERANDS_MAX)
1574
+ operands.length = 0;
1575
+ continue;
1576
+ }
1577
+ if (tok.v === 'begincodespacerange' || tok.v === 'beginbfchar' || tok.v === 'beginbfrange')
1578
+ operands.length = 0;
1579
+ else if (tok.v === 'endcodespacerange') {
1580
+ for (let i = 0; i + 1 < operands.length; i += 2) {
1581
+ const lo = operands[i];
1582
+ const hi = operands[i + 1];
1583
+ // At most `CMAP_SPACES_MAX` ranges, however many sections declare more.
1584
+ if (isString(lo) && isString(hi) && lo.bytes.length >= 1 && lo.bytes.length <= 4 && ranges.length < CMAP_SPACES_MAX) {
1585
+ ranges.push([lo.bytes.length, bytesToInt(lo.bytes), bytesToInt(hi.bytes.subarray(0, 4))]);
1586
+ }
1587
+ }
1588
+ operands.length = 0;
1589
+ }
1590
+ else if (tok.v === 'endbfchar') {
1591
+ for (let i = 0; i + 1 < operands.length; i += 2) {
1592
+ const src = operands[i];
1593
+ const dst = operands[i + 1];
1594
+ if (isString(src) && isString(dst) && src.bytes.length <= 4 && dst.bytes.length <= CMAP_DST_MAX) {
1595
+ map.setChar(src.bytes.length, bytesToInt(src.bytes), utf16be(dst.bytes));
1596
+ }
1597
+ }
1598
+ operands.length = 0;
1599
+ }
1600
+ else if (tok.v === 'endbfrange') {
1601
+ for (let i = 0; i + 2 < operands.length; i += 3) {
1602
+ const lo = operands[i];
1603
+ const hi = operands[i + 1];
1604
+ const dst = operands[i + 2];
1605
+ if (!isString(lo) || !isString(hi) || lo.bytes.length > 4)
1606
+ continue;
1607
+ const from = bytesToInt(lo.bytes);
1608
+ const to = Math.min(bytesToInt(hi.bytes.subarray(0, 4)), from + 0xffff);
1609
+ if (isString(dst)) {
1610
+ // One entry for the whole range; a destination past the format's 512 bytes is not one.
1611
+ if (dst.bytes.length <= CMAP_DST_MAX)
1612
+ map.setRange(lo.bytes.length, from, to, dst.bytes);
1613
+ }
1614
+ else if (Array.isArray(dst)) {
1615
+ // An array names each code's destination itself: as many entries as it holds.
1616
+ for (let k = 0; k < dst.length && from + k <= to; k += 1) {
1617
+ if (pace.room <= 0)
1618
+ await pace.turn();
1619
+ pace.charge(1);
1620
+ const d = dst[k];
1621
+ if (isString(d) && d.bytes.length <= CMAP_DST_MAX)
1622
+ map.setChar(lo.bytes.length, from + k, utf16be(d.bytes));
1623
+ }
1624
+ }
1625
+ }
1626
+ operands.length = 0;
1627
+ }
1628
+ }
1629
+ return { spaces: new CodeSpace(ranges), map: await map.seal(pace) };
1630
+ }
1631
+ /** A CMap stream, parsed once per object number however many fonts name it. */
1632
+ async function cmapOf(doc, ref) {
1633
+ const num = isRef(ref) ? ref.num : null;
1634
+ if (num !== null && doc.cmaps.has(num))
1635
+ return doc.cmaps.get(num);
1636
+ const s = await doc.resolve(ref);
1637
+ let cmap = null;
1638
+ if (isStream(s)) {
1639
+ const decoded = await doc.decode(s);
1640
+ if (decoded)
1641
+ cmap = await parseCMap(decoded.data, doc.pace, doc.retained);
1642
+ }
1643
+ if (num !== null)
1644
+ doc.cmaps.set(num, cmap);
1645
+ return cmap;
1646
+ }
1647
+ /**
1648
+ * A string's text through a code space and a code → text map, for the codes starting in
1649
+ * `from`…`until`: each code the shortest length whose bytes fall in the space (or `fallback`
1650
+ * bytes), placed by at most four binary searches, and mapped by one more.
1651
+ */
1652
+ function codesToText(bytes, from, until, spaces, fallback, map) {
1653
+ let s = '';
1654
+ let i = from;
1655
+ while (i < until) {
1656
+ let length = 0;
1657
+ let code = 0;
1658
+ for (let n = 1; n <= 4 && i + n <= bytes.length; n += 1) {
1659
+ code = code * 256 + bytes[i + n - 1];
1660
+ if (spaces.has(n, code)) {
1661
+ length = n;
1662
+ break;
1663
+ }
1664
+ }
1665
+ if (length === 0) {
1666
+ length = Math.min(fallback, bytes.length - i);
1667
+ code = 0;
1668
+ for (let n = 0; n < length; n += 1)
1669
+ code = code * 256 + bytes[i + n];
1670
+ }
1671
+ s += map.get(length, code) ?? '';
1672
+ i += length;
1673
+ }
1674
+ return { text: s, next: i };
1675
+ }
1676
+ /** A font's decoder: `/ToUnicode` first, then its encoding; unreadable composite fonts give nothing. */
1677
+ async function fontDecoder(doc, font) {
1678
+ const subtype = nameOf(font.get('Subtype'));
1679
+ // Each font's decoder is kept for the extraction (`Reading.fonts`): charged before it is built,
1680
+ // for what it holds. A CMap it reads is charged as that CMap is parsed, once however many fonts share it.
1681
+ const toUnicode = await cmapOf(doc, font.get('ToUnicode'));
1682
+ if (subtype === 'Type0') {
1683
+ doc.retained.take(COMPOSITE_FONT_COST);
1684
+ const encRef = font.get('Encoding');
1685
+ let spaces = IDENTITY_SPACE;
1686
+ const encoding = isName(encRef) ? null : await cmapOf(doc, encRef);
1687
+ if (encoding && !encoding.spaces.empty)
1688
+ spaces = encoding.spaces;
1689
+ else if (!encoding && toUnicode && !toUnicode.spaces.empty)
1690
+ spaces = toUnicode.spaces;
1691
+ return toUnicode ? { decode: (bytes, from, until) => codesToText(bytes, from, until, spaces, 2, toUnicode.map) } : SILENT;
1692
+ }
1693
+ // A simple font: one byte per code.
1694
+ const enc = await doc.resolve(font.get('Encoding'));
1695
+ const encDict = isDict(enc) ? enc : null;
1696
+ const baseName = nameOf(encDict ? encDict.get('BaseEncoding') : enc) ?? '';
1697
+ const base = Object.hasOwn(BASE_ENCODINGS, baseName) ? BASE_ENCODINGS[baseName] : subtype === 'TrueType' ? WIN_ANSI : STANDARD;
1698
+ doc.retained.take(SIMPLE_FONT_COST);
1699
+ const table = [...base];
1700
+ const differences = encDict?.get('Differences');
1701
+ if (Array.isArray(differences)) {
1702
+ let code = 0;
1703
+ for (const d of differences) {
1704
+ if (typeof d === 'number')
1705
+ code = d;
1706
+ else if (isName(d) && code >= 0 && code < 256) {
1707
+ const text = glyphText(d.name);
1708
+ doc.retained.take(DIFFERENCE_COST + text.length * 2);
1709
+ table[code++] = text;
1710
+ }
1711
+ }
1712
+ }
1713
+ return {
1714
+ decode: (bytes, from, until) => {
1715
+ let s = '';
1716
+ for (let i = from; i < until; i += 1) {
1717
+ const mapped = toUnicode?.map.get(1, bytes[i]);
1718
+ s += mapped ?? table[bytes[i]] ?? '';
1719
+ }
1720
+ return { text: s, next: until };
1721
+ },
1722
+ };
1723
+ }
1724
+ // -- content ------------------------------------------------------------------------------
1725
+ /** Damage is skipped where the rest of the file still reads; a bound or an abort never is. */
1726
+ function onlyDamage(fallback) {
1727
+ return (err) => {
1728
+ if (err instanceof MalformedInput)
1729
+ return fallback;
1730
+ throw err;
1731
+ };
1732
+ }
1733
+ function emit(r, s) {
1734
+ if (s.length === 0)
1735
+ return;
1736
+ r.out.push(s);
1737
+ r.collected += s.length;
1738
+ if (r.collected > r.limit)
1739
+ throw new Enough();
1740
+ }
1741
+ const LINE_OPERATORS = new Set(['T*', "'", '"', 'Tm', 'ET']);
1742
+ const INLINE_DATA = ascii('ID');
1743
+ const INLINE_END = ascii('EI');
1744
+ /** Interpret one content stream, emitting the text it shows. */
1745
+ async function interpret(r, content, resources, depth) {
1746
+ if (content.length > r.interpretLeft) {
1747
+ r.interpretLeft = 0;
1748
+ throw new Enough();
1749
+ }
1750
+ r.interpretLeft -= content.length;
1751
+ const { doc } = r;
1752
+ const pace = doc.pace;
1753
+ const lex = new Lexer(content, 0, content.length, pace);
1754
+ const operands = [];
1755
+ let font = null;
1756
+ const fontsDict = await doc.resolveDict(resources?.get('Font'));
1757
+ const xobjects = await doc.resolveDict(resources?.get('XObject'));
1758
+ // A string up to a token long, each byte a lookup: decoded a window at a time, charged as
1759
+ // the work it is, so the thread is never held for the whole string at once.
1760
+ const show = async (v) => {
1761
+ if (!isString(v) || !font)
1762
+ return;
1763
+ const { bytes } = v;
1764
+ for (let at = 0; at < bytes.length;) {
1765
+ if (pace.room <= 0)
1766
+ await pace.turn();
1767
+ const until = Math.min(bytes.length, at + Math.max(1, Math.floor(pace.room / DECODE_COST_PER_BYTE)));
1768
+ const { text, next } = font.decode(bytes, at, until);
1769
+ pace.charge((next - at) * DECODE_COST_PER_BYTE);
1770
+ emit(r, text);
1771
+ at = next;
1772
+ }
1773
+ };
1774
+ for (;;) {
1775
+ if (pace.room <= 0)
1776
+ await pace.turn();
1777
+ const tok = lex.next();
1778
+ if (tok.t === 'eof')
1779
+ return;
1780
+ if (tok.t !== 'kw' || tok.v === 'true' || tok.v === 'false' || tok.v === 'null') {
1781
+ operands.push(valueFrom(tok, lex, false));
1782
+ if (operands.length > OPERANDS_MAX)
1783
+ operands.length = 0;
1784
+ continue;
1785
+ }
1786
+ const op = tok.v;
1787
+ if (op === 'BI') {
1788
+ // An inline image: its data is binary, so skip to an `EI` standing alone after it.
1789
+ const id = await findBytes(content, INLINE_DATA, lex.pos, pace);
1790
+ if (id < 0)
1791
+ return;
1792
+ let e = id + 2;
1793
+ for (;;) {
1794
+ e = await findBytes(content, INLINE_END, e, pace);
1795
+ if (e < 0)
1796
+ return;
1797
+ if (isWhite(content[e - 1]) && (e + 2 >= content.length || !isRegular(content[e + 2])))
1798
+ break;
1799
+ e += 1;
1800
+ }
1801
+ lex.pos = e + 2;
1802
+ }
1803
+ else if (op === 'Tf') {
1804
+ const fontName = nameOf(operands[operands.length - 2]);
1805
+ font = null;
1806
+ const fontDict = fontName ? await doc.resolveDict(fontsDict?.get(fontName)) : null;
1807
+ if (fontDict) {
1808
+ font = r.fonts.get(fontDict) ?? null;
1809
+ if (!font) {
1810
+ // A font this cannot read draws nothing; the rest of the page still reads.
1811
+ font = await fontDecoder(doc, fontDict).catch(onlyDamage(SILENT));
1812
+ r.fonts.set(fontDict, font);
1813
+ }
1814
+ }
1815
+ }
1816
+ else if (op === 'Tj')
1817
+ await show(operands[operands.length - 1]);
1818
+ else if (op === "'" || op === '"') {
1819
+ emit(r, '\n');
1820
+ await show(operands[operands.length - 1]);
1821
+ }
1822
+ else if (op === 'TJ') {
1823
+ const arr = operands[operands.length - 1];
1824
+ if (Array.isArray(arr)) {
1825
+ for (const item of arr) {
1826
+ // A wide negative adjustment is the gap a writer leaves for a space.
1827
+ if (typeof item === 'number' && item < -150)
1828
+ emit(r, ' ');
1829
+ else
1830
+ await show(item);
1831
+ }
1832
+ }
1833
+ }
1834
+ else if (op === 'Td' || op === 'TD') {
1835
+ const ty = operands[operands.length - 1];
1836
+ const tx = operands[operands.length - 2];
1837
+ if (typeof ty === 'number' && ty !== 0)
1838
+ emit(r, '\n');
1839
+ else if (typeof tx === 'number' && tx !== 0)
1840
+ emit(r, ' ');
1841
+ }
1842
+ else if (LINE_OPERATORS.has(op))
1843
+ emit(r, '\n');
1844
+ else if (op === 'Do' && depth < PDF_FORM_DEPTH_MAX) {
1845
+ const ref = xobjects?.get(nameOf(operands[operands.length - 1]) ?? '');
1846
+ const num = isRef(ref) ? ref.num : null;
1847
+ if (num !== null && !r.drawing.has(num)) {
1848
+ const form = await doc.resolve(ref);
1849
+ if (isStream(form) && nameOf(form.dict.get('Subtype')) === 'Form') {
1850
+ const decoded = await doc.decode(form);
1851
+ if (decoded) {
1852
+ r.drawing.add(num);
1853
+ try {
1854
+ const own = await doc.resolveDict(form.dict.get('Resources'));
1855
+ await interpret(r, decoded.data, own ?? resources, depth + 1);
1856
+ }
1857
+ finally {
1858
+ r.drawing.delete(num);
1859
+ }
1860
+ if (decoded.exhausted)
1861
+ throw new Enough();
1862
+ }
1863
+ }
1864
+ }
1865
+ }
1866
+ operands.length = 0;
1867
+ }
1868
+ }
1869
+ /** A page's content: one stream or an array of them, read as one. */
1870
+ async function pageContent(doc, page) {
1871
+ const c = await doc.resolve(page.get('Contents'));
1872
+ const parts = Array.isArray(c) ? c : [c];
1873
+ const pieces = [];
1874
+ let exhausted = false;
1875
+ for (const part of parts) {
1876
+ const s = await doc.resolve(part);
1877
+ if (!isStream(s))
1878
+ continue;
1879
+ const d = await doc.decode(s);
1880
+ if (!d)
1881
+ continue;
1882
+ // Parts are read as one stream, a line apart: a token never runs from one into the next.
1883
+ if (pieces.length > 0)
1884
+ pieces.push(NEWLINE);
1885
+ pieces.push(d.data);
1886
+ if ((exhausted = d.exhausted))
1887
+ break;
1888
+ }
1889
+ if (pieces.length === 0)
1890
+ return null;
1891
+ if (pieces.length === 1)
1892
+ return { data: pieces[0], exhausted, held: 0 };
1893
+ // Joining is a new allocation of every part's size — a part named twice is joined twice —
1894
+ // so it is charged before it is made, and given back once the page is read (#2062 r3).
1895
+ const held = pieces.reduce((n, p) => n + p.length, 0);
1896
+ doc.retained.take(held);
1897
+ return { data: concatBytes(pieces), exhausted, held };
1898
+ }
1899
+ /** Walk the page tree in order, drawing each page: depth- and node-bounded, ancestors refused. */
1900
+ async function readPages(r, node, inherited, depth) {
1901
+ if (depth > PDF_TREE_DEPTH_MAX)
1902
+ throw new MalformedInput('the PDF page tree is nested too deeply');
1903
+ const num = isRef(node) ? node.num : null;
1904
+ if (num !== null && r.ancestors.has(num))
1905
+ throw new MalformedInput('the PDF page tree loops');
1906
+ if ((r.visited += 1) > PDF_TREE_NODES_MAX)
1907
+ throw new Enough();
1908
+ const dict = await r.doc.resolveDict(node);
1909
+ if (!dict)
1910
+ return;
1911
+ const resources = (await r.doc.resolveDict(dict.get('Resources'))) ?? inherited;
1912
+ const kids = await r.doc.resolve(dict.get('Kids'));
1913
+ if (Array.isArray(kids)) {
1914
+ if (num !== null)
1915
+ r.ancestors.add(num);
1916
+ try {
1917
+ for (const kid of kids)
1918
+ await readPages(r, kid, resources, depth + 1);
1919
+ }
1920
+ finally {
1921
+ if (num !== null)
1922
+ r.ancestors.delete(num);
1923
+ }
1924
+ return;
1925
+ }
1926
+ const content = await pageContent(r.doc, dict).catch(onlyDamage(null));
1927
+ if (!content)
1928
+ return;
1929
+ // A damaged page keeps the text read before the damage; the next page still reads.
1930
+ try {
1931
+ await interpret(r, content.data, resources, 0).catch(onlyDamage(undefined));
1932
+ }
1933
+ finally {
1934
+ r.doc.retained.give(content.held);
1935
+ }
1936
+ emit(r, '\n\n');
1937
+ if (content.exhausted)
1938
+ throw new Enough();
1939
+ }
1940
+ /**
1941
+ * A PDF's text, in page order. `{ text, truncated }` like every extractor here; a file that is
1942
+ * not a readable PDF throws `MalformedInput` and one that breaks a bound
1943
+ * `ExtractionBoundExceeded`, which the extractor turns into `{ failed }`.
1944
+ */
1945
+ export async function pdfExtract(body, maxInflatedBytes, maxTextBytes, signal,
1946
+ /** The memory budget; the package's own tests pass one to read what was charged. */
1947
+ retained = new Retained(maxInflatedBytes * PDF_RETAINED_FACTOR + PDF_RETAINED_BASE),
1948
+ /** The work budget; the package's own tests pass one to count what was charged. */
1949
+ pace = new Pace(signal)) {
1950
+ if (latin1(body.subarray(0, 1024)).indexOf('%PDF-') < 0)
1951
+ throw new MalformedInput('not a PDF file');
1952
+ const doc = new PdfDocument(body, { remaining: maxInflatedBytes }, pace, retained);
1953
+ let root = null;
1954
+ const refuseEncrypted = () => {
1955
+ if (doc.trailer.has('Encrypt'))
1956
+ throw new Refusal('the PDF is encrypted, and encrypted PDFs are not read');
1957
+ };
1958
+ try {
1959
+ await readXref(doc);
1960
+ refuseEncrypted();
1961
+ root = await doc.resolveDict(doc.trailer.get('Root'));
1962
+ }
1963
+ catch (err) {
1964
+ // A refusal or a bound is the file's answer; only DAMAGE earns a second way in.
1965
+ if (!(err instanceof MalformedInput) || err instanceof Refusal)
1966
+ throw err;
1967
+ }
1968
+ if (!root) {
1969
+ await scanObjects(doc);
1970
+ root = await doc.resolveDict(doc.trailer.get('Root'));
1971
+ }
1972
+ refuseEncrypted();
1973
+ if (!root)
1974
+ throw new MalformedInput('the PDF has no document catalog');
1975
+ const r = {
1976
+ doc,
1977
+ out: [],
1978
+ collected: 0,
1979
+ limit: maxTextBytes * COLLECT_FACTOR,
1980
+ interpretLeft: maxInflatedBytes * PDF_INTERPRET_FACTOR,
1981
+ fonts: new WeakMap(),
1982
+ drawing: new Set(),
1983
+ ancestors: new Set(),
1984
+ visited: 0,
1985
+ };
1986
+ let truncated = false;
1987
+ try {
1988
+ await readPages(r, root.get('Pages') ?? null, null, 0);
1989
+ }
1990
+ catch (err) {
1991
+ // A spent memory bound while reading pages ends the reading as the text budget does: what
1992
+ // was read is kept, marked cut. Before the pages, it fails the file (`extractWith`).
1993
+ if (!(err instanceof Enough) && !(err instanceof RetainedBoundExceeded))
1994
+ throw err;
1995
+ truncated = true;
1996
+ }
1997
+ return { text: r.out.join(''), truncated };
1998
+ }
1999
+ /** The glyph-name and encoding tables, for the package's own tests. */
2000
+ export const pdfTables = { glyphText, WIN_ANSI, MAC_ROMAN, STANDARD };
2001
+ /** The stream decoders, for the package's own tests: each judged on what it charges and keeps. */
2002
+ export const pdfDecoders = { unpredict, asciiHex, ascii85, runLength, lzw };
2003
+ /** The lexer, for the package's own tests: judged on what it charges for one token. */
2004
+ /** The CMap parser, for the package's own tests. */
2005
+ export const pdfCMap = (data, pace, retained) => parseCMap(data, pace, retained);
2006
+ /** What a font's decoder is charged, for the package's own tests. */
2007
+ export const pdfFontCosts = { simple: SIMPLE_FONT_COST, difference: DIFFERENCE_COST, composite: COMPOSITE_FONT_COST, decodePerByte: DECODE_COST_PER_BYTE };
2008
+ /** A CodeMap with nothing parsed into it, for the package's own tests to define and seal directly. */
2009
+ export const pdfCodeMap = (retained) => new CodeMap(retained);
2010
+ export const pdfLexer = (buf, pace) => new Lexer(buf, 0, buf.length, pace);
2011
+ //# sourceMappingURL=pdf.js.map