modern-pdf-lib 0.13.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
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-B7vA518n.mjs";
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-CTfeeqtF.mjs";
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
+ 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
3
  import { n as isAvailable, t as deflateSync$1 } from "./libdeflateWasm-DlHgU5oy.mjs";
4
4
  import { i as subsetFont, n as computeSubsetTag, t as buildSubsetCmap } from "./fontSubset-ZpLoOZ2e.mjs";
5
5
  import { t as embedPng } from "./pngEmbed-DTOqgEUC.mjs";
@@ -199,20 +199,19 @@ var PdfWriter = class {
199
199
  */
200
200
  writeObjectStream(entries, xrefEntries) {
201
201
  const serializedObjects = [];
202
- new StringByteWriter();
203
202
  for (const entry of entries) {
204
203
  const objWriter = new StringByteWriter();
205
204
  entry.object.serialize(objWriter);
206
205
  serializedObjects.push(objWriter.toUint8Array());
207
206
  }
208
- let headerStr = "";
207
+ const headerParts = [];
209
208
  let dataOffset = 0;
210
209
  for (let i = 0; i < entries.length; i++) {
211
- if (i > 0) headerStr += " ";
212
- headerStr += `${entries[i].ref.objectNumber} ${dataOffset}`;
210
+ headerParts.push(`${entries[i].ref.objectNumber} ${dataOffset}`);
213
211
  dataOffset += serializedObjects[i].length;
214
212
  if (i < entries.length - 1) dataOffset += 1;
215
213
  }
214
+ const headerStr = headerParts.join(" ");
216
215
  const headerBytes = encoder$5.encode(headerStr + " ");
217
216
  const firstOffset = headerBytes.length;
218
217
  let totalDataLen = firstOffset;
@@ -3754,7 +3753,7 @@ const isDelimiter = /* @__PURE__ */ (() => {
3754
3753
  * `hexVal[b]` is the numeric value (0-15) of a hex character, or -1 if
3755
3754
  * the byte is not a valid hex digit.
3756
3755
  */
3757
- const hexVal = /* @__PURE__ */ (() => {
3756
+ const hexVal$1 = /* @__PURE__ */ (() => {
3758
3757
  const t = new Int8Array(256).fill(-1);
3759
3758
  for (let i = 0; i <= 9; i++) t[CH_0 + i] = i;
3760
3759
  for (let i = 0; i < 6; i++) {
@@ -4051,7 +4050,7 @@ var PdfLexer = class {
4051
4050
  const d = this._data;
4052
4051
  let pos = startPos + 1;
4053
4052
  let depth = 1;
4054
- let result = "";
4053
+ const parts = [];
4055
4054
  while (pos < this.len && depth > 0) {
4056
4055
  const c = d[pos];
4057
4056
  if (c === CH_BACKSLASH) {
@@ -4060,35 +4059,35 @@ var PdfLexer = class {
4060
4059
  const esc = d[pos];
4061
4060
  switch (esc) {
4062
4061
  case 110:
4063
- result += "\n";
4062
+ parts.push("\n");
4064
4063
  pos++;
4065
4064
  break;
4066
4065
  case 114:
4067
- result += "\r";
4066
+ parts.push("\r");
4068
4067
  pos++;
4069
4068
  break;
4070
4069
  case 116:
4071
- result += " ";
4070
+ parts.push(" ");
4072
4071
  pos++;
4073
4072
  break;
4074
4073
  case 98:
4075
- result += "\b";
4074
+ parts.push("\b");
4076
4075
  pos++;
4077
4076
  break;
4078
4077
  case 102:
4079
- result += "\f";
4078
+ parts.push("\f");
4080
4079
  pos++;
4081
4080
  break;
4082
4081
  case CH_LPAREN:
4083
- result += "(";
4082
+ parts.push("(");
4084
4083
  pos++;
4085
4084
  break;
4086
4085
  case CH_RPAREN:
4087
- result += ")";
4086
+ parts.push(")");
4088
4087
  pos++;
4089
4088
  break;
4090
4089
  case CH_BACKSLASH:
4091
- result += "\\";
4090
+ parts.push("\\");
4092
4091
  pos++;
4093
4092
  break;
4094
4093
  case WS_LF:
@@ -4110,9 +4109,9 @@ var PdfLexer = class {
4110
4109
  pos++;
4111
4110
  }
4112
4111
  }
4113
- result += String.fromCharCode(octal & 255);
4112
+ parts.push(String.fromCharCode(octal & 255));
4114
4113
  } else {
4115
- result += String.fromCharCode(esc);
4114
+ parts.push(String.fromCharCode(esc));
4116
4115
  pos++;
4117
4116
  }
4118
4117
  break;
@@ -4121,23 +4120,23 @@ var PdfLexer = class {
4121
4120
  }
4122
4121
  if (c === CH_LPAREN) {
4123
4122
  depth++;
4124
- result += "(";
4123
+ parts.push("(");
4125
4124
  pos++;
4126
4125
  continue;
4127
4126
  }
4128
4127
  if (c === CH_RPAREN) {
4129
4128
  depth--;
4130
- if (depth > 0) result += ")";
4129
+ if (depth > 0) parts.push(")");
4131
4130
  pos++;
4132
4131
  continue;
4133
4132
  }
4134
4133
  if (c === WS_CR) {
4135
- result += "\n";
4134
+ parts.push("\n");
4136
4135
  pos++;
4137
4136
  if (pos < this.len && d[pos] === WS_LF) pos++;
4138
4137
  continue;
4139
4138
  }
4140
- result += String.fromCharCode(c);
4139
+ parts.push(String.fromCharCode(c));
4141
4140
  pos++;
4142
4141
  }
4143
4142
  if (depth !== 0) throw new PdfParseError({
@@ -4150,7 +4149,7 @@ var PdfLexer = class {
4150
4149
  this.position = pos;
4151
4150
  return {
4152
4151
  type: TokenType$1.LiteralString,
4153
- value: result,
4152
+ value: parts.join(""),
4154
4153
  offset: startPos
4155
4154
  };
4156
4155
  }
@@ -4163,7 +4162,8 @@ var PdfLexer = class {
4163
4162
  readHexString(startPos) {
4164
4163
  const d = this._data;
4165
4164
  let pos = startPos + 1;
4166
- let hex = "";
4165
+ const bytes = [];
4166
+ let hi = -1;
4167
4167
  while (pos < this.len) {
4168
4168
  const c = d[pos];
4169
4169
  if (c === CH_GT) {
@@ -4174,27 +4174,26 @@ var PdfLexer = class {
4174
4174
  pos++;
4175
4175
  continue;
4176
4176
  }
4177
- if (hexVal[c] === -1) throw new PdfParseError({
4177
+ const v = hexVal$1[c];
4178
+ if (v === -1) throw new PdfParseError({
4178
4179
  message: `PdfLexer: invalid hex digit 0x${c.toString(16).padStart(2, "0")} at offset ${pos} in hex string starting at ${startPos}`,
4179
4180
  offset: pos,
4180
4181
  expected: "hex digit (0-9, a-f, A-F)",
4181
4182
  actual: `0x${c.toString(16).padStart(2, "0")}`,
4182
4183
  data: this._data
4183
4184
  });
4184
- hex += String.fromCharCode(c);
4185
+ if (hi === -1) hi = v;
4186
+ else {
4187
+ bytes.push(hi << 4 | v);
4188
+ hi = -1;
4189
+ }
4185
4190
  pos++;
4186
4191
  }
4187
- if (hex.length % 2 !== 0) hex += "0";
4188
- let result = "";
4189
- for (let i = 0; i < hex.length; i += 2) {
4190
- const hi = hexVal[hex.charCodeAt(i)];
4191
- const lo = hexVal[hex.charCodeAt(i + 1)];
4192
- result += String.fromCharCode(hi << 4 | lo);
4193
- }
4192
+ if (hi !== -1) bytes.push(hi << 4);
4194
4193
  this.position = pos;
4195
4194
  return {
4196
4195
  type: TokenType$1.HexString,
4197
- value: result,
4196
+ value: String.fromCharCode.apply(null, bytes),
4198
4197
  offset: startPos
4199
4198
  };
4200
4199
  }
@@ -4207,7 +4206,7 @@ var PdfLexer = class {
4207
4206
  readName(startPos) {
4208
4207
  const d = this._data;
4209
4208
  let pos = startPos + 1;
4210
- let name = "/";
4209
+ const parts = ["/"];
4211
4210
  while (pos < this.len) {
4212
4211
  const c = d[pos];
4213
4212
  if (isWhitespace[c] || isDelimiter[c]) break;
@@ -4219,8 +4218,8 @@ var PdfLexer = class {
4219
4218
  actual: "end of input",
4220
4219
  data: this._data
4221
4220
  });
4222
- const hi = hexVal[d[pos + 1]];
4223
- const lo = hexVal[d[pos + 2]];
4221
+ const hi = hexVal$1[d[pos + 1]];
4222
+ const lo = hexVal$1[d[pos + 2]];
4224
4223
  if (hi === -1 || lo === -1) throw new PdfParseError({
4225
4224
  message: `PdfLexer: invalid #XX escape in name at offset ${pos}`,
4226
4225
  offset: pos,
@@ -4228,17 +4227,17 @@ var PdfLexer = class {
4228
4227
  actual: `#${String.fromCharCode(d[pos + 1])}${String.fromCharCode(d[pos + 2])}`,
4229
4228
  data: this._data
4230
4229
  });
4231
- name += String.fromCharCode(hi << 4 | lo);
4230
+ parts.push(String.fromCharCode(hi << 4 | lo));
4232
4231
  pos += 3;
4233
4232
  continue;
4234
4233
  }
4235
- name += String.fromCharCode(c);
4234
+ parts.push(String.fromCharCode(c));
4236
4235
  pos++;
4237
4236
  }
4238
4237
  this.position = pos;
4239
4238
  return {
4240
4239
  type: TokenType$1.Name,
4241
- value: name,
4240
+ value: parts.join(""),
4242
4241
  offset: startPos
4243
4242
  };
4244
4243
  }
@@ -4353,10 +4352,7 @@ var PdfLexer = class {
4353
4352
  * because it avoids the per-call overhead of the streaming decoder.
4354
4353
  */
4355
4354
  bytesToAscii(from, to) {
4356
- const d = this._data;
4357
- let s = "";
4358
- for (let i = from; i < to; i++) s += String.fromCharCode(d[i]);
4359
- return s;
4355
+ return String.fromCharCode.apply(null, this._data.subarray(from, to));
4360
4356
  }
4361
4357
  };
4362
4358
 
@@ -5250,18 +5246,20 @@ var XrefParser = class {
5250
5246
  parseTraditionalXref(offset) {
5251
5247
  const entries = [];
5252
5248
  let pos = offset;
5253
- const xrefTag = TEXT_DECODER$1.decode(this.data.subarray(pos, pos + 4));
5254
- if (xrefTag !== "xref") throw new PdfParseError({
5255
- message: `Invalid PDF: expected "xref" keyword at offset ${offset}, found "${xrefTag}".`,
5256
- offset,
5257
- expected: "\"xref\" keyword",
5258
- actual: `"${xrefTag}"`,
5259
- data: this.data
5260
- });
5249
+ if (!this.isTraditionalXref(pos)) {
5250
+ const xrefTag = TEXT_DECODER$1.decode(this.data.subarray(pos, pos + 4));
5251
+ throw new PdfParseError({
5252
+ message: `Invalid PDF: expected "xref" keyword at offset ${offset}, found "${xrefTag}".`,
5253
+ offset,
5254
+ expected: "\"xref\" keyword",
5255
+ actual: `"${xrefTag}"`,
5256
+ data: this.data
5257
+ });
5258
+ }
5261
5259
  pos += 4;
5262
5260
  pos = this.skipWhitespaceAt(pos);
5263
5261
  while (pos < this.data.length) {
5264
- if (TEXT_DECODER$1.decode(this.data.subarray(pos, Math.min(pos + 7, this.data.length))).startsWith("trailer")) break;
5262
+ if (this.matchesBytes(pos, 116, 114, 97, 105, 108, 101, 114)) break;
5265
5263
  const headerLine = this.readLineAt(pos);
5266
5264
  const headerMatch = headerLine.text.trim().match(/^(\d+)\s+(\d+)/);
5267
5265
  if (!headerMatch) throw new PdfParseError({
@@ -5276,35 +5274,45 @@ var XrefParser = class {
5276
5274
  pos = headerLine.nextPos;
5277
5275
  for (let i = 0; i < count; i++) {
5278
5276
  const objectNumber = firstObjNum + i;
5279
- const entryText = this.readXrefEntryAt(pos);
5280
- const entryMatch = entryText.text.trim().match(/^(\d{10})\s+(\d{5})\s+([fn])/);
5281
- if (!entryMatch) throw new PdfParseError({
5282
- message: `Invalid PDF: malformed xref entry at offset ${pos} for object ${objectNumber}: "${entryText.text.trim()}"`,
5283
- offset: pos,
5284
- expected: "xref entry \"OOOOOOOOOO GGGGG f/n\"",
5285
- actual: `"${entryText.text.trim()}"`,
5286
- data: this.data
5287
- });
5288
- const entryOffset = parseInt(entryMatch[1], 10);
5289
- const gen = parseInt(entryMatch[2], 10);
5290
- const marker = entryMatch[3];
5291
- entries.push({
5292
- objectNumber,
5293
- generationNumber: gen,
5294
- offset: entryOffset,
5295
- type: marker === "n" ? "in-use" : "free"
5296
- });
5297
- pos = entryText.nextPos;
5277
+ const parsed = this.parseXrefEntryDirect(pos);
5278
+ if (parsed) {
5279
+ entries.push({
5280
+ objectNumber,
5281
+ generationNumber: parsed.gen,
5282
+ offset: parsed.offset,
5283
+ type: parsed.type
5284
+ });
5285
+ pos = parsed.nextPos;
5286
+ } else {
5287
+ const entryText = this.readXrefEntryAt(pos);
5288
+ const entryMatch = entryText.text.trim().match(/^(\d{10})\s+(\d{5})\s+([fn])/);
5289
+ if (!entryMatch) throw new PdfParseError({
5290
+ message: `Invalid PDF: malformed xref entry at offset ${pos} for object ${objectNumber}: "${entryText.text.trim()}"`,
5291
+ offset: pos,
5292
+ expected: "xref entry \"OOOOOOOOOO GGGGG f/n\"",
5293
+ actual: `"${entryText.text.trim()}"`,
5294
+ data: this.data
5295
+ });
5296
+ entries.push({
5297
+ objectNumber,
5298
+ generationNumber: parseInt(entryMatch[2], 10),
5299
+ offset: parseInt(entryMatch[1], 10),
5300
+ type: entryMatch[3] === "n" ? "in-use" : "free"
5301
+ });
5302
+ pos = entryText.nextPos;
5303
+ }
5298
5304
  }
5299
5305
  }
5300
- const trailerTag = TEXT_DECODER$1.decode(this.data.subarray(pos, pos + 7));
5301
- if (trailerTag !== "trailer") throw new PdfParseError({
5302
- message: `Invalid PDF: expected "trailer" keyword at offset ${pos}, found "${trailerTag}".`,
5303
- offset: pos,
5304
- expected: "\"trailer\" keyword",
5305
- actual: `"${trailerTag}"`,
5306
- data: this.data
5307
- });
5306
+ if (!this.matchesBytes(pos, 116, 114, 97, 105, 108, 101, 114)) {
5307
+ const trailerTag = TEXT_DECODER$1.decode(this.data.subarray(pos, pos + 7));
5308
+ throw new PdfParseError({
5309
+ message: `Invalid PDF: expected "trailer" keyword at offset ${pos}, found "${trailerTag}".`,
5310
+ offset: pos,
5311
+ expected: "\"trailer\" keyword",
5312
+ actual: `"${trailerTag}"`,
5313
+ data: this.data
5314
+ });
5315
+ }
5308
5316
  pos += 7;
5309
5317
  const trailerObj = this.objectParser.parseObjectAt(pos);
5310
5318
  if (trailerObj.kind !== "dict") throw new PdfParseError({
@@ -5459,17 +5467,33 @@ var XrefParser = class {
5459
5467
  */
5460
5468
  rebuildXrefFromScan() {
5461
5469
  const entries = /* @__PURE__ */ new Map();
5462
- const fileText = TEXT_DECODER$1.decode(this.data);
5463
- const objPattern = /(\d+)\s+(\d+)\s+obj\b/g;
5464
- let match;
5465
- while ((match = objPattern.exec(fileText)) !== null) {
5466
- const objectNumber = parseInt(match[1], 10);
5467
- const gen = parseInt(match[2], 10);
5468
- const offset = match.index;
5470
+ const d = this.data;
5471
+ const len = d.length;
5472
+ for (let i = 0; i < len - 2; i++) {
5473
+ if (d[i] !== 111 || d[i + 1] !== 98 || d[i + 2] !== 106) continue;
5474
+ if (i + 3 < len) {
5475
+ const after = d[i + 3];
5476
+ if (after > 32 && after !== 37 && after !== 40 && after !== 41 && after !== 47 && after !== 60 && after !== 62 && after !== 91 && after !== 93 && after !== 123 && after !== 125) continue;
5477
+ }
5478
+ let j = i - 1;
5479
+ while (j >= 0 && (d[j] === 32 || d[j] === 10 || d[j] === 13 || d[j] === 9 || d[j] === 0)) j--;
5480
+ const genEnd = j + 1;
5481
+ while (j >= 0 && d[j] >= 48 && d[j] <= 57) j--;
5482
+ const genStart = j + 1;
5483
+ if (genStart >= genEnd) continue;
5484
+ while (j >= 0 && (d[j] === 32 || d[j] === 10 || d[j] === 13 || d[j] === 9 || d[j] === 0)) j--;
5485
+ const objEnd = j + 1;
5486
+ while (j >= 0 && d[j] >= 48 && d[j] <= 57) j--;
5487
+ const objStart = j + 1;
5488
+ if (objStart >= objEnd) continue;
5489
+ let objectNumber = 0;
5490
+ for (let k = objStart; k < objEnd; k++) objectNumber = objectNumber * 10 + (d[k] - 48);
5491
+ let gen = 0;
5492
+ for (let k = genStart; k < genEnd; k++) gen = gen * 10 + (d[k] - 48);
5469
5493
  entries.set(objectNumber, {
5470
5494
  objectNumber,
5471
5495
  generationNumber: gen,
5472
- offset,
5496
+ offset: objStart,
5473
5497
  type: "in-use"
5474
5498
  });
5475
5499
  }
@@ -5482,7 +5506,11 @@ var XrefParser = class {
5482
5506
  });
5483
5507
  let rootRef;
5484
5508
  let infoRef;
5485
- const trailerIdx = fileText.lastIndexOf("trailer");
5509
+ let trailerIdx = -1;
5510
+ for (let i = len - 7; i >= 0; i--) if (d[i] === 116 && d[i + 1] === 114 && d[i + 2] === 97 && d[i + 3] === 105 && d[i + 4] === 108 && d[i + 5] === 101 && d[i + 6] === 114) {
5511
+ trailerIdx = i;
5512
+ break;
5513
+ }
5486
5514
  if (trailerIdx !== -1) try {
5487
5515
  const trailerObj = this.objectParser.parseObjectAt(trailerIdx + 7);
5488
5516
  if (trailerObj.kind === "dict") {
@@ -5523,6 +5551,48 @@ var XrefParser = class {
5523
5551
  * Determine whether the data at the given offset starts with the
5524
5552
  * "xref" keyword (indicating a traditional xref table).
5525
5553
  */
5554
+ /**
5555
+ * Check if the bytes at `pos` match the given byte values.
5556
+ */
5557
+ matchesBytes(pos, ...bytes) {
5558
+ if (pos + bytes.length > this.data.length) return false;
5559
+ for (let i = 0; i < bytes.length; i++) if (this.data[pos + i] !== bytes[i]) return false;
5560
+ return true;
5561
+ }
5562
+ /**
5563
+ * Parse a standard xref entry directly from bytes, avoiding TextDecoder
5564
+ * and regex. Returns null if the bytes don't form a valid entry.
5565
+ *
5566
+ * Standard format: `OOOOOOOOOO GGGGG f/n` (18 significant bytes)
5567
+ */
5568
+ parseXrefEntryDirect(pos) {
5569
+ const d = this.data;
5570
+ if (pos + 18 > d.length) return null;
5571
+ let offset = 0;
5572
+ for (let k = 0; k < 10; k++) {
5573
+ const b = d[pos + k];
5574
+ if (b < 48 || b > 57) return null;
5575
+ offset = offset * 10 + (b - 48);
5576
+ }
5577
+ if (d[pos + 10] !== 32) return null;
5578
+ let gen = 0;
5579
+ for (let k = 0; k < 5; k++) {
5580
+ const b = d[pos + 11 + k];
5581
+ if (b < 48 || b > 57) return null;
5582
+ gen = gen * 10 + (b - 48);
5583
+ }
5584
+ if (d[pos + 16] !== 32) return null;
5585
+ const marker = d[pos + 17];
5586
+ if (marker !== 110 && marker !== 102) return null;
5587
+ let nextPos = pos + 18;
5588
+ while (nextPos < d.length && (d[nextPos] === 32 || d[nextPos] === 13 || d[nextPos] === 10)) nextPos++;
5589
+ return {
5590
+ offset,
5591
+ gen,
5592
+ type: marker === 110 ? "in-use" : "free",
5593
+ nextPos
5594
+ };
5595
+ }
5526
5596
  isTraditionalXref(offset) {
5527
5597
  if (offset + 4 > this.data.length) return false;
5528
5598
  return this.data[offset] === 120 && this.data[offset + 1] === 114 && this.data[offset + 2] === 101 && this.data[offset + 3] === 102;
@@ -6236,15 +6306,76 @@ function padPassword(password) {
6236
6306
  return result;
6237
6307
  }
6238
6308
  /**
6239
- * Truncate a UTF-8 password to 127 bytes for R=5/R=6 (SASLprep would
6240
- * be ideal but we approximate with simple truncation as most PDF
6241
- * libraries do).
6309
+ * Prepare a password for R=5/R=6 (AES-256) using SASLprep (RFC 4013).
6310
+ *
6311
+ * ISO 32000-2 SS7.6.3.1 requires SASLprep normalization for passwords
6312
+ * used with encryption revision 5 and 6. The steps are:
6313
+ *
6314
+ * 1. Map: Convert non-ASCII space characters to U+0020, remove
6315
+ * "commonly mapped to nothing" characters (RFC 3454 B.1).
6316
+ * 2. Normalize: Apply Unicode NFKC normalization.
6317
+ * 3. Prohibit: Reject characters from RFC 3454 prohibited tables.
6318
+ * 4. Bidi: Check bidirectional string rules.
6319
+ *
6320
+ * The result is truncated to 127 UTF-8 bytes per the PDF spec.
6242
6321
  */
6243
6322
  function preparePasswordV5(password) {
6244
- const encoded = new TextEncoder().encode(password);
6323
+ const prepared = saslprep(password);
6324
+ const encoded = new TextEncoder().encode(prepared);
6245
6325
  return encoded.length > 127 ? encoded.subarray(0, 127) : encoded;
6246
6326
  }
6247
6327
  /**
6328
+ * Simplified SASLprep (RFC 4013) profile of stringprep (RFC 3454).
6329
+ *
6330
+ * Covers the mapping, normalization, and prohibited-character steps
6331
+ * needed for PDF password preparation. Bidi checking is omitted
6332
+ * since PDF passwords are typically LTR and the spec allows
6333
+ * implementations to skip it.
6334
+ *
6335
+ * @internal
6336
+ */
6337
+ function saslprep(input) {
6338
+ let mapped = "";
6339
+ for (const ch of input) {
6340
+ const cp = ch.codePointAt(0);
6341
+ if (isMappedToNothing(cp)) continue;
6342
+ if (isNonAsciiSpace(cp)) {
6343
+ mapped += " ";
6344
+ continue;
6345
+ }
6346
+ mapped += ch;
6347
+ }
6348
+ const normalized = mapped.normalize("NFKC");
6349
+ for (const ch of normalized) {
6350
+ const cp = ch.codePointAt(0);
6351
+ if (isProhibited(cp)) throw new Error(`Password contains prohibited character U+${cp.toString(16).toUpperCase().padStart(4, "0")} (SASLprep)`);
6352
+ }
6353
+ return normalized;
6354
+ }
6355
+ /** RFC 3454 Table B.1 — Commonly mapped to nothing. */
6356
+ function isMappedToNothing(cp) {
6357
+ 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;
6358
+ }
6359
+ /** RFC 3454 Table C.1.2 — Non-ASCII space characters. */
6360
+ function isNonAsciiSpace(cp) {
6361
+ 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;
6362
+ }
6363
+ /**
6364
+ * RFC 3454 prohibited tables (C.2.1, C.2.2, C.3, C.4, C.5, C.6, C.7, C.8, C.9).
6365
+ * Simplified to the ranges most likely to appear in passwords.
6366
+ */
6367
+ function isProhibited(cp) {
6368
+ if (cp <= 31 || cp === 127) return true;
6369
+ 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;
6370
+ if (cp >= 57344 && cp <= 63743 || cp >= 983040 && cp <= 1048573 || cp >= 1048576 && cp <= 1114109) return true;
6371
+ if (cp >= 64976 && cp <= 65007 || (cp & 65535) === 65534 || (cp & 65535) === 65535) return true;
6372
+ if (cp >= 55296 && cp <= 57343) return true;
6373
+ if (cp === 65529 || cp === 65530 || cp === 65531 || cp === 65532) return true;
6374
+ if (cp === 832 || cp === 833 || cp === 8206 || cp === 8207 || cp >= 8234 && cp <= 8238 || cp >= 8298 && cp <= 8303) return true;
6375
+ if (cp === 917505 || cp >= 917536 && cp <= 917631) return true;
6376
+ return false;
6377
+ }
6378
+ /**
6248
6379
  * Concatenate multiple Uint8Arrays into one.
6249
6380
  */
6250
6381
  function concat$1(...arrays) {
@@ -6320,10 +6451,12 @@ function computeOwnerPasswordValue(ownerPassword, userPassword, revision, keyLen
6320
6451
  if (revision >= 3) for (let i = 0; i < 50; i++) hash = md5(hash.subarray(0, keyByteLength));
6321
6452
  const rc4Key = hash.subarray(0, keyByteLength);
6322
6453
  let encrypted = rc4(rc4Key, padPassword(userPassword));
6323
- if (revision >= 3) for (let i = 1; i <= 19; i++) {
6454
+ if (revision >= 3) {
6324
6455
  const modKey = new Uint8Array(rc4Key.length);
6325
- for (let j = 0; j < rc4Key.length; j++) modKey[j] = (rc4Key[j] ^ i) & 255;
6326
- encrypted = rc4(modKey, encrypted);
6456
+ for (let i = 1; i <= 19; i++) {
6457
+ for (let j = 0; j < rc4Key.length; j++) modKey[j] = (rc4Key[j] ^ i) & 255;
6458
+ encrypted = rc4(modKey, encrypted);
6459
+ }
6327
6460
  }
6328
6461
  return encrypted;
6329
6462
  }
@@ -6353,10 +6486,12 @@ function computeUserPasswordR2(fileKey) {
6353
6486
  */
6354
6487
  function computeUserPasswordR3R4(fileKey, fileId) {
6355
6488
  let encrypted = rc4(fileKey, md5(concat$1(PASSWORD_PADDING, fileId)));
6356
- for (let i = 1; i <= 19; i++) {
6489
+ {
6357
6490
  const modKey = new Uint8Array(fileKey.length);
6358
- for (let j = 0; j < fileKey.length; j++) modKey[j] = (fileKey[j] ^ i) & 255;
6359
- encrypted = rc4(modKey, encrypted);
6491
+ for (let i = 1; i <= 19; i++) {
6492
+ for (let j = 0; j < fileKey.length; j++) modKey[j] = (fileKey[j] ^ i) & 255;
6493
+ encrypted = rc4(modKey, encrypted);
6494
+ }
6360
6495
  }
6361
6496
  const result = new Uint8Array(32);
6362
6497
  result.set(encrypted.subarray(0, 16));
@@ -6422,9 +6557,17 @@ async function algorithm2B(password, salt, uKey) {
6422
6557
  let K = await sha256(uKey ? concat$1(password, salt, uKey) : concat$1(password, salt));
6423
6558
  let round = 0;
6424
6559
  while (true) {
6425
- const K1block = uKey ? concat$1(password, K, uKey) : concat$1(password, K);
6426
- const K1 = new Uint8Array(K1block.length * 64);
6427
- for (let i = 0; i < 64; i++) K1.set(K1block, i * K1block.length);
6560
+ const pLen = password.length;
6561
+ const kLen = K.length;
6562
+ const uLen = uKey ? uKey.length : 0;
6563
+ const blockLen = pLen + kLen + uLen;
6564
+ const K1 = new Uint8Array(blockLen * 64);
6565
+ for (let i = 0; i < 64; i++) {
6566
+ const base = i * blockLen;
6567
+ K1.set(password, base);
6568
+ K1.set(K, base + pLen);
6569
+ if (uKey) K1.set(uKey, base + pLen + kLen);
6570
+ }
6428
6571
  const E = await aesEncryptCBCNoPad(K.subarray(0, 16), K.subarray(16, 32), K1);
6429
6572
  let bigModVal = 0;
6430
6573
  for (let i = 0; i < 16; i++) bigModVal = (bigModVal * 256 + E[i]) % 3;
@@ -6511,11 +6654,33 @@ async function computeEncryptionKeyR6(password, dict, isOwner) {
6511
6654
  }
6512
6655
  }
6513
6656
  /**
6657
+ * LRU cache for file encryption keys.
6658
+ *
6659
+ * Keyed on a hash derived from password + encryption dict parameters,
6660
+ * so re-opening the same PDF with the same password skips the expensive
6661
+ * key derivation (especially for R=6 which runs 64+ rounds of AES+SHA).
6662
+ */
6663
+ const fileKeyCache = /* @__PURE__ */ new Map();
6664
+ const FILE_KEY_CACHE_MAX = 32;
6665
+ /**
6666
+ * Build a cache key string from the inputs that uniquely identify
6667
+ * a key derivation result.
6668
+ */
6669
+ function buildCacheKey(password, dict, fileId) {
6670
+ const oHex = Array.from(dict.ownerKey.subarray(0, 16), (b) => b.toString(16).padStart(2, "0")).join("");
6671
+ const uHex = Array.from(dict.userKey.subarray(0, 16), (b) => b.toString(16).padStart(2, "0")).join("");
6672
+ const fHex = Array.from(fileId.subarray(0, 16), (b) => b.toString(16).padStart(2, "0")).join("");
6673
+ return `${dict.revision}:${dict.permissions}:${password}:${oHex}:${uHex}:${fHex}`;
6674
+ }
6675
+ /**
6514
6676
  * Compute the file encryption key from a password and encryption dict.
6515
6677
  *
6516
6678
  * Tries the password as both user and owner password. Returns the key
6517
6679
  * on the first successful match, or throws if neither works.
6518
6680
  *
6681
+ * Results are cached so that re-opening the same PDF with the same
6682
+ * password skips the expensive key derivation.
6683
+ *
6519
6684
  * @param password The password to try.
6520
6685
  * @param dict Encryption dictionary values.
6521
6686
  * @param fileId The first element of the /ID array (unused for R>=5).
@@ -6523,23 +6688,35 @@ async function computeEncryptionKeyR6(password, dict, isOwner) {
6523
6688
  * @throws If the password is incorrect.
6524
6689
  */
6525
6690
  async function computeFileEncryptionKey(password, dict, fileId) {
6691
+ const ck = buildCacheKey(password, dict, fileId);
6692
+ const cached = fileKeyCache.get(ck);
6693
+ if (cached) return cached.slice();
6694
+ let result;
6526
6695
  if (dict.revision >= 6) {
6527
6696
  const userKey = await computeEncryptionKeyR6(password, dict, false);
6528
- if (userKey) return userKey;
6529
- const ownerKey = await computeEncryptionKeyR6(password, dict, true);
6530
- if (ownerKey) return ownerKey;
6531
- throw new Error("Incorrect password for R=6 encryption");
6532
- }
6533
- if (dict.revision === 5) {
6697
+ if (userKey) result = userKey;
6698
+ else {
6699
+ const ownerKey = await computeEncryptionKeyR6(password, dict, true);
6700
+ if (ownerKey) result = ownerKey;
6701
+ else throw new Error("Incorrect password for R=6 encryption");
6702
+ }
6703
+ } else if (dict.revision === 5) {
6534
6704
  const userKey = await computeEncryptionKeyR5(password, dict, false);
6535
- if (userKey) return userKey;
6536
- const ownerKey = await computeEncryptionKeyR5(password, dict, true);
6537
- if (ownerKey) return ownerKey;
6538
- throw new Error("Incorrect password for R=5 encryption");
6539
- }
6540
- if (await verifyUserPassword(password, dict, fileId)) return computeEncryptionKeyR2R4(password, dict, fileId);
6541
- if (await verifyOwnerPassword(password, dict, fileId)) return computeEncryptionKeyR2R4Bytes(recoverUserKeyFromOwner(password, dict), dict, fileId);
6542
- throw new Error("Incorrect password");
6705
+ if (userKey) result = userKey;
6706
+ else {
6707
+ const ownerKey = await computeEncryptionKeyR5(password, dict, true);
6708
+ if (ownerKey) result = ownerKey;
6709
+ else throw new Error("Incorrect password for R=5 encryption");
6710
+ }
6711
+ } else if (await verifyUserPassword(password, dict, fileId)) result = computeEncryptionKeyR2R4(password, dict, fileId);
6712
+ else if (await verifyOwnerPassword(password, dict, fileId)) result = computeEncryptionKeyR2R4Bytes(recoverUserKeyFromOwner(password, dict), dict, fileId);
6713
+ else throw new Error("Incorrect password");
6714
+ if (fileKeyCache.size >= FILE_KEY_CACHE_MAX) {
6715
+ const firstKey = fileKeyCache.keys().next().value;
6716
+ if (firstKey !== void 0) fileKeyCache.delete(firstKey);
6717
+ }
6718
+ fileKeyCache.set(ck, result.slice());
6719
+ return result;
6543
6720
  }
6544
6721
  /**
6545
6722
  * Compute encryption key from raw password bytes (already padded/recovered).
@@ -6802,6 +6979,15 @@ var PdfEncryptionHandler = class PdfEncryptionHandler {
6802
6979
  perms;
6803
6980
  /** The file ID (first element of /ID array). */
6804
6981
  fileId;
6982
+ /**
6983
+ * Cache for per-object derived keys (V=1-4 only).
6984
+ * Key: `(objNum << 16) | genNum` — unique integer per object.
6985
+ * Value: the derived encryption key.
6986
+ *
6987
+ * Avoids recomputing MD5(fileKey + objNum + genNum [+ sAlT]) for
6988
+ * every string and stream in the same object.
6989
+ */
6990
+ objectKeyCache = /* @__PURE__ */ new Map();
6805
6991
  constructor(params) {
6806
6992
  this.fileKey = params.fileKey;
6807
6993
  this.version = params.version;
@@ -6954,6 +7140,9 @@ var PdfEncryptionHandler = class PdfEncryptionHandler {
6954
7140
  */
6955
7141
  deriveObjectKey(objNum, genNum) {
6956
7142
  if (this.version === 5) return this.fileKey;
7143
+ const cacheKey = objNum << 16 | genNum;
7144
+ const cached = this.objectKeyCache.get(cacheKey);
7145
+ if (cached) return cached;
6957
7146
  const extra = this.useAes ? 4 : 0;
6958
7147
  const input = new Uint8Array(this.fileKey.length + 5 + extra);
6959
7148
  input.set(this.fileKey, 0);
@@ -6971,7 +7160,9 @@ var PdfEncryptionHandler = class PdfEncryptionHandler {
6971
7160
  }
6972
7161
  const hash = md5(input);
6973
7162
  const keyLen = Math.min(this.keyLengthBits / 8 + 5, 16);
6974
- return hash.subarray(0, keyLen);
7163
+ const key = hash.subarray(0, keyLen);
7164
+ this.objectKeyCache.set(cacheKey, key);
7165
+ return key;
6975
7166
  }
6976
7167
  /**
6977
7168
  * Encrypt raw data for a specific object.
@@ -8576,7 +8767,17 @@ const NS_PDFAID = "http://www.aiim.org/pdfa/ns/id/";
8576
8767
  * @internal
8577
8768
  */
8578
8769
  function escapeXml(str) {
8579
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
8770
+ const parts = [];
8771
+ for (let i = 0; i < str.length; i++) {
8772
+ const c = str.charCodeAt(i);
8773
+ if (c === 38) parts.push("&amp;");
8774
+ else if (c === 60) parts.push("&lt;");
8775
+ else if (c === 62) parts.push("&gt;");
8776
+ else if (c === 34) parts.push("&quot;");
8777
+ else if (c === 39) parts.push("&apos;");
8778
+ else parts.push(str[i]);
8779
+ }
8780
+ return parts.join("");
8580
8781
  }
8581
8782
  /**
8582
8783
  * Format a Date as an ISO 8601 string for XMP.
@@ -9879,9 +10080,10 @@ function remapRef$1(sourceRef, context) {
9879
10080
  context.refMap.set(sourceRef.objectNumber, placeholderRef);
9880
10081
  return placeholderRef;
9881
10082
  }
10083
+ let streamHash;
9882
10084
  if (sourceObj.kind === "stream") {
9883
- const hash = hashBytes(sourceObj.data);
9884
- const dedup = context.hashMap.get(hash);
10085
+ streamHash = hashBytes(sourceObj.data);
10086
+ const dedup = context.hashMap.get(streamHash);
9885
10087
  if (dedup) {
9886
10088
  context.refMap.set(sourceRef.objectNumber, dedup);
9887
10089
  return dedup;
@@ -9891,10 +10093,7 @@ function remapRef$1(sourceRef, context) {
9891
10093
  context.refMap.set(sourceRef.objectNumber, targetRef);
9892
10094
  const clonedObj = deepClone$1(sourceObj, context);
9893
10095
  context.targetRegistry.assign(targetRef, clonedObj);
9894
- if (sourceObj.kind === "stream") {
9895
- const hash = hashBytes(sourceObj.data);
9896
- context.hashMap.set(hash, targetRef);
9897
- }
10096
+ if (streamHash !== void 0) context.hashMap.set(streamHash, targetRef);
9898
10097
  return targetRef;
9899
10098
  }
9900
10099
  /**
@@ -10125,23 +10324,73 @@ function findStringForward(data, needle, startOffset) {
10125
10324
  return -1;
10126
10325
  }
10127
10326
  /**
10128
- * Prepare a PDF for signing by appending a signature dictionary
10129
- * via incremental update.
10130
- *
10131
- * This function:
10132
- * 1. Appends a new signature field and value to the PDF
10133
- * 2. Inserts an empty `/Contents` placeholder of the specified size
10134
- * 3. Computes the `/ByteRange` that excludes the `/Contents` value
10327
+ * Build a PDF content stream for a visible signature appearance.
10135
10328
  *
10136
- * The resulting PDF bytes can be hashed (excluding the placeholder gap)
10137
- * and the hash can be signed.
10329
+ * Renders a bordered rectangle with optional background color and
10330
+ * text lines rendered in Helvetica.
10138
10331
  *
10139
- * @param pdfBytes The original PDF file bytes.
10140
- * @param signatureFieldName The name for the signature field.
10141
- * @param placeholderSize Size in bytes for the signature. Default 8192.
10142
- * @returns The prepared PDF and ByteRange info.
10332
+ * @internal
10143
10333
  */
10144
- function prepareForSigning(pdfBytes, signatureFieldName, placeholderSize = 8192) {
10334
+ function buildSignatureAppearanceStream(options) {
10335
+ const [, , w, h] = options.rect;
10336
+ const fontSize = options.fontSize ?? 10;
10337
+ const borderWidth = options.borderWidth ?? 1;
10338
+ const borderColor = options.borderColor ?? [
10339
+ 0,
10340
+ 0,
10341
+ 0
10342
+ ];
10343
+ const bgColor = options.backgroundColor;
10344
+ const lines = options.textLines;
10345
+ const ops = [];
10346
+ ops.push("q");
10347
+ if (bgColor) {
10348
+ ops.push(`${n$5(bgColor[0])} ${n$5(bgColor[1])} ${n$5(bgColor[2])} rg`);
10349
+ ops.push(`0 0 ${n$5(w)} ${n$5(h)} re`);
10350
+ ops.push("f");
10351
+ }
10352
+ if (borderWidth > 0) {
10353
+ ops.push(`${n$5(borderColor[0])} ${n$5(borderColor[1])} ${n$5(borderColor[2])} RG`);
10354
+ ops.push(`${n$5(borderWidth)} w`);
10355
+ const bw2 = borderWidth / 2;
10356
+ ops.push(`${n$5(bw2)} ${n$5(bw2)} ${n$5(w - borderWidth)} ${n$5(h - borderWidth)} re`);
10357
+ ops.push("S");
10358
+ }
10359
+ if (lines.length > 0) {
10360
+ const margin = borderWidth + 4;
10361
+ const lineHeight = fontSize * 1.2;
10362
+ ops.push("BT");
10363
+ ops.push(`/F1 ${n$5(fontSize)} Tf`);
10364
+ ops.push("0 0 0 rg");
10365
+ const startY = h - margin - fontSize;
10366
+ for (let i = 0; i < lines.length; i++) {
10367
+ const y = startY - i * lineHeight;
10368
+ if (y < margin) break;
10369
+ ops.push(`${n$5(margin)} ${n$5(y)} Td`);
10370
+ ops.push(`(${escapePdfString(lines[i])}) Tj`);
10371
+ ops.push(`${n$5(-margin)} ${n$5(-y)} Td`);
10372
+ }
10373
+ ops.push("ET");
10374
+ }
10375
+ ops.push("Q");
10376
+ return ops.join("\n");
10377
+ }
10378
+ /**
10379
+ * Escape a string for use inside a PDF literal string `(...)`.
10380
+ * @internal
10381
+ */
10382
+ function escapePdfString(str) {
10383
+ return str.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
10384
+ }
10385
+ /**
10386
+ * Format a number for PDF operators (max 6 decimal places, no trailing zeros).
10387
+ * @internal
10388
+ */
10389
+ function n$5(value) {
10390
+ if (Number.isInteger(value)) return value.toString();
10391
+ return value.toFixed(6).replace(/\.?0+$/, "");
10392
+ }
10393
+ function prepareForSigning(pdfBytes, signatureFieldName, placeholderSize = 8192, appearance) {
10145
10394
  const pdfStr = decoder$4.decode(pdfBytes);
10146
10395
  const startxrefIdx = pdfStr.lastIndexOf("startxref");
10147
10396
  if (startxrefIdx === -1) throw new Error("Cannot find startxref in PDF — file may be corrupted");
@@ -10158,9 +10407,21 @@ function prepareForSigning(pdfBytes, signatureFieldName, placeholderSize = 8192)
10158
10407
  const infoMatch = pdfStr.match(/\/Info\s+(\d+)\s+(\d+)\s+R/);
10159
10408
  const sigValueObjNum = originalSize;
10160
10409
  const sigFieldObjNum = originalSize + 1;
10161
- const newSize = originalSize + 2;
10410
+ let apStreamObjNum = -1;
10411
+ let newSize = originalSize + 2;
10412
+ if (appearance) {
10413
+ apStreamObjNum = newSize;
10414
+ newSize++;
10415
+ }
10162
10416
  const sigDictStr = buildSignatureDictString(placeholderSize, signatureFieldName);
10163
- const sigFieldDict = `<< /Type /Annot /Subtype /Widget /FT /Sig /T (${signatureFieldName}) /V ${sigValueObjNum} 0 R /F 132 /Rect [0 0 0 0] >>`;
10417
+ let rectStr = "0 0 0 0";
10418
+ if (appearance) {
10419
+ const [x, y, w, h] = appearance.rect;
10420
+ rectStr = `${x} ${y} ${x + w} ${y + h}`;
10421
+ }
10422
+ let sigFieldDict = `<< /Type /Annot /Subtype /Widget /FT /Sig /T (${signatureFieldName}) /V ${sigValueObjNum} 0 R /F 132 /Rect [${rectStr}]`;
10423
+ if (appearance && apStreamObjNum >= 0) sigFieldDict += ` /AP << /N ${apStreamObjNum} 0 R >>`;
10424
+ sigFieldDict += " >>";
10164
10425
  let appendix = "\n";
10165
10426
  const objOffsets = /* @__PURE__ */ new Map();
10166
10427
  const sigValueStart = pdfBytes.length + encoder$3.encode(appendix).length;
@@ -10173,11 +10434,28 @@ function prepareForSigning(pdfBytes, signatureFieldName, placeholderSize = 8192)
10173
10434
  appendix += `${sigFieldObjNum} 0 obj\n`;
10174
10435
  appendix += sigFieldDict;
10175
10436
  appendix += `\nendobj\n`;
10437
+ let apStreamStart = -1;
10438
+ if (appearance && apStreamObjNum >= 0) {
10439
+ apStreamStart = pdfBytes.length + encoder$3.encode(appendix).length;
10440
+ objOffsets.set(apStreamObjNum, apStreamStart);
10441
+ const apContent = buildSignatureAppearanceStream(appearance);
10442
+ const [, , w, h] = appearance.rect;
10443
+ appendix += `${apStreamObjNum} 0 obj\n`;
10444
+ appendix += `<< /Type /XObject /Subtype /Form /BBox [0 0 ${w} ${h}]`;
10445
+ appendix += ` /Resources << /Font << /F1 << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> >> >>`;
10446
+ appendix += ` /Length ${apContent.length} >>\n`;
10447
+ appendix += `stream\n`;
10448
+ appendix += apContent;
10449
+ appendix += `\nendstream\n`;
10450
+ appendix += `endobj\n`;
10451
+ }
10176
10452
  const xrefOffset = pdfBytes.length + encoder$3.encode(appendix).length;
10453
+ const objCount = appearance ? 3 : 2;
10177
10454
  appendix += "xref\n";
10178
- appendix += `${sigValueObjNum} 2\n`;
10455
+ appendix += `${sigValueObjNum} ${objCount}\n`;
10179
10456
  appendix += `${sigValueStart.toString().padStart(10, "0")} 00000 n \n`;
10180
10457
  appendix += `${sigFieldStart.toString().padStart(10, "0")} 00000 n \n`;
10458
+ if (appearance && apStreamStart >= 0) appendix += `${apStreamStart.toString().padStart(10, "0")} 00000 n \n`;
10181
10459
  appendix += "trailer\n";
10182
10460
  appendix += "<<\n";
10183
10461
  appendix += `/Size ${newSize}\n`;
@@ -10967,7 +11245,32 @@ function parsePkcs7ForCert(pkcs7Bytes) {
10967
11245
  */
10968
11246
  async function signPdf(pdfBytes, fieldName, options) {
10969
11247
  const hashAlgorithm = options.hashAlgorithm ?? "SHA-256";
10970
- const { preparedPdf, byteRange } = prepareForSigning(pdfBytes, fieldName, 8192);
11248
+ let prepareAppearance;
11249
+ if (options.appearance) {
11250
+ const ap = options.appearance;
11251
+ let textLines = ap.text;
11252
+ if (!textLines) {
11253
+ textLines = [];
11254
+ try {
11255
+ const { subjectCN } = extractIssuerAndSerial(options.certificate);
11256
+ if (subjectCN) textLines.push(`Signed by: ${subjectCN}`);
11257
+ } catch {
11258
+ textLines.push("Digitally Signed");
11259
+ }
11260
+ if (options.reason) textLines.push(`Reason: ${options.reason}`);
11261
+ if (options.location) textLines.push(`Location: ${options.location}`);
11262
+ textLines.push(`Date: ${(/* @__PURE__ */ new Date()).toISOString().substring(0, 10)}`);
11263
+ }
11264
+ prepareAppearance = {
11265
+ rect: ap.rect,
11266
+ textLines,
11267
+ fontSize: ap.fontSize,
11268
+ backgroundColor: ap.backgroundColor,
11269
+ borderColor: ap.borderColor,
11270
+ borderWidth: ap.borderWidth
11271
+ };
11272
+ }
11273
+ const { preparedPdf, byteRange } = prepareForSigning(pdfBytes, fieldName, 8192, prepareAppearance);
10971
11274
  return embedSignature(preparedPdf, await buildPkcs7Signature(await computeSignatureHash(preparedPdf, byteRange.byteRange, hashAlgorithm), {
10972
11275
  signerInfo: {
10973
11276
  certificate: options.certificate,
@@ -14473,7 +14776,6 @@ function read2DMode(reader) {
14473
14776
  if (bit === 1) {
14474
14777
  bit = reader.readBit();
14475
14778
  if (bit < 0) return Mode2D.EOL;
14476
- if (bit === 1) return Mode2D.HORIZONTAL;
14477
14779
  if (bit === 0) return Mode2D.VERTICAL_MINUS_1;
14478
14780
  return Mode2D.VERTICAL_PLUS_1;
14479
14781
  }
@@ -17604,9 +17906,28 @@ function hexValue(ch) {
17604
17906
  * `~>` marks end-of-data.
17605
17907
  */
17606
17908
  function decodeASCII85(data) {
17607
- const result = [];
17608
- const group = [];
17909
+ let out = new Uint8Array(Math.max(data.length * 4 / 5 | 0, 256));
17910
+ let outPos = 0;
17911
+ const group = new Uint8Array(5);
17912
+ let groupLen = 0;
17609
17913
  let i = 0;
17914
+ function ensureOut(needed) {
17915
+ if (outPos + needed > out.length) {
17916
+ const newBuf = new Uint8Array(Math.max(out.length * 2, outPos + needed));
17917
+ newBuf.set(out.subarray(0, outPos));
17918
+ out = newBuf;
17919
+ }
17920
+ }
17921
+ function decodeGroup(count) {
17922
+ for (let k = count; k < 5; k++) group[k] = 84;
17923
+ const val = group[0] * 85 * 85 * 85 * 85 + group[1] * 85 * 85 * 85 + group[2] * 85 * 85 + group[3] * 85 + group[4];
17924
+ const numBytes = count === 5 ? 4 : count - 1;
17925
+ ensureOut(numBytes);
17926
+ if (numBytes >= 1) out[outPos++] = val >>> 24 & 255;
17927
+ if (numBytes >= 2) out[outPos++] = val >>> 16 & 255;
17928
+ if (numBytes >= 3) out[outPos++] = val >>> 8 & 255;
17929
+ if (numBytes >= 4) out[outPos++] = val & 255;
17930
+ }
17610
17931
  while (i < data.length) {
17611
17932
  const ch = data[i];
17612
17933
  if (ch === 126) break;
@@ -17615,8 +17936,12 @@ function decodeASCII85(data) {
17615
17936
  continue;
17616
17937
  }
17617
17938
  if (ch === 122) {
17618
- if (group.length > 0) throw new Error("ASCII85Decode: \"z\" inside a group");
17619
- result.push(0, 0, 0, 0);
17939
+ if (groupLen > 0) throw new Error("ASCII85Decode: \"z\" inside a group");
17940
+ ensureOut(4);
17941
+ out[outPos++] = 0;
17942
+ out[outPos++] = 0;
17943
+ out[outPos++] = 0;
17944
+ out[outPos++] = 0;
17620
17945
  i++;
17621
17946
  continue;
17622
17947
  }
@@ -17624,24 +17949,15 @@ function decodeASCII85(data) {
17624
17949
  i++;
17625
17950
  continue;
17626
17951
  }
17627
- group.push(ch - 33);
17628
- if (group.length === 5) {
17629
- const val = group[0] * 85 * 85 * 85 * 85 + group[1] * 85 * 85 * 85 + group[2] * 85 * 85 + group[3] * 85 + group[4];
17630
- result.push(val >>> 24 & 255, val >>> 16 & 255, val >>> 8 & 255, val & 255);
17631
- group.length = 0;
17952
+ group[groupLen++] = ch - 33;
17953
+ if (groupLen === 5) {
17954
+ decodeGroup(5);
17955
+ groupLen = 0;
17632
17956
  }
17633
17957
  i++;
17634
17958
  }
17635
- if (group.length >= 2) {
17636
- const origLen = group.length;
17637
- while (group.length < 5) group.push(84);
17638
- const val = group[0] * 85 * 85 * 85 * 85 + group[1] * 85 * 85 * 85 + group[2] * 85 * 85 + group[3] * 85 + group[4];
17639
- const numBytes = origLen - 1;
17640
- if (numBytes >= 1) result.push(val >>> 24 & 255);
17641
- if (numBytes >= 2) result.push(val >>> 16 & 255);
17642
- if (numBytes >= 3) result.push(val >>> 8 & 255);
17643
- }
17644
- return new Uint8Array(result);
17959
+ if (groupLen >= 2) decodeGroup(groupLen);
17960
+ return out.subarray(0, outPos);
17645
17961
  }
17646
17962
  /**
17647
17963
  * Decode an LZWDecode stream.
@@ -17661,6 +17977,10 @@ function decodeLZW(data, parms) {
17661
17977
  }
17662
17978
  /**
17663
17979
  * Core LZW decompression.
17980
+ *
17981
+ * Uses a flat pooled buffer for the code table instead of per-entry
17982
+ * Uint8Array allocations, and a pre-allocated output buffer with
17983
+ * manual growth instead of `number[]` + push.
17664
17984
  */
17665
17985
  function lzwDecompress(data, earlyChange) {
17666
17986
  const CLEAR_TABLE = 256;
@@ -17685,71 +18005,88 @@ function lzwDecompress(data, earlyChange) {
17685
18005
  }
17686
18006
  return result;
17687
18007
  }
17688
- let table = [];
18008
+ let tableBuf = new Uint8Array(65536);
18009
+ const tableOff = new Int32Array(4096);
18010
+ const tableLen = new Int32Array(4096);
18011
+ let tableBufUsed = 256;
17689
18012
  let codeSize = 9;
17690
18013
  let nextCode = 258;
18014
+ for (let i = 0; i < 256; i++) {
18015
+ tableBuf[i] = i;
18016
+ tableOff[i] = i;
18017
+ tableLen[i] = 1;
18018
+ }
17691
18019
  function resetTable() {
17692
- table = [];
17693
- for (let i = 0; i < 256; i++) table[i] = new Uint8Array([i]);
17694
- table[256] = new Uint8Array(0);
17695
- table[257] = new Uint8Array(0);
18020
+ tableBufUsed = 256;
17696
18021
  nextCode = 258;
17697
18022
  codeSize = 9;
17698
18023
  }
17699
- resetTable();
17700
- const output = [];
17701
- let prevEntry = null;
18024
+ let out = new Uint8Array(Math.max(data.length * 3, 4096));
18025
+ let outPos = 0;
18026
+ function ensureOut(needed) {
18027
+ if (outPos + needed > out.length) {
18028
+ const newBuf = new Uint8Array(Math.max(out.length * 2, outPos + needed));
18029
+ newBuf.set(out.subarray(0, outPos));
18030
+ out = newBuf;
18031
+ }
18032
+ }
18033
+ function ensureTable(needed) {
18034
+ if (tableBufUsed + needed > tableBuf.length) {
18035
+ const newBuf = new Uint8Array(Math.max(tableBuf.length * 2, tableBufUsed + needed));
18036
+ newBuf.set(tableBuf.subarray(0, tableBufUsed));
18037
+ tableBuf = newBuf;
18038
+ }
18039
+ }
18040
+ function writeEntry(code) {
18041
+ const off = tableOff[code];
18042
+ const len = tableLen[code];
18043
+ ensureOut(len);
18044
+ out.set(tableBuf.subarray(off, off + len), outPos);
18045
+ outPos += len;
18046
+ }
18047
+ function addEntry(prevCode, firstByte) {
18048
+ const prevOff = tableOff[prevCode];
18049
+ const prevLen = tableLen[prevCode];
18050
+ const newLen = prevLen + 1;
18051
+ ensureTable(newLen);
18052
+ tableBuf.set(tableBuf.subarray(prevOff, prevOff + prevLen), tableBufUsed);
18053
+ tableBuf[tableBufUsed + prevLen] = firstByte;
18054
+ tableOff[nextCode] = tableBufUsed;
18055
+ tableLen[nextCode] = newLen;
18056
+ tableBufUsed += newLen;
18057
+ nextCode++;
18058
+ }
17702
18059
  let code = readBits(codeSize);
17703
18060
  if (code === CLEAR_TABLE) {
17704
18061
  resetTable();
17705
18062
  code = readBits(codeSize);
17706
18063
  }
17707
- if (code === EOD) return new Uint8Array(output);
17708
- let entry = table[code];
17709
- if (entry) {
17710
- for (const b of entry) output.push(b);
17711
- prevEntry = entry;
17712
- }
18064
+ if (code === EOD) return new Uint8Array(0);
18065
+ writeEntry(code);
18066
+ let prevCode = code;
17713
18067
  while (true) {
17714
18068
  code = readBits(codeSize);
17715
18069
  if (code === EOD) break;
17716
18070
  if (code === CLEAR_TABLE) {
17717
18071
  resetTable();
17718
- prevEntry = null;
17719
18072
  code = readBits(codeSize);
17720
18073
  if (code === EOD) break;
17721
- entry = table[code];
17722
- if (entry) {
17723
- for (const b of entry) output.push(b);
17724
- prevEntry = entry;
17725
- }
18074
+ writeEntry(code);
18075
+ prevCode = code;
17726
18076
  continue;
17727
18077
  }
17728
- if (code < nextCode && table[code]) {
17729
- entry = table[code];
17730
- for (const b of entry) output.push(b);
17731
- if (prevEntry) {
17732
- const newEntry = new Uint8Array(prevEntry.length + 1);
17733
- newEntry.set(prevEntry);
17734
- newEntry[prevEntry.length] = entry[0];
17735
- table[nextCode] = newEntry;
17736
- nextCode++;
17737
- }
18078
+ if (code < nextCode) {
18079
+ writeEntry(code);
18080
+ addEntry(prevCode, tableBuf[tableOff[code]]);
17738
18081
  } else {
17739
- if (!prevEntry) throw new Error("LZWDecode: invalid code sequence");
17740
- const newEntry = new Uint8Array(prevEntry.length + 1);
17741
- newEntry.set(prevEntry);
17742
- newEntry[prevEntry.length] = prevEntry[0];
17743
- for (const b of newEntry) output.push(b);
17744
- table[nextCode] = newEntry;
17745
- nextCode++;
17746
- entry = newEntry;
17747
- }
17748
- prevEntry = entry;
18082
+ addEntry(prevCode, tableBuf[tableOff[prevCode]]);
18083
+ writeEntry(nextCode - 1);
18084
+ }
18085
+ prevCode = code;
17749
18086
  const threshold = (1 << codeSize) - earlyChange;
17750
18087
  if (nextCode >= threshold && codeSize < 12) codeSize++;
17751
18088
  }
17752
- return new Uint8Array(output);
18089
+ return out.subarray(0, outPos);
17753
18090
  }
17754
18091
  /**
17755
18092
  * Decode a RunLengthDecode stream.
@@ -17881,7 +18218,6 @@ function embedPageAsFormXObject(page, sourceRegistry, targetRegistry, xObjectNam
17881
18218
  const pageWidth = page.width;
17882
18219
  const pageHeight = page.height;
17883
18220
  const contentChunks = [];
17884
- new TextDecoder();
17885
18221
  const originalRefs = page.getOriginalContentRefs();
17886
18222
  for (const ref of originalRefs) {
17887
18223
  const obj = sourceRegistry.resolve(ref);
@@ -18399,7 +18735,7 @@ var PdfDocument = class PdfDocument {
18399
18735
  async embedTrueTypeFont(fontData, options) {
18400
18736
  const embeddedFont = await embedFont(fontData);
18401
18737
  const metrics = embeddedFont.metrics;
18402
- const psName = options?.customName || metrics.postScriptName || metrics.familyName || "CustomFont";
18738
+ const psName = options?.customName ?? metrics.postScriptName ?? metrics.familyName ?? "CustomFont";
18403
18739
  const existing = this.embeddedFonts.get(`__ttf__${psName}`);
18404
18740
  if (existing) return existing;
18405
18741
  this.fontCounter++;
@@ -18514,7 +18850,7 @@ var PdfDocument = class PdfDocument {
18514
18850
  async embedCffFont(fontData, options) {
18515
18851
  const embeddedFont = await embedFont(fontData);
18516
18852
  const metrics = embeddedFont.metrics;
18517
- const psName = options?.customName || metrics.postScriptName || metrics.familyName || "CFFFont";
18853
+ const psName = options?.customName ?? metrics.postScriptName ?? metrics.familyName ?? "CFFFont";
18518
18854
  const existing = this.embeddedFonts.get(`__cff__${psName}`);
18519
18855
  if (existing) return existing;
18520
18856
  this.fontCounter++;
@@ -18746,6 +19082,30 @@ var PdfDocument = class PdfDocument {
18746
19082
  return imageRef;
18747
19083
  }
18748
19084
  /**
19085
+ * Embed an image, auto-detecting the format from file headers.
19086
+ *
19087
+ * Inspects the first bytes to determine whether the data is PNG or JPEG,
19088
+ * then delegates to {@link embedPng} or {@link embedJpeg} accordingly.
19089
+ *
19090
+ * @param imageData Raw image file bytes (PNG or JPEG).
19091
+ * @returns An {@link ImageRef} to pass to `page.drawImage()`.
19092
+ * @throws If the image format cannot be detected.
19093
+ *
19094
+ * @example
19095
+ * ```ts
19096
+ * const bytes = new Uint8Array(await readFile('photo.jpg'));
19097
+ * const image = await pdf.embedImage(bytes);
19098
+ * page.drawImage(image, { x: 50, y: 400, width: 200, height: 150 });
19099
+ * ```
19100
+ */
19101
+ async embedImage(imageData) {
19102
+ const data = imageData instanceof ArrayBuffer ? new Uint8Array(imageData) : imageData;
19103
+ if (data.length < 4) throw new Error("Image data too short to detect format");
19104
+ if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) return this.embedPng(data);
19105
+ if (data[0] === 255 && data[1] === 216 && data[2] === 255) return this.embedJpeg(data);
19106
+ 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(" ")}.`);
19107
+ }
19108
+ /**
18749
19109
  * Embed pages from another PDF as Form XObjects.
18750
19110
  *
18751
19111
  * Each embedded page is turned into a self-contained Form XObject that
@@ -18804,7 +19164,7 @@ var PdfDocument = class PdfDocument {
18804
19164
  * @param pages Array of PdfPage instances to embed.
18805
19165
  * @returns Array of {@link EmbeddedPdfPage} handles, one per input page.
18806
19166
  */
18807
- async embedPages(pages) {
19167
+ embedPages(pages) {
18808
19168
  return pages.map((page) => this.embedPage(page));
18809
19169
  }
18810
19170
  /** Set the document title. */
@@ -20336,8 +20696,8 @@ function compressStream(stream, level) {
20336
20696
  * @returns The incremental save result.
20337
20697
  */
20338
20698
  async function saveDocumentIncremental(originalBytes, doc, options) {
20339
- const { buildDocumentStructure } = await import("./pdfCatalog-CTfeeqtF.mjs").then((n) => n.o);
20340
- const { PdfPage: _PdfPage } = await import("./pdfPage-B7vA518n.mjs").then((n) => n.r);
20699
+ const { buildDocumentStructure } = await import("./pdfCatalog-BB2Wnmud.mjs").then((n) => n.o);
20700
+ const { PdfPage: _PdfPage } = await import("./pdfPage-N1K2U3jI.mjs").then((n) => n.r);
20341
20701
  const registry = doc.getRegistry();
20342
20702
  const structure = buildDocumentStructure(doc.getInternalPages().map((p) => p.finalize()), {
20343
20703
  producer: doc.getProducer(),
@@ -21646,6 +22006,261 @@ var PdfRedactAnnotation = class PdfRedactAnnotation extends PdfAnnotation {
21646
22006
  }
21647
22007
  };
21648
22008
 
22009
+ //#endregion
22010
+ //#region src/annotation/types/popupAnnotation.ts
22011
+ /**
22012
+ * @module annotation/types/popupAnnotation
22013
+ *
22014
+ * Popup annotation — a floating window that displays the text of its
22015
+ * parent annotation (typically a text/sticky note annotation).
22016
+ *
22017
+ * Popup annotations have no appearance of their own; the PDF viewer
22018
+ * renders them as a resizable window near the parent annotation.
22019
+ *
22020
+ * Reference: PDF 1.7 spec, Section 12.5.6.14 (Popup Annotations).
22021
+ */
22022
+ /**
22023
+ * A popup annotation (subtype /Popup).
22024
+ *
22025
+ * Displays a floating window containing the text of its parent
22026
+ * annotation. The parent annotation references this popup via its
22027
+ * `/Popup` entry, and this popup references its parent via `/Parent`.
22028
+ */
22029
+ var PdfPopupAnnotation = class PdfPopupAnnotation extends PdfAnnotation {
22030
+ constructor(dict) {
22031
+ super("Popup", dict);
22032
+ }
22033
+ /**
22034
+ * Create a new popup annotation.
22035
+ *
22036
+ * @param options.open Whether the popup is initially open. Default: false.
22037
+ * @param options.parent Reference to the parent annotation (set after registration).
22038
+ */
22039
+ static create(options) {
22040
+ const annot = new PdfPopupAnnotation(buildAnnotationDict("Popup", options));
22041
+ if (options.open !== void 0) annot.setOpen(options.open);
22042
+ return annot;
22043
+ }
22044
+ /**
22045
+ * Create a PdfPopupAnnotation from an existing dictionary.
22046
+ */
22047
+ static fromDict(dict, _resolver) {
22048
+ return new PdfPopupAnnotation(dict);
22049
+ }
22050
+ /** Whether the popup is initially open. */
22051
+ isOpen() {
22052
+ const obj = this.dict.get("/Open");
22053
+ if (obj && obj.kind === "bool") return obj.value;
22054
+ return false;
22055
+ }
22056
+ /** Set the initial open state. */
22057
+ setOpen(open) {
22058
+ this.dict.set("/Open", PdfBool.of(open));
22059
+ }
22060
+ /**
22061
+ * Set the parent annotation reference.
22062
+ * The parent is the annotation whose text this popup displays.
22063
+ */
22064
+ setParent(parentRef) {
22065
+ this.dict.set("/Parent", parentRef);
22066
+ }
22067
+ /** Get the parent annotation reference, if set. */
22068
+ getParent() {
22069
+ const obj = this.dict.get("/Parent");
22070
+ if (obj && obj.kind === "ref") return obj;
22071
+ }
22072
+ };
22073
+
22074
+ //#endregion
22075
+ //#region src/annotation/types/caretAnnotation.ts
22076
+ /**
22077
+ * @module annotation/types/caretAnnotation
22078
+ *
22079
+ * Caret annotation — marks a text insertion point in the document.
22080
+ *
22081
+ * A caret annotation indicates where text should be inserted. It is
22082
+ * typically used in document review workflows to suggest additions.
22083
+ * The annotation renders as a caret (^) symbol at the specified
22084
+ * location.
22085
+ *
22086
+ * Reference: PDF 1.7 spec, Section 12.5.6.11 (Caret Annotations).
22087
+ */
22088
+ /**
22089
+ * A caret annotation (subtype /Caret).
22090
+ *
22091
+ * Marks an insertion point in the text. Used in review workflows
22092
+ * to indicate where new content should be added.
22093
+ */
22094
+ var PdfCaretAnnotation = class PdfCaretAnnotation extends PdfAnnotation {
22095
+ constructor(dict) {
22096
+ super("Caret", dict);
22097
+ }
22098
+ /**
22099
+ * Create a new caret annotation.
22100
+ *
22101
+ * @param options.symbol The caret symbol. Default: 'None'.
22102
+ * @param options.caretRect The inner rectangle (RD) that describes
22103
+ * the difference between the annotation rect and the actual caret
22104
+ * position. Format: [left, bottom, right, top] insets.
22105
+ */
22106
+ static create(options) {
22107
+ const annot = new PdfCaretAnnotation(buildAnnotationDict("Caret", options));
22108
+ if (options.symbol !== void 0) annot.setSymbol(options.symbol);
22109
+ if (options.caretRect !== void 0) annot.setCaretRect(options.caretRect);
22110
+ return annot;
22111
+ }
22112
+ /**
22113
+ * Create a PdfCaretAnnotation from an existing dictionary.
22114
+ */
22115
+ static fromDict(dict, _resolver) {
22116
+ return new PdfCaretAnnotation(dict);
22117
+ }
22118
+ /** Get the caret symbol. Defaults to 'None'. */
22119
+ getSymbol() {
22120
+ const obj = this.dict.get("/Sy");
22121
+ if (obj && obj.kind === "name") {
22122
+ if ((obj.value.startsWith("/") ? obj.value.slice(1) : obj.value) === "P") return "P";
22123
+ }
22124
+ return "None";
22125
+ }
22126
+ /** Set the caret symbol. */
22127
+ setSymbol(symbol) {
22128
+ this.dict.set("/Sy", PdfName.of(symbol));
22129
+ }
22130
+ /**
22131
+ * Get the inner rectangle differences (RD entry).
22132
+ * Returns [left, bottom, right, top] insets from the annotation rect.
22133
+ */
22134
+ getCaretRect() {
22135
+ const obj = this.dict.get("/RD");
22136
+ if (obj && obj.kind === "array" && obj.items.length === 4) return obj.items.map((item) => {
22137
+ if (item.kind === "number") return item.value;
22138
+ return 0;
22139
+ });
22140
+ }
22141
+ /** Set the inner rectangle differences (RD entry). */
22142
+ setCaretRect(rd) {
22143
+ this.dict.set("/RD", PdfArray.of(rd.map(PdfNumber.of)));
22144
+ }
22145
+ };
22146
+
22147
+ //#endregion
22148
+ //#region src/annotation/types/fileAttachmentAnnotation.ts
22149
+ /**
22150
+ * @module annotation/types/fileAttachmentAnnotation
22151
+ *
22152
+ * File attachment annotation — embeds a file as an inline annotation
22153
+ * on a page, displayed as a clickable icon.
22154
+ *
22155
+ * Unlike document-level attachments (via `attachFile()`), file attachment
22156
+ * annotations are positioned on a specific page and rendered as an icon
22157
+ * that users can click to open or save the embedded file.
22158
+ *
22159
+ * Reference: PDF 1.7 spec, Section 12.5.6.15 (File Attachment Annotations).
22160
+ */
22161
+ /**
22162
+ * A file attachment annotation (subtype /FileAttachment).
22163
+ *
22164
+ * Embeds a file directly in the annotation, rendered as a clickable
22165
+ * icon on the page. When the user clicks the icon, the PDF viewer
22166
+ * allows them to open or save the embedded file.
22167
+ */
22168
+ var PdfFileAttachmentAnnotation = class PdfFileAttachmentAnnotation extends PdfAnnotation {
22169
+ /** The raw file data to embed. */
22170
+ fileData;
22171
+ /** The filename to display. */
22172
+ fileName;
22173
+ /** Optional MIME type. */
22174
+ mimeType;
22175
+ /** Optional file description. */
22176
+ fileDescription;
22177
+ constructor(dict) {
22178
+ super("FileAttachment", dict);
22179
+ }
22180
+ /**
22181
+ * Create a new file attachment annotation.
22182
+ *
22183
+ * @param options.file The file data to embed.
22184
+ * @param options.fileName The filename (e.g., 'invoice.xml').
22185
+ * @param options.mimeType Optional MIME type (e.g., 'application/xml').
22186
+ * @param options.description Optional description of the file.
22187
+ * @param options.icon Icon to display. Default: 'GraphPushPin'.
22188
+ */
22189
+ static create(options) {
22190
+ const annot = new PdfFileAttachmentAnnotation(buildAnnotationDict("FileAttachment", options));
22191
+ annot.fileData = options.file;
22192
+ annot.fileName = options.fileName;
22193
+ annot.mimeType = options.mimeType;
22194
+ annot.fileDescription = options.description;
22195
+ if (options.icon !== void 0) annot.setIcon(options.icon);
22196
+ return annot;
22197
+ }
22198
+ /**
22199
+ * Create a PdfFileAttachmentAnnotation from an existing dictionary.
22200
+ */
22201
+ static fromDict(dict, _resolver) {
22202
+ return new PdfFileAttachmentAnnotation(dict);
22203
+ }
22204
+ /** Get the icon name. Defaults to 'GraphPushPin'. */
22205
+ getIcon() {
22206
+ const obj = this.dict.get("/Name");
22207
+ if (obj && obj.kind === "name") {
22208
+ const val = obj.value.startsWith("/") ? obj.value.slice(1) : obj.value;
22209
+ if ([
22210
+ "GraphPushPin",
22211
+ "PaperclipTag",
22212
+ "Paperclip",
22213
+ "Tag"
22214
+ ].includes(val)) return val;
22215
+ }
22216
+ return "GraphPushPin";
22217
+ }
22218
+ /** Set the icon name. */
22219
+ setIcon(icon) {
22220
+ this.dict.set("/Name", PdfName.of(icon));
22221
+ }
22222
+ /** Get the filename, if set. */
22223
+ getFileName() {
22224
+ const fs = this.dict.get("/FS");
22225
+ if (fs && fs.kind === "dict") {
22226
+ const uf = fs.get("/UF");
22227
+ if (uf && uf.kind === "string") return uf.value;
22228
+ const f = fs.get("/F");
22229
+ if (f && f.kind === "string") return f.value;
22230
+ }
22231
+ return this.fileName;
22232
+ }
22233
+ /**
22234
+ * Build the file specification dictionary and register the embedded
22235
+ * file stream. Call this before serializing the annotation.
22236
+ *
22237
+ * @param registry The document's object registry.
22238
+ * @returns The annotation dict with `/FS` referencing the file.
22239
+ */
22240
+ buildFileSpec(registry) {
22241
+ if (!this.fileData || !this.fileName) return this.dict;
22242
+ const efStreamDict = new PdfDict();
22243
+ efStreamDict.set("/Type", PdfName.of("EmbeddedFile"));
22244
+ if (this.mimeType) efStreamDict.set("/Subtype", PdfName.of(this.mimeType.replace("/", "#2F")));
22245
+ const params = new PdfDict();
22246
+ params.set("/Size", PdfNumber.of(this.fileData.length));
22247
+ efStreamDict.set("/Params", params);
22248
+ const efStream = new PdfStream(efStreamDict, this.fileData);
22249
+ const efRef = registry.register(efStream);
22250
+ const efDict = new PdfDict();
22251
+ efDict.set("/F", efRef);
22252
+ const fsDict = new PdfDict();
22253
+ fsDict.set("/Type", PdfName.of("Filespec"));
22254
+ fsDict.set("/F", PdfString.literal(this.fileName));
22255
+ fsDict.set("/UF", PdfString.literal(this.fileName));
22256
+ fsDict.set("/EF", efDict);
22257
+ if (this.fileDescription) fsDict.set("/Desc", PdfString.literal(this.fileDescription));
22258
+ const fsRef = registry.register(fsDict);
22259
+ this.dict.set("/FS", fsRef);
22260
+ return this.dict;
22261
+ }
22262
+ };
22263
+
21649
22264
  //#endregion
21650
22265
  //#region src/parser/textExtractor.ts
21651
22266
  /**
@@ -22444,6 +23059,19 @@ var TokenType = /* @__PURE__ */ function(TokenType) {
22444
23059
  return TokenType;
22445
23060
  }(TokenType || {});
22446
23061
  /**
23062
+ * `hexVal[b]` is the numeric value (0-15) of a hex character, or -1 if
23063
+ * the byte is not a valid hex digit.
23064
+ */
23065
+ const hexVal = /* @__PURE__ */ (() => {
23066
+ const t = new Int8Array(256).fill(-1);
23067
+ for (let i = 0; i <= 9; i++) t[48 + i] = i;
23068
+ for (let i = 0; i < 6; i++) {
23069
+ t[65 + i] = 10 + i;
23070
+ t[97 + i] = 10 + i;
23071
+ }
23072
+ return t;
23073
+ })();
23074
+ /**
22447
23075
  * Combined lexer + parser for PDF content streams.
22448
23076
  *
22449
23077
  * Content streams are simpler than full PDF object syntax — there are no
@@ -22562,20 +23190,22 @@ var ContentStreamLexer = class {
22562
23190
  }
22563
23191
  const dataStart = this.pos;
22564
23192
  let dataEnd = this.pos;
22565
- while (this.pos < this.data.length) {
22566
- if (this.isWhitespace(this.data[this.pos])) {
22567
- const wsPos = this.pos;
22568
- let probe = wsPos + 1;
22569
- if (probe + 1 < this.data.length && this.data[probe] === 69 && this.data[probe + 1] === 73) {
22570
- const afterEI = probe + 2;
22571
- if (afterEI >= this.data.length || this.isWhitespace(this.data[afterEI])) {
22572
- dataEnd = wsPos;
22573
- this.pos = afterEI;
22574
- break;
22575
- }
23193
+ let searchFrom = this.pos;
23194
+ while (searchFrom < this.data.length) {
23195
+ const eIdx = this.data.indexOf(69, searchFrom);
23196
+ if (eIdx === -1 || eIdx + 1 >= this.data.length) {
23197
+ this.pos = this.data.length;
23198
+ break;
23199
+ }
23200
+ if (eIdx > dataStart && this.isWhitespace(this.data[eIdx - 1]) && this.data[eIdx + 1] === 73) {
23201
+ const afterEI = eIdx + 2;
23202
+ if (afterEI >= this.data.length || this.isWhitespace(this.data[afterEI])) {
23203
+ dataEnd = eIdx - 1;
23204
+ this.pos = afterEI;
23205
+ break;
22576
23206
  }
22577
23207
  }
22578
- this.pos++;
23208
+ searchFrom = eIdx + 1;
22579
23209
  }
22580
23210
  return {
22581
23211
  dict,
@@ -22643,7 +23273,7 @@ var ContentStreamLexer = class {
22643
23273
  */
22644
23274
  readLiteralString() {
22645
23275
  this.pos++;
22646
- let result = "";
23276
+ const parts = [];
22647
23277
  let depth = 1;
22648
23278
  while (this.pos < this.data.length && depth > 0) {
22649
23279
  const ch = this.data[this.pos];
@@ -22653,35 +23283,35 @@ var ContentStreamLexer = class {
22653
23283
  const esc = this.data[this.pos];
22654
23284
  switch (esc) {
22655
23285
  case 110:
22656
- result += "\n";
23286
+ parts.push("\n");
22657
23287
  this.pos++;
22658
23288
  break;
22659
23289
  case 114:
22660
- result += "\r";
23290
+ parts.push("\r");
22661
23291
  this.pos++;
22662
23292
  break;
22663
23293
  case 116:
22664
- result += " ";
23294
+ parts.push(" ");
22665
23295
  this.pos++;
22666
23296
  break;
22667
23297
  case 98:
22668
- result += "\b";
23298
+ parts.push("\b");
22669
23299
  this.pos++;
22670
23300
  break;
22671
23301
  case 102:
22672
- result += "\f";
23302
+ parts.push("\f");
22673
23303
  this.pos++;
22674
23304
  break;
22675
23305
  case 40:
22676
- result += "(";
23306
+ parts.push("(");
22677
23307
  this.pos++;
22678
23308
  break;
22679
23309
  case 41:
22680
- result += ")";
23310
+ parts.push(")");
22681
23311
  this.pos++;
22682
23312
  break;
22683
23313
  case 92:
22684
- result += "\\";
23314
+ parts.push("\\");
22685
23315
  this.pos++;
22686
23316
  break;
22687
23317
  case 10:
@@ -22709,29 +23339,29 @@ var ContentStreamLexer = class {
22709
23339
  }
22710
23340
  }
22711
23341
  }
22712
- result += String.fromCharCode(octal & 255);
23342
+ parts.push(String.fromCharCode(octal & 255));
22713
23343
  } else {
22714
- result += String.fromCharCode(esc);
23344
+ parts.push(String.fromCharCode(esc));
22715
23345
  this.pos++;
22716
23346
  }
22717
23347
  break;
22718
23348
  }
22719
23349
  } else if (ch === 40) {
22720
23350
  depth++;
22721
- result += "(";
23351
+ parts.push("(");
22722
23352
  this.pos++;
22723
23353
  } else if (ch === 41) {
22724
23354
  depth--;
22725
- if (depth > 0) result += ")";
23355
+ if (depth > 0) parts.push(")");
22726
23356
  this.pos++;
22727
23357
  } else {
22728
- result += String.fromCharCode(ch);
23358
+ parts.push(String.fromCharCode(ch));
22729
23359
  this.pos++;
22730
23360
  }
22731
23361
  }
22732
23362
  return {
22733
23363
  type: TokenType.String,
22734
- value: result
23364
+ value: parts.join("")
22735
23365
  };
22736
23366
  }
22737
23367
  /**
@@ -22739,7 +23369,8 @@ var ContentStreamLexer = class {
22739
23369
  */
22740
23370
  readHexString() {
22741
23371
  this.pos++;
22742
- let hex = "";
23372
+ const bytes = [];
23373
+ let hi = -1;
22743
23374
  while (this.pos < this.data.length) {
22744
23375
  const ch = this.data[this.pos];
22745
23376
  if (ch === 62) {
@@ -22750,18 +23381,22 @@ var ContentStreamLexer = class {
22750
23381
  this.pos++;
22751
23382
  continue;
22752
23383
  }
22753
- hex += String.fromCharCode(ch);
23384
+ const v = hexVal[ch];
23385
+ if (v === -1) {
23386
+ this.pos++;
23387
+ continue;
23388
+ }
23389
+ if (hi === -1) hi = v;
23390
+ else {
23391
+ bytes.push(hi << 4 | v);
23392
+ hi = -1;
23393
+ }
22754
23394
  this.pos++;
22755
23395
  }
22756
- if (hex.length % 2 !== 0) hex += "0";
22757
- let result = "";
22758
- for (let i = 0; i < hex.length; i += 2) {
22759
- const byte = parseInt(hex.substring(i, i + 2), 16);
22760
- if (!isNaN(byte)) result += String.fromCharCode(byte);
22761
- }
23396
+ if (hi !== -1) bytes.push(hi << 4);
22762
23397
  return {
22763
23398
  type: TokenType.HexString,
22764
- value: result
23399
+ value: String.fromCharCode.apply(null, bytes)
22765
23400
  };
22766
23401
  }
22767
23402
  /**
@@ -22769,27 +23404,25 @@ var ContentStreamLexer = class {
22769
23404
  */
22770
23405
  readName() {
22771
23406
  this.pos++;
22772
- let name = "/";
23407
+ const parts = ["/"];
22773
23408
  while (this.pos < this.data.length) {
22774
23409
  const ch = this.data[this.pos];
22775
23410
  if (this.isWhitespace(ch) || this.isDelimiter(ch)) break;
22776
23411
  if (ch === 35 && this.pos + 2 < this.data.length) {
22777
- const hi = this.data[this.pos + 1];
22778
- const lo = this.data[this.pos + 2];
22779
- const hex = String.fromCharCode(hi) + String.fromCharCode(lo);
22780
- const code = parseInt(hex, 16);
22781
- if (!isNaN(code)) {
22782
- name += String.fromCharCode(code);
23412
+ const hi = hexVal[this.data[this.pos + 1]];
23413
+ const lo = hexVal[this.data[this.pos + 2]];
23414
+ if (hi !== -1 && lo !== -1) {
23415
+ parts.push(String.fromCharCode(hi << 4 | lo));
22783
23416
  this.pos += 3;
22784
23417
  continue;
22785
23418
  }
22786
23419
  }
22787
- name += String.fromCharCode(ch);
23420
+ parts.push(String.fromCharCode(ch));
22788
23421
  this.pos++;
22789
23422
  }
22790
23423
  return {
22791
23424
  type: TokenType.Name,
22792
- value: PdfName.of(name)
23425
+ value: PdfName.of(parts.join(""))
22793
23426
  };
22794
23427
  }
22795
23428
  /**
@@ -22883,9 +23516,7 @@ var ContentStreamLexer = class {
22883
23516
  * Decode a slice of the data as ASCII text.
22884
23517
  */
22885
23518
  decodeAscii(start, end) {
22886
- let s = "";
22887
- for (let i = start; i < end; i++) s += String.fromCharCode(this.data[i]);
22888
- return s;
23519
+ return String.fromCharCode.apply(null, this.data.subarray(start, end));
22889
23520
  }
22890
23521
  };
22891
23522
 
@@ -23713,6 +24344,339 @@ function addTrailerId(data) {
23713
24344
  return concatBytes(...parts);
23714
24345
  }
23715
24346
 
24347
+ //#endregion
24348
+ //#region src/assets/image/imageOptimize.ts
24349
+ /**
24350
+ * Downscale an image to fit within the specified dimensions.
24351
+ *
24352
+ * If the image is already smaller than the target dimensions, it is
24353
+ * returned unchanged.
24354
+ *
24355
+ * @param image - The raw image pixel data.
24356
+ * @param options - Downscaling options (target dimensions, algorithm).
24357
+ * @returns The downscaled image, or the original if no scaling needed.
24358
+ *
24359
+ * @example
24360
+ * ```ts
24361
+ * const result = downscaleImage(rawImage, {
24362
+ * maxWidth: 1024,
24363
+ * maxHeight: 768,
24364
+ * algorithm: 'bilinear',
24365
+ * });
24366
+ * ```
24367
+ */
24368
+ function downscaleImage(image, options = {}) {
24369
+ const target = computeTargetDimensions(image.width, image.height, options);
24370
+ if (target.width >= image.width && target.height >= image.height) return image;
24371
+ switch (options.algorithm ?? "bilinear") {
24372
+ case "nearest": return resampleNearest(image, target.width, target.height);
24373
+ case "bilinear": return resampleBilinear(image, target.width, target.height);
24374
+ case "lanczos": return resampleLanczos(image, target.width, target.height);
24375
+ default: return resampleBilinear(image, target.width, target.height);
24376
+ }
24377
+ }
24378
+ /**
24379
+ * Recompress raw image pixel data using the specified format.
24380
+ *
24381
+ * @param image - The raw image pixel data.
24382
+ * @param options - Recompression options (format, quality).
24383
+ * @returns The compressed image data.
24384
+ *
24385
+ * @example
24386
+ * ```ts
24387
+ * const result = await recompressImage(rawImage, {
24388
+ * format: 'deflate',
24389
+ * compressionLevel: 9,
24390
+ * });
24391
+ * ```
24392
+ */
24393
+ async function recompressImage(image, options = {}) {
24394
+ switch (options.format ?? "deflate") {
24395
+ case "deflate": return recompressDeflate(image, options.compressionLevel ?? 6);
24396
+ case "jpeg": return recompressJpeg(image, options.quality ?? 85);
24397
+ default: return {
24398
+ data: image.pixels,
24399
+ width: image.width,
24400
+ height: image.height,
24401
+ channels: image.channels,
24402
+ format: "raw",
24403
+ wasOptimized: false
24404
+ };
24405
+ }
24406
+ }
24407
+ /**
24408
+ * Run the full image optimization pipeline: downscale then recompress.
24409
+ *
24410
+ * @param image - The raw image pixel data.
24411
+ * @param options - Combined optimization options.
24412
+ * @returns The optimized result.
24413
+ */
24414
+ async function optimizeImage(image, options = {}) {
24415
+ if (options.skipBelowBytes && image.pixels.length < options.skipBelowBytes) return {
24416
+ data: image.pixels,
24417
+ width: image.width,
24418
+ height: image.height,
24419
+ channels: image.channels,
24420
+ format: "raw",
24421
+ wasOptimized: false
24422
+ };
24423
+ return recompressImage(downscaleImage(image, options), options);
24424
+ }
24425
+ /**
24426
+ * Compute target dimensions from options, preserving aspect ratio.
24427
+ * @internal
24428
+ */
24429
+ function computeTargetDimensions(srcWidth, srcHeight, options) {
24430
+ let targetWidth = srcWidth;
24431
+ let targetHeight = srcHeight;
24432
+ if (options.targetDpi && options.printWidth && options.printHeight) {
24433
+ const printWidthInches = options.printWidth / 72;
24434
+ const printHeightInches = options.printHeight / 72;
24435
+ const dpiWidth = Math.round(printWidthInches * options.targetDpi);
24436
+ const dpiHeight = Math.round(printHeightInches * options.targetDpi);
24437
+ targetWidth = Math.min(targetWidth, dpiWidth);
24438
+ targetHeight = Math.min(targetHeight, dpiHeight);
24439
+ }
24440
+ if (options.maxWidth && targetWidth > options.maxWidth) {
24441
+ const scale = options.maxWidth / targetWidth;
24442
+ targetWidth = options.maxWidth;
24443
+ targetHeight = Math.round(targetHeight * scale);
24444
+ }
24445
+ if (options.maxHeight && targetHeight > options.maxHeight) {
24446
+ const scale = options.maxHeight / targetHeight;
24447
+ targetHeight = options.maxHeight;
24448
+ targetWidth = Math.round(targetWidth * scale);
24449
+ }
24450
+ targetWidth = Math.max(1, targetWidth);
24451
+ targetHeight = Math.max(1, targetHeight);
24452
+ return {
24453
+ width: targetWidth,
24454
+ height: targetHeight
24455
+ };
24456
+ }
24457
+ /**
24458
+ * Nearest-neighbor resampling.
24459
+ * @internal
24460
+ */
24461
+ function resampleNearest(src, dstWidth, dstHeight) {
24462
+ const channels = src.channels;
24463
+ const dst = new Uint8Array(dstWidth * dstHeight * channels);
24464
+ const xRatio = src.width / dstWidth;
24465
+ const yRatio = src.height / dstHeight;
24466
+ for (let y = 0; y < dstHeight; y++) {
24467
+ const srcY = Math.min(Math.floor(y * yRatio), src.height - 1);
24468
+ for (let x = 0; x < dstWidth; x++) {
24469
+ const srcX = Math.min(Math.floor(x * xRatio), src.width - 1);
24470
+ const srcIdx = (srcY * src.width + srcX) * channels;
24471
+ const dstIdx = (y * dstWidth + x) * channels;
24472
+ for (let c = 0; c < channels; c++) dst[dstIdx + c] = src.pixels[srcIdx + c];
24473
+ }
24474
+ }
24475
+ return {
24476
+ pixels: dst,
24477
+ width: dstWidth,
24478
+ height: dstHeight,
24479
+ channels,
24480
+ bitsPerChannel: src.bitsPerChannel
24481
+ };
24482
+ }
24483
+ /**
24484
+ * Bilinear interpolation resampling.
24485
+ * @internal
24486
+ */
24487
+ function resampleBilinear(src, dstWidth, dstHeight) {
24488
+ const channels = src.channels;
24489
+ const dst = new Uint8Array(dstWidth * dstHeight * channels);
24490
+ const xRatio = (src.width - 1) / Math.max(1, dstWidth - 1);
24491
+ const yRatio = (src.height - 1) / Math.max(1, dstHeight - 1);
24492
+ for (let y = 0; y < dstHeight; y++) {
24493
+ const srcYf = y * yRatio;
24494
+ const srcY0 = Math.floor(srcYf);
24495
+ const srcY1 = Math.min(srcY0 + 1, src.height - 1);
24496
+ const yFrac = srcYf - srcY0;
24497
+ for (let x = 0; x < dstWidth; x++) {
24498
+ const srcXf = x * xRatio;
24499
+ const srcX0 = Math.floor(srcXf);
24500
+ const srcX1 = Math.min(srcX0 + 1, src.width - 1);
24501
+ const xFrac = srcXf - srcX0;
24502
+ const dstIdx = (y * dstWidth + x) * channels;
24503
+ for (let c = 0; c < channels; c++) {
24504
+ const topLeft = src.pixels[(srcY0 * src.width + srcX0) * channels + c];
24505
+ const topRight = src.pixels[(srcY0 * src.width + srcX1) * channels + c];
24506
+ const bottomLeft = src.pixels[(srcY1 * src.width + srcX0) * channels + c];
24507
+ const bottomRight = src.pixels[(srcY1 * src.width + srcX1) * channels + c];
24508
+ const top = topLeft + (topRight - topLeft) * xFrac;
24509
+ const value = top + (bottomLeft + (bottomRight - bottomLeft) * xFrac - top) * yFrac;
24510
+ dst[dstIdx + c] = Math.round(Math.max(0, Math.min(255, value)));
24511
+ }
24512
+ }
24513
+ }
24514
+ return {
24515
+ pixels: dst,
24516
+ width: dstWidth,
24517
+ height: dstHeight,
24518
+ channels,
24519
+ bitsPerChannel: src.bitsPerChannel
24520
+ };
24521
+ }
24522
+ /**
24523
+ * Lanczos kernel function.
24524
+ *
24525
+ * Computes the Lanczos windowed sinc value for a given distance `x`
24526
+ * and window size `a`. For Lanczos-3, `a = 3`.
24527
+ *
24528
+ * @param x - Distance from the center sample.
24529
+ * @param a - Window radius (3 for Lanczos-3).
24530
+ * @returns The kernel weight.
24531
+ * @internal
24532
+ */
24533
+ function lanczos(x, a = 3) {
24534
+ if (x === 0) return 1;
24535
+ if (Math.abs(x) >= a) return 0;
24536
+ const pix = Math.PI * x;
24537
+ return Math.sin(pix) / pix * (Math.sin(pix / a) / (pix / a));
24538
+ }
24539
+ /**
24540
+ * Lanczos-3 resampling.
24541
+ *
24542
+ * Uses a 6-tap (a=3) windowed sinc filter in both dimensions for
24543
+ * high-quality downscaling. This is the best quality option but
24544
+ * also the slowest.
24545
+ *
24546
+ * @internal
24547
+ */
24548
+ function resampleLanczos(src, dstWidth, dstHeight) {
24549
+ const channels = src.channels;
24550
+ const a = 3;
24551
+ const dst = new Uint8Array(dstWidth * dstHeight * channels);
24552
+ const xRatio = src.width / dstWidth;
24553
+ const yRatio = src.height / dstHeight;
24554
+ for (let y = 0; y < dstHeight; y++) {
24555
+ const srcYf = (y + .5) * yRatio - .5;
24556
+ for (let x = 0; x < dstWidth; x++) {
24557
+ const srcXf = (x + .5) * xRatio - .5;
24558
+ const dstIdx = (y * dstWidth + x) * channels;
24559
+ const sum = new Float64Array(channels);
24560
+ let weightSum = 0;
24561
+ const yStart = Math.floor(srcYf) - a + 1;
24562
+ const yEnd = Math.floor(srcYf) + a;
24563
+ const xStart = Math.floor(srcXf) - a + 1;
24564
+ const xEnd = Math.floor(srcXf) + a;
24565
+ for (let sy = yStart; sy <= yEnd; sy++) {
24566
+ const wy = lanczos(srcYf - sy, a);
24567
+ if (wy === 0) continue;
24568
+ const clampedY = Math.max(0, Math.min(src.height - 1, sy));
24569
+ for (let sx = xStart; sx <= xEnd; sx++) {
24570
+ const wx = lanczos(srcXf - sx, a);
24571
+ if (wx === 0) continue;
24572
+ const w = wx * wy;
24573
+ const clampedX = Math.max(0, Math.min(src.width - 1, sx));
24574
+ const srcIdx = (clampedY * src.width + clampedX) * channels;
24575
+ for (let c = 0; c < channels; c++) sum[c] = (sum[c] ?? 0) + src.pixels[srcIdx + c] * w;
24576
+ weightSum += w;
24577
+ }
24578
+ }
24579
+ if (weightSum > 0) for (let c = 0; c < channels; c++) dst[dstIdx + c] = Math.round(Math.max(0, Math.min(255, sum[c] / weightSum)));
24580
+ }
24581
+ }
24582
+ return {
24583
+ pixels: dst,
24584
+ width: dstWidth,
24585
+ height: dstHeight,
24586
+ channels,
24587
+ bitsPerChannel: src.bitsPerChannel
24588
+ };
24589
+ }
24590
+ /**
24591
+ * Recompress image data using deflate (for PDF FlateDecode).
24592
+ * @internal
24593
+ */
24594
+ async function recompressDeflate(image, level) {
24595
+ if (typeof CompressionStream !== "undefined") {
24596
+ const cs = new CompressionStream("deflate");
24597
+ const writer = cs.writable.getWriter();
24598
+ const reader = cs.readable.getReader();
24599
+ const chunks = [];
24600
+ writer.write(new Uint8Array(image.pixels)).catch(() => {});
24601
+ writer.close().catch(() => {});
24602
+ while (true) {
24603
+ const { done, value } = await reader.read();
24604
+ if (done) break;
24605
+ chunks.push(value);
24606
+ }
24607
+ const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
24608
+ const result = new Uint8Array(totalLength);
24609
+ let pos = 0;
24610
+ for (const chunk of chunks) {
24611
+ result.set(chunk, pos);
24612
+ pos += chunk.length;
24613
+ }
24614
+ return {
24615
+ data: result,
24616
+ width: image.width,
24617
+ height: image.height,
24618
+ channels: image.channels,
24619
+ format: "deflate",
24620
+ wasOptimized: true
24621
+ };
24622
+ }
24623
+ try {
24624
+ const { deflateSync } = await import("fflate");
24625
+ return {
24626
+ data: deflateSync(image.pixels, { level }),
24627
+ width: image.width,
24628
+ height: image.height,
24629
+ channels: image.channels,
24630
+ format: "deflate",
24631
+ wasOptimized: true
24632
+ };
24633
+ } catch {
24634
+ return {
24635
+ data: image.pixels,
24636
+ width: image.width,
24637
+ height: image.height,
24638
+ channels: image.channels,
24639
+ format: "raw",
24640
+ wasOptimized: false
24641
+ };
24642
+ }
24643
+ }
24644
+ /**
24645
+ * Recompress image data as JPEG.
24646
+ * @internal
24647
+ */
24648
+ /**
24649
+ * Recompress image data as JPEG.
24650
+ *
24651
+ * JPEG encoding in pure JS is complex (DCT, Huffman coding, quantization).
24652
+ * A full implementation requires either:
24653
+ *
24654
+ * 1. **WASM-based encoder** (preferred) -- compile libjpeg-turbo or mozjpeg
24655
+ * to WASM, feed raw pixels, get JPEG bytes back.
24656
+ * 2. **Canvas API** (browser-only fallback) -- use `OffscreenCanvas` with
24657
+ * `convertToBlob({ type: 'image/jpeg', quality })`.
24658
+ * 3. **Pure JS encoder** (last resort) -- very slow but works everywhere.
24659
+ *
24660
+ * Until a WASM encoder module is bundled, this function returns the input
24661
+ * data unchanged. The caller receives `format: 'raw'` and
24662
+ * `wasOptimized: false` to indicate that JPEG encoding was not applied.
24663
+ *
24664
+ * @param image - The raw image pixel data.
24665
+ * @param quality - JPEG quality 1-100 (reserved for future use).
24666
+ * @returns The input data as-is, marked as un-optimized.
24667
+ * @internal
24668
+ */
24669
+ async function recompressJpeg(image, quality) {
24670
+ return {
24671
+ data: image.pixels,
24672
+ width: image.width,
24673
+ height: image.height,
24674
+ channels: image.channels,
24675
+ format: "raw",
24676
+ wasOptimized: false
24677
+ };
24678
+ }
24679
+
23716
24680
  //#endregion
23717
24681
  //#region src/errors.ts
23718
24682
  /**
@@ -23895,5 +24859,5 @@ async function initWasm(options) {
23895
24859
  }
23896
24860
 
23897
24861
  //#endregion
23898
- 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 };
24862
+ 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, computeSignatureHash, concatMatrix, concatMatrix as concatTransformationMatrix, copyPages, createAnnotation, createMarkedContentScope, createPdf, createXmpStream, cropPage, curveToFinal, curveToInitial, curveTo as curveToOp, decodePermissions, decodeStream, degrees, degreesToRadians, downscaleImage, 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, 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 };
23899
24863
  //# sourceMappingURL=index.mjs.map