@libpdf/core 0.2.7 → 0.2.9

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,4 +1,5 @@
1
1
  import { t as __exportAll } from "./chunk-15K8U1wQ.mjs";
2
+ import { LRUCache } from "lru-cache";
2
3
  import pako, { deflate, inflate } from "pako";
3
4
  import { cbc, ecb } from "@noble/ciphers/aes.js";
4
5
  import { randomBytes } from "@noble/ciphers/utils.js";
@@ -10,7 +11,7 @@ import { createCMSECDSASignature } from "pkijs";
10
11
  import { base64 } from "@scure/base";
11
12
 
12
13
  //#region package.json
13
- var version = "0.2.7";
14
+ var version = "0.2.9";
14
15
 
15
16
  //#endregion
16
17
  //#region src/objects/pdf-array.ts
@@ -112,6 +113,80 @@ var PdfArray = class PdfArray {
112
113
  }
113
114
  };
114
115
 
116
+ //#endregion
117
+ //#region src/helpers/buffer.ts
118
+ /**
119
+ * Buffer utilities for working with ArrayBuffer and Uint8Array.
120
+ */
121
+ /**
122
+ * Ensure we have a proper ArrayBuffer (not SharedArrayBuffer or slice).
123
+ *
124
+ * Web Crypto APIs require a true ArrayBuffer, not a view into one.
125
+ *
126
+ * @param data - Uint8Array to convert
127
+ * @returns ArrayBuffer containing the data
128
+ */
129
+ function toArrayBuffer(data) {
130
+ if (data.buffer instanceof ArrayBuffer && data.byteOffset === 0 && data.byteLength === data.buffer.byteLength) return data.buffer;
131
+ return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
132
+ }
133
+ /**
134
+ * Concatenate multiple Uint8Arrays into a single Uint8Array.
135
+ *
136
+ * @param arrays - Arrays to concatenate
137
+ * @returns Single Uint8Array containing all data
138
+ */
139
+ function concatBytes(arrays) {
140
+ const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
141
+ const result = new Uint8Array(totalLength);
142
+ let offset = 0;
143
+ for (const arr of arrays) {
144
+ result.set(arr, offset);
145
+ offset += arr.length;
146
+ }
147
+ return result;
148
+ }
149
+ /** Pre-computed hex lookup: byte value → "XX" uppercase string. */
150
+ const HEX_TABLE = new Array(256);
151
+ for (let i = 0; i < 256; i++) HEX_TABLE[i] = i.toString(16).toUpperCase().padStart(2, "0");
152
+ /**
153
+ * Convert bytes to uppercase hex string.
154
+ *
155
+ * @param bytes - Raw bytes
156
+ * @returns Hex string (e.g., "48656C6C6F")
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * bytesToHex(new Uint8Array([72, 101, 108, 108, 111])) // "48656C6C6F"
161
+ * ```
162
+ */
163
+ function bytesToHex(bytes) {
164
+ let hex = "";
165
+ for (const byte of bytes) hex += HEX_TABLE[byte];
166
+ return hex;
167
+ }
168
+ /**
169
+ * Convert a hex string to bytes.
170
+ *
171
+ * Whitespace is ignored. Odd-length strings are padded with trailing 0.
172
+ *
173
+ * @param hex - Hex string (e.g., "48656C6C6F" or "48 65 6C 6C 6F")
174
+ * @returns Decoded bytes
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * hexToBytes("48656C6C6F") // Uint8Array([72, 101, 108, 108, 111])
179
+ * hexToBytes("ABC") // Uint8Array([171, 192]) - padded to "ABC0"
180
+ * ```
181
+ */
182
+ function hexToBytes(hex) {
183
+ const clean = hex.replace(/\s/g, "");
184
+ const padded = clean.length % 2 === 1 ? `${clean}0` : clean;
185
+ const bytes = new Uint8Array(padded.length / 2);
186
+ for (let i = 0; i < bytes.length; i++) bytes[i] = Number.parseInt(padded.slice(i * 2, i * 2 + 2), 16);
187
+ return bytes;
188
+ }
189
+
115
190
  //#endregion
116
191
  //#region src/helpers/chars.ts
117
192
  /**
@@ -221,83 +296,6 @@ function hexValue(byte) {
221
296
  */
222
297
  const SINGLE_BYTE_MASK = 255;
223
298
 
224
- //#endregion
225
- //#region src/helpers/lru-cache.ts
226
- /**
227
- * Simple LRU (Least Recently Used) cache implementation.
228
- *
229
- * Used for interning frequently-used PDF objects (PdfName, PdfRef)
230
- * while preventing unbounded memory growth.
231
- */
232
- /**
233
- * A bounded cache that evicts least-recently-used entries when full.
234
- *
235
- * @typeParam K - Key type
236
- * @typeParam V - Value type
237
- */
238
- var LRUCache = class {
239
- maxSize;
240
- cache = /* @__PURE__ */ new Map();
241
- /**
242
- * Create a new LRU cache.
243
- *
244
- * @param maxSize - Maximum number of entries to retain (default: 10000)
245
- */
246
- constructor(maxSize = 1e4) {
247
- this.maxSize = maxSize;
248
- }
249
- /**
250
- * Get a value from the cache, updating its recency.
251
- *
252
- * @returns The cached value, or undefined if not present
253
- */
254
- get(key$1) {
255
- const value = this.cache.get(key$1);
256
- if (value !== void 0) {
257
- this.cache.delete(key$1);
258
- this.cache.set(key$1, value);
259
- }
260
- return value;
261
- }
262
- /**
263
- * Check if a key exists in the cache (without updating recency).
264
- */
265
- has(key$1) {
266
- return this.cache.has(key$1);
267
- }
268
- /**
269
- * Add or update a value in the cache.
270
- *
271
- * If the cache is at capacity, the least-recently-used entry is evicted.
272
- */
273
- set(key$1, value) {
274
- if (this.cache.has(key$1)) this.cache.delete(key$1);
275
- else if (this.cache.size >= this.maxSize) {
276
- const oldestKey = this.cache.keys().next().value;
277
- if (oldestKey !== void 0) this.cache.delete(oldestKey);
278
- }
279
- this.cache.set(key$1, value);
280
- }
281
- /**
282
- * Remove a value from the cache.
283
- */
284
- delete(key$1) {
285
- return this.cache.delete(key$1);
286
- }
287
- /**
288
- * Clear all entries from the cache.
289
- */
290
- clear() {
291
- this.cache.clear();
292
- }
293
- /**
294
- * Get the current number of entries in the cache.
295
- */
296
- get size() {
297
- return this.cache.size;
298
- }
299
- };
300
-
301
299
  //#endregion
302
300
  //#region src/objects/pdf-name.ts
303
301
  const NAME_NEEDS_ESCAPE = new Set([
@@ -305,11 +303,20 @@ const NAME_NEEDS_ESCAPE = new Set([
305
303
  ...DELIMITERS,
306
304
  CHAR_HASH
307
305
  ]);
306
+ /** Module-level encoder — avoids constructing one per escapeName call. */
307
+ const textEncoder = new TextEncoder();
308
308
  /**
309
- * Default cache size for PdfName interning.
310
- * Can be overridden via PdfName.setCacheSize().
309
+ * Check whether a name is pure "safe" ASCII — every char is printable ASCII
310
+ * (33–126) and not in the escape set. If so, no escaping is needed and we
311
+ * can skip the TextEncoder entirely.
311
312
  */
312
- const DEFAULT_NAME_CACHE_SIZE = 1e4;
313
+ function isSimpleAsciiName(name) {
314
+ for (let i = 0; i < name.length; i++) {
315
+ const c = name.charCodeAt(i);
316
+ if (c < 33 || c > 126 || NAME_NEEDS_ESCAPE.has(c)) return false;
317
+ }
318
+ return true;
319
+ }
313
320
  /**
314
321
  * Escape a PDF name for serialization.
315
322
  *
@@ -319,13 +326,19 @@ const DEFAULT_NAME_CACHE_SIZE = 1e4;
319
326
  * - The # character itself
320
327
  */
321
328
  function escapeName$1(name) {
322
- const bytes = new TextEncoder().encode(name);
329
+ if (isSimpleAsciiName(name)) return name;
330
+ const bytes = textEncoder.encode(name);
323
331
  let result = "";
324
- for (const byte of bytes) if (byte < 33 || byte > 126 || NAME_NEEDS_ESCAPE.has(byte)) result += `#${byte.toString(16).toUpperCase().padStart(2, "0")}`;
332
+ for (const byte of bytes) if (byte < 33 || byte > 126 || NAME_NEEDS_ESCAPE.has(byte)) result += `#${HEX_TABLE[byte]}`;
325
333
  else result += String.fromCharCode(byte);
326
334
  return result;
327
335
  }
328
336
  /**
337
+ * Default cache size for PdfName interning.
338
+ * Can be overridden via PdfName.setCacheSize().
339
+ */
340
+ const DEFAULT_NAME_CACHE_SIZE = 1e4;
341
+ /**
329
342
  * PDF name object (interned).
330
343
  *
331
344
  * In PDF: `/Type`, `/Page`, `/Length`
@@ -340,7 +353,7 @@ var PdfName = class PdfName {
340
353
  get type() {
341
354
  return "name";
342
355
  }
343
- static cache = new LRUCache(DEFAULT_NAME_CACHE_SIZE);
356
+ static cache = new LRUCache({ max: DEFAULT_NAME_CACHE_SIZE });
344
357
  /**
345
358
  * Pre-cached common names that should never be evicted.
346
359
  * These are stored separately from the LRU cache.
@@ -359,6 +372,8 @@ var PdfName = class PdfName {
359
372
  static Length = PdfName.createPermanent("Length");
360
373
  static Filter = PdfName.createPermanent("Filter");
361
374
  static FlateDecode = PdfName.createPermanent("FlateDecode");
375
+ /** Cached serialized form (e.g. "/Type"). Computed lazily on first toBytes(). */
376
+ cachedBytes = null;
362
377
  constructor(value) {
363
378
  this.value = value;
364
379
  }
@@ -394,7 +409,13 @@ var PdfName = class PdfName {
394
409
  return PdfName.cache.size;
395
410
  }
396
411
  toBytes(writer) {
397
- writer.writeAscii(`/${escapeName$1(this.value)}`);
412
+ let bytes = this.cachedBytes;
413
+ if (bytes === null) {
414
+ const escaped = escapeName$1(this.value);
415
+ bytes = textEncoder.encode(`/${escaped}`);
416
+ this.cachedBytes = bytes;
417
+ }
418
+ writer.writeBytes(bytes);
398
419
  }
399
420
  /**
400
421
  * Create a permanent (non-evictable) name.
@@ -427,7 +448,7 @@ var PdfRef = class PdfRef {
427
448
  get type() {
428
449
  return "ref";
429
450
  }
430
- static cache = new LRUCache(DEFAULT_REF_CACHE_SIZE);
451
+ static cache = new LRUCache({ max: DEFAULT_REF_CACHE_SIZE });
431
452
  constructor(objectNumber, generation) {
432
453
  this.objectNumber = objectNumber;
433
454
  this.generation = generation;
@@ -2044,77 +2065,6 @@ var PdfStream = class PdfStream extends PdfDict {
2044
2065
  }
2045
2066
  };
2046
2067
 
2047
- //#endregion
2048
- //#region src/helpers/buffer.ts
2049
- /**
2050
- * Buffer utilities for working with ArrayBuffer and Uint8Array.
2051
- */
2052
- /**
2053
- * Ensure we have a proper ArrayBuffer (not SharedArrayBuffer or slice).
2054
- *
2055
- * Web Crypto APIs require a true ArrayBuffer, not a view into one.
2056
- *
2057
- * @param data - Uint8Array to convert
2058
- * @returns ArrayBuffer containing the data
2059
- */
2060
- function toArrayBuffer(data) {
2061
- if (data.buffer instanceof ArrayBuffer && data.byteOffset === 0 && data.byteLength === data.buffer.byteLength) return data.buffer;
2062
- return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
2063
- }
2064
- /**
2065
- * Concatenate multiple Uint8Arrays into a single Uint8Array.
2066
- *
2067
- * @param arrays - Arrays to concatenate
2068
- * @returns Single Uint8Array containing all data
2069
- */
2070
- function concatBytes(arrays) {
2071
- const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
2072
- const result = new Uint8Array(totalLength);
2073
- let offset = 0;
2074
- for (const arr of arrays) {
2075
- result.set(arr, offset);
2076
- offset += arr.length;
2077
- }
2078
- return result;
2079
- }
2080
- /**
2081
- * Convert bytes to uppercase hex string.
2082
- *
2083
- * @param bytes - Raw bytes
2084
- * @returns Hex string (e.g., "48656C6C6F")
2085
- *
2086
- * @example
2087
- * ```ts
2088
- * bytesToHex(new Uint8Array([72, 101, 108, 108, 111])) // "48656C6C6F"
2089
- * ```
2090
- */
2091
- function bytesToHex(bytes) {
2092
- let hex = "";
2093
- for (const byte of bytes) hex += byte.toString(16).toUpperCase().padStart(2, "0");
2094
- return hex;
2095
- }
2096
- /**
2097
- * Convert a hex string to bytes.
2098
- *
2099
- * Whitespace is ignored. Odd-length strings are padded with trailing 0.
2100
- *
2101
- * @param hex - Hex string (e.g., "48656C6C6F" or "48 65 6C 6C 6F")
2102
- * @returns Decoded bytes
2103
- *
2104
- * @example
2105
- * ```ts
2106
- * hexToBytes("48656C6C6F") // Uint8Array([72, 101, 108, 108, 111])
2107
- * hexToBytes("ABC") // Uint8Array([171, 192]) - padded to "ABC0"
2108
- * ```
2109
- */
2110
- function hexToBytes(hex) {
2111
- const clean = hex.replace(/\s/g, "");
2112
- const padded = clean.length % 2 === 1 ? `${clean}0` : clean;
2113
- const bytes = new Uint8Array(padded.length / 2);
2114
- for (let i = 0; i < bytes.length; i++) bytes[i] = Number.parseInt(padded.slice(i * 2, i * 2 + 2), 16);
2115
- return bytes;
2116
- }
2117
-
2118
2068
  //#endregion
2119
2069
  //#region src/content/operators.ts
2120
2070
  /**
@@ -2133,7 +2083,6 @@ function hexToBytes(hex) {
2133
2083
  *
2134
2084
  * This module provides type-safe creation and serialization of operators.
2135
2085
  */
2136
- const encoder$1 = new TextEncoder();
2137
2086
  const SPACE$1 = 32;
2138
2087
  /** All PDF content stream operator names */
2139
2088
  const Op = {
@@ -2223,18 +2172,24 @@ var Operator = class Operator {
2223
2172
  return new Operator(op, Object.freeze([...operands]));
2224
2173
  }
2225
2174
  /**
2175
+ * Write operator bytes directly into a shared ByteWriter.
2176
+ * Avoids intermediate allocations compared to toBytes().
2177
+ */
2178
+ writeTo(writer) {
2179
+ for (const operand of this.operands) {
2180
+ writeOperand(writer, operand);
2181
+ writer.writeByte(SPACE$1);
2182
+ }
2183
+ writer.writeAscii(this.op);
2184
+ }
2185
+ /**
2226
2186
  * Serialize to bytes for content stream output.
2227
2187
  * Format: "operand1 operand2 ... operator"
2228
2188
  */
2229
2189
  toBytes() {
2230
- if (this.operands.length === 0) return encoder$1.encode(this.op);
2231
- const parts = [];
2232
- for (const operand of this.operands) {
2233
- parts.push(serializeOperand(operand));
2234
- parts.push(new Uint8Array([SPACE$1]));
2235
- }
2236
- parts.push(encoder$1.encode(this.op));
2237
- return concatBytes(parts);
2190
+ const writer = new ByteWriter(void 0, { initialSize: 64 });
2191
+ this.writeTo(writer);
2192
+ return writer.toBytes();
2238
2193
  }
2239
2194
  /**
2240
2195
  * Serialize to PDF content stream syntax string.
@@ -2244,21 +2199,25 @@ var Operator = class Operator {
2244
2199
  return new TextDecoder().decode(this.toBytes());
2245
2200
  }
2246
2201
  /**
2247
- * Get byte length when serialized (for pre-allocation).
2202
+ * Get byte length when serialized.
2203
+ *
2204
+ * Should be avoided in performance-critical paths, use {@link writeTo} instead.
2248
2205
  */
2249
2206
  byteLength() {
2250
2207
  return this.toBytes().length;
2251
2208
  }
2252
2209
  };
2253
- /**
2254
- * Serialize an operand to bytes.
2255
- */
2256
- function serializeOperand(operand) {
2257
- if (typeof operand === "number") return encoder$1.encode(formatPdfNumber(operand));
2258
- if (typeof operand === "string") return encoder$1.encode(operand);
2259
- const writer = new ByteWriter();
2210
+ /** Write an operand directly into a ByteWriter. */
2211
+ function writeOperand(writer, operand) {
2212
+ if (typeof operand === "number") {
2213
+ writer.writeAscii(formatPdfNumber(operand));
2214
+ return;
2215
+ }
2216
+ if (typeof operand === "string") {
2217
+ writer.writeAscii(operand);
2218
+ return;
2219
+ }
2260
2220
  operand.toBytes(writer);
2261
- return writer.toBytes();
2262
2221
  }
2263
2222
 
2264
2223
  //#endregion
@@ -2280,7 +2239,7 @@ function isInlineImageOperation(op) {
2280
2239
  */
2281
2240
  const encoder = new TextEncoder();
2282
2241
  const SPACE = new Uint8Array([32]);
2283
- const NEWLINE = new Uint8Array([10]);
2242
+ const NEWLINE$1 = new Uint8Array([10]);
2284
2243
  /**
2285
2244
  * Serializes content stream operations to bytes.
2286
2245
  */
@@ -2294,7 +2253,7 @@ var ContentStreamSerializer = class ContentStreamSerializer {
2294
2253
  const parts = [];
2295
2254
  for (const op of operations) {
2296
2255
  parts.push(ContentStreamSerializer.serializeOperation(op));
2297
- parts.push(NEWLINE);
2256
+ parts.push(NEWLINE$1);
2298
2257
  }
2299
2258
  return concatBytes(parts);
2300
2259
  }
@@ -2318,15 +2277,15 @@ var ContentStreamSerializer = class ContentStreamSerializer {
2318
2277
  static serializeInlineImage(op) {
2319
2278
  const parts = [];
2320
2279
  parts.push(encoder.encode("BI"));
2321
- parts.push(NEWLINE);
2280
+ parts.push(NEWLINE$1);
2322
2281
  for (const [key$1, value] of op.params) {
2323
2282
  parts.push(encoder.encode(`/${key$1} `));
2324
2283
  parts.push(ContentStreamSerializer.serializeToken(value));
2325
- parts.push(NEWLINE);
2284
+ parts.push(NEWLINE$1);
2326
2285
  }
2327
2286
  parts.push(encoder.encode("ID "));
2328
2287
  parts.push(op.data);
2329
- parts.push(NEWLINE);
2288
+ parts.push(NEWLINE$1);
2330
2289
  parts.push(encoder.encode("EI"));
2331
2290
  return concatBytes(parts);
2332
2291
  }
@@ -22749,21 +22708,21 @@ function executeSvgPathString(options) {
22749
22708
 
22750
22709
  //#endregion
22751
22710
  //#region src/drawing/serialize.ts
22711
+ const NEWLINE = 10;
22752
22712
  /**
22753
22713
  * Serialize operators to bytes for content streams.
22754
22714
  *
22755
- * Uses Operator.toBytes() directly to avoid UTF-8 round-trip corruption
22756
- * of non-ASCII bytes in PdfString operands (e.g., WinAnsi-encoded text).
22715
+ * Uses Operator.writeTo() to write directly into a shared ByteWriter,
22716
+ * avoiding per-operator intermediate allocations.
22757
22717
  */
22758
22718
  function serializeOperators(ops) {
22759
22719
  if (ops.length === 0) return new Uint8Array(0);
22760
- const newline = new Uint8Array([10]);
22761
- const parts = [];
22720
+ const writer = new ByteWriter(void 0, { initialSize: ops.length * 24 });
22762
22721
  for (let i = 0; i < ops.length; i++) {
22763
- if (i > 0) parts.push(newline);
22764
- parts.push(ops[i].toBytes());
22722
+ if (i > 0) writer.writeByte(NEWLINE);
22723
+ ops[i].writeTo(writer);
22765
22724
  }
22766
- return concatBytes(parts);
22725
+ return writer.toBytes();
22767
22726
  }
22768
22727
 
22769
22728
  //#endregion
@@ -24410,6 +24369,14 @@ function mergeBboxes(boxes) {
24410
24369
  //#endregion
24411
24370
  //#region src/text/line-grouper.ts
24412
24371
  /**
24372
+ * Minimum fraction of consecutive char pairs with decreasing x-positions
24373
+ * (in stream order) to classify a line as "RTL-placed".
24374
+ *
24375
+ * Figma/Canva exports produce ~100% decreasing pairs within words.
24376
+ * 80% tolerates small forward jumps at word boundaries.
24377
+ */
24378
+ const RTL_PLACED_THRESHOLD = .8;
24379
+ /**
24413
24380
  * Group extracted characters into lines and spans.
24414
24381
  *
24415
24382
  * @param chars - Array of extracted characters
@@ -24423,8 +24390,8 @@ function groupCharsIntoLines(chars, options = {}) {
24423
24390
  const lineGroups = groupByBaseline(chars, baselineTolerance);
24424
24391
  const lines = [];
24425
24392
  for (const group of lineGroups) {
24426
- const sorted = [...group].sort((a, b) => a.bbox.x - b.bbox.x);
24427
- const spans = groupIntoSpans(sorted, spaceThreshold);
24393
+ const { chars: sorted, rtlPlaced } = orderLineChars(group);
24394
+ const spans = groupIntoSpans(sorted, spaceThreshold, rtlPlaced);
24428
24395
  if (spans.length === 0) continue;
24429
24396
  const lineText = spans.map((s) => s.text).join("");
24430
24397
  const lineBbox = mergeBboxes(spans.map((s) => s.bbox));
@@ -24440,6 +24407,71 @@ function groupCharsIntoLines(chars, options = {}) {
24440
24407
  return lines;
24441
24408
  }
24442
24409
  /**
24410
+ * Determine the correct character order for a line.
24411
+ *
24412
+ * Design tools like Figma and Canva export PDFs where LTR characters are placed
24413
+ * right-to-left via TJ positioning adjustments (positive values move the pen left).
24414
+ * The font has near-zero glyph widths, so all positioning comes from TJ. Characters
24415
+ * appear in correct reading order in the content stream, but their x-positions
24416
+ * decrease monotonically.
24417
+ *
24418
+ * When this pattern is detected, we preserve content stream order instead of sorting
24419
+ * by x-position, which would reverse the text.
24420
+ *
24421
+ * **Limitation**: Detection requires `sequenceIndex` on every character. If any
24422
+ * character in the group lacks a `sequenceIndex`, we fall back to x-position sorting
24423
+ * because stream order cannot be reliably reconstructed.
24424
+ */
24425
+ function orderLineChars(group) {
24426
+ if (group.length <= 1) return {
24427
+ chars: [...group],
24428
+ rtlPlaced: false
24429
+ };
24430
+ if (!group.every((c) => c.sequenceIndex != null)) return {
24431
+ chars: [...group].sort((a, b) => a.bbox.x - b.bbox.x),
24432
+ rtlPlaced: false
24433
+ };
24434
+ const streamOrder = [...group].sort((a, b) => a.sequenceIndex - b.sequenceIndex);
24435
+ if (isRtlPlaced(streamOrder)) return {
24436
+ chars: streamOrder,
24437
+ rtlPlaced: true
24438
+ };
24439
+ return {
24440
+ chars: [...group].sort((a, b) => a.bbox.x - b.bbox.x),
24441
+ rtlPlaced: false
24442
+ };
24443
+ }
24444
+ /**
24445
+ * Detect whether characters are placed right-to-left in user space while
24446
+ * content stream order represents the correct reading order.
24447
+ *
24448
+ * Returns true when x-positions in stream order are predominantly decreasing
24449
+ * (≥ 80% of consecutive pairs). In that case, position-based sorting would
24450
+ * reverse the reading order, so we preserve stream order instead.
24451
+ *
24452
+ * This covers two real-world scenarios:
24453
+ * - **Design-tool PDFs** (Figma, Canva): LTR text placed right-to-left via
24454
+ * TJ positioning adjustments. Stream order = correct reading order.
24455
+ * - **Genuine RTL text** (Arabic, Hebrew): characters naturally placed
24456
+ * right-to-left. PDF producers typically emit them in reading order, so
24457
+ * stream order is again correct.
24458
+ *
24459
+ * In both cases, when x-positions decrease in stream order, preserving stream
24460
+ * order produces the correct reading order.
24461
+ *
24462
+ * **Known limitation**: mixed bidi text (e.g., Arabic with embedded English)
24463
+ * requires a full Unicode bidi algorithm, which is out of scope for this
24464
+ * heuristic. For mixed lines, neither stream order nor x-sort is fully
24465
+ * correct; a future bidi implementation should replace this heuristic.
24466
+ */
24467
+ function isRtlPlaced(streamOrder) {
24468
+ if (streamOrder.length < 2) return false;
24469
+ let decreasingCount = 0;
24470
+ for (let i = 1; i < streamOrder.length; i++) if (streamOrder[i].bbox.x < streamOrder[i - 1].bbox.x) decreasingCount++;
24471
+ const totalPairs = streamOrder.length - 1;
24472
+ return decreasingCount / totalPairs >= RTL_PLACED_THRESHOLD;
24473
+ }
24474
+ /**
24443
24475
  * Group characters by baseline Y coordinate.
24444
24476
  */
24445
24477
  function groupByBaseline(chars, tolerance) {
@@ -24461,7 +24493,7 @@ function groupByBaseline(chars, tolerance) {
24461
24493
  /**
24462
24494
  * Group characters into spans based on font/size and detect spaces.
24463
24495
  */
24464
- function groupIntoSpans(chars, spaceThreshold) {
24496
+ function groupIntoSpans(chars, spaceThreshold, rtlPlaced) {
24465
24497
  if (chars.length === 0) return [];
24466
24498
  const spans = [];
24467
24499
  let currentSpan = [chars[0]];
@@ -24471,14 +24503,14 @@ function groupIntoSpans(chars, spaceThreshold) {
24471
24503
  const prevChar = chars[i - 1];
24472
24504
  const char = chars[i];
24473
24505
  const fontChanged = char.fontName !== currentFontName || Math.abs(char.fontSize - currentFontSize) > .5;
24474
- const needsSpace = char.bbox.x - (prevChar.bbox.x + prevChar.bbox.width) > (prevChar.fontSize + char.fontSize) / 2 * spaceThreshold;
24506
+ const needsSpace = (rtlPlaced ? prevChar.bbox.x - (char.bbox.x + char.bbox.width) : char.bbox.x - (prevChar.bbox.x + prevChar.bbox.width)) > (prevChar.fontSize + char.fontSize) / 2 * spaceThreshold;
24475
24507
  if (fontChanged) {
24476
24508
  spans.push(buildSpan(currentSpan));
24477
24509
  currentSpan = [char];
24478
24510
  currentFontName = char.fontName;
24479
24511
  currentFontSize = char.fontSize;
24480
24512
  } else if (needsSpace) {
24481
- currentSpan.push(createSpaceChar(prevChar, char));
24513
+ currentSpan.push(createSpaceChar(prevChar, char, rtlPlaced));
24482
24514
  currentSpan.push(char);
24483
24515
  } else currentSpan.push(char);
24484
24516
  }
@@ -24503,9 +24535,9 @@ function buildSpan(chars) {
24503
24535
  /**
24504
24536
  * Create a synthetic space character between two characters.
24505
24537
  */
24506
- function createSpaceChar(before, after) {
24507
- const x = before.bbox.x + before.bbox.width;
24508
- const width = after.bbox.x - x;
24538
+ function createSpaceChar(before, after, rtlPlaced) {
24539
+ const x = rtlPlaced ? after.bbox.x + after.bbox.width : before.bbox.x + before.bbox.width;
24540
+ const width = rtlPlaced ? before.bbox.x - x : after.bbox.x - x;
24509
24541
  return {
24510
24542
  char: " ",
24511
24543
  bbox: {
@@ -24516,7 +24548,8 @@ function createSpaceChar(before, after) {
24516
24548
  },
24517
24549
  fontSize: (before.fontSize + after.fontSize) / 2,
24518
24550
  fontName: before.fontName,
24519
- baseline: (before.baseline + after.baseline) / 2
24551
+ baseline: (before.baseline + after.baseline) / 2,
24552
+ sequenceIndex: before.sequenceIndex != null ? before.sequenceIndex + .5 : void 0
24520
24553
  };
24521
24554
  }
24522
24555
  /**
@@ -26152,7 +26185,8 @@ var TextExtractor = class {
26152
26185
  },
26153
26186
  fontSize: this.state.effectiveFontSize,
26154
26187
  fontName: font.baseFontName,
26155
- baseline: bbox.baseline
26188
+ baseline: bbox.baseline,
26189
+ sequenceIndex: this.chars.length
26156
26190
  });
26157
26191
  const isSpace = char === " " || char === "\xA0";
26158
26192
  this.state.advanceChar(width, isSpace);
@@ -27873,7 +27907,7 @@ var PDFPage = class PDFPage {
27873
27907
  */
27874
27908
  addXObjectResource(ref) {
27875
27909
  const resources = this.getResources();
27876
- let xobjects = resources.get("XObject");
27910
+ let xobjects = resources.get("XObject", this.ctx.resolve.bind(this.ctx));
27877
27911
  if (!(xobjects instanceof PdfDict)) {
27878
27912
  xobjects = new PdfDict();
27879
27913
  resources.set("XObject", xobjects);
@@ -27908,7 +27942,7 @@ var PDFPage = class PDFPage {
27908
27942
  const cachedName = this._resourceCache.get(ref);
27909
27943
  if (cachedName) return cachedName;
27910
27944
  const resources = this.getResources();
27911
- let subdict = resources.get(resourceType);
27945
+ let subdict = resources.get(resourceType, this.ctx.resolve.bind(this.ctx));
27912
27946
  if (!(subdict instanceof PdfDict)) {
27913
27947
  subdict = new PdfDict();
27914
27948
  resources.set(resourceType, subdict);
@@ -29131,7 +29165,7 @@ const INHERITABLE_PAGE_ATTRS = [
29131
29165
  * @example
29132
29166
  * ```typescript
29133
29167
  * const copier = new ObjectCopier(sourcePdf, destPdf);
29134
- * const copiedPageRef = await copier.copyPage(sourcePageRef);
29168
+ * const copiedPageRef = copier.copyPage(sourcePageRef);
29135
29169
  * destPdf.insertPage(0, copiedPageRef);
29136
29170
  * ```
29137
29171
  */
@@ -29161,14 +29195,14 @@ var ObjectCopier = class {
29161
29195
  * @param srcPageRef Reference to the page in source document
29162
29196
  * @returns Reference to the copied page in destination document
29163
29197
  */
29164
- async copyPage(srcPageRef) {
29198
+ copyPage(srcPageRef) {
29165
29199
  const srcPage = this.source.getObject(srcPageRef);
29166
29200
  if (!(srcPage instanceof PdfDict)) throw new Error(`Page object not found or not a dictionary: ${srcPageRef.objectNumber} ${srcPageRef.generation} R`);
29167
29201
  const cloned = srcPage.clone();
29168
29202
  for (const key$1 of INHERITABLE_PAGE_ATTRS) if (!cloned.has(key$1)) {
29169
29203
  const inherited = this.getInheritedAttribute(srcPage, key$1);
29170
29204
  if (inherited) {
29171
- const copied = await this.copyObject(inherited);
29205
+ const copied = this.copyObject(inherited);
29172
29206
  cloned.set(key$1, copied);
29173
29207
  }
29174
29208
  }
@@ -29177,17 +29211,17 @@ var ObjectCopier = class {
29177
29211
  if (!this.options.includeThumbnails) cloned.delete("Thumb");
29178
29212
  if (!this.options.includeStructure) cloned.delete("StructParents");
29179
29213
  cloned.delete("Parent");
29180
- const copiedPage = await this.copyDictValues(cloned);
29214
+ const copiedPage = this.copyDictValues(cloned);
29181
29215
  return this.dest.register(copiedPage);
29182
29216
  }
29183
29217
  /**
29184
29218
  * Deep copy any PDF object, remapping references to destination.
29185
29219
  */
29186
- async copyObject(obj) {
29187
- if (obj instanceof PdfRef) return await this.copyRef(obj);
29188
- if (obj instanceof PdfStream) return await this.copyStream(obj);
29189
- if (obj instanceof PdfDict) return await this.copyDict(obj);
29190
- if (obj instanceof PdfArray) return await this.copyArray(obj);
29220
+ copyObject(obj) {
29221
+ if (obj instanceof PdfRef) return this.copyRef(obj);
29222
+ if (obj instanceof PdfStream) return this.copyStream(obj);
29223
+ if (obj instanceof PdfDict) return this.copyDict(obj);
29224
+ if (obj instanceof PdfArray) return this.copyArray(obj);
29191
29225
  return obj;
29192
29226
  }
29193
29227
  /**
@@ -29196,7 +29230,7 @@ var ObjectCopier = class {
29196
29230
  * Handles circular references by registering a placeholder before
29197
29231
  * recursively copying the referenced object's contents.
29198
29232
  */
29199
- async copyRef(ref) {
29233
+ copyRef(ref) {
29200
29234
  const key$1 = `${ref.objectNumber}:${ref.generation}`;
29201
29235
  const existing = this.refMap.get(key$1);
29202
29236
  if (existing) return existing;
@@ -29210,7 +29244,7 @@ var ObjectCopier = class {
29210
29244
  if (srcObj instanceof PdfDict) return this.copyDictRef(key$1, srcObj);
29211
29245
  if (srcObj instanceof PdfArray) {
29212
29246
  const items = [];
29213
- for (const item of srcObj) items.push(await this.copyObject(item));
29247
+ for (const item of srcObj) items.push(this.copyObject(item));
29214
29248
  const copiedArr = new PdfArray(items);
29215
29249
  const destRef$1 = this.dest.register(copiedArr);
29216
29250
  this.refMap.set(key$1, destRef$1);
@@ -29223,17 +29257,17 @@ var ObjectCopier = class {
29223
29257
  /**
29224
29258
  * Copy a dict reference, handling circular references.
29225
29259
  */
29226
- async copyDictRef(key$1, srcDict) {
29260
+ copyDictRef(key$1, srcDict) {
29227
29261
  const cloned = srcDict.clone();
29228
29262
  const destRef = this.dest.register(cloned);
29229
29263
  this.refMap.set(key$1, destRef);
29230
- await this.copyDictValues(cloned);
29264
+ this.copyDictValues(cloned);
29231
29265
  return destRef;
29232
29266
  }
29233
29267
  /**
29234
29268
  * Copy a stream reference, handling circular references and encryption.
29235
29269
  */
29236
- async copyStreamRef(key$1, srcStream) {
29270
+ copyStreamRef(key$1, srcStream) {
29237
29271
  const sourceWasEncrypted = this.source.isEncrypted;
29238
29272
  const clonedDict = srcStream.clone();
29239
29273
  let streamData;
@@ -29268,7 +29302,7 @@ var ObjectCopier = class {
29268
29302
  const destRef = this.dest.register(copiedStream);
29269
29303
  this.refMap.set(key$1, destRef);
29270
29304
  for (const [entryKey, value] of clonedDict) {
29271
- const copied = await this.copyObject(value);
29305
+ const copied = this.copyObject(value);
29272
29306
  copiedStream.set(entryKey.value, copied);
29273
29307
  }
29274
29308
  return destRef;
@@ -29276,7 +29310,7 @@ var ObjectCopier = class {
29276
29310
  /**
29277
29311
  * Copy a dictionary, remapping all reference values.
29278
29312
  */
29279
- async copyDict(dict) {
29313
+ copyDict(dict) {
29280
29314
  const cloned = dict.clone();
29281
29315
  return this.copyDictValues(cloned);
29282
29316
  }
@@ -29284,9 +29318,9 @@ var ObjectCopier = class {
29284
29318
  * Copy all values in a dictionary, remapping references.
29285
29319
  * Modifies the dict in place and returns it.
29286
29320
  */
29287
- async copyDictValues(dict) {
29321
+ copyDictValues(dict) {
29288
29322
  for (const [key$1, value] of dict) {
29289
- const copied = await this.copyObject(value);
29323
+ const copied = this.copyObject(value);
29290
29324
  dict.set(key$1.value, copied);
29291
29325
  }
29292
29326
  return dict;
@@ -29294,9 +29328,9 @@ var ObjectCopier = class {
29294
29328
  /**
29295
29329
  * Copy an array, remapping all reference elements.
29296
29330
  */
29297
- async copyArray(arr) {
29331
+ copyArray(arr) {
29298
29332
  const items = [];
29299
- for (const item of arr) items.push(await this.copyObject(item));
29333
+ for (const item of arr) items.push(this.copyObject(item));
29300
29334
  return new PdfArray(items);
29301
29335
  }
29302
29336
  /**
@@ -29305,10 +29339,10 @@ var ObjectCopier = class {
29305
29339
  * If source wasn't encrypted, copies raw encoded bytes (fastest).
29306
29340
  * If source was encrypted, decodes and re-encodes with same filters.
29307
29341
  */
29308
- async copyStream(stream) {
29342
+ copyStream(stream) {
29309
29343
  const sourceWasEncrypted = this.source.isEncrypted;
29310
29344
  const clonedDict = stream.clone();
29311
- await this.copyDictValues(clonedDict);
29345
+ this.copyDictValues(clonedDict);
29312
29346
  if (!sourceWasEncrypted) return new PdfStream(clonedDict, stream.data);
29313
29347
  try {
29314
29348
  const decodedData = stream.getDecodedData();
@@ -30672,15 +30706,21 @@ function aesEncrypt(key$1, plaintext) {
30672
30706
  * @param key - 16 bytes (AES-128) or 32 bytes (AES-256)
30673
30707
  * @param data - IV (16 bytes) + ciphertext
30674
30708
  * @returns Decrypted plaintext
30675
- * @throws {Error} if data is too short or padding is invalid
30709
+ * @throws {Error} if data is too short to contain an IV
30676
30710
  */
30677
30711
  function aesDecrypt(key$1, data) {
30678
30712
  validateAesKey(key$1);
30679
30713
  if (data.length < AES_BLOCK_SIZE) throw new Error(`AES ciphertext too short: expected at least ${AES_BLOCK_SIZE} bytes for IV`);
30680
30714
  if (data.length === AES_BLOCK_SIZE) return new Uint8Array(0);
30681
30715
  const iv = data.subarray(0, AES_BLOCK_SIZE);
30682
- const ciphertext = data.subarray(AES_BLOCK_SIZE);
30683
- if (ciphertext.length % AES_BLOCK_SIZE !== 0) throw new Error(`AES ciphertext length must be multiple of ${AES_BLOCK_SIZE}, got ${ciphertext.length}`);
30716
+ let ciphertext = data.subarray(AES_BLOCK_SIZE);
30717
+ if (ciphertext.length % AES_BLOCK_SIZE !== 0) {
30718
+ const remainder = ciphertext.length % AES_BLOCK_SIZE;
30719
+ const aligned = ciphertext.length - remainder;
30720
+ console.warn(`AES ciphertext length (${ciphertext.length}) is not a multiple of ${AES_BLOCK_SIZE}, truncating ${remainder} trailing bytes`);
30721
+ if (aligned === 0) return new Uint8Array(0);
30722
+ ciphertext = ciphertext.subarray(0, aligned);
30723
+ }
30684
30724
  return cbc(key$1, iv).decrypt(ciphertext);
30685
30725
  }
30686
30726
  /**
@@ -33281,29 +33321,34 @@ var DocumentParser = class {
33281
33321
  * Decrypt an object's strings and stream data.
33282
33322
  */
33283
33323
  const decryptObject = (obj, objNum, genNum) => {
33284
- if (!securityHandler?.isAuthenticated) return obj;
33285
- if (obj instanceof PdfString) return new PdfString(securityHandler.decryptString(obj.bytes, objNum, genNum), obj.format);
33286
- if (obj instanceof PdfArray) {
33287
- const decryptedItems = [];
33288
- for (const item of obj) decryptedItems.push(decryptObject(item, objNum, genNum));
33289
- return new PdfArray(decryptedItems);
33290
- }
33291
- if (obj instanceof PdfStream) {
33292
- const streamType = obj.getName("Type")?.value;
33293
- if (!securityHandler.shouldEncryptStream(streamType)) return obj;
33294
- const newStream = new PdfStream(obj, securityHandler.decryptStream(obj.data, objNum, genNum));
33295
- for (const [key$1, value] of obj) {
33296
- const decryptedValue = decryptObject(value, objNum, genNum);
33297
- if (decryptedValue !== value) newStream.set(key$1.value, decryptedValue);
33324
+ try {
33325
+ if (!securityHandler?.isAuthenticated) return obj;
33326
+ if (obj instanceof PdfString) return new PdfString(securityHandler.decryptString(obj.bytes, objNum, genNum), obj.format);
33327
+ if (obj instanceof PdfArray) {
33328
+ const decryptedItems = [];
33329
+ for (const item of obj) decryptedItems.push(decryptObject(item, objNum, genNum));
33330
+ return new PdfArray(decryptedItems);
33298
33331
  }
33299
- return newStream;
33300
- }
33301
- if (obj instanceof PdfDict) {
33302
- const decryptedDict = new PdfDict();
33303
- for (const [key$1, value] of obj) decryptedDict.set(key$1.value, decryptObject(value, objNum, genNum));
33304
- return decryptedDict;
33332
+ if (obj instanceof PdfStream) {
33333
+ const streamType = obj.getName("Type")?.value;
33334
+ if (!securityHandler.shouldEncryptStream(streamType)) return obj;
33335
+ const newStream = new PdfStream(obj, securityHandler.decryptStream(obj.data, objNum, genNum));
33336
+ for (const [key$1, value] of obj) {
33337
+ const decryptedValue = decryptObject(value, objNum, genNum);
33338
+ if (decryptedValue !== value) newStream.set(key$1.value, decryptedValue);
33339
+ }
33340
+ return newStream;
33341
+ }
33342
+ if (obj instanceof PdfDict) {
33343
+ const decryptedDict = new PdfDict();
33344
+ for (const [key$1, value] of obj) decryptedDict.set(key$1.value, decryptObject(value, objNum, genNum));
33345
+ return decryptedDict;
33346
+ }
33347
+ return obj;
33348
+ } catch (error) {
33349
+ console.warn(`Failed to decrypt object ${objNum} ${genNum}:`, error);
33350
+ return obj;
33305
33351
  }
33306
- return obj;
33307
33352
  };
33308
33353
  const getObject = (ref) => {
33309
33354
  const key$1 = `${ref.objectNumber} ${ref.generation}`;
@@ -33781,11 +33826,12 @@ function writeIndirectObject(writer, ref, obj) {
33781
33826
  * Streams that already have filters are returned unchanged - this includes
33782
33827
  * image formats (DCTDecode, JPXDecode, etc.) that are already compressed.
33783
33828
  */
33784
- function prepareObjectForWrite(obj, compress) {
33829
+ const DEFAULT_COMPRESSION_THRESHOLD = 512;
33830
+ function prepareObjectForWrite(obj, compress, compressionThreshold) {
33785
33831
  if (!(obj instanceof PdfStream)) return obj;
33786
33832
  if (obj.has("Filter")) return obj;
33787
33833
  if (!compress) return obj;
33788
- if (obj.data.length === 0) return obj;
33834
+ if (obj.data.length < compressionThreshold) return obj;
33789
33835
  const compressed = FilterPipeline.encode(obj.data, { name: "FlateDecode" });
33790
33836
  if (compressed.length >= obj.data.length) return obj;
33791
33837
  const compressedStream = new PdfStream(obj, compressed);
@@ -33886,6 +33932,7 @@ function collectReachableRefs(registry, root, info, encrypt) {
33886
33932
  function writeComplete(registry, options) {
33887
33933
  const writer = new ByteWriter();
33888
33934
  const compress = options.compressStreams ?? true;
33935
+ const threshold = options.compressionThreshold ?? DEFAULT_COMPRESSION_THRESHOLD;
33889
33936
  const version$1 = options.version ?? "1.7";
33890
33937
  writer.writeAscii(`%PDF-${version$1}\n`);
33891
33938
  writer.writeBytes(new Uint8Array([
@@ -33901,7 +33948,7 @@ function writeComplete(registry, options) {
33901
33948
  for (const [ref, obj] of registry.entries()) {
33902
33949
  const key$1 = `${ref.objectNumber} ${ref.generation}`;
33903
33950
  if (!reachableKeys.has(key$1)) continue;
33904
- let prepared = prepareObjectForWrite(obj, compress);
33951
+ let prepared = prepareObjectForWrite(obj, compress, threshold);
33905
33952
  if (options.securityHandler && options.encrypt && ref !== options.encrypt) prepared = encryptObject(prepared, {
33906
33953
  handler: options.securityHandler,
33907
33954
  objectNumber: ref.objectNumber,
@@ -33986,12 +34033,13 @@ function writeIncremental(registry, options) {
33986
34033
  xrefOffset: options.originalXRefOffset
33987
34034
  };
33988
34035
  const compress = options.compressStreams ?? true;
34036
+ const threshold = options.compressionThreshold ?? DEFAULT_COMPRESSION_THRESHOLD;
33989
34037
  const writer = new ByteWriter(options.originalBytes);
33990
34038
  const lastByte = options.originalBytes[options.originalBytes.length - 1];
33991
34039
  if (lastByte !== LF && lastByte !== CR) writer.writeByte(10);
33992
34040
  const offsets = /* @__PURE__ */ new Map();
33993
34041
  for (const [ref, obj] of changes.modified) {
33994
- let prepared = prepareObjectForWrite(obj, compress);
34042
+ let prepared = prepareObjectForWrite(obj, compress, threshold);
33995
34043
  if (options.securityHandler && options.encrypt && ref !== options.encrypt) prepared = encryptObject(prepared, {
33996
34044
  handler: options.securityHandler,
33997
34045
  objectNumber: ref.objectNumber,
@@ -34004,7 +34052,7 @@ function writeIncremental(registry, options) {
34004
34052
  writeIndirectObject(writer, ref, prepared);
34005
34053
  }
34006
34054
  for (const [ref, obj] of changes.created) {
34007
- let prepared = prepareObjectForWrite(obj, compress);
34055
+ let prepared = prepareObjectForWrite(obj, compress, threshold);
34008
34056
  if (options.securityHandler && options.encrypt && ref !== options.encrypt) prepared = encryptObject(prepared, {
34009
34057
  handler: options.securityHandler,
34010
34058
  objectNumber: ref.objectNumber,
@@ -39387,8 +39435,8 @@ function findBytesReverse(buffer, pattern) {
39387
39435
  * @throws {Error} if placeholders cannot be found
39388
39436
  */
39389
39437
  function findPlaceholders(buffer) {
39390
- const encoder$2 = new TextEncoder();
39391
- const byteRangeKey = encoder$2.encode("/ByteRange");
39438
+ const encoder$1 = new TextEncoder();
39439
+ const byteRangeKey = encoder$1.encode("/ByteRange");
39392
39440
  const byteRangeKeyPos = findBytesReverse(buffer, byteRangeKey);
39393
39441
  if (byteRangeKeyPos === -1) throw new Error("ByteRange placeholder not found in PDF");
39394
39442
  let byteRangeStart = byteRangeKeyPos + byteRangeKey.length;
@@ -39398,7 +39446,7 @@ function findPlaceholders(buffer) {
39398
39446
  while (byteRangeEnd < buffer.length && buffer[byteRangeEnd] !== 93) byteRangeEnd++;
39399
39447
  if (byteRangeEnd >= buffer.length) throw new Error("ByteRange ']' not found in PDF");
39400
39448
  const byteRangeLength = byteRangeEnd - byteRangeStart + 1;
39401
- const contentsKey = encoder$2.encode("/Contents");
39449
+ const contentsKey = encoder$1.encode("/Contents");
39402
39450
  const contentsKeyPos = findBytes(buffer, contentsKey, byteRangeKeyPos);
39403
39451
  if (contentsKeyPos === -1) throw new Error("Contents placeholder not found in PDF");
39404
39452
  let contentsStart = contentsKeyPos + contentsKey.length;
@@ -40849,7 +40897,7 @@ var PDF = class PDF {
40849
40897
  for (const index of indices) {
40850
40898
  const srcPage = source.getPage(index);
40851
40899
  if (!srcPage) throw new Error(`Source page ${index} not found`);
40852
- const copiedPageRef = await copier.copyPage(srcPage.ref);
40900
+ const copiedPageRef = copier.copyPage(srcPage.ref);
40853
40901
  copiedRefs.push(copiedPageRef);
40854
40902
  }
40855
40903
  let insertIndex = options.insertAt ?? this.getPageCount();
@@ -40925,7 +40973,7 @@ var PDF = class PDF {
40925
40973
  const srcResources = srcPage.dict.getDict("Resources", source.getObject.bind(source));
40926
40974
  let resources;
40927
40975
  if (srcResources) {
40928
- const copied = await copier.copyObject(srcResources);
40976
+ const copied = copier.copyObject(srcResources);
40929
40977
  resources = copied instanceof PdfDict ? copied : new PdfDict();
40930
40978
  } else resources = new PdfDict();
40931
40979
  const mediaBox = srcPage.getMediaBox();
@@ -41903,7 +41951,9 @@ var PDF = class PDF {
41903
41951
  encrypt: encryptRef,
41904
41952
  id: fileId,
41905
41953
  useXRefStream,
41906
- securityHandler
41954
+ securityHandler,
41955
+ compressStreams: options.compressStreams,
41956
+ compressionThreshold: options.compressionThreshold
41907
41957
  });
41908
41958
  this._pendingSecurity = { action: "none" };
41909
41959
  return result$1;
@@ -41915,7 +41965,9 @@ var PDF = class PDF {
41915
41965
  encrypt: encryptRef,
41916
41966
  id: fileId,
41917
41967
  useXRefStream,
41918
- securityHandler
41968
+ securityHandler,
41969
+ compressStreams: options.compressStreams,
41970
+ compressionThreshold: options.compressionThreshold
41919
41971
  });
41920
41972
  this._pendingSecurity = { action: "none" };
41921
41973
  return result;