modern-pdf-lib 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,9 +1,10 @@
1
1
  import { $ as colorToComponents, $t as setTextMatrix, A as beginMarkedContent, At as fill, B as radians, Bt as setLineJoin, C as AnnotationFlags, Ct as closeFillEvenOddAndStroke, D as createAnnotation, Dt as curveToInitial, E as buildAnnotationDict, Et as curveToFinal, F as endMarkedContent, Ft as moveTo, G as saveState, Gt as endText, H as restoreState, Ht as setMiterLimit, I as wrapInMarkedContent, It as rectangle, J as skew, Jt as nextLine, K as scale, Kt as moveText, L as concatMatrix, Lt as setDashPattern, M as beginMarkedContentWithProperties, Mt as fillEvenOdd, N as createMarkedContentScope, Nt as fillEvenOddAndStroke, O as beginArtifact, Ot as ellipsePath, P as endArtifact, Pt as lineTo, Q as cmyk, Qt as setLeading, R as degrees, Rt as setFlatness, S as parseSvgTransform, St as closeFillAndStroke, T as annotationFromDict, Tt as curveTo, U as rotate, Ut as stroke, V as radiansToDegrees, Vt as setLineWidth, W as rotationMatrix, Wt as beginText, X as applyFillColor, Xt as setFont, Y as translate, Yt as setCharacterSpacing, Z as applyStrokeColor, Zt as setFontSize, _ as drawSvgOnPage, _t as drawXObject, a as buildGradientObjects, an as showTextHex, at as setFillColorCmyk, b as parseSvgColor, bt as clipEvenOdd, c as radialGradient, ct as setFillingColor, d as getRedactionMarks, dt as setStrokeColorGray, en as setTextRenderingMode, et as componentsToColor, f as markForRedaction, ft as setStrokeColorRgb, g as endLayerContent, gt as drawImageXObject, h as beginLayerContent, ht as drawImageWithMatrix, i as wrapText, in as showTextArray, it as setFillColor, j as beginMarkedContentSequence, jt as fillAndStroke, k as beginArtifactWithType, kt as endPath, l as tilingPattern, lt as setStrokeColor, m as PdfLayerManager, mt as setStrokingColor, n as PdfPage, nn as setWordSpacing, nt as rgb, o as buildPatternObjects, on as showTextNextLine, ot as setFillColorGray, p as PdfLayer, pt as setStrokeColorSpace, q as setGraphicsState, qt as moveTextSetLeading, rn as showText, rt as setColorSpace, s as linearGradient, sn as showTextWithSpacing, st as setFillColorRgb, t as PageSizes, tn as setTextRise, tt as grayscale, u as applyRedactions, ut as setStrokeColorCmyk, v as svgToPdfOperators, vt as circlePath, w as PdfAnnotation, wt as closePath, x as parseSvgPath, xt as closeAndStroke, y as parseSvg, yt as clip, z as degreesToRadians, zt as setLineCap } from "./pdfPage-N1K2U3jI.mjs";
2
2
  import { a as formatPdfDate, c as PdfBool, d as PdfNull, f as PdfNumber, g as PdfString, h as PdfStream, i as buildPageTree, l as PdfDict, m as PdfRef, n as buildDocumentStructure, p as PdfObjectRegistry, r as buildInfoDict, s as PdfArray, t as buildCatalog, u as PdfName } from "./pdfCatalog-BB2Wnmud.mjs";
3
- import { n as isAvailable, t as deflateSync$1 } from "./libdeflateWasm-DlHgU5oy.mjs";
3
+ import { n as isAvailable, t as deflateSync$1 } from "./libdeflateWasm-82loOtIV.mjs";
4
4
  import { i as subsetFont, n as computeSubsetTag, t as buildSubsetCmap } from "./fontSubset-ZpLoOZ2e.mjs";
5
- import { t as embedPng } from "./pngEmbed-DTOqgEUC.mjs";
5
+ import { t as embedPng } from "./pngEmbed-gaJ9S2Dk.mjs";
6
6
  import { t as decompressSync } from "./fflateAdapter-DX0VqT5k.mjs";
7
+ import { a as isJpegWasmReady, i as initJpegWasm, n as decodeJpegWasm, r as encodeJpegWasm } from "./bridge-C7U4E7St.mjs";
7
8
  import { deflateSync, inflateSync, unzlibSync } from "fflate";
8
9
 
9
10
  //#region src/core/pdfWriter.ts
@@ -4826,10 +4827,10 @@ async function tryLoadLibdeflate() {
4826
4827
  if (libdeflateAttempted) return libdeflateEngine;
4827
4828
  libdeflateAttempted = true;
4828
4829
  try {
4829
- const { LibdeflateWasm: LibdeflateCtor, initDeflateWasm } = await import("./libdeflateWasm-DlHgU5oy.mjs").then((n) => n.r);
4830
+ const { LibdeflateWasm: LibdeflateCtor, initDeflateWasm } = await import("./libdeflateWasm-82loOtIV.mjs").then((n) => n.r);
4830
4831
  let customBytes;
4831
4832
  try {
4832
- const { getWasmLoaderConfig } = await import("./loader-CQfoGFp9.mjs");
4833
+ const { getWasmLoaderConfig } = await import("./loader-1VJXLlMZ.mjs");
4833
4834
  customBytes = getWasmLoaderConfig().moduleBytes?.["libdeflate"];
4834
4835
  } catch {}
4835
4836
  await initDeflateWasm(customBytes);
@@ -6306,15 +6307,76 @@ function padPassword(password) {
6306
6307
  return result;
6307
6308
  }
6308
6309
  /**
6309
- * Truncate a UTF-8 password to 127 bytes for R=5/R=6 (SASLprep would
6310
- * be ideal but we approximate with simple truncation as most PDF
6311
- * libraries do).
6310
+ * Prepare a password for R=5/R=6 (AES-256) using SASLprep (RFC 4013).
6311
+ *
6312
+ * ISO 32000-2 SS7.6.3.1 requires SASLprep normalization for passwords
6313
+ * used with encryption revision 5 and 6. The steps are:
6314
+ *
6315
+ * 1. Map: Convert non-ASCII space characters to U+0020, remove
6316
+ * "commonly mapped to nothing" characters (RFC 3454 B.1).
6317
+ * 2. Normalize: Apply Unicode NFKC normalization.
6318
+ * 3. Prohibit: Reject characters from RFC 3454 prohibited tables.
6319
+ * 4. Bidi: Check bidirectional string rules.
6320
+ *
6321
+ * The result is truncated to 127 UTF-8 bytes per the PDF spec.
6312
6322
  */
6313
6323
  function preparePasswordV5(password) {
6314
- const encoded = new TextEncoder().encode(password);
6324
+ const prepared = saslprep(password);
6325
+ const encoded = new TextEncoder().encode(prepared);
6315
6326
  return encoded.length > 127 ? encoded.subarray(0, 127) : encoded;
6316
6327
  }
6317
6328
  /**
6329
+ * Simplified SASLprep (RFC 4013) profile of stringprep (RFC 3454).
6330
+ *
6331
+ * Covers the mapping, normalization, and prohibited-character steps
6332
+ * needed for PDF password preparation. Bidi checking is omitted
6333
+ * since PDF passwords are typically LTR and the spec allows
6334
+ * implementations to skip it.
6335
+ *
6336
+ * @internal
6337
+ */
6338
+ function saslprep(input) {
6339
+ let mapped = "";
6340
+ for (const ch of input) {
6341
+ const cp = ch.codePointAt(0);
6342
+ if (isMappedToNothing(cp)) continue;
6343
+ if (isNonAsciiSpace(cp)) {
6344
+ mapped += " ";
6345
+ continue;
6346
+ }
6347
+ mapped += ch;
6348
+ }
6349
+ const normalized = mapped.normalize("NFKC");
6350
+ for (const ch of normalized) {
6351
+ const cp = ch.codePointAt(0);
6352
+ if (isProhibited(cp)) throw new Error(`Password contains prohibited character U+${cp.toString(16).toUpperCase().padStart(4, "0")} (SASLprep)`);
6353
+ }
6354
+ return normalized;
6355
+ }
6356
+ /** RFC 3454 Table B.1 — Commonly mapped to nothing. */
6357
+ function isMappedToNothing(cp) {
6358
+ return cp === 173 || cp === 6150 || cp === 8203 || cp === 8288 || cp === 65279 || cp === 847 || cp === 6155 || cp === 6156 || cp === 6157 || cp === 8204 || cp === 8205 || cp >= 65024 && cp <= 65039;
6359
+ }
6360
+ /** RFC 3454 Table C.1.2 — Non-ASCII space characters. */
6361
+ function isNonAsciiSpace(cp) {
6362
+ return cp === 160 || cp === 5760 || cp === 8192 || cp === 8193 || cp === 8194 || cp === 8195 || cp === 8196 || cp === 8197 || cp === 8198 || cp === 8199 || cp === 8200 || cp === 8201 || cp === 8202 || cp === 8239 || cp === 8287 || cp === 12288;
6363
+ }
6364
+ /**
6365
+ * RFC 3454 prohibited tables (C.2.1, C.2.2, C.3, C.4, C.5, C.6, C.7, C.8, C.9).
6366
+ * Simplified to the ranges most likely to appear in passwords.
6367
+ */
6368
+ function isProhibited(cp) {
6369
+ if (cp <= 31 || cp === 127) return true;
6370
+ if (cp >= 128 && cp <= 159 || cp === 1757 || cp === 1807 || cp === 6158 || cp >= 8204 && cp <= 8205 || cp >= 8232 && cp <= 8233 || cp >= 8288 && cp <= 8291 || cp >= 8298 && cp <= 8303 || cp === 65279 || cp >= 65529 && cp <= 65532 || cp >= 119155 && cp <= 119162) return true;
6371
+ if (cp >= 57344 && cp <= 63743 || cp >= 983040 && cp <= 1048573 || cp >= 1048576 && cp <= 1114109) return true;
6372
+ if (cp >= 64976 && cp <= 65007 || (cp & 65535) === 65534 || (cp & 65535) === 65535) return true;
6373
+ if (cp >= 55296 && cp <= 57343) return true;
6374
+ if (cp === 65529 || cp === 65530 || cp === 65531 || cp === 65532) return true;
6375
+ if (cp === 832 || cp === 833 || cp === 8206 || cp === 8207 || cp >= 8234 && cp <= 8238 || cp >= 8298 && cp <= 8303) return true;
6376
+ if (cp === 917505 || cp >= 917536 && cp <= 917631) return true;
6377
+ return false;
6378
+ }
6379
+ /**
6318
6380
  * Concatenate multiple Uint8Arrays into one.
6319
6381
  */
6320
6382
  function concat$1(...arrays) {
@@ -6593,11 +6655,33 @@ async function computeEncryptionKeyR6(password, dict, isOwner) {
6593
6655
  }
6594
6656
  }
6595
6657
  /**
6658
+ * LRU cache for file encryption keys.
6659
+ *
6660
+ * Keyed on a hash derived from password + encryption dict parameters,
6661
+ * so re-opening the same PDF with the same password skips the expensive
6662
+ * key derivation (especially for R=6 which runs 64+ rounds of AES+SHA).
6663
+ */
6664
+ const fileKeyCache = /* @__PURE__ */ new Map();
6665
+ const FILE_KEY_CACHE_MAX = 32;
6666
+ /**
6667
+ * Build a cache key string from the inputs that uniquely identify
6668
+ * a key derivation result.
6669
+ */
6670
+ function buildCacheKey(password, dict, fileId) {
6671
+ const oHex = Array.from(dict.ownerKey.subarray(0, 16), (b) => b.toString(16).padStart(2, "0")).join("");
6672
+ const uHex = Array.from(dict.userKey.subarray(0, 16), (b) => b.toString(16).padStart(2, "0")).join("");
6673
+ const fHex = Array.from(fileId.subarray(0, 16), (b) => b.toString(16).padStart(2, "0")).join("");
6674
+ return `${dict.revision}:${dict.permissions}:${password}:${oHex}:${uHex}:${fHex}`;
6675
+ }
6676
+ /**
6596
6677
  * Compute the file encryption key from a password and encryption dict.
6597
6678
  *
6598
6679
  * Tries the password as both user and owner password. Returns the key
6599
6680
  * on the first successful match, or throws if neither works.
6600
6681
  *
6682
+ * Results are cached so that re-opening the same PDF with the same
6683
+ * password skips the expensive key derivation.
6684
+ *
6601
6685
  * @param password The password to try.
6602
6686
  * @param dict Encryption dictionary values.
6603
6687
  * @param fileId The first element of the /ID array (unused for R>=5).
@@ -6605,23 +6689,35 @@ async function computeEncryptionKeyR6(password, dict, isOwner) {
6605
6689
  * @throws If the password is incorrect.
6606
6690
  */
6607
6691
  async function computeFileEncryptionKey(password, dict, fileId) {
6692
+ const ck = buildCacheKey(password, dict, fileId);
6693
+ const cached = fileKeyCache.get(ck);
6694
+ if (cached) return cached.slice();
6695
+ let result;
6608
6696
  if (dict.revision >= 6) {
6609
6697
  const userKey = await computeEncryptionKeyR6(password, dict, false);
6610
- if (userKey) return userKey;
6611
- const ownerKey = await computeEncryptionKeyR6(password, dict, true);
6612
- if (ownerKey) return ownerKey;
6613
- throw new Error("Incorrect password for R=6 encryption");
6614
- }
6615
- if (dict.revision === 5) {
6698
+ if (userKey) result = userKey;
6699
+ else {
6700
+ const ownerKey = await computeEncryptionKeyR6(password, dict, true);
6701
+ if (ownerKey) result = ownerKey;
6702
+ else throw new Error("Incorrect password for R=6 encryption");
6703
+ }
6704
+ } else if (dict.revision === 5) {
6616
6705
  const userKey = await computeEncryptionKeyR5(password, dict, false);
6617
- if (userKey) return userKey;
6618
- const ownerKey = await computeEncryptionKeyR5(password, dict, true);
6619
- if (ownerKey) return ownerKey;
6620
- throw new Error("Incorrect password for R=5 encryption");
6621
- }
6622
- if (await verifyUserPassword(password, dict, fileId)) return computeEncryptionKeyR2R4(password, dict, fileId);
6623
- if (await verifyOwnerPassword(password, dict, fileId)) return computeEncryptionKeyR2R4Bytes(recoverUserKeyFromOwner(password, dict), dict, fileId);
6624
- throw new Error("Incorrect password");
6706
+ if (userKey) result = userKey;
6707
+ else {
6708
+ const ownerKey = await computeEncryptionKeyR5(password, dict, true);
6709
+ if (ownerKey) result = ownerKey;
6710
+ else throw new Error("Incorrect password for R=5 encryption");
6711
+ }
6712
+ } else if (await verifyUserPassword(password, dict, fileId)) result = computeEncryptionKeyR2R4(password, dict, fileId);
6713
+ else if (await verifyOwnerPassword(password, dict, fileId)) result = computeEncryptionKeyR2R4Bytes(recoverUserKeyFromOwner(password, dict), dict, fileId);
6714
+ else throw new Error("Incorrect password");
6715
+ if (fileKeyCache.size >= FILE_KEY_CACHE_MAX) {
6716
+ const firstKey = fileKeyCache.keys().next().value;
6717
+ if (firstKey !== void 0) fileKeyCache.delete(firstKey);
6718
+ }
6719
+ fileKeyCache.set(ck, result.slice());
6720
+ return result;
6625
6721
  }
6626
6722
  /**
6627
6723
  * Compute encryption key from raw password bytes (already padded/recovered).
@@ -6884,6 +6980,15 @@ var PdfEncryptionHandler = class PdfEncryptionHandler {
6884
6980
  perms;
6885
6981
  /** The file ID (first element of /ID array). */
6886
6982
  fileId;
6983
+ /**
6984
+ * Cache for per-object derived keys (V=1-4 only).
6985
+ * Key: `(objNum << 16) | genNum` — unique integer per object.
6986
+ * Value: the derived encryption key.
6987
+ *
6988
+ * Avoids recomputing MD5(fileKey + objNum + genNum [+ sAlT]) for
6989
+ * every string and stream in the same object.
6990
+ */
6991
+ objectKeyCache = /* @__PURE__ */ new Map();
6887
6992
  constructor(params) {
6888
6993
  this.fileKey = params.fileKey;
6889
6994
  this.version = params.version;
@@ -7036,6 +7141,9 @@ var PdfEncryptionHandler = class PdfEncryptionHandler {
7036
7141
  */
7037
7142
  deriveObjectKey(objNum, genNum) {
7038
7143
  if (this.version === 5) return this.fileKey;
7144
+ const cacheKey = objNum << 16 | genNum;
7145
+ const cached = this.objectKeyCache.get(cacheKey);
7146
+ if (cached) return cached;
7039
7147
  const extra = this.useAes ? 4 : 0;
7040
7148
  const input = new Uint8Array(this.fileKey.length + 5 + extra);
7041
7149
  input.set(this.fileKey, 0);
@@ -7053,7 +7161,9 @@ var PdfEncryptionHandler = class PdfEncryptionHandler {
7053
7161
  }
7054
7162
  const hash = md5(input);
7055
7163
  const keyLen = Math.min(this.keyLengthBits / 8 + 5, 16);
7056
- return hash.subarray(0, keyLen);
7164
+ const key = hash.subarray(0, keyLen);
7165
+ this.objectKeyCache.set(cacheKey, key);
7166
+ return key;
7057
7167
  }
7058
7168
  /**
7059
7169
  * Encrypt raw data for a specific object.
@@ -9881,7 +9991,7 @@ function isAccessible(issues) {
9881
9991
  * @param data The bytes to hash.
9882
9992
  * @returns A hex string hash.
9883
9993
  */
9884
- function hashBytes(data) {
9994
+ function hashBytes$1(data) {
9885
9995
  let hash = 2166136261;
9886
9996
  for (let i = 0; i < data.length; i++) {
9887
9997
  hash ^= data[i];
@@ -9973,7 +10083,7 @@ function remapRef$1(sourceRef, context) {
9973
10083
  }
9974
10084
  let streamHash;
9975
10085
  if (sourceObj.kind === "stream") {
9976
- streamHash = hashBytes(sourceObj.data);
10086
+ streamHash = hashBytes$1(sourceObj.data);
9977
10087
  const dedup = context.hashMap.get(streamHash);
9978
10088
  if (dedup) {
9979
10089
  context.refMap.set(sourceRef.objectNumber, dedup);
@@ -10215,23 +10325,73 @@ function findStringForward(data, needle, startOffset) {
10215
10325
  return -1;
10216
10326
  }
10217
10327
  /**
10218
- * Prepare a PDF for signing by appending a signature dictionary
10219
- * via incremental update.
10220
- *
10221
- * This function:
10222
- * 1. Appends a new signature field and value to the PDF
10223
- * 2. Inserts an empty `/Contents` placeholder of the specified size
10224
- * 3. Computes the `/ByteRange` that excludes the `/Contents` value
10328
+ * Build a PDF content stream for a visible signature appearance.
10225
10329
  *
10226
- * The resulting PDF bytes can be hashed (excluding the placeholder gap)
10227
- * and the hash can be signed.
10330
+ * Renders a bordered rectangle with optional background color and
10331
+ * text lines rendered in Helvetica.
10228
10332
  *
10229
- * @param pdfBytes The original PDF file bytes.
10230
- * @param signatureFieldName The name for the signature field.
10231
- * @param placeholderSize Size in bytes for the signature. Default 8192.
10232
- * @returns The prepared PDF and ByteRange info.
10333
+ * @internal
10334
+ */
10335
+ function buildSignatureAppearanceStream(options) {
10336
+ const [, , w, h] = options.rect;
10337
+ const fontSize = options.fontSize ?? 10;
10338
+ const borderWidth = options.borderWidth ?? 1;
10339
+ const borderColor = options.borderColor ?? [
10340
+ 0,
10341
+ 0,
10342
+ 0
10343
+ ];
10344
+ const bgColor = options.backgroundColor;
10345
+ const lines = options.textLines;
10346
+ const ops = [];
10347
+ ops.push("q");
10348
+ if (bgColor) {
10349
+ ops.push(`${n$5(bgColor[0])} ${n$5(bgColor[1])} ${n$5(bgColor[2])} rg`);
10350
+ ops.push(`0 0 ${n$5(w)} ${n$5(h)} re`);
10351
+ ops.push("f");
10352
+ }
10353
+ if (borderWidth > 0) {
10354
+ ops.push(`${n$5(borderColor[0])} ${n$5(borderColor[1])} ${n$5(borderColor[2])} RG`);
10355
+ ops.push(`${n$5(borderWidth)} w`);
10356
+ const bw2 = borderWidth / 2;
10357
+ ops.push(`${n$5(bw2)} ${n$5(bw2)} ${n$5(w - borderWidth)} ${n$5(h - borderWidth)} re`);
10358
+ ops.push("S");
10359
+ }
10360
+ if (lines.length > 0) {
10361
+ const margin = borderWidth + 4;
10362
+ const lineHeight = fontSize * 1.2;
10363
+ ops.push("BT");
10364
+ ops.push(`/F1 ${n$5(fontSize)} Tf`);
10365
+ ops.push("0 0 0 rg");
10366
+ const startY = h - margin - fontSize;
10367
+ for (let i = 0; i < lines.length; i++) {
10368
+ const y = startY - i * lineHeight;
10369
+ if (y < margin) break;
10370
+ ops.push(`${n$5(margin)} ${n$5(y)} Td`);
10371
+ ops.push(`(${escapePdfString(lines[i])}) Tj`);
10372
+ ops.push(`${n$5(-margin)} ${n$5(-y)} Td`);
10373
+ }
10374
+ ops.push("ET");
10375
+ }
10376
+ ops.push("Q");
10377
+ return ops.join("\n");
10378
+ }
10379
+ /**
10380
+ * Escape a string for use inside a PDF literal string `(...)`.
10381
+ * @internal
10382
+ */
10383
+ function escapePdfString(str) {
10384
+ return str.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
10385
+ }
10386
+ /**
10387
+ * Format a number for PDF operators (max 6 decimal places, no trailing zeros).
10388
+ * @internal
10233
10389
  */
10234
- function prepareForSigning(pdfBytes, signatureFieldName, placeholderSize = 8192) {
10390
+ function n$5(value) {
10391
+ if (Number.isInteger(value)) return value.toString();
10392
+ return value.toFixed(6).replace(/\.?0+$/, "");
10393
+ }
10394
+ function prepareForSigning(pdfBytes, signatureFieldName, placeholderSize = 8192, appearance) {
10235
10395
  const pdfStr = decoder$4.decode(pdfBytes);
10236
10396
  const startxrefIdx = pdfStr.lastIndexOf("startxref");
10237
10397
  if (startxrefIdx === -1) throw new Error("Cannot find startxref in PDF — file may be corrupted");
@@ -10248,9 +10408,21 @@ function prepareForSigning(pdfBytes, signatureFieldName, placeholderSize = 8192)
10248
10408
  const infoMatch = pdfStr.match(/\/Info\s+(\d+)\s+(\d+)\s+R/);
10249
10409
  const sigValueObjNum = originalSize;
10250
10410
  const sigFieldObjNum = originalSize + 1;
10251
- const newSize = originalSize + 2;
10411
+ let apStreamObjNum = -1;
10412
+ let newSize = originalSize + 2;
10413
+ if (appearance) {
10414
+ apStreamObjNum = newSize;
10415
+ newSize++;
10416
+ }
10252
10417
  const sigDictStr = buildSignatureDictString(placeholderSize, signatureFieldName);
10253
- const sigFieldDict = `<< /Type /Annot /Subtype /Widget /FT /Sig /T (${signatureFieldName}) /V ${sigValueObjNum} 0 R /F 132 /Rect [0 0 0 0] >>`;
10418
+ let rectStr = "0 0 0 0";
10419
+ if (appearance) {
10420
+ const [x, y, w, h] = appearance.rect;
10421
+ rectStr = `${x} ${y} ${x + w} ${y + h}`;
10422
+ }
10423
+ let sigFieldDict = `<< /Type /Annot /Subtype /Widget /FT /Sig /T (${signatureFieldName}) /V ${sigValueObjNum} 0 R /F 132 /Rect [${rectStr}]`;
10424
+ if (appearance && apStreamObjNum >= 0) sigFieldDict += ` /AP << /N ${apStreamObjNum} 0 R >>`;
10425
+ sigFieldDict += " >>";
10254
10426
  let appendix = "\n";
10255
10427
  const objOffsets = /* @__PURE__ */ new Map();
10256
10428
  const sigValueStart = pdfBytes.length + encoder$3.encode(appendix).length;
@@ -10263,11 +10435,28 @@ function prepareForSigning(pdfBytes, signatureFieldName, placeholderSize = 8192)
10263
10435
  appendix += `${sigFieldObjNum} 0 obj\n`;
10264
10436
  appendix += sigFieldDict;
10265
10437
  appendix += `\nendobj\n`;
10438
+ let apStreamStart = -1;
10439
+ if (appearance && apStreamObjNum >= 0) {
10440
+ apStreamStart = pdfBytes.length + encoder$3.encode(appendix).length;
10441
+ objOffsets.set(apStreamObjNum, apStreamStart);
10442
+ const apContent = buildSignatureAppearanceStream(appearance);
10443
+ const [, , w, h] = appearance.rect;
10444
+ appendix += `${apStreamObjNum} 0 obj\n`;
10445
+ appendix += `<< /Type /XObject /Subtype /Form /BBox [0 0 ${w} ${h}]`;
10446
+ appendix += ` /Resources << /Font << /F1 << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> >> >>`;
10447
+ appendix += ` /Length ${apContent.length} >>\n`;
10448
+ appendix += `stream\n`;
10449
+ appendix += apContent;
10450
+ appendix += `\nendstream\n`;
10451
+ appendix += `endobj\n`;
10452
+ }
10266
10453
  const xrefOffset = pdfBytes.length + encoder$3.encode(appendix).length;
10454
+ const objCount = appearance ? 3 : 2;
10267
10455
  appendix += "xref\n";
10268
- appendix += `${sigValueObjNum} 2\n`;
10456
+ appendix += `${sigValueObjNum} ${objCount}\n`;
10269
10457
  appendix += `${sigValueStart.toString().padStart(10, "0")} 00000 n \n`;
10270
10458
  appendix += `${sigFieldStart.toString().padStart(10, "0")} 00000 n \n`;
10459
+ if (appearance && apStreamStart >= 0) appendix += `${apStreamStart.toString().padStart(10, "0")} 00000 n \n`;
10271
10460
  appendix += "trailer\n";
10272
10461
  appendix += "<<\n";
10273
10462
  appendix += `/Size ${newSize}\n`;
@@ -11057,7 +11246,32 @@ function parsePkcs7ForCert(pkcs7Bytes) {
11057
11246
  */
11058
11247
  async function signPdf(pdfBytes, fieldName, options) {
11059
11248
  const hashAlgorithm = options.hashAlgorithm ?? "SHA-256";
11060
- const { preparedPdf, byteRange } = prepareForSigning(pdfBytes, fieldName, 8192);
11249
+ let prepareAppearance;
11250
+ if (options.appearance) {
11251
+ const ap = options.appearance;
11252
+ let textLines = ap.text;
11253
+ if (!textLines) {
11254
+ textLines = [];
11255
+ try {
11256
+ const { subjectCN } = extractIssuerAndSerial(options.certificate);
11257
+ if (subjectCN) textLines.push(`Signed by: ${subjectCN}`);
11258
+ } catch {
11259
+ textLines.push("Digitally Signed");
11260
+ }
11261
+ if (options.reason) textLines.push(`Reason: ${options.reason}`);
11262
+ if (options.location) textLines.push(`Location: ${options.location}`);
11263
+ textLines.push(`Date: ${(/* @__PURE__ */ new Date()).toISOString().substring(0, 10)}`);
11264
+ }
11265
+ prepareAppearance = {
11266
+ rect: ap.rect,
11267
+ textLines,
11268
+ fontSize: ap.fontSize,
11269
+ backgroundColor: ap.backgroundColor,
11270
+ borderColor: ap.borderColor,
11271
+ borderWidth: ap.borderWidth
11272
+ };
11273
+ }
11274
+ const { preparedPdf, byteRange } = prepareForSigning(pdfBytes, fieldName, 8192, prepareAppearance);
11061
11275
  return embedSignature(preparedPdf, await buildPkcs7Signature(await computeSignatureHash(preparedPdf, byteRange.byteRange, hashAlgorithm), {
11062
11276
  signerInfo: {
11063
11277
  certificate: options.certificate,
@@ -18869,6 +19083,30 @@ var PdfDocument = class PdfDocument {
18869
19083
  return imageRef;
18870
19084
  }
18871
19085
  /**
19086
+ * Embed an image, auto-detecting the format from file headers.
19087
+ *
19088
+ * Inspects the first bytes to determine whether the data is PNG or JPEG,
19089
+ * then delegates to {@link embedPng} or {@link embedJpeg} accordingly.
19090
+ *
19091
+ * @param imageData Raw image file bytes (PNG or JPEG).
19092
+ * @returns An {@link ImageRef} to pass to `page.drawImage()`.
19093
+ * @throws If the image format cannot be detected.
19094
+ *
19095
+ * @example
19096
+ * ```ts
19097
+ * const bytes = new Uint8Array(await readFile('photo.jpg'));
19098
+ * const image = await pdf.embedImage(bytes);
19099
+ * page.drawImage(image, { x: 50, y: 400, width: 200, height: 150 });
19100
+ * ```
19101
+ */
19102
+ async embedImage(imageData) {
19103
+ const data = imageData instanceof ArrayBuffer ? new Uint8Array(imageData) : imageData;
19104
+ if (data.length < 4) throw new Error("Image data too short to detect format");
19105
+ if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) return this.embedPng(data);
19106
+ if (data[0] === 255 && data[1] === 216 && data[2] === 255) return this.embedJpeg(data);
19107
+ throw new Error(`Unsupported image format. Expected PNG (89 50 4E 47) or JPEG (FF D8 FF), got ${Array.from(data.subarray(0, 4)).map((b) => b.toString(16).padStart(2, "0")).join(" ")}.`);
19108
+ }
19109
+ /**
18872
19110
  * Embed pages from another PDF as Form XObjects.
18873
19111
  *
18874
19112
  * Each embedded page is turned into a self-contained Form XObject that
@@ -21769,6 +22007,261 @@ var PdfRedactAnnotation = class PdfRedactAnnotation extends PdfAnnotation {
21769
22007
  }
21770
22008
  };
21771
22009
 
22010
+ //#endregion
22011
+ //#region src/annotation/types/popupAnnotation.ts
22012
+ /**
22013
+ * @module annotation/types/popupAnnotation
22014
+ *
22015
+ * Popup annotation — a floating window that displays the text of its
22016
+ * parent annotation (typically a text/sticky note annotation).
22017
+ *
22018
+ * Popup annotations have no appearance of their own; the PDF viewer
22019
+ * renders them as a resizable window near the parent annotation.
22020
+ *
22021
+ * Reference: PDF 1.7 spec, Section 12.5.6.14 (Popup Annotations).
22022
+ */
22023
+ /**
22024
+ * A popup annotation (subtype /Popup).
22025
+ *
22026
+ * Displays a floating window containing the text of its parent
22027
+ * annotation. The parent annotation references this popup via its
22028
+ * `/Popup` entry, and this popup references its parent via `/Parent`.
22029
+ */
22030
+ var PdfPopupAnnotation = class PdfPopupAnnotation extends PdfAnnotation {
22031
+ constructor(dict) {
22032
+ super("Popup", dict);
22033
+ }
22034
+ /**
22035
+ * Create a new popup annotation.
22036
+ *
22037
+ * @param options.open Whether the popup is initially open. Default: false.
22038
+ * @param options.parent Reference to the parent annotation (set after registration).
22039
+ */
22040
+ static create(options) {
22041
+ const annot = new PdfPopupAnnotation(buildAnnotationDict("Popup", options));
22042
+ if (options.open !== void 0) annot.setOpen(options.open);
22043
+ return annot;
22044
+ }
22045
+ /**
22046
+ * Create a PdfPopupAnnotation from an existing dictionary.
22047
+ */
22048
+ static fromDict(dict, _resolver) {
22049
+ return new PdfPopupAnnotation(dict);
22050
+ }
22051
+ /** Whether the popup is initially open. */
22052
+ isOpen() {
22053
+ const obj = this.dict.get("/Open");
22054
+ if (obj && obj.kind === "bool") return obj.value;
22055
+ return false;
22056
+ }
22057
+ /** Set the initial open state. */
22058
+ setOpen(open) {
22059
+ this.dict.set("/Open", PdfBool.of(open));
22060
+ }
22061
+ /**
22062
+ * Set the parent annotation reference.
22063
+ * The parent is the annotation whose text this popup displays.
22064
+ */
22065
+ setParent(parentRef) {
22066
+ this.dict.set("/Parent", parentRef);
22067
+ }
22068
+ /** Get the parent annotation reference, if set. */
22069
+ getParent() {
22070
+ const obj = this.dict.get("/Parent");
22071
+ if (obj && obj.kind === "ref") return obj;
22072
+ }
22073
+ };
22074
+
22075
+ //#endregion
22076
+ //#region src/annotation/types/caretAnnotation.ts
22077
+ /**
22078
+ * @module annotation/types/caretAnnotation
22079
+ *
22080
+ * Caret annotation — marks a text insertion point in the document.
22081
+ *
22082
+ * A caret annotation indicates where text should be inserted. It is
22083
+ * typically used in document review workflows to suggest additions.
22084
+ * The annotation renders as a caret (^) symbol at the specified
22085
+ * location.
22086
+ *
22087
+ * Reference: PDF 1.7 spec, Section 12.5.6.11 (Caret Annotations).
22088
+ */
22089
+ /**
22090
+ * A caret annotation (subtype /Caret).
22091
+ *
22092
+ * Marks an insertion point in the text. Used in review workflows
22093
+ * to indicate where new content should be added.
22094
+ */
22095
+ var PdfCaretAnnotation = class PdfCaretAnnotation extends PdfAnnotation {
22096
+ constructor(dict) {
22097
+ super("Caret", dict);
22098
+ }
22099
+ /**
22100
+ * Create a new caret annotation.
22101
+ *
22102
+ * @param options.symbol The caret symbol. Default: 'None'.
22103
+ * @param options.caretRect The inner rectangle (RD) that describes
22104
+ * the difference between the annotation rect and the actual caret
22105
+ * position. Format: [left, bottom, right, top] insets.
22106
+ */
22107
+ static create(options) {
22108
+ const annot = new PdfCaretAnnotation(buildAnnotationDict("Caret", options));
22109
+ if (options.symbol !== void 0) annot.setSymbol(options.symbol);
22110
+ if (options.caretRect !== void 0) annot.setCaretRect(options.caretRect);
22111
+ return annot;
22112
+ }
22113
+ /**
22114
+ * Create a PdfCaretAnnotation from an existing dictionary.
22115
+ */
22116
+ static fromDict(dict, _resolver) {
22117
+ return new PdfCaretAnnotation(dict);
22118
+ }
22119
+ /** Get the caret symbol. Defaults to 'None'. */
22120
+ getSymbol() {
22121
+ const obj = this.dict.get("/Sy");
22122
+ if (obj && obj.kind === "name") {
22123
+ if ((obj.value.startsWith("/") ? obj.value.slice(1) : obj.value) === "P") return "P";
22124
+ }
22125
+ return "None";
22126
+ }
22127
+ /** Set the caret symbol. */
22128
+ setSymbol(symbol) {
22129
+ this.dict.set("/Sy", PdfName.of(symbol));
22130
+ }
22131
+ /**
22132
+ * Get the inner rectangle differences (RD entry).
22133
+ * Returns [left, bottom, right, top] insets from the annotation rect.
22134
+ */
22135
+ getCaretRect() {
22136
+ const obj = this.dict.get("/RD");
22137
+ if (obj && obj.kind === "array" && obj.items.length === 4) return obj.items.map((item) => {
22138
+ if (item.kind === "number") return item.value;
22139
+ return 0;
22140
+ });
22141
+ }
22142
+ /** Set the inner rectangle differences (RD entry). */
22143
+ setCaretRect(rd) {
22144
+ this.dict.set("/RD", PdfArray.of(rd.map(PdfNumber.of)));
22145
+ }
22146
+ };
22147
+
22148
+ //#endregion
22149
+ //#region src/annotation/types/fileAttachmentAnnotation.ts
22150
+ /**
22151
+ * @module annotation/types/fileAttachmentAnnotation
22152
+ *
22153
+ * File attachment annotation — embeds a file as an inline annotation
22154
+ * on a page, displayed as a clickable icon.
22155
+ *
22156
+ * Unlike document-level attachments (via `attachFile()`), file attachment
22157
+ * annotations are positioned on a specific page and rendered as an icon
22158
+ * that users can click to open or save the embedded file.
22159
+ *
22160
+ * Reference: PDF 1.7 spec, Section 12.5.6.15 (File Attachment Annotations).
22161
+ */
22162
+ /**
22163
+ * A file attachment annotation (subtype /FileAttachment).
22164
+ *
22165
+ * Embeds a file directly in the annotation, rendered as a clickable
22166
+ * icon on the page. When the user clicks the icon, the PDF viewer
22167
+ * allows them to open or save the embedded file.
22168
+ */
22169
+ var PdfFileAttachmentAnnotation = class PdfFileAttachmentAnnotation extends PdfAnnotation {
22170
+ /** The raw file data to embed. */
22171
+ fileData;
22172
+ /** The filename to display. */
22173
+ fileName;
22174
+ /** Optional MIME type. */
22175
+ mimeType;
22176
+ /** Optional file description. */
22177
+ fileDescription;
22178
+ constructor(dict) {
22179
+ super("FileAttachment", dict);
22180
+ }
22181
+ /**
22182
+ * Create a new file attachment annotation.
22183
+ *
22184
+ * @param options.file The file data to embed.
22185
+ * @param options.fileName The filename (e.g., 'invoice.xml').
22186
+ * @param options.mimeType Optional MIME type (e.g., 'application/xml').
22187
+ * @param options.description Optional description of the file.
22188
+ * @param options.icon Icon to display. Default: 'GraphPushPin'.
22189
+ */
22190
+ static create(options) {
22191
+ const annot = new PdfFileAttachmentAnnotation(buildAnnotationDict("FileAttachment", options));
22192
+ annot.fileData = options.file;
22193
+ annot.fileName = options.fileName;
22194
+ annot.mimeType = options.mimeType;
22195
+ annot.fileDescription = options.description;
22196
+ if (options.icon !== void 0) annot.setIcon(options.icon);
22197
+ return annot;
22198
+ }
22199
+ /**
22200
+ * Create a PdfFileAttachmentAnnotation from an existing dictionary.
22201
+ */
22202
+ static fromDict(dict, _resolver) {
22203
+ return new PdfFileAttachmentAnnotation(dict);
22204
+ }
22205
+ /** Get the icon name. Defaults to 'GraphPushPin'. */
22206
+ getIcon() {
22207
+ const obj = this.dict.get("/Name");
22208
+ if (obj && obj.kind === "name") {
22209
+ const val = obj.value.startsWith("/") ? obj.value.slice(1) : obj.value;
22210
+ if ([
22211
+ "GraphPushPin",
22212
+ "PaperclipTag",
22213
+ "Paperclip",
22214
+ "Tag"
22215
+ ].includes(val)) return val;
22216
+ }
22217
+ return "GraphPushPin";
22218
+ }
22219
+ /** Set the icon name. */
22220
+ setIcon(icon) {
22221
+ this.dict.set("/Name", PdfName.of(icon));
22222
+ }
22223
+ /** Get the filename, if set. */
22224
+ getFileName() {
22225
+ const fs = this.dict.get("/FS");
22226
+ if (fs && fs.kind === "dict") {
22227
+ const uf = fs.get("/UF");
22228
+ if (uf && uf.kind === "string") return uf.value;
22229
+ const f = fs.get("/F");
22230
+ if (f && f.kind === "string") return f.value;
22231
+ }
22232
+ return this.fileName;
22233
+ }
22234
+ /**
22235
+ * Build the file specification dictionary and register the embedded
22236
+ * file stream. Call this before serializing the annotation.
22237
+ *
22238
+ * @param registry The document's object registry.
22239
+ * @returns The annotation dict with `/FS` referencing the file.
22240
+ */
22241
+ buildFileSpec(registry) {
22242
+ if (!this.fileData || !this.fileName) return this.dict;
22243
+ const efStreamDict = new PdfDict();
22244
+ efStreamDict.set("/Type", PdfName.of("EmbeddedFile"));
22245
+ if (this.mimeType) efStreamDict.set("/Subtype", PdfName.of(this.mimeType.replace("/", "#2F")));
22246
+ const params = new PdfDict();
22247
+ params.set("/Size", PdfNumber.of(this.fileData.length));
22248
+ efStreamDict.set("/Params", params);
22249
+ const efStream = new PdfStream(efStreamDict, this.fileData);
22250
+ const efRef = registry.register(efStream);
22251
+ const efDict = new PdfDict();
22252
+ efDict.set("/F", efRef);
22253
+ const fsDict = new PdfDict();
22254
+ fsDict.set("/Type", PdfName.of("Filespec"));
22255
+ fsDict.set("/F", PdfString.literal(this.fileName));
22256
+ fsDict.set("/UF", PdfString.literal(this.fileName));
22257
+ fsDict.set("/EF", efDict);
22258
+ if (this.fileDescription) fsDict.set("/Desc", PdfString.literal(this.fileDescription));
22259
+ const fsRef = registry.register(fsDict);
22260
+ this.dict.set("/FS", fsRef);
22261
+ return this.dict;
22262
+ }
22263
+ };
22264
+
21772
22265
  //#endregion
21773
22266
  //#region src/parser/textExtractor.ts
21774
22267
  /**
@@ -23853,66 +24346,1197 @@ function addTrailerId(data) {
23853
24346
  }
23854
24347
 
23855
24348
  //#endregion
23856
- //#region src/errors.ts
24349
+ //#region src/assets/image/imageOptimize.ts
23857
24350
  /**
23858
- * @module errors
24351
+ * Downscale an image to fit within the specified dimensions.
23859
24352
  *
23860
- * Typed error classes for common failure modes in the modern-pdf library.
23861
- * Each error class extends the native `Error` and carries a descriptive
23862
- * `name` so callers can use `instanceof` checks or `error.name` comparisons.
24353
+ * If the image is already smaller than the target dimensions, it is
24354
+ * returned unchanged.
23863
24355
  *
23864
- * All constructors accept an optional `ErrorOptions` parameter to support
23865
- * error chaining via the standard `{ cause }` option (ES2022+).
24356
+ * @param image - The raw image pixel data.
24357
+ * @param options - Downscaling options (target dimensions, algorithm).
24358
+ * @returns The downscaled image, or the original if no scaling needed.
23866
24359
  *
23867
- * These match the pdf-lib error hierarchy for API compatibility.
23868
- */
23869
- /**
23870
- * Thrown when attempting to load or manipulate an encrypted PDF without
23871
- * providing the correct password.
24360
+ * @example
24361
+ * ```ts
24362
+ * const result = downscaleImage(rawImage, {
24363
+ * maxWidth: 1024,
24364
+ * maxHeight: 768,
24365
+ * algorithm: 'bilinear',
24366
+ * });
24367
+ * ```
23872
24368
  */
23873
- var EncryptedPdfError = class extends Error {
23874
- name = "EncryptedPdfError";
23875
- constructor(message = "The PDF is encrypted. Please provide a password.", options) {
23876
- super(message, options);
24369
+ function downscaleImage(image, options = {}) {
24370
+ const target = computeTargetDimensions$1(image.width, image.height, options);
24371
+ if (target.width >= image.width && target.height >= image.height) return image;
24372
+ switch (options.algorithm ?? "bilinear") {
24373
+ case "nearest": return resampleNearest(image, target.width, target.height);
24374
+ case "bilinear": return resampleBilinear(image, target.width, target.height);
24375
+ case "lanczos": return resampleLanczos(image, target.width, target.height);
24376
+ default: return resampleBilinear(image, target.width, target.height);
23877
24377
  }
23878
- };
24378
+ }
23879
24379
  /**
23880
- * Thrown when a font operation requires an embedded font but none has been
23881
- * registered or the font reference is invalid.
24380
+ * Recompress raw image pixel data using the specified format.
24381
+ *
24382
+ * @param image - The raw image pixel data.
24383
+ * @param options - Recompression options (format, quality).
24384
+ * @returns The compressed image data.
24385
+ *
24386
+ * @example
24387
+ * ```ts
24388
+ * const result = await recompressImage(rawImage, {
24389
+ * format: 'deflate',
24390
+ * compressionLevel: 9,
24391
+ * });
24392
+ * ```
23882
24393
  */
23883
- var FontNotEmbeddedError = class extends Error {
23884
- name = "FontNotEmbeddedError";
23885
- constructor(fontName, options) {
23886
- super(fontName ? `The font "${fontName}" has not been embedded in this document.` : "No font has been embedded. Call doc.embedFont() first.", options);
24394
+ async function recompressImage(image, options = {}) {
24395
+ switch (options.format ?? "deflate") {
24396
+ case "deflate": return recompressDeflate(image, options.compressionLevel ?? 6);
24397
+ case "jpeg": return recompressJpeg(image, options.quality ?? 85, options.progressive ?? false, options.chromaSubsampling ?? "4:2:0");
24398
+ default: return {
24399
+ data: image.pixels,
24400
+ width: image.width,
24401
+ height: image.height,
24402
+ channels: image.channels,
24403
+ format: "raw",
24404
+ wasOptimized: false
24405
+ };
23887
24406
  }
23888
- };
24407
+ }
23889
24408
  /**
23890
- * Thrown when attempting to use a page from a different document without
23891
- * first copying it.
24409
+ * Run the full image optimization pipeline: downscale then recompress.
24410
+ *
24411
+ * @param image - The raw image pixel data.
24412
+ * @param options - Combined optimization options.
24413
+ * @returns The optimized result.
23892
24414
  */
23893
- var ForeignPageError = class extends Error {
23894
- name = "ForeignPageError";
23895
- constructor(options) {
23896
- super("The page belongs to a different document. Use doc.copyPages() to import pages from another document before adding them.", options);
23897
- }
23898
- };
24415
+ async function optimizeImage(image, options = {}) {
24416
+ if (options.skipBelowBytes && image.pixels.length < options.skipBelowBytes) return {
24417
+ data: image.pixels,
24418
+ width: image.width,
24419
+ height: image.height,
24420
+ channels: image.channels,
24421
+ format: "raw",
24422
+ wasOptimized: false
24423
+ };
24424
+ return recompressImage(downscaleImage(image, options), options);
24425
+ }
23899
24426
  /**
23900
- * Thrown when attempting to remove a page from a document that has no pages.
24427
+ * Standard JPEG luminance quantization table (Table K.1 from JPEG spec)
24428
+ * at quality 50, scaled to quality 100 = all-ones.
24429
+ * @internal
23901
24430
  */
23902
- var RemovePageFromEmptyDocumentError = class extends Error {
23903
- name = "RemovePageFromEmptyDocumentError";
23904
- constructor(options) {
23905
- super("Cannot remove a page from a document with no pages.", options);
23906
- }
23907
- };
24431
+ const STANDARD_LUMINANCE_QT = [
24432
+ 16,
24433
+ 11,
24434
+ 10,
24435
+ 16,
24436
+ 24,
24437
+ 40,
24438
+ 51,
24439
+ 61,
24440
+ 12,
24441
+ 12,
24442
+ 14,
24443
+ 19,
24444
+ 26,
24445
+ 58,
24446
+ 60,
24447
+ 55,
24448
+ 14,
24449
+ 13,
24450
+ 16,
24451
+ 24,
24452
+ 40,
24453
+ 57,
24454
+ 69,
24455
+ 56,
24456
+ 14,
24457
+ 17,
24458
+ 22,
24459
+ 29,
24460
+ 51,
24461
+ 87,
24462
+ 80,
24463
+ 62,
24464
+ 18,
24465
+ 22,
24466
+ 37,
24467
+ 56,
24468
+ 68,
24469
+ 109,
24470
+ 103,
24471
+ 77,
24472
+ 24,
24473
+ 35,
24474
+ 55,
24475
+ 64,
24476
+ 81,
24477
+ 104,
24478
+ 113,
24479
+ 92,
24480
+ 49,
24481
+ 64,
24482
+ 78,
24483
+ 87,
24484
+ 103,
24485
+ 121,
24486
+ 120,
24487
+ 101,
24488
+ 72,
24489
+ 92,
24490
+ 95,
24491
+ 98,
24492
+ 112,
24493
+ 100,
24494
+ 103,
24495
+ 99
24496
+ ];
23908
24497
  /**
23909
- * Thrown when looking up a form field by name that does not exist.
23910
- */
23911
- var NoSuchFieldError = class extends Error {
23912
- name = "NoSuchFieldError";
23913
- constructor(fieldName, options) {
23914
- super(`No form field named "${fieldName}" exists in this document.`, options);
23915
- }
24498
+ * Estimate the JPEG quality level (1–100) from the quantization tables
24499
+ * embedded in a JPEG file.
24500
+ *
24501
+ * Parses the DQT (Define Quantization Table, marker 0xFFDB) segments
24502
+ * from the raw JPEG bytes and compares the table values against the
24503
+ * standard JPEG luminance quantization table to estimate the quality
24504
+ * factor that was used during encoding.
24505
+ *
24506
+ * If no DQT marker is found, returns `undefined`.
24507
+ *
24508
+ * @param jpegBytes - Raw JPEG file bytes.
24509
+ * @returns Estimated quality 1–100, or `undefined` if no DQT is found.
24510
+ *
24511
+ * @example
24512
+ * ```ts
24513
+ * import { estimateJpegQuality } from 'modern-pdf-lib';
24514
+ *
24515
+ * const quality = estimateJpegQuality(jpegBytes);
24516
+ * if (quality !== undefined) {
24517
+ * console.log(`Estimated JPEG quality: ${quality}`);
24518
+ * }
24519
+ * ```
24520
+ */
24521
+ function estimateJpegQuality(jpegBytes) {
24522
+ if (jpegBytes.length < 2 || jpegBytes[0] !== 255 || jpegBytes[1] !== 216) return;
24523
+ let offset = 2;
24524
+ let bestTable;
24525
+ while (offset < jpegBytes.length - 1) {
24526
+ if (jpegBytes[offset] !== 255) {
24527
+ offset++;
24528
+ continue;
24529
+ }
24530
+ const marker = jpegBytes[offset + 1];
24531
+ if (marker === 255) {
24532
+ offset++;
24533
+ continue;
24534
+ }
24535
+ if (marker === 0 || marker === 1 || marker >= 208 && marker <= 217) {
24536
+ offset += 2;
24537
+ continue;
24538
+ }
24539
+ if (marker === 218) break;
24540
+ if (offset + 3 >= jpegBytes.length) break;
24541
+ const segLen = jpegBytes[offset + 2] << 8 | jpegBytes[offset + 3];
24542
+ if (marker === 219) {
24543
+ let pos = offset + 4;
24544
+ const segEnd = offset + 2 + segLen;
24545
+ while (pos < segEnd && pos + 1 < jpegBytes.length) {
24546
+ const pqTq = jpegBytes[pos];
24547
+ const precision = pqTq >> 4 & 15;
24548
+ const tableId = pqTq & 15;
24549
+ pos++;
24550
+ const tableSize = 64 * (precision === 0 ? 1 : 2);
24551
+ if (pos + tableSize > jpegBytes.length) break;
24552
+ const table = [];
24553
+ for (let i = 0; i < 64; i++) if (precision === 0) table.push(jpegBytes[pos + i]);
24554
+ else table.push(jpegBytes[pos + i * 2] << 8 | jpegBytes[pos + i * 2 + 1]);
24555
+ pos += tableSize;
24556
+ if (tableId === 0 || !bestTable) bestTable = table;
24557
+ }
24558
+ }
24559
+ offset += 2 + segLen;
24560
+ }
24561
+ if (!bestTable) return void 0;
24562
+ let totalRatio = 0;
24563
+ let count = 0;
24564
+ for (let i = 0; i < 64; i++) {
24565
+ const std = STANDARD_LUMINANCE_QT[i];
24566
+ const actual = bestTable[i];
24567
+ if (std === 0 || actual === 0) continue;
24568
+ const scaleFactor = actual * 100 / std;
24569
+ totalRatio += scaleFactor;
24570
+ count++;
24571
+ }
24572
+ if (count === 0) return void 0;
24573
+ const avgScale = totalRatio / count;
24574
+ let quality;
24575
+ if (avgScale < 100) quality = (200 - avgScale) / 2;
24576
+ else quality = 5e3 / avgScale;
24577
+ return Math.max(1, Math.min(100, Math.round(quality)));
24578
+ }
24579
+ /**
24580
+ * Compute target dimensions from options, preserving aspect ratio.
24581
+ * @internal
24582
+ */
24583
+ function computeTargetDimensions$1(srcWidth, srcHeight, options) {
24584
+ let targetWidth = srcWidth;
24585
+ let targetHeight = srcHeight;
24586
+ if (options.targetDpi && options.printWidth && options.printHeight) {
24587
+ const printWidthInches = options.printWidth / 72;
24588
+ const printHeightInches = options.printHeight / 72;
24589
+ const dpiWidth = Math.round(printWidthInches * options.targetDpi);
24590
+ const dpiHeight = Math.round(printHeightInches * options.targetDpi);
24591
+ targetWidth = Math.min(targetWidth, dpiWidth);
24592
+ targetHeight = Math.min(targetHeight, dpiHeight);
24593
+ }
24594
+ if (options.maxWidth && targetWidth > options.maxWidth) {
24595
+ const scale = options.maxWidth / targetWidth;
24596
+ targetWidth = options.maxWidth;
24597
+ targetHeight = Math.round(targetHeight * scale);
24598
+ }
24599
+ if (options.maxHeight && targetHeight > options.maxHeight) {
24600
+ const scale = options.maxHeight / targetHeight;
24601
+ targetHeight = options.maxHeight;
24602
+ targetWidth = Math.round(targetWidth * scale);
24603
+ }
24604
+ targetWidth = Math.max(1, targetWidth);
24605
+ targetHeight = Math.max(1, targetHeight);
24606
+ return {
24607
+ width: targetWidth,
24608
+ height: targetHeight
24609
+ };
24610
+ }
24611
+ /**
24612
+ * Nearest-neighbor resampling.
24613
+ * @internal
24614
+ */
24615
+ function resampleNearest(src, dstWidth, dstHeight) {
24616
+ const channels = src.channels;
24617
+ const dst = new Uint8Array(dstWidth * dstHeight * channels);
24618
+ const xRatio = src.width / dstWidth;
24619
+ const yRatio = src.height / dstHeight;
24620
+ for (let y = 0; y < dstHeight; y++) {
24621
+ const srcY = Math.min(Math.floor(y * yRatio), src.height - 1);
24622
+ for (let x = 0; x < dstWidth; x++) {
24623
+ const srcX = Math.min(Math.floor(x * xRatio), src.width - 1);
24624
+ const srcIdx = (srcY * src.width + srcX) * channels;
24625
+ const dstIdx = (y * dstWidth + x) * channels;
24626
+ for (let c = 0; c < channels; c++) dst[dstIdx + c] = src.pixels[srcIdx + c];
24627
+ }
24628
+ }
24629
+ return {
24630
+ pixels: dst,
24631
+ width: dstWidth,
24632
+ height: dstHeight,
24633
+ channels,
24634
+ bitsPerChannel: src.bitsPerChannel
24635
+ };
24636
+ }
24637
+ /**
24638
+ * Bilinear interpolation resampling.
24639
+ * @internal
24640
+ */
24641
+ function resampleBilinear(src, dstWidth, dstHeight) {
24642
+ const channels = src.channels;
24643
+ const dst = new Uint8Array(dstWidth * dstHeight * channels);
24644
+ const xRatio = (src.width - 1) / Math.max(1, dstWidth - 1);
24645
+ const yRatio = (src.height - 1) / Math.max(1, dstHeight - 1);
24646
+ for (let y = 0; y < dstHeight; y++) {
24647
+ const srcYf = y * yRatio;
24648
+ const srcY0 = Math.floor(srcYf);
24649
+ const srcY1 = Math.min(srcY0 + 1, src.height - 1);
24650
+ const yFrac = srcYf - srcY0;
24651
+ for (let x = 0; x < dstWidth; x++) {
24652
+ const srcXf = x * xRatio;
24653
+ const srcX0 = Math.floor(srcXf);
24654
+ const srcX1 = Math.min(srcX0 + 1, src.width - 1);
24655
+ const xFrac = srcXf - srcX0;
24656
+ const dstIdx = (y * dstWidth + x) * channels;
24657
+ for (let c = 0; c < channels; c++) {
24658
+ const topLeft = src.pixels[(srcY0 * src.width + srcX0) * channels + c];
24659
+ const topRight = src.pixels[(srcY0 * src.width + srcX1) * channels + c];
24660
+ const bottomLeft = src.pixels[(srcY1 * src.width + srcX0) * channels + c];
24661
+ const bottomRight = src.pixels[(srcY1 * src.width + srcX1) * channels + c];
24662
+ const top = topLeft + (topRight - topLeft) * xFrac;
24663
+ const value = top + (bottomLeft + (bottomRight - bottomLeft) * xFrac - top) * yFrac;
24664
+ dst[dstIdx + c] = Math.round(Math.max(0, Math.min(255, value)));
24665
+ }
24666
+ }
24667
+ }
24668
+ return {
24669
+ pixels: dst,
24670
+ width: dstWidth,
24671
+ height: dstHeight,
24672
+ channels,
24673
+ bitsPerChannel: src.bitsPerChannel
24674
+ };
24675
+ }
24676
+ /**
24677
+ * Lanczos kernel function.
24678
+ *
24679
+ * Computes the Lanczos windowed sinc value for a given distance `x`
24680
+ * and window size `a`. For Lanczos-3, `a = 3`.
24681
+ *
24682
+ * @param x - Distance from the center sample.
24683
+ * @param a - Window radius (3 for Lanczos-3).
24684
+ * @returns The kernel weight.
24685
+ * @internal
24686
+ */
24687
+ function lanczos(x, a = 3) {
24688
+ if (x === 0) return 1;
24689
+ if (Math.abs(x) >= a) return 0;
24690
+ const pix = Math.PI * x;
24691
+ return Math.sin(pix) / pix * (Math.sin(pix / a) / (pix / a));
24692
+ }
24693
+ /**
24694
+ * Lanczos-3 resampling.
24695
+ *
24696
+ * Uses a 6-tap (a=3) windowed sinc filter in both dimensions for
24697
+ * high-quality downscaling. This is the best quality option but
24698
+ * also the slowest.
24699
+ *
24700
+ * @internal
24701
+ */
24702
+ function resampleLanczos(src, dstWidth, dstHeight) {
24703
+ const channels = src.channels;
24704
+ const a = 3;
24705
+ const dst = new Uint8Array(dstWidth * dstHeight * channels);
24706
+ const xRatio = src.width / dstWidth;
24707
+ const yRatio = src.height / dstHeight;
24708
+ for (let y = 0; y < dstHeight; y++) {
24709
+ const srcYf = (y + .5) * yRatio - .5;
24710
+ for (let x = 0; x < dstWidth; x++) {
24711
+ const srcXf = (x + .5) * xRatio - .5;
24712
+ const dstIdx = (y * dstWidth + x) * channels;
24713
+ const sum = new Float64Array(channels);
24714
+ let weightSum = 0;
24715
+ const yStart = Math.floor(srcYf) - a + 1;
24716
+ const yEnd = Math.floor(srcYf) + a;
24717
+ const xStart = Math.floor(srcXf) - a + 1;
24718
+ const xEnd = Math.floor(srcXf) + a;
24719
+ for (let sy = yStart; sy <= yEnd; sy++) {
24720
+ const wy = lanczos(srcYf - sy, a);
24721
+ if (wy === 0) continue;
24722
+ const clampedY = Math.max(0, Math.min(src.height - 1, sy));
24723
+ for (let sx = xStart; sx <= xEnd; sx++) {
24724
+ const wx = lanczos(srcXf - sx, a);
24725
+ if (wx === 0) continue;
24726
+ const w = wx * wy;
24727
+ const clampedX = Math.max(0, Math.min(src.width - 1, sx));
24728
+ const srcIdx = (clampedY * src.width + clampedX) * channels;
24729
+ for (let c = 0; c < channels; c++) sum[c] = (sum[c] ?? 0) + src.pixels[srcIdx + c] * w;
24730
+ weightSum += w;
24731
+ }
24732
+ }
24733
+ if (weightSum > 0) for (let c = 0; c < channels; c++) dst[dstIdx + c] = Math.round(Math.max(0, Math.min(255, sum[c] / weightSum)));
24734
+ }
24735
+ }
24736
+ return {
24737
+ pixels: dst,
24738
+ width: dstWidth,
24739
+ height: dstHeight,
24740
+ channels,
24741
+ bitsPerChannel: src.bitsPerChannel
24742
+ };
24743
+ }
24744
+ /**
24745
+ * Recompress image data using deflate (for PDF FlateDecode).
24746
+ * @internal
24747
+ */
24748
+ async function recompressDeflate(image, level) {
24749
+ if (typeof CompressionStream !== "undefined") {
24750
+ const cs = new CompressionStream("deflate");
24751
+ const writer = cs.writable.getWriter();
24752
+ const reader = cs.readable.getReader();
24753
+ const chunks = [];
24754
+ writer.write(new Uint8Array(image.pixels)).catch(() => {});
24755
+ writer.close().catch(() => {});
24756
+ while (true) {
24757
+ const { done, value } = await reader.read();
24758
+ if (done) break;
24759
+ chunks.push(value);
24760
+ }
24761
+ const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
24762
+ const result = new Uint8Array(totalLength);
24763
+ let pos = 0;
24764
+ for (const chunk of chunks) {
24765
+ result.set(chunk, pos);
24766
+ pos += chunk.length;
24767
+ }
24768
+ return {
24769
+ data: result,
24770
+ width: image.width,
24771
+ height: image.height,
24772
+ channels: image.channels,
24773
+ format: "deflate",
24774
+ wasOptimized: true
24775
+ };
24776
+ }
24777
+ try {
24778
+ const { deflateSync } = await import("fflate");
24779
+ return {
24780
+ data: deflateSync(image.pixels, { level }),
24781
+ width: image.width,
24782
+ height: image.height,
24783
+ channels: image.channels,
24784
+ format: "deflate",
24785
+ wasOptimized: true
24786
+ };
24787
+ } catch {
24788
+ return {
24789
+ data: image.pixels,
24790
+ width: image.width,
24791
+ height: image.height,
24792
+ channels: image.channels,
24793
+ format: "raw",
24794
+ wasOptimized: false
24795
+ };
24796
+ }
24797
+ }
24798
+ /**
24799
+ * Recompress image data as JPEG.
24800
+ *
24801
+ * Uses the JPEG WASM encoder when available (initialized via
24802
+ * `initJpegWasm()` or `initWasm({ jpeg: true })`). When WASM is not
24803
+ * loaded, returns the input data unchanged with `wasOptimized: false`.
24804
+ *
24805
+ * @param image - The raw image pixel data.
24806
+ * @param quality - JPEG quality 1–100.
24807
+ * @param progressive - Encode as progressive JPEG (default: false).
24808
+ * @param chromaSubsampling - Chroma subsampling mode (default: '4:2:0').
24809
+ * @returns The JPEG-encoded result, or raw data if WASM is unavailable.
24810
+ * @internal
24811
+ */
24812
+ async function recompressJpeg(image, quality, progressive = false, chromaSubsampling = "4:2:0") {
24813
+ const { encodeJpegWasm, isJpegWasmReady } = await import("./bridge-C7U4E7St.mjs").then((n) => n.t);
24814
+ if (isJpegWasmReady()) {
24815
+ let pixels = image.pixels;
24816
+ let channels = image.channels;
24817
+ if (image.channels === 4 && image.colorSpace === "cmyk") {
24818
+ pixels = convertCmykToRgb(image.pixels, image.width, image.height);
24819
+ channels = 3;
24820
+ }
24821
+ const jpegBytes = encodeJpegWasm(pixels, image.width, image.height, channels, quality, progressive, chromaSubsampling);
24822
+ if (jpegBytes) return {
24823
+ data: jpegBytes,
24824
+ width: image.width,
24825
+ height: image.height,
24826
+ channels,
24827
+ format: "jpeg",
24828
+ wasOptimized: true
24829
+ };
24830
+ }
24831
+ return {
24832
+ data: image.pixels,
24833
+ width: image.width,
24834
+ height: image.height,
24835
+ channels: image.channels,
24836
+ format: "raw",
24837
+ wasOptimized: false
24838
+ };
24839
+ }
24840
+ /**
24841
+ * Convert CMYK pixel data to RGB.
24842
+ *
24843
+ * Uses the standard CMYK→RGB formula (inverted CMYK, Adobe convention):
24844
+ * ```
24845
+ * R = 255 × (1 − C/255) × (1 − K/255)
24846
+ * G = 255 × (1 − M/255) × (1 − K/255)
24847
+ * B = 255 × (1 − Y/255) × (1 − K/255)
24848
+ * ```
24849
+ *
24850
+ * @param pixels - CMYK pixel data (4 bytes per pixel, row-major).
24851
+ * @param width - Image width.
24852
+ * @param height - Image height.
24853
+ * @returns RGB pixel data (3 bytes per pixel).
24854
+ * @internal
24855
+ */
24856
+ function convertCmykToRgb(pixels, width, height) {
24857
+ const pixelCount = width * height;
24858
+ const rgb = new Uint8Array(pixelCount * 3);
24859
+ for (let i = 0; i < pixelCount; i++) {
24860
+ const c = pixels[i * 4] / 255;
24861
+ const m = pixels[i * 4 + 1] / 255;
24862
+ const y = pixels[i * 4 + 2] / 255;
24863
+ const k = pixels[i * 4 + 3] / 255;
24864
+ rgb[i * 3] = Math.round(255 * (1 - c) * (1 - k));
24865
+ rgb[i * 3 + 1] = Math.round(255 * (1 - m) * (1 - k));
24866
+ rgb[i * 3 + 2] = Math.round(255 * (1 - y) * (1 - k));
24867
+ }
24868
+ return rgb;
24869
+ }
24870
+
24871
+ //#endregion
24872
+ //#region src/assets/image/imageExtract.ts
24873
+ /**
24874
+ * Resolve the color space name from a `/ColorSpace` entry.
24875
+ * Handles both simple names (`/DeviceRGB`) and array forms
24876
+ * (`[/ICCBased ...]`, `[/Indexed /DeviceRGB ...]`).
24877
+ * @internal
24878
+ */
24879
+ function resolveColorSpace(csEntry, registry) {
24880
+ if (!csEntry) return "DeviceRGB";
24881
+ if (csEntry.kind === "ref") {
24882
+ const resolved = registry.resolve(csEntry);
24883
+ if (!resolved) return "DeviceRGB";
24884
+ return resolveColorSpace(resolved, registry);
24885
+ }
24886
+ if (csEntry.kind === "name") return csEntry.value.replace(/^\//, "");
24887
+ if (csEntry.kind === "array") {
24888
+ const arr = csEntry;
24889
+ const first = arr.items[0];
24890
+ if (first && first.kind === "name") {
24891
+ const csName = first.value.replace(/^\//, "");
24892
+ if (csName === "ICCBased") {
24893
+ const profileRef = arr.items[1];
24894
+ if (profileRef && profileRef.kind === "ref") {
24895
+ const profile = registry.resolve(profileRef);
24896
+ if (profile && profile.kind === "stream") {
24897
+ const n = profile.dict.get("/N");
24898
+ if (n && n.kind === "number") {
24899
+ const channels = n.value;
24900
+ if (channels === 1) return "DeviceGray";
24901
+ if (channels === 3) return "DeviceRGB";
24902
+ if (channels === 4) return "DeviceCMYK";
24903
+ }
24904
+ }
24905
+ }
24906
+ return "DeviceRGB";
24907
+ }
24908
+ if (csName === "Indexed") return "Indexed";
24909
+ return csName;
24910
+ }
24911
+ }
24912
+ return "DeviceRGB";
24913
+ }
24914
+ /**
24915
+ * Determine the number of channels from a color space name.
24916
+ * @internal
24917
+ */
24918
+ function channelsFromColorSpace(colorSpace) {
24919
+ switch (colorSpace) {
24920
+ case "DeviceGray":
24921
+ case "CalGray": return 1;
24922
+ case "DeviceCMYK": return 4;
24923
+ case "Indexed": return 1;
24924
+ default: return 3;
24925
+ }
24926
+ }
24927
+ /**
24928
+ * Extract all image XObjects from a PDF document.
24929
+ *
24930
+ * Walks every page's `/Resources /XObject` dictionary and collects
24931
+ * metadata for each image XObject found.
24932
+ *
24933
+ * @param doc - A parsed `PdfDocument`.
24934
+ * @returns An array of `ImageInfo` objects, one per image XObject.
24935
+ *
24936
+ * @example
24937
+ * ```ts
24938
+ * import { loadPdf, extractImages } from 'modern-pdf-lib';
24939
+ *
24940
+ * const doc = await loadPdf(pdfBytes);
24941
+ * const images = extractImages(doc);
24942
+ *
24943
+ * for (const img of images) {
24944
+ * console.log(`${img.name}: ${img.width}x${img.height} ${img.colorSpace} (${img.compressedSize} bytes)`);
24945
+ * }
24946
+ * ```
24947
+ */
24948
+ function extractImages(doc) {
24949
+ const images = [];
24950
+ const seenRefs = /* @__PURE__ */ new Set();
24951
+ const pages = doc.getPages();
24952
+ for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) {
24953
+ const page = pages[pageIndex];
24954
+ const resources = page.getOriginalResources();
24955
+ if (!resources) continue;
24956
+ let xObjDict;
24957
+ const xObjEntry = resources.get("/XObject");
24958
+ if (!xObjEntry) continue;
24959
+ if (xObjEntry.kind === "dict") xObjDict = xObjEntry;
24960
+ else if (xObjEntry.kind === "ref") {
24961
+ const resolved = page.getRegistry().resolve(xObjEntry);
24962
+ if (resolved && resolved.kind === "dict") xObjDict = resolved;
24963
+ }
24964
+ if (!xObjDict) continue;
24965
+ const registry = page.getRegistry();
24966
+ for (const [name, value] of xObjDict) {
24967
+ let ref;
24968
+ let stream;
24969
+ if (value.kind === "ref") {
24970
+ ref = value;
24971
+ if (seenRefs.has(ref.objectNumber)) continue;
24972
+ const resolved = registry.resolve(ref);
24973
+ if (resolved && resolved.kind === "stream") stream = resolved;
24974
+ } else if (value.kind === "stream") stream = value;
24975
+ if (!stream || !ref) continue;
24976
+ const subtype = stream.dict.get("/Subtype");
24977
+ if (!subtype || subtype.kind !== "name") continue;
24978
+ if (subtype.value !== "/Image") continue;
24979
+ const widthObj = stream.dict.get("/Width");
24980
+ const heightObj = stream.dict.get("/Height");
24981
+ const bpcObj = stream.dict.get("/BitsPerComponent");
24982
+ const width = widthObj && widthObj.kind === "number" ? widthObj.value : 0;
24983
+ const height = heightObj && heightObj.kind === "number" ? heightObj.value : 0;
24984
+ const bitsPerComponent = bpcObj && bpcObj.kind === "number" ? bpcObj.value : 8;
24985
+ const colorSpace = resolveColorSpace(stream.dict.get("/ColorSpace"), registry);
24986
+ const channels = channelsFromColorSpace(colorSpace);
24987
+ const { filters } = getStreamFilters(stream.dict);
24988
+ seenRefs.add(ref.objectNumber);
24989
+ images.push({
24990
+ stream,
24991
+ ref,
24992
+ name,
24993
+ pageIndex,
24994
+ width,
24995
+ height,
24996
+ bitsPerComponent,
24997
+ colorSpace,
24998
+ channels,
24999
+ filters,
25000
+ compressedSize: stream.data.length
25001
+ });
25002
+ }
25003
+ }
25004
+ return images;
25005
+ }
25006
+ /**
25007
+ * Decode image stream data into raw pixels.
25008
+ *
25009
+ * For DCTDecode (JPEG) streams, returns the raw JPEG bytes (not decoded
25010
+ * to pixels) since JPEG decoding requires the WASM module.
25011
+ *
25012
+ * For FlateDecode and other filters, fully decodes the stream.
25013
+ *
25014
+ * @param imageInfo - An `ImageInfo` from `extractImages()`.
25015
+ * @returns The decoded stream data.
25016
+ */
25017
+ function decodeImageStream(imageInfo) {
25018
+ if (imageInfo.filters.length === 0) return imageInfo.stream.data;
25019
+ return decodeStream(imageInfo.stream.data, imageInfo.filters, null);
25020
+ }
25021
+
25022
+ //#endregion
25023
+ //#region src/assets/image/grayscaleDetect.ts
25024
+ /**
25025
+ * @module assets/image/grayscaleDetect
25026
+ *
25027
+ * Grayscale detection and conversion for image optimization.
25028
+ *
25029
+ * Detects RGB images where all pixels are effectively grayscale
25030
+ * (R ≈ G ≈ B) and converts them to single-channel grayscale,
25031
+ * reducing data size by ~66%.
25032
+ *
25033
+ * No Buffer — uses Uint8Array exclusively.
25034
+ */
25035
+ /**
25036
+ * Check whether an RGB/RGBA image is effectively grayscale.
25037
+ *
25038
+ * Scans all pixels and checks if R, G, and B channels are within
25039
+ * `tolerance` of each other. If ≥99% of pixels pass, the image
25040
+ * is considered grayscale.
25041
+ *
25042
+ * @param pixels - Raw pixel data (row-major, channel-interleaved).
25043
+ * @param width - Image width in pixels.
25044
+ * @param height - Image height in pixels.
25045
+ * @param channels - Number of channels: 3 (RGB) or 4 (RGBA).
25046
+ * @param tolerance - Maximum allowed difference between R, G, and B
25047
+ * values for a pixel to be considered gray.
25048
+ * Default: `2`.
25049
+ * @returns `true` if the image is effectively grayscale.
25050
+ *
25051
+ * @example
25052
+ * ```ts
25053
+ * import { isGrayscaleImage, convertToGrayscale } from 'modern-pdf-lib';
25054
+ *
25055
+ * if (isGrayscaleImage(pixels, width, height, 3)) {
25056
+ * const grayPixels = convertToGrayscale(pixels, width, height, 3);
25057
+ * // grayPixels has 1 byte per pixel instead of 3
25058
+ * }
25059
+ * ```
25060
+ */
25061
+ function isGrayscaleImage(pixels, width, height, channels, tolerance = 2) {
25062
+ const pixelCount = width * height;
25063
+ const maxNonGray = Math.floor(pixelCount * .01);
25064
+ let nonGrayCount = 0;
25065
+ for (let i = 0; i < pixelCount; i++) {
25066
+ const r = pixels[i * channels];
25067
+ const g = pixels[i * channels + 1];
25068
+ const b = pixels[i * channels + 2];
25069
+ if (Math.max(r, g, b) - Math.min(r, g, b) > tolerance) {
25070
+ nonGrayCount++;
25071
+ if (nonGrayCount > maxNonGray) return false;
25072
+ }
25073
+ }
25074
+ return true;
25075
+ }
25076
+ /**
25077
+ * Convert an RGB/RGBA image to single-channel grayscale.
25078
+ *
25079
+ * Uses the ITU-R BT.601 luma formula:
25080
+ * ```
25081
+ * gray = 0.299 × R + 0.587 × G + 0.114 × B
25082
+ * ```
25083
+ *
25084
+ * The alpha channel (if present) is discarded.
25085
+ *
25086
+ * @param pixels - Raw pixel data (row-major, channel-interleaved).
25087
+ * @param width - Image width in pixels.
25088
+ * @param height - Image height in pixels.
25089
+ * @param channels - Number of channels: 3 (RGB) or 4 (RGBA).
25090
+ * @returns Grayscale pixel data (1 byte per pixel).
25091
+ */
25092
+ function convertToGrayscale(pixels, width, height, channels) {
25093
+ const pixelCount = width * height;
25094
+ const gray = new Uint8Array(pixelCount);
25095
+ for (let i = 0; i < pixelCount; i++) {
25096
+ const r = pixels[i * channels];
25097
+ const g = pixels[i * channels + 1];
25098
+ const b = pixels[i * channels + 2];
25099
+ gray[i] = Math.round(.299 * r + .587 * g + .114 * b);
25100
+ }
25101
+ return gray;
25102
+ }
25103
+
25104
+ //#endregion
25105
+ //#region src/assets/image/batchOptimize.ts
25106
+ /** Minimum image size to bother optimizing (10 KB). */
25107
+ const SMALL_IMAGE_THRESHOLD = 10240;
25108
+ /**
25109
+ * Optimize all images in a PDF document by recompressing them as JPEG.
25110
+ *
25111
+ * Walks every image XObject in the document, decodes its pixel data,
25112
+ * recompresses it as JPEG using the WASM encoder (if available), and
25113
+ * replaces the stream data in-place when the result is smaller.
25114
+ *
25115
+ * **Requires the JPEG WASM module to be initialized** via
25116
+ * `initJpegWasm()` or `initWasm({ jpeg: true })`. Without it,
25117
+ * no images will be optimized (all will be skipped).
25118
+ *
25119
+ * @param doc - A parsed `PdfDocument` (from `loadPdf()`).
25120
+ * @param options - Optimization settings.
25121
+ * @returns A report summarizing the optimization results.
25122
+ *
25123
+ * @example
25124
+ * ```ts
25125
+ * import { loadPdf, initWasm, optimizeAllImages } from 'modern-pdf-lib';
25126
+ *
25127
+ * await initWasm({ jpeg: true });
25128
+ *
25129
+ * const doc = await loadPdf(pdfBytes);
25130
+ * const report = await optimizeAllImages(doc);
25131
+ *
25132
+ * console.log(`Optimized ${report.optimizedImages} of ${report.totalImages} images`);
25133
+ * console.log(`Savings: ${report.savings.toFixed(1)}%`);
25134
+ *
25135
+ * const optimizedBytes = await doc.save();
25136
+ * ```
25137
+ */
25138
+ async function optimizeAllImages(doc, options = {}) {
25139
+ const quality = options.quality ?? 80;
25140
+ const minSavingsPercent = options.minSavingsPercent ?? 10;
25141
+ const skipSmall = options.skipSmallImages ?? false;
25142
+ const progressive = options.progressive ?? false;
25143
+ const chromaSubsampling = options.chromaSubsampling ?? "4:2:0";
25144
+ const { encodeJpegWasm, isJpegWasmReady } = await import("./bridge-C7U4E7St.mjs").then((n) => n.t);
25145
+ const { decodeJpegWasm } = await import("./bridge-C7U4E7St.mjs").then((n) => n.t);
25146
+ const images = extractImages(doc);
25147
+ const perImage = [];
25148
+ let totalOriginal = 0;
25149
+ let totalNew = 0;
25150
+ let optimizedCount = 0;
25151
+ for (const img of images) {
25152
+ totalOriginal += img.compressedSize;
25153
+ if (!isJpegWasmReady()) {
25154
+ perImage.push({
25155
+ name: img.name,
25156
+ pageIndex: img.pageIndex,
25157
+ originalSize: img.compressedSize,
25158
+ newSize: img.compressedSize,
25159
+ skipped: true,
25160
+ reason: "JPEG WASM encoder not initialized"
25161
+ });
25162
+ totalNew += img.compressedSize;
25163
+ continue;
25164
+ }
25165
+ if (skipSmall && img.compressedSize < SMALL_IMAGE_THRESHOLD) {
25166
+ perImage.push({
25167
+ name: img.name,
25168
+ pageIndex: img.pageIndex,
25169
+ originalSize: img.compressedSize,
25170
+ newSize: img.compressedSize,
25171
+ skipped: true,
25172
+ reason: `Below size threshold (${SMALL_IMAGE_THRESHOLD} bytes)`
25173
+ });
25174
+ totalNew += img.compressedSize;
25175
+ continue;
25176
+ }
25177
+ if (img.bitsPerComponent !== 8) {
25178
+ perImage.push({
25179
+ name: img.name,
25180
+ pageIndex: img.pageIndex,
25181
+ originalSize: img.compressedSize,
25182
+ newSize: img.compressedSize,
25183
+ skipped: true,
25184
+ reason: `Unsupported bits per component: ${img.bitsPerComponent}`
25185
+ });
25186
+ totalNew += img.compressedSize;
25187
+ continue;
25188
+ }
25189
+ if (img.colorSpace === "Indexed") {
25190
+ perImage.push({
25191
+ name: img.name,
25192
+ pageIndex: img.pageIndex,
25193
+ originalSize: img.compressedSize,
25194
+ newSize: img.compressedSize,
25195
+ skipped: true,
25196
+ reason: "Indexed color space not suitable for JPEG"
25197
+ });
25198
+ totalNew += img.compressedSize;
25199
+ continue;
25200
+ }
25201
+ let pixels;
25202
+ let channels = img.channels;
25203
+ try {
25204
+ if (img.filters[0] === "DCTDecode") {
25205
+ const decoded = decodeJpegWasm(img.stream.data);
25206
+ if (!decoded) {
25207
+ perImage.push({
25208
+ name: img.name,
25209
+ pageIndex: img.pageIndex,
25210
+ originalSize: img.compressedSize,
25211
+ newSize: img.compressedSize,
25212
+ skipped: true,
25213
+ reason: "Failed to decode existing JPEG"
25214
+ });
25215
+ totalNew += img.compressedSize;
25216
+ continue;
25217
+ }
25218
+ pixels = decoded.pixels;
25219
+ channels = decoded.channels;
25220
+ } else pixels = decodeImageStream(img);
25221
+ } catch {
25222
+ perImage.push({
25223
+ name: img.name,
25224
+ pageIndex: img.pageIndex,
25225
+ originalSize: img.compressedSize,
25226
+ newSize: img.compressedSize,
25227
+ skipped: true,
25228
+ reason: "Failed to decode image stream"
25229
+ });
25230
+ totalNew += img.compressedSize;
25231
+ continue;
25232
+ }
25233
+ const expectedLen = img.width * img.height * channels;
25234
+ if (pixels.length !== expectedLen) {
25235
+ perImage.push({
25236
+ name: img.name,
25237
+ pageIndex: img.pageIndex,
25238
+ originalSize: img.compressedSize,
25239
+ newSize: img.compressedSize,
25240
+ skipped: true,
25241
+ reason: `Pixel data length mismatch: got ${pixels.length}, expected ${expectedLen}`
25242
+ });
25243
+ totalNew += img.compressedSize;
25244
+ continue;
25245
+ }
25246
+ if (channels === 4 && img.colorSpace === "DeviceCMYK") {
25247
+ const rgb = new Uint8Array(img.width * img.height * 3);
25248
+ for (let i = 0; i < img.width * img.height; i++) {
25249
+ const c = pixels[i * 4] / 255;
25250
+ const m = pixels[i * 4 + 1] / 255;
25251
+ const y = pixels[i * 4 + 2] / 255;
25252
+ const k = pixels[i * 4 + 3] / 255;
25253
+ rgb[i * 3] = Math.round(255 * (1 - c) * (1 - k));
25254
+ rgb[i * 3 + 1] = Math.round(255 * (1 - m) * (1 - k));
25255
+ rgb[i * 3 + 2] = Math.round(255 * (1 - y) * (1 - k));
25256
+ }
25257
+ pixels = rgb;
25258
+ channels = 3;
25259
+ }
25260
+ if (options.autoGrayscale && (channels === 3 || channels === 4)) {
25261
+ if (isGrayscaleImage(pixels, img.width, img.height, channels)) {
25262
+ pixels = convertToGrayscale(pixels, img.width, img.height, channels);
25263
+ channels = 1;
25264
+ }
25265
+ }
25266
+ const jpegBytes = encodeJpegWasm(pixels, img.width, img.height, channels, quality, progressive, chromaSubsampling);
25267
+ if (!jpegBytes) {
25268
+ perImage.push({
25269
+ name: img.name,
25270
+ pageIndex: img.pageIndex,
25271
+ originalSize: img.compressedSize,
25272
+ newSize: img.compressedSize,
25273
+ skipped: true,
25274
+ reason: "JPEG encoding failed"
25275
+ });
25276
+ totalNew += img.compressedSize;
25277
+ continue;
25278
+ }
25279
+ const savingsPercent = (img.compressedSize - jpegBytes.length) / img.compressedSize * 100;
25280
+ if (savingsPercent < minSavingsPercent) {
25281
+ perImage.push({
25282
+ name: img.name,
25283
+ pageIndex: img.pageIndex,
25284
+ originalSize: img.compressedSize,
25285
+ newSize: img.compressedSize,
25286
+ skipped: true,
25287
+ reason: `Savings ${savingsPercent.toFixed(1)}% below threshold ${minSavingsPercent}%`
25288
+ });
25289
+ totalNew += img.compressedSize;
25290
+ continue;
25291
+ }
25292
+ img.stream.data = jpegBytes;
25293
+ img.stream.syncLength();
25294
+ const dict = img.stream.dict;
25295
+ dict.set("/Filter", PdfName.of("/DCTDecode"));
25296
+ if (img.colorSpace === "DeviceCMYK" && channels === 3) dict.set("/ColorSpace", PdfName.of("/DeviceRGB"));
25297
+ if (channels === 1) dict.set("/ColorSpace", PdfName.of("/DeviceGray"));
25298
+ dict.delete("/DecodeParms");
25299
+ if (img.colorSpace === "DeviceCMYK") dict.delete("/Decode");
25300
+ optimizedCount++;
25301
+ perImage.push({
25302
+ name: img.name,
25303
+ pageIndex: img.pageIndex,
25304
+ originalSize: img.compressedSize,
25305
+ newSize: jpegBytes.length,
25306
+ skipped: false
25307
+ });
25308
+ totalNew += jpegBytes.length;
25309
+ }
25310
+ const overallSavings = totalOriginal > 0 ? (totalOriginal - totalNew) / totalOriginal * 100 : 0;
25311
+ return {
25312
+ totalImages: images.length,
25313
+ optimizedImages: optimizedCount,
25314
+ originalTotalBytes: totalOriginal,
25315
+ optimizedTotalBytes: totalNew,
25316
+ savings: overallSavings,
25317
+ perImage
25318
+ };
25319
+ }
25320
+
25321
+ //#endregion
25322
+ //#region src/assets/image/deduplicateImages.ts
25323
+ /**
25324
+ * Compute a fast FNV-1a hash of a byte array.
25325
+ *
25326
+ * This is used instead of SHA-256 because:
25327
+ * 1. It's synchronous (no need for crypto.subtle)
25328
+ * 2. It's fast for large buffers
25329
+ * 3. We only need collision resistance within a single document
25330
+ *
25331
+ * Returns a 64-char hex string (two 32-bit hashes concatenated).
25332
+ * @internal
25333
+ */
25334
+ function hashBytes(data) {
25335
+ let h1 = 2166136261;
25336
+ for (let i = 0; i < data.length; i++) {
25337
+ h1 ^= data[i];
25338
+ h1 = Math.imul(h1, 16777619);
25339
+ }
25340
+ let h2 = 16777619;
25341
+ for (let i = data.length - 1; i >= 0; i--) {
25342
+ h2 ^= data[i];
25343
+ h2 = Math.imul(h2, 2166136261);
25344
+ }
25345
+ const h3 = data.length * 2654435769 | 0;
25346
+ return (h1 >>> 0).toString(16).padStart(8, "0") + (h2 >>> 0).toString(16).padStart(8, "0") + (h3 >>> 0).toString(16).padStart(8, "0");
25347
+ }
25348
+ /**
25349
+ * Deduplicate identical images in a PDF document.
25350
+ *
25351
+ * Scans all image XObjects, hashes their compressed stream data (plus
25352
+ * dimensions and filter), and replaces duplicate references in page
25353
+ * resource dictionaries with the canonical (first-seen) copy.
25354
+ *
25355
+ * This operation modifies the document in-place. Duplicate streams
25356
+ * are not removed from the object registry (they become unreferenced
25357
+ * and will be omitted on save if the writer supports garbage collection).
25358
+ *
25359
+ * @param doc - A parsed `PdfDocument` (from `loadPdf()`).
25360
+ * @returns A report summarizing deduplication results.
25361
+ *
25362
+ * @example
25363
+ * ```ts
25364
+ * import { loadPdf, deduplicateImages } from 'modern-pdf-lib';
25365
+ *
25366
+ * const doc = await loadPdf(pdfBytes);
25367
+ * const report = await deduplicateImages(doc);
25368
+ *
25369
+ * console.log(`Removed ${report.duplicatesRemoved} duplicate images`);
25370
+ * console.log(`Saved ~${(report.bytesSaved / 1024).toFixed(0)} KB`);
25371
+ *
25372
+ * const optimizedBytes = await doc.save();
25373
+ * ```
25374
+ */
25375
+ function deduplicateImages(doc) {
25376
+ const images = extractImages(doc);
25377
+ const hashToCanonical = /* @__PURE__ */ new Map();
25378
+ const duplicates = [];
25379
+ for (const img of images) {
25380
+ const key = `${img.width}x${img.height}:${img.filters.join(",")}:` + hashBytes(img.stream.data);
25381
+ const existing = hashToCanonical.get(key);
25382
+ if (existing) duplicates.push({
25383
+ image: img,
25384
+ canonicalRef: existing.ref
25385
+ });
25386
+ else hashToCanonical.set(key, {
25387
+ ref: img.ref,
25388
+ size: img.compressedSize
25389
+ });
25390
+ }
25391
+ let bytesSaved = 0;
25392
+ for (const { image, canonicalRef } of duplicates) {
25393
+ const page = doc.getPages()[image.pageIndex];
25394
+ if (!page) continue;
25395
+ const resources = page.getOriginalResources();
25396
+ if (!resources) continue;
25397
+ const xObjEntry = resources.get("/XObject");
25398
+ if (!xObjEntry) continue;
25399
+ let xObjDict;
25400
+ if (xObjEntry.kind === "dict") xObjDict = xObjEntry;
25401
+ else if (xObjEntry.kind === "ref") {
25402
+ const resolved = page.getRegistry().resolve(xObjEntry);
25403
+ if (resolved && resolved.kind === "dict") xObjDict = resolved;
25404
+ }
25405
+ if (!xObjDict) continue;
25406
+ xObjDict.set(image.name, canonicalRef);
25407
+ bytesSaved += image.compressedSize;
25408
+ }
25409
+ return {
25410
+ totalImages: images.length,
25411
+ uniqueImages: hashToCanonical.size,
25412
+ duplicatesRemoved: duplicates.length,
25413
+ bytesSaved
25414
+ };
25415
+ }
25416
+
25417
+ //#endregion
25418
+ //#region src/assets/image/dpiAnalyze.ts
25419
+ /**
25420
+ * Compute the effective DPI of an image given its pixel dimensions
25421
+ * and display dimensions in points.
25422
+ *
25423
+ * PDF uses 72 points per inch, so:
25424
+ * ```
25425
+ * DPI = imagePixels / (displayPoints / 72)
25426
+ * ```
25427
+ *
25428
+ * @param imageWidth - Image width in pixels.
25429
+ * @param imageHeight - Image height in pixels.
25430
+ * @param displayWidth - Display width in PDF points (1/72 inch).
25431
+ * @param displayHeight - Display height in PDF points (1/72 inch).
25432
+ * @returns DPI information.
25433
+ *
25434
+ * @example
25435
+ * ```ts
25436
+ * import { computeImageDpi } from 'modern-pdf-lib';
25437
+ *
25438
+ * // A 3000×2000 image displayed at 4.17×2.78 inches (300×200 points)
25439
+ * const dpi = computeImageDpi(3000, 2000, 300, 200);
25440
+ * console.log(dpi.effectiveDpi); // 720
25441
+ * ```
25442
+ */
25443
+ function computeImageDpi(imageWidth, imageHeight, displayWidth, displayHeight) {
25444
+ const xDpi = displayWidth > 0 ? imageWidth / displayWidth * 72 : Infinity;
25445
+ const yDpi = displayHeight > 0 ? imageHeight / displayHeight * 72 : Infinity;
25446
+ return {
25447
+ xDpi,
25448
+ yDpi,
25449
+ effectiveDpi: Math.min(xDpi, yDpi)
25450
+ };
25451
+ }
25452
+ /**
25453
+ * Compute the target pixel dimensions for downscaling an image
25454
+ * to a maximum DPI at a given display size.
25455
+ *
25456
+ * @param imageWidth - Current image width in pixels.
25457
+ * @param imageHeight - Current image height in pixels.
25458
+ * @param displayWidth - Display width in PDF points.
25459
+ * @param displayHeight - Display height in PDF points.
25460
+ * @param maxDpi - Maximum allowed DPI.
25461
+ * @returns Target dimensions, or the original dimensions if no
25462
+ * downscaling is needed.
25463
+ */
25464
+ function computeTargetDimensions(imageWidth, imageHeight, displayWidth, displayHeight, maxDpi) {
25465
+ const dpi = computeImageDpi(imageWidth, imageHeight, displayWidth, displayHeight);
25466
+ if (dpi.effectiveDpi <= maxDpi || !isFinite(dpi.effectiveDpi)) return {
25467
+ width: imageWidth,
25468
+ height: imageHeight,
25469
+ downscaled: false
25470
+ };
25471
+ const scale = maxDpi / dpi.effectiveDpi;
25472
+ return {
25473
+ width: Math.max(1, Math.round(imageWidth * scale)),
25474
+ height: Math.max(1, Math.round(imageHeight * scale)),
25475
+ downscaled: true
25476
+ };
25477
+ }
25478
+
25479
+ //#endregion
25480
+ //#region src/errors.ts
25481
+ /**
25482
+ * @module errors
25483
+ *
25484
+ * Typed error classes for common failure modes in the modern-pdf library.
25485
+ * Each error class extends the native `Error` and carries a descriptive
25486
+ * `name` so callers can use `instanceof` checks or `error.name` comparisons.
25487
+ *
25488
+ * All constructors accept an optional `ErrorOptions` parameter to support
25489
+ * error chaining via the standard `{ cause }` option (ES2022+).
25490
+ *
25491
+ * These match the pdf-lib error hierarchy for API compatibility.
25492
+ */
25493
+ /**
25494
+ * Thrown when attempting to load or manipulate an encrypted PDF without
25495
+ * providing the correct password.
25496
+ */
25497
+ var EncryptedPdfError = class extends Error {
25498
+ name = "EncryptedPdfError";
25499
+ constructor(message = "The PDF is encrypted. Please provide a password.", options) {
25500
+ super(message, options);
25501
+ }
25502
+ };
25503
+ /**
25504
+ * Thrown when a font operation requires an embedded font but none has been
25505
+ * registered or the font reference is invalid.
25506
+ */
25507
+ var FontNotEmbeddedError = class extends Error {
25508
+ name = "FontNotEmbeddedError";
25509
+ constructor(fontName, options) {
25510
+ super(fontName ? `The font "${fontName}" has not been embedded in this document.` : "No font has been embedded. Call doc.embedFont() first.", options);
25511
+ }
25512
+ };
25513
+ /**
25514
+ * Thrown when attempting to use a page from a different document without
25515
+ * first copying it.
25516
+ */
25517
+ var ForeignPageError = class extends Error {
25518
+ name = "ForeignPageError";
25519
+ constructor(options) {
25520
+ super("The page belongs to a different document. Use doc.copyPages() to import pages from another document before adding them.", options);
25521
+ }
25522
+ };
25523
+ /**
25524
+ * Thrown when attempting to remove a page from a document that has no pages.
25525
+ */
25526
+ var RemovePageFromEmptyDocumentError = class extends Error {
25527
+ name = "RemovePageFromEmptyDocumentError";
25528
+ constructor(options) {
25529
+ super("Cannot remove a page from a document with no pages.", options);
25530
+ }
25531
+ };
25532
+ /**
25533
+ * Thrown when looking up a form field by name that does not exist.
25534
+ */
25535
+ var NoSuchFieldError = class extends Error {
25536
+ name = "NoSuchFieldError";
25537
+ constructor(fieldName, options) {
25538
+ super(`No form field named "${fieldName}" exists in this document.`, options);
25539
+ }
23916
25540
  };
23917
25541
  /**
23918
25542
  * Thrown when a form field is accessed via the wrong typed getter
@@ -24020,19 +25644,22 @@ async function initWasm(options) {
24020
25644
  if (options === void 0 || typeof options === "string" || options instanceof URL) return;
24021
25645
  if (wasmInitialized) return;
24022
25646
  const inits = [];
24023
- if (options.deflate || options.deflateWasm) inits.push(import("./libdeflateWasm-DlHgU5oy.mjs").then((n) => n.r).then(async ({ initDeflateWasm }) => {
25647
+ if (options.deflate || options.deflateWasm) inits.push(import("./libdeflateWasm-82loOtIV.mjs").then((n) => n.r).then(async ({ initDeflateWasm }) => {
24024
25648
  await initDeflateWasm(options.deflateWasm);
24025
25649
  }));
24026
- if (options.png || options.pngWasm) inits.push(import("./pngEmbed-DTOqgEUC.mjs").then((n) => n.n).then(async ({ initPngWasm }) => {
25650
+ if (options.png || options.pngWasm) inits.push(import("./pngEmbed-gaJ9S2Dk.mjs").then((n) => n.n).then(async ({ initPngWasm }) => {
24027
25651
  await initPngWasm(options.pngWasm);
24028
25652
  }));
24029
25653
  if (options.fonts || options.fontWasm) inits.push(import("./fontSubset-ZpLoOZ2e.mjs").then((n) => n.r).then(async ({ initSubsetWasm }) => {
24030
25654
  await initSubsetWasm(options.fontWasm);
24031
25655
  }));
25656
+ if (options.jpeg || options.jpegWasm) inits.push(import("./bridge-C7U4E7St.mjs").then((n) => n.t).then(async ({ initJpegWasm }) => {
25657
+ await initJpegWasm(options.jpegWasm);
25658
+ }));
24032
25659
  await Promise.all(inits);
24033
25660
  wasmInitialized = true;
24034
25661
  }
24035
25662
 
24036
25663
  //#endregion
24037
- export { AnnotationFlags, BlendMode, ChangeTracker, CombedTextLayoutError, EmbeddedFont, EncryptedPdfError, ExceededMaxLengthError, FieldAlreadyExistsError, FieldExistsAsNonTerminalError, FieldFlags, FontNotEmbeddedError, ForeignPageError, ImageAlignment, InvalidFieldNamePartError, LineCapStyle, LineJoinStyle, MissingOnValueCheckError, NoSuchFieldError, PDFOperator, PageSizes, ParseSpeeds, PdfAnnotation, PdfArray, PdfBool, PdfButtonField, PdfCheckboxField, PdfCircleAnnotation, PdfDict, PdfDocument, PdfDropdownField, PdfEncryptionHandler, PdfField, PdfForm, PdfFreeTextAnnotation, PdfHighlightAnnotation, PdfInkAnnotation, PdfLayer, PdfLayerManager, PdfLineAnnotation, PdfLinkAnnotation, PdfListboxField, PdfName, PdfNull, PdfNumber, PdfObjectRegistry, PdfOutlineItem, PdfOutlineTree, PdfPage, PdfParseError, PdfPolyLineAnnotation, PdfPolygonAnnotation, PdfRadioGroup, PdfRedactAnnotation, PdfRef, PdfSignatureField, PdfSquareAnnotation, PdfSquigglyAnnotation, PdfStampAnnotation, PdfStream, PdfStreamWriter, PdfStrikeOutAnnotation, PdfString, PdfStructureElement, PdfStructureTree, PdfTextAnnotation, PdfTextField, PdfUnderlineAnnotation, PdfViewerPreferences, PdfWriter, RemovePageFromEmptyDocumentError, RichTextFieldReadError, StandardFonts, TextAlignment, TextRenderingMode, UnexpectedFieldTypeError, addWatermark, addWatermarkToPage, aesDecryptCBC, aesEncryptCBC, annotationFromDict, applyFillColor, applyRedactions, applyStrokeColor, asNumber, asPDFName, asPDFNumber, attachFile, base64Decode, base64Encode, beginArtifact, beginArtifactWithType, beginLayerContent, beginMarkedContent, beginMarkedContentSequence, beginMarkedContentWithProperties, beginText, buildAnnotationDict, buildCatalog, buildDocumentStructure, buildEmbeddedFilesNameTree, buildGradientObjects, buildInfoDict, buildPageTree, buildPatternObjects, buildPkcs7Signature, buildTimestampRequest, buildViewerPreferencesDict, buildXmpMetadata, checkAccessibility, circlePath, clipEvenOdd, clip as clipOp, closeAndStroke, closeFillAndStroke, closeFillEvenOddAndStroke, closePath as closePathOp, cmyk, colorToComponents, componentsToColor, computeFileEncryptionKey, computeFontSize, computeSignatureHash, concatMatrix, concatMatrix as concatTransformationMatrix, copyPages, createAnnotation, createMarkedContentScope, createPdf, createXmpStream, cropPage, curveToFinal, curveToInitial, curveTo as curveToOp, decodePermissions, decodeStream, degrees, degreesToRadians, drawImageWithMatrix, drawImageXObject, drawXObject as drawObject, drawXObject, drawSvgOnPage, ellipsePath, embedPageAsFormXObject, embedSignature, encodeContextTag, encodeInteger, encodeLength, encodeOID, encodeOctetString, encodePermissions, encodePrintableString, encodeSequence, encodeSet, encodeUTCTime, encodeUtf8String, endArtifact, endLayerContent, endMarkedContent, endPath as endPathOp, endText, enforcePdfA, extractMetrics, extractText, extractTextWithPositions, fillAndStroke as fillAndStrokeOp, fillEvenOdd, fillEvenOddAndStroke, fill as fillOp, findSignatures, formatHexContext, formatPdfDate, generateButtonAppearance, generateCheckboxAppearance, generateCircleAppearance, generateDropdownAppearance, generateFreeTextAppearance, generateHighlightAppearance, generateInkAppearance, generateLineAppearance, generateListboxAppearance, generateRadioAppearance, generateSignatureAppearance, generateSquareAppearance, generateSquigglyAppearance, generateStrikeOutAppearance, generateTextAppearance, generateUnderlineAppearance, getAttachments, getPageSize, getRedactionMarks, getSignatures, grayscale, initWasm, insertPage, isAccessible, isLinearized, isOpenTypeCFF, isTrueType, layoutCombedText, layoutMultilineText, layoutSinglelineText, lineTo as lineToOp, linearGradient, linearizePdf, loadPdf, markForRedaction, md5, mergePdfs, movePage, moveText as moveTextOp, moveTextSetLeading, moveTo as moveToOp, nextLine as nextLineOp, parseContentStream, parseSvg, parseSvgColor, parseSvgPath, parseSvgTransform, parseTimestampResponse, parseViewerPreferences, parseXmpMetadata, restoreState as popGraphicsState, restoreState, prepareForSigning, saveState as pushGraphicsState, saveState, radialGradient, radians, radiansToDegrees, rc4, rectangle as rectangleOp, removePage, removePages, requestTimestamp, resizePage, reversePages, rgb, rotateAllPages, rotate as rotateOp, rotatePage, rotationMatrix, saveDocumentIncremental, saveIncremental, scale as scaleOp, serializePdf, setCharacterSpacing as setCharacterSpacingOp, setCharacterSpacing as setCharacterSqueeze, setColorSpace, setDashPattern as setDashPatternOp, setFillColor, setFillColorCmyk, setFillColorGray, setFillColorRgb, setFillingColor, setFlatness, setFont as setFontAndSize, setFont as setFontOp, setFontSize as setFontSizeOp, setGraphicsState as setGraphicsStateOp, setLeading as setLeadingOp, setLeading as setLineHeight, setLineCap as setLineCapOp, setLineJoin as setLineJoinOp, setLineWidth as setLineWidthOp, setMiterLimit, setStrokeColor, setStrokeColorCmyk, setStrokeColorGray, setStrokeColorRgb, setStrokeColorSpace, setStrokingColor, setTextMatrix as setTextMatrixOp, setTextRenderingMode as setTextRenderingModeOp, setTextRise as setTextRiseOp, setWordSpacing as setWordSpacingOp, sha256, sha384, sha512, showTextArray, showTextHex, showTextNextLine, showText as showTextOp, showTextWithSpacing, signPdf, skew as skewOp, splitPdf, stroke as strokeOp, summarizeIssues, svgToPdfOperators, tilingPattern, translate as translateOp, validatePdfA, verifyOwnerPassword, verifySignature, verifySignatures, verifyUserPassword, wrapInMarkedContent };
25664
+ export { AnnotationFlags, BlendMode, ChangeTracker, CombedTextLayoutError, EmbeddedFont, EncryptedPdfError, ExceededMaxLengthError, FieldAlreadyExistsError, FieldExistsAsNonTerminalError, FieldFlags, FontNotEmbeddedError, ForeignPageError, ImageAlignment, InvalidFieldNamePartError, LineCapStyle, LineJoinStyle, MissingOnValueCheckError, NoSuchFieldError, PDFOperator, PageSizes, ParseSpeeds, PdfAnnotation, PdfArray, PdfBool, PdfButtonField, PdfCaretAnnotation, PdfCheckboxField, PdfCircleAnnotation, PdfDict, PdfDocument, PdfDropdownField, PdfEncryptionHandler, PdfField, PdfFileAttachmentAnnotation, PdfForm, PdfFreeTextAnnotation, PdfHighlightAnnotation, PdfInkAnnotation, PdfLayer, PdfLayerManager, PdfLineAnnotation, PdfLinkAnnotation, PdfListboxField, PdfName, PdfNull, PdfNumber, PdfObjectRegistry, PdfOutlineItem, PdfOutlineTree, PdfPage, PdfParseError, PdfPolyLineAnnotation, PdfPolygonAnnotation, PdfPopupAnnotation, PdfRadioGroup, PdfRedactAnnotation, PdfRef, PdfSignatureField, PdfSquareAnnotation, PdfSquigglyAnnotation, PdfStampAnnotation, PdfStream, PdfStreamWriter, PdfStrikeOutAnnotation, PdfString, PdfStructureElement, PdfStructureTree, PdfTextAnnotation, PdfTextField, PdfUnderlineAnnotation, PdfViewerPreferences, PdfWriter, RemovePageFromEmptyDocumentError, RichTextFieldReadError, StandardFonts, TextAlignment, TextRenderingMode, UnexpectedFieldTypeError, addWatermark, addWatermarkToPage, aesDecryptCBC, aesEncryptCBC, annotationFromDict, applyFillColor, applyRedactions, applyStrokeColor, asNumber, asPDFName, asPDFNumber, attachFile, base64Decode, base64Encode, beginArtifact, beginArtifactWithType, beginLayerContent, beginMarkedContent, beginMarkedContentSequence, beginMarkedContentWithProperties, beginText, buildAnnotationDict, buildCatalog, buildDocumentStructure, buildEmbeddedFilesNameTree, buildGradientObjects, buildInfoDict, buildPageTree, buildPatternObjects, buildPkcs7Signature, buildTimestampRequest, buildViewerPreferencesDict, buildXmpMetadata, checkAccessibility, circlePath, clipEvenOdd, clip as clipOp, closeAndStroke, closeFillAndStroke, closeFillEvenOddAndStroke, closePath as closePathOp, cmyk, colorToComponents, componentsToColor, computeFileEncryptionKey, computeFontSize, computeImageDpi, computeSignatureHash, computeTargetDimensions, concatMatrix, concatMatrix as concatTransformationMatrix, convertToGrayscale, copyPages, createAnnotation, createMarkedContentScope, createPdf, createXmpStream, cropPage, curveToFinal, curveToInitial, curveTo as curveToOp, decodeImageStream, decodeJpegWasm, decodePermissions, decodeStream, deduplicateImages, degrees, degreesToRadians, downscaleImage, drawImageWithMatrix, drawImageXObject, drawXObject as drawObject, drawXObject, drawSvgOnPage, ellipsePath, embedPageAsFormXObject, embedSignature, encodeContextTag, encodeInteger, encodeJpegWasm, encodeLength, encodeOID, encodeOctetString, encodePermissions, encodePrintableString, encodeSequence, encodeSet, encodeUTCTime, encodeUtf8String, endArtifact, endLayerContent, endMarkedContent, endPath as endPathOp, endText, enforcePdfA, estimateJpegQuality, extractImages, extractMetrics, extractText, extractTextWithPositions, fillAndStroke as fillAndStrokeOp, fillEvenOdd, fillEvenOddAndStroke, fill as fillOp, findSignatures, formatHexContext, formatPdfDate, generateButtonAppearance, generateCheckboxAppearance, generateCircleAppearance, generateDropdownAppearance, generateFreeTextAppearance, generateHighlightAppearance, generateInkAppearance, generateLineAppearance, generateListboxAppearance, generateRadioAppearance, generateSignatureAppearance, generateSquareAppearance, generateSquigglyAppearance, generateStrikeOutAppearance, generateTextAppearance, generateUnderlineAppearance, getAttachments, getPageSize, getRedactionMarks, getSignatures, grayscale, initJpegWasm, initWasm, insertPage, isAccessible, isGrayscaleImage, isJpegWasmReady, isLinearized, isOpenTypeCFF, isTrueType, layoutCombedText, layoutMultilineText, layoutSinglelineText, lineTo as lineToOp, linearGradient, linearizePdf, loadPdf, markForRedaction, md5, mergePdfs, movePage, moveText as moveTextOp, moveTextSetLeading, moveTo as moveToOp, nextLine as nextLineOp, optimizeAllImages, optimizeImage, parseContentStream, parseSvg, parseSvgColor, parseSvgPath, parseSvgTransform, parseTimestampResponse, parseViewerPreferences, parseXmpMetadata, restoreState as popGraphicsState, restoreState, prepareForSigning, saveState as pushGraphicsState, saveState, radialGradient, radians, radiansToDegrees, rc4, recompressImage, rectangle as rectangleOp, removePage, removePages, requestTimestamp, resizePage, reversePages, rgb, rotateAllPages, rotate as rotateOp, rotatePage, rotationMatrix, saveDocumentIncremental, saveIncremental, scale as scaleOp, serializePdf, setCharacterSpacing as setCharacterSpacingOp, setCharacterSpacing as setCharacterSqueeze, setColorSpace, setDashPattern as setDashPatternOp, setFillColor, setFillColorCmyk, setFillColorGray, setFillColorRgb, setFillingColor, setFlatness, setFont as setFontAndSize, setFont as setFontOp, setFontSize as setFontSizeOp, setGraphicsState as setGraphicsStateOp, setLeading as setLeadingOp, setLeading as setLineHeight, setLineCap as setLineCapOp, setLineJoin as setLineJoinOp, setLineWidth as setLineWidthOp, setMiterLimit, setStrokeColor, setStrokeColorCmyk, setStrokeColorGray, setStrokeColorRgb, setStrokeColorSpace, setStrokingColor, setTextMatrix as setTextMatrixOp, setTextRenderingMode as setTextRenderingModeOp, setTextRise as setTextRiseOp, setWordSpacing as setWordSpacingOp, sha256, sha384, sha512, showTextArray, showTextHex, showTextNextLine, showText as showTextOp, showTextWithSpacing, signPdf, skew as skewOp, splitPdf, stroke as strokeOp, summarizeIssues, svgToPdfOperators, tilingPattern, translate as translateOp, validatePdfA, verifyOwnerPassword, verifySignature, verifySignatures, verifyUserPassword, wrapInMarkedContent };
24038
25665
  //# sourceMappingURL=index.mjs.map