pdf-lite 1.5.0 → 1.6.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/EXAMPLES.md +51 -70
- package/README.md +1 -1
- package/dist/acroform/appearance/pdf-button-appearance-stream.d.ts +1 -1
- package/dist/acroform/appearance/pdf-button-appearance-stream.js +6 -2
- package/dist/acroform/fields/pdf-button-form-field.d.ts +0 -9
- package/dist/acroform/fields/pdf-button-form-field.js +7 -39
- package/dist/acroform/fields/pdf-choice-form-field.d.ts +2 -1
- package/dist/acroform/fields/pdf-choice-form-field.js +19 -38
- package/dist/acroform/fields/pdf-default-appearance.js +1 -1
- package/dist/acroform/fields/pdf-form-field.d.ts +27 -32
- package/dist/acroform/fields/pdf-form-field.js +180 -94
- package/dist/acroform/fields/pdf-text-form-field.js +6 -33
- package/dist/acroform/fields/types.d.ts +1 -1
- package/dist/acroform/index.d.ts +0 -2
- package/dist/acroform/index.js +0 -2
- package/dist/acroform/pdf-acro-form.d.ts +12 -36
- package/dist/acroform/pdf-acro-form.js +111 -201
- package/dist/acroform/xfa/pdf-xfa-data.d.ts +4 -3
- package/dist/acroform/xfa/pdf-xfa-data.js +16 -12
- package/dist/acroform/xfa/pdf-xfa-form.d.ts +9 -4
- package/dist/acroform/xfa/pdf-xfa-form.js +17 -39
- package/dist/annotations/index.d.ts +0 -1
- package/dist/annotations/index.js +0 -1
- package/dist/annotations/pdf-annotation.d.ts +7 -2
- package/dist/annotations/pdf-annotation.js +30 -19
- package/dist/annotations/pdf-default-resources.d.ts +11 -0
- package/dist/annotations/pdf-default-resources.js +3 -0
- package/dist/core/decoder.js +1 -1
- package/dist/core/objects/pdf-array.d.ts +8 -1
- package/dist/core/objects/pdf-array.js +31 -0
- package/dist/core/objects/pdf-dictionary.d.ts +2 -0
- package/dist/core/objects/pdf-dictionary.js +14 -7
- package/dist/core/objects/pdf-hexadecimal.d.ts +1 -0
- package/dist/core/objects/pdf-hexadecimal.js +3 -3
- package/dist/core/objects/pdf-indirect-object.d.ts +18 -9
- package/dist/core/objects/pdf-indirect-object.js +75 -16
- package/dist/core/objects/pdf-number.d.ts +1 -0
- package/dist/core/objects/pdf-number.js +5 -4
- package/dist/core/objects/pdf-object-reference.d.ts +8 -1
- package/dist/core/objects/pdf-object-reference.js +14 -0
- package/dist/core/objects/pdf-object.d.ts +14 -0
- package/dist/core/objects/pdf-object.js +36 -0
- package/dist/core/objects/pdf-start-xref.d.ts +1 -0
- package/dist/core/objects/pdf-start-xref.js +4 -0
- package/dist/core/objects/pdf-stream.d.ts +47 -7
- package/dist/core/objects/pdf-stream.js +301 -32
- package/dist/core/objects/pdf-string.d.ts +1 -0
- package/dist/core/objects/pdf-string.js +3 -6
- package/dist/core/objects/pdf-trailer.d.ts +1 -0
- package/dist/core/objects/pdf-trailer.js +6 -3
- package/dist/core/objects/pdf-xref-table.js +1 -1
- package/dist/core/ref.d.ts +3 -1
- package/dist/core/ref.js +8 -5
- package/dist/core/tokens/token.d.ts +2 -1
- package/dist/core/tokens/token.js +3 -0
- package/dist/fonts/index.d.ts +0 -1
- package/dist/fonts/index.js +0 -1
- package/dist/fonts/pdf-font.d.ts +32 -27
- package/dist/fonts/pdf-font.js +115 -77
- package/dist/pdf/index.d.ts +2 -0
- package/dist/pdf/index.js +2 -0
- package/dist/pdf/pdf-document.d.ts +63 -37
- package/dist/pdf/pdf-document.js +351 -135
- package/dist/pdf/pdf-page.d.ts +50 -0
- package/dist/pdf/pdf-page.js +144 -0
- package/dist/pdf/pdf-pages.d.ts +28 -0
- package/dist/pdf/pdf-pages.js +94 -0
- package/dist/pdf/pdf-reader.d.ts +5 -1
- package/dist/pdf/pdf-reader.js +36 -2
- package/dist/pdf/pdf-revision.d.ts +3 -3
- package/dist/pdf/pdf-revision.js +7 -7
- package/dist/pdf/pdf-xref-lookup.js +34 -14
- package/dist/signing/document-security-store.d.ts +14 -17
- package/dist/signing/document-security-store.js +19 -34
- package/dist/signing/signer.d.ts +23 -8
- package/dist/signing/signer.js +51 -17
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/needsCentralWhitespace.d.ts +10 -0
- package/dist/utils/needsCentralWhitespace.js +34 -0
- package/package.json +3 -3
- package/dist/acroform/acroform.d.ts +0 -9
- package/dist/acroform/acroform.js +0 -7
- package/dist/acroform/manager.d.ts +0 -37
- package/dist/acroform/manager.js +0 -57
- package/dist/acroform/pdf-font-encoding-cache.d.ts +0 -27
- package/dist/acroform/pdf-font-encoding-cache.js +0 -188
- package/dist/annotations/pdf-annotation-writer.d.ts +0 -20
- package/dist/annotations/pdf-annotation-writer.js +0 -76
- package/dist/fonts/manager.d.ts +0 -127
- package/dist/fonts/manager.js +0 -378
- package/dist/utils/predictors.d.ts +0 -113
- package/dist/utils/predictors.js +0 -279
package/dist/pdf/pdf-document.js
CHANGED
|
@@ -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';
|
|
@@ -17,9 +18,9 @@ import { PdfStartXRef } from '../core/objects/pdf-start-xref.js';
|
|
|
17
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/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,16 @@ export class PdfDocument extends PdfObject {
|
|
|
44
45
|
signer;
|
|
45
46
|
/** Security handler for encryption/decryption operations */
|
|
46
47
|
securityHandler;
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
52
|
+
_resolvedCache = new Map();
|
|
53
|
+
_objStreamCache = new Map();
|
|
54
|
+
_committing = false;
|
|
55
|
+
_updating = false;
|
|
56
|
+
_finalized = false;
|
|
57
|
+
_signed = false;
|
|
52
58
|
/**
|
|
53
59
|
* Creates a new PDF document instance.
|
|
54
60
|
*
|
|
@@ -75,13 +81,48 @@ export class PdfDocument extends PdfObject {
|
|
|
75
81
|
if (options?.ownerPassword) {
|
|
76
82
|
this.setOwnerPassword(options.ownerPassword);
|
|
77
83
|
}
|
|
78
|
-
this.signer = options?.signer ?? new PdfSigner();
|
|
84
|
+
this.signer = options?.signer ?? new PdfSigner({ document: this });
|
|
79
85
|
this.linkRevisions();
|
|
86
|
+
this.wireResolvers(...this.objects.filter((x) => x instanceof PdfIndirectObject), ...this.revisions.map((rev) => rev.xref.trailerDict));
|
|
80
87
|
this.calculateOffsets();
|
|
81
|
-
this.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
this.originalSecurityHandler = options?.securityHandler;
|
|
89
|
+
this.resetSecurityHandler();
|
|
90
|
+
}
|
|
91
|
+
resolve(objectNumber, generationNumber) {
|
|
92
|
+
const cacheKey = `${objectNumber} ${generationNumber}`;
|
|
93
|
+
const cached = this._resolvedCache.get(cacheKey);
|
|
94
|
+
if (cached)
|
|
95
|
+
return cached;
|
|
96
|
+
const found = this.readObject({ objectNumber, generationNumber });
|
|
97
|
+
if (!found) {
|
|
98
|
+
throw new Error(`Object ${objectNumber} ${generationNumber} not found`);
|
|
99
|
+
}
|
|
100
|
+
this.wireResolvers(found);
|
|
101
|
+
this._resolvedCache.set(cacheKey, found);
|
|
102
|
+
return found;
|
|
103
|
+
}
|
|
104
|
+
get acroform() {
|
|
105
|
+
const root = this.root;
|
|
106
|
+
const acroFormEntry = root.content.get('AcroForm');
|
|
107
|
+
if (!acroFormEntry)
|
|
108
|
+
return null;
|
|
109
|
+
const acroFormRef = acroFormEntry.as(PdfObjectReference)?.resolve();
|
|
110
|
+
if (!acroFormRef)
|
|
111
|
+
return null;
|
|
112
|
+
return acroFormRef.becomes(PdfAcroForm);
|
|
113
|
+
}
|
|
114
|
+
get pages() {
|
|
115
|
+
const root = this.root;
|
|
116
|
+
const pagesEntry = root.content.get('Pages');
|
|
117
|
+
if (pagesEntry) {
|
|
118
|
+
const pagesRef = pagesEntry.as(PdfObjectReference)?.resolve();
|
|
119
|
+
if (pagesRef)
|
|
120
|
+
return pagesRef.becomes(PdfPages);
|
|
121
|
+
}
|
|
122
|
+
const pages = new PdfPages();
|
|
123
|
+
this.add(pages);
|
|
124
|
+
root.content.set('Pages', pages.reference);
|
|
125
|
+
return pages;
|
|
85
126
|
}
|
|
86
127
|
get header() {
|
|
87
128
|
return this.revisions[0].header;
|
|
@@ -132,6 +173,9 @@ export class PdfDocument extends PdfObject {
|
|
|
132
173
|
}
|
|
133
174
|
return this;
|
|
134
175
|
}
|
|
176
|
+
hasObjectInLatestRevision(obj) {
|
|
177
|
+
return this.latestRevision.objects.includes(obj);
|
|
178
|
+
}
|
|
135
179
|
/**
|
|
136
180
|
* Adds objects to the document's latest revision.
|
|
137
181
|
* Automatically starts a new revision if the current one is locked.
|
|
@@ -143,12 +187,21 @@ export class PdfDocument extends PdfObject {
|
|
|
143
187
|
this.startNewRevision();
|
|
144
188
|
}
|
|
145
189
|
for (const obj of objects) {
|
|
146
|
-
|
|
147
|
-
|
|
190
|
+
this.wireResolvers(obj);
|
|
191
|
+
if (this.hasObjectInLatestRevision(obj)) {
|
|
192
|
+
continue;
|
|
148
193
|
}
|
|
149
|
-
this.toBeCommitted.push(obj);
|
|
150
194
|
this.latestRevision.addObject(obj);
|
|
151
195
|
}
|
|
196
|
+
// Auto-add any referenced-but-missing objects
|
|
197
|
+
const missing = this.collectMissingReferences(...objects);
|
|
198
|
+
if (missing.length > 0) {
|
|
199
|
+
this.add(...missing);
|
|
200
|
+
}
|
|
201
|
+
this.update();
|
|
202
|
+
for (const obj of objects) {
|
|
203
|
+
obj.setModified(false);
|
|
204
|
+
}
|
|
152
205
|
}
|
|
153
206
|
/**
|
|
154
207
|
* Packs non-stream indirect objects into compressed ObjStm containers.
|
|
@@ -290,6 +343,10 @@ export class PdfDocument extends PdfObject {
|
|
|
290
343
|
}
|
|
291
344
|
return metadataRef;
|
|
292
345
|
}
|
|
346
|
+
resetSecurityHandler() {
|
|
347
|
+
this.securityHandler =
|
|
348
|
+
this.originalSecurityHandler ?? this.getSecurityHandler();
|
|
349
|
+
}
|
|
293
350
|
getSecurityHandler() {
|
|
294
351
|
const encryptionDictionaryRef = this.trailerDict
|
|
295
352
|
.get('Encrypt')
|
|
@@ -383,11 +440,47 @@ export class PdfDocument extends PdfObject {
|
|
|
383
440
|
}
|
|
384
441
|
return true;
|
|
385
442
|
}
|
|
443
|
+
commitIncrementalUpdates() {
|
|
444
|
+
if (!this.incremental || this._committing) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
this._committing = true;
|
|
448
|
+
try {
|
|
449
|
+
for (const revision of this.revisions) {
|
|
450
|
+
if (!revision.locked)
|
|
451
|
+
continue;
|
|
452
|
+
for (const obj of revision.objects) {
|
|
453
|
+
if (!(obj instanceof PdfIndirectObject)) {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (obj.isModified()) {
|
|
457
|
+
// Pre-register any new objects referenced by obj before
|
|
458
|
+
// cloning so the clone gets correct object numbers.
|
|
459
|
+
// Without this, proxy references (objectNumber = -1) get
|
|
460
|
+
// frozen into the clone as "-1 0 R" — a dangling reference.
|
|
461
|
+
const missing = this.collectMissingReferences(obj);
|
|
462
|
+
if (missing.length > 0) {
|
|
463
|
+
this.add(...missing);
|
|
464
|
+
}
|
|
465
|
+
const adding = obj.clone();
|
|
466
|
+
this.add(adding);
|
|
467
|
+
adding.setModified(false);
|
|
468
|
+
obj.setModified(false);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
finally {
|
|
474
|
+
this._committing = false;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
386
477
|
/**
|
|
387
|
-
* Decrypts all encrypted
|
|
388
|
-
*
|
|
478
|
+
* Decrypts all encrypted object data in-place without removing
|
|
479
|
+
* the encryption infrastructure. Useful in incremental mode where
|
|
480
|
+
* the original (encrypted) bytes are preserved via cached tokens
|
|
481
|
+
* but the live object data needs to be readable.
|
|
389
482
|
*/
|
|
390
|
-
async
|
|
483
|
+
async decryptObjects() {
|
|
391
484
|
if (!this.securityHandler) {
|
|
392
485
|
return;
|
|
393
486
|
}
|
|
@@ -400,19 +493,55 @@ export class PdfDocument extends PdfObject {
|
|
|
400
493
|
}
|
|
401
494
|
await this.securityHandler.decryptObject(object);
|
|
402
495
|
}
|
|
403
|
-
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Re-encrypts all objects and updates the document structure.
|
|
499
|
+
* No-op if the document has no security handler (unencrypted document).
|
|
500
|
+
*/
|
|
501
|
+
async finalize() {
|
|
502
|
+
if (this._finalized) {
|
|
503
|
+
throw new Error('Document has already been finalized');
|
|
504
|
+
}
|
|
505
|
+
this._finalized = true;
|
|
506
|
+
this.update();
|
|
507
|
+
if (this.securityHandler) {
|
|
508
|
+
await this.encrypt();
|
|
509
|
+
}
|
|
510
|
+
this.update();
|
|
511
|
+
await this.sign();
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Decrypts all encrypted objects in the document.
|
|
515
|
+
* Removes the security handler and encryption dictionary after decryption.
|
|
516
|
+
*/
|
|
517
|
+
async decrypt() {
|
|
518
|
+
if (!this.securityHandler) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
await this.decryptObjects();
|
|
404
522
|
this.hasEncryptionDictionary = false;
|
|
523
|
+
this.securityHandler = undefined;
|
|
405
524
|
const encryptionDict = this.encryptionDictionary;
|
|
525
|
+
this.trailerDict.delete('Encrypt');
|
|
406
526
|
if (encryptionDict) {
|
|
407
|
-
|
|
527
|
+
this.deleteObject(encryptionDict);
|
|
408
528
|
}
|
|
409
|
-
|
|
529
|
+
this.update();
|
|
410
530
|
}
|
|
411
531
|
/**
|
|
412
|
-
* Encrypts all objects
|
|
413
|
-
*
|
|
532
|
+
* Encrypts all encryptable objects using the security handler.
|
|
533
|
+
* Re-uses the existing encryption dictionary or creates one if needed,
|
|
534
|
+
* propagating it to all revisions.
|
|
414
535
|
*/
|
|
415
536
|
async encrypt() {
|
|
537
|
+
if (!this.securityHandler) {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
const addingEncryption = !this.hasEncryptionDictionary;
|
|
541
|
+
const wasIncremental = this.incremental;
|
|
542
|
+
if (addingEncryption) {
|
|
543
|
+
this.setIncremental(false);
|
|
544
|
+
}
|
|
416
545
|
this.initSecurityHandler({});
|
|
417
546
|
await this.securityHandler.write();
|
|
418
547
|
for (const object of this.objects) {
|
|
@@ -423,20 +552,24 @@ export class PdfDocument extends PdfObject {
|
|
|
423
552
|
continue;
|
|
424
553
|
}
|
|
425
554
|
await this.securityHandler.encryptObject(object);
|
|
555
|
+
object.setModified(false);
|
|
426
556
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
557
|
+
if (addingEncryption) {
|
|
558
|
+
const encryptionDictObject = new PdfIndirectObject({
|
|
559
|
+
content: this.securityHandler.dict,
|
|
560
|
+
encryptable: false,
|
|
561
|
+
});
|
|
562
|
+
this.add(encryptionDictObject);
|
|
563
|
+
this.hasEncryptionDictionary = true;
|
|
564
|
+
for (const revision of this.revisions) {
|
|
565
|
+
revision.xref.trailerDict.set('Encrypt', encryptionDictObject.reference);
|
|
566
|
+
if (!revision.xref.trailerDict.get('ID')) {
|
|
567
|
+
revision.xref.trailerDict.set('ID', this.securityHandler.getDocumentId());
|
|
568
|
+
}
|
|
435
569
|
}
|
|
570
|
+
this.setIncremental(wasIncremental);
|
|
436
571
|
}
|
|
437
|
-
|
|
438
|
-
this.hasEncryptionDictionary = true;
|
|
439
|
-
await this.update();
|
|
572
|
+
this.update();
|
|
440
573
|
}
|
|
441
574
|
/**
|
|
442
575
|
* Finds a compressed object by its object number within an object stream.
|
|
@@ -445,28 +578,26 @@ export class PdfDocument extends PdfObject {
|
|
|
445
578
|
* @returns The found indirect object or undefined if not found
|
|
446
579
|
* @throws Error if the object cannot be found in the expected object stream
|
|
447
580
|
*/
|
|
448
|
-
|
|
581
|
+
findCompressedObject(options) {
|
|
449
582
|
const xrefEntry = this.xrefLookup.getObject(options.objectNumber);
|
|
450
583
|
if (!(xrefEntry instanceof PdfXRefStreamCompressedEntry)) {
|
|
451
584
|
throw new Error('Cannot find object inside object stream via PdfDocument.findObject');
|
|
452
585
|
}
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
.
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
});
|
|
469
|
-
return decompressedObject;
|
|
586
|
+
const streamNumber = xrefEntry.objectStreamNumber.value;
|
|
587
|
+
let objectStream = this._objStreamCache.get(streamNumber);
|
|
588
|
+
if (!objectStream) {
|
|
589
|
+
const objectStreamIndirect = this.findUncompressedObject({
|
|
590
|
+
objectNumber: streamNumber,
|
|
591
|
+
});
|
|
592
|
+
if (!objectStreamIndirect) {
|
|
593
|
+
throw new Error(`Cannot find object stream ${streamNumber} for object ${options.objectNumber}`);
|
|
594
|
+
}
|
|
595
|
+
objectStream = objectStreamIndirect.content
|
|
596
|
+
.as(PdfStream)
|
|
597
|
+
.parseAs(PdfObjStream);
|
|
598
|
+
this._objStreamCache.set(streamNumber, objectStream);
|
|
599
|
+
}
|
|
600
|
+
return objectStream.getObject({ objectNumber: options.objectNumber });
|
|
470
601
|
}
|
|
471
602
|
/**
|
|
472
603
|
* Finds an uncompressed indirect object by its object number.
|
|
@@ -502,14 +633,14 @@ export class PdfDocument extends PdfObject {
|
|
|
502
633
|
* @param options.allowUnindexed - If true, searches unindexed objects as fallback
|
|
503
634
|
* @returns A cloned and decrypted copy of the object, or undefined if not found
|
|
504
635
|
*/
|
|
505
|
-
|
|
636
|
+
readObject(options) {
|
|
506
637
|
let foundObject;
|
|
507
638
|
try {
|
|
508
639
|
foundObject = this.findUncompressedObject(options);
|
|
509
640
|
}
|
|
510
641
|
catch (e) {
|
|
511
642
|
if (e instanceof FoundCompressedObjectError) {
|
|
512
|
-
foundObject =
|
|
643
|
+
foundObject = this.findCompressedObject(options);
|
|
513
644
|
}
|
|
514
645
|
else {
|
|
515
646
|
throw e;
|
|
@@ -524,24 +655,32 @@ export class PdfDocument extends PdfObject {
|
|
|
524
655
|
if (!foundObject) {
|
|
525
656
|
return undefined;
|
|
526
657
|
}
|
|
527
|
-
if (
|
|
658
|
+
if (options.cloned) {
|
|
528
659
|
foundObject = foundObject.clone();
|
|
529
|
-
await this.securityHandler.decryptObject(foundObject);
|
|
530
660
|
}
|
|
531
661
|
return foundObject;
|
|
532
662
|
}
|
|
663
|
+
/**
|
|
664
|
+
* Finds the revision that contains a given PDF object.
|
|
665
|
+
* Useful for determining the origin of an object across multiple revisions.
|
|
666
|
+
* @param obj - The PDF object to find the revision for
|
|
667
|
+
* @returns The PdfRevision that contains the object, or undefined if not found in any revision
|
|
668
|
+
*/
|
|
669
|
+
findRevisionForObject(obj) {
|
|
670
|
+
return this.revisions.find((rev) => rev.objects.includes(obj));
|
|
671
|
+
}
|
|
533
672
|
/**
|
|
534
673
|
* Deletes an object from all revisions in the document.
|
|
535
674
|
*
|
|
536
675
|
* @param obj - The PDF object to delete
|
|
537
676
|
*/
|
|
538
|
-
|
|
677
|
+
deleteObject(obj) {
|
|
539
678
|
if (!obj)
|
|
540
679
|
return;
|
|
541
680
|
for (const revision of this.revisions) {
|
|
542
681
|
revision.deleteObject(obj);
|
|
543
682
|
}
|
|
544
|
-
|
|
683
|
+
this.update();
|
|
545
684
|
}
|
|
546
685
|
/**
|
|
547
686
|
* Sets the PDF version for the document.
|
|
@@ -565,10 +704,10 @@ export class PdfDocument extends PdfObject {
|
|
|
565
704
|
if (value === this.isIncremental()) {
|
|
566
705
|
return;
|
|
567
706
|
}
|
|
707
|
+
this.incremental = value;
|
|
568
708
|
for (const revision of this.revisions) {
|
|
569
709
|
revision.locked = value;
|
|
570
710
|
}
|
|
571
|
-
this.startNewRevision();
|
|
572
711
|
}
|
|
573
712
|
/**
|
|
574
713
|
* Checks if the document is in incremental mode.
|
|
@@ -576,57 +715,7 @@ export class PdfDocument extends PdfObject {
|
|
|
576
715
|
* @returns True if all revisions are locked for incremental updates
|
|
577
716
|
*/
|
|
578
717
|
isIncremental() {
|
|
579
|
-
return this.
|
|
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
|
-
this.ensureEncryptionDictionary();
|
|
597
|
-
await this.securityHandler.encryptObject(newObject);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
await this.update();
|
|
601
|
-
}
|
|
602
|
-
ensureEncryptionDictionary() {
|
|
603
|
-
if (this.hasEncryptionDictionary) {
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
const encryptionDictObject = new PdfIndirectObject({
|
|
607
|
-
content: this.securityHandler.dict,
|
|
608
|
-
encryptable: false,
|
|
609
|
-
});
|
|
610
|
-
this.latestRevision.addObject(encryptionDictObject);
|
|
611
|
-
this.trailerDict.set('Encrypt', encryptionDictObject.reference);
|
|
612
|
-
this.hasEncryptionDictionary = true;
|
|
613
|
-
}
|
|
614
|
-
/**
|
|
615
|
-
* Sets the Document Security Store (DSS) for the document.
|
|
616
|
-
* Used for long-term validation of digital signatures.
|
|
617
|
-
*
|
|
618
|
-
* @param dss - The Document Security Store object to set
|
|
619
|
-
* @throws Error if the document has no root dictionary
|
|
620
|
-
*/
|
|
621
|
-
async setDocumentSecurityStore(dss) {
|
|
622
|
-
let rootDictionary = this.root?.content;
|
|
623
|
-
if (!rootDictionary) {
|
|
624
|
-
throw new Error('Cannot set DSS - document has no root dictionary');
|
|
625
|
-
}
|
|
626
|
-
rootDictionary.set('DSS', dss.reference);
|
|
627
|
-
if (!this.hasObject(dss)) {
|
|
628
|
-
await this.commit(dss);
|
|
629
|
-
}
|
|
718
|
+
return this.incremental;
|
|
630
719
|
}
|
|
631
720
|
/**
|
|
632
721
|
* Returns tokens paired with their source objects.
|
|
@@ -648,12 +737,93 @@ export class PdfDocument extends PdfObject {
|
|
|
648
737
|
tokenize() {
|
|
649
738
|
return this.tokensWithObjects().map(({ token }) => token);
|
|
650
739
|
}
|
|
740
|
+
wireResolvers(...objects) {
|
|
741
|
+
const seen = new Set();
|
|
742
|
+
const walk = (obj) => {
|
|
743
|
+
if (seen.has(obj))
|
|
744
|
+
return;
|
|
745
|
+
seen.add(obj);
|
|
746
|
+
if (obj instanceof PdfObjectReference &&
|
|
747
|
+
!(obj instanceof PdfIndirectObject)) {
|
|
748
|
+
obj.resolver = this;
|
|
749
|
+
}
|
|
750
|
+
else if (obj instanceof PdfStream) {
|
|
751
|
+
walk(obj.header);
|
|
752
|
+
}
|
|
753
|
+
else if (obj instanceof PdfDictionary) {
|
|
754
|
+
for (const [, value] of obj.entries()) {
|
|
755
|
+
if (value)
|
|
756
|
+
walk(value);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
else if (obj instanceof PdfArray) {
|
|
760
|
+
for (const item of obj.items) {
|
|
761
|
+
walk(item);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
else if (obj instanceof PdfIndirectObject) {
|
|
765
|
+
walk(obj.content);
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
for (const obj of objects) {
|
|
769
|
+
walk(obj);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
collectMissingReferences(...objects) {
|
|
773
|
+
const seen = new Set();
|
|
774
|
+
const missing = [];
|
|
775
|
+
const queue = [...objects];
|
|
776
|
+
while (queue.length > 0) {
|
|
777
|
+
const obj = queue.pop();
|
|
778
|
+
if (seen.has(obj))
|
|
779
|
+
continue;
|
|
780
|
+
seen.add(obj);
|
|
781
|
+
if (obj instanceof PdfObjectReference &&
|
|
782
|
+
!(obj instanceof PdfIndirectObject)) {
|
|
783
|
+
if (obj.resolver) {
|
|
784
|
+
try {
|
|
785
|
+
const resolved = obj.resolve();
|
|
786
|
+
if (resolved instanceof PdfIndirectObject &&
|
|
787
|
+
!resolved.inPdf()) {
|
|
788
|
+
missing.push(resolved);
|
|
789
|
+
queue.push(resolved);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
catch {
|
|
793
|
+
// Skip unresolvable references
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
else if (obj instanceof PdfStream) {
|
|
798
|
+
queue.push(obj.header);
|
|
799
|
+
}
|
|
800
|
+
else if (obj instanceof PdfDictionary) {
|
|
801
|
+
for (const [, value] of obj.entries()) {
|
|
802
|
+
if (value)
|
|
803
|
+
queue.push(value);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
else if (obj instanceof PdfArray) {
|
|
807
|
+
for (const item of obj.items)
|
|
808
|
+
queue.push(item);
|
|
809
|
+
}
|
|
810
|
+
else if (obj instanceof PdfIndirectObject) {
|
|
811
|
+
queue.push(obj.content);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return missing;
|
|
815
|
+
}
|
|
651
816
|
linkRevisions() {
|
|
652
817
|
const xrefLookups = this.revisions.map((rev) => rev.xref);
|
|
653
818
|
const indirectObjects = this.objects.filter((x) => x instanceof PdfIndirectObject);
|
|
654
819
|
for (const revision of this.revisions) {
|
|
655
820
|
revision.xref.linkPrev(xrefLookups);
|
|
656
|
-
|
|
821
|
+
// Walk the entire prev chain to link all xref entries
|
|
822
|
+
let xref = revision.xref;
|
|
823
|
+
while (xref) {
|
|
824
|
+
xref.linkIndirectObjects(indirectObjects);
|
|
825
|
+
xref = xref.prev;
|
|
826
|
+
}
|
|
657
827
|
}
|
|
658
828
|
}
|
|
659
829
|
linkOffsets() {
|
|
@@ -700,7 +870,7 @@ export class PdfDocument extends PdfObject {
|
|
|
700
870
|
}
|
|
701
871
|
updateRevisions() {
|
|
702
872
|
let modified = false;
|
|
703
|
-
this.revisions.forEach((rev
|
|
873
|
+
this.revisions.forEach((rev) => {
|
|
704
874
|
if (rev.isModified()) {
|
|
705
875
|
modified = true;
|
|
706
876
|
}
|
|
@@ -708,11 +878,72 @@ export class PdfDocument extends PdfObject {
|
|
|
708
878
|
rev.update();
|
|
709
879
|
}
|
|
710
880
|
});
|
|
881
|
+
// Always rebuild xref binary data to reflect recalculated offsets
|
|
882
|
+
// This walks the entire prev chain for each revision
|
|
883
|
+
for (const rev of this.revisions) {
|
|
884
|
+
let xref = rev.xref;
|
|
885
|
+
while (xref) {
|
|
886
|
+
xref.update();
|
|
887
|
+
xref = xref.prev;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
711
890
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
891
|
+
/**
|
|
892
|
+
* Performs a full update cycle to ensure all revisions are consistent and offsets are correct.
|
|
893
|
+
*/
|
|
894
|
+
update() {
|
|
895
|
+
if (this._updating)
|
|
896
|
+
return;
|
|
897
|
+
this._updating = true;
|
|
898
|
+
try {
|
|
899
|
+
this.commitIncrementalUpdates();
|
|
900
|
+
this.flushResolvedCache();
|
|
901
|
+
this.registerNewReferences();
|
|
902
|
+
this.calculateOffsets();
|
|
903
|
+
this.updateRevisions();
|
|
904
|
+
// Second pass: xref binary may have changed size (e.g. FlateDecode removed
|
|
905
|
+
// from xref stream), shifting objects that follow it. Recalculate so entry
|
|
906
|
+
// byteOffset refs hold the new positions, then rebuild the xref binary once
|
|
907
|
+
// more so the baked bytes match those positions.
|
|
908
|
+
this.calculateOffsets();
|
|
909
|
+
this.updateRevisions();
|
|
910
|
+
// Third pass: confirm positions are stable (xref binary size should not
|
|
911
|
+
// change again because W widths and entry count are the same).
|
|
912
|
+
this.calculateOffsets();
|
|
913
|
+
}
|
|
914
|
+
finally {
|
|
915
|
+
this._updating = false;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Walks all objects in the document and registers any newly created
|
|
920
|
+
* PdfIndirectObjects that are referenced but not yet part of the document
|
|
921
|
+
* (e.g. appearance streams created by generateAppearance).
|
|
922
|
+
*/
|
|
923
|
+
registerNewReferences() {
|
|
924
|
+
const missing = this.collectMissingReferences(...this.latestRevision.objects);
|
|
925
|
+
if (missing.length > 0) {
|
|
926
|
+
this.add(...missing);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
flushResolvedCache() {
|
|
930
|
+
const existing = new Set(this.objects);
|
|
931
|
+
for (const [, obj] of this._resolvedCache) {
|
|
932
|
+
if (obj.isModified() && !existing.has(obj)) {
|
|
933
|
+
this.add(obj);
|
|
934
|
+
existing.add(obj);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
async sign() {
|
|
939
|
+
if (this._signed) {
|
|
940
|
+
throw new Error('Document has already been signed');
|
|
941
|
+
}
|
|
942
|
+
if (!this.signer) {
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
await this.signer.sign();
|
|
946
|
+
this._signed = true;
|
|
716
947
|
}
|
|
717
948
|
/**
|
|
718
949
|
* Serializes the document to a byte array.
|
|
@@ -720,24 +951,9 @@ export class PdfDocument extends PdfObject {
|
|
|
720
951
|
* @returns The PDF document as a Uint8Array
|
|
721
952
|
*/
|
|
722
953
|
toBytes() {
|
|
723
|
-
this.
|
|
724
|
-
this.updateRevisions();
|
|
954
|
+
this.update();
|
|
725
955
|
return concatUint8Arrays(...this.revisions.map((x) => x.toBytes()));
|
|
726
956
|
}
|
|
727
|
-
/**
|
|
728
|
-
* Serializes the document to a Base64-encoded string.
|
|
729
|
-
*
|
|
730
|
-
* @returns A promise that resolves to the PDF document as a Base64 string
|
|
731
|
-
*/
|
|
732
|
-
toBase64() {
|
|
733
|
-
const bytes = this.toBytes();
|
|
734
|
-
let binary = '';
|
|
735
|
-
const len = bytes.byteLength;
|
|
736
|
-
for (let i = 0; i < len; i++) {
|
|
737
|
-
binary += String.fromCharCode(bytes[i]);
|
|
738
|
-
}
|
|
739
|
-
return btoa(binary);
|
|
740
|
-
}
|
|
741
957
|
/**
|
|
742
958
|
* Creates a deep copy of the document.
|
|
743
959
|
*
|
|
@@ -757,8 +973,8 @@ export class PdfDocument extends PdfObject {
|
|
|
757
973
|
* @param input - Async or sync iterable of byte arrays
|
|
758
974
|
* @returns A promise that resolves to the parsed PdfDocument
|
|
759
975
|
*/
|
|
760
|
-
static fromBytes(input) {
|
|
761
|
-
return PdfReader.fromBytes(input);
|
|
976
|
+
static fromBytes(input, options) {
|
|
977
|
+
return PdfReader.fromBytes(input, options);
|
|
762
978
|
}
|
|
763
979
|
isModified() {
|
|
764
980
|
return (super.isModified() || this.revisions.some((rev) => rev.isModified()));
|
|
@@ -769,6 +985,6 @@ export class PdfDocument extends PdfObject {
|
|
|
769
985
|
* @returns A promise that resolves to the verification result
|
|
770
986
|
*/
|
|
771
987
|
async verifySignatures() {
|
|
772
|
-
return await this.signer.verify(
|
|
988
|
+
return await this.signer.verify();
|
|
773
989
|
}
|
|
774
990
|
}
|