@libpdf/core 0.0.1-beta.13 → 0.0.1-beta.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -5895,6 +5895,16 @@ interface RemoveAnnotationsOptions {
5895
5895
  interface FlattenAnnotationsOptions {
5896
5896
  /** Annotation types to exclude from flattening */
5897
5897
  exclude?: AnnotationSubtype[];
5898
+ /**
5899
+ * Remove Link annotations instead of keeping them.
5900
+ *
5901
+ * Link annotations contain URI or other actions that Adobe considers
5902
+ * "hidden behavior" which can cause signature validation warnings.
5903
+ * Enable this option when preparing documents for signing.
5904
+ *
5905
+ * @default false
5906
+ */
5907
+ removeLinks?: boolean;
5898
5908
  }
5899
5909
  //#endregion
5900
5910
  //#region src/attachments/types.d.ts
@@ -7684,6 +7694,24 @@ declare class PDF {
7684
7694
  static merge(sources: Uint8Array[], options?: MergeOptions): Promise<PDF>;
7685
7695
  /** PDF version string (e.g., "1.7", "2.0") */
7686
7696
  get version(): string;
7697
+ /**
7698
+ * Upgrade the PDF version.
7699
+ *
7700
+ * Sets the /Version entry in the catalog dictionary. This is the standard
7701
+ * way to upgrade PDF version in incremental updates since the header
7702
+ * cannot be modified.
7703
+ *
7704
+ * The version will only be upgraded if the new version is higher than
7705
+ * the current version.
7706
+ *
7707
+ * @param version - Target version (e.g., "1.7", "2.0")
7708
+ *
7709
+ * @example
7710
+ * ```typescript
7711
+ * pdf.upgradeVersion("1.7");
7712
+ * ```
7713
+ */
7714
+ upgradeVersion(version: string): void;
7687
7715
  /** Whether the document is encrypted */
7688
7716
  get isEncrypted(): boolean;
7689
7717
  /** Whether authentication succeeded (for encrypted docs) */
@@ -8619,12 +8647,6 @@ declare class PDF {
8619
8647
  * Used by signing to chain incremental updates.
8620
8648
  */
8621
8649
  private saveInternal;
8622
- /**
8623
- * Ensure all reachable objects are loaded into the registry.
8624
- *
8625
- * Walks from the catalog to load all referenced objects.
8626
- */
8627
- private ensureObjectsLoaded;
8628
8650
  }
8629
8651
  //#endregion
8630
8652
  //#region src/signatures/signers/crypto-key.d.ts
package/dist/index.mjs CHANGED
@@ -9,7 +9,7 @@ import { createCMSECDSASignature } from "pkijs";
9
9
  import { base64 } from "@scure/base";
10
10
 
11
11
  //#region package.json
12
- var version = "0.0.1-beta.13";
12
+ var version = "0.0.1-beta.14";
13
13
 
14
14
  //#endregion
15
15
  //#region src/objects/pdf-array.ts
@@ -26270,6 +26270,11 @@ var AnnotationFlattener = class {
26270
26270
  if (isPopupAnnotation(annotDict)) continue;
26271
26271
  const subtype = annotDict.getName("Subtype")?.value;
26272
26272
  if (!subtype) continue;
26273
+ if (subtype === "Link") {
26274
+ if (options.removeLinks) {
26275
+ if (annotRef) refsToRemove.add(`${annotRef.objectNumber} ${annotRef.generation}`);
26276
+ }
26277
+ }
26273
26278
  if (NON_FLATTENABLE_TYPES.includes(subtype)) continue;
26274
26279
  if (options.exclude?.includes(subtype)) continue;
26275
26280
  const annotation = createAnnotation(annotDict, annotRef, this.registry);
@@ -26295,7 +26300,8 @@ var AnnotationFlattener = class {
26295
26300
  }
26296
26301
  this.normalizeAppearanceStream(appearance);
26297
26302
  const xObjectName = `FlatAnnot${xObjectIndex++}`;
26298
- const appearanceRef = this.registry.register(appearance);
26303
+ let appearanceRef = this.registry.getRef(appearance);
26304
+ if (!appearanceRef) appearanceRef = this.registry.register(appearance);
26299
26305
  xObjects.set(xObjectName, appearanceRef);
26300
26306
  const rect = this.getAnnotationRect(annotDict);
26301
26307
  const matrix = this.calculateTransformMatrix(rect, appearance);
@@ -26320,7 +26326,6 @@ var AnnotationFlattener = class {
26320
26326
  * Generate appearance for annotation types we support.
26321
26327
  */
26322
26328
  generateAppearance(annotation) {
26323
- annotation.type;
26324
26329
  const rect = annotation.rect;
26325
26330
  if (annotation instanceof PDFHighlightAnnotation) {
26326
26331
  const highlight = annotation;
@@ -26512,22 +26517,42 @@ var AnnotationFlattener = class {
26512
26517
  }
26513
26518
  /**
26514
26519
  * Remove specific annotations from page.
26520
+ *
26521
+ * IMPORTANT: If Annots is an indirect reference, we modify the array in-place
26522
+ * to preserve the indirection. This is critical for signing: if we convert
26523
+ * an indirect Annots to a direct array, then later signing will convert it
26524
+ * back to indirect, modifying the page object and potentially breaking
26525
+ * signature validation.
26515
26526
  */
26516
26527
  removeAnnotations(page, toRemove) {
26517
26528
  if (toRemove.size === 0) return;
26518
- let annots = page.get("Annots");
26519
- if (annots instanceof PdfRef) annots = this.registry.resolve(annots) ?? void 0;
26520
- if (!(annots instanceof PdfArray)) return;
26521
- const remaining = [];
26529
+ const annotsEntry = page.get("Annots");
26530
+ if (!annotsEntry) return;
26531
+ const wasIndirect = annotsEntry instanceof PdfRef;
26532
+ let annots;
26533
+ if (annotsEntry instanceof PdfRef) {
26534
+ const resolved = this.registry.resolve(annotsEntry);
26535
+ annots = resolved instanceof PdfArray ? resolved : void 0;
26536
+ } else if (annotsEntry instanceof PdfArray) annots = annotsEntry;
26537
+ if (!annots) return;
26538
+ const indicesToRemove = [];
26522
26539
  for (let i = 0; i < annots.length; i++) {
26523
26540
  const item = annots.at(i);
26524
26541
  if (item instanceof PdfRef) {
26525
26542
  const key$1 = `${item.objectNumber} ${item.generation}`;
26526
- if (!toRemove.has(key$1)) remaining.push(item);
26543
+ if (toRemove.has(key$1)) indicesToRemove.push(i);
26527
26544
  }
26528
26545
  }
26529
- if (remaining.length === 0) page.delete("Annots");
26530
- else if (remaining.length < annots.length) page.set("Annots", PdfArray.of(...remaining));
26546
+ if (indicesToRemove.length === 0) return;
26547
+ if (wasIndirect) for (let i = indicesToRemove.length - 1; i >= 0; i--) annots.remove(indicesToRemove[i]);
26548
+ else {
26549
+ const remaining = [];
26550
+ for (let i = 0; i < annots.length; i++) if (!indicesToRemove.includes(i)) {
26551
+ const item = annots.at(i);
26552
+ if (item instanceof PdfRef) remaining.push(item);
26553
+ }
26554
+ page.set("Annots", PdfArray.of(...remaining));
26555
+ }
26531
26556
  }
26532
26557
  };
26533
26558
 
@@ -30298,7 +30323,11 @@ var XRefParser = class {
30298
30323
  const entries = /* @__PURE__ */ new Map();
30299
30324
  this.expectKeyword("xref");
30300
30325
  this.skipWhitespaceFromCurrent();
30301
- while (!this.peekKeyword("trailer")) this.parseSubsection(entries);
30326
+ while (true) {
30327
+ this.skipWhitespaceFromCurrent();
30328
+ if (this.peekKeyword("trailer")) break;
30329
+ this.parseSubsection(entries);
30330
+ }
30302
30331
  this.expectKeyword("trailer");
30303
30332
  this.skipWhitespaceFromCurrent();
30304
30333
  const result = new ObjectParser(new TokenReader(this.scanner)).parseObject();
@@ -31352,6 +31381,30 @@ function encryptStreamDict(stream, ctx) {
31352
31381
  return encrypted;
31353
31382
  }
31354
31383
  /**
31384
+ * Collect all refs reachable from the document root and trailer entries.
31385
+ *
31386
+ * Walks the object graph starting from Root, Info, and Encrypt (if present),
31387
+ * returning the set of all object keys (as "objNum gen" strings) that are reachable.
31388
+ * This is used for garbage collection during full saves.
31389
+ */
31390
+ function collectReachableRefs(registry, root, info, encrypt) {
31391
+ const visited = /* @__PURE__ */ new Set();
31392
+ const walk = (obj) => {
31393
+ if (obj === null) return;
31394
+ if (obj instanceof PdfRef) {
31395
+ const key$1 = `${obj.objectNumber} ${obj.generation}`;
31396
+ if (visited.has(key$1)) return;
31397
+ visited.add(key$1);
31398
+ walk(registry.resolve(obj));
31399
+ } else if (obj instanceof PdfDict) for (const [, value] of obj) walk(value);
31400
+ else if (obj instanceof PdfArray) for (const item of obj) walk(item);
31401
+ };
31402
+ walk(root);
31403
+ if (info) walk(info);
31404
+ if (encrypt) walk(encrypt);
31405
+ return visited;
31406
+ }
31407
+ /**
31355
31408
  * Write a complete PDF from scratch.
31356
31409
  *
31357
31410
  * Structure:
@@ -31386,9 +31439,10 @@ function writeComplete(registry, options) {
31386
31439
  10
31387
31440
  ]));
31388
31441
  const offsets = /* @__PURE__ */ new Map();
31389
- const allObjects = /* @__PURE__ */ new Map();
31390
- for (const [ref, obj] of registry.entries()) allObjects.set(ref, obj);
31391
- for (const [ref, obj] of allObjects) {
31442
+ const reachableKeys = collectReachableRefs(registry, options.root, options.info, options.encrypt);
31443
+ for (const [ref, obj] of registry.entries()) {
31444
+ const key$1 = `${ref.objectNumber} ${ref.generation}`;
31445
+ if (!reachableKeys.has(key$1)) continue;
31392
31446
  let prepared = prepareObjectForWrite(obj, compress);
31393
31447
  if (options.securityHandler && options.encrypt && ref !== options.encrypt) prepared = encryptObject(prepared, {
31394
31448
  handler: options.securityHandler,
@@ -37639,6 +37693,33 @@ var PDF = class PDF {
37639
37693
  get version() {
37640
37694
  return this.ctx.info.version;
37641
37695
  }
37696
+ /**
37697
+ * Upgrade the PDF version.
37698
+ *
37699
+ * Sets the /Version entry in the catalog dictionary. This is the standard
37700
+ * way to upgrade PDF version in incremental updates since the header
37701
+ * cannot be modified.
37702
+ *
37703
+ * The version will only be upgraded if the new version is higher than
37704
+ * the current version.
37705
+ *
37706
+ * @param version - Target version (e.g., "1.7", "2.0")
37707
+ *
37708
+ * @example
37709
+ * ```typescript
37710
+ * pdf.upgradeVersion("1.7");
37711
+ * ```
37712
+ */
37713
+ upgradeVersion(version$1) {
37714
+ const parseVersion$1 = (v) => {
37715
+ const [major, minor] = v.split(".").map(Number);
37716
+ return major * 10 + (minor || 0);
37717
+ };
37718
+ const currentVersion = parseVersion$1(this.ctx.info.version);
37719
+ if (parseVersion$1(version$1) <= currentVersion) return;
37720
+ this.ctx.catalog.getDict().set("Version", PdfName.of(version$1));
37721
+ this.ctx.info.version = version$1;
37722
+ }
37642
37723
  /** Whether the document is encrypted */
37643
37724
  get isEncrypted() {
37644
37725
  return this.ctx.info.isEncrypted;
@@ -39019,7 +39100,11 @@ var PDF = class PDF {
39019
39100
  formFields = form.getFields().length;
39020
39101
  form.flatten(options?.form);
39021
39102
  }
39022
- const annotations = this.flattenAnnotations(options?.annotations);
39103
+ const annotationOptions = {
39104
+ removeLinks: true,
39105
+ ...options?.annotations
39106
+ };
39107
+ const annotations = this.flattenAnnotations(annotationOptions);
39023
39108
  return {
39024
39109
  layers: layerResult.layerCount,
39025
39110
  formFields,
@@ -39179,7 +39264,6 @@ var PDF = class PDF {
39179
39264
  this._pendingSecurity = { action: "none" };
39180
39265
  return result$1;
39181
39266
  }
39182
- this.ensureObjectsLoaded();
39183
39267
  const result = writeComplete(this.ctx.registry, {
39184
39268
  version: this.ctx.info.version,
39185
39269
  root,
@@ -39192,28 +39276,6 @@ var PDF = class PDF {
39192
39276
  this._pendingSecurity = { action: "none" };
39193
39277
  return result;
39194
39278
  }
39195
- /**
39196
- * Ensure all reachable objects are loaded into the registry.
39197
- *
39198
- * Walks from the catalog to load all referenced objects.
39199
- */
39200
- ensureObjectsLoaded() {
39201
- const visited = /* @__PURE__ */ new Set();
39202
- const walk = (obj) => {
39203
- if (obj === null) return;
39204
- if (obj instanceof PdfRef) {
39205
- const key$1 = `${obj.objectNumber} ${obj.generation}`;
39206
- if (visited.has(key$1)) return;
39207
- visited.add(key$1);
39208
- walk(this.getObject(obj));
39209
- } else if (obj instanceof PdfDict) for (const [, value] of obj) walk(value);
39210
- else if (obj instanceof PdfArray) for (const item of obj) walk(item);
39211
- };
39212
- const root = this.ctx.info.trailer.getRef("Root");
39213
- if (root) walk(root);
39214
- const infoRef = this.ctx.info.trailer.getRef("Info");
39215
- if (infoRef) walk(infoRef);
39216
- }
39217
39279
  };
39218
39280
 
39219
39281
  //#endregion