pdf-lite 1.7.3-alpha.1 → 1.7.4-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acroform/appearance/pdf-choice-appearance-stream.d.ts +1 -3
- package/dist/acroform/appearance/pdf-choice-appearance-stream.js +16 -2
- package/dist/acroform/appearance/pdf-text-appearance-stream.d.ts +1 -0
- package/dist/acroform/appearance/pdf-text-appearance-stream.js +29 -6
- package/dist/acroform/fields/pdf-choice-form-field.js +1 -0
- package/dist/acroform/fields/pdf-form-field.js +1 -0
- package/dist/acroform/fields/pdf-text-form-field.js +1 -0
- package/dist/crypto/key-derivation/key-derivation-aes256.js +2 -1
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +18 -0
- package/dist/pdf/pdf-document.d.ts +105 -3
- package/dist/pdf/pdf-document.js +246 -31
- package/dist/pdf/pdf-reader.js +3 -37
- package/dist/security/handlers/base.d.ts +35 -0
- package/dist/security/handlers/base.js +40 -0
- package/dist/security/handlers/pubSec.d.ts +2 -0
- package/dist/security/handlers/pubSec.js +8 -0
- package/dist/security/handlers/v4.d.ts +1 -0
- package/dist/security/handlers/v4.js +10 -0
- package/package.json +1 -1
|
@@ -3,9 +3,6 @@ import { PdfAppearanceStream } from './pdf-appearance-stream.js';
|
|
|
3
3
|
import type { PdfDictionary } from '../../core/objects/pdf-dictionary.js';
|
|
4
4
|
import type { PdfFont } from '../../fonts/pdf-font.js';
|
|
5
5
|
import { PdfFormFieldFlags } from '../fields/pdf-form-field-flags.js';
|
|
6
|
-
/**
|
|
7
|
-
* Appearance stream for choice fields (dropdowns, list boxes).
|
|
8
|
-
*/
|
|
9
6
|
export declare class PdfChoiceAppearanceStream extends PdfAppearanceStream {
|
|
10
7
|
constructor(ctx: {
|
|
11
8
|
rect: [number, number, number, number];
|
|
@@ -18,5 +15,6 @@ export declare class PdfChoiceAppearanceStream extends PdfAppearanceStream {
|
|
|
18
15
|
reverseEncodingMap?: Map<string, number>;
|
|
19
16
|
displayOptions?: string[];
|
|
20
17
|
selectedIndex?: number;
|
|
18
|
+
quadding?: number;
|
|
21
19
|
});
|
|
22
20
|
}
|
|
@@ -4,6 +4,14 @@ import { PdfFormFieldFlags } from '../fields/pdf-form-field-flags.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Appearance stream for choice fields (dropdowns, list boxes).
|
|
6
6
|
*/
|
|
7
|
+
/** Compute the X offset for a line of text given the quadding value. */
|
|
8
|
+
function calcTextX(quadding, padding, availableWidth, textWidth) {
|
|
9
|
+
if (quadding === 2)
|
|
10
|
+
return padding + Math.max(availableWidth - textWidth, 0);
|
|
11
|
+
if (quadding === 1)
|
|
12
|
+
return padding + Math.max((availableWidth - textWidth) / 2, 0);
|
|
13
|
+
return padding;
|
|
14
|
+
}
|
|
7
15
|
export class PdfChoiceAppearanceStream extends PdfAppearanceStream {
|
|
8
16
|
constructor(ctx) {
|
|
9
17
|
const [x1, y1, x2, y2] = ctx.rect;
|
|
@@ -17,10 +25,14 @@ export class PdfChoiceAppearanceStream extends PdfAppearanceStream {
|
|
|
17
25
|
const isUnicode = ctx.isUnicode ?? false;
|
|
18
26
|
const reverseEncodingMap = ctx.reverseEncodingMap;
|
|
19
27
|
const padding = 2;
|
|
28
|
+
const availableWidth = width - 2 * padding;
|
|
20
29
|
const isCombo = new PdfFormFieldFlags(ctx.flags).combo;
|
|
30
|
+
const quadding = ctx.quadding ?? 0;
|
|
21
31
|
const g = new PdfGraphics({ resolvedFonts: ctx.resolvedFonts });
|
|
22
32
|
g.beginMarkedContent();
|
|
23
33
|
g.save();
|
|
34
|
+
// Set DA early so measureTextWidth works for alignment calculations
|
|
35
|
+
g.setDefaultAppearance(ctx.da);
|
|
24
36
|
if (!isCombo && ctx.displayOptions && ctx.displayOptions.length > 0) {
|
|
25
37
|
// Listbox: render all items, highlight the selected one
|
|
26
38
|
const lineHeight = ctx.da.fontSize + 4;
|
|
@@ -39,9 +51,10 @@ export class PdfChoiceAppearanceStream extends PdfAppearanceStream {
|
|
|
39
51
|
g.restore();
|
|
40
52
|
}
|
|
41
53
|
const textY = itemY + lineHeight * 0.25;
|
|
54
|
+
const textX = calcTextX(quadding, padding, availableWidth, g.measureTextWidth(ctx.displayOptions[i]));
|
|
42
55
|
g.beginText();
|
|
43
56
|
g.setDefaultAppearance(ctx.da);
|
|
44
|
-
g.moveTo(
|
|
57
|
+
g.moveTo(textX, textY);
|
|
45
58
|
g.showText(ctx.displayOptions[i], isUnicode, reverseEncodingMap);
|
|
46
59
|
g.endText();
|
|
47
60
|
}
|
|
@@ -49,9 +62,10 @@ export class PdfChoiceAppearanceStream extends PdfAppearanceStream {
|
|
|
49
62
|
else {
|
|
50
63
|
// Combo (dropdown) or no options: show selected value only
|
|
51
64
|
const textY = (height - ctx.da.fontSize) / 2 + ctx.da.fontSize * 0.2;
|
|
65
|
+
const textX = calcTextX(quadding, padding, availableWidth, g.measureTextWidth(ctx.value));
|
|
52
66
|
g.beginText();
|
|
53
67
|
g.setDefaultAppearance(ctx.da);
|
|
54
|
-
g.moveTo(
|
|
68
|
+
g.moveTo(textX, textY);
|
|
55
69
|
g.showText(ctx.value, isUnicode, reverseEncodingMap);
|
|
56
70
|
g.endText();
|
|
57
71
|
if (isCombo) {
|
|
@@ -2,6 +2,19 @@ import { PdfDefaultAppearance } from '../fields/pdf-default-appearance.js';
|
|
|
2
2
|
import { PdfAppearanceStream } from './pdf-appearance-stream.js';
|
|
3
3
|
import { PdfGraphics } from './pdf-graphics.js';
|
|
4
4
|
const DEFAULT_FONT_SIZE = 12;
|
|
5
|
+
/** Compute the X offset for a line of text given the quadding value. */
|
|
6
|
+
function calcTextX(quadding, padding, availableWidth, textWidth) {
|
|
7
|
+
if (quadding === 2) {
|
|
8
|
+
// Right-aligned
|
|
9
|
+
return padding + Math.max(availableWidth - textWidth, 0);
|
|
10
|
+
}
|
|
11
|
+
if (quadding === 1) {
|
|
12
|
+
// Center-aligned
|
|
13
|
+
return padding + Math.max((availableWidth - textWidth) / 2, 0);
|
|
14
|
+
}
|
|
15
|
+
// Left-aligned (default)
|
|
16
|
+
return padding;
|
|
17
|
+
}
|
|
5
18
|
/**
|
|
6
19
|
* Appearance stream for text fields (single-line, multiline, comb).
|
|
7
20
|
* Enhanced with word wrapping and automatic font scaling.
|
|
@@ -18,6 +31,7 @@ export class PdfTextAppearanceStream extends PdfAppearanceStream {
|
|
|
18
31
|
const availableWidth = width - 2 * padding;
|
|
19
32
|
const availableHeight = height - 2 * padding;
|
|
20
33
|
const autoSize = ctx.da.fontSize <= 0;
|
|
34
|
+
const quadding = ctx.quadding ?? 0;
|
|
21
35
|
// Create graphics with font context for text measurement
|
|
22
36
|
const g = new PdfGraphics({
|
|
23
37
|
resolvedFonts: ctx.resolvedFonts,
|
|
@@ -108,11 +122,18 @@ export class PdfTextAppearanceStream extends PdfAppearanceStream {
|
|
|
108
122
|
}
|
|
109
123
|
else {
|
|
110
124
|
g.beginText();
|
|
111
|
-
|
|
125
|
+
let prevLineX = 0;
|
|
112
126
|
for (let i = 0; i < lines.length; i++) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
127
|
+
const lineText = lines[i].replace(/\r/g, '');
|
|
128
|
+
const lineX = calcTextX(quadding, padding, availableWidth, g.measureTextWidth(lineText));
|
|
129
|
+
if (i === 0) {
|
|
130
|
+
g.moveTo(lineX, startY);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
g.moveTo(lineX - prevLineX, -renderLineHeight);
|
|
134
|
+
}
|
|
135
|
+
prevLineX = lineX;
|
|
136
|
+
g.showText(lineText, isUnicode, reverseEncodingMap);
|
|
116
137
|
}
|
|
117
138
|
g.endText();
|
|
118
139
|
}
|
|
@@ -126,12 +147,14 @@ export class PdfTextAppearanceStream extends PdfAppearanceStream {
|
|
|
126
147
|
}
|
|
127
148
|
}
|
|
128
149
|
const textY = (height - finalFontSize) / 2 + finalFontSize * 0.2;
|
|
150
|
+
const textWidth = g.measureTextWidth(value);
|
|
151
|
+
const textX = calcTextX(quadding, padding, availableWidth, textWidth);
|
|
129
152
|
if (ctx.markdown) {
|
|
130
|
-
g.showMarkdown(ctx.markdown, isUnicode, reverseEncodingMap,
|
|
153
|
+
g.showMarkdown(ctx.markdown, isUnicode, reverseEncodingMap, textX, textY, finalFontSize);
|
|
131
154
|
}
|
|
132
155
|
else {
|
|
133
156
|
g.beginText();
|
|
134
|
-
g.moveTo(
|
|
157
|
+
g.moveTo(textX, textY);
|
|
135
158
|
g.showText(value, isUnicode, reverseEncodingMap);
|
|
136
159
|
g.endText();
|
|
137
160
|
}
|
|
@@ -101,6 +101,7 @@ export class PdfChoiceFormField extends PdfFormField {
|
|
|
101
101
|
reverseEncodingMap,
|
|
102
102
|
displayOptions: this.options.map((opt) => opt.label),
|
|
103
103
|
selectedIndex: this.selectedIndex,
|
|
104
|
+
quadding: this.quadding,
|
|
104
105
|
});
|
|
105
106
|
if (options?.makeReadOnly) {
|
|
106
107
|
this.readOnly = true;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PdfInvalidPasswordError } from '../../errors.js';
|
|
1
2
|
import { aes128CbcNoPaddingEncrypt, aes256CbcNoPaddingDecrypt, sha256, sha384, sha512, } from '../../utils/algos.js';
|
|
2
3
|
import { assert } from '../../utils/assert.js';
|
|
3
4
|
/**
|
|
@@ -162,7 +163,7 @@ export async function getFileKey(userPassword, ownerPassword, u, ue, o, oe) {
|
|
|
162
163
|
return key;
|
|
163
164
|
}
|
|
164
165
|
catch (e) {
|
|
165
|
-
throw new
|
|
166
|
+
throw new PdfInvalidPasswordError();
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -20,3 +20,17 @@ export declare class UnexpectedTokenError extends Error {
|
|
|
20
20
|
*/
|
|
21
21
|
export declare class FoundCompressedObjectError extends Error {
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Error thrown when a PDF is encrypted and the blank password
|
|
25
|
+
* does not grant access. Callers should catch this and prompt
|
|
26
|
+
* the user for a password, then retry with the `password` option.
|
|
27
|
+
*/
|
|
28
|
+
export declare class PdfPasswordProtectedError extends Error {
|
|
29
|
+
constructor(message?: string);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Error thrown when a provided password is invalid for decrypting a PDF.
|
|
33
|
+
*/
|
|
34
|
+
export declare class PdfInvalidPasswordError extends Error {
|
|
35
|
+
constructor(message?: string);
|
|
36
|
+
}
|
package/dist/errors.js
CHANGED
|
@@ -22,3 +22,21 @@ export class UnexpectedTokenError extends Error {
|
|
|
22
22
|
*/
|
|
23
23
|
export class FoundCompressedObjectError extends Error {
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Error thrown when a PDF is encrypted and the blank password
|
|
27
|
+
* does not grant access. Callers should catch this and prompt
|
|
28
|
+
* the user for a password, then retry with the `password` option.
|
|
29
|
+
*/
|
|
30
|
+
export class PdfPasswordProtectedError extends Error {
|
|
31
|
+
constructor(message) {
|
|
32
|
+
super(message ?? 'PDF is password protected');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Error thrown when a provided password is invalid for decrypting a PDF.
|
|
37
|
+
*/
|
|
38
|
+
export class PdfInvalidPasswordError extends Error {
|
|
39
|
+
constructor(message) {
|
|
40
|
+
super(message ?? 'Invalid password');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -47,6 +47,8 @@ export declare class PdfDocument extends PdfObject implements IPdfObjectResolver
|
|
|
47
47
|
private _updating;
|
|
48
48
|
private _finalized;
|
|
49
49
|
private _signed;
|
|
50
|
+
/** @internal */
|
|
51
|
+
_batching: boolean;
|
|
50
52
|
/**
|
|
51
53
|
* Creates a new PDF document instance.
|
|
52
54
|
*
|
|
@@ -71,14 +73,48 @@ export declare class PdfDocument extends PdfObject implements IPdfObjectResolver
|
|
|
71
73
|
get pages(): PdfPages;
|
|
72
74
|
get header(): PdfComment | undefined;
|
|
73
75
|
set header(comment: PdfComment | undefined);
|
|
76
|
+
/**
|
|
77
|
+
* Loads PDF objects into the document, organizing them into revisions.
|
|
78
|
+
* Parses objects into revisions based on EOF comments.
|
|
79
|
+
*
|
|
80
|
+
* @param objects - Array of PDF objects to load into the document
|
|
81
|
+
* @param options - Optional security and mode configuration
|
|
82
|
+
* @param options.password - User password for encrypted documents
|
|
83
|
+
* @param options.ownerPassword - Owner password for encrypted documents
|
|
84
|
+
* @param options.incremental - Whether to use incremental mode
|
|
85
|
+
*/
|
|
86
|
+
loadObjects(objects: PdfObject[], options?: {
|
|
87
|
+
password?: string;
|
|
88
|
+
ownerPassword?: string;
|
|
89
|
+
incremental?: boolean;
|
|
90
|
+
}): Promise<this>;
|
|
91
|
+
/**
|
|
92
|
+
* Loads a PDF document from a byte stream, parsing it into objects and revisions.
|
|
93
|
+
* @param input - Async or sync iterable of byte arrays representing the PDF file
|
|
94
|
+
* @param options - Optional security and mode configuration
|
|
95
|
+
* @returns A promise that resolves to the loaded PdfDocument instance
|
|
96
|
+
*/
|
|
97
|
+
load(input: AsyncIterable<ByteArray> | Iterable<ByteArray>, options?: {
|
|
98
|
+
password?: string;
|
|
99
|
+
ownerPassword?: string;
|
|
100
|
+
incremental?: boolean;
|
|
101
|
+
}): Promise<this>;
|
|
74
102
|
/**
|
|
75
103
|
* Creates a PdfDocument from an array of PDF objects.
|
|
76
104
|
* Parses objects into revisions based on EOF comments.
|
|
77
105
|
*
|
|
78
106
|
* @param objects - Array of PDF objects to construct the document from
|
|
79
|
-
* @
|
|
107
|
+
* @param options - Optional security and mode configuration
|
|
108
|
+
* @param options.password - User password for encrypted documents
|
|
109
|
+
* @param options.ownerPassword - Owner password for encrypted documents
|
|
110
|
+
* @param options.incremental - Whether to use incremental mode
|
|
111
|
+
* @returns A promise that resolves to the new PdfDocument instance
|
|
80
112
|
*/
|
|
81
|
-
static fromObjects(objects: PdfObject[]
|
|
113
|
+
static fromObjects(objects: PdfObject[], options?: {
|
|
114
|
+
password?: string;
|
|
115
|
+
ownerPassword?: string;
|
|
116
|
+
incremental?: boolean;
|
|
117
|
+
}): Promise<PdfDocument>;
|
|
82
118
|
/**
|
|
83
119
|
* Starts a new revision for incremental updates.
|
|
84
120
|
* Creates a new revision linked to the previous one.
|
|
@@ -94,6 +130,31 @@ export declare class PdfDocument extends PdfObject implements IPdfObjectResolver
|
|
|
94
130
|
* @param objects - PDF objects to add to the document
|
|
95
131
|
*/
|
|
96
132
|
add(...objects: PdfObject[]): void;
|
|
133
|
+
/**
|
|
134
|
+
* Recursively collects and adds all missing referenced objects in batches.
|
|
135
|
+
* More efficient than recursive add() calls since it collects all missing
|
|
136
|
+
* objects before processing them.
|
|
137
|
+
* @private
|
|
138
|
+
*/
|
|
139
|
+
private addAllMissingReferences;
|
|
140
|
+
/**
|
|
141
|
+
* Creates a batch for adding multiple objects with a single update pass.
|
|
142
|
+
* This is significantly faster than calling `add()` multiple times when
|
|
143
|
+
* adding objects with many sub-references (e.g. fonts with descriptors,
|
|
144
|
+
* CIDToGIDMap streams, ToUnicode CMaps).
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```typescript
|
|
148
|
+
* const batch = document.batch()
|
|
149
|
+
* batch.add(font1)
|
|
150
|
+
* batch.add(font2)
|
|
151
|
+
* batch.add(imageStream)
|
|
152
|
+
* batch.commit()
|
|
153
|
+
* ```
|
|
154
|
+
*
|
|
155
|
+
* @returns A PdfDocumentBatch instance
|
|
156
|
+
*/
|
|
157
|
+
batch(): PdfDocumentBatch;
|
|
97
158
|
/**
|
|
98
159
|
* Packs non-stream indirect objects into compressed ObjStm containers.
|
|
99
160
|
* Objects that already have an object number are left unchanged.
|
|
@@ -289,8 +350,9 @@ export declare class PdfDocument extends PdfObject implements IPdfObjectResolver
|
|
|
289
350
|
private updateRevisions;
|
|
290
351
|
/**
|
|
291
352
|
* Performs a full update cycle to ensure all revisions are consistent and offsets are correct.
|
|
353
|
+
* @internal
|
|
292
354
|
*/
|
|
293
|
-
|
|
355
|
+
update(): void;
|
|
294
356
|
/**
|
|
295
357
|
* Walks all objects in the document and registers any newly created
|
|
296
358
|
* PdfIndirectObjects that are referenced but not yet part of the document
|
|
@@ -318,6 +380,20 @@ export declare class PdfDocument extends PdfObject implements IPdfObjectResolver
|
|
|
318
380
|
objects: object[];
|
|
319
381
|
}[];
|
|
320
382
|
};
|
|
383
|
+
/**
|
|
384
|
+
* Creates a new PdfDocument instance.
|
|
385
|
+
*
|
|
386
|
+
* @param options - Configuration options for the document
|
|
387
|
+
* @returns A new PdfDocument instance
|
|
388
|
+
*/
|
|
389
|
+
static newDocument(options?: {
|
|
390
|
+
revisions?: PdfRevision[];
|
|
391
|
+
version?: string | PdfComment;
|
|
392
|
+
password?: string;
|
|
393
|
+
ownerPassword?: string;
|
|
394
|
+
securityHandler?: PdfSecurityHandler;
|
|
395
|
+
signer?: PdfSigner;
|
|
396
|
+
}): PdfDocument;
|
|
321
397
|
/**
|
|
322
398
|
* Creates a PdfDocument from a byte stream.
|
|
323
399
|
*
|
|
@@ -337,3 +413,29 @@ export declare class PdfDocument extends PdfObject implements IPdfObjectResolver
|
|
|
337
413
|
*/
|
|
338
414
|
verifySignatures(): Promise<PdfDocumentVerificationResult>;
|
|
339
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* Batches multiple `add()` calls into a single update pass.
|
|
418
|
+
* Created via {@link PdfDocument.batch}.
|
|
419
|
+
*/
|
|
420
|
+
export declare class PdfDocumentBatch {
|
|
421
|
+
private _document;
|
|
422
|
+
private _completed;
|
|
423
|
+
private _addedObjects;
|
|
424
|
+
/** @internal */
|
|
425
|
+
constructor(document: PdfDocument);
|
|
426
|
+
/**
|
|
427
|
+
* Adds one or more objects to the document without triggering an update.
|
|
428
|
+
*
|
|
429
|
+
* @param objects - PDF objects to add
|
|
430
|
+
*/
|
|
431
|
+
add(...objects: PdfObject[]): this;
|
|
432
|
+
/**
|
|
433
|
+
* Commits the batch, running a single update pass for all added objects.
|
|
434
|
+
*/
|
|
435
|
+
commit(): void;
|
|
436
|
+
/**
|
|
437
|
+
* Rolls back the batch, removing all objects that were added and
|
|
438
|
+
* restoring the document to its state before the batch was created.
|
|
439
|
+
*/
|
|
440
|
+
rollback(): void;
|
|
441
|
+
}
|
package/dist/pdf/pdf-document.js
CHANGED
|
@@ -15,12 +15,13 @@ import { PdfByteOffsetToken } from '../core/tokens/byte-offset-token.js';
|
|
|
15
15
|
import { PdfNumberToken } from '../core/tokens/number-token.js';
|
|
16
16
|
import { PdfXRefTableEntryToken } from '../core/tokens/xref-table-entry-token.js';
|
|
17
17
|
import { PdfStartXRef } from '../core/objects/pdf-start-xref.js';
|
|
18
|
-
import { FoundCompressedObjectError } from '../errors.js';
|
|
18
|
+
import { FoundCompressedObjectError, PdfPasswordProtectedError, } from '../errors.js';
|
|
19
19
|
import { PdfReader } from './pdf-reader.js';
|
|
20
20
|
import { PdfSigner } from '../signing/signer.js';
|
|
21
21
|
import { concatUint8Arrays } from '../utils/concatUint8Arrays.js';
|
|
22
22
|
import { PdfAcroForm } from '../acroform/pdf-acro-form.js';
|
|
23
23
|
import { PdfPages } from './pdf-pages.js';
|
|
24
|
+
import { PdfObjectStream } from '../index.js';
|
|
24
25
|
/**
|
|
25
26
|
* Represents a PDF document with support for reading, writing, and modifying PDF files.
|
|
26
27
|
* Handles document structure, revisions, encryption, and digital signatures.
|
|
@@ -55,6 +56,8 @@ export class PdfDocument extends PdfObject {
|
|
|
55
56
|
_updating = false;
|
|
56
57
|
_finalized = false;
|
|
57
58
|
_signed = false;
|
|
59
|
+
/** @internal */
|
|
60
|
+
_batching = false;
|
|
58
61
|
/**
|
|
59
62
|
* Creates a new PDF document instance.
|
|
60
63
|
*
|
|
@@ -75,18 +78,18 @@ export class PdfDocument extends PdfObject {
|
|
|
75
78
|
else {
|
|
76
79
|
this.setVersion(options?.version ?? '2.0');
|
|
77
80
|
}
|
|
78
|
-
if (options?.password) {
|
|
79
|
-
this.setPassword(options.password);
|
|
80
|
-
}
|
|
81
|
-
if (options?.ownerPassword) {
|
|
82
|
-
this.setOwnerPassword(options.ownerPassword);
|
|
83
|
-
}
|
|
84
81
|
this.signer = options?.signer ?? new PdfSigner({ document: this });
|
|
85
82
|
this.linkRevisions();
|
|
86
83
|
this.wireResolvers(...this.objects.filter((x) => x instanceof PdfIndirectObject), ...this.revisions.map((rev) => rev.xref.trailerDict));
|
|
87
84
|
this.calculateOffsets();
|
|
88
85
|
this.originalSecurityHandler = options?.securityHandler;
|
|
89
86
|
this.resetSecurityHandler();
|
|
87
|
+
if (options?.password) {
|
|
88
|
+
this.setPassword(options.password);
|
|
89
|
+
}
|
|
90
|
+
if (options?.ownerPassword) {
|
|
91
|
+
this.setOwnerPassword(options.ownerPassword);
|
|
92
|
+
}
|
|
90
93
|
}
|
|
91
94
|
resolve(objectNumber, generationNumber) {
|
|
92
95
|
const cacheKey = `${objectNumber} ${generationNumber}`;
|
|
@@ -132,13 +135,16 @@ export class PdfDocument extends PdfObject {
|
|
|
132
135
|
this.revisions[0].header = comment;
|
|
133
136
|
}
|
|
134
137
|
/**
|
|
135
|
-
*
|
|
138
|
+
* Loads PDF objects into the document, organizing them into revisions.
|
|
136
139
|
* Parses objects into revisions based on EOF comments.
|
|
137
140
|
*
|
|
138
|
-
* @param objects - Array of PDF objects to
|
|
139
|
-
* @
|
|
141
|
+
* @param objects - Array of PDF objects to load into the document
|
|
142
|
+
* @param options - Optional security and mode configuration
|
|
143
|
+
* @param options.password - User password for encrypted documents
|
|
144
|
+
* @param options.ownerPassword - Owner password for encrypted documents
|
|
145
|
+
* @param options.incremental - Whether to use incremental mode
|
|
140
146
|
*/
|
|
141
|
-
|
|
147
|
+
async loadObjects(objects, options) {
|
|
142
148
|
let header;
|
|
143
149
|
const revisions = [];
|
|
144
150
|
let currentObjects = [];
|
|
@@ -156,7 +162,111 @@ export class PdfDocument extends PdfObject {
|
|
|
156
162
|
if (currentObjects.length > 0) {
|
|
157
163
|
revisions.push(new PdfRevision({ objects: currentObjects }));
|
|
158
164
|
}
|
|
159
|
-
|
|
165
|
+
this.revisions = revisions;
|
|
166
|
+
if (header) {
|
|
167
|
+
this.header = header;
|
|
168
|
+
}
|
|
169
|
+
this.linkRevisions();
|
|
170
|
+
this.wireResolvers(...this.objects.filter((x) => x instanceof PdfIndirectObject), ...this.revisions.map((rev) => rev.xref.trailerDict));
|
|
171
|
+
this.calculateOffsets();
|
|
172
|
+
// Reset security handler to detect and initialize encryption from the PDF
|
|
173
|
+
// Preserve any passwords that were set before load() was called
|
|
174
|
+
let presetPassword = options?.password;
|
|
175
|
+
let presetOwnerPassword = options?.ownerPassword;
|
|
176
|
+
if (this.securityHandler instanceof PdfStandardSecurityHandler) {
|
|
177
|
+
const pw = this.securityHandler.getPassword();
|
|
178
|
+
const opw = this.securityHandler.getOwnerPassword();
|
|
179
|
+
if (!presetPassword && pw.length > 0) {
|
|
180
|
+
presetPassword = new TextDecoder().decode(pw);
|
|
181
|
+
}
|
|
182
|
+
if (!presetOwnerPassword && opw) {
|
|
183
|
+
presetOwnerPassword = new TextDecoder().decode(opw);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
this.resetSecurityHandler();
|
|
187
|
+
// Apply passwords
|
|
188
|
+
if (presetPassword) {
|
|
189
|
+
this.setPassword(presetPassword);
|
|
190
|
+
}
|
|
191
|
+
if (presetOwnerPassword) {
|
|
192
|
+
this.setOwnerPassword(presetOwnerPassword);
|
|
193
|
+
}
|
|
194
|
+
// Handle encryption/decryption
|
|
195
|
+
let shouldDecrypt = Boolean(this.encryptionDictionary);
|
|
196
|
+
const hasExplicitPassword = !!presetPassword || !!presetOwnerPassword;
|
|
197
|
+
// If encrypted, verify the password is valid before attempting decryption
|
|
198
|
+
if (shouldDecrypt &&
|
|
199
|
+
this.securityHandler instanceof PdfStandardSecurityHandler) {
|
|
200
|
+
const valid = await this.securityHandler.testPassword();
|
|
201
|
+
if (!valid) {
|
|
202
|
+
if (!hasExplicitPassword) {
|
|
203
|
+
throw new PdfPasswordProtectedError();
|
|
204
|
+
}
|
|
205
|
+
this.resetSecurityHandler();
|
|
206
|
+
shouldDecrypt = false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (options?.incremental) {
|
|
210
|
+
// Lock revisions first to preserve the original bytes
|
|
211
|
+
// (including encrypted data) via cached tokens.
|
|
212
|
+
this.setIncremental(true);
|
|
213
|
+
// Then decrypt the live object data so built-in operations
|
|
214
|
+
// (AcroForm, fonts, etc.) can read it. The cached tokens
|
|
215
|
+
// still produce the original encrypted bytes on serialization.
|
|
216
|
+
if (shouldDecrypt) {
|
|
217
|
+
try {
|
|
218
|
+
await this.decryptObjects();
|
|
219
|
+
}
|
|
220
|
+
catch (e) {
|
|
221
|
+
if (!hasExplicitPassword) {
|
|
222
|
+
throw new PdfPasswordProtectedError();
|
|
223
|
+
}
|
|
224
|
+
this.resetSecurityHandler();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else if (shouldDecrypt) {
|
|
229
|
+
try {
|
|
230
|
+
await this.decryptObjects();
|
|
231
|
+
}
|
|
232
|
+
catch (e) {
|
|
233
|
+
if (!hasExplicitPassword) {
|
|
234
|
+
throw new PdfPasswordProtectedError();
|
|
235
|
+
}
|
|
236
|
+
this.resetSecurityHandler();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return this;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Loads a PDF document from a byte stream, parsing it into objects and revisions.
|
|
243
|
+
* @param input - Async or sync iterable of byte arrays representing the PDF file
|
|
244
|
+
* @param options - Optional security and mode configuration
|
|
245
|
+
* @returns A promise that resolves to the loaded PdfDocument instance
|
|
246
|
+
*/
|
|
247
|
+
async load(input, options) {
|
|
248
|
+
const objectStream = new PdfObjectStream(input);
|
|
249
|
+
const objects = [];
|
|
250
|
+
for await (const obj of objectStream) {
|
|
251
|
+
objects.push(obj);
|
|
252
|
+
}
|
|
253
|
+
return await this.loadObjects(objects, options);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Creates a PdfDocument from an array of PDF objects.
|
|
257
|
+
* Parses objects into revisions based on EOF comments.
|
|
258
|
+
*
|
|
259
|
+
* @param objects - Array of PDF objects to construct the document from
|
|
260
|
+
* @param options - Optional security and mode configuration
|
|
261
|
+
* @param options.password - User password for encrypted documents
|
|
262
|
+
* @param options.ownerPassword - Owner password for encrypted documents
|
|
263
|
+
* @param options.incremental - Whether to use incremental mode
|
|
264
|
+
* @returns A promise that resolves to the new PdfDocument instance
|
|
265
|
+
*/
|
|
266
|
+
static async fromObjects(objects, options) {
|
|
267
|
+
const document = new PdfDocument();
|
|
268
|
+
await document.loadObjects(objects, options);
|
|
269
|
+
return document;
|
|
160
270
|
}
|
|
161
271
|
/**
|
|
162
272
|
* Starts a new revision for incremental updates.
|
|
@@ -193,16 +303,54 @@ export class PdfDocument extends PdfObject {
|
|
|
193
303
|
}
|
|
194
304
|
this.latestRevision.addObject(obj);
|
|
195
305
|
}
|
|
196
|
-
// Auto-add any referenced-but-missing objects
|
|
197
|
-
|
|
198
|
-
if (
|
|
199
|
-
this.
|
|
306
|
+
// Auto-add any referenced-but-missing objects recursively in one pass
|
|
307
|
+
this.addAllMissingReferences(objects);
|
|
308
|
+
if (!this._batching) {
|
|
309
|
+
this.update();
|
|
200
310
|
}
|
|
201
|
-
this.update();
|
|
202
311
|
for (const obj of objects) {
|
|
203
312
|
obj.setModified(false);
|
|
204
313
|
}
|
|
205
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Recursively collects and adds all missing referenced objects in batches.
|
|
317
|
+
* More efficient than recursive add() calls since it collects all missing
|
|
318
|
+
* objects before processing them.
|
|
319
|
+
* @private
|
|
320
|
+
*/
|
|
321
|
+
addAllMissingReferences(objects) {
|
|
322
|
+
let batch = this.collectMissingReferences(...objects);
|
|
323
|
+
while (batch.length > 0) {
|
|
324
|
+
for (const obj of batch) {
|
|
325
|
+
this.wireResolvers(obj);
|
|
326
|
+
if (!this.hasObjectInLatestRevision(obj)) {
|
|
327
|
+
this.latestRevision.addObject(obj);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// Check if these newly added objects reference more missing objects
|
|
331
|
+
batch = this.collectMissingReferences(...batch);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Creates a batch for adding multiple objects with a single update pass.
|
|
336
|
+
* This is significantly faster than calling `add()` multiple times when
|
|
337
|
+
* adding objects with many sub-references (e.g. fonts with descriptors,
|
|
338
|
+
* CIDToGIDMap streams, ToUnicode CMaps).
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ```typescript
|
|
342
|
+
* const batch = document.batch()
|
|
343
|
+
* batch.add(font1)
|
|
344
|
+
* batch.add(font2)
|
|
345
|
+
* batch.add(imageStream)
|
|
346
|
+
* batch.commit()
|
|
347
|
+
* ```
|
|
348
|
+
*
|
|
349
|
+
* @returns A PdfDocumentBatch instance
|
|
350
|
+
*/
|
|
351
|
+
batch() {
|
|
352
|
+
return new PdfDocumentBatch(this);
|
|
353
|
+
}
|
|
206
354
|
/**
|
|
207
355
|
* Packs non-stream indirect objects into compressed ObjStm containers.
|
|
208
356
|
* Objects that already have an object number are left unchanged.
|
|
@@ -826,9 +974,9 @@ export class PdfDocument extends PdfObject {
|
|
|
826
974
|
}
|
|
827
975
|
}
|
|
828
976
|
}
|
|
829
|
-
linkOffsets() {
|
|
977
|
+
linkOffsets(tokens) {
|
|
830
978
|
const refMap = new Map();
|
|
831
|
-
|
|
979
|
+
tokens = tokens ?? this.toTokens();
|
|
832
980
|
for (let i = 0; i < tokens.length; i++) {
|
|
833
981
|
const token = tokens[i];
|
|
834
982
|
let main;
|
|
@@ -862,11 +1010,13 @@ export class PdfDocument extends PdfObject {
|
|
|
862
1010
|
}
|
|
863
1011
|
}
|
|
864
1012
|
}
|
|
865
|
-
calculateOffsets() {
|
|
1013
|
+
calculateOffsets(cachedTokens) {
|
|
866
1014
|
const serializer = new PdfTokenSerializer();
|
|
867
|
-
|
|
1015
|
+
const tokens = cachedTokens ?? this.toTokens();
|
|
1016
|
+
this.linkOffsets(tokens);
|
|
1017
|
+
serializer.feedMany(tokens);
|
|
868
1018
|
serializer.calculateOffsets();
|
|
869
|
-
|
|
1019
|
+
return tokens;
|
|
870
1020
|
}
|
|
871
1021
|
updateRevisions() {
|
|
872
1022
|
let modified = false;
|
|
@@ -890,6 +1040,7 @@ export class PdfDocument extends PdfObject {
|
|
|
890
1040
|
}
|
|
891
1041
|
/**
|
|
892
1042
|
* Performs a full update cycle to ensure all revisions are consistent and offsets are correct.
|
|
1043
|
+
* @internal
|
|
893
1044
|
*/
|
|
894
1045
|
update() {
|
|
895
1046
|
if (this._updating)
|
|
@@ -899,17 +1050,17 @@ export class PdfDocument extends PdfObject {
|
|
|
899
1050
|
this.commitIncrementalUpdates();
|
|
900
1051
|
this.flushResolvedCache();
|
|
901
1052
|
this.registerNewReferences();
|
|
1053
|
+
// First pass: generate tokens and calculate offsets
|
|
902
1054
|
this.calculateOffsets();
|
|
903
1055
|
this.updateRevisions();
|
|
904
1056
|
// Second pass: xref binary may have changed size (e.g. FlateDecode removed
|
|
905
|
-
// from xref stream), shifting objects that follow it.
|
|
906
|
-
//
|
|
907
|
-
|
|
908
|
-
this.calculateOffsets();
|
|
1057
|
+
// from xref stream), shifting objects that follow it. Regenerate tokens
|
|
1058
|
+
// to capture xref changes, then recalculate offsets.
|
|
1059
|
+
const tokens = this.calculateOffsets();
|
|
909
1060
|
this.updateRevisions();
|
|
910
|
-
// Third pass:
|
|
911
|
-
// change
|
|
912
|
-
this.calculateOffsets();
|
|
1061
|
+
// Third pass: reuse tokens from second pass since xref should now be stable
|
|
1062
|
+
// (xref binary size should not change because W widths and entry count are same).
|
|
1063
|
+
this.calculateOffsets(tokens);
|
|
913
1064
|
}
|
|
914
1065
|
finally {
|
|
915
1066
|
this._updating = false;
|
|
@@ -961,11 +1112,13 @@ export class PdfDocument extends PdfObject {
|
|
|
961
1112
|
*/
|
|
962
1113
|
cloneImpl() {
|
|
963
1114
|
const clonedRevisions = this.revisions.map((rev) => rev.clone());
|
|
964
|
-
|
|
1115
|
+
const cloned = new PdfDocument({
|
|
965
1116
|
revisions: clonedRevisions,
|
|
966
1117
|
version: this.header?.clone(),
|
|
967
|
-
securityHandler: this.securityHandler,
|
|
1118
|
+
securityHandler: this.securityHandler?.clone(),
|
|
968
1119
|
});
|
|
1120
|
+
cloned.hasEncryptionDictionary = this.hasEncryptionDictionary;
|
|
1121
|
+
return cloned;
|
|
969
1122
|
}
|
|
970
1123
|
toJSON() {
|
|
971
1124
|
return {
|
|
@@ -973,6 +1126,15 @@ export class PdfDocument extends PdfObject {
|
|
|
973
1126
|
revisions: this.revisions.map((rev) => rev.toJSON()),
|
|
974
1127
|
};
|
|
975
1128
|
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Creates a new PdfDocument instance.
|
|
1131
|
+
*
|
|
1132
|
+
* @param options - Configuration options for the document
|
|
1133
|
+
* @returns A new PdfDocument instance
|
|
1134
|
+
*/
|
|
1135
|
+
static newDocument(options) {
|
|
1136
|
+
return new PdfDocument(options);
|
|
1137
|
+
}
|
|
976
1138
|
/**
|
|
977
1139
|
* Creates a PdfDocument from a byte stream.
|
|
978
1140
|
*
|
|
@@ -994,3 +1156,56 @@ export class PdfDocument extends PdfObject {
|
|
|
994
1156
|
return await this.signer.verify();
|
|
995
1157
|
}
|
|
996
1158
|
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Batches multiple `add()` calls into a single update pass.
|
|
1161
|
+
* Created via {@link PdfDocument.batch}.
|
|
1162
|
+
*/
|
|
1163
|
+
export class PdfDocumentBatch {
|
|
1164
|
+
_document;
|
|
1165
|
+
_completed = false;
|
|
1166
|
+
_addedObjects = [];
|
|
1167
|
+
/** @internal */
|
|
1168
|
+
constructor(document) {
|
|
1169
|
+
this._document = document;
|
|
1170
|
+
this._document._batching = true;
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Adds one or more objects to the document without triggering an update.
|
|
1174
|
+
*
|
|
1175
|
+
* @param objects - PDF objects to add
|
|
1176
|
+
*/
|
|
1177
|
+
add(...objects) {
|
|
1178
|
+
if (this._completed) {
|
|
1179
|
+
throw new Error('Batch already completed (committed or rolled back)');
|
|
1180
|
+
}
|
|
1181
|
+
this._addedObjects.push(...objects);
|
|
1182
|
+
this._document.add(...objects);
|
|
1183
|
+
return this;
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Commits the batch, running a single update pass for all added objects.
|
|
1187
|
+
*/
|
|
1188
|
+
commit() {
|
|
1189
|
+
if (this._completed) {
|
|
1190
|
+
throw new Error('Batch already completed (committed or rolled back)');
|
|
1191
|
+
}
|
|
1192
|
+
this._completed = true;
|
|
1193
|
+
this._document._batching = false;
|
|
1194
|
+
this._document.update();
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Rolls back the batch, removing all objects that were added and
|
|
1198
|
+
* restoring the document to its state before the batch was created.
|
|
1199
|
+
*/
|
|
1200
|
+
rollback() {
|
|
1201
|
+
if (this._completed) {
|
|
1202
|
+
throw new Error('Batch already completed (committed or rolled back)');
|
|
1203
|
+
}
|
|
1204
|
+
this._completed = true;
|
|
1205
|
+
this._document._batching = false;
|
|
1206
|
+
// Remove all objects that were added during this batch
|
|
1207
|
+
for (const obj of this._addedObjects) {
|
|
1208
|
+
this._document.deleteObject(obj);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
package/dist/pdf/pdf-reader.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { PdfObjectStream } from '../core/streams/object-stream.js';
|
|
2
1
|
import { PdfDocument } from './pdf-document.js';
|
|
3
2
|
/**
|
|
4
3
|
* A reader for parsing PDF data into PdfDocument instances.
|
|
@@ -31,7 +30,7 @@ export class PdfReader {
|
|
|
31
30
|
for await (const obj of this.objectStream) {
|
|
32
31
|
objects.push(obj);
|
|
33
32
|
}
|
|
34
|
-
return PdfDocument.fromObjects(objects);
|
|
33
|
+
return await PdfDocument.fromObjects(objects);
|
|
35
34
|
}
|
|
36
35
|
/**
|
|
37
36
|
* Creates a PdfDocument directly from a byte stream.
|
|
@@ -41,41 +40,8 @@ export class PdfReader {
|
|
|
41
40
|
* @returns A promise that resolves to the parsed PdfDocument
|
|
42
41
|
*/
|
|
43
42
|
static async fromBytes(input, options) {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
let shouldDecrypt = Boolean(document.encryptionDictionary);
|
|
47
|
-
if (typeof options?.password === 'string') {
|
|
48
|
-
document.setPassword(options.password);
|
|
49
|
-
shouldDecrypt = true;
|
|
50
|
-
}
|
|
51
|
-
if (typeof options?.ownerPassword === 'string') {
|
|
52
|
-
document.setOwnerPassword(options.ownerPassword);
|
|
53
|
-
shouldDecrypt = true;
|
|
54
|
-
}
|
|
55
|
-
if (options?.incremental) {
|
|
56
|
-
// Lock revisions first to preserve the original bytes
|
|
57
|
-
// (including encrypted data) via cached tokens.
|
|
58
|
-
document.setIncremental(true);
|
|
59
|
-
// Then decrypt the live object data so built-in operations
|
|
60
|
-
// (AcroForm, fonts, etc.) can read it. The cached tokens
|
|
61
|
-
// still produce the original encrypted bytes on serialization.
|
|
62
|
-
if (shouldDecrypt) {
|
|
63
|
-
try {
|
|
64
|
-
await document.decryptObjects();
|
|
65
|
-
}
|
|
66
|
-
catch (e) {
|
|
67
|
-
document.resetSecurityHandler();
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
else if (shouldDecrypt) {
|
|
72
|
-
try {
|
|
73
|
-
await document.decryptObjects();
|
|
74
|
-
}
|
|
75
|
-
catch (e) {
|
|
76
|
-
document.resetSecurityHandler();
|
|
77
|
-
}
|
|
78
|
-
}
|
|
43
|
+
const document = new PdfDocument();
|
|
44
|
+
await document.load(input, options);
|
|
79
45
|
return document;
|
|
80
46
|
}
|
|
81
47
|
}
|
|
@@ -116,6 +116,11 @@ export declare abstract class PdfSecurityHandler {
|
|
|
116
116
|
* Writes the encryption dictionary with computed keys.
|
|
117
117
|
*/
|
|
118
118
|
abstract write(): Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* Tests whether the current password can decrypt this document.
|
|
121
|
+
* Returns true if the password is valid, false otherwise.
|
|
122
|
+
*/
|
|
123
|
+
abstract testPassword(): Promise<boolean>;
|
|
119
124
|
/**
|
|
120
125
|
* Builds the numeric permission flags from a PdfPermissions object.
|
|
121
126
|
*
|
|
@@ -123,6 +128,12 @@ export declare abstract class PdfSecurityHandler {
|
|
|
123
128
|
* @returns The numeric permission flags.
|
|
124
129
|
*/
|
|
125
130
|
protected buildPermissions(perm: PdfPermissions): number;
|
|
131
|
+
/**
|
|
132
|
+
* Creates a shallow clone of this security handler with an independent
|
|
133
|
+
* encryption dictionary, so that mutating the clone (e.g. during
|
|
134
|
+
* finalize/encrypt) does not affect the original.
|
|
135
|
+
*/
|
|
136
|
+
clone(): this;
|
|
126
137
|
/**
|
|
127
138
|
* Recursively decrypts all strings and streams within an indirect object.
|
|
128
139
|
*
|
|
@@ -213,6 +224,11 @@ export declare abstract class PdfStandardSecurityHandler extends PdfSecurityHand
|
|
|
213
224
|
* @returns The document ID, or undefined if not set.
|
|
214
225
|
*/
|
|
215
226
|
getDocumentId(): PdfId | undefined;
|
|
227
|
+
/**
|
|
228
|
+
* Tests whether the current password can decrypt this document.
|
|
229
|
+
* Attempts to compute the master key and returns true if successful.
|
|
230
|
+
*/
|
|
231
|
+
testPassword(): Promise<boolean>;
|
|
216
232
|
/**
|
|
217
233
|
* Sets the user password.
|
|
218
234
|
*
|
|
@@ -225,6 +241,18 @@ export declare abstract class PdfStandardSecurityHandler extends PdfSecurityHand
|
|
|
225
241
|
* @param ownerPassword - The owner password string or bytes.
|
|
226
242
|
*/
|
|
227
243
|
setOwnerPassword(ownerPassword: string | ByteArray): void;
|
|
244
|
+
/**
|
|
245
|
+
* Gets the user password.
|
|
246
|
+
*
|
|
247
|
+
* @returns The user password as bytes.
|
|
248
|
+
*/
|
|
249
|
+
getPassword(): ByteArray;
|
|
250
|
+
/**
|
|
251
|
+
* Gets the owner password.
|
|
252
|
+
*
|
|
253
|
+
* @returns The owner password as bytes, or undefined if not set.
|
|
254
|
+
*/
|
|
255
|
+
getOwnerPassword(): ByteArray | undefined;
|
|
228
256
|
/**
|
|
229
257
|
* Checks if metadata encryption is enabled.
|
|
230
258
|
*
|
|
@@ -243,6 +271,13 @@ export declare abstract class PdfStandardSecurityHandler extends PdfSecurityHand
|
|
|
243
271
|
* @returns The computed user key.
|
|
244
272
|
*/
|
|
245
273
|
protected abstract computeUserKey(): Promise<ByteArray>;
|
|
274
|
+
/**
|
|
275
|
+
* Computes the master encryption key from the password.
|
|
276
|
+
*
|
|
277
|
+
* @returns The computed master key.
|
|
278
|
+
* @throws Error if the password is incorrect or required parameters are missing.
|
|
279
|
+
*/
|
|
280
|
+
protected abstract computeMasterKey(): Promise<ByteArray>;
|
|
246
281
|
/**
|
|
247
282
|
* Computes the owner key (O value) for the encryption dictionary.
|
|
248
283
|
*
|
|
@@ -69,6 +69,17 @@ export class PdfSecurityHandler {
|
|
|
69
69
|
}
|
|
70
70
|
return flags | 0xfffff000; // ensure unused high bits are set
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Creates a shallow clone of this security handler with an independent
|
|
74
|
+
* encryption dictionary, so that mutating the clone (e.g. during
|
|
75
|
+
* finalize/encrypt) does not affect the original.
|
|
76
|
+
*/
|
|
77
|
+
clone() {
|
|
78
|
+
const cloned = Object.create(Object.getPrototypeOf(this));
|
|
79
|
+
Object.assign(cloned, this);
|
|
80
|
+
cloned.dict = this.dict.clone();
|
|
81
|
+
return cloned;
|
|
82
|
+
}
|
|
72
83
|
/**
|
|
73
84
|
* Recursively decrypts all strings and streams within an indirect object.
|
|
74
85
|
*
|
|
@@ -241,6 +252,19 @@ export class PdfStandardSecurityHandler extends PdfSecurityHandler {
|
|
|
241
252
|
getDocumentId() {
|
|
242
253
|
return this.documentId;
|
|
243
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Tests whether the current password can decrypt this document.
|
|
257
|
+
* Attempts to compute the master key and returns true if successful.
|
|
258
|
+
*/
|
|
259
|
+
async testPassword() {
|
|
260
|
+
try {
|
|
261
|
+
await this.computeMasterKey();
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
244
268
|
/**
|
|
245
269
|
* Sets the user password.
|
|
246
270
|
*
|
|
@@ -261,6 +285,22 @@ export class PdfStandardSecurityHandler extends PdfSecurityHandler {
|
|
|
261
285
|
? stringToBytes(ownerPassword)
|
|
262
286
|
: ownerPassword;
|
|
263
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* Gets the user password.
|
|
290
|
+
*
|
|
291
|
+
* @returns The user password as bytes.
|
|
292
|
+
*/
|
|
293
|
+
getPassword() {
|
|
294
|
+
return this.password;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Gets the owner password.
|
|
298
|
+
*
|
|
299
|
+
* @returns The owner password as bytes, or undefined if not set.
|
|
300
|
+
*/
|
|
301
|
+
getOwnerPassword() {
|
|
302
|
+
return this.ownerPassword;
|
|
303
|
+
}
|
|
264
304
|
/**
|
|
265
305
|
* Checks if metadata encryption is enabled.
|
|
266
306
|
*
|
|
@@ -36,6 +36,7 @@ export declare class PdfPublicKeySecurityHandler extends PdfSecurityHandler {
|
|
|
36
36
|
permissions?: PdfPermissions | number;
|
|
37
37
|
encryptMetadata?: boolean;
|
|
38
38
|
});
|
|
39
|
+
clone(): this;
|
|
39
40
|
/**
|
|
40
41
|
* Gets the security handler filter name.
|
|
41
42
|
*
|
|
@@ -66,6 +67,7 @@ export declare class PdfPublicKeySecurityHandler extends PdfSecurityHandler {
|
|
|
66
67
|
* @returns True if the underlying handler is ready.
|
|
67
68
|
*/
|
|
68
69
|
isReady(): boolean;
|
|
70
|
+
testPassword(): Promise<boolean>;
|
|
69
71
|
/**
|
|
70
72
|
* Gets the encryption version number.
|
|
71
73
|
*
|
|
@@ -50,6 +50,11 @@ export class PdfPublicKeySecurityHandler extends PdfSecurityHandler {
|
|
|
50
50
|
pkcs7Input[23] = this.permissions & 0xff;
|
|
51
51
|
this.recipientsCms = this.getRecipientsPkcs7(pkcs7Input);
|
|
52
52
|
}
|
|
53
|
+
clone() {
|
|
54
|
+
const cloned = super.clone();
|
|
55
|
+
cloned.standardSecurityHandler = this.standardSecurityHandler.clone();
|
|
56
|
+
return cloned;
|
|
57
|
+
}
|
|
53
58
|
/**
|
|
54
59
|
* Gets the security handler filter name.
|
|
55
60
|
*
|
|
@@ -90,6 +95,9 @@ export class PdfPublicKeySecurityHandler extends PdfSecurityHandler {
|
|
|
90
95
|
isReady() {
|
|
91
96
|
return this.standardSecurityHandler.isReady();
|
|
92
97
|
}
|
|
98
|
+
async testPassword() {
|
|
99
|
+
return this.standardSecurityHandler.testPassword();
|
|
100
|
+
}
|
|
93
101
|
/**
|
|
94
102
|
* Gets the encryption version number.
|
|
95
103
|
*
|
|
@@ -84,6 +84,16 @@ export class PdfV4SecurityHandler extends PdfV2SecurityHandler {
|
|
|
84
84
|
filter.setSecurityHandler(this);
|
|
85
85
|
this.cryptFilters.set(name, filter);
|
|
86
86
|
}
|
|
87
|
+
clone() {
|
|
88
|
+
const cloned = super.clone();
|
|
89
|
+
cloned.cryptFilters = new Map();
|
|
90
|
+
cloned.cryptFiltersByType = { ...this.cryptFiltersByType };
|
|
91
|
+
for (const [name, filter] of this.cryptFilters) {
|
|
92
|
+
cloned.cryptFilters.set(name, filter);
|
|
93
|
+
filter.setSecurityHandler(cloned);
|
|
94
|
+
}
|
|
95
|
+
return cloned;
|
|
96
|
+
}
|
|
87
97
|
/**
|
|
88
98
|
* Gets the encryption revision number.
|
|
89
99
|
*
|