@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.d.mts +30 -1
- package/dist/index.mjs +309 -257
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
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.
|
|
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
|
-
*
|
|
310
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
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
|
|
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
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
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.
|
|
22756
|
-
*
|
|
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
|
|
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)
|
|
22764
|
-
|
|
22722
|
+
if (i > 0) writer.writeByte(NEWLINE);
|
|
22723
|
+
ops[i].writeTo(writer);
|
|
22765
22724
|
}
|
|
22766
|
-
return
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
29187
|
-
if (obj instanceof PdfRef) return
|
|
29188
|
-
if (obj instanceof PdfStream) return
|
|
29189
|
-
if (obj instanceof PdfDict) return
|
|
29190
|
-
if (obj instanceof PdfArray) return
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
29321
|
+
copyDictValues(dict) {
|
|
29288
29322
|
for (const [key$1, value] of dict) {
|
|
29289
|
-
const copied =
|
|
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
|
-
|
|
29331
|
+
copyArray(arr) {
|
|
29298
29332
|
const items = [];
|
|
29299
|
-
for (const item of arr) items.push(
|
|
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
|
-
|
|
29342
|
+
copyStream(stream) {
|
|
29309
29343
|
const sourceWasEncrypted = this.source.isEncrypted;
|
|
29310
29344
|
const clonedDict = stream.clone();
|
|
29311
|
-
|
|
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
|
|
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
|
-
|
|
30683
|
-
if (ciphertext.length % AES_BLOCK_SIZE !== 0)
|
|
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
|
-
|
|
33285
|
-
|
|
33286
|
-
|
|
33287
|
-
|
|
33288
|
-
|
|
33289
|
-
|
|
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
|
-
|
|
33300
|
-
|
|
33301
|
-
|
|
33302
|
-
|
|
33303
|
-
|
|
33304
|
-
|
|
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
|
-
|
|
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
|
|
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$
|
|
39391
|
-
const byteRangeKey = encoder$
|
|
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$
|
|
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 =
|
|
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 =
|
|
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;
|