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

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
@@ -4842,6 +4842,23 @@ interface DrawPageOptions {
4842
4842
  height?: number;
4843
4843
  /** Opacity 0-1 (default: 1.0, fully opaque) */
4844
4844
  opacity?: number;
4845
+ /**
4846
+ * Rotation specification.
4847
+ * Can be a simple number (degrees) or an object with angle and origin.
4848
+ *
4849
+ * @example
4850
+ * ```typescript
4851
+ * // Simple rotation (around position x, y)
4852
+ * page.drawPage(embedded, { x: 100, y: 100, rotate: 45 });
4853
+ *
4854
+ * // Rotation with custom origin
4855
+ * page.drawPage(embedded, { x: 100, y: 100, rotate: { angle: 45, origin: "center" } });
4856
+ *
4857
+ * // Rotation with explicit origin coordinates
4858
+ * page.drawPage(embedded, { x: 100, y: 100, rotate: { angle: 45, origin: { x: 150, y: 150 } } });
4859
+ * ```
4860
+ */
4861
+ rotate?: number | Rotation;
4845
4862
  /** Draw as background behind existing content (default: false = foreground) */
4846
4863
  background?: boolean;
4847
4864
  }
@@ -4956,7 +4973,7 @@ declare class PDFPage {
4956
4973
  * Use `{ background: true }` to draw behind existing content.
4957
4974
  *
4958
4975
  * @param embedded - The embedded page to draw
4959
- * @param options - Drawing options (position, scale, opacity, background)
4976
+ * @param options - Drawing options (position, scale, rotate, opacity, background)
4960
4977
  *
4961
4978
  * @example
4962
4979
  * ```typescript
@@ -4973,6 +4990,15 @@ declare class PDFPage {
4973
4990
  *
4974
4991
  * // Draw as a background
4975
4992
  * page.drawPage(letterhead, { background: true });
4993
+ *
4994
+ * // Draw rotated 45 degrees counter-clockwise (around position x, y)
4995
+ * page.drawPage(stamp, { x: 100, y: 100, rotate: 45, scale: 0.5 });
4996
+ *
4997
+ * // Draw rotated around the center of the embedded page
4998
+ * page.drawPage(stamp, { x: 100, y: 100, rotate: { angle: 45, origin: "center" }, scale: 0.5 });
4999
+ *
5000
+ * // Draw rotated around a custom point
5001
+ * page.drawPage(stamp, { x: 100, y: 100, rotate: { angle: 90, origin: { x: 200, y: 200 } } });
4976
5002
  * ```
4977
5003
  */
4978
5004
  drawPage(embedded: PDFEmbeddedPage, options?: DrawPageOptions): void;
@@ -5895,6 +5921,16 @@ interface RemoveAnnotationsOptions {
5895
5921
  interface FlattenAnnotationsOptions {
5896
5922
  /** Annotation types to exclude from flattening */
5897
5923
  exclude?: AnnotationSubtype[];
5924
+ /**
5925
+ * Remove Link annotations instead of keeping them.
5926
+ *
5927
+ * Link annotations contain URI or other actions that Adobe considers
5928
+ * "hidden behavior" which can cause signature validation warnings.
5929
+ * Enable this option when preparing documents for signing.
5930
+ *
5931
+ * @default false
5932
+ */
5933
+ removeLinks?: boolean;
5898
5934
  }
5899
5935
  //#endregion
5900
5936
  //#region src/attachments/types.d.ts
@@ -7684,6 +7720,24 @@ declare class PDF {
7684
7720
  static merge(sources: Uint8Array[], options?: MergeOptions): Promise<PDF>;
7685
7721
  /** PDF version string (e.g., "1.7", "2.0") */
7686
7722
  get version(): string;
7723
+ /**
7724
+ * Upgrade the PDF version.
7725
+ *
7726
+ * Sets the /Version entry in the catalog dictionary. This is the standard
7727
+ * way to upgrade PDF version in incremental updates since the header
7728
+ * cannot be modified.
7729
+ *
7730
+ * The version will only be upgraded if the new version is higher than
7731
+ * the current version.
7732
+ *
7733
+ * @param version - Target version (e.g., "1.7", "2.0")
7734
+ *
7735
+ * @example
7736
+ * ```typescript
7737
+ * pdf.upgradeVersion("1.7");
7738
+ * ```
7739
+ */
7740
+ upgradeVersion(version: string): void;
7687
7741
  /** Whether the document is encrypted */
7688
7742
  get isEncrypted(): boolean;
7689
7743
  /** Whether authentication succeeded (for encrypted docs) */
@@ -8619,12 +8673,6 @@ declare class PDF {
8619
8673
  * Used by signing to chain incremental updates.
8620
8674
  */
8621
8675
  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
8676
  }
8629
8677
  //#endregion
8630
8678
  //#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.15";
13
13
 
14
14
  //#endregion
15
15
  //#region src/objects/pdf-array.ts
@@ -24339,7 +24339,7 @@ var PDFPage = class PDFPage {
24339
24339
  * Use `{ background: true }` to draw behind existing content.
24340
24340
  *
24341
24341
  * @param embedded - The embedded page to draw
24342
- * @param options - Drawing options (position, scale, opacity, background)
24342
+ * @param options - Drawing options (position, scale, rotate, opacity, background)
24343
24343
  *
24344
24344
  * @example
24345
24345
  * ```typescript
@@ -24356,6 +24356,15 @@ var PDFPage = class PDFPage {
24356
24356
  *
24357
24357
  * // Draw as a background
24358
24358
  * page.drawPage(letterhead, { background: true });
24359
+ *
24360
+ * // Draw rotated 45 degrees counter-clockwise (around position x, y)
24361
+ * page.drawPage(stamp, { x: 100, y: 100, rotate: 45, scale: 0.5 });
24362
+ *
24363
+ * // Draw rotated around the center of the embedded page
24364
+ * page.drawPage(stamp, { x: 100, y: 100, rotate: { angle: 45, origin: "center" }, scale: 0.5 });
24365
+ *
24366
+ * // Draw rotated around a custom point
24367
+ * page.drawPage(stamp, { x: 100, y: 100, rotate: { angle: 90, origin: { x: 200, y: 200 } } });
24359
24368
  * ```
24360
24369
  */
24361
24370
  drawPage(embedded, options = {}) {
@@ -24377,9 +24386,40 @@ var PDFPage = class PDFPage {
24377
24386
  });
24378
24387
  ops.push(`/${gsName} gs`);
24379
24388
  }
24380
- const translateX = x - embedded.box.x * scaleX;
24381
- const translateY = y - embedded.box.y * scaleY;
24382
- ops.push(`${this.formatNumber(scaleX)} 0 0 ${this.formatNumber(scaleY)} ${this.formatNumber(translateX)} ${this.formatNumber(translateY)} cm`);
24389
+ let rotateDegrees = 0;
24390
+ let rotateOriginX = x;
24391
+ let rotateOriginY = y;
24392
+ if (options.rotate !== void 0) if (typeof options.rotate === "number") rotateDegrees = options.rotate;
24393
+ else {
24394
+ rotateDegrees = options.rotate.angle;
24395
+ const bounds = {
24396
+ x,
24397
+ y,
24398
+ width: embedded.width * scaleX,
24399
+ height: embedded.height * scaleY
24400
+ };
24401
+ const defaultOrigin = {
24402
+ x,
24403
+ y
24404
+ };
24405
+ const origin = resolveRotationOrigin(options.rotate.origin, bounds, defaultOrigin);
24406
+ rotateOriginX = origin.x;
24407
+ rotateOriginY = origin.y;
24408
+ }
24409
+ if (rotateDegrees !== 0) {
24410
+ const radians = rotateDegrees * Math.PI / 180;
24411
+ const cos = Math.cos(radians);
24412
+ const sin = Math.sin(radians);
24413
+ ops.push(`1 0 0 1 ${this.formatNumber(rotateOriginX)} ${this.formatNumber(rotateOriginY)} cm`);
24414
+ ops.push(`${this.formatNumber(cos)} ${this.formatNumber(sin)} ${this.formatNumber(-sin)} ${this.formatNumber(cos)} 0 0 cm`);
24415
+ const offsetX = x - rotateOriginX - embedded.box.x * scaleX;
24416
+ const offsetY = y - rotateOriginY - embedded.box.y * scaleY;
24417
+ ops.push(`${this.formatNumber(scaleX)} 0 0 ${this.formatNumber(scaleY)} ${this.formatNumber(offsetX)} ${this.formatNumber(offsetY)} cm`);
24418
+ } else {
24419
+ const translateX = x - embedded.box.x * scaleX;
24420
+ const translateY = y - embedded.box.y * scaleY;
24421
+ ops.push(`${this.formatNumber(scaleX)} 0 0 ${this.formatNumber(scaleY)} ${this.formatNumber(translateX)} ${this.formatNumber(translateY)} cm`);
24422
+ }
24383
24423
  ops.push(`/${xobjectName} Do`);
24384
24424
  ops.push("Q");
24385
24425
  const contentOps = ops.join("\n");
@@ -26270,6 +26310,11 @@ var AnnotationFlattener = class {
26270
26310
  if (isPopupAnnotation(annotDict)) continue;
26271
26311
  const subtype = annotDict.getName("Subtype")?.value;
26272
26312
  if (!subtype) continue;
26313
+ if (subtype === "Link") {
26314
+ if (options.removeLinks) {
26315
+ if (annotRef) refsToRemove.add(`${annotRef.objectNumber} ${annotRef.generation}`);
26316
+ }
26317
+ }
26273
26318
  if (NON_FLATTENABLE_TYPES.includes(subtype)) continue;
26274
26319
  if (options.exclude?.includes(subtype)) continue;
26275
26320
  const annotation = createAnnotation(annotDict, annotRef, this.registry);
@@ -26295,7 +26340,8 @@ var AnnotationFlattener = class {
26295
26340
  }
26296
26341
  this.normalizeAppearanceStream(appearance);
26297
26342
  const xObjectName = `FlatAnnot${xObjectIndex++}`;
26298
- const appearanceRef = this.registry.register(appearance);
26343
+ let appearanceRef = this.registry.getRef(appearance);
26344
+ if (!appearanceRef) appearanceRef = this.registry.register(appearance);
26299
26345
  xObjects.set(xObjectName, appearanceRef);
26300
26346
  const rect = this.getAnnotationRect(annotDict);
26301
26347
  const matrix = this.calculateTransformMatrix(rect, appearance);
@@ -26320,7 +26366,6 @@ var AnnotationFlattener = class {
26320
26366
  * Generate appearance for annotation types we support.
26321
26367
  */
26322
26368
  generateAppearance(annotation) {
26323
- annotation.type;
26324
26369
  const rect = annotation.rect;
26325
26370
  if (annotation instanceof PDFHighlightAnnotation) {
26326
26371
  const highlight = annotation;
@@ -26512,22 +26557,42 @@ var AnnotationFlattener = class {
26512
26557
  }
26513
26558
  /**
26514
26559
  * Remove specific annotations from page.
26560
+ *
26561
+ * IMPORTANT: If Annots is an indirect reference, we modify the array in-place
26562
+ * to preserve the indirection. This is critical for signing: if we convert
26563
+ * an indirect Annots to a direct array, then later signing will convert it
26564
+ * back to indirect, modifying the page object and potentially breaking
26565
+ * signature validation.
26515
26566
  */
26516
26567
  removeAnnotations(page, toRemove) {
26517
26568
  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 = [];
26569
+ const annotsEntry = page.get("Annots");
26570
+ if (!annotsEntry) return;
26571
+ const wasIndirect = annotsEntry instanceof PdfRef;
26572
+ let annots;
26573
+ if (annotsEntry instanceof PdfRef) {
26574
+ const resolved = this.registry.resolve(annotsEntry);
26575
+ annots = resolved instanceof PdfArray ? resolved : void 0;
26576
+ } else if (annotsEntry instanceof PdfArray) annots = annotsEntry;
26577
+ if (!annots) return;
26578
+ const indicesToRemove = [];
26522
26579
  for (let i = 0; i < annots.length; i++) {
26523
26580
  const item = annots.at(i);
26524
26581
  if (item instanceof PdfRef) {
26525
26582
  const key$1 = `${item.objectNumber} ${item.generation}`;
26526
- if (!toRemove.has(key$1)) remaining.push(item);
26583
+ if (toRemove.has(key$1)) indicesToRemove.push(i);
26527
26584
  }
26528
26585
  }
26529
- if (remaining.length === 0) page.delete("Annots");
26530
- else if (remaining.length < annots.length) page.set("Annots", PdfArray.of(...remaining));
26586
+ if (indicesToRemove.length === 0) return;
26587
+ if (wasIndirect) for (let i = indicesToRemove.length - 1; i >= 0; i--) annots.remove(indicesToRemove[i]);
26588
+ else {
26589
+ const remaining = [];
26590
+ for (let i = 0; i < annots.length; i++) if (!indicesToRemove.includes(i)) {
26591
+ const item = annots.at(i);
26592
+ if (item instanceof PdfRef) remaining.push(item);
26593
+ }
26594
+ page.set("Annots", PdfArray.of(...remaining));
26595
+ }
26531
26596
  }
26532
26597
  };
26533
26598
 
@@ -30298,7 +30363,11 @@ var XRefParser = class {
30298
30363
  const entries = /* @__PURE__ */ new Map();
30299
30364
  this.expectKeyword("xref");
30300
30365
  this.skipWhitespaceFromCurrent();
30301
- while (!this.peekKeyword("trailer")) this.parseSubsection(entries);
30366
+ while (true) {
30367
+ this.skipWhitespaceFromCurrent();
30368
+ if (this.peekKeyword("trailer")) break;
30369
+ this.parseSubsection(entries);
30370
+ }
30302
30371
  this.expectKeyword("trailer");
30303
30372
  this.skipWhitespaceFromCurrent();
30304
30373
  const result = new ObjectParser(new TokenReader(this.scanner)).parseObject();
@@ -31352,6 +31421,30 @@ function encryptStreamDict(stream, ctx) {
31352
31421
  return encrypted;
31353
31422
  }
31354
31423
  /**
31424
+ * Collect all refs reachable from the document root and trailer entries.
31425
+ *
31426
+ * Walks the object graph starting from Root, Info, and Encrypt (if present),
31427
+ * returning the set of all object keys (as "objNum gen" strings) that are reachable.
31428
+ * This is used for garbage collection during full saves.
31429
+ */
31430
+ function collectReachableRefs(registry, root, info, encrypt) {
31431
+ const visited = /* @__PURE__ */ new Set();
31432
+ const walk = (obj) => {
31433
+ if (obj === null) return;
31434
+ if (obj instanceof PdfRef) {
31435
+ const key$1 = `${obj.objectNumber} ${obj.generation}`;
31436
+ if (visited.has(key$1)) return;
31437
+ visited.add(key$1);
31438
+ walk(registry.resolve(obj));
31439
+ } else if (obj instanceof PdfDict) for (const [, value] of obj) walk(value);
31440
+ else if (obj instanceof PdfArray) for (const item of obj) walk(item);
31441
+ };
31442
+ walk(root);
31443
+ if (info) walk(info);
31444
+ if (encrypt) walk(encrypt);
31445
+ return visited;
31446
+ }
31447
+ /**
31355
31448
  * Write a complete PDF from scratch.
31356
31449
  *
31357
31450
  * Structure:
@@ -31386,9 +31479,10 @@ function writeComplete(registry, options) {
31386
31479
  10
31387
31480
  ]));
31388
31481
  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) {
31482
+ const reachableKeys = collectReachableRefs(registry, options.root, options.info, options.encrypt);
31483
+ for (const [ref, obj] of registry.entries()) {
31484
+ const key$1 = `${ref.objectNumber} ${ref.generation}`;
31485
+ if (!reachableKeys.has(key$1)) continue;
31392
31486
  let prepared = prepareObjectForWrite(obj, compress);
31393
31487
  if (options.securityHandler && options.encrypt && ref !== options.encrypt) prepared = encryptObject(prepared, {
31394
31488
  handler: options.securityHandler,
@@ -37639,6 +37733,33 @@ var PDF = class PDF {
37639
37733
  get version() {
37640
37734
  return this.ctx.info.version;
37641
37735
  }
37736
+ /**
37737
+ * Upgrade the PDF version.
37738
+ *
37739
+ * Sets the /Version entry in the catalog dictionary. This is the standard
37740
+ * way to upgrade PDF version in incremental updates since the header
37741
+ * cannot be modified.
37742
+ *
37743
+ * The version will only be upgraded if the new version is higher than
37744
+ * the current version.
37745
+ *
37746
+ * @param version - Target version (e.g., "1.7", "2.0")
37747
+ *
37748
+ * @example
37749
+ * ```typescript
37750
+ * pdf.upgradeVersion("1.7");
37751
+ * ```
37752
+ */
37753
+ upgradeVersion(version$1) {
37754
+ const parseVersion$1 = (v) => {
37755
+ const [major, minor] = v.split(".").map(Number);
37756
+ return major * 10 + (minor || 0);
37757
+ };
37758
+ const currentVersion = parseVersion$1(this.ctx.info.version);
37759
+ if (parseVersion$1(version$1) <= currentVersion) return;
37760
+ this.ctx.catalog.getDict().set("Version", PdfName.of(version$1));
37761
+ this.ctx.info.version = version$1;
37762
+ }
37642
37763
  /** Whether the document is encrypted */
37643
37764
  get isEncrypted() {
37644
37765
  return this.ctx.info.isEncrypted;
@@ -39019,7 +39140,11 @@ var PDF = class PDF {
39019
39140
  formFields = form.getFields().length;
39020
39141
  form.flatten(options?.form);
39021
39142
  }
39022
- const annotations = this.flattenAnnotations(options?.annotations);
39143
+ const annotationOptions = {
39144
+ removeLinks: true,
39145
+ ...options?.annotations
39146
+ };
39147
+ const annotations = this.flattenAnnotations(annotationOptions);
39023
39148
  return {
39024
39149
  layers: layerResult.layerCount,
39025
39150
  formFields,
@@ -39179,7 +39304,6 @@ var PDF = class PDF {
39179
39304
  this._pendingSecurity = { action: "none" };
39180
39305
  return result$1;
39181
39306
  }
39182
- this.ensureObjectsLoaded();
39183
39307
  const result = writeComplete(this.ctx.registry, {
39184
39308
  version: this.ctx.info.version,
39185
39309
  root,
@@ -39192,28 +39316,6 @@ var PDF = class PDF {
39192
39316
  this._pendingSecurity = { action: "none" };
39193
39317
  return result;
39194
39318
  }
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
39319
  };
39218
39320
 
39219
39321
  //#endregion