pdf-lite 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/EXAMPLES.md +51 -70
  2. package/README.md +1 -1
  3. package/dist/acroform/appearance/index.d.ts +4 -4
  4. package/dist/acroform/appearance/index.js +4 -4
  5. package/dist/acroform/appearance/{PdfAppearanceStream.d.ts → pdf-appearance-stream.d.ts} +9 -3
  6. package/dist/acroform/appearance/{PdfAppearanceStream.js → pdf-appearance-stream.js} +14 -5
  7. package/dist/acroform/appearance/{PdfButtonAppearanceStream.d.ts → pdf-button-appearance-stream.d.ts} +3 -2
  8. package/dist/acroform/appearance/pdf-button-appearance-stream.js +58 -0
  9. package/dist/acroform/appearance/pdf-choice-appearance-stream.d.ts +22 -0
  10. package/dist/acroform/appearance/pdf-choice-appearance-stream.js +75 -0
  11. package/dist/acroform/appearance/pdf-graphics.d.ts +51 -0
  12. package/dist/acroform/appearance/pdf-graphics.js +239 -0
  13. package/dist/acroform/appearance/{PdfTextAppearanceStream.d.ts → pdf-text-appearance-stream.d.ts} +7 -2
  14. package/dist/acroform/appearance/pdf-text-appearance-stream.js +104 -0
  15. package/dist/acroform/fields/index.d.ts +7 -7
  16. package/dist/acroform/fields/index.js +7 -7
  17. package/dist/acroform/fields/pdf-button-form-field.d.ts +14 -0
  18. package/dist/acroform/fields/pdf-button-form-field.js +70 -0
  19. package/dist/acroform/fields/pdf-choice-form-field.d.ts +19 -0
  20. package/dist/acroform/fields/pdf-choice-form-field.js +112 -0
  21. package/dist/acroform/fields/{PdfFormFieldFlags.d.ts → pdf-form-field-flags.d.ts} +5 -6
  22. package/dist/acroform/fields/{PdfFormFieldFlags.js → pdf-form-field-flags.js} +12 -18
  23. package/dist/acroform/fields/{PdfFormField.d.ts → pdf-form-field.d.ts} +37 -38
  24. package/dist/acroform/fields/pdf-form-field.js +519 -0
  25. package/dist/acroform/fields/{PdfSignatureFormField.d.ts → pdf-signature-form-field.d.ts} +1 -1
  26. package/dist/acroform/fields/{PdfSignatureFormField.js → pdf-signature-form-field.js} +1 -1
  27. package/dist/acroform/fields/{PdfTextFormField.d.ts → pdf-text-form-field.d.ts} +1 -1
  28. package/dist/acroform/fields/{PdfTextFormField.js → pdf-text-form-field.js} +11 -13
  29. package/dist/acroform/fields/types.d.ts +6 -1
  30. package/dist/acroform/index.d.ts +1 -3
  31. package/dist/acroform/index.js +1 -3
  32. package/dist/acroform/pdf-acro-form.d.ts +45 -0
  33. package/dist/acroform/pdf-acro-form.js +203 -0
  34. package/dist/acroform/xfa/index.d.ts +3 -3
  35. package/dist/acroform/xfa/index.js +2 -2
  36. package/dist/acroform/xfa/{PdfXfaData.d.ts → pdf-xfa-data.d.ts} +4 -3
  37. package/dist/acroform/xfa/{PdfXfaData.js → pdf-xfa-data.js} +16 -12
  38. package/dist/acroform/xfa/pdf-xfa-form.d.ts +16 -0
  39. package/dist/acroform/xfa/pdf-xfa-form.js +34 -0
  40. package/dist/annotations/index.d.ts +3 -4
  41. package/dist/annotations/index.js +3 -4
  42. package/dist/annotations/{PdfAnnotationFlags.d.ts → pdf-annotation-flags.d.ts} +3 -4
  43. package/dist/annotations/{PdfAnnotationFlags.js → pdf-annotation-flags.js} +5 -6
  44. package/dist/annotations/{PdfAnnotation.d.ts → pdf-annotation.d.ts} +31 -5
  45. package/dist/annotations/{PdfAnnotation.js → pdf-annotation.js} +31 -19
  46. package/dist/annotations/pdf-default-resources.d.ts +11 -0
  47. package/dist/annotations/pdf-default-resources.js +3 -0
  48. package/dist/annotations/{PdfWidgetAnnotation.d.ts → pdf-widget-annotation.d.ts} +1 -1
  49. package/dist/annotations/{PdfWidgetAnnotation.js → pdf-widget-annotation.js} +1 -1
  50. package/dist/core/decoder.js +1 -1
  51. package/dist/core/objects/pdf-array.d.ts +8 -1
  52. package/dist/core/objects/pdf-array.js +31 -0
  53. package/dist/core/objects/pdf-dictionary.d.ts +2 -0
  54. package/dist/core/objects/pdf-dictionary.js +14 -7
  55. package/dist/core/objects/pdf-hexadecimal.d.ts +1 -0
  56. package/dist/core/objects/pdf-hexadecimal.js +3 -3
  57. package/dist/core/objects/pdf-indirect-object.d.ts +18 -9
  58. package/dist/core/objects/pdf-indirect-object.js +75 -16
  59. package/dist/core/objects/pdf-number.d.ts +1 -0
  60. package/dist/core/objects/pdf-number.js +5 -4
  61. package/dist/core/objects/pdf-object-reference.d.ts +8 -1
  62. package/dist/core/objects/pdf-object-reference.js +14 -0
  63. package/dist/core/objects/pdf-object.d.ts +14 -0
  64. package/dist/core/objects/pdf-object.js +36 -0
  65. package/dist/core/objects/pdf-start-xref.d.ts +1 -0
  66. package/dist/core/objects/pdf-start-xref.js +4 -0
  67. package/dist/core/objects/pdf-stream.d.ts +44 -7
  68. package/dist/core/objects/pdf-stream.js +284 -26
  69. package/dist/core/objects/pdf-string.d.ts +1 -0
  70. package/dist/core/objects/pdf-string.js +3 -6
  71. package/dist/core/objects/pdf-trailer.d.ts +1 -0
  72. package/dist/core/objects/pdf-trailer.js +6 -3
  73. package/dist/core/objects/pdf-xref-table.js +1 -1
  74. package/dist/core/parser/incremental-parser.d.ts +0 -13
  75. package/dist/core/parser/incremental-parser.js +1 -18
  76. package/dist/core/ref.d.ts +3 -1
  77. package/dist/core/ref.js +8 -5
  78. package/dist/core/streams/object-stream.d.ts +1 -1
  79. package/dist/core/streams/object-stream.js +1 -1
  80. package/dist/core/tokens/token.d.ts +2 -1
  81. package/dist/core/tokens/token.js +3 -0
  82. package/dist/errors.d.ts +22 -0
  83. package/dist/errors.js +24 -0
  84. package/dist/fonts/index.d.ts +0 -1
  85. package/dist/fonts/index.js +0 -1
  86. package/dist/fonts/pdf-font.d.ts +94 -32
  87. package/dist/fonts/pdf-font.js +301 -83
  88. package/dist/index.d.ts +1 -0
  89. package/dist/index.js +1 -0
  90. package/dist/pdf/index.d.ts +2 -1
  91. package/dist/pdf/index.js +2 -1
  92. package/dist/pdf/pdf-document.d.ts +61 -36
  93. package/dist/pdf/pdf-document.js +315 -117
  94. package/dist/pdf/pdf-page.d.ts +50 -0
  95. package/dist/pdf/pdf-page.js +144 -0
  96. package/dist/pdf/pdf-pages.d.ts +28 -0
  97. package/dist/pdf/pdf-pages.js +94 -0
  98. package/dist/pdf/pdf-reader.d.ts +5 -1
  99. package/dist/pdf/pdf-reader.js +36 -2
  100. package/dist/pdf/pdf-revision.d.ts +3 -3
  101. package/dist/pdf/pdf-revision.js +7 -7
  102. package/dist/pdf/pdf-xref-lookup.js +34 -14
  103. package/dist/signing/document-security-store.d.ts +14 -17
  104. package/dist/signing/document-security-store.js +19 -34
  105. package/dist/signing/signer.d.ts +23 -8
  106. package/dist/signing/signer.js +51 -17
  107. package/dist/utils/encodePdfText.d.ts +17 -0
  108. package/dist/utils/encodePdfText.js +61 -0
  109. package/dist/utils/index.d.ts +1 -2
  110. package/dist/utils/index.js +1 -2
  111. package/dist/utils/needsCentralWhitespace.d.ts +10 -0
  112. package/dist/utils/needsCentralWhitespace.js +34 -0
  113. package/package.json +3 -3
  114. package/dist/acroform/PdfAcroForm.d.ts +0 -63
  115. package/dist/acroform/PdfAcroForm.js +0 -279
  116. package/dist/acroform/PdfFontEncodingCache.d.ts +0 -16
  117. package/dist/acroform/PdfFontEncodingCache.js +0 -75
  118. package/dist/acroform/acroform.d.ts +0 -9
  119. package/dist/acroform/acroform.js +0 -7
  120. package/dist/acroform/appearance/PdfButtonAppearanceStream.js +0 -54
  121. package/dist/acroform/appearance/PdfChoiceAppearanceStream.d.ts +0 -15
  122. package/dist/acroform/appearance/PdfChoiceAppearanceStream.js +0 -48
  123. package/dist/acroform/appearance/PdfTextAppearanceStream.js +0 -75
  124. package/dist/acroform/fields/PdfButtonFormField.d.ts +0 -9
  125. package/dist/acroform/fields/PdfButtonFormField.js +0 -35
  126. package/dist/acroform/fields/PdfChoiceFormField.d.ts +0 -9
  127. package/dist/acroform/fields/PdfChoiceFormField.js +0 -46
  128. package/dist/acroform/fields/PdfFormField.js +0 -499
  129. package/dist/acroform/manager.d.ts +0 -33
  130. package/dist/acroform/manager.js +0 -51
  131. package/dist/acroform/xfa/PdfXfaForm.d.ts +0 -12
  132. package/dist/acroform/xfa/PdfXfaForm.js +0 -64
  133. package/dist/annotations/PdfAnnotationWriter.d.ts +0 -20
  134. package/dist/annotations/PdfAnnotationWriter.js +0 -76
  135. package/dist/fonts/font-manager.d.ts +0 -127
  136. package/dist/fonts/font-manager.js +0 -378
  137. package/dist/pdf/errors.d.ts +0 -6
  138. package/dist/pdf/errors.js +0 -6
  139. package/dist/utils/predictors.d.ts +0 -113
  140. package/dist/utils/predictors.js +0 -279
  141. /package/dist/acroform/fields/{PdfDefaultAppearance.d.ts → pdf-default-appearance.d.ts} +0 -0
  142. /package/dist/acroform/fields/{PdfDefaultAppearance.js → pdf-default-appearance.js} +0 -0
  143. /package/dist/utils/{IterableReadableStream.d.ts → iterable-readable-stream.d.ts} +0 -0
  144. /package/dist/utils/{IterableReadableStream.js → iterable-readable-stream.js} +0 -0
@@ -5,8 +5,9 @@ import { PdfIndirectObject } from '../core/objects/pdf-indirect-object.js';
5
5
  import { PdfComment } from '../core/objects/pdf-comment.js';
6
6
  import { PdfWhitespaceToken } from '../core/tokens/whitespace-token.js';
7
7
  import { PdfObjStream, PdfStream, PdfXRefStreamCompressedEntry, } from '../core/objects/pdf-stream.js';
8
+ import { PdfArray } from '../core/objects/pdf-array.js';
8
9
  import { PdfDictionary } from '../core/objects/pdf-dictionary.js';
9
- import { PdfObjectReference } from '../core/objects/pdf-object-reference.js';
10
+ import { PdfObjectReference, } from '../core/objects/pdf-object-reference.js';
10
11
  import { PdfTokenSerializer } from '../core/serializer.js';
11
12
  import { PdfRevision } from './pdf-revision.js';
12
13
  import { PdfV5SecurityHandler } from '../security/handlers/v5.js';
@@ -14,12 +15,12 @@ import { PdfByteOffsetToken } from '../core/tokens/byte-offset-token.js';
14
15
  import { PdfNumberToken } from '../core/tokens/number-token.js';
15
16
  import { PdfXRefTableEntryToken } from '../core/tokens/xref-table-entry-token.js';
16
17
  import { PdfStartXRef } from '../core/objects/pdf-start-xref.js';
17
- import { FoundCompressedObjectError } from './errors.js';
18
+ import { FoundCompressedObjectError } from '../errors.js';
18
19
  import { PdfReader } from './pdf-reader.js';
19
20
  import { PdfSigner } from '../signing/signer.js';
20
- import { PdfAcroFormManager } from '../acroform/manager.js';
21
- import { PdfFontManager } from '../fonts/font-manager.js';
22
21
  import { concatUint8Arrays } from '../utils/concatUint8Arrays.js';
22
+ import { PdfAcroForm } from '../acroform/pdf-acro-form.js';
23
+ import { PdfPages } from './pdf-pages.js';
23
24
  /**
24
25
  * Represents a PDF document with support for reading, writing, and modifying PDF files.
25
26
  * Handles document structure, revisions, encryption, and digital signatures.
@@ -44,11 +45,14 @@ export class PdfDocument extends PdfObject {
44
45
  signer;
45
46
  /** Security handler for encryption/decryption operations */
46
47
  securityHandler;
47
- /** */
48
- acroForm;
49
- fonts;
48
+ /** Whether the document is currently in incremental mode (appending changes as a new revision) */
49
+ incremental = false;
50
+ originalSecurityHandler;
50
51
  hasEncryptionDictionary = false;
51
- toBeCommitted = [];
52
+ _resolvedCache = new Map();
53
+ _committing = false;
54
+ _finalized = false;
55
+ _signed = false;
52
56
  /**
53
57
  * Creates a new PDF document instance.
54
58
  *
@@ -75,13 +79,48 @@ export class PdfDocument extends PdfObject {
75
79
  if (options?.ownerPassword) {
76
80
  this.setOwnerPassword(options.ownerPassword);
77
81
  }
78
- this.signer = options?.signer ?? new PdfSigner();
82
+ this.signer = options?.signer ?? new PdfSigner({ document: this });
79
83
  this.linkRevisions();
84
+ this.wireResolvers(...this.objects.filter((x) => x instanceof PdfIndirectObject), ...this.revisions.map((rev) => rev.xref.trailerDict));
80
85
  this.calculateOffsets();
81
- this.securityHandler =
82
- options?.securityHandler ?? this.getSecurityHandler();
83
- this.acroForm = new PdfAcroFormManager(this);
84
- this.fonts = new PdfFontManager(this);
86
+ this.originalSecurityHandler = options?.securityHandler;
87
+ this.resetSecurityHandler();
88
+ }
89
+ resolve(objectNumber, generationNumber) {
90
+ const cacheKey = `${objectNumber} ${generationNumber}`;
91
+ const cached = this._resolvedCache.get(cacheKey);
92
+ if (cached)
93
+ return cached;
94
+ const found = this.readObject({ objectNumber, generationNumber });
95
+ if (!found) {
96
+ throw new Error(`Object ${objectNumber} ${generationNumber} not found`);
97
+ }
98
+ this.wireResolvers(found);
99
+ this._resolvedCache.set(cacheKey, found);
100
+ return found;
101
+ }
102
+ get acroform() {
103
+ const root = this.root;
104
+ const acroFormEntry = root.content.get('AcroForm');
105
+ if (!acroFormEntry)
106
+ return null;
107
+ const acroFormRef = acroFormEntry.as(PdfObjectReference)?.resolve();
108
+ if (!acroFormRef)
109
+ return null;
110
+ return acroFormRef.becomes(PdfAcroForm);
111
+ }
112
+ get pages() {
113
+ const root = this.root;
114
+ const pagesEntry = root.content.get('Pages');
115
+ if (pagesEntry) {
116
+ const pagesRef = pagesEntry.as(PdfObjectReference)?.resolve();
117
+ if (pagesRef)
118
+ return pagesRef.becomes(PdfPages);
119
+ }
120
+ const pages = new PdfPages();
121
+ this.add(pages);
122
+ root.content.set('Pages', pages.reference);
123
+ return pages;
85
124
  }
86
125
  get header() {
87
126
  return this.revisions[0].header;
@@ -132,6 +171,9 @@ export class PdfDocument extends PdfObject {
132
171
  }
133
172
  return this;
134
173
  }
174
+ hasObjectInLatestRevision(obj) {
175
+ return this.latestRevision.objects.includes(obj);
176
+ }
135
177
  /**
136
178
  * Adds objects to the document's latest revision.
137
179
  * Automatically starts a new revision if the current one is locked.
@@ -143,12 +185,21 @@ export class PdfDocument extends PdfObject {
143
185
  this.startNewRevision();
144
186
  }
145
187
  for (const obj of objects) {
146
- if (obj.isImmutable()) {
147
- throw new Error('Risky adding a immutable obj');
188
+ this.wireResolvers(obj);
189
+ if (this.hasObjectInLatestRevision(obj)) {
190
+ continue;
148
191
  }
149
- this.toBeCommitted.push(obj);
150
192
  this.latestRevision.addObject(obj);
151
193
  }
194
+ // Auto-add any referenced-but-missing objects
195
+ const missing = this.collectMissingReferences(...objects);
196
+ if (missing.length > 0) {
197
+ this.add(...missing);
198
+ }
199
+ this.update();
200
+ for (const obj of objects) {
201
+ obj.setModified(false);
202
+ }
152
203
  }
153
204
  /**
154
205
  * Packs non-stream indirect objects into compressed ObjStm containers.
@@ -290,6 +341,10 @@ export class PdfDocument extends PdfObject {
290
341
  }
291
342
  return metadataRef;
292
343
  }
344
+ resetSecurityHandler() {
345
+ this.securityHandler =
346
+ this.originalSecurityHandler ?? this.getSecurityHandler();
347
+ }
293
348
  getSecurityHandler() {
294
349
  const encryptionDictionaryRef = this.trailerDict
295
350
  .get('Encrypt')
@@ -383,11 +438,39 @@ export class PdfDocument extends PdfObject {
383
438
  }
384
439
  return true;
385
440
  }
441
+ commitIncrementalUpdates() {
442
+ if (!this.incremental || this._committing) {
443
+ return;
444
+ }
445
+ this._committing = true;
446
+ try {
447
+ for (const revision of this.revisions) {
448
+ if (!revision.locked)
449
+ continue;
450
+ for (const obj of revision.objects) {
451
+ if (!(obj instanceof PdfIndirectObject)) {
452
+ continue;
453
+ }
454
+ if (obj.isModified()) {
455
+ const adding = obj.clone();
456
+ this.add(adding);
457
+ adding.setModified(false);
458
+ obj.setModified(false);
459
+ }
460
+ }
461
+ }
462
+ }
463
+ finally {
464
+ this._committing = false;
465
+ }
466
+ }
386
467
  /**
387
- * Decrypts all encrypted objects in the document.
388
- * Removes the security handler and encryption dictionary after decryption.
468
+ * Decrypts all encrypted object data in-place without removing
469
+ * the encryption infrastructure. Useful in incremental mode where
470
+ * the original (encrypted) bytes are preserved via cached tokens
471
+ * but the live object data needs to be readable.
389
472
  */
390
- async decrypt() {
473
+ async decryptObjects() {
391
474
  if (!this.securityHandler) {
392
475
  return;
393
476
  }
@@ -400,19 +483,55 @@ export class PdfDocument extends PdfObject {
400
483
  }
401
484
  await this.securityHandler.decryptObject(object);
402
485
  }
403
- this.securityHandler = undefined;
486
+ }
487
+ /**
488
+ * Re-encrypts all objects and updates the document structure.
489
+ * No-op if the document has no security handler (unencrypted document).
490
+ */
491
+ async finalize() {
492
+ if (this._finalized) {
493
+ throw new Error('Document has already been finalized');
494
+ }
495
+ this._finalized = true;
496
+ this.update();
497
+ if (this.securityHandler) {
498
+ await this.encrypt();
499
+ }
500
+ this.update();
501
+ await this.sign();
502
+ }
503
+ /**
504
+ * Decrypts all encrypted objects in the document.
505
+ * Removes the security handler and encryption dictionary after decryption.
506
+ */
507
+ async decrypt() {
508
+ if (!this.securityHandler) {
509
+ return;
510
+ }
511
+ await this.decryptObjects();
404
512
  this.hasEncryptionDictionary = false;
513
+ this.securityHandler = undefined;
405
514
  const encryptionDict = this.encryptionDictionary;
515
+ this.trailerDict.delete('Encrypt');
406
516
  if (encryptionDict) {
407
- await this.deleteObject(encryptionDict);
517
+ this.deleteObject(encryptionDict);
408
518
  }
409
- await this.update();
519
+ this.update();
410
520
  }
411
521
  /**
412
- * Encrypts all objects in the document using the security handler.
413
- * Creates and adds an encryption dictionary to all revisions.
522
+ * Encrypts all encryptable objects using the security handler.
523
+ * Re-uses the existing encryption dictionary or creates one if needed,
524
+ * propagating it to all revisions.
414
525
  */
415
526
  async encrypt() {
527
+ if (!this.securityHandler) {
528
+ return;
529
+ }
530
+ const addingEncryption = !this.hasEncryptionDictionary;
531
+ const wasIncremental = this.incremental;
532
+ if (addingEncryption) {
533
+ this.setIncremental(false);
534
+ }
416
535
  this.initSecurityHandler({});
417
536
  await this.securityHandler.write();
418
537
  for (const object of this.objects) {
@@ -423,20 +542,24 @@ export class PdfDocument extends PdfObject {
423
542
  continue;
424
543
  }
425
544
  await this.securityHandler.encryptObject(object);
545
+ object.setModified(false);
426
546
  }
427
- const encryptionDictObject = new PdfIndirectObject({
428
- content: this.securityHandler.dict,
429
- encryptable: false,
430
- });
431
- for (const revision of this.revisions) {
432
- revision.xref.trailerDict.set('Encrypt', encryptionDictObject.reference);
433
- if (!revision.xref.trailerDict.get('ID')) {
434
- revision.xref.trailerDict.set('ID', this.securityHandler.getDocumentId());
547
+ if (addingEncryption) {
548
+ const encryptionDictObject = new PdfIndirectObject({
549
+ content: this.securityHandler.dict,
550
+ encryptable: false,
551
+ });
552
+ this.add(encryptionDictObject);
553
+ this.hasEncryptionDictionary = true;
554
+ for (const revision of this.revisions) {
555
+ revision.xref.trailerDict.set('Encrypt', encryptionDictObject.reference);
556
+ if (!revision.xref.trailerDict.get('ID')) {
557
+ revision.xref.trailerDict.set('ID', this.securityHandler.getDocumentId());
558
+ }
435
559
  }
560
+ this.setIncremental(wasIncremental);
436
561
  }
437
- await this.commit(encryptionDictObject);
438
- this.hasEncryptionDictionary = true;
439
- await this.update();
562
+ this.update();
440
563
  }
441
564
  /**
442
565
  * Finds a compressed object by its object number within an object stream.
@@ -445,7 +568,7 @@ export class PdfDocument extends PdfObject {
445
568
  * @returns The found indirect object or undefined if not found
446
569
  * @throws Error if the object cannot be found in the expected object stream
447
570
  */
448
- async findCompressedObject(options) {
571
+ findCompressedObject(options) {
449
572
  const xrefEntry = this.xrefLookup.getObject(options.objectNumber);
450
573
  if (!(xrefEntry instanceof PdfXRefStreamCompressedEntry)) {
451
574
  throw new Error('Cannot find object inside object stream via PdfDocument.findObject');
@@ -456,10 +579,6 @@ export class PdfDocument extends PdfObject {
456
579
  if (!objectStreamIndirect) {
457
580
  throw new Error(`Cannot find object stream ${xrefEntry.objectStreamNumber.value} for object ${options.objectNumber}`);
458
581
  }
459
- if (this.securityHandler &&
460
- this.isObjectEncryptable(objectStreamIndirect)) {
461
- await this.securityHandler.decryptObject(objectStreamIndirect);
462
- }
463
582
  const objectStream = objectStreamIndirect.content
464
583
  .as(PdfStream)
465
584
  .parseAs(PdfObjStream);
@@ -502,14 +621,14 @@ export class PdfDocument extends PdfObject {
502
621
  * @param options.allowUnindexed - If true, searches unindexed objects as fallback
503
622
  * @returns A cloned and decrypted copy of the object, or undefined if not found
504
623
  */
505
- async readObject(options) {
624
+ readObject(options) {
506
625
  let foundObject;
507
626
  try {
508
627
  foundObject = this.findUncompressedObject(options);
509
628
  }
510
629
  catch (e) {
511
630
  if (e instanceof FoundCompressedObjectError) {
512
- foundObject = await this.findCompressedObject(options);
631
+ foundObject = this.findCompressedObject(options);
513
632
  }
514
633
  else {
515
634
  throw e;
@@ -524,24 +643,32 @@ export class PdfDocument extends PdfObject {
524
643
  if (!foundObject) {
525
644
  return undefined;
526
645
  }
527
- if (this.securityHandler && this.isObjectEncryptable(foundObject)) {
646
+ if (options.cloned) {
528
647
  foundObject = foundObject.clone();
529
- await this.securityHandler.decryptObject(foundObject);
530
648
  }
531
649
  return foundObject;
532
650
  }
651
+ /**
652
+ * Finds the revision that contains a given PDF object.
653
+ * Useful for determining the origin of an object across multiple revisions.
654
+ * @param obj - The PDF object to find the revision for
655
+ * @returns The PdfRevision that contains the object, or undefined if not found in any revision
656
+ */
657
+ findRevisionForObject(obj) {
658
+ return this.revisions.find((rev) => rev.objects.includes(obj));
659
+ }
533
660
  /**
534
661
  * Deletes an object from all revisions in the document.
535
662
  *
536
663
  * @param obj - The PDF object to delete
537
664
  */
538
- async deleteObject(obj) {
665
+ deleteObject(obj) {
539
666
  if (!obj)
540
667
  return;
541
668
  for (const revision of this.revisions) {
542
669
  revision.deleteObject(obj);
543
670
  }
544
- await this.update();
671
+ this.update();
545
672
  }
546
673
  /**
547
674
  * Sets the PDF version for the document.
@@ -565,10 +692,10 @@ export class PdfDocument extends PdfObject {
565
692
  if (value === this.isIncremental()) {
566
693
  return;
567
694
  }
695
+ this.incremental = value;
568
696
  for (const revision of this.revisions) {
569
697
  revision.locked = value;
570
698
  }
571
- this.startNewRevision();
572
699
  }
573
700
  /**
574
701
  * Checks if the document is in incremental mode.
@@ -576,53 +703,7 @@ export class PdfDocument extends PdfObject {
576
703
  * @returns True if all revisions are locked for incremental updates
577
704
  */
578
705
  isIncremental() {
579
- return this.latestRevision.locked;
580
- }
581
- /**
582
- * Commits pending objects to the document.
583
- * Adds objects, applies encryption if configured, and updates the document structure.
584
- *
585
- * @param newObjects - Additional objects to add before committing
586
- */
587
- async commit(...newObjects) {
588
- this.add(...newObjects);
589
- const queue = this.toBeCommitted.slice();
590
- this.toBeCommitted = [];
591
- for (const newObject of queue) {
592
- if (this.securityHandler &&
593
- newObject instanceof PdfIndirectObject &&
594
- this.isObjectEncryptable(newObject)) {
595
- await this.securityHandler.write();
596
- if (!this.hasEncryptionDictionary) {
597
- const encryptionDictObject = new PdfIndirectObject({
598
- content: this.securityHandler.dict,
599
- encryptable: false,
600
- });
601
- this.latestRevision.addObject(encryptionDictObject);
602
- this.trailerDict.set('Encrypt', encryptionDictObject.reference);
603
- this.hasEncryptionDictionary = true;
604
- }
605
- await this.securityHandler.encryptObject(newObject);
606
- }
607
- }
608
- await this.update();
609
- }
610
- /**
611
- * Sets the Document Security Store (DSS) for the document.
612
- * Used for long-term validation of digital signatures.
613
- *
614
- * @param dss - The Document Security Store object to set
615
- * @throws Error if the document has no root dictionary
616
- */
617
- async setDocumentSecurityStore(dss) {
618
- let rootDictionary = this.root?.content;
619
- if (!rootDictionary) {
620
- throw new Error('Cannot set DSS - document has no root dictionary');
621
- }
622
- rootDictionary.set('DSS', dss.reference);
623
- if (!this.hasObject(dss)) {
624
- await this.commit(dss);
625
- }
706
+ return this.incremental;
626
707
  }
627
708
  /**
628
709
  * Returns tokens paired with their source objects.
@@ -644,12 +725,93 @@ export class PdfDocument extends PdfObject {
644
725
  tokenize() {
645
726
  return this.tokensWithObjects().map(({ token }) => token);
646
727
  }
728
+ wireResolvers(...objects) {
729
+ const seen = new Set();
730
+ const walk = (obj) => {
731
+ if (seen.has(obj))
732
+ return;
733
+ seen.add(obj);
734
+ if (obj instanceof PdfObjectReference &&
735
+ !(obj instanceof PdfIndirectObject)) {
736
+ obj.resolver = this;
737
+ }
738
+ else if (obj instanceof PdfStream) {
739
+ walk(obj.header);
740
+ }
741
+ else if (obj instanceof PdfDictionary) {
742
+ for (const [, value] of obj.entries()) {
743
+ if (value)
744
+ walk(value);
745
+ }
746
+ }
747
+ else if (obj instanceof PdfArray) {
748
+ for (const item of obj.items) {
749
+ walk(item);
750
+ }
751
+ }
752
+ else if (obj instanceof PdfIndirectObject) {
753
+ walk(obj.content);
754
+ }
755
+ };
756
+ for (const obj of objects) {
757
+ walk(obj);
758
+ }
759
+ }
760
+ collectMissingReferences(...objects) {
761
+ const seen = new Set();
762
+ const missing = [];
763
+ const queue = [...objects];
764
+ while (queue.length > 0) {
765
+ const obj = queue.pop();
766
+ if (seen.has(obj))
767
+ continue;
768
+ seen.add(obj);
769
+ if (obj instanceof PdfObjectReference &&
770
+ !(obj instanceof PdfIndirectObject)) {
771
+ if (obj.resolver) {
772
+ try {
773
+ const resolved = obj.resolve();
774
+ if (resolved instanceof PdfIndirectObject &&
775
+ !resolved.inPdf()) {
776
+ missing.push(resolved);
777
+ queue.push(resolved);
778
+ }
779
+ }
780
+ catch {
781
+ // Skip unresolvable references
782
+ }
783
+ }
784
+ }
785
+ else if (obj instanceof PdfStream) {
786
+ queue.push(obj.header);
787
+ }
788
+ else if (obj instanceof PdfDictionary) {
789
+ for (const [, value] of obj.entries()) {
790
+ if (value)
791
+ queue.push(value);
792
+ }
793
+ }
794
+ else if (obj instanceof PdfArray) {
795
+ for (const item of obj.items)
796
+ queue.push(item);
797
+ }
798
+ else if (obj instanceof PdfIndirectObject) {
799
+ queue.push(obj.content);
800
+ }
801
+ }
802
+ return missing;
803
+ }
647
804
  linkRevisions() {
648
805
  const xrefLookups = this.revisions.map((rev) => rev.xref);
649
806
  const indirectObjects = this.objects.filter((x) => x instanceof PdfIndirectObject);
650
807
  for (const revision of this.revisions) {
651
808
  revision.xref.linkPrev(xrefLookups);
652
- revision.xref.linkIndirectObjects(indirectObjects);
809
+ // Walk the entire prev chain to link all xref entries
810
+ let xref = revision.xref;
811
+ while (xref) {
812
+ xref.linkIndirectObjects(indirectObjects);
813
+ xref = xref.prev;
814
+ }
653
815
  }
654
816
  }
655
817
  linkOffsets() {
@@ -696,7 +858,7 @@ export class PdfDocument extends PdfObject {
696
858
  }
697
859
  updateRevisions() {
698
860
  let modified = false;
699
- this.revisions.forEach((rev, i) => {
861
+ this.revisions.forEach((rev) => {
700
862
  if (rev.isModified()) {
701
863
  modified = true;
702
864
  }
@@ -704,11 +866,62 @@ export class PdfDocument extends PdfObject {
704
866
  rev.update();
705
867
  }
706
868
  });
869
+ // Always rebuild xref binary data to reflect recalculated offsets
870
+ // This walks the entire prev chain for each revision
871
+ for (const rev of this.revisions) {
872
+ let xref = rev.xref;
873
+ while (xref) {
874
+ xref.update();
875
+ xref = xref.prev;
876
+ }
877
+ }
707
878
  }
708
- async update() {
879
+ /**
880
+ * Performs a full update cycle to ensure all revisions are consistent and offsets are correct.
881
+ */
882
+ update() {
883
+ this.commitIncrementalUpdates();
884
+ this.flushResolvedCache();
885
+ this.registerNewReferences();
886
+ this.calculateOffsets();
887
+ this.updateRevisions();
888
+ // Second pass: xref binary may have changed size (e.g. FlateDecode removed
889
+ // from xref stream), shifting objects that follow it. Recalculate so entry
890
+ // byteOffset refs hold the new positions, then rebuild the xref binary once
891
+ // more so the baked bytes match those positions.
709
892
  this.calculateOffsets();
710
893
  this.updateRevisions();
711
- await this.signer?.sign(this);
894
+ // Third pass: confirm positions are stable (xref binary size should not
895
+ // change again because W widths and entry count are the same).
896
+ this.calculateOffsets();
897
+ }
898
+ /**
899
+ * Walks all objects in the document and registers any newly created
900
+ * PdfIndirectObjects that are referenced but not yet part of the document
901
+ * (e.g. appearance streams created by generateAppearance).
902
+ */
903
+ registerNewReferences() {
904
+ const missing = this.collectMissingReferences(...this.latestRevision.objects);
905
+ if (missing.length > 0) {
906
+ this.add(...missing);
907
+ }
908
+ }
909
+ flushResolvedCache() {
910
+ for (const [, obj] of this._resolvedCache) {
911
+ if (obj.isModified() && !this.objects.includes(obj)) {
912
+ this.add(obj);
913
+ }
914
+ }
915
+ }
916
+ async sign() {
917
+ if (this._signed) {
918
+ throw new Error('Document has already been signed');
919
+ }
920
+ if (!this.signer) {
921
+ return;
922
+ }
923
+ await this.signer.sign();
924
+ this._signed = true;
712
925
  }
713
926
  /**
714
927
  * Serializes the document to a byte array.
@@ -716,24 +929,9 @@ export class PdfDocument extends PdfObject {
716
929
  * @returns The PDF document as a Uint8Array
717
930
  */
718
931
  toBytes() {
719
- this.calculateOffsets();
720
- this.updateRevisions();
932
+ this.update();
721
933
  return concatUint8Arrays(...this.revisions.map((x) => x.toBytes()));
722
934
  }
723
- /**
724
- * Serializes the document to a Base64-encoded string.
725
- *
726
- * @returns A promise that resolves to the PDF document as a Base64 string
727
- */
728
- toBase64() {
729
- const bytes = this.toBytes();
730
- let binary = '';
731
- const len = bytes.byteLength;
732
- for (let i = 0; i < len; i++) {
733
- binary += String.fromCharCode(bytes[i]);
734
- }
735
- return btoa(binary);
736
- }
737
935
  /**
738
936
  * Creates a deep copy of the document.
739
937
  *
@@ -753,8 +951,8 @@ export class PdfDocument extends PdfObject {
753
951
  * @param input - Async or sync iterable of byte arrays
754
952
  * @returns A promise that resolves to the parsed PdfDocument
755
953
  */
756
- static fromBytes(input) {
757
- return PdfReader.fromBytes(input);
954
+ static fromBytes(input, options) {
955
+ return PdfReader.fromBytes(input, options);
758
956
  }
759
957
  isModified() {
760
958
  return (super.isModified() || this.revisions.some((rev) => rev.isModified()));
@@ -765,6 +963,6 @@ export class PdfDocument extends PdfObject {
765
963
  * @returns A promise that resolves to the verification result
766
964
  */
767
965
  async verifySignatures() {
768
- return await this.signer.verify(this);
966
+ return await this.signer.verify();
769
967
  }
770
968
  }
@@ -0,0 +1,50 @@
1
+ import { PdfDictionary } from '../core/objects/pdf-dictionary.js';
2
+ import { PdfArray } from '../core/objects/pdf-array.js';
3
+ import { PdfObjectReference } from '../core/objects/pdf-object-reference.js';
4
+ import { PdfIndirectObject } from '../core/objects/pdf-indirect-object.js';
5
+ import { PdfNumber } from '../core/objects/pdf-number.js';
6
+ import { PdfName } from '../core/objects/pdf-name.js';
7
+ import { PdfPages } from './pdf-pages.js';
8
+ type PdfPageDictionary = PdfDictionary<{
9
+ Type: PdfName<'Page'>;
10
+ Parent: PdfObjectReference;
11
+ MediaBox: PdfArray<PdfNumber>;
12
+ CropBox?: PdfArray<PdfNumber>;
13
+ BleedBox?: PdfArray<PdfNumber>;
14
+ TrimBox?: PdfArray<PdfNumber>;
15
+ ArtBox?: PdfArray<PdfNumber>;
16
+ Rotate?: PdfNumber;
17
+ Contents?: PdfObjectReference | PdfArray<PdfObjectReference>;
18
+ Resources?: PdfDictionary | PdfObjectReference;
19
+ Annots?: PdfArray<PdfObjectReference> | PdfObjectReference;
20
+ }>;
21
+ export declare class PdfPage extends PdfIndirectObject<PdfPageDictionary> {
22
+ constructor(options?: PdfIndirectObject);
23
+ private getBox;
24
+ private setBox;
25
+ get mediaBox(): [number, number, number, number];
26
+ set mediaBox(value: [number, number, number, number]);
27
+ get cropBox(): [number, number, number, number] | null;
28
+ set cropBox(value: [number, number, number, number] | null);
29
+ get bleedBox(): [number, number, number, number] | null;
30
+ set bleedBox(value: [number, number, number, number] | null);
31
+ get trimBox(): [number, number, number, number] | null;
32
+ set trimBox(value: [number, number, number, number] | null);
33
+ get artBox(): [number, number, number, number] | null;
34
+ set artBox(value: [number, number, number, number] | null);
35
+ get width(): number;
36
+ get height(): number;
37
+ get rotate(): number;
38
+ set rotate(value: number);
39
+ get contents(): PdfObjectReference | PdfArray<PdfObjectReference> | null;
40
+ set contents(value: PdfObjectReference | PdfArray<PdfObjectReference> | null);
41
+ get resources(): PdfDictionary | null;
42
+ set resources(value: PdfDictionary | PdfObjectReference | null);
43
+ get annotations(): PdfArray<PdfObjectReference>;
44
+ set annotations(value: PdfArray<PdfObjectReference>);
45
+ get parentRef(): PdfObjectReference;
46
+ set parentRef(value: PdfObjectReference);
47
+ get parent(): PdfPages;
48
+ set parent(value: PdfPages);
49
+ }
50
+ export {};