@aglyn/aglyn 1.0.0-beta.197 → 1.0.0-beta.199

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/docs-help.generated.d.ts +1 -1
  3. package/src/lib/app-utils/docs-help.generated.js +1 -0
  4. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  5. package/src/lib/app-utils/docs-index.generated.js +3 -3
  6. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  7. package/src/lib/app-utils/media-embedded-fields.d.ts +471 -0
  8. package/src/lib/app-utils/media-embedded-fields.js +619 -0
  9. package/src/lib/app-utils/media-embedded-fields.js.map +1 -0
  10. package/src/lib/app-utils/media-embedded-metadata/exif.d.ts +160 -0
  11. package/src/lib/app-utils/media-embedded-metadata/exif.js +1267 -0
  12. package/src/lib/app-utils/media-embedded-metadata/exif.js.map +1 -0
  13. package/src/lib/app-utils/media-embedded-metadata/gif.d.ts +44 -0
  14. package/src/lib/app-utils/media-embedded-metadata/gif.js +134 -0
  15. package/src/lib/app-utils/media-embedded-metadata/gif.js.map +1 -0
  16. package/src/lib/app-utils/media-embedded-metadata/heif.d.ts +23 -0
  17. package/src/lib/app-utils/media-embedded-metadata/heif.js +354 -0
  18. package/src/lib/app-utils/media-embedded-metadata/heif.js.map +1 -0
  19. package/src/lib/app-utils/media-embedded-metadata/icc.d.ts +22 -0
  20. package/src/lib/app-utils/media-embedded-metadata/icc.js +85 -0
  21. package/src/lib/app-utils/media-embedded-metadata/icc.js.map +1 -0
  22. package/src/lib/app-utils/media-embedded-metadata/image-blocks.d.ts +145 -0
  23. package/src/lib/app-utils/media-embedded-metadata/image-blocks.js +200 -0
  24. package/src/lib/app-utils/media-embedded-metadata/image-blocks.js.map +1 -0
  25. package/src/lib/app-utils/media-embedded-metadata/index.d.ts +93 -0
  26. package/src/lib/app-utils/media-embedded-metadata/index.js +744 -0
  27. package/src/lib/app-utils/media-embedded-metadata/index.js.map +1 -0
  28. package/src/lib/app-utils/media-embedded-metadata/iptc.d.ts +59 -0
  29. package/src/lib/app-utils/media-embedded-metadata/iptc.js +529 -0
  30. package/src/lib/app-utils/media-embedded-metadata/iptc.js.map +1 -0
  31. package/src/lib/app-utils/media-embedded-metadata/jpeg.d.ts +43 -0
  32. package/src/lib/app-utils/media-embedded-metadata/jpeg.js +456 -0
  33. package/src/lib/app-utils/media-embedded-metadata/jpeg.js.map +1 -0
  34. package/src/lib/app-utils/media-embedded-metadata/mp4.d.ts +37 -0
  35. package/src/lib/app-utils/media-embedded-metadata/mp4.js +621 -0
  36. package/src/lib/app-utils/media-embedded-metadata/mp4.js.map +1 -0
  37. package/src/lib/app-utils/media-embedded-metadata/ooxml.d.ts +131 -0
  38. package/src/lib/app-utils/media-embedded-metadata/ooxml.js +1058 -0
  39. package/src/lib/app-utils/media-embedded-metadata/ooxml.js.map +1 -0
  40. package/src/lib/app-utils/media-embedded-metadata/pdf-objects.d.ts +223 -0
  41. package/src/lib/app-utils/media-embedded-metadata/pdf-objects.js +936 -0
  42. package/src/lib/app-utils/media-embedded-metadata/pdf-objects.js.map +1 -0
  43. package/src/lib/app-utils/media-embedded-metadata/pdf.d.ts +82 -0
  44. package/src/lib/app-utils/media-embedded-metadata/pdf.js +1167 -0
  45. package/src/lib/app-utils/media-embedded-metadata/pdf.js.map +1 -0
  46. package/src/lib/app-utils/media-embedded-metadata/png.d.ts +41 -0
  47. package/src/lib/app-utils/media-embedded-metadata/png.js +512 -0
  48. package/src/lib/app-utils/media-embedded-metadata/png.js.map +1 -0
  49. package/src/lib/app-utils/media-embedded-metadata/svg.d.ts +30 -0
  50. package/src/lib/app-utils/media-embedded-metadata/svg.js +154 -0
  51. package/src/lib/app-utils/media-embedded-metadata/svg.js.map +1 -0
  52. package/src/lib/app-utils/media-embedded-metadata/tiff.d.ts +40 -0
  53. package/src/lib/app-utils/media-embedded-metadata/tiff.js +158 -0
  54. package/src/lib/app-utils/media-embedded-metadata/tiff.js.map +1 -0
  55. package/src/lib/app-utils/media-embedded-metadata/types.d.ts +60 -0
  56. package/src/lib/app-utils/media-embedded-metadata/types.js +32 -0
  57. package/src/lib/app-utils/media-embedded-metadata/types.js.map +1 -0
  58. package/src/lib/app-utils/media-embedded-metadata/webm.d.ts +26 -0
  59. package/src/lib/app-utils/media-embedded-metadata/webm.js +417 -0
  60. package/src/lib/app-utils/media-embedded-metadata/webm.js.map +1 -0
  61. package/src/lib/app-utils/media-embedded-metadata/webp.d.ts +37 -0
  62. package/src/lib/app-utils/media-embedded-metadata/webp.js +272 -0
  63. package/src/lib/app-utils/media-embedded-metadata/webp.js.map +1 -0
  64. package/src/lib/app-utils/media-embedded-metadata/xml.d.ts +231 -0
  65. package/src/lib/app-utils/media-embedded-metadata/xml.js +598 -0
  66. package/src/lib/app-utils/media-embedded-metadata/xml.js.map +1 -0
  67. package/src/lib/app-utils/media-embedded-metadata/xmp.d.ts +100 -0
  68. package/src/lib/app-utils/media-embedded-metadata/xmp.js +1475 -0
  69. package/src/lib/app-utils/media-embedded-metadata/xmp.js.map +1 -0
  70. package/src/lib/app-utils/media-embedded-metadata/zip.d.ts +93 -0
  71. package/src/lib/app-utils/media-embedded-metadata/zip.js +531 -0
  72. package/src/lib/app-utils/media-embedded-metadata/zip.js.map +1 -0
  73. package/src/lib/app-utils/name-search.d.ts +12 -0
  74. package/src/lib/app-utils/name-search.js +11 -0
  75. package/src/lib/app-utils/name-search.js.map +1 -1
  76. package/src/lib/foundation/definitions/platform.types.d.ts +11 -0
  77. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  78. package/src/server.d.ts +1 -0
  79. package/src/server.js +7 -0
  80. package/src/server.js.map +1 -1
@@ -0,0 +1,936 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * The object layer under the PDF metadata reader and writer (AGL-3331).
18
+ *
19
+ * - A tokenizer and parser for the COS syntax of ISO 32000-2 §7.2–§7.3:
20
+ * names, literal and hexadecimal strings, numbers, arrays, dictionaries,
21
+ * indirect references, `stream`…`endstream` bodies, booleans and null.
22
+ * - A serializer that writes a parsed object back out faithfully, so a
23
+ * dictionary the writer re-emits (a catalog gaining `/Metadata`, an Info
24
+ * dictionary with one entry changed) keeps every other entry as it was.
25
+ * - The text-string codec of §7.9.2.2 (PDFDocEncoding, UTF-16BE, UTF-8).
26
+ * - FlateDecode with the §7.4.4.4 predictors — the only filter a reader of
27
+ * cross-reference streams, object streams and metadata streams needs.
28
+ *
29
+ * ## Hostile input
30
+ *
31
+ * Every byte here comes from an uploaded file. Nothing in this module
32
+ * throws on malformed input: the parser returns `null` for what it cannot
33
+ * read, nesting is capped at {@link PDF_MAX_DEPTH}, one object may spend at
34
+ * most {@link PDF_MAX_TOKENS} tokens, every offset is checked against the
35
+ * buffer, and inflation is capped by the caller's `maxOutputLength`.
36
+ */ import { constants, inflateRawSync, inflateSync } from "zlib";
37
+ export const PDF_NULL = {
38
+ kind: 'null'
39
+ };
40
+ /** Deepest array/dictionary nesting the parser follows. */ export const PDF_MAX_DEPTH = 64;
41
+ /** Tokens one object may consume before the parser gives up on it. */ export const PDF_MAX_TOKENS = 2000000;
42
+ /** Longest name or bare keyword kept; the rest of the run is skipped. */ const MAX_WORD = 4096;
43
+ // §7.2.3 character classes: 0 regular, 1 white-space, 2 delimiter.
44
+ const CHAR_CLASS = new Uint8Array(256);
45
+ for (const b of [
46
+ 0x00,
47
+ 0x09,
48
+ 0x0a,
49
+ 0x0c,
50
+ 0x0d,
51
+ 0x20
52
+ ])CHAR_CLASS[b] = 1;
53
+ for (const c of '()<>[]{}/%')CHAR_CLASS[c.charCodeAt(0)] = 2;
54
+ /** A byte of `bytes`, or -1 outside `[0, end)`. */ function byteAt(bytes, i, end = bytes.length) {
55
+ var _bytes_i;
56
+ return i >= 0 && i < end ? (_bytes_i = bytes[i]) != null ? _bytes_i : -1 : -1;
57
+ }
58
+ export function isPdfWhitespace(b) {
59
+ return b >= 0 && b < 256 && CHAR_CLASS[b] === 1;
60
+ }
61
+ function isRegular(b) {
62
+ return b >= 0 && b < 256 && CHAR_CLASS[b] === 0;
63
+ }
64
+ function hexValue(b) {
65
+ if (b >= 0x30 && b <= 0x39) return b - 0x30;
66
+ if (b >= 0x41 && b <= 0x46) return b - 0x37;
67
+ if (b >= 0x61 && b <= 0x66) return b - 0x57;
68
+ return -1;
69
+ }
70
+ /** Bytes as a binary string, one char per byte. */ export function latin1Text(bytes) {
71
+ let out = '';
72
+ for(let i = 0; i < bytes.length; i += 8192){
73
+ out += String.fromCharCode(...bytes.subarray(i, i + 8192));
74
+ }
75
+ return out;
76
+ }
77
+ /** A binary string (chars 0–255) as bytes. */ export function latin1Bytes(text) {
78
+ const out = new Uint8Array(text.length);
79
+ for(let i = 0; i < text.length; i++)out[i] = text.charCodeAt(i) & 0xff;
80
+ return out;
81
+ }
82
+ /** The first index of `needle` in `bytes[from, to)`, or -1. */ export function indexOfBytes(bytes, needle, from = 0, to = bytes.length) {
83
+ const first = needle[0];
84
+ if (first === undefined) return -1;
85
+ const last = Math.min(to, bytes.length) - needle.length;
86
+ outer: for(let i = Math.max(0, from); i <= last; i++){
87
+ if (bytes[i] !== first) continue;
88
+ for(let j = 1; j < needle.length; j++){
89
+ if (bytes[i + j] !== needle[j]) continue outer;
90
+ }
91
+ return i;
92
+ }
93
+ return -1;
94
+ }
95
+ /** The last index of `needle` in `bytes` starting at or before `from`. */ export function lastIndexOfBytes(bytes, needle, from = bytes.length - needle.length) {
96
+ const first = needle[0];
97
+ if (first === undefined) return -1;
98
+ outer: for(let i = Math.min(from, bytes.length - needle.length); i >= 0; i--){
99
+ if (bytes[i] !== first) continue;
100
+ for(let j = 1; j < needle.length; j++){
101
+ if (bytes[i + j] !== needle[j]) continue outer;
102
+ }
103
+ return i;
104
+ }
105
+ return -1;
106
+ }
107
+ /** A growable byte buffer for string bodies of unknown length. */ let ByteSink = class ByteSink {
108
+ push(b) {
109
+ if (this.length === this.buf.length) {
110
+ const next = new Uint8Array(this.buf.length * 2);
111
+ next.set(this.buf);
112
+ this.buf = next;
113
+ }
114
+ this.buf[this.length++] = b;
115
+ }
116
+ bytes() {
117
+ return this.buf.slice(0, this.length);
118
+ }
119
+ constructor(){
120
+ this.buf = new Uint8Array(64);
121
+ this.length = 0;
122
+ }
123
+ };
124
+ const NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)$/;
125
+ const INTEGER = /^\d+$/;
126
+ /** The §7.2 tokenizer. `end` is exclusive; nothing is read past it. */ export class PdfLexer {
127
+ at(i) {
128
+ return byteAt(this.bytes, i, this.end);
129
+ }
130
+ /** Skips white-space and `%` comments (§7.2.4). */ skipSpace() {
131
+ for(;;){
132
+ const b = this.at(this.pos);
133
+ if (b < 0) return;
134
+ if (isPdfWhitespace(b)) {
135
+ this.pos++;
136
+ } else if (b === 0x25) {
137
+ while(this.pos < this.end){
138
+ const c = this.at(this.pos);
139
+ if (c === 0x0a || c === 0x0d) break;
140
+ this.pos++;
141
+ }
142
+ } else {
143
+ return;
144
+ }
145
+ }
146
+ }
147
+ /** Whether `word` is the next token, as a whole keyword. */ peekKeyword(word) {
148
+ this.skipSpace();
149
+ for(let i = 0; i < word.length; i++){
150
+ if (this.at(this.pos + i) !== word.charCodeAt(i)) return false;
151
+ }
152
+ return !isRegular(this.at(this.pos + word.length));
153
+ }
154
+ next() {
155
+ this.skipSpace();
156
+ const b = this.at(this.pos);
157
+ if (b < 0) return {
158
+ type: 'eof'
159
+ };
160
+ switch(b){
161
+ case 0x2f:
162
+ return this.name();
163
+ case 0x28:
164
+ return this.literalString();
165
+ case 0x3c:
166
+ if (this.at(this.pos + 1) === 0x3c) {
167
+ this.pos += 2;
168
+ return {
169
+ type: 'punct',
170
+ text: '<<'
171
+ };
172
+ }
173
+ return this.hexString();
174
+ case 0x3e:
175
+ if (this.at(this.pos + 1) === 0x3e) {
176
+ this.pos += 2;
177
+ return {
178
+ type: 'punct',
179
+ text: '>>'
180
+ };
181
+ }
182
+ this.pos++;
183
+ return {
184
+ type: 'keyword',
185
+ text: '>'
186
+ };
187
+ case 0x5b:
188
+ this.pos++;
189
+ return {
190
+ type: 'punct',
191
+ text: '['
192
+ };
193
+ case 0x5d:
194
+ this.pos++;
195
+ return {
196
+ type: 'punct',
197
+ text: ']'
198
+ };
199
+ case 0x7b:
200
+ this.pos++;
201
+ return {
202
+ type: 'punct',
203
+ text: '{'
204
+ };
205
+ case 0x7d:
206
+ this.pos++;
207
+ return {
208
+ type: 'punct',
209
+ text: '}'
210
+ };
211
+ case 0x29:
212
+ this.pos++;
213
+ return {
214
+ type: 'keyword',
215
+ text: ')'
216
+ };
217
+ }
218
+ let text = '';
219
+ while(isRegular(this.at(this.pos))){
220
+ if (text.length < MAX_WORD) text += String.fromCharCode(this.at(this.pos));
221
+ this.pos++;
222
+ }
223
+ if (NUMBER.test(text)) {
224
+ const value = Number(text);
225
+ if (Number.isFinite(value)) return {
226
+ type: 'number',
227
+ value,
228
+ text
229
+ };
230
+ }
231
+ return {
232
+ type: 'keyword',
233
+ text
234
+ };
235
+ }
236
+ /** §7.3.5: a name, with `#xx` escapes decoded to bytes. */ name() {
237
+ this.pos++;
238
+ let name = '';
239
+ while(isRegular(this.at(this.pos))){
240
+ let b = this.at(this.pos);
241
+ if (b === 0x23) {
242
+ const hi = hexValue(this.at(this.pos + 1));
243
+ const lo = hexValue(this.at(this.pos + 2));
244
+ if (hi >= 0 && lo >= 0) {
245
+ b = hi * 16 + lo;
246
+ this.pos += 2;
247
+ }
248
+ }
249
+ if (name.length < MAX_WORD) name += String.fromCharCode(b);
250
+ this.pos++;
251
+ }
252
+ return {
253
+ type: 'name',
254
+ name
255
+ };
256
+ }
257
+ /**
258
+ * §7.3.4.2: a literal string. Balanced parentheses nest; `\` escapes
259
+ * `n r t b f ( ) \`, one to three octal digits, and an end-of-line (a
260
+ * line continuation, which contributes nothing). An unescaped CR, LF or
261
+ * CRLF reads as a single LF. An unterminated string ends at the buffer.
262
+ */ literalString() {
263
+ this.pos++;
264
+ const out = new ByteSink();
265
+ let depth = 1;
266
+ while(this.pos < this.end){
267
+ const b = this.at(this.pos++);
268
+ if (b === 0x28) {
269
+ depth++;
270
+ out.push(b);
271
+ } else if (b === 0x29) {
272
+ if (--depth === 0) break;
273
+ out.push(b);
274
+ } else if (b === 0x5c) {
275
+ const e = this.at(this.pos++);
276
+ switch(e){
277
+ case 0x6e:
278
+ out.push(0x0a);
279
+ break;
280
+ case 0x72:
281
+ out.push(0x0d);
282
+ break;
283
+ case 0x74:
284
+ out.push(0x09);
285
+ break;
286
+ case 0x62:
287
+ out.push(0x08);
288
+ break;
289
+ case 0x66:
290
+ out.push(0x0c);
291
+ break;
292
+ case 0x0d:
293
+ if (this.at(this.pos) === 0x0a) this.pos++;
294
+ break;
295
+ case 0x0a:
296
+ break;
297
+ case -1:
298
+ break;
299
+ default:
300
+ if (e >= 0x30 && e <= 0x37) {
301
+ let code = e - 0x30;
302
+ for(let k = 0; k < 2; k++){
303
+ const d = this.at(this.pos);
304
+ if (d < 0x30 || d > 0x37) break;
305
+ code = code * 8 + (d - 0x30);
306
+ this.pos++;
307
+ }
308
+ out.push(code & 0xff);
309
+ } else {
310
+ out.push(e);
311
+ }
312
+ }
313
+ } else if (b === 0x0d) {
314
+ if (this.at(this.pos) === 0x0a) this.pos++;
315
+ out.push(0x0a);
316
+ } else {
317
+ out.push(b);
318
+ }
319
+ }
320
+ return {
321
+ type: 'string',
322
+ bytes: out.bytes(),
323
+ hex: false
324
+ };
325
+ }
326
+ /**
327
+ * §7.3.4.3: a hexadecimal string. White-space is ignored, an odd final
328
+ * digit is followed by an implied 0, and any other stray byte is skipped
329
+ * rather than ending the string.
330
+ */ hexString() {
331
+ this.pos++;
332
+ const out = new ByteSink();
333
+ let high = -1;
334
+ while(this.pos < this.end){
335
+ const b = this.at(this.pos++);
336
+ if (b === 0x3e) break;
337
+ const v = hexValue(b);
338
+ if (v < 0) continue;
339
+ if (high < 0) {
340
+ high = v;
341
+ } else {
342
+ out.push(high * 16 + v);
343
+ high = -1;
344
+ }
345
+ }
346
+ if (high >= 0) out.push(high * 16);
347
+ return {
348
+ type: 'string',
349
+ bytes: out.bytes(),
350
+ hex: true
351
+ };
352
+ }
353
+ constructor(bytes, pos = 0, end = bytes.length){
354
+ this.bytes = bytes;
355
+ this.end = Math.max(0, Math.min(end, bytes.length));
356
+ this.pos = Math.max(0, pos);
357
+ }
358
+ }
359
+ /** Keywords that end whatever is being parsed; seeing one means a truncated container. */ const STRUCTURAL = new Set([
360
+ 'obj',
361
+ 'endobj',
362
+ 'stream',
363
+ 'endstream',
364
+ 'xref',
365
+ 'trailer',
366
+ 'startxref'
367
+ ]);
368
+ const ENDSTREAM = latin1Bytes('endstream');
369
+ /**
370
+ * The §7.3 object parser over {@link PdfLexer}. Returns `null` for what it
371
+ * cannot read rather than throwing; see the module doc for the limits.
372
+ */ export class PdfParser {
373
+ get pos() {
374
+ return this.lexer.pos;
375
+ }
376
+ /** One direct object at the current position. */ parseObject() {
377
+ this.failed = false;
378
+ this.budget = PDF_MAX_TOKENS;
379
+ const obj = this.object(this.lexer.next(), 0);
380
+ return this.failed ? null : obj;
381
+ }
382
+ /**
383
+ * `num gen obj … endobj` at the current position (§7.3.10). A dictionary
384
+ * followed by `stream` becomes a {@link PdfStream} (§7.3.8). `endobj` is
385
+ * not required: plenty of writers forget it, and nothing depends on it.
386
+ */ parseIndirectObject() {
387
+ const numToken = this.lexer.next();
388
+ const genToken = this.lexer.next();
389
+ if (!isIntegerToken(numToken) || !isIntegerToken(genToken)) return null;
390
+ if (!this.lexer.peekKeyword('obj')) return null;
391
+ this.lexer.next();
392
+ this.failed = false;
393
+ this.budget = PDF_MAX_TOKENS;
394
+ const start = this.lexer.pos;
395
+ const token = this.lexer.next();
396
+ let obj;
397
+ if (token.type === 'keyword' && token.text === 'endobj') {
398
+ obj = PDF_NULL;
399
+ } else {
400
+ obj = this.object(token, 0);
401
+ if (this.failed || !obj) {
402
+ this.lexer.pos = start;
403
+ return null;
404
+ }
405
+ }
406
+ if (obj.kind === 'dict' && this.lexer.peekKeyword('stream')) {
407
+ this.lexer.next();
408
+ obj = this.streamBody(obj);
409
+ }
410
+ if (this.lexer.peekKeyword('endobj')) this.lexer.next();
411
+ return {
412
+ num: numToken.value,
413
+ gen: genToken.value,
414
+ obj
415
+ };
416
+ }
417
+ object(token, depth) {
418
+ if (--this.budget < 0 || depth > PDF_MAX_DEPTH) {
419
+ this.failed = true;
420
+ return null;
421
+ }
422
+ switch(token.type){
423
+ case 'number':
424
+ {
425
+ if (INTEGER.test(token.text)) {
426
+ const mark = this.lexer.pos;
427
+ const gen = this.lexer.next();
428
+ if (isIntegerToken(gen)) {
429
+ const r = this.lexer.next();
430
+ if (r.type === 'keyword' && r.text === 'R') {
431
+ return {
432
+ kind: 'ref',
433
+ num: token.value,
434
+ gen: gen.value
435
+ };
436
+ }
437
+ }
438
+ this.lexer.pos = mark;
439
+ }
440
+ return {
441
+ kind: 'number',
442
+ value: token.value,
443
+ text: token.text
444
+ };
445
+ }
446
+ case 'name':
447
+ return {
448
+ kind: 'name',
449
+ name: token.name
450
+ };
451
+ case 'string':
452
+ return {
453
+ kind: 'string',
454
+ bytes: token.bytes,
455
+ hex: token.hex
456
+ };
457
+ case 'keyword':
458
+ if (token.text === 'true') return {
459
+ kind: 'boolean',
460
+ value: true
461
+ };
462
+ if (token.text === 'false') return {
463
+ kind: 'boolean',
464
+ value: false
465
+ };
466
+ if (token.text === 'null') return PDF_NULL;
467
+ return null;
468
+ case 'punct':
469
+ if (token.text === '[') return this.array(depth);
470
+ if (token.text === '<<') return this.dict(depth);
471
+ return null;
472
+ default:
473
+ return null;
474
+ }
475
+ }
476
+ array(depth) {
477
+ const items = [];
478
+ for(;;){
479
+ const mark = this.lexer.pos;
480
+ const token = this.lexer.next();
481
+ if (token.type === 'eof') break;
482
+ if (token.type === 'punct' && token.text === ']') break;
483
+ if (token.type === 'keyword' && STRUCTURAL.has(token.text)) {
484
+ this.lexer.pos = mark;
485
+ break;
486
+ }
487
+ const item = this.object(token, depth + 1);
488
+ if (this.failed) return null;
489
+ if (item) items.push(item);
490
+ }
491
+ return {
492
+ kind: 'array',
493
+ items
494
+ };
495
+ }
496
+ dict(depth) {
497
+ const entries = new Map();
498
+ for(;;){
499
+ const mark = this.lexer.pos;
500
+ const token = this.lexer.next();
501
+ if (token.type === 'eof') break;
502
+ if (token.type === 'punct' && token.text === '>>') break;
503
+ if (token.type === 'keyword' && STRUCTURAL.has(token.text)) {
504
+ this.lexer.pos = mark;
505
+ break;
506
+ }
507
+ if (token.type !== 'name') {
508
+ // A key that is not a name: skip it (and whatever it opened).
509
+ this.object(token, depth + 1);
510
+ if (this.failed) return null;
511
+ continue;
512
+ }
513
+ const valueMark = this.lexer.pos;
514
+ const valueToken = this.lexer.next();
515
+ if (valueToken.type === 'punct' && valueToken.text === '>>') break;
516
+ if (valueToken.type === 'eof' || valueToken.type === 'keyword' && STRUCTURAL.has(valueToken.text)) {
517
+ this.lexer.pos = valueMark;
518
+ break;
519
+ }
520
+ const value = this.object(valueToken, depth + 1);
521
+ if (this.failed) return null;
522
+ // §7.3.7 leaves duplicate keys undefined; the first one wins, as in
523
+ // Poppler, whose `pdfinfo` is what these files are checked against.
524
+ if (value && !entries.has(token.name)) entries.set(token.name, value);
525
+ }
526
+ return {
527
+ kind: 'dict',
528
+ entries
529
+ };
530
+ }
531
+ /**
532
+ * §7.3.8.1: the data starts after the EOL that ends the `stream` keyword
533
+ * (CRLF or LF; a lone CR is tolerated) and runs for `/Length` bytes. When
534
+ * `/Length` is missing, unresolvable or does not land on `endstream`, the
535
+ * data runs to the next `endstream` instead, less the EOL before it.
536
+ */ streamBody(dict) {
537
+ const { bytes } = this;
538
+ const end = this.lexer.end;
539
+ let start = this.lexer.pos;
540
+ if (byteAt(bytes, start, end) === 0x0d) start++;
541
+ if (byteAt(bytes, start, end) === 0x0a) start++;
542
+ const length = this.streamLength(dict);
543
+ if (length !== null && start + length <= end) {
544
+ const probe = new PdfLexer(bytes, start + length, end);
545
+ if (probe.peekKeyword('endstream')) {
546
+ probe.next();
547
+ this.lexer.pos = probe.pos;
548
+ return {
549
+ kind: 'stream',
550
+ dict,
551
+ data: bytes.subarray(start, start + length)
552
+ };
553
+ }
554
+ }
555
+ const found = indexOfBytes(bytes, ENDSTREAM, start, end);
556
+ let stop = found < 0 ? end : found;
557
+ if (byteAt(bytes, stop - 1, end) === 0x0a && stop > start) stop--;
558
+ if (byteAt(bytes, stop - 1, end) === 0x0d && stop > start) stop--;
559
+ this.lexer.pos = found < 0 ? end : found + ENDSTREAM.length;
560
+ return {
561
+ kind: 'stream',
562
+ dict,
563
+ data: bytes.subarray(start, stop)
564
+ };
565
+ }
566
+ streamLength(dict) {
567
+ var _ref;
568
+ const value = dict.entries.get('Length');
569
+ let length = null;
570
+ if ((value == null ? void 0 : value.kind) === 'number') length = value.value;
571
+ else if ((value == null ? void 0 : value.kind) === 'ref') length = (_ref = this.options.resolveLength == null ? void 0 : this.options.resolveLength.call(this.options, value)) != null ? _ref : null;
572
+ return length !== null && Number.isSafeInteger(length) && length >= 0 ? length : null;
573
+ }
574
+ constructor(bytes, pos = 0, options = {}, end = bytes.length){
575
+ this.bytes = bytes;
576
+ this.options = options;
577
+ this.budget = PDF_MAX_TOKENS;
578
+ this.failed = false;
579
+ this.lexer = new PdfLexer(bytes, pos, end);
580
+ }
581
+ }
582
+ function isIntegerToken(token) {
583
+ return token.type === 'number' && INTEGER.test(token.text);
584
+ }
585
+ // ---------------------------------------------------------------------------
586
+ // Serializing
587
+ const HEX = '0123456789ABCDEF';
588
+ /** §7.3.5: regular characters 0x21–0x7E other than `#` are written as-is. */ export function serializePdfName(name) {
589
+ let out = '/';
590
+ for(let i = 0; i < name.length; i++){
591
+ const b = name.charCodeAt(i) & 0xff;
592
+ if (b > 0x20 && b < 0x7f && b !== 0x23 && CHAR_CLASS[b] === 0) {
593
+ out += String.fromCharCode(b);
594
+ } else {
595
+ out += '#' + HEX.charAt(b >> 4) + HEX.charAt(b & 15);
596
+ }
597
+ }
598
+ return out;
599
+ }
600
+ /**
601
+ * A string as a literal (§7.3.4.2) or, when it was read as one, a hex
602
+ * string (§7.3.4.3). Literal bytes outside printable ASCII are written as
603
+ * three-digit octal escapes so the output is 7-bit clean and byte-exact.
604
+ */ export function serializePdfString(value) {
605
+ if (value.hex) {
606
+ let out = '<';
607
+ for (const b of value.bytes)out += HEX.charAt(b >> 4) + HEX.charAt(b & 15);
608
+ return out + '>';
609
+ }
610
+ let out = '(';
611
+ for (const b of value.bytes){
612
+ if (b === 0x28 || b === 0x29 || b === 0x5c) out += '\\' + String.fromCharCode(b);
613
+ else if (b === 0x0a) out += '\\n';
614
+ else if (b === 0x0d) out += '\\r';
615
+ else if (b === 0x09) out += '\\t';
616
+ else if (b >= 0x20 && b < 0x7f) out += String.fromCharCode(b);
617
+ else out += '\\' + b.toString(8).padStart(3, '0');
618
+ }
619
+ return out + ')';
620
+ }
621
+ /**
622
+ * An object as PDF syntax, in a binary string (one char per byte). Streams
623
+ * cannot be nested inside another object (§7.3.8.1), so one met here is
624
+ * written as `null`; a top-level stream is laid out by its writer.
625
+ */ export function serializePdfObject(obj, depth = 0) {
626
+ if (depth > PDF_MAX_DEPTH) return 'null';
627
+ switch(obj.kind){
628
+ case 'null':
629
+ return 'null';
630
+ case 'boolean':
631
+ return obj.value ? 'true' : 'false';
632
+ case 'number':
633
+ return NUMBER.test(obj.text) ? obj.text : String(obj.value);
634
+ case 'string':
635
+ return serializePdfString(obj);
636
+ case 'name':
637
+ return serializePdfName(obj.name);
638
+ case 'ref':
639
+ return `${obj.num} ${obj.gen} R`;
640
+ case 'array':
641
+ return '[' + obj.items.map((item)=>serializePdfObject(item, depth + 1)).join(' ') + ']';
642
+ case 'dict':
643
+ {
644
+ const entries = [];
645
+ for (const [key, value] of obj.entries){
646
+ entries.push(serializePdfName(key) + ' ' + serializePdfObject(value, depth + 1));
647
+ }
648
+ return '<<' + entries.join(' ') + '>>';
649
+ }
650
+ case 'stream':
651
+ return 'null';
652
+ }
653
+ }
654
+ // ---------------------------------------------------------------------------
655
+ // Text strings (§7.9.2.2)
656
+ /**
657
+ * PDFDocEncoding (Annex D.2) where it differs from Latin-1: 0x18–0x1F are
658
+ * spacing accents and 0x80–0xA0 are typographic marks. 0x7F, 0x9F and 0xAD
659
+ * are undefined and read as their Latin-1 code points, as pdf.js does.
660
+ */ const PDF_DOC_ENCODING = {
661
+ 0x18: 0x02d8,
662
+ 0x19: 0x02c7,
663
+ 0x1a: 0x02c6,
664
+ 0x1b: 0x02d9,
665
+ 0x1c: 0x02dd,
666
+ 0x1d: 0x02db,
667
+ 0x1e: 0x02da,
668
+ 0x1f: 0x02dc,
669
+ 0x80: 0x2022,
670
+ 0x81: 0x2020,
671
+ 0x82: 0x2021,
672
+ 0x83: 0x2026,
673
+ 0x84: 0x2014,
674
+ 0x85: 0x2013,
675
+ 0x86: 0x0192,
676
+ 0x87: 0x2044,
677
+ 0x88: 0x2039,
678
+ 0x89: 0x203a,
679
+ 0x8a: 0x2212,
680
+ 0x8b: 0x2030,
681
+ 0x8c: 0x201e,
682
+ 0x8d: 0x201c,
683
+ 0x8e: 0x201d,
684
+ 0x8f: 0x2018,
685
+ 0x90: 0x2019,
686
+ 0x91: 0x201a,
687
+ 0x92: 0x2122,
688
+ 0x93: 0xfb01,
689
+ 0x94: 0xfb02,
690
+ 0x95: 0x0141,
691
+ 0x96: 0x0152,
692
+ 0x97: 0x0160,
693
+ 0x98: 0x0178,
694
+ 0x99: 0x017d,
695
+ 0x9a: 0x0131,
696
+ 0x9b: 0x0142,
697
+ 0x9c: 0x0153,
698
+ 0x9d: 0x0161,
699
+ 0x9e: 0x017e,
700
+ 0xa0: 0x20ac
701
+ };
702
+ function utf16Text(bytes, start, littleEndian) {
703
+ let out = '';
704
+ for(let i = start; i + 1 < bytes.length; i += 2){
705
+ var _bytes_i, _bytes_;
706
+ const a = (_bytes_i = bytes[i]) != null ? _bytes_i : 0;
707
+ const b = (_bytes_ = bytes[i + 1]) != null ? _bytes_ : 0;
708
+ out += String.fromCharCode(littleEndian ? b * 256 + a : a * 256 + b);
709
+ }
710
+ // Lone surrogates from a truncated pair become U+FFFD.
711
+ return out.toWellFormed();
712
+ }
713
+ /**
714
+ * A text string's bytes as text (§7.9.2.2): UTF-16BE after a FE FF byte
715
+ * order mark, UTF-8 after EF BB BF (PDF 2.0), PDFDocEncoding otherwise.
716
+ * UTF-16LE after FF FE is not in the standard but is written by enough
717
+ * software that reading it as PDFDocEncoding ("ÿþ…") helps nobody.
718
+ * Language escapes (ESC lang ESC, §7.9.2.2.1) and trailing NULs are removed.
719
+ */ export function decodePdfTextString(bytes) {
720
+ let text;
721
+ if (bytes[0] === 0xfe && bytes[1] === 0xff) {
722
+ text = utf16Text(bytes, 2, false);
723
+ } else if (bytes[0] === 0xff && bytes[1] === 0xfe) {
724
+ text = utf16Text(bytes, 2, true);
725
+ } else if (bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) {
726
+ text = new TextDecoder('utf-8').decode(bytes.subarray(3));
727
+ } else {
728
+ var _PDF_DOC_ENCODING_b;
729
+ text = '';
730
+ for (const b of bytes)text += String.fromCharCode((_PDF_DOC_ENCODING_b = PDF_DOC_ENCODING[b]) != null ? _PDF_DOC_ENCODING_b : b);
731
+ }
732
+ return stripLanguageEscapes(text).replace(TRAILING_NULS, '');
733
+ }
734
+ const ESC = String.fromCharCode(0x1b);
735
+ const TRAILING_NULS = new RegExp(String.fromCharCode(0) + '+$');
736
+ /** §7.9.2.2.1: ESC, a 2-letter language, an optional 2-letter country, ESC. */ function stripLanguageEscapes(text) {
737
+ let out = '';
738
+ let at = 0;
739
+ for(;;){
740
+ const open = text.indexOf(ESC, at);
741
+ if (open < 0) break;
742
+ const close = text.indexOf(ESC, open + 1);
743
+ if (close < 0 || close - open > 9) {
744
+ out += text.slice(at, open + 1);
745
+ at = open + 1;
746
+ continue;
747
+ }
748
+ out += text.slice(at, open);
749
+ at = close + 1;
750
+ }
751
+ return out + text.slice(at);
752
+ }
753
+ /**
754
+ * Text as a text string: a literal when every char is printable ASCII (or
755
+ * tab, CR, LF, which escape cleanly and mean the same in PDFDocEncoding),
756
+ * otherwise UTF-16BE with a byte order mark, written as hex (§7.9.2.2).
757
+ */ export function encodePdfTextString(text) {
758
+ if (/^[\x20-\x7e\t\n\r]*$/.test(text)) {
759
+ return {
760
+ kind: 'string',
761
+ bytes: latin1Bytes(text),
762
+ hex: false
763
+ };
764
+ }
765
+ const units = text.toWellFormed();
766
+ const bytes = new Uint8Array(2 + units.length * 2);
767
+ bytes[0] = 0xfe;
768
+ bytes[1] = 0xff;
769
+ for(let i = 0; i < units.length; i++){
770
+ const unit = units.charCodeAt(i);
771
+ bytes[2 + i * 2] = unit >> 8;
772
+ bytes[3 + i * 2] = unit & 0xff;
773
+ }
774
+ return {
775
+ kind: 'string',
776
+ bytes,
777
+ hex: true
778
+ };
779
+ }
780
+ const UTF8_STRICT = new TextDecoder('utf-8', {
781
+ fatal: true
782
+ });
783
+ /**
784
+ * A name's bytes as display text: UTF-8 when they are valid UTF-8 (PDF 2.0
785
+ * §7.3.5 recommends it), Latin-1 otherwise.
786
+ */ export function pdfNameText(name) {
787
+ let ascii = true;
788
+ for(let i = 0; i < name.length && ascii; i++)ascii = name.charCodeAt(i) < 0x80;
789
+ if (ascii) return name;
790
+ try {
791
+ return UTF8_STRICT.decode(latin1Bytes(name));
792
+ } catch (unused) {
793
+ return name;
794
+ }
795
+ }
796
+ /** Display text as name bytes (UTF-8), the inverse of {@link pdfNameText}. */ export function pdfNameFromText(text) {
797
+ return latin1Text(new TextEncoder().encode(text));
798
+ }
799
+ function asArray(obj, resolve) {
800
+ const value = resolve(obj);
801
+ if (!value || value.kind === 'null') return [];
802
+ return value.kind === 'array' ? value.items : [
803
+ value
804
+ ];
805
+ }
806
+ function intParam(dict, key, fallback) {
807
+ const value = dict == null ? void 0 : dict.entries.get(key);
808
+ return (value == null ? void 0 : value.kind) === 'number' && Number.isInteger(value.value) ? value.value : fallback;
809
+ }
810
+ /**
811
+ * zlib inflation capped at `maxOutputLength`. A strict pass first; a stream
812
+ * truncated or missing its Adler-32 (common, and harmless to the data) is
813
+ * retried with a sync flush, and one written without a zlib header as raw
814
+ * deflate. `null` when none of them produces anything.
815
+ */ export function inflatePdf(data, maxOutputLength) {
816
+ const options = {
817
+ maxOutputLength
818
+ };
819
+ const lenient = {
820
+ maxOutputLength,
821
+ finishFlush: constants.Z_SYNC_FLUSH
822
+ };
823
+ const attempts = [
824
+ ()=>inflateSync(data, options),
825
+ ()=>inflateSync(data, lenient),
826
+ ()=>inflateRawSync(data, lenient)
827
+ ];
828
+ for (const attempt of attempts){
829
+ try {
830
+ const out = attempt();
831
+ return new Uint8Array(out.buffer, out.byteOffset, out.byteLength);
832
+ } catch (unused) {
833
+ // The next attempt is more lenient; a size-cap error fails them all.
834
+ }
835
+ }
836
+ return null;
837
+ }
838
+ /**
839
+ * §7.4.4.4: undo a TIFF (2) or PNG (10–15) predictor. PNG rows each carry
840
+ * their own filter-type byte, so the Predictor value past 10 is only a hint.
841
+ * `null` for parameters this cannot honor.
842
+ */ export function unpredict(data, params) {
843
+ const predictor = intParam(params, 'Predictor', 1);
844
+ if (predictor === 1) return data;
845
+ const colors = intParam(params, 'Colors', 1);
846
+ const bpc = intParam(params, 'BitsPerComponent', 8);
847
+ const columns = intParam(params, 'Columns', 1);
848
+ if (colors < 1 || colors > 32 || ![
849
+ 1,
850
+ 2,
851
+ 4,
852
+ 8,
853
+ 16
854
+ ].includes(bpc)) return null;
855
+ if (columns < 1 || columns > 1 << 24) return null;
856
+ const bpp = Math.max(1, Math.ceil(colors * bpc / 8));
857
+ const rowLength = Math.ceil(colors * bpc * columns / 8);
858
+ if (predictor === 2) {
859
+ if (bpc !== 8) return null;
860
+ const out = data.slice();
861
+ for(let row = 0; row < out.length; row += rowLength){
862
+ for(let i = row + bpp; i < Math.min(row + rowLength, out.length); i++){
863
+ var _out_i, _out_;
864
+ out[i] = ((_out_i = out[i]) != null ? _out_i : 0) + ((_out_ = out[i - bpp]) != null ? _out_ : 0) & 0xff;
865
+ }
866
+ }
867
+ return out;
868
+ }
869
+ if (predictor < 10 || predictor > 15) return null;
870
+ const rows = Math.ceil(data.length / (rowLength + 1));
871
+ // Every row writes at most its own input, so the output is never longer
872
+ // than the input — a hostile /Columns cannot make this allocate more.
873
+ const out = new Uint8Array(Math.min(rows * rowLength, data.length));
874
+ let written = 0;
875
+ for(let row = 0; row < rows; row++){
876
+ var _data_src;
877
+ const src = row * (rowLength + 1);
878
+ const type = (_data_src = data[src]) != null ? _data_src : 0;
879
+ const base = row * rowLength;
880
+ const available = Math.min(rowLength, data.length - src - 1);
881
+ for(let i = 0; i < available; i++){
882
+ var _data_, _out_1, _out_2, _out_3;
883
+ const raw = (_data_ = data[src + 1 + i]) != null ? _data_ : 0;
884
+ const left = i >= bpp ? (_out_1 = out[base + i - bpp]) != null ? _out_1 : 0 : 0;
885
+ const up = row > 0 ? (_out_2 = out[base - rowLength + i]) != null ? _out_2 : 0 : 0;
886
+ const upLeft = row > 0 && i >= bpp ? (_out_3 = out[base - rowLength + i - bpp]) != null ? _out_3 : 0 : 0;
887
+ let value;
888
+ switch(type){
889
+ case 1:
890
+ value = raw + left;
891
+ break;
892
+ case 2:
893
+ value = raw + up;
894
+ break;
895
+ case 3:
896
+ value = raw + (left + up >> 1);
897
+ break;
898
+ case 4:
899
+ {
900
+ const p = left + up - upLeft;
901
+ const pa = Math.abs(p - left);
902
+ const pb = Math.abs(p - up);
903
+ const pc = Math.abs(p - upLeft);
904
+ value = raw + (pa <= pb && pa <= pc ? left : pb <= pc ? up : upLeft);
905
+ break;
906
+ }
907
+ default:
908
+ value = raw;
909
+ }
910
+ out[base + i] = value & 0xff;
911
+ }
912
+ written = base + Math.max(0, available);
913
+ }
914
+ return out.subarray(0, written);
915
+ }
916
+ /**
917
+ * A stream's decoded data, or `null` when it uses a filter other than
918
+ * FlateDecode (§7.4.4) or does not decode within `maxOutputLength`.
919
+ */ export function decodePdfStream(stream, resolve, maxOutputLength) {
920
+ const filters = asArray(stream.dict.entries.get('Filter'), resolve);
921
+ const params = asArray(stream.dict.entries.get('DecodeParms'), resolve);
922
+ if (filters.length > 8) return null;
923
+ let data = stream.data;
924
+ for(let i = 0; i < filters.length && data; i++){
925
+ const filter = resolve(filters[i]);
926
+ if ((filter == null ? void 0 : filter.kind) !== 'name') return null;
927
+ if (filter.name !== 'FlateDecode' && filter.name !== 'Fl') return null;
928
+ const param = resolve(params[i]);
929
+ data = inflatePdf(data, maxOutputLength);
930
+ if (data) data = unpredict(data, (param == null ? void 0 : param.kind) === 'dict' ? param : null);
931
+ }
932
+ if (data && data.length > maxOutputLength) return null;
933
+ return data;
934
+ }
935
+
936
+ //# sourceMappingURL=pdf-objects.js.map