pdf-lite 1.2.1 → 1.3.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.
Files changed (52) hide show
  1. package/EXAMPLES.md +1 -1
  2. package/dist/acroform/acroform.d.ts +272 -16
  3. package/dist/acroform/acroform.js +1084 -144
  4. package/dist/acroform/manager.d.ts +2 -2
  5. package/dist/acroform/manager.js +3 -3
  6. package/dist/core/decoder.d.ts +1 -1
  7. package/dist/core/decoder.js +3 -3
  8. package/dist/core/index.d.ts +2 -2
  9. package/dist/core/index.js +2 -2
  10. package/dist/core/objects/pdf-array.d.ts +1 -0
  11. package/dist/core/objects/pdf-array.js +4 -0
  12. package/dist/core/objects/pdf-dictionary.d.ts +1 -0
  13. package/dist/core/objects/pdf-dictionary.js +12 -0
  14. package/dist/core/objects/pdf-hexadecimal.d.ts +9 -2
  15. package/dist/core/objects/pdf-hexadecimal.js +25 -5
  16. package/dist/core/objects/pdf-indirect-object.d.ts +5 -3
  17. package/dist/core/objects/pdf-indirect-object.js +23 -5
  18. package/dist/core/objects/pdf-number.js +3 -0
  19. package/dist/core/objects/pdf-object.d.ts +6 -0
  20. package/dist/core/objects/pdf-object.js +10 -0
  21. package/dist/core/objects/pdf-stream.js +3 -0
  22. package/dist/core/objects/pdf-string.d.ts +11 -1
  23. package/dist/core/objects/pdf-string.js +24 -6
  24. package/dist/core/ref.d.ts +5 -0
  25. package/dist/core/ref.js +14 -0
  26. package/dist/core/serializer.d.ts +1 -1
  27. package/dist/core/serializer.js +1 -1
  28. package/dist/core/tokeniser.d.ts +2 -2
  29. package/dist/core/tokeniser.js +37 -75
  30. package/dist/core/tokens/hexadecimal-token.d.ts +8 -1
  31. package/dist/core/tokens/hexadecimal-token.js +20 -2
  32. package/dist/core/tokens/name-token.js +0 -3
  33. package/dist/core/tokens/string-token.d.ts +8 -1
  34. package/dist/core/tokens/string-token.js +20 -2
  35. package/dist/fonts/font-manager.js +6 -8
  36. package/dist/pdf/pdf-document.d.ts +12 -11
  37. package/dist/pdf/pdf-document.js +50 -42
  38. package/dist/pdf/pdf-revision.d.ts +33 -4
  39. package/dist/pdf/pdf-revision.js +100 -26
  40. package/dist/pdf/pdf-xref-lookup.js +3 -2
  41. package/dist/utils/decodeWithFontEncoding.d.ts +20 -0
  42. package/dist/utils/decodeWithFontEncoding.js +67 -0
  43. package/dist/utils/escapeString.d.ts +1 -1
  44. package/dist/utils/escapeString.js +12 -3
  45. package/dist/utils/glyphNameToUnicode.d.ts +10 -0
  46. package/dist/utils/glyphNameToUnicode.js +4292 -0
  47. package/dist/xfa/manager.js +2 -4
  48. package/package.json +1 -1
  49. /package/dist/core/{incremental-parser.d.ts → parser/incremental-parser.d.ts} +0 -0
  50. /package/dist/core/{incremental-parser.js → parser/incremental-parser.js} +0 -0
  51. /package/dist/core/{parser.d.ts → parser/parser.d.ts} +0 -0
  52. /package/dist/core/{parser.js → parser/parser.js} +0 -0
@@ -1,6 +1,7 @@
1
1
  import { assert } from '../utils/assert.js';
2
2
  import { bytesToString } from '../utils/bytesToString.js';
3
- import { IncrementalParser } from './incremental-parser.js';
3
+ import { unescapeString } from '../utils/unescapeString.js';
4
+ import { IncrementalParser } from './parser/incremental-parser.js';
4
5
  import { PdfBooleanToken } from './tokens/boolean-token.js';
5
6
  import { PdfCommentToken } from './tokens/comment-token.js';
6
7
  import { PdfEndArrayToken } from './tokens/end-array-token.js';
@@ -24,7 +25,7 @@ import { PdfWhitespaceToken } from './tokens/whitespace-token.js';
24
25
  import { PdfXRefTableEntryToken } from './tokens/xref-table-entry-token.js';
25
26
  import { PdfXRefTableSectionStartToken } from './tokens/xref-table-section-start-token.js';
26
27
  import { PdfXRefTableStartToken } from './tokens/xref-table-start-token.js';
27
- import { Parser } from './parser.js';
28
+ import { Parser } from './parser/parser.js';
28
29
  import { concatUint8Arrays } from '../utils/concatUint8Arrays.js';
29
30
  import { stringToBytes } from '../utils/stringToBytes.js';
30
31
  const ByteMap = {
@@ -128,7 +129,8 @@ export class PdfByteStreamTokeniser extends IncrementalParser {
128
129
  nameBytes.push(this.next());
129
130
  byte = this.peek();
130
131
  }
131
- return new PdfNameToken(bytesToString(new Uint8Array(nameBytes)));
132
+ const name = bytesToString(new Uint8Array(nameBytes));
133
+ return new PdfNameToken(name);
132
134
  }
133
135
  nextDictionaryEndToken() {
134
136
  this.expect(ByteMap.RIGHT_ANGLE_BRACKET);
@@ -136,6 +138,8 @@ export class PdfByteStreamTokeniser extends IncrementalParser {
136
138
  return new PdfEndDictionaryToken();
137
139
  }
138
140
  nextHexadecimalToken() {
141
+ // Capture starting position (before the opening angle bracket)
142
+ const startIndex = this.bufferIndex;
139
143
  this.expect(ByteMap.LEFT_ANGLE_BRACKET);
140
144
  const hexBytes = [];
141
145
  let byte = this.peek();
@@ -146,7 +150,10 @@ export class PdfByteStreamTokeniser extends IncrementalParser {
146
150
  byte = this.peek();
147
151
  }
148
152
  this.expect(ByteMap.RIGHT_ANGLE_BRACKET);
149
- return new PdfHexadecimalToken(new Uint8Array(hexBytes));
153
+ // Capture original bytes including angle brackets for incremental updates
154
+ const endIndex = this.bufferIndex; // After the closing angle bracket
155
+ const originalBytes = new Uint8Array(this.buffer.slice(startIndex, endIndex));
156
+ return new PdfHexadecimalToken(new Uint8Array(hexBytes), originalBytes);
150
157
  }
151
158
  nextNumberToken() {
152
159
  const numberBytes = [];
@@ -187,8 +194,11 @@ export class PdfByteStreamTokeniser extends IncrementalParser {
187
194
  return new PdfEndArrayToken();
188
195
  }
189
196
  nextStringToken() {
197
+ // Capture starting position (before the opening parenthesis)
198
+ const startIndex = this.bufferIndex;
190
199
  this.expect(ByteMap.LEFT_PARENTHESIS);
191
- const stringBytes = [];
200
+ // Collect raw bytes until we find the matching closing parenthesis
201
+ const rawBytes = [];
192
202
  let nesting = 1;
193
203
  let inEscape = false;
194
204
  while (inEscape || nesting > 0) {
@@ -196,82 +206,34 @@ export class PdfByteStreamTokeniser extends IncrementalParser {
196
206
  if (byte === null) {
197
207
  throw new Error('Unexpected end of input in string token');
198
208
  }
199
- if (byte === ByteMap.LEFT_PARENTHESIS) {
200
- nesting++;
201
- }
202
- else if (byte === ByteMap.RIGHT_PARENTHESIS) {
203
- nesting--;
204
- if (nesting === 0) {
205
- break;
206
- }
207
- }
208
- else if (byte === ByteMap.BACKSLASH || inEscape) {
209
- inEscape = true;
210
- const next = this.next();
211
- if (next === null) {
212
- throw new Error('Unexpected end of input in string token');
209
+ // Add byte to rawBytes first (including the closing parenthesis)
210
+ rawBytes.push(byte);
211
+ // Track nesting level for proper parenthesis matching
212
+ if (!inEscape) {
213
+ if (byte === ByteMap.LEFT_PARENTHESIS) {
214
+ nesting++;
213
215
  }
214
- switch (next) {
215
- case ByteMap.n:
216
- stringBytes.push(0x0a);
217
- break; // \n
218
- case ByteMap.r:
219
- stringBytes.push(0x0d);
220
- break; // \r
221
- case ByteMap.t:
222
- stringBytes.push(0x09);
223
- break; // \t
224
- case ByteMap.b:
225
- stringBytes.push(0x08);
226
- break; // \b
227
- case ByteMap.f:
228
- stringBytes.push(0x0c);
229
- break; // \f
230
- case ByteMap.LEFT_PARENTHESIS:
231
- stringBytes.push(ByteMap.LEFT_PARENTHESIS);
232
- break; // \(
233
- case ByteMap.RIGHT_PARENTHESIS:
234
- stringBytes.push(ByteMap.RIGHT_PARENTHESIS);
235
- break; // \)
236
- case ByteMap.BACKSLASH:
237
- stringBytes.push(ByteMap.BACKSLASH);
238
- break; // \\
239
- case ByteMap.LINE_FEED: // Line feed
240
- case ByteMap.CARRIAGE_RETURN: // Carriage return
241
- stringBytes.push(next);
242
- break;
243
- default:
244
- if (PdfByteStreamTokeniser.isOctet(next)) {
245
- let octal = String.fromCharCode(next);
246
- // Octal: up to 3 digits
247
- const next2 = this.peek();
248
- if (next2 === null) {
249
- throw new Error('Unexpected end of input in string token');
250
- }
251
- if (PdfByteStreamTokeniser.isOctet(next2)) {
252
- octal += String.fromCharCode(this.next());
253
- }
254
- const next3 = this.peek();
255
- if (next3 === null) {
256
- throw new Error('Unexpected end of input in string token');
257
- }
258
- if (PdfByteStreamTokeniser.isOctet(next3)) {
259
- octal += String.fromCharCode(this.next());
260
- }
261
- stringBytes.push(parseInt(octal, 8));
262
- }
263
- else {
264
- // If it's not a valid escape sequence, just add the next byte
265
- stringBytes.push(next);
266
- }
216
+ else if (byte === ByteMap.RIGHT_PARENTHESIS) {
217
+ nesting--;
218
+ if (nesting === 0) {
267
219
  break;
220
+ }
221
+ }
222
+ else if (byte === ByteMap.BACKSLASH) {
223
+ inEscape = true;
268
224
  }
225
+ }
226
+ else {
269
227
  inEscape = false;
270
- continue;
271
228
  }
272
- stringBytes.push(byte);
273
229
  }
274
- return new PdfStringToken(new Uint8Array(stringBytes));
230
+ // Capture original bytes including parentheses for incremental updates
231
+ const endIndex = this.bufferIndex; // After the closing parenthesis
232
+ const originalBytes = new Uint8Array(this.buffer.slice(startIndex, endIndex));
233
+ // Use unescapeString utility to process escape sequences
234
+ // unescapeString expects bytes including the closing parenthesis
235
+ const unescapedBytes = unescapeString(new Uint8Array(rawBytes));
236
+ return new PdfStringToken(unescapedBytes, originalBytes);
275
237
  }
276
238
  nextEndObjectToken() {
277
239
  this.expect(ByteMap.e);
@@ -2,6 +2,13 @@ import { ByteArray } from '../../types.js';
2
2
  import { PdfToken } from './token.js';
3
3
  export declare class PdfHexadecimalToken extends PdfToken {
4
4
  raw: ByteArray;
5
- constructor(hexadecimal: string | ByteArray);
5
+ /**
6
+ * Original bytes from the PDF file, including angle brackets.
7
+ * Used to preserve exact formatting for incremental updates.
8
+ * @internal - Non-enumerable to avoid affecting test comparisons
9
+ */
10
+ private _originalBytes?;
11
+ constructor(hexadecimal: string | ByteArray, originalBytes?: ByteArray);
12
+ get originalBytes(): ByteArray | undefined;
6
13
  private static toBytes;
7
14
  }
@@ -2,12 +2,30 @@ import { stringToBytes } from '../../utils/stringToBytes.js';
2
2
  import { PdfToken } from './token.js';
3
3
  export class PdfHexadecimalToken extends PdfToken {
4
4
  raw;
5
- constructor(hexadecimal) {
6
- super(PdfHexadecimalToken.toBytes(hexadecimal));
5
+ /**
6
+ * Original bytes from the PDF file, including angle brackets.
7
+ * Used to preserve exact formatting for incremental updates.
8
+ * @internal - Non-enumerable to avoid affecting test comparisons
9
+ */
10
+ _originalBytes;
11
+ constructor(hexadecimal, originalBytes) {
12
+ super(originalBytes ?? PdfHexadecimalToken.toBytes(hexadecimal));
7
13
  this.raw =
8
14
  typeof hexadecimal === 'string'
9
15
  ? stringToBytes(hexadecimal)
10
16
  : hexadecimal;
17
+ if (originalBytes) {
18
+ // Make non-enumerable so it doesn't affect .toEqual() comparisons
19
+ Object.defineProperty(this, '_originalBytes', {
20
+ value: originalBytes,
21
+ writable: true,
22
+ enumerable: false,
23
+ configurable: true,
24
+ });
25
+ }
26
+ }
27
+ get originalBytes() {
28
+ return this._originalBytes;
11
29
  }
12
30
  static toBytes(hexadecimal) {
13
31
  const bytes = stringToBytes(hexadecimal);
@@ -3,9 +3,6 @@ import { PdfToken } from './token.js';
3
3
  export class PdfNameToken extends PdfToken {
4
4
  name;
5
5
  constructor(name) {
6
- if (typeof name !== 'string' || name.length === 0) {
7
- throw new Error('PdfNameToken name must be a non-empty string');
8
- }
9
6
  super(PdfNameToken.toBytes(name));
10
7
  this.name = name;
11
8
  }
@@ -2,6 +2,13 @@ import { ByteArray } from '../../types.js';
2
2
  import { PdfToken } from './token.js';
3
3
  export declare class PdfStringToken extends PdfToken {
4
4
  value: ByteArray;
5
- constructor(value: string | ByteArray);
5
+ /**
6
+ * Original bytes from the PDF file, including parentheses and escape sequences.
7
+ * Used to preserve exact formatting for incremental updates.
8
+ * @internal - Non-enumerable to avoid affecting test comparisons
9
+ */
10
+ private _originalBytes?;
11
+ constructor(value: string | ByteArray, originalBytes?: ByteArray);
12
+ get originalBytes(): ByteArray | undefined;
6
13
  private static toBytes;
7
14
  }
@@ -3,9 +3,27 @@ import { stringToBytes } from '../../utils/stringToBytes.js';
3
3
  import { PdfToken } from './token.js';
4
4
  export class PdfStringToken extends PdfToken {
5
5
  value;
6
- constructor(value) {
7
- super(PdfStringToken.toBytes(value));
6
+ /**
7
+ * Original bytes from the PDF file, including parentheses and escape sequences.
8
+ * Used to preserve exact formatting for incremental updates.
9
+ * @internal - Non-enumerable to avoid affecting test comparisons
10
+ */
11
+ _originalBytes;
12
+ constructor(value, originalBytes) {
13
+ super(originalBytes ?? PdfStringToken.toBytes(value));
8
14
  this.value = typeof value === 'string' ? stringToBytes(value) : value;
15
+ if (originalBytes) {
16
+ // Make non-enumerable so it doesn't affect .toEqual() comparisons
17
+ Object.defineProperty(this, '_originalBytes', {
18
+ value: originalBytes,
19
+ writable: true,
20
+ enumerable: false,
21
+ configurable: true,
22
+ });
23
+ }
24
+ }
25
+ get originalBytes() {
26
+ return this._originalBytes;
9
27
  }
10
28
  static toBytes(value) {
11
29
  return new Uint8Array([0x28, ...escapeString(value), 0x29]);
@@ -273,10 +273,10 @@ export class PdfFontManager {
273
273
  */
274
274
  async collectAllFontsFromPdf() {
275
275
  const fonts = new Map();
276
- const catalog = this.document.rootDictionary;
276
+ const catalog = this.document.root;
277
277
  if (!catalog)
278
278
  return fonts;
279
- const pagesRef = catalog.get('Pages');
279
+ const pagesRef = catalog.content.get('Pages');
280
280
  if (!pagesRef)
281
281
  return fonts;
282
282
  const pagesObjRef = pagesRef.as(PdfObjectReference);
@@ -290,11 +290,11 @@ export class PdfFontManager {
290
290
  * This ensures fonts are available to form fields.
291
291
  */
292
292
  async addFontToAcroFormResources(resourceName, fontObject) {
293
- const catalog = this.document.rootDictionary;
293
+ const catalog = this.document.root;
294
294
  if (!catalog)
295
295
  return;
296
296
  // Get AcroForm dictionary
297
- const acroFormRef = catalog.get('AcroForm');
297
+ const acroFormRef = catalog.content.get('AcroForm');
298
298
  if (!acroFormRef)
299
299
  return;
300
300
  let acroFormDict;
@@ -339,10 +339,8 @@ export class PdfFontManager {
339
339
  * This is more efficient than adding to each individual page.
340
340
  */
341
341
  async addFontToPageResources(resourceName, fontObject) {
342
- const catalog = this.document.rootDictionary;
343
- if (!catalog)
344
- return;
345
- const pagesRef = catalog.get('Pages');
342
+ const catalog = this.document.root;
343
+ const pagesRef = catalog.content.get('Pages');
346
344
  if (!pagesRef)
347
345
  return;
348
346
  const pagesObjRef = pagesRef.as(PdfObjectReference);
@@ -33,8 +33,6 @@ import { PdfFontManager } from '../fonts/font-manager.js';
33
33
  * ```
34
34
  */
35
35
  export declare class PdfDocument extends PdfObject {
36
- /** PDF version comment header */
37
- header: PdfComment;
38
36
  /** List of document revisions for incremental updates */
39
37
  revisions: PdfRevision[];
40
38
  /** Signer instance for digital signature operations */
@@ -46,12 +44,6 @@ export declare class PdfDocument extends PdfObject {
46
44
  private _fonts?;
47
45
  private hasEncryptionDictionary?;
48
46
  private toBeCommitted;
49
- /** XFA manager for handling XFA forms */
50
- get xfa(): PdfXfaManager;
51
- /** AcroForm manager for handling form fields */
52
- get acroForm(): PdfAcroFormManager;
53
- /** Font manager for embedding and managing fonts */
54
- get fonts(): PdfFontManager;
55
47
  /**
56
48
  * Creates a new PDF document instance.
57
49
  *
@@ -71,6 +63,14 @@ export declare class PdfDocument extends PdfObject {
71
63
  securityHandler?: PdfSecurityHandler;
72
64
  signer?: PdfSigner;
73
65
  });
66
+ get header(): PdfComment | undefined;
67
+ set header(comment: PdfComment | undefined);
68
+ /** XFA manager for handling XFA forms */
69
+ get xfa(): PdfXfaManager;
70
+ /** AcroForm manager for handling form fields */
71
+ get acroForm(): PdfAcroFormManager;
72
+ /** Font manager for embedding and managing fonts */
73
+ get fonts(): PdfFontManager;
74
74
  /**
75
75
  * Creates a PdfDocument from an array of PDF objects.
76
76
  * Parses objects into revisions based on EOF comments.
@@ -125,13 +125,14 @@ export declare class PdfDocument extends PdfObject {
125
125
  * @throws Error if the encryption dictionary reference points to a non-dictionary object
126
126
  */
127
127
  get encryptionDictionary(): PdfEncryptionDictionaryObject | undefined;
128
+ get rootReference(): PdfObjectReference;
128
129
  /**
129
- * Gets the document catalog (root) dictionary.
130
+ * Gets the document catalog (root) dictionary, or creates one if it doesn't exist.
130
131
  *
131
- * @returns The root dictionary or undefined if not found
132
+ * @returns The root dictionary
132
133
  * @throws Error if the Root reference points to a non-dictionary object
133
134
  */
134
- get rootDictionary(): PdfDictionary | undefined;
135
+ get root(): PdfIndirectObject<PdfDictionary>;
135
136
  /**
136
137
  * Gets the reference to the metadata stream from the document catalog.
137
138
  *
@@ -20,6 +20,7 @@ import { PdfSigner } from '../signing/signer.js';
20
20
  import { PdfXfaManager } from '../xfa/manager.js';
21
21
  import { PdfAcroFormManager } from '../acroform/manager.js';
22
22
  import { PdfFontManager } from '../fonts/font-manager.js';
23
+ import { concatUint8Arrays } from '../utils/concatUint8Arrays.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.
@@ -38,8 +39,6 @@ import { PdfFontManager } from '../fonts/font-manager.js';
38
39
  * ```
39
40
  */
40
41
  export class PdfDocument extends PdfObject {
41
- /** PDF version comment header */
42
- header = PdfComment.versionComment('1.7');
43
42
  /** List of document revisions for incremental updates */
44
43
  revisions;
45
44
  /** Signer instance for digital signature operations */
@@ -51,27 +50,6 @@ export class PdfDocument extends PdfObject {
51
50
  _fonts;
52
51
  hasEncryptionDictionary = false;
53
52
  toBeCommitted = [];
54
- /** XFA manager for handling XFA forms */
55
- get xfa() {
56
- if (!this._xfa) {
57
- this._xfa = new PdfXfaManager(this);
58
- }
59
- return this._xfa;
60
- }
61
- /** AcroForm manager for handling form fields */
62
- get acroForm() {
63
- if (!this._acroForm) {
64
- this._acroForm = new PdfAcroFormManager(this);
65
- }
66
- return this._acroForm;
67
- }
68
- /** Font manager for embedding and managing fonts */
69
- get fonts() {
70
- if (!this._fonts) {
71
- this._fonts = new PdfFontManager(this);
72
- }
73
- return this._fonts;
74
- }
75
53
  /**
76
54
  * Creates a new PDF document instance.
77
55
  *
@@ -104,6 +82,34 @@ export class PdfDocument extends PdfObject {
104
82
  this.securityHandler =
105
83
  options?.securityHandler ?? this.getSecurityHandler();
106
84
  }
85
+ get header() {
86
+ return this.revisions[0].header;
87
+ }
88
+ set header(comment) {
89
+ if (comment)
90
+ this.revisions[0].header = comment;
91
+ }
92
+ /** XFA manager for handling XFA forms */
93
+ get xfa() {
94
+ if (!this._xfa) {
95
+ this._xfa = new PdfXfaManager(this);
96
+ }
97
+ return this._xfa;
98
+ }
99
+ /** AcroForm manager for handling form fields */
100
+ get acroForm() {
101
+ if (!this._acroForm) {
102
+ this._acroForm = new PdfAcroFormManager(this);
103
+ }
104
+ return this._acroForm;
105
+ }
106
+ /** Font manager for embedding and managing fonts */
107
+ get fonts() {
108
+ if (!this._fonts) {
109
+ this._fonts = new PdfFontManager(this);
110
+ }
111
+ return this._fonts;
112
+ }
107
113
  /**
108
114
  * Creates a PdfDocument from an array of PDF objects.
109
115
  * Parses objects into revisions based on EOF comments.
@@ -157,6 +163,9 @@ export class PdfDocument extends PdfObject {
157
163
  this.startNewRevision();
158
164
  }
159
165
  for (const obj of objects) {
166
+ if (obj.isImmutable()) {
167
+ throw new Error('Risky adding a immutable obj');
168
+ }
160
169
  this.toBeCommitted.push(obj);
161
170
  this.latestRevision.addObject(obj);
162
171
  }
@@ -227,16 +236,24 @@ export class PdfDocument extends PdfObject {
227
236
  encryptionDictObject.encryptable = false;
228
237
  return encryptionDictObject;
229
238
  }
239
+ get rootReference() {
240
+ return this.root.reference;
241
+ }
230
242
  /**
231
- * Gets the document catalog (root) dictionary.
243
+ * Gets the document catalog (root) dictionary, or creates one if it doesn't exist.
232
244
  *
233
- * @returns The root dictionary or undefined if not found
245
+ * @returns The root dictionary
234
246
  * @throws Error if the Root reference points to a non-dictionary object
235
247
  */
236
- get rootDictionary() {
248
+ get root() {
237
249
  const rootRef = this.trailerDict.get('Root')?.as(PdfObjectReference);
238
250
  if (!rootRef) {
239
- return undefined;
251
+ const rootObject = new PdfIndirectObject({
252
+ content: new PdfDictionary(),
253
+ });
254
+ this.add(rootObject);
255
+ this.trailerDict.set('Root', rootObject.reference);
256
+ return rootObject;
240
257
  }
241
258
  const rootObject = this.findUncompressedObject(rootRef);
242
259
  if (!rootObject) {
@@ -245,7 +262,7 @@ export class PdfDocument extends PdfObject {
245
262
  if (!(rootObject?.content instanceof PdfDictionary)) {
246
263
  throw new Error(`Root object ${rootRef.objectNumber} ${rootRef.generationNumber} is not a dictionary, it is a ${rootObject?.content.objectType}`);
247
264
  }
248
- return rootObject.content;
265
+ return rootObject;
249
266
  }
250
267
  /**
251
268
  * Gets the reference to the metadata stream from the document catalog.
@@ -253,11 +270,11 @@ export class PdfDocument extends PdfObject {
253
270
  * @returns The metadata stream reference or undefined if not present
254
271
  */
255
272
  get metadataStreamReference() {
256
- const root = this.rootDictionary;
273
+ const root = this.root;
257
274
  if (!root) {
258
275
  return;
259
276
  }
260
- const metadataRef = root.get('Metadata')?.as(PdfObjectReference);
277
+ const metadataRef = root.content.get('Metadata')?.as(PdfObjectReference);
261
278
  if (!metadataRef) {
262
279
  return;
263
280
  }
@@ -501,9 +518,6 @@ export class PdfDocument extends PdfObject {
501
518
  foundObject = foundObject.clone();
502
519
  await this.securityHandler.decryptObject(foundObject);
503
520
  }
504
- else if (this.isIncremental()) {
505
- foundObject = foundObject.clone(); // Clone to prevent modifications in locked revisions
506
- }
507
521
  return foundObject;
508
522
  }
509
523
  /**
@@ -591,7 +605,7 @@ export class PdfDocument extends PdfObject {
591
605
  * @throws Error if the document has no root dictionary
592
606
  */
593
607
  async setDocumentSecurityStore(dss) {
594
- let rootDictionary = this.rootDictionary;
608
+ let rootDictionary = this.root?.content;
595
609
  if (!rootDictionary) {
596
610
  throw new Error('Cannot set DSS - document has no root dictionary');
597
611
  }
@@ -615,10 +629,6 @@ export class PdfDocument extends PdfObject {
615
629
  }
616
630
  return tokens.map((token) => ({ token, object: obj }));
617
631
  });
618
- const headerTokens = this.header
619
- .toTokens()
620
- .map((token) => ({ token, object: this.header }));
621
- documentTokens.unshift(...headerTokens);
622
632
  return documentTokens;
623
633
  }
624
634
  tokenize() {
@@ -698,9 +708,7 @@ export class PdfDocument extends PdfObject {
698
708
  toBytes() {
699
709
  this.calculateOffsets();
700
710
  this.updateRevisions();
701
- const serializer = new PdfTokenSerializer();
702
- serializer.feedMany(this.toTokens());
703
- return serializer.toBytes();
711
+ return concatUint8Arrays(...this.revisions.map((x) => x.toBytes()));
704
712
  }
705
713
  /**
706
714
  * Serializes the document to a Base64-encoded string.
@@ -725,7 +733,7 @@ export class PdfDocument extends PdfObject {
725
733
  const clonedRevisions = this.revisions.map((rev) => rev.clone());
726
734
  return new PdfDocument({
727
735
  revisions: clonedRevisions,
728
- version: this.header.clone(),
736
+ version: this.header?.clone(),
729
737
  securityHandler: this.securityHandler,
730
738
  });
731
739
  }
@@ -2,6 +2,8 @@ import { PdfDictionary } from '../core/objects/pdf-dictionary.js';
2
2
  import { PdfObject } from '../core/objects/pdf-object.js';
3
3
  import { PdfTrailerEntries } from '../core/objects/pdf-trailer.js';
4
4
  import { PdfToken } from '../core/tokens/token.js';
5
+ import { PdfComment } from '../index.js';
6
+ import { ByteArray } from '../types.js';
5
7
  import { PdfXrefLookup } from './pdf-xref-lookup.js';
6
8
  /**
7
9
  * Represents a single revision of a PDF document.
@@ -9,12 +11,13 @@ import { PdfXrefLookup } from './pdf-xref-lookup.js';
9
11
  * where each revision contains its own set of objects and cross-reference table.
10
12
  */
11
13
  export declare class PdfRevision extends PdfObject {
12
- /** Objects contained in this revision */
13
- objects: PdfObject[];
14
+ /** Objects contained in this revision (private backing field) */
15
+ private _objects;
16
+ /** Whether this revision is locked (private backing field) */
17
+ private _locked;
14
18
  /** Cross-reference lookup table for this revision */
15
19
  xref: PdfXrefLookup;
16
- /** Whether this revision is locked (cannot be modified) */
17
- locked: boolean;
20
+ private cachedBytes?;
18
21
  /**
19
22
  * Creates a new PDF revision.
20
23
  *
@@ -28,6 +31,29 @@ export declare class PdfRevision extends PdfObject {
28
31
  prev?: PdfXrefLookup | PdfRevision;
29
32
  locked?: boolean;
30
33
  });
34
+ get header(): PdfComment | undefined;
35
+ set header(comment: PdfComment);
36
+ /**
37
+ * Gets whether this revision is locked (cannot be modified).
38
+ */
39
+ get locked(): boolean;
40
+ /**
41
+ * Sets whether this revision is locked.
42
+ * When locking, creates a cached clone of all objects to freeze their state.
43
+ * When unlocking, clears the cache.
44
+ */
45
+ set locked(value: boolean);
46
+ /**
47
+ * Gets the objects in this revision.
48
+ * Returns fresh clones of cached objects if the revision is locked, otherwise returns live objects.
49
+ * Each access to a locked revision's objects returns new clones to prevent mutations.
50
+ */
51
+ get objects(): ReadonlyArray<PdfObject>;
52
+ /**
53
+ * Sets the objects array.
54
+ * @throws Error if the revision is locked
55
+ */
56
+ set objects(value: PdfObject[]);
31
57
  /**
32
58
  * Links this revision to a previous revision's cross-reference table.
33
59
  *
@@ -52,12 +78,14 @@ export declare class PdfRevision extends PdfObject {
52
78
  * Adds objects to the beginning of the revision's object list.
53
79
  *
54
80
  * @param objects - Objects to add at the beginning
81
+ * @throws Error if the revision is locked
55
82
  */
56
83
  unshift(...objects: PdfObject[]): void;
57
84
  /**
58
85
  * Adds objects to the revision.
59
86
  *
60
87
  * @param objects - Objects to add to the revision
88
+ * @throws Error if the revision is locked
61
89
  */
62
90
  addObject(...objects: PdfObject[]): void;
63
91
  /**
@@ -98,4 +126,5 @@ export declare class PdfRevision extends PdfObject {
98
126
  */
99
127
  clone(): this;
100
128
  protected tokenize(): PdfToken[];
129
+ toBytes(): ByteArray;
101
130
  }