• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

adnsistemas / pdf-lib / #7

07 Nov 2025 11:19PM UTC coverage: 70.248% (+0.07%) from 70.174%
#7

push

David N. Abdala
Fix in object streams usage on XREF generation, for incremental update and incremental generation. Parameter adding for incremental update saving, to force useObjectStreams preference

2415 of 3900 branches covered (61.92%)

Branch coverage included in aggregate %.

6718 of 9101 relevant lines covered (73.82%)

121826.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.78
/src/api/PDFDocument.ts
1
import {
32✔
2
  parse as parseHtml,
3
  HTMLElement,
4
  NodeType,
5
} from 'node-html-better-parser';
6
import Embeddable from './Embeddable';
7
import {
32✔
8
  EncryptedPDFError,
9
  FontkitNotRegisteredError,
10
  ForeignPageError,
11
  RemovePageFromEmptyDocumentError,
12
} from './errors';
13
import PDFEmbeddedPage from './PDFEmbeddedPage';
32✔
14
import PDFFont from './PDFFont';
32✔
15
import PDFImage from './PDFImage';
32✔
16
import PDFPage from './PDFPage';
32✔
17
import PDFForm from './form/PDFForm';
32✔
18
import { PageSizes } from './sizes';
32✔
19
import { StandardFonts } from './StandardFonts';
20
import {
32✔
21
  CustomFontEmbedder,
22
  CustomFontSubsetEmbedder,
23
  JpegEmbedder,
24
  PageBoundingBox,
25
  PageEmbeddingMismatchedContextError,
26
  PDFArray,
27
  PDFCatalog,
28
  PDFContext,
29
  PDFDict,
30
  decodePDFRawStream,
31
  PDFStream,
32
  PDFRawStream,
33
  PDFHexString,
34
  PDFName,
35
  PDFObjectCopier,
36
  PDFPageEmbedder,
37
  PDFPageLeaf,
38
  PDFPageTree,
39
  PDFParser,
40
  PDFStreamWriter,
41
  PDFString,
42
  PDFWriter,
43
  PngEmbedder,
44
  StandardFontEmbedder,
45
  UnexpectedObjectTypeError,
46
} from '../core';
47
import {
32✔
48
  ParseSpeeds,
49
  AttachmentOptions,
50
  SaveOptions,
51
  Base64SaveOptions,
52
  LoadOptions,
53
  CreateOptions,
54
  EmbedFontOptions,
55
  SetTitleOptions,
56
  IncrementalSaveOptions,
57
} from './PDFDocumentOptions';
58
import PDFObject from '../core/objects/PDFObject';
59
import PDFRef from '../core/objects/PDFRef';
60
import { Fontkit } from '../types/fontkit';
61
import { TransformationMatrix } from '../types/matrix';
62
import {
32✔
63
  assertIs,
64
  assertIsOneOfOrUndefined,
65
  assertOrUndefined,
66
  assertRange,
67
  Cache,
68
  canBeConvertedToUint8Array,
69
  encodeToBase64,
70
  isStandardFont,
71
  pluckIndices,
72
  range,
73
  toUint8Array,
74
} from '../utils';
75
import FileEmbedder, { AFRelationship } from '../core/embedders/FileEmbedder';
32✔
76
import PDFEmbeddedFile from './PDFEmbeddedFile';
32✔
77
import PDFJavaScript from './PDFJavaScript';
32✔
78
import JavaScriptEmbedder from '../core/embedders/JavaScriptEmbedder';
32✔
79
import { CipherTransformFactory } from '../core/crypto';
32✔
80
import PDFSvg from './PDFSvg';
32✔
81
import PDFSecurity, { SecurityOptions } from '../core/security/PDFSecurity';
32✔
82
import { IncrementalDocumentSnapshot } from './snapshot';
32✔
83
import type { DocumentSnapshot } from './snapshot';
84

85
export type PDFAttachment = {
86
  name: string;
87
  data: Uint8Array;
88
  mimeType: string | undefined;
89
  afRelationship: AFRelationship | undefined;
90
  description: string | undefined;
91
  creationDate: Date | undefined;
92
  modificationDate: Date | undefined;
93
};
94

95
export type PDFObjectVersions = {
96
  ref: PDFRef;
97
  actual: PDFObject | undefined;
98
  previous: PDFObject[];
99
};
100

101
/**
102
 * Represents a PDF document.
103
 */
104
export default class PDFDocument {
32✔
105
  /**
106
   * Load an existing [[PDFDocument]]. The input data can be provided in
107
   * multiple formats:
108
   *
109
   * | Type          | Contents                                               |
110
   * | ------------- | ------------------------------------------------------ |
111
   * | `string`      | A base64 encoded string (or data URI) containing a PDF |
112
   * | `Uint8Array`  | The raw bytes of a PDF                                 |
113
   * | `ArrayBuffer` | The raw bytes of a PDF                                 |
114
   *
115
   * For example:
116
   * ```js
117
   * import { PDFDocument } from 'pdf-lib'
118
   *
119
   * // pdf=string
120
   * const base64 =
121
   *  'JVBERi0xLjcKJYGBgYEKCjUgMCBvYmoKPDwKL0ZpbHRlciAvRmxhdGVEZWNvZGUKL0xlbm' +
122
   *  'd0aCAxMDQKPj4Kc3RyZWFtCniccwrhMlAAwaJ0Ln2P1Jyy1JLM5ERdc0MjCwUjE4WQNC4Q' +
123
   *  '6cNlCFZkqGCqYGSqEJLLZWNuYGZiZmbkYuZsZmlmZGRgZmluDCQNzc3NTM2NzdzMXMxMjQ' +
124
   *  'ztFEKyuEK0uFxDuAAOERdVCmVuZHN0cmVhbQplbmRvYmoKCjYgMCBvYmoKPDwKL0ZpbHRl' +
125
   *  'ciAvRmxhdGVEZWNvZGUKL1R5cGUgL09ialN0bQovTiA0Ci9GaXJzdCAyMAovTGVuZ3RoID' +
126
   *  'IxNQo+PgpzdHJlYW0KeJxVj9GqwjAMhu/zFHkBzTo3nCCCiiKIHPEICuJF3cKoSCu2E8/b' +
127
   *  '20wPIr1p8v9/8kVhgilmGfawX2CGaVrgcAi0/bsy0lrX7IGWpvJ4iJYEN3gEmrrGBlQwGs' +
128
   *  'HHO9VBX1wNrxAqMX87RBD5xpJuddqwd82tjAHxzV1U5LPgy52DKXWnr1Lheg+j/c/pzGVr' +
129
   *  'iqV0VlwZPXGPCJjElw/ybkwUmeoWgxesDXGhHJC/D/iikp1Av80ptKU0FdBEe25pPihAM1' +
130
   *  'u6ytgaaWfs2Hrz35CJT1+EWmAKZW5kc3RyZWFtCmVuZG9iagoKNyAwIG9iago8PAovU2l6' +
131
   *  'ZSA4Ci9Sb290IDIgMCBSCi9GaWx0ZXIgL0ZsYXRlRGVjb2RlCi9UeXBlIC9YUmVmCi9MZW' +
132
   *  '5ndGggMzgKL1cgWyAxIDIgMiBdCi9JbmRleCBbIDAgOCBdCj4+CnN0cmVhbQp4nBXEwREA' +
133
   *  'EBAEsCwz3vrvRmOOyyOoGhZdutHN2MT55fIAVocD+AplbmRzdHJlYW0KZW5kb2JqCgpzdG' +
134
   *  'FydHhyZWYKNTEwCiUlRU9G'
135
   *
136
   * const dataUri = 'data:application/pdf;base64,' + base64
137
   *
138
   * const pdfDoc1 = await PDFDocument.load(base64)
139
   * const pdfDoc2 = await PDFDocument.load(dataUri)
140
   *
141
   * // pdf=Uint8Array
142
   * import fs from 'fs'
143
   * const uint8Array = fs.readFileSync('with_update_sections.pdf')
144
   * const pdfDoc3 = await PDFDocument.load(uint8Array)
145
   *
146
   * // pdf=ArrayBuffer
147
   * const url = 'https://pdf-lib.js.org/assets/with_update_sections.pdf'
148
   * const arrayBuffer = await fetch(url).then(res => res.arrayBuffer())
149
   * const pdfDoc4 = await PDFDocument.load(arrayBuffer)
150
   *
151
   * ```
152
   *
153
   * @param pdf The input data containing a PDF document.
154
   * @param options The options to be used when loading the document.
155
   * @returns Resolves with a document loaded from the input.
156
   */
157
  static async load(
158
    pdf: string | Uint8Array | ArrayBuffer,
159
    options: LoadOptions = {},
60✔
160
  ) {
161
    const {
162
      ignoreEncryption = false,
89✔
163
      parseSpeed = ParseSpeeds.Slow,
83✔
164
      throwOnInvalidObject = false,
91✔
165
      warnOnInvalidObjects = false,
93✔
166
      updateMetadata = true,
92✔
167
      capNumbers = false,
93✔
168
      password,
169
      forIncrementalUpdate = false,
76✔
170
      preserveObjectsVersions = false,
88✔
171
    } = options;
93✔
172

173
    assertIs(pdf, 'pdf', ['string', Uint8Array, ArrayBuffer]);
93✔
174
    assertIs(ignoreEncryption, 'ignoreEncryption', ['boolean']);
93✔
175
    assertIs(parseSpeed, 'parseSpeed', ['number']);
93✔
176
    assertIs(throwOnInvalidObject, 'throwOnInvalidObject', ['boolean']);
93✔
177
    assertIs(warnOnInvalidObjects, 'warnOnInvalidObjects', ['boolean']);
93✔
178
    assertIs(password, 'password', ['string', 'undefined']);
93✔
179
    assertIs(forIncrementalUpdate, 'forIncrementalUpdate', ['boolean']);
93✔
180
    assertIs(preserveObjectsVersions, 'preserveObjectsVersions', ['boolean']);
93✔
181

182
    const bytes = toUint8Array(pdf);
93✔
183
    const context = await PDFParser.forBytesWithOptions(
93✔
184
      bytes,
185
      parseSpeed,
186
      throwOnInvalidObject,
187
      undefined,
188
      capNumbers,
189
      undefined,
190
      forIncrementalUpdate,
191
      preserveObjectsVersions,
192
    ).parseDocument();
193
    if (
92!
194
      !!context.lookup(context.trailerInfo.Encrypt) &&
96✔
195
      password !== undefined
196
    ) {
197
      // Decrypt
198
      const fileIds = context.lookup(context.trailerInfo.ID, PDFArray);
×
199
      const encryptDict = context.lookup(context.trailerInfo.Encrypt, PDFDict);
×
200
      const decryptedContext = await PDFParser.forBytesWithOptions(
×
201
        bytes,
202
        parseSpeed,
203
        throwOnInvalidObject,
204
        warnOnInvalidObjects,
205
        capNumbers,
206
        new CipherTransformFactory(
207
          encryptDict,
208
          (fileIds.get(0) as PDFHexString).asBytes(),
209
          password,
210
        ),
211
        forIncrementalUpdate,
212
        preserveObjectsVersions,
213
      ).parseDocument();
214
      const pdfDoc = new PDFDocument(decryptedContext, true, updateMetadata);
×
215
      if (forIncrementalUpdate) pdfDoc.takeSnapshot();
×
216
      return pdfDoc;
×
217
    } else {
218
      const pdfDoc = new PDFDocument(context, ignoreEncryption, updateMetadata);
92✔
219
      if (forIncrementalUpdate) pdfDoc.takeSnapshot();
90✔
220
      return pdfDoc;
90✔
221
    }
222
  }
223

224
  /**
225
   * Create a new [[PDFDocument]].
226
   * @returns Resolves with the newly created document.
227
   */
228
  static async create(options: CreateOptions = {}) {
47✔
229
    const { updateMetadata = true } = options;
52✔
230

231
    const context = PDFContext.create();
52✔
232
    const pageTree = PDFPageTree.withContext(context);
52✔
233
    const pageTreeRef = context.register(pageTree);
52✔
234
    const catalog = PDFCatalog.withContextAndPages(context, pageTreeRef);
52✔
235
    context.trailerInfo.Root = context.register(catalog);
52✔
236

237
    return new PDFDocument(context, false, updateMetadata);
52✔
238
  }
239

240
  /** The low-level context of this document. */
241
  readonly context: PDFContext;
242

243
  /** The catalog of this document. */
244
  readonly catalog: PDFCatalog;
245

246
  /** Whether or not this document is encrypted. */
247
  readonly isEncrypted: boolean;
248

249
  /** The default word breaks used in PDFPage.drawText */
250
  defaultWordBreaks: string[] = [' '];
144✔
251

252
  private fontkit?: Fontkit;
253
  private pageCount: number | undefined;
254
  private readonly pageCache: Cache<PDFPage[]>;
255
  private readonly pageMap: Map<PDFPageLeaf, PDFPage>;
256
  private readonly formCache: Cache<PDFForm>;
257
  private readonly fonts: PDFFont[];
258
  private readonly images: PDFImage[];
259
  private readonly embeddedPages: PDFEmbeddedPage[];
260
  private readonly embeddedFiles: PDFEmbeddedFile[];
261
  private readonly javaScripts: PDFJavaScript[];
262

263
  private constructor(
264
    context: PDFContext,
265
    ignoreEncryption: boolean,
266
    updateMetadata: boolean,
267
  ) {
268
    assertIs(context, 'context', [[PDFContext, 'PDFContext']]);
144✔
269
    assertIs(ignoreEncryption, 'ignoreEncryption', ['boolean']);
144✔
270

271
    this.context = context;
144✔
272
    this.catalog = context.lookup(context.trailerInfo.Root) as PDFCatalog;
144✔
273

274
    if (!!context.lookup(context.trailerInfo.Encrypt) && context.isDecrypted) {
144!
275
      // context.delete(context.trailerInfo.Encrypt);
276
      delete context.trailerInfo.Encrypt;
×
277
    }
278
    this.isEncrypted = !!context.lookup(context.trailerInfo.Encrypt);
144✔
279

280
    this.pageCache = Cache.populatedBy(this.computePages);
144✔
281
    this.pageMap = new Map();
144✔
282
    this.formCache = Cache.populatedBy(this.getOrCreateForm);
144✔
283
    this.fonts = [];
144✔
284
    this.images = [];
144✔
285
    this.embeddedPages = [];
144✔
286
    this.embeddedFiles = [];
144✔
287
    this.javaScripts = [];
144✔
288

289
    if (!ignoreEncryption && this.isEncrypted) throw new EncryptedPDFError();
144✔
290

291
    if (updateMetadata) this.updateInfoDict();
142✔
292
  }
293

294
  /**
295
   * Register a fontkit instance. This must be done before custom fonts can
296
   * be embedded. See [here](https://github.com/Hopding/pdf-lib/tree/master#fontkit-installation)
297
   * for instructions on how to install and register a fontkit instance.
298
   *
299
   * > You do **not** need to call this method to embed standard fonts.
300
   *
301
   * For example:
302
   * ```js
303
   * import { PDFDocument } from 'pdf-lib'
304
   * import fontkit from '@pdf-lib/fontkit'
305
   *
306
   * const pdfDoc = await PDFDocument.create()
307
   * pdfDoc.registerFontkit(fontkit)
308
   * ```
309
   *
310
   * @param fontkit The fontkit instance to be registered.
311
   */
312
  registerFontkit(fontkit: Fontkit): void {
313
    this.fontkit = fontkit;
2✔
314
  }
315

316
  /**
317
   * Get the [[PDFForm]] containing all interactive fields for this document.
318
   * For example:
319
   * ```js
320
   * const form = pdfDoc.getForm()
321
   * const fields = form.getFields()
322
   * fields.forEach(field => {
323
   *   const type = field.constructor.name
324
   *   const name = field.getName()
325
   *   console.log(`${type}: ${name}`)
326
   * })
327
   * ```
328
   * @returns The form for this document.
329
   */
330
  getForm(): PDFForm {
331
    const form = this.formCache.access();
151✔
332
    if (form.hasXFA()) {
151✔
333
      console.warn(
1✔
334
        'Removing XFA form data as pdf-lib does not support reading or writing XFA',
335
      );
336
      form.deleteXFA();
1✔
337
    }
338
    return form;
151✔
339
  }
340

341
  /**
342
   * Get this document's title metadata. The title appears in the
343
   * "Document Properties" section of most PDF readers. For example:
344
   * ```js
345
   * const title = pdfDoc.getTitle()
346
   * ```
347
   * @returns A string containing the title of this document, if it has one.
348
   */
349
  getTitle(): string | undefined {
350
    const title = this.getInfoDict().lookup(PDFName.Title);
8✔
351
    if (!title) return undefined;
8✔
352
    assertIsLiteralOrHexString(title);
7✔
353
    return title.decodeText();
7✔
354
  }
355

356
  /**
357
   * Get this document's author metadata. The author appears in the
358
   * "Document Properties" section of most PDF readers. For example:
359
   * ```js
360
   * const author = pdfDoc.getAuthor()
361
   * ```
362
   * @returns A string containing the author of this document, if it has one.
363
   */
364
  getAuthor(): string | undefined {
365
    const author = this.getInfoDict().lookup(PDFName.Author);
7✔
366
    if (!author) return undefined;
7✔
367
    assertIsLiteralOrHexString(author);
6✔
368
    return author.decodeText();
6✔
369
  }
370

371
  /**
372
   * Get this document's subject metadata. The subject appears in the
373
   * "Document Properties" section of most PDF readers. For example:
374
   * ```js
375
   * const subject = pdfDoc.getSubject()
376
   * ```
377
   * @returns A string containing the subject of this document, if it has one.
378
   */
379
  getSubject(): string | undefined {
380
    const subject = this.getInfoDict().lookup(PDFName.Subject);
7✔
381
    if (!subject) return undefined;
7✔
382
    assertIsLiteralOrHexString(subject);
6✔
383
    return subject.decodeText();
6✔
384
  }
385

386
  /**
387
   * Get this document's keywords metadata. The keywords appear in the
388
   * "Document Properties" section of most PDF readers. For example:
389
   * ```js
390
   * const keywords = pdfDoc.getKeywords()
391
   * ```
392
   * @returns A string containing the keywords of this document, if it has any.
393
   */
394
  getKeywords(): string | undefined {
395
    const keywords = this.getInfoDict().lookup(PDFName.Keywords);
3✔
396
    if (!keywords) return undefined;
3✔
397
    assertIsLiteralOrHexString(keywords);
2✔
398
    return keywords.decodeText();
2✔
399
  }
400

401
  /**
402
   * Get this document's creator metadata. The creator appears in the
403
   * "Document Properties" section of most PDF readers. For example:
404
   * ```js
405
   * const creator = pdfDoc.getCreator()
406
   * ```
407
   * @returns A string containing the creator of this document, if it has one.
408
   */
409
  getCreator(): string | undefined {
410
    const creator = this.getInfoDict().lookup(PDFName.Creator);
6✔
411
    if (!creator) return undefined;
6!
412
    assertIsLiteralOrHexString(creator);
6✔
413
    return creator.decodeText();
6✔
414
  }
415

416
  /**
417
   * Get this document's producer metadata. The producer appears in the
418
   * "Document Properties" section of most PDF readers. For example:
419
   * ```js
420
   * const producer = pdfDoc.getProducer()
421
   * ```
422
   * @returns A string containing the producer of this document, if it has one.
423
   */
424
  getProducer(): string | undefined {
425
    const producer = this.getInfoDict().lookup(PDFName.Producer);
7✔
426
    if (!producer) return undefined;
7!
427
    assertIsLiteralOrHexString(producer);
7✔
428
    return producer.decodeText();
7✔
429
  }
430

431
  /**
432
   * Get this document's language metadata. The language appears in the
433
   * "Document Properties" section of most PDF readers. For example:
434
   * ```js
435
   * const language = pdfDoc.getLanguage()
436
   * ```
437
   * @returns A string containing the RFC 3066 _Language-Tag_ of this document,
438
   *          if it has one.
439
   */
440
  getLanguage(): string | undefined {
441
    const language = this.catalog.get(PDFName.of('Lang'));
5✔
442
    if (!language) return undefined;
5✔
443
    assertIsLiteralOrHexString(language);
3✔
444
    return language.decodeText();
3✔
445
  }
446

447
  /**
448
   * Get this document's creation date metadata. The creation date appears in
449
   * the "Document Properties" section of most PDF readers. For example:
450
   * ```js
451
   * const creationDate = pdfDoc.getCreationDate()
452
   * ```
453
   * @returns A Date containing the creation date of this document,
454
   *          if it has one.
455
   */
456
  getCreationDate(): Date | undefined {
457
    const creationDate = this.getInfoDict().lookup(PDFName.CreationDate);
6✔
458
    if (!creationDate) return undefined;
6!
459
    assertIsLiteralOrHexString(creationDate);
6✔
460
    return creationDate.decodeDate();
6✔
461
  }
462

463
  /**
464
   * Get this document's modification date metadata. The modification date
465
   * appears in the "Document Properties" section of most PDF readers.
466
   * For example:
467
   * ```js
468
   * const modification = pdfDoc.getModificationDate()
469
   * ```
470
   * @returns A Date containing the modification date of this document,
471
   *          if it has one.
472
   */
473
  getModificationDate(): Date | undefined {
474
    const modificationDate = this.getInfoDict().lookup(PDFName.ModDate);
6✔
475
    if (!modificationDate) return undefined;
6!
476
    assertIsLiteralOrHexString(modificationDate);
6✔
477
    return modificationDate.decodeDate();
6✔
478
  }
479

480
  /**
481
   * Set this document's title metadata. The title will appear in the
482
   * "Document Properties" section of most PDF readers. For example:
483
   * ```js
484
   * pdfDoc.setTitle('🥚 The Life of an Egg 🍳')
485
   * ```
486
   *
487
   * To display the title in the window's title bar, set the
488
   * `showInWindowTitleBar` option to `true` (works for _most_ PDF readers).
489
   * For example:
490
   * ```js
491
   * pdfDoc.setTitle('🥚 The Life of an Egg 🍳', { showInWindowTitleBar: true })
492
   * ```
493
   *
494
   * @param title The title of this document.
495
   * @param options The options to be used when setting the title.
496
   */
497
  setTitle(title: string, options?: SetTitleOptions): void {
498
    assertIs(title, 'title', ['string']);
8✔
499
    const key = PDFName.of('Title');
8✔
500
    this.getInfoDict().set(key, PDFHexString.fromText(title));
8✔
501

502
    // Indicate that readers should display the title rather than the filename
503
    if (options?.showInWindowTitleBar) {
8✔
504
      const prefs = this.catalog.getOrCreateViewerPreferences();
1✔
505
      prefs.setDisplayDocTitle(true);
1✔
506
    }
507
  }
508

509
  /**
510
   * Set this document's author metadata. The author will appear in the
511
   * "Document Properties" section of most PDF readers. For example:
512
   * ```js
513
   * pdfDoc.setAuthor('Humpty Dumpty')
514
   * ```
515
   * @param author The author of this document.
516
   */
517
  setAuthor(author: string): void {
518
    assertIs(author, 'author', ['string']);
3✔
519
    const key = PDFName.of('Author');
3✔
520
    this.getInfoDict().set(key, PDFHexString.fromText(author));
3✔
521
  }
522

523
  /**
524
   * Set this document's subject metadata. The subject will appear in the
525
   * "Document Properties" section of most PDF readers. For example:
526
   * ```js
527
   * pdfDoc.setSubject('📘 An Epic Tale of Woe 📖')
528
   * ```
529
   * @param subject The subject of this document.
530
   */
531
  setSubject(subject: string): void {
532
    assertIs(subject, 'author', ['string']);
3✔
533
    const key = PDFName.of('Subject');
3✔
534
    this.getInfoDict().set(key, PDFHexString.fromText(subject));
3✔
535
  }
536

537
  /**
538
   * Set this document's keyword metadata. These keywords will appear in the
539
   * "Document Properties" section of most PDF readers. For example:
540
   * ```js
541
   * pdfDoc.setKeywords(['eggs', 'wall', 'fall', 'king', 'horses', 'men'])
542
   * ```
543
   * @param keywords An array of keywords associated with this document.
544
   */
545
  setKeywords(keywords: string[]): void {
546
    assertIs(keywords, 'keywords', [Array]);
2✔
547
    const key = PDFName.of('Keywords');
2✔
548
    this.getInfoDict().set(key, PDFHexString.fromText(keywords.join(' ')));
2✔
549
  }
550

551
  /**
552
   * Set this document's creator metadata. The creator will appear in the
553
   * "Document Properties" section of most PDF readers. For example:
554
   * ```js
555
   * pdfDoc.setCreator('PDF App 9000 🤖')
556
   * ```
557
   * @param creator The creator of this document.
558
   */
559
  setCreator(creator: string): void {
560
    assertIs(creator, 'creator', ['string']);
67✔
561
    const key = PDFName.of('Creator');
67✔
562
    this.getInfoDict().set(key, PDFHexString.fromText(creator));
67✔
563
  }
564

565
  /**
566
   * Set this document's producer metadata. The producer will appear in the
567
   * "Document Properties" section of most PDF readers. For example:
568
   * ```js
569
   * pdfDoc.setProducer('PDF App 9000 🤖')
570
   * ```
571
   * @param producer The producer of this document.
572
   */
573
  setProducer(producer: string): void {
574
    assertIs(producer, 'creator', ['string']);
139✔
575
    const key = PDFName.of('Producer');
139✔
576
    this.getInfoDict().set(key, PDFHexString.fromText(producer));
139✔
577
  }
578

579
  /**
580
   * Set this document's language metadata. The language will appear in the
581
   * "Document Properties" section of some PDF readers. For example:
582
   * ```js
583
   * pdfDoc.setLanguage('en-us')
584
   * ```
585
   *
586
   * @param language An RFC 3066 _Language-Tag_ denoting the language of this
587
   *                 document, or an empty string if the language is unknown.
588
   */
589
  setLanguage(language: string): void {
590
    assertIs(language, 'language', ['string']);
3✔
591
    const key = PDFName.of('Lang');
3✔
592
    this.catalog.set(key, PDFString.of(language));
3✔
593
  }
594

595
  /**
596
   * Set this document's creation date metadata. The creation date will appear
597
   * in the "Document Properties" section of most PDF readers. For example:
598
   * ```js
599
   * pdfDoc.setCreationDate(new Date())
600
   * ```
601
   * @param creationDate The date this document was created.
602
   */
603
  setCreationDate(creationDate: Date): void {
604
    assertIs(creationDate, 'creationDate', [[Date, 'Date']]);
53✔
605
    const key = PDFName.of('CreationDate');
53✔
606
    this.getInfoDict().set(key, PDFString.fromDate(creationDate));
53✔
607
  }
608

609
  /**
610
   * Set this document's modification date metadata. The modification date will
611
   * appear in the "Document Properties" section of most PDF readers. For
612
   * example:
613
   * ```js
614
   * pdfDoc.setModificationDate(new Date())
615
   * ```
616
   * @param modificationDate The date this document was last modified.
617
   */
618
  setModificationDate(modificationDate: Date): void {
619
    assertIs(modificationDate, 'modificationDate', [[Date, 'Date']]);
139✔
620
    const key = PDFName.of('ModDate');
139✔
621
    this.getInfoDict().set(key, PDFString.fromDate(modificationDate));
139✔
622
  }
623

624
  /**
625
   * Get the number of pages contained in this document. For example:
626
   * ```js
627
   * const totalPages = pdfDoc.getPageCount()
628
   * ```
629
   * @returns The number of pages in this document.
630
   */
631
  getPageCount(): number {
632
    if (this.pageCount === undefined) this.pageCount = this.getPages().length;
118✔
633
    return this.pageCount;
118✔
634
  }
635

636
  /**
637
   * Get an array of all the pages contained in this document. The pages are
638
   * stored in the array in the same order that they are rendered in the
639
   * document. For example:
640
   * ```js
641
   * const pages = pdfDoc.getPages()
642
   * pages[0]   // The first page of the document
643
   * pages[2]   // The third page of the document
644
   * pages[197] // The 198th page of the document
645
   * ```
646
   * @returns An array of all the pages contained in this document.
647
   */
648
  getPages(): PDFPage[] {
649
    return this.pageCache.access();
90✔
650
  }
651

652
  /**
653
   * Get the page rendered at a particular `index` of the document. For example:
654
   * ```js
655
   * pdfDoc.getPage(0)   // The first page of the document
656
   * pdfDoc.getPage(2)   // The third page of the document
657
   * pdfDoc.getPage(197) // The 198th page of the document
658
   * ```
659
   * @returns The [[PDFPage]] rendered at the given `index` of the document.
660
   */
661
  getPage(index: number): PDFPage {
662
    const pages = this.getPages();
28✔
663
    assertRange(index, 'index', 0, pages.length - 1);
28✔
664
    return pages[index];
28✔
665
  }
666

667
  /**
668
   * Get an array of indices for all the pages contained in this document. The
669
   * array will contain a range of integers from
670
   * `0..pdfDoc.getPageCount() - 1`. For example:
671
   * ```js
672
   * const pdfDoc = await PDFDocument.create()
673
   * pdfDoc.addPage()
674
   * pdfDoc.addPage()
675
   * pdfDoc.addPage()
676
   *
677
   * const indices = pdfDoc.getPageIndices()
678
   * indices // => [0, 1, 2]
679
   * ```
680
   * @returns An array of indices for all pages contained in this document.
681
   */
682
  getPageIndices(): number[] {
683
    return range(0, this.getPageCount());
1✔
684
  }
685

686
  /**
687
   * Remove the page at a given index from this document. For example:
688
   * ```js
689
   * pdfDoc.removePage(0)   // Remove the first page of the document
690
   * pdfDoc.removePage(2)   // Remove the third page of the document
691
   * pdfDoc.removePage(197) // Remove the 198th page of the document
692
   * ```
693
   * Once a page has been removed, it will no longer be rendered at that index
694
   * in the document.
695
   * @param index The index of the page to be removed.
696
   */
697
  removePage(index: number): void {
698
    const pageCount = this.getPageCount();
4✔
699
    if (this.pageCount === 0) throw new RemovePageFromEmptyDocumentError();
4✔
700
    assertRange(index, 'index', 0, pageCount - 1);
3✔
701
    const page = this.getPage(index);
3✔
702
    this.catalog.removeLeafNode(index);
3✔
703
    this.pageCount = pageCount - 1;
3✔
704
    this.context.delete(page.ref);
3✔
705
  }
706

707
  /**
708
   * Add a page to the end of this document. This method accepts three
709
   * different value types for the `page` parameter:
710
   *
711
   * | Type               | Behavior                                                                            |
712
   * | ------------------ | ----------------------------------------------------------------------------------- |
713
   * | `undefined`        | Create a new page and add it to the end of this document                            |
714
   * | `[number, number]` | Create a new page with the given dimensions and add it to the end of this document  |
715
   * | `PDFPage`          | Add the existing page to the end of this document                                   |
716
   *
717
   * For example:
718
   * ```js
719
   * // page=undefined
720
   * const newPage = pdfDoc.addPage()
721
   *
722
   * // page=[number, number]
723
   * import { PageSizes } from 'pdf-lib'
724
   * const newPage1 = pdfDoc.addPage(PageSizes.A7)
725
   * const newPage2 = pdfDoc.addPage(PageSizes.Letter)
726
   * const newPage3 = pdfDoc.addPage([500, 750])
727
   *
728
   * // page=PDFPage
729
   * const pdfDoc1 = await PDFDocument.create()
730
   * const pdfDoc2 = await PDFDocument.load(...)
731
   * const [existingPage] = await pdfDoc1.copyPages(pdfDoc2, [0])
732
   * pdfDoc1.addPage(existingPage)
733
   * ```
734
   *
735
   * @param page Optionally, the desired dimensions or existing page.
736
   * @returns The newly created (or existing) page.
737
   */
738
  addPage(page?: PDFPage | [number, number]): PDFPage {
739
    assertIs(page, 'page', ['undefined', [PDFPage, 'PDFPage'], Array]);
39✔
740
    return this.insertPage(this.getPageCount(), page);
39✔
741
  }
742

743
  /**
744
   * Insert a page at a given index within this document. This method accepts
745
   * three different value types for the `page` parameter:
746
   *
747
   * | Type               | Behavior                                                                       |
748
   * | ------------------ | ------------------------------------------------------------------------------ |
749
   * | `undefined`        | Create a new page and insert it into this document                             |
750
   * | `[number, number]` | Create a new page with the given dimensions and insert it into this document   |
751
   * | `PDFPage`          | Insert the existing page into this document                                    |
752
   *
753
   * For example:
754
   * ```js
755
   * // page=undefined
756
   * const newPage = pdfDoc.insertPage(2)
757
   *
758
   * // page=[number, number]
759
   * import { PageSizes } from 'pdf-lib'
760
   * const newPage1 = pdfDoc.insertPage(2, PageSizes.A7)
761
   * const newPage2 = pdfDoc.insertPage(0, PageSizes.Letter)
762
   * const newPage3 = pdfDoc.insertPage(198, [500, 750])
763
   *
764
   * // page=PDFPage
765
   * const pdfDoc1 = await PDFDocument.create()
766
   * const pdfDoc2 = await PDFDocument.load(...)
767
   * const [existingPage] = await pdfDoc1.copyPages(pdfDoc2, [0])
768
   * pdfDoc1.insertPage(0, existingPage)
769
   * ```
770
   *
771
   * @param index The index at which the page should be inserted (zero-based).
772
   * @param page Optionally, the desired dimensions or existing page.
773
   * @returns The newly created (or existing) page.
774
   */
775
  insertPage(index: number, page?: PDFPage | [number, number]): PDFPage {
776
    const pageCount = this.getPageCount();
42✔
777
    assertRange(index, 'index', 0, pageCount);
42✔
778
    assertIs(page, 'page', ['undefined', [PDFPage, 'PDFPage'], Array]);
42✔
779
    if (!page || Array.isArray(page)) {
42✔
780
      const dims = Array.isArray(page) ? page : PageSizes.A4;
40✔
781
      page = PDFPage.create(this);
40✔
782
      page.setSize(...dims);
40✔
783
    } else if (page.doc !== this) {
2!
784
      throw new ForeignPageError();
×
785
    }
786

787
    const parentRef = this.catalog.insertLeafNode(page.ref, index);
42✔
788
    page.node.setParent(parentRef);
42✔
789

790
    this.pageMap.set(page.node, page);
42✔
791
    this.pageCache.invalidate();
42✔
792

793
    this.pageCount = pageCount + 1;
42✔
794

795
    return page;
42✔
796
  }
797

798
  /**
799
   * Copy pages from a source document into this document. Allows pages to be
800
   * copied between different [[PDFDocument]] instances. For example:
801
   * ```js
802
   * const pdfDoc = await PDFDocument.create()
803
   * const srcDoc = await PDFDocument.load(...)
804
   *
805
   * const copiedPages = await pdfDoc.copyPages(srcDoc, [0, 3, 89])
806
   * const [firstPage, fourthPage, ninetiethPage] = copiedPages;
807
   *
808
   * pdfDoc.addPage(fourthPage)
809
   * pdfDoc.insertPage(0, ninetiethPage)
810
   * pdfDoc.addPage(firstPage)
811
   * ```
812
   * @param srcDoc The document from which pages should be copied.
813
   * @param indices The indices of the pages that should be copied.
814
   * @returns Resolves with an array of pages copied into this document.
815
   */
816
  async copyPages(srcDoc: PDFDocument, indices: number[]): Promise<PDFPage[]> {
817
    assertIs(srcDoc, 'srcDoc', [[PDFDocument, 'PDFDocument']]);
1✔
818
    assertIs(indices, 'indices', [Array]);
1✔
819
    await srcDoc.flush();
1✔
820
    const copier = PDFObjectCopier.for(srcDoc.context, this.context);
1✔
821
    const srcPages = srcDoc.getPages();
1✔
822
    // Copy each page in a separate thread
823
    const copiedPages = indices
1✔
824
      .map((i) => srcPages[i])
2✔
825
      .map(async (page) => copier.copy(page.node))
2✔
826
      .map((p) =>
827
        p.then((copy) => PDFPage.of(copy, this.context.register(copy), this)),
2✔
828
      );
829
    return Promise.all(copiedPages);
1✔
830
  }
831

832
  /**
833
   * Get a copy of this document.
834
   *
835
   * For example:
836
   * ```js
837
   * const srcDoc = await PDFDocument.load(...)
838
   * const pdfDoc = await srcDoc.copy()
839
   * ```
840
   *
841
   * > **NOTE:**  This method won't copy all information over to the new
842
   * > document (acroforms, outlines, etc...).
843
   *
844
   * @returns Resolves with a copy this document.
845
   */
846
  async copy(): Promise<PDFDocument> {
847
    const pdfCopy = await PDFDocument.create();
1✔
848
    const contentPages = await pdfCopy.copyPages(this, this.getPageIndices());
1✔
849

850
    for (let idx = 0, len = contentPages.length; idx < len; idx++) {
1✔
851
      pdfCopy.addPage(contentPages[idx]);
2✔
852
    }
853

854
    if (this.getAuthor() !== undefined) {
1✔
855
      pdfCopy.setAuthor(this.getAuthor()!);
1✔
856
    }
857
    if (this.getCreationDate() !== undefined) {
1✔
858
      pdfCopy.setCreationDate(this.getCreationDate()!);
1✔
859
    }
860
    if (this.getCreator() !== undefined) {
1✔
861
      pdfCopy.setCreator(this.getCreator()!);
1✔
862
    }
863
    if (this.getLanguage() !== undefined) {
1!
864
      pdfCopy.setLanguage(this.getLanguage()!);
×
865
    }
866
    if (this.getModificationDate() !== undefined) {
1✔
867
      pdfCopy.setModificationDate(this.getModificationDate()!);
1✔
868
    }
869
    if (this.getProducer() !== undefined) {
1✔
870
      pdfCopy.setProducer(this.getProducer()!);
1✔
871
    }
872
    if (this.getSubject() !== undefined) {
1✔
873
      pdfCopy.setSubject(this.getSubject()!);
1✔
874
    }
875
    if (this.getTitle() !== undefined) {
1✔
876
      pdfCopy.setTitle(this.getTitle()!);
1✔
877
    }
878
    pdfCopy.defaultWordBreaks = this.defaultWordBreaks;
1✔
879

880
    return pdfCopy;
1✔
881
  }
882

883
  /**
884
   * Add JavaScript to this document. The supplied `script` is executed when the
885
   * document is opened. The `script` can be used to perform some operation
886
   * when the document is opened (e.g. logging to the console), or it can be
887
   * used to define a function that can be referenced later in a JavaScript
888
   * action. For example:
889
   * ```js
890
   * // Show "Hello World!" in the console when the PDF is opened
891
   * pdfDoc.addJavaScript(
892
   *   'main',
893
   *   'console.show(); console.println("Hello World!");'
894
   * );
895
   *
896
   * // Define a function named "foo" that can be called in JavaScript Actions
897
   * pdfDoc.addJavaScript(
898
   *   'foo',
899
   *   'function foo() { return "foo"; }'
900
   * );
901
   * ```
902
   * See the [JavaScript for Acrobat API Reference](https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/js_api_reference.pdf)
903
   * for details.
904
   * @param name The name of the script. Must be unique per document.
905
   * @param script The JavaScript to execute.
906
   */
907
  addJavaScript(name: string, script: string) {
908
    assertIs(name, 'name', ['string']);
3✔
909
    assertIs(script, 'script', ['string']);
3✔
910

911
    const embedder = JavaScriptEmbedder.for(script, name);
3✔
912

913
    const ref = this.context.nextRef();
3✔
914
    const javaScript = PDFJavaScript.of(ref, this, embedder);
3✔
915
    this.javaScripts.push(javaScript);
3✔
916
  }
917

918
  /**
919
   * Add an attachment to this document. Attachments are visible in the
920
   * "Attachments" panel of Adobe Acrobat and some other PDF readers. Any
921
   * type of file can be added as an attachment. This includes, but is not
922
   * limited to, `.png`, `.jpg`, `.pdf`, `.csv`, `.docx`, and `.xlsx` files.
923
   *
924
   * The input data can be provided in multiple formats:
925
   *
926
   * | Type          | Contents                                                       |
927
   * | ------------- | -------------------------------------------------------------- |
928
   * | `string`      | A base64 encoded string (or data URI) containing an attachment |
929
   * | `Uint8Array`  | The raw bytes of an attachment                                 |
930
   * | `ArrayBuffer` | The raw bytes of an attachment                                 |
931
   *
932
   * For example:
933
   * ```js
934
   * // attachment=string
935
   * await pdfDoc.attach('/9j/4AAQSkZJRgABAQAAAQABAAD/2wBD...', 'cat_riding_unicorn.jpg', {
936
   *   mimeType: 'image/jpeg',
937
   *   description: 'Cool cat riding a unicorn! 🦄🐈🕶️',
938
   *   creationDate: new Date('2019/12/01'),
939
   *   modificationDate: new Date('2020/04/19'),
940
   * })
941
   * await pdfDoc.attach('data:image/jpeg;base64,/9j/4AAQ...', 'cat_riding_unicorn.jpg', {
942
   *   mimeType: 'image/jpeg',
943
   *   description: 'Cool cat riding a unicorn! 🦄🐈🕶️',
944
   *   creationDate: new Date('2019/12/01'),
945
   *   modificationDate: new Date('2020/04/19'),
946
   * })
947
   *
948
   * // attachment=Uint8Array
949
   * import fs from 'fs'
950
   * const uint8Array = fs.readFileSync('cat_riding_unicorn.jpg')
951
   * await pdfDoc.attach(uint8Array, 'cat_riding_unicorn.jpg', {
952
   *   mimeType: 'image/jpeg',
953
   *   description: 'Cool cat riding a unicorn! 🦄🐈🕶️',
954
   *   creationDate: new Date('2019/12/01'),
955
   *   modificationDate: new Date('2020/04/19'),
956
   * })
957
   *
958
   * // attachment=ArrayBuffer
959
   * const url = 'https://pdf-lib.js.org/assets/cat_riding_unicorn.jpg'
960
   * const arrayBuffer = await fetch(url).then(res => res.arrayBuffer())
961
   * await pdfDoc.attach(arrayBuffer, 'cat_riding_unicorn.jpg', {
962
   *   mimeType: 'image/jpeg',
963
   *   description: 'Cool cat riding a unicorn! 🦄🐈🕶️',
964
   *   creationDate: new Date('2019/12/01'),
965
   *   modificationDate: new Date('2020/04/19'),
966
   * })
967
   * ```
968
   *
969
   * @param attachment The input data containing the file to be attached.
970
   * @param name The name of the file to be attached.
971
   * @returns Resolves when the attachment is complete.
972
   */
973
  async attach(
974
    attachment: string | Uint8Array | ArrayBuffer,
975
    name: string,
976
    options: AttachmentOptions = {},
×
977
  ): Promise<void> {
978
    assertIs(attachment, 'attachment', ['string', Uint8Array, ArrayBuffer]);
9✔
979
    assertIs(name, 'name', ['string']);
9✔
980
    assertOrUndefined(options.mimeType, 'mimeType', ['string']);
9✔
981
    assertOrUndefined(options.description, 'description', ['string']);
9✔
982
    assertOrUndefined(options.creationDate, 'options.creationDate', [Date]);
9✔
983
    assertOrUndefined(options.modificationDate, 'options.modificationDate', [
9✔
984
      Date,
985
    ]);
986
    assertIsOneOfOrUndefined(
9✔
987
      options.afRelationship,
988
      'options.afRelationship',
989
      AFRelationship,
990
    );
991

992
    const bytes = toUint8Array(attachment);
9✔
993
    const embedder = FileEmbedder.for(bytes, name, options);
9✔
994

995
    const ref = this.context.nextRef();
9✔
996
    const embeddedFile = PDFEmbeddedFile.of(ref, this, embedder);
9✔
997
    this.embeddedFiles.push(embeddedFile);
9✔
998
  }
999

1000
  private getRawAttachments() {
1001
    if (!this.catalog.has(PDFName.of('Names'))) return [];
3!
1002
    const Names = this.catalog.lookup(PDFName.of('Names'), PDFDict);
3✔
1003

1004
    if (!Names.has(PDFName.of('EmbeddedFiles'))) return [];
3!
1005
    const EmbeddedFiles = Names.lookup(PDFName.of('EmbeddedFiles'), PDFDict);
3✔
1006

1007
    if (!EmbeddedFiles.has(PDFName.of('Names'))) return [];
3!
1008
    const EFNames = EmbeddedFiles.lookup(PDFName.of('Names'), PDFArray);
3✔
1009

1010
    const rawAttachments = [];
3✔
1011
    for (let idx = 0, len = EFNames.size(); idx < len; idx += 2) {
3✔
1012
      const fileName = EFNames.lookup(idx) as PDFHexString | PDFString;
7✔
1013
      const fileSpec = EFNames.lookup(idx + 1, PDFDict);
7✔
1014
      rawAttachments.push({ fileName, fileSpec });
7✔
1015
    }
1016

1017
    return rawAttachments;
3✔
1018
  }
1019

1020
  private getSavedAttachments(): PDFAttachment[] {
1021
    const rawAttachments = this.getRawAttachments();
3✔
1022
    return rawAttachments.flatMap(({ fileName, fileSpec }) => {
3✔
1023
      const efDict = fileSpec.lookup(PDFName.of('EF'));
7✔
1024
      if (!(efDict instanceof PDFDict)) return [];
7!
1025

1026
      const stream = efDict.lookup(PDFName.of('F'));
7✔
1027
      if (!(stream instanceof PDFStream)) return [];
7!
1028

1029
      const afr = fileSpec.lookup(PDFName.of('AFRelationship'));
7✔
1030
      const afRelationship =
1031
        afr instanceof PDFName
7✔
1032
          ? afr.toString().slice(1) // Remove leading slash
1033
          : afr instanceof PDFString
4!
1034
            ? afr.decodeText()
1035
            : undefined;
1036

1037
      const embeddedFileDict = stream.dict;
7✔
1038
      const subtype = embeddedFileDict.lookup(PDFName.of('Subtype'));
7✔
1039

1040
      const mimeType =
1041
        subtype instanceof PDFName
7!
1042
          ? subtype.toString().slice(1)
1043
          : subtype instanceof PDFString
×
1044
            ? subtype.decodeText()
1045
            : undefined;
1046

1047
      const paramsDict = embeddedFileDict.lookup(PDFName.of('Params'), PDFDict);
7✔
1048

1049
      let creationDate: Date | undefined;
1050
      let modificationDate: Date | undefined;
1051

1052
      if (paramsDict instanceof PDFDict) {
7✔
1053
        const creationDateRaw = paramsDict.lookup(PDFName.of('CreationDate'));
7✔
1054
        const modDateRaw = paramsDict.lookup(PDFName.of('ModDate'));
7✔
1055

1056
        if (creationDateRaw instanceof PDFString) {
7✔
1057
          creationDate = creationDateRaw.decodeDate();
4✔
1058
        }
1059

1060
        if (modDateRaw instanceof PDFString) {
7✔
1061
          modificationDate = modDateRaw.decodeDate();
4✔
1062
        }
1063
      }
1064

1065
      const description = (
1066
        fileSpec.lookup(PDFName.of('Desc')) as PDFHexString
7✔
1067
      ).decodeText();
1068

1069
      return [
7✔
1070
        {
1071
          name: fileName.decodeText(),
1072
          data: decodePDFRawStream(stream as PDFRawStream).decode(),
1073
          mimeType: mimeType?.replace(/#([0-9A-Fa-f]{2})/g, (_, hex) =>
21!
1074
            String.fromCharCode(parseInt(hex, 16)),
7✔
1075
          ),
1076
          afRelationship: afRelationship as AFRelationship,
1077
          description,
1078
          creationDate,
1079
          modificationDate,
1080
        },
1081
      ];
1082
    });
1083
  }
1084

1085
  private getUnsavedAttachments(): PDFAttachment[] {
1086
    const attachments = this.embeddedFiles.map((file) => {
3✔
1087
      const embedder = file.getEmbedder();
2✔
1088

1089
      return {
2✔
1090
        name: embedder.fileName,
1091
        data: embedder.getFileData(),
1092
        description: embedder.options.description,
1093
        mimeType: embedder.options.mimeType,
1094
        afRelationship: embedder.options.afRelationship,
1095
        creationDate: embedder.options.creationDate,
1096
        modificationDate: embedder.options.modificationDate,
1097
      };
1098
    });
1099

1100
    return attachments;
3✔
1101
  }
1102

1103
  /**
1104
   * Get all attachments that are embedded in this document.
1105
   *
1106
   * @returns Array of attachments with name and data
1107
   */
1108
  getAttachments(): PDFAttachment[] {
1109
    const savedAttachments = this.getSavedAttachments();
3✔
1110
    const unsavedAttachments = this.getUnsavedAttachments();
3✔
1111

1112
    return [...savedAttachments, ...unsavedAttachments];
3✔
1113
  }
1114

1115
  /**
1116
   * Embed a font into this document. The input data can be provided in multiple
1117
   * formats:
1118
   *
1119
   * | Type            | Contents                                                |
1120
   * | --------------- | ------------------------------------------------------- |
1121
   * | `StandardFonts` | One of the standard 14 fonts                            |
1122
   * | `string`        | A base64 encoded string (or data URI) containing a font |
1123
   * | `Uint8Array`    | The raw bytes of a font                                 |
1124
   * | `ArrayBuffer`   | The raw bytes of a font                                 |
1125
   *
1126
   * For example:
1127
   * ```js
1128
   * // font=StandardFonts
1129
   * import { StandardFonts } from 'pdf-lib'
1130
   * const font1 = await pdfDoc.embedFont(StandardFonts.Helvetica)
1131
   *
1132
   * // font=string
1133
   * const font2 = await pdfDoc.embedFont('AAEAAAAVAQAABABQRFNJRx/upe...')
1134
   * const font3 = await pdfDoc.embedFont('data:font/opentype;base64,AAEAAA...')
1135
   *
1136
   * // font=Uint8Array
1137
   * import fs from 'fs'
1138
   * const font4 = await pdfDoc.embedFont(fs.readFileSync('Ubuntu-R.ttf'))
1139
   *
1140
   * // font=ArrayBuffer
1141
   * const url = 'https://pdf-lib.js.org/assets/ubuntu/Ubuntu-R.ttf'
1142
   * const ubuntuBytes = await fetch(url).then(res => res.arrayBuffer())
1143
   * const font5 = await pdfDoc.embedFont(ubuntuBytes)
1144
   * ```
1145
   * See also: [[registerFontkit]]
1146
   * @param font The input data for a font.
1147
   * @param options The options to be used when embedding the font.
1148
   * @returns Resolves with the embedded font.
1149
   */
1150
  async embedFont(
1151
    font: StandardFonts | string | Uint8Array | ArrayBuffer,
1152
    options: EmbedFontOptions = {},
28✔
1153
  ): Promise<PDFFont> {
1154
    const { subset = false, customName, features } = options;
28✔
1155

1156
    assertIs(font, 'font', ['string', Uint8Array, ArrayBuffer]);
28✔
1157
    assertIs(subset, 'subset', ['boolean']);
28✔
1158

1159
    let embedder: CustomFontEmbedder | StandardFontEmbedder;
1160
    if (isStandardFont(font)) {
28✔
1161
      embedder = StandardFontEmbedder.for(font, customName);
26✔
1162
    } else if (canBeConvertedToUint8Array(font)) {
2!
1163
      const bytes = toUint8Array(font);
2✔
1164
      const fontkit = this.assertFontkit();
2✔
1165
      embedder = subset
2!
1166
        ? await CustomFontSubsetEmbedder.for(
1167
            fontkit,
1168
            bytes,
1169
            customName,
1170
            features,
1171
          )
1172
        : await CustomFontEmbedder.for(fontkit, bytes, customName, features);
1173
    } else {
1174
      throw new TypeError(
×
1175
        '`font` must be one of `StandardFonts | string | Uint8Array | ArrayBuffer`',
1176
      );
1177
    }
1178

1179
    const ref = this.context.nextRef();
28✔
1180
    const pdfFont = PDFFont.of(ref, this, embedder);
28✔
1181
    this.fonts.push(pdfFont);
28✔
1182

1183
    return pdfFont;
28✔
1184
  }
1185

1186
  /**
1187
   * Embed a standard font into this document.
1188
   * For example:
1189
   * ```js
1190
   * import { StandardFonts } from 'pdf-lib'
1191
   * const helveticaFont = pdfDoc.embedFont(StandardFonts.Helvetica)
1192
   * ```
1193
   * @param font The standard font to be embedded.
1194
   * @param customName The name to be used when embedding the font.
1195
   * @returns The embedded font.
1196
   */
1197
  embedStandardFont(font: StandardFonts, customName?: string): PDFFont {
1198
    assertIs(font, 'font', ['string']);
15✔
1199
    if (!isStandardFont(font)) {
15✔
1200
      throw new TypeError('`font` must be one of type `StandardFonts`');
1✔
1201
    }
1202

1203
    const embedder = StandardFontEmbedder.for(font, customName);
14✔
1204

1205
    const ref = this.context.nextRef();
14✔
1206
    const pdfFont = PDFFont.of(ref, this, embedder);
14✔
1207
    this.fonts.push(pdfFont);
14✔
1208

1209
    return pdfFont;
14✔
1210
  }
1211

1212
  /**
1213
   * Embed a JPEG image into this document. The input data can be provided in
1214
   * multiple formats:
1215
   *
1216
   * | Type          | Contents                                                      |
1217
   * | ------------- | ------------------------------------------------------------- |
1218
   * | `string`      | A base64 encoded string (or data URI) containing a JPEG image |
1219
   * | `Uint8Array`  | The raw bytes of a JPEG image                                 |
1220
   * | `ArrayBuffer` | The raw bytes of a JPEG image                                 |
1221
   *
1222
   * For example:
1223
   * ```js
1224
   * // jpg=string
1225
   * const image1 = await pdfDoc.embedJpg('/9j/4AAQSkZJRgABAQAAAQABAAD/2wBD...')
1226
   * const image2 = await pdfDoc.embedJpg('data:image/jpeg;base64,/9j/4AAQ...')
1227
   *
1228
   * // jpg=Uint8Array
1229
   * import fs from 'fs'
1230
   * const uint8Array = fs.readFileSync('cat_riding_unicorn.jpg')
1231
   * const image3 = await pdfDoc.embedJpg(uint8Array)
1232
   *
1233
   * // jpg=ArrayBuffer
1234
   * const url = 'https://pdf-lib.js.org/assets/cat_riding_unicorn.jpg'
1235
   * const arrayBuffer = await fetch(url).then(res => res.arrayBuffer())
1236
   * const image4 = await pdfDoc.embedJpg(arrayBuffer)
1237
   * ```
1238
   *
1239
   * @param jpg The input data for a JPEG image.
1240
   * @returns Resolves with the embedded image.
1241
   */
1242
  async embedJpg(jpg: string | Uint8Array | ArrayBuffer): Promise<PDFImage> {
1243
    assertIs(jpg, 'jpg', ['string', Uint8Array, ArrayBuffer]);
×
1244
    const bytes = toUint8Array(jpg);
×
1245
    const embedder = await JpegEmbedder.for(bytes);
×
1246
    const ref = this.context.nextRef();
×
1247
    const pdfImage = PDFImage.of(ref, this, embedder);
×
1248
    this.images.push(pdfImage);
×
1249
    return pdfImage;
×
1250
  }
1251

1252
  /**
1253
   * Embed a PNG image into this document. The input data can be provided in
1254
   * multiple formats:
1255
   *
1256
   * | Type          | Contents                                                     |
1257
   * | ------------- | ------------------------------------------------------------ |
1258
   * | `string`      | A base64 encoded string (or data URI) containing a PNG image |
1259
   * | `Uint8Array`  | The raw bytes of a PNG image                                 |
1260
   * | `ArrayBuffer` | The raw bytes of a PNG image                                 |
1261
   *
1262
   * For example:
1263
   * ```js
1264
   * // png=string
1265
   * const image1 = await pdfDoc.embedPng('iVBORw0KGgoAAAANSUhEUgAAAlgAAAF3...')
1266
   * const image2 = await pdfDoc.embedPng('data:image/png;base64,iVBORw0KGg...')
1267
   *
1268
   * // png=Uint8Array
1269
   * import fs from 'fs'
1270
   * const uint8Array = fs.readFileSync('small_mario.png')
1271
   * const image3 = await pdfDoc.embedPng(uint8Array)
1272
   *
1273
   * // png=ArrayBuffer
1274
   * const url = 'https://pdf-lib.js.org/assets/small_mario.png'
1275
   * const arrayBuffer = await fetch(url).then(res => res.arrayBuffer())
1276
   * const image4 = await pdfDoc.embedPng(arrayBuffer)
1277
   * ```
1278
   *
1279
   * @param png The input data for a PNG image.
1280
   * @returns Resolves with the embedded image.
1281
   */
1282
  async embedPng(png: string | Uint8Array | ArrayBuffer): Promise<PDFImage> {
1283
    assertIs(png, 'png', ['string', Uint8Array, ArrayBuffer]);
4✔
1284
    const bytes = toUint8Array(png);
4✔
1285
    const embedder = await PngEmbedder.for(bytes);
4✔
1286
    const ref = this.context.nextRef();
4✔
1287
    const pdfImage = PDFImage.of(ref, this, embedder);
4✔
1288
    this.images.push(pdfImage);
4✔
1289
    return pdfImage;
4✔
1290
  }
1291

1292
  async embedSvg(svg: string): Promise<PDFSvg> {
1293
    if (!svg) return new PDFSvg(svg);
×
1294
    const parsedSvg = parseHtml(svg);
×
1295
    const findImages = (element: HTMLElement): HTMLElement[] => {
×
1296
      if (element.tagName === 'image') return [element];
×
1297
      else {
1298
        return element.childNodes
×
1299
          .map((child) =>
1300
            child.nodeType === NodeType.ELEMENT_NODE ? findImages(child) : [],
×
1301
          )
1302
          .flat();
1303
      }
1304
    };
1305
    const images = findImages(parsedSvg);
×
1306
    const imagesDict = {} as Record<string, PDFImage>;
×
1307

1308
    await Promise.all(
×
1309
      images.map(async (image) => {
×
1310
        const href = image.attributes.href ?? image.attributes['xlink:href'];
×
1311
        if (!href || imagesDict[href]) return;
×
1312
        const isPng = href.match(/\.png(\?|$)|^data:image\/png;base64/gim);
×
1313
        const pdfImage = isPng
×
1314
          ? await this.embedPng(href)
1315
          : await this.embedJpg(href);
1316
        imagesDict[href] = pdfImage;
×
1317
      }),
1318
    );
1319

1320
    return new PDFSvg(svg, imagesDict);
×
1321
  }
1322
  /**
1323
   * Embed one or more PDF pages into this document.
1324
   *
1325
   * For example:
1326
   * ```js
1327
   * const pdfDoc = await PDFDocument.create()
1328
   *
1329
   * const sourcePdfUrl = 'https://pdf-lib.js.org/assets/with_large_page_count.pdf'
1330
   * const sourcePdf = await fetch(sourcePdfUrl).then((res) => res.arrayBuffer())
1331
   *
1332
   * // Embed page 74 of `sourcePdf` into `pdfDoc`
1333
   * const [embeddedPage] = await pdfDoc.embedPdf(sourcePdf, [73])
1334
   * ```
1335
   *
1336
   * See [[PDFDocument.load]] for examples of the allowed input data formats.
1337
   *
1338
   * @param pdf The input data containing a PDF document.
1339
   * @param indices The indices of the pages that should be embedded.
1340
   * @returns Resolves with an array of the embedded pages.
1341
   */
1342
  async embedPdf(
1343
    pdf: string | Uint8Array | ArrayBuffer | PDFDocument,
1344
    indices: number[] = [0],
×
1345
  ): Promise<PDFEmbeddedPage[]> {
1346
    assertIs(pdf, 'pdf', [
×
1347
      'string',
1348
      Uint8Array,
1349
      ArrayBuffer,
1350
      [PDFDocument, 'PDFDocument'],
1351
    ]);
1352
    assertIs(indices, 'indices', [Array]);
×
1353

1354
    const srcDoc =
1355
      pdf instanceof PDFDocument ? pdf : await PDFDocument.load(pdf);
×
1356

1357
    const srcPages = pluckIndices(srcDoc.getPages(), indices);
×
1358

1359
    return this.embedPages(srcPages);
×
1360
  }
1361

1362
  /**
1363
   * Embed a single PDF page into this document.
1364
   *
1365
   * For example:
1366
   * ```js
1367
   * const pdfDoc = await PDFDocument.create()
1368
   *
1369
   * const sourcePdfUrl = 'https://pdf-lib.js.org/assets/with_large_page_count.pdf'
1370
   * const sourceBuffer = await fetch(sourcePdfUrl).then((res) => res.arrayBuffer())
1371
   * const sourcePdfDoc = await PDFDocument.load(sourceBuffer)
1372
   * const sourcePdfPage = sourcePdfDoc.getPages()[73]
1373
   *
1374
   * const embeddedPage = await pdfDoc.embedPage(
1375
   *   sourcePdfPage,
1376
   *
1377
   *   // Clip a section of the source page so that we only embed part of it
1378
   *   { left: 100, right: 450, bottom: 330, top: 570 },
1379
   *
1380
   *   // Translate all drawings of the embedded page by (10, 200) units
1381
   *   [1, 0, 0, 1, 10, 200],
1382
   * )
1383
   * ```
1384
   *
1385
   * @param page The page to be embedded.
1386
   * @param boundingBox
1387
   * Optionally, an area of the source page that should be embedded
1388
   * (defaults to entire page).
1389
   * @param transformationMatrix
1390
   * Optionally, a transformation matrix that is always applied to the embedded
1391
   * page anywhere it is drawn.
1392
   * @returns Resolves with the embedded pdf page.
1393
   */
1394
  async embedPage(
1395
    page: PDFPage,
1396
    boundingBox?: PageBoundingBox,
1397
    transformationMatrix?: TransformationMatrix,
1398
  ): Promise<PDFEmbeddedPage> {
1399
    assertIs(page, 'page', [[PDFPage, 'PDFPage']]);
×
1400
    const [embeddedPage] = await this.embedPages(
×
1401
      [page],
1402
      [boundingBox],
1403
      [transformationMatrix],
1404
    );
1405
    return embeddedPage;
×
1406
  }
1407

1408
  /**
1409
   * Embed one or more PDF pages into this document.
1410
   *
1411
   * For example:
1412
   * ```js
1413
   * const pdfDoc = await PDFDocument.create()
1414
   *
1415
   * const sourcePdfUrl = 'https://pdf-lib.js.org/assets/with_large_page_count.pdf'
1416
   * const sourceBuffer = await fetch(sourcePdfUrl).then((res) => res.arrayBuffer())
1417
   * const sourcePdfDoc = await PDFDocument.load(sourceBuffer)
1418
   *
1419
   * const page1 = sourcePdfDoc.getPages()[0]
1420
   * const page2 = sourcePdfDoc.getPages()[52]
1421
   * const page3 = sourcePdfDoc.getPages()[73]
1422
   *
1423
   * const embeddedPages = await pdfDoc.embedPages([page1, page2, page3])
1424
   * ```
1425
   *
1426
   * @param page
1427
   * The pages to be embedded (they must all share the same context).
1428
   * @param boundingBoxes
1429
   * Optionally, an array of clipping boundaries - one for each page
1430
   * (defaults to entirety of each page).
1431
   * @param transformationMatrices
1432
   * Optionally, an array of transformation matrices - one for each page
1433
   * (each page's transformation will apply anywhere it is drawn).
1434
   * @returns Resolves with an array of the embedded pdf pages.
1435
   */
1436
  async embedPages(
1437
    pages: PDFPage[],
1438
    boundingBoxes: (PageBoundingBox | undefined)[] = [],
×
1439
    transformationMatrices: (TransformationMatrix | undefined)[] = [],
×
1440
  ) {
1441
    if (pages.length === 0) return [];
×
1442

1443
    // Assert all pages have the same context
1444
    for (let idx = 0, len = pages.length - 1; idx < len; idx++) {
×
1445
      const currPage = pages[idx];
×
1446
      const nextPage = pages[idx + 1];
×
1447
      if (currPage.node.context !== nextPage.node.context) {
×
1448
        throw new PageEmbeddingMismatchedContextError();
×
1449
      }
1450
    }
1451

1452
    const context = pages[0].node.context;
×
1453
    const maybeCopyPage =
1454
      context === this.context
×
1455
        ? (p: PDFPageLeaf) => p
×
1456
        : PDFObjectCopier.for(context, this.context).copy;
1457

1458
    const embeddedPages = new Array<PDFEmbeddedPage>(pages.length);
×
1459
    for (let idx = 0, len = pages.length; idx < len; idx++) {
×
1460
      const page = maybeCopyPage(pages[idx].node);
×
1461
      const box = boundingBoxes[idx];
×
1462
      const matrix = transformationMatrices[idx];
×
1463

1464
      const embedder = await PDFPageEmbedder.for(page, box, matrix);
×
1465

1466
      const ref = this.context.nextRef();
×
1467
      embeddedPages[idx] = PDFEmbeddedPage.of(ref, this, embedder);
×
1468
    }
1469

1470
    this.embeddedPages.push(...embeddedPages);
×
1471

1472
    return embeddedPages;
×
1473
  }
1474

1475
  encrypt(options: SecurityOptions) {
1476
    this.context.security = PDFSecurity.create(this.context, options).encrypt();
×
1477
  }
1478

1479
  /**
1480
   * > **NOTE:** You shouldn't need to call this method directly. The [[save]]
1481
   * > and [[saveAsBase64]] methods will automatically ensure that all embedded
1482
   * > assets are flushed before serializing the document.
1483
   *
1484
   * Flush all embedded fonts, PDF pages, and images to this document's
1485
   * [[context]].
1486
   *
1487
   * @returns Resolves when the flush is complete.
1488
   */
1489
  async flush(): Promise<void> {
1490
    await this.embedAll(this.fonts);
49✔
1491
    await this.embedAll(this.images);
49✔
1492
    await this.embedAll(this.embeddedPages);
49✔
1493
    await this.embedAll(this.embeddedFiles);
49✔
1494
    await this.embedAll(this.javaScripts);
49✔
1495
  }
1496

1497
  /**
1498
   * Serialize this document to an array of bytes making up a PDF file.
1499
   * For example:
1500
   * ```js
1501
   * const pdfBytes = await pdfDoc.save()
1502
   * ```
1503
   *
1504
   * There are a number of things you can do with the serialized document,
1505
   * depending on the JavaScript environment you're running in:
1506
   * * Write it to a file in Node or React Native
1507
   * * Download it as a Blob in the browser
1508
   * * Render it in an `iframe`
1509
   *
1510
   * @param options The options to be used when saving the document.
1511
   * @returns Resolves with the bytes of the serialized document.
1512
   */
1513
  async save(options: SaveOptions = {}): Promise<Uint8Array> {
20✔
1514
    // check PDF version
1515
    const vparts = this.context.header.getVersionString().split('.');
35✔
1516
    const uOS =
1517
      options.rewrite || Number(vparts[0]) > 1 || Number(vparts[1]) >= 5;
35✔
1518
    const {
1519
      useObjectStreams = uOS,
27✔
1520
      objectsPerTick = 50,
35✔
1521
      rewrite = false,
31✔
1522
    } = options;
35✔
1523

1524
    assertIs(useObjectStreams, 'useObjectStreams', ['boolean']);
35✔
1525
    assertIs(objectsPerTick, 'objectsPerTick', ['number']);
35✔
1526
    assertIs(rewrite, 'rewrite', ['boolean']);
35✔
1527
    const incrementalUpdate =
1528
      !rewrite &&
35✔
1529
      this.context.pdfFileDetails.originalBytes &&
1530
      this.context.snapshot;
1531
    if (incrementalUpdate) {
35✔
1532
      options.addDefaultPage = false;
15✔
1533
      options.updateFieldAppearances = false;
15✔
1534
    }
1535

1536
    await this.prepareForSave(options);
35✔
1537

1538
    const Writer = useObjectStreams ? PDFStreamWriter : PDFWriter;
35✔
1539
    if (incrementalUpdate) {
35✔
1540
      const increment = await Writer.forContextWithSnapshot(
15✔
1541
        this.context,
1542
        objectsPerTick,
1543
        this.context.snapshot!,
1544
      ).serializeToBuffer();
1545
      const result = new Uint8Array(
15✔
1546
        this.context.pdfFileDetails.originalBytes!.byteLength +
1547
          increment.byteLength,
1548
      );
1549
      result.set(this.context.pdfFileDetails.originalBytes!);
15✔
1550
      result.set(
15✔
1551
        increment,
1552
        this.context.pdfFileDetails.originalBytes!.byteLength,
1553
      );
1554
      return result;
15✔
1555
    }
1556
    return Writer.forContext(this.context, objectsPerTick).serializeToBuffer();
20✔
1557
  }
1558

1559
  /**
1560
   * Serialize only the changes to this document to an array of bytes making up a PDF file.
1561
   * For example:
1562
   * ```js
1563
   * const snapshot = pdfDoc.takeSnapshot();
1564
   * ...
1565
   * const pdfBytes = await pdfDoc.saveIncremental(snapshot);
1566
   * ```
1567
   *
1568
   * Similar to [[save]] function.
1569
   * The changes are saved in an incremental way, the result buffer
1570
   * will contain only the differences
1571
   *
1572
   * @param snapshot The snapshot to be used when saving the document.
1573
   * @param options The options to be used when saving the document.
1574
   * @returns Resolves with the bytes of the serialized document.
1575
   */
1576
  async saveIncremental(
1577
    snapshot: DocumentSnapshot,
1578
    options: IncrementalSaveOptions = {},
6✔
1579
  ): Promise<Uint8Array> {
1580
    // check PDF version
1581
    const vparts = this.context.header.getVersionString().split('.');
11✔
1582
    const uOS = Number(vparts[0]) > 1 || Number(vparts[1]) >= 5;
11✔
1583
    const { objectsPerTick = 50 } = options;
11✔
1584

1585
    assertIs(objectsPerTick, 'objectsPerTick', ['number']);
11✔
1586

1587
    const saveOptions: SaveOptions = {
11✔
1588
      useObjectStreams: uOS,
1589
      ...options,
1590
      addDefaultPage: false,
1591
      updateFieldAppearances: false,
1592
    };
1593
    await this.prepareForSave(saveOptions);
11✔
1594

1595
    const Writer = saveOptions.useObjectStreams ? PDFStreamWriter : PDFWriter;
11✔
1596
    return Writer.forContextWithSnapshot(
11✔
1597
      this.context,
1598
      objectsPerTick,
1599
      snapshot,
1600
    ).serializeToBuffer();
1601
  }
1602

1603
  /**
1604
   * Serialize this document to a base64 encoded string or data URI making up a
1605
   * PDF file. For example:
1606
   * ```js
1607
   * const base64String = await pdfDoc.saveAsBase64()
1608
   * base64String // => 'JVBERi0xLjcKJYGBgYEKC...'
1609
   *
1610
   * const base64DataUri = await pdfDoc.saveAsBase64({ dataUri: true })
1611
   * base64DataUri // => 'data:application/pdf;base64,JVBERi0xLjcKJYGBgYEKC...'
1612
   * ```
1613
   *
1614
   * @param options The options to be used when saving the document.
1615
   * @returns Resolves with a base64 encoded string or data URI of the
1616
   *          serialized document.
1617
   */
1618
  async saveAsBase64(options: Base64SaveOptions = {}): Promise<string> {
×
1619
    const { dataUri = false, ...otherOptions } = options;
×
1620
    assertIs(dataUri, 'dataUri', ['boolean']);
×
1621
    const bytes = await this.save(otherOptions);
×
1622
    const base64 = encodeToBase64(bytes);
×
1623
    return dataUri ? `data:application/pdf;base64,${base64}` : base64;
×
1624
  }
1625

1626
  findPageForAnnotationRef(ref: PDFRef): PDFPage | undefined {
1627
    const pages = this.getPages();
×
1628
    for (let idx = 0, len = pages.length; idx < len; idx++) {
×
1629
      const page = pages[idx];
×
1630
      const annotations = page.node.Annots();
×
1631

1632
      if (annotations?.indexOf(ref) !== undefined) {
×
1633
        return page;
×
1634
      }
1635
    }
1636

1637
    return undefined;
×
1638
  }
1639

1640
  takeSnapshot(): DocumentSnapshot {
1641
    const indirectObjects: number[] = [];
31✔
1642

1643
    const snapshot = new IncrementalDocumentSnapshot(
31✔
1644
      this.context.largestObjectNumber,
1645
      indirectObjects,
1646
      this.context.pdfFileDetails.pdfSize,
1647
      this.context.pdfFileDetails.prevStartXRef,
1648
      this.context,
1649
    );
1650
    if (!this.context.snapshot && this.context.pdfFileDetails.originalBytes) {
31✔
1651
      this.context.snapshot = snapshot;
16✔
1652
      this.catalog.registerChange();
16✔
1653
    }
1654
    return snapshot;
31✔
1655
  }
1656

1657
  /**
1658
   * Returns the update version of the object as 'actual', and all the previous versions, of the objects
1659
   * that has changed in the indicated update (or the last one).
1660
   * If document wasn't load to preserve objects versions, an empty array is returned.
1661
   * @param {number} lastUpdateMinusX If not the last update, how many updates before the last.
1662
   * @returns  {PDFObjectVersions[]} Objects modified in the update, and previous versions
1663
   */
1664
  getChangedObjects(lastUpdateMinusX: number = 0): PDFObjectVersions[] {
6✔
1665
    if (!this.context.preserveObjectsVersions) return [];
8✔
1666
    if (lastUpdateMinusX < 0) lastUpdateMinusX = 0;
6!
1667
    const upind = this.context.xrefs.length - lastUpdateMinusX - 1;
6✔
1668
    const entries = this.context.listXrefEntries(upind);
6✔
1669
    if (!entries.length) return [];
6!
1670
    const changed = new Map<PDFRef, PDFObjectVersions>();
6✔
1671
    for (const entry of entries) {
6✔
1672
      const ref = entry.ref;
370✔
1673
      changed.set(ref, {
370✔
1674
        ref,
1675
        actual: entry.deleted ? undefined : this.context.lookup(ref),
370✔
1676
        previous: this.context.getObjectVersions(ref),
1677
      });
1678
    }
1679
    // if not the las update, then check objects later modified and adjust PDFObjectVersions accordingly
1680
    if (!lastUpdateMinusX)
6✔
1681
      return Array.from(changed.entries()).map((value) => value[1]);
32✔
1682
    while (lastUpdateMinusX) {
2✔
1683
      lastUpdateMinusX -= 1;
3✔
1684
      const upind = this.context.xrefs.length - lastUpdateMinusX - 1;
3✔
1685
      const nentries = this.context.listXrefEntries(upind);
3✔
1686
      for (const nentry of nentries) {
3✔
1687
        const oce = changed.get(nentry.ref);
343✔
1688
        if (oce && oce.actual) {
343✔
1689
          oce.actual = oce.previous[0];
11✔
1690
          oce.previous = oce.previous.slice(1);
11✔
1691
        }
1692
      }
1693
    }
1694
    // if PDF has errors, it may happen to end with objects that has no current, nor previous versions
1695
    return Array.from(changed.entries())
2✔
1696
      .map((value) => value[1])
338✔
1697
      .filter((ov) => ov.actual || ov.previous.length);
338✔
1698
  }
1699

1700
  private async prepareForSave(options: SaveOptions): Promise<void> {
1701
    const { addDefaultPage = true, updateFieldAppearances = true } = options;
46✔
1702

1703
    assertIs(addDefaultPage, 'addDefaultPage', ['boolean']);
46✔
1704
    assertIs(updateFieldAppearances, 'updateFieldAppearances', ['boolean']);
46✔
1705

1706
    if (addDefaultPage && this.getPageCount() === 0) this.addPage();
46✔
1707

1708
    if (updateFieldAppearances) {
46✔
1709
      const form = this.formCache.getValue();
19✔
1710
      if (form) form.updateFieldAppearances();
19✔
1711
    }
1712

1713
    await this.flush();
46✔
1714
  }
1715

1716
  private async embedAll(embeddables: Embeddable[]): Promise<void> {
1717
    for (let idx = 0, len = embeddables.length; idx < len; idx++) {
245✔
1718
      await embeddables[idx].embed();
50✔
1719
    }
1720
  }
1721

1722
  private updateInfoDict(): void {
1723
    const pdfLib = 'pdf-lib (https://github.com/Hopding/pdf-lib)';
136✔
1724
    const now = new Date();
136✔
1725

1726
    const info = this.getInfoDict();
136✔
1727

1728
    this.setProducer(pdfLib);
136✔
1729
    this.setModificationDate(now);
136✔
1730

1731
    if (!info.get(PDFName.of('Creator'))) this.setCreator(pdfLib);
136✔
1732
    if (!info.get(PDFName.of('CreationDate'))) this.setCreationDate(now);
136✔
1733
  }
1734

1735
  private getInfoDict(): PDFDict {
1736
    const existingInfo = this.context.lookup(this.context.trailerInfo.Info);
600✔
1737
    if (existingInfo instanceof PDFDict) {
600✔
1738
      return existingInfo;
551✔
1739
    }
1740

1741
    const newInfo = this.context.obj({});
49✔
1742
    this.context.trailerInfo.Info = this.context.register(newInfo);
49✔
1743

1744
    return newInfo;
49✔
1745
  }
1746

1747
  private assertFontkit(): Fontkit {
1748
    if (!this.fontkit) throw new FontkitNotRegisteredError();
2!
1749
    return this.fontkit;
2✔
1750
  }
1751

1752
  private computePages = (): PDFPage[] => {
144✔
1753
    const pages: PDFPage[] = [];
75✔
1754
    this.catalog.Pages().traverse((node, ref) => {
75✔
1755
      if (node instanceof PDFPageLeaf) {
509✔
1756
        let page = this.pageMap.get(node);
427✔
1757
        if (!page) {
427✔
1758
          page = PDFPage.of(node, ref, this);
420✔
1759
          this.pageMap.set(node, page);
420✔
1760
        }
1761
        pages.push(page);
427✔
1762
      }
1763
    });
1764
    return pages;
75✔
1765
  };
1766

1767
  private getOrCreateForm = (): PDFForm => {
144✔
1768
    const acroForm = this.catalog.getOrCreateAcroForm();
51✔
1769
    return PDFForm.of(acroForm, this);
51✔
1770
  };
1771
}
1772

1773
/* tslint:disable-next-line only-arrow-functions */
1774
function assertIsLiteralOrHexString(
1775
  pdfObject: PDFObject,
1776
): asserts pdfObject is PDFHexString | PDFString {
1777
  if (
49!
1778
    !(pdfObject instanceof PDFHexString) &&
66✔
1779
    !(pdfObject instanceof PDFString)
1780
  ) {
1781
    throw new UnexpectedObjectTypeError([PDFHexString, PDFString], pdfObject);
×
1782
  }
1783
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc