@libpdf/core 0.2.6 → 0.2.8
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 +40 -0
- package/dist/index.mjs +296 -265
- 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.8";
|
|
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
|
/**
|
|
@@ -11699,6 +11649,8 @@ var EmbeddedFont = class EmbeddedFont extends PdfFont {
|
|
|
11699
11649
|
_subsetTag = null;
|
|
11700
11650
|
/** Whether this font is used in a form field (prevents subsetting) */
|
|
11701
11651
|
_usedInForm = false;
|
|
11652
|
+
/** Pre-allocated PDF reference (set by PDFFonts.embed()) */
|
|
11653
|
+
_ref = null;
|
|
11702
11654
|
/** Cached descriptor */
|
|
11703
11655
|
_descriptor = null;
|
|
11704
11656
|
constructor(fontProgram, fontData) {
|
|
@@ -11895,6 +11847,27 @@ var EmbeddedFont = class EmbeddedFont extends PdfFont {
|
|
|
11895
11847
|
this._subsetTag = null;
|
|
11896
11848
|
}
|
|
11897
11849
|
/**
|
|
11850
|
+
* Get the pre-allocated PDF reference for this font.
|
|
11851
|
+
*
|
|
11852
|
+
* Set by `PDFFonts.embed()`. At save time, the actual font objects
|
|
11853
|
+
* (Type0 dict, CIDFont, FontDescriptor, font program, ToUnicode)
|
|
11854
|
+
* are created and registered at this ref.
|
|
11855
|
+
*
|
|
11856
|
+
* @throws {Error} if the font was not embedded via `pdf.embedFont()`
|
|
11857
|
+
*/
|
|
11858
|
+
get ref() {
|
|
11859
|
+
if (!this._ref) throw new Error("Font has no PDF reference. Use pdf.embedFont() to embed fonts.");
|
|
11860
|
+
return this._ref;
|
|
11861
|
+
}
|
|
11862
|
+
/**
|
|
11863
|
+
* Set the pre-allocated PDF reference.
|
|
11864
|
+
*
|
|
11865
|
+
* @internal Called by PDFFonts.embed()
|
|
11866
|
+
*/
|
|
11867
|
+
setRef(ref) {
|
|
11868
|
+
this._ref = ref;
|
|
11869
|
+
}
|
|
11870
|
+
/**
|
|
11898
11871
|
* Mark this font as used in a form field.
|
|
11899
11872
|
*
|
|
11900
11873
|
* Fonts used in form fields cannot be subsetted because users may type
|
|
@@ -18674,10 +18647,8 @@ function buildResources$2(ctx, font, fontName) {
|
|
|
18674
18647
|
const resources = new PdfDict();
|
|
18675
18648
|
const fonts = new PdfDict();
|
|
18676
18649
|
const cleanName = fontName.startsWith("/") ? fontName.slice(1) : fontName;
|
|
18677
|
-
if (isEmbeddedFont(font))
|
|
18678
|
-
|
|
18679
|
-
fonts.set(cleanName, fontRef);
|
|
18680
|
-
} else if (isExistingFont(font) && font.ref) fonts.set(cleanName, font.ref);
|
|
18650
|
+
if (isEmbeddedFont(font)) fonts.set(cleanName, font.ref);
|
|
18651
|
+
else if (isExistingFont(font) && font.ref) fonts.set(cleanName, font.ref);
|
|
18681
18652
|
else {
|
|
18682
18653
|
const fontDict = new PdfDict();
|
|
18683
18654
|
fontDict.set("Type", PdfName.of("Font"));
|
|
@@ -18688,14 +18659,6 @@ function buildResources$2(ctx, font, fontName) {
|
|
|
18688
18659
|
resources.set("Font", fonts);
|
|
18689
18660
|
return resources;
|
|
18690
18661
|
}
|
|
18691
|
-
function buildEmbeddedFontDict$2(font) {
|
|
18692
|
-
const dict = new PdfDict();
|
|
18693
|
-
dict.set("Type", PdfName.of("Font"));
|
|
18694
|
-
dict.set("Subtype", PdfName.of("Type0"));
|
|
18695
|
-
dict.set("BaseFont", PdfName.of(font.baseFontName));
|
|
18696
|
-
dict.set("Encoding", PdfName.of("Identity-H"));
|
|
18697
|
-
return dict;
|
|
18698
|
-
}
|
|
18699
18662
|
|
|
18700
18663
|
//#endregion
|
|
18701
18664
|
//#region src/document/forms/choice-appearance.ts
|
|
@@ -18888,10 +18851,8 @@ function buildResources$1(ctx, font, fontName) {
|
|
|
18888
18851
|
const resources = new PdfDict();
|
|
18889
18852
|
const fonts = new PdfDict();
|
|
18890
18853
|
const cleanName = fontName.startsWith("/") ? fontName.slice(1) : fontName;
|
|
18891
|
-
if (isEmbeddedFont(font))
|
|
18892
|
-
|
|
18893
|
-
fonts.set(cleanName, fontRef);
|
|
18894
|
-
} else if (isExistingFont(font) && font.ref) fonts.set(cleanName, font.ref);
|
|
18854
|
+
if (isEmbeddedFont(font)) fonts.set(cleanName, font.ref);
|
|
18855
|
+
else if (isExistingFont(font) && font.ref) fonts.set(cleanName, font.ref);
|
|
18895
18856
|
else {
|
|
18896
18857
|
const fontDict = new PdfDict();
|
|
18897
18858
|
fontDict.set("Type", PdfName.of("Font"));
|
|
@@ -18902,14 +18863,6 @@ function buildResources$1(ctx, font, fontName) {
|
|
|
18902
18863
|
resources.set("Font", fonts);
|
|
18903
18864
|
return resources;
|
|
18904
18865
|
}
|
|
18905
|
-
function buildEmbeddedFontDict$1(font) {
|
|
18906
|
-
const dict = new PdfDict();
|
|
18907
|
-
dict.set("Type", PdfName.of("Font"));
|
|
18908
|
-
dict.set("Subtype", PdfName.of("Type0"));
|
|
18909
|
-
dict.set("BaseFont", PdfName.of(font.baseFontName));
|
|
18910
|
-
dict.set("Encoding", PdfName.of("Identity-H"));
|
|
18911
|
-
return dict;
|
|
18912
|
-
}
|
|
18913
18866
|
|
|
18914
18867
|
//#endregion
|
|
18915
18868
|
//#region src/document/forms/text-appearance.ts
|
|
@@ -19211,10 +19164,8 @@ function buildResources(ctx, font, fontName) {
|
|
|
19211
19164
|
const resources = new PdfDict();
|
|
19212
19165
|
const fonts = new PdfDict();
|
|
19213
19166
|
const cleanName = fontName.startsWith("/") ? fontName.slice(1) : fontName;
|
|
19214
|
-
if (isEmbeddedFont(font))
|
|
19215
|
-
|
|
19216
|
-
fonts.set(cleanName, fontRef);
|
|
19217
|
-
} else if (isExistingFont(font) && font.ref) fonts.set(cleanName, font.ref);
|
|
19167
|
+
if (isEmbeddedFont(font)) fonts.set(cleanName, font.ref);
|
|
19168
|
+
else if (isExistingFont(font) && font.ref) fonts.set(cleanName, font.ref);
|
|
19218
19169
|
else {
|
|
19219
19170
|
const fontDict = new PdfDict();
|
|
19220
19171
|
fontDict.set("Type", PdfName.of("Font"));
|
|
@@ -19225,14 +19176,6 @@ function buildResources(ctx, font, fontName) {
|
|
|
19225
19176
|
resources.set("Font", fonts);
|
|
19226
19177
|
return resources;
|
|
19227
19178
|
}
|
|
19228
|
-
function buildEmbeddedFontDict(font) {
|
|
19229
|
-
const dict = new PdfDict();
|
|
19230
|
-
dict.set("Type", PdfName.of("Font"));
|
|
19231
|
-
dict.set("Subtype", PdfName.of("Type0"));
|
|
19232
|
-
dict.set("BaseFont", PdfName.of(font.baseFontName));
|
|
19233
|
-
dict.set("Encoding", PdfName.of("Identity-H"));
|
|
19234
|
-
return dict;
|
|
19235
|
-
}
|
|
19236
19179
|
function calculateAppearanceMatrix(width, height, rotation) {
|
|
19237
19180
|
switch (Math.abs(rotation)) {
|
|
19238
19181
|
case 90: return [
|
|
@@ -24417,6 +24360,14 @@ function mergeBboxes(boxes) {
|
|
|
24417
24360
|
//#endregion
|
|
24418
24361
|
//#region src/text/line-grouper.ts
|
|
24419
24362
|
/**
|
|
24363
|
+
* Minimum fraction of consecutive char pairs with decreasing x-positions
|
|
24364
|
+
* (in stream order) to classify a line as "RTL-placed".
|
|
24365
|
+
*
|
|
24366
|
+
* Figma/Canva exports produce ~100% decreasing pairs within words.
|
|
24367
|
+
* 80% tolerates small forward jumps at word boundaries.
|
|
24368
|
+
*/
|
|
24369
|
+
const RTL_PLACED_THRESHOLD = .8;
|
|
24370
|
+
/**
|
|
24420
24371
|
* Group extracted characters into lines and spans.
|
|
24421
24372
|
*
|
|
24422
24373
|
* @param chars - Array of extracted characters
|
|
@@ -24430,8 +24381,8 @@ function groupCharsIntoLines(chars, options = {}) {
|
|
|
24430
24381
|
const lineGroups = groupByBaseline(chars, baselineTolerance);
|
|
24431
24382
|
const lines = [];
|
|
24432
24383
|
for (const group of lineGroups) {
|
|
24433
|
-
const
|
|
24434
|
-
const spans = groupIntoSpans(sorted, spaceThreshold);
|
|
24384
|
+
const { chars: sorted, rtlPlaced } = orderLineChars(group);
|
|
24385
|
+
const spans = groupIntoSpans(sorted, spaceThreshold, rtlPlaced);
|
|
24435
24386
|
if (spans.length === 0) continue;
|
|
24436
24387
|
const lineText = spans.map((s) => s.text).join("");
|
|
24437
24388
|
const lineBbox = mergeBboxes(spans.map((s) => s.bbox));
|
|
@@ -24447,6 +24398,71 @@ function groupCharsIntoLines(chars, options = {}) {
|
|
|
24447
24398
|
return lines;
|
|
24448
24399
|
}
|
|
24449
24400
|
/**
|
|
24401
|
+
* Determine the correct character order for a line.
|
|
24402
|
+
*
|
|
24403
|
+
* Design tools like Figma and Canva export PDFs where LTR characters are placed
|
|
24404
|
+
* right-to-left via TJ positioning adjustments (positive values move the pen left).
|
|
24405
|
+
* The font has near-zero glyph widths, so all positioning comes from TJ. Characters
|
|
24406
|
+
* appear in correct reading order in the content stream, but their x-positions
|
|
24407
|
+
* decrease monotonically.
|
|
24408
|
+
*
|
|
24409
|
+
* When this pattern is detected, we preserve content stream order instead of sorting
|
|
24410
|
+
* by x-position, which would reverse the text.
|
|
24411
|
+
*
|
|
24412
|
+
* **Limitation**: Detection requires `sequenceIndex` on every character. If any
|
|
24413
|
+
* character in the group lacks a `sequenceIndex`, we fall back to x-position sorting
|
|
24414
|
+
* because stream order cannot be reliably reconstructed.
|
|
24415
|
+
*/
|
|
24416
|
+
function orderLineChars(group) {
|
|
24417
|
+
if (group.length <= 1) return {
|
|
24418
|
+
chars: [...group],
|
|
24419
|
+
rtlPlaced: false
|
|
24420
|
+
};
|
|
24421
|
+
if (!group.every((c) => c.sequenceIndex != null)) return {
|
|
24422
|
+
chars: [...group].sort((a, b) => a.bbox.x - b.bbox.x),
|
|
24423
|
+
rtlPlaced: false
|
|
24424
|
+
};
|
|
24425
|
+
const streamOrder = [...group].sort((a, b) => a.sequenceIndex - b.sequenceIndex);
|
|
24426
|
+
if (isRtlPlaced(streamOrder)) return {
|
|
24427
|
+
chars: streamOrder,
|
|
24428
|
+
rtlPlaced: true
|
|
24429
|
+
};
|
|
24430
|
+
return {
|
|
24431
|
+
chars: [...group].sort((a, b) => a.bbox.x - b.bbox.x),
|
|
24432
|
+
rtlPlaced: false
|
|
24433
|
+
};
|
|
24434
|
+
}
|
|
24435
|
+
/**
|
|
24436
|
+
* Detect whether characters are placed right-to-left in user space while
|
|
24437
|
+
* content stream order represents the correct reading order.
|
|
24438
|
+
*
|
|
24439
|
+
* Returns true when x-positions in stream order are predominantly decreasing
|
|
24440
|
+
* (≥ 80% of consecutive pairs). In that case, position-based sorting would
|
|
24441
|
+
* reverse the reading order, so we preserve stream order instead.
|
|
24442
|
+
*
|
|
24443
|
+
* This covers two real-world scenarios:
|
|
24444
|
+
* - **Design-tool PDFs** (Figma, Canva): LTR text placed right-to-left via
|
|
24445
|
+
* TJ positioning adjustments. Stream order = correct reading order.
|
|
24446
|
+
* - **Genuine RTL text** (Arabic, Hebrew): characters naturally placed
|
|
24447
|
+
* right-to-left. PDF producers typically emit them in reading order, so
|
|
24448
|
+
* stream order is again correct.
|
|
24449
|
+
*
|
|
24450
|
+
* In both cases, when x-positions decrease in stream order, preserving stream
|
|
24451
|
+
* order produces the correct reading order.
|
|
24452
|
+
*
|
|
24453
|
+
* **Known limitation**: mixed bidi text (e.g., Arabic with embedded English)
|
|
24454
|
+
* requires a full Unicode bidi algorithm, which is out of scope for this
|
|
24455
|
+
* heuristic. For mixed lines, neither stream order nor x-sort is fully
|
|
24456
|
+
* correct; a future bidi implementation should replace this heuristic.
|
|
24457
|
+
*/
|
|
24458
|
+
function isRtlPlaced(streamOrder) {
|
|
24459
|
+
if (streamOrder.length < 2) return false;
|
|
24460
|
+
let decreasingCount = 0;
|
|
24461
|
+
for (let i = 1; i < streamOrder.length; i++) if (streamOrder[i].bbox.x < streamOrder[i - 1].bbox.x) decreasingCount++;
|
|
24462
|
+
const totalPairs = streamOrder.length - 1;
|
|
24463
|
+
return decreasingCount / totalPairs >= RTL_PLACED_THRESHOLD;
|
|
24464
|
+
}
|
|
24465
|
+
/**
|
|
24450
24466
|
* Group characters by baseline Y coordinate.
|
|
24451
24467
|
*/
|
|
24452
24468
|
function groupByBaseline(chars, tolerance) {
|
|
@@ -24468,7 +24484,7 @@ function groupByBaseline(chars, tolerance) {
|
|
|
24468
24484
|
/**
|
|
24469
24485
|
* Group characters into spans based on font/size and detect spaces.
|
|
24470
24486
|
*/
|
|
24471
|
-
function groupIntoSpans(chars, spaceThreshold) {
|
|
24487
|
+
function groupIntoSpans(chars, spaceThreshold, rtlPlaced) {
|
|
24472
24488
|
if (chars.length === 0) return [];
|
|
24473
24489
|
const spans = [];
|
|
24474
24490
|
let currentSpan = [chars[0]];
|
|
@@ -24478,14 +24494,14 @@ function groupIntoSpans(chars, spaceThreshold) {
|
|
|
24478
24494
|
const prevChar = chars[i - 1];
|
|
24479
24495
|
const char = chars[i];
|
|
24480
24496
|
const fontChanged = char.fontName !== currentFontName || Math.abs(char.fontSize - currentFontSize) > .5;
|
|
24481
|
-
const needsSpace = char.bbox.x - (prevChar.bbox.x + prevChar.bbox.width) > (prevChar.fontSize + char.fontSize) / 2 * spaceThreshold;
|
|
24497
|
+
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;
|
|
24482
24498
|
if (fontChanged) {
|
|
24483
24499
|
spans.push(buildSpan(currentSpan));
|
|
24484
24500
|
currentSpan = [char];
|
|
24485
24501
|
currentFontName = char.fontName;
|
|
24486
24502
|
currentFontSize = char.fontSize;
|
|
24487
24503
|
} else if (needsSpace) {
|
|
24488
|
-
currentSpan.push(createSpaceChar(prevChar, char));
|
|
24504
|
+
currentSpan.push(createSpaceChar(prevChar, char, rtlPlaced));
|
|
24489
24505
|
currentSpan.push(char);
|
|
24490
24506
|
} else currentSpan.push(char);
|
|
24491
24507
|
}
|
|
@@ -24510,9 +24526,9 @@ function buildSpan(chars) {
|
|
|
24510
24526
|
/**
|
|
24511
24527
|
* Create a synthetic space character between two characters.
|
|
24512
24528
|
*/
|
|
24513
|
-
function createSpaceChar(before, after) {
|
|
24514
|
-
const x = before.bbox.x + before.bbox.width;
|
|
24515
|
-
const width = after.bbox.x - x;
|
|
24529
|
+
function createSpaceChar(before, after, rtlPlaced) {
|
|
24530
|
+
const x = rtlPlaced ? after.bbox.x + after.bbox.width : before.bbox.x + before.bbox.width;
|
|
24531
|
+
const width = rtlPlaced ? before.bbox.x - x : after.bbox.x - x;
|
|
24516
24532
|
return {
|
|
24517
24533
|
char: " ",
|
|
24518
24534
|
bbox: {
|
|
@@ -24523,7 +24539,8 @@ function createSpaceChar(before, after) {
|
|
|
24523
24539
|
},
|
|
24524
24540
|
fontSize: (before.fontSize + after.fontSize) / 2,
|
|
24525
24541
|
fontName: before.fontName,
|
|
24526
|
-
baseline: (before.baseline + after.baseline) / 2
|
|
24542
|
+
baseline: (before.baseline + after.baseline) / 2,
|
|
24543
|
+
sequenceIndex: before.sequenceIndex != null ? before.sequenceIndex + .5 : void 0
|
|
24527
24544
|
};
|
|
24528
24545
|
}
|
|
24529
24546
|
/**
|
|
@@ -26159,7 +26176,8 @@ var TextExtractor = class {
|
|
|
26159
26176
|
},
|
|
26160
26177
|
fontSize: this.state.effectiveFontSize,
|
|
26161
26178
|
fontName: font.baseFontName,
|
|
26162
|
-
baseline: bbox.baseline
|
|
26179
|
+
baseline: bbox.baseline,
|
|
26180
|
+
sequenceIndex: this.chars.length
|
|
26163
26181
|
});
|
|
26164
26182
|
const isSpace = char === " " || char === "\xA0";
|
|
26165
26183
|
this.state.advanceChar(width, isSpace);
|
|
@@ -29138,7 +29156,7 @@ const INHERITABLE_PAGE_ATTRS = [
|
|
|
29138
29156
|
* @example
|
|
29139
29157
|
* ```typescript
|
|
29140
29158
|
* const copier = new ObjectCopier(sourcePdf, destPdf);
|
|
29141
|
-
* const copiedPageRef =
|
|
29159
|
+
* const copiedPageRef = copier.copyPage(sourcePageRef);
|
|
29142
29160
|
* destPdf.insertPage(0, copiedPageRef);
|
|
29143
29161
|
* ```
|
|
29144
29162
|
*/
|
|
@@ -29168,14 +29186,14 @@ var ObjectCopier = class {
|
|
|
29168
29186
|
* @param srcPageRef Reference to the page in source document
|
|
29169
29187
|
* @returns Reference to the copied page in destination document
|
|
29170
29188
|
*/
|
|
29171
|
-
|
|
29189
|
+
copyPage(srcPageRef) {
|
|
29172
29190
|
const srcPage = this.source.getObject(srcPageRef);
|
|
29173
29191
|
if (!(srcPage instanceof PdfDict)) throw new Error(`Page object not found or not a dictionary: ${srcPageRef.objectNumber} ${srcPageRef.generation} R`);
|
|
29174
29192
|
const cloned = srcPage.clone();
|
|
29175
29193
|
for (const key$1 of INHERITABLE_PAGE_ATTRS) if (!cloned.has(key$1)) {
|
|
29176
29194
|
const inherited = this.getInheritedAttribute(srcPage, key$1);
|
|
29177
29195
|
if (inherited) {
|
|
29178
|
-
const copied =
|
|
29196
|
+
const copied = this.copyObject(inherited);
|
|
29179
29197
|
cloned.set(key$1, copied);
|
|
29180
29198
|
}
|
|
29181
29199
|
}
|
|
@@ -29184,17 +29202,17 @@ var ObjectCopier = class {
|
|
|
29184
29202
|
if (!this.options.includeThumbnails) cloned.delete("Thumb");
|
|
29185
29203
|
if (!this.options.includeStructure) cloned.delete("StructParents");
|
|
29186
29204
|
cloned.delete("Parent");
|
|
29187
|
-
const copiedPage =
|
|
29205
|
+
const copiedPage = this.copyDictValues(cloned);
|
|
29188
29206
|
return this.dest.register(copiedPage);
|
|
29189
29207
|
}
|
|
29190
29208
|
/**
|
|
29191
29209
|
* Deep copy any PDF object, remapping references to destination.
|
|
29192
29210
|
*/
|
|
29193
|
-
|
|
29194
|
-
if (obj instanceof PdfRef) return
|
|
29195
|
-
if (obj instanceof PdfStream) return
|
|
29196
|
-
if (obj instanceof PdfDict) return
|
|
29197
|
-
if (obj instanceof PdfArray) return
|
|
29211
|
+
copyObject(obj) {
|
|
29212
|
+
if (obj instanceof PdfRef) return this.copyRef(obj);
|
|
29213
|
+
if (obj instanceof PdfStream) return this.copyStream(obj);
|
|
29214
|
+
if (obj instanceof PdfDict) return this.copyDict(obj);
|
|
29215
|
+
if (obj instanceof PdfArray) return this.copyArray(obj);
|
|
29198
29216
|
return obj;
|
|
29199
29217
|
}
|
|
29200
29218
|
/**
|
|
@@ -29203,7 +29221,7 @@ var ObjectCopier = class {
|
|
|
29203
29221
|
* Handles circular references by registering a placeholder before
|
|
29204
29222
|
* recursively copying the referenced object's contents.
|
|
29205
29223
|
*/
|
|
29206
|
-
|
|
29224
|
+
copyRef(ref) {
|
|
29207
29225
|
const key$1 = `${ref.objectNumber}:${ref.generation}`;
|
|
29208
29226
|
const existing = this.refMap.get(key$1);
|
|
29209
29227
|
if (existing) return existing;
|
|
@@ -29217,7 +29235,7 @@ var ObjectCopier = class {
|
|
|
29217
29235
|
if (srcObj instanceof PdfDict) return this.copyDictRef(key$1, srcObj);
|
|
29218
29236
|
if (srcObj instanceof PdfArray) {
|
|
29219
29237
|
const items = [];
|
|
29220
|
-
for (const item of srcObj) items.push(
|
|
29238
|
+
for (const item of srcObj) items.push(this.copyObject(item));
|
|
29221
29239
|
const copiedArr = new PdfArray(items);
|
|
29222
29240
|
const destRef$1 = this.dest.register(copiedArr);
|
|
29223
29241
|
this.refMap.set(key$1, destRef$1);
|
|
@@ -29230,17 +29248,17 @@ var ObjectCopier = class {
|
|
|
29230
29248
|
/**
|
|
29231
29249
|
* Copy a dict reference, handling circular references.
|
|
29232
29250
|
*/
|
|
29233
|
-
|
|
29251
|
+
copyDictRef(key$1, srcDict) {
|
|
29234
29252
|
const cloned = srcDict.clone();
|
|
29235
29253
|
const destRef = this.dest.register(cloned);
|
|
29236
29254
|
this.refMap.set(key$1, destRef);
|
|
29237
|
-
|
|
29255
|
+
this.copyDictValues(cloned);
|
|
29238
29256
|
return destRef;
|
|
29239
29257
|
}
|
|
29240
29258
|
/**
|
|
29241
29259
|
* Copy a stream reference, handling circular references and encryption.
|
|
29242
29260
|
*/
|
|
29243
|
-
|
|
29261
|
+
copyStreamRef(key$1, srcStream) {
|
|
29244
29262
|
const sourceWasEncrypted = this.source.isEncrypted;
|
|
29245
29263
|
const clonedDict = srcStream.clone();
|
|
29246
29264
|
let streamData;
|
|
@@ -29275,7 +29293,7 @@ var ObjectCopier = class {
|
|
|
29275
29293
|
const destRef = this.dest.register(copiedStream);
|
|
29276
29294
|
this.refMap.set(key$1, destRef);
|
|
29277
29295
|
for (const [entryKey, value] of clonedDict) {
|
|
29278
|
-
const copied =
|
|
29296
|
+
const copied = this.copyObject(value);
|
|
29279
29297
|
copiedStream.set(entryKey.value, copied);
|
|
29280
29298
|
}
|
|
29281
29299
|
return destRef;
|
|
@@ -29283,7 +29301,7 @@ var ObjectCopier = class {
|
|
|
29283
29301
|
/**
|
|
29284
29302
|
* Copy a dictionary, remapping all reference values.
|
|
29285
29303
|
*/
|
|
29286
|
-
|
|
29304
|
+
copyDict(dict) {
|
|
29287
29305
|
const cloned = dict.clone();
|
|
29288
29306
|
return this.copyDictValues(cloned);
|
|
29289
29307
|
}
|
|
@@ -29291,9 +29309,9 @@ var ObjectCopier = class {
|
|
|
29291
29309
|
* Copy all values in a dictionary, remapping references.
|
|
29292
29310
|
* Modifies the dict in place and returns it.
|
|
29293
29311
|
*/
|
|
29294
|
-
|
|
29312
|
+
copyDictValues(dict) {
|
|
29295
29313
|
for (const [key$1, value] of dict) {
|
|
29296
|
-
const copied =
|
|
29314
|
+
const copied = this.copyObject(value);
|
|
29297
29315
|
dict.set(key$1.value, copied);
|
|
29298
29316
|
}
|
|
29299
29317
|
return dict;
|
|
@@ -29301,9 +29319,9 @@ var ObjectCopier = class {
|
|
|
29301
29319
|
/**
|
|
29302
29320
|
* Copy an array, remapping all reference elements.
|
|
29303
29321
|
*/
|
|
29304
|
-
|
|
29322
|
+
copyArray(arr) {
|
|
29305
29323
|
const items = [];
|
|
29306
|
-
for (const item of arr) items.push(
|
|
29324
|
+
for (const item of arr) items.push(this.copyObject(item));
|
|
29307
29325
|
return new PdfArray(items);
|
|
29308
29326
|
}
|
|
29309
29327
|
/**
|
|
@@ -29312,10 +29330,10 @@ var ObjectCopier = class {
|
|
|
29312
29330
|
* If source wasn't encrypted, copies raw encoded bytes (fastest).
|
|
29313
29331
|
* If source was encrypted, decodes and re-encodes with same filters.
|
|
29314
29332
|
*/
|
|
29315
|
-
|
|
29333
|
+
copyStream(stream) {
|
|
29316
29334
|
const sourceWasEncrypted = this.source.isEncrypted;
|
|
29317
29335
|
const clonedDict = stream.clone();
|
|
29318
|
-
|
|
29336
|
+
this.copyDictValues(clonedDict);
|
|
29319
29337
|
if (!sourceWasEncrypted) return new PdfStream(clonedDict, stream.data);
|
|
29320
29338
|
try {
|
|
29321
29339
|
const decodedData = stream.getDecodedData();
|
|
@@ -30679,15 +30697,21 @@ function aesEncrypt(key$1, plaintext) {
|
|
|
30679
30697
|
* @param key - 16 bytes (AES-128) or 32 bytes (AES-256)
|
|
30680
30698
|
* @param data - IV (16 bytes) + ciphertext
|
|
30681
30699
|
* @returns Decrypted plaintext
|
|
30682
|
-
* @throws {Error} if data is too short
|
|
30700
|
+
* @throws {Error} if data is too short to contain an IV
|
|
30683
30701
|
*/
|
|
30684
30702
|
function aesDecrypt(key$1, data) {
|
|
30685
30703
|
validateAesKey(key$1);
|
|
30686
30704
|
if (data.length < AES_BLOCK_SIZE) throw new Error(`AES ciphertext too short: expected at least ${AES_BLOCK_SIZE} bytes for IV`);
|
|
30687
30705
|
if (data.length === AES_BLOCK_SIZE) return new Uint8Array(0);
|
|
30688
30706
|
const iv = data.subarray(0, AES_BLOCK_SIZE);
|
|
30689
|
-
|
|
30690
|
-
if (ciphertext.length % AES_BLOCK_SIZE !== 0)
|
|
30707
|
+
let ciphertext = data.subarray(AES_BLOCK_SIZE);
|
|
30708
|
+
if (ciphertext.length % AES_BLOCK_SIZE !== 0) {
|
|
30709
|
+
const remainder = ciphertext.length % AES_BLOCK_SIZE;
|
|
30710
|
+
const aligned = ciphertext.length - remainder;
|
|
30711
|
+
console.warn(`AES ciphertext length (${ciphertext.length}) is not a multiple of ${AES_BLOCK_SIZE}, truncating ${remainder} trailing bytes`);
|
|
30712
|
+
if (aligned === 0) return new Uint8Array(0);
|
|
30713
|
+
ciphertext = ciphertext.subarray(0, aligned);
|
|
30714
|
+
}
|
|
30691
30715
|
return cbc(key$1, iv).decrypt(ciphertext);
|
|
30692
30716
|
}
|
|
30693
30717
|
/**
|
|
@@ -33288,29 +33312,34 @@ var DocumentParser = class {
|
|
|
33288
33312
|
* Decrypt an object's strings and stream data.
|
|
33289
33313
|
*/
|
|
33290
33314
|
const decryptObject = (obj, objNum, genNum) => {
|
|
33291
|
-
|
|
33292
|
-
|
|
33293
|
-
|
|
33294
|
-
|
|
33295
|
-
|
|
33296
|
-
|
|
33297
|
-
|
|
33298
|
-
if (obj instanceof PdfStream) {
|
|
33299
|
-
const streamType = obj.getName("Type")?.value;
|
|
33300
|
-
if (!securityHandler.shouldEncryptStream(streamType)) return obj;
|
|
33301
|
-
const newStream = new PdfStream(obj, securityHandler.decryptStream(obj.data, objNum, genNum));
|
|
33302
|
-
for (const [key$1, value] of obj) {
|
|
33303
|
-
const decryptedValue = decryptObject(value, objNum, genNum);
|
|
33304
|
-
if (decryptedValue !== value) newStream.set(key$1.value, decryptedValue);
|
|
33315
|
+
try {
|
|
33316
|
+
if (!securityHandler?.isAuthenticated) return obj;
|
|
33317
|
+
if (obj instanceof PdfString) return new PdfString(securityHandler.decryptString(obj.bytes, objNum, genNum), obj.format);
|
|
33318
|
+
if (obj instanceof PdfArray) {
|
|
33319
|
+
const decryptedItems = [];
|
|
33320
|
+
for (const item of obj) decryptedItems.push(decryptObject(item, objNum, genNum));
|
|
33321
|
+
return new PdfArray(decryptedItems);
|
|
33305
33322
|
}
|
|
33306
|
-
|
|
33307
|
-
|
|
33308
|
-
|
|
33309
|
-
|
|
33310
|
-
|
|
33311
|
-
|
|
33323
|
+
if (obj instanceof PdfStream) {
|
|
33324
|
+
const streamType = obj.getName("Type")?.value;
|
|
33325
|
+
if (!securityHandler.shouldEncryptStream(streamType)) return obj;
|
|
33326
|
+
const newStream = new PdfStream(obj, securityHandler.decryptStream(obj.data, objNum, genNum));
|
|
33327
|
+
for (const [key$1, value] of obj) {
|
|
33328
|
+
const decryptedValue = decryptObject(value, objNum, genNum);
|
|
33329
|
+
if (decryptedValue !== value) newStream.set(key$1.value, decryptedValue);
|
|
33330
|
+
}
|
|
33331
|
+
return newStream;
|
|
33332
|
+
}
|
|
33333
|
+
if (obj instanceof PdfDict) {
|
|
33334
|
+
const decryptedDict = new PdfDict();
|
|
33335
|
+
for (const [key$1, value] of obj) decryptedDict.set(key$1.value, decryptObject(value, objNum, genNum));
|
|
33336
|
+
return decryptedDict;
|
|
33337
|
+
}
|
|
33338
|
+
return obj;
|
|
33339
|
+
} catch (error) {
|
|
33340
|
+
console.warn(`Failed to decrypt object ${objNum} ${genNum}:`, error);
|
|
33341
|
+
return obj;
|
|
33312
33342
|
}
|
|
33313
|
-
return obj;
|
|
33314
33343
|
};
|
|
33315
33344
|
const getObject = (ref) => {
|
|
33316
33345
|
const key$1 = `${ref.objectNumber} ${ref.generation}`;
|
|
@@ -33788,11 +33817,12 @@ function writeIndirectObject(writer, ref, obj) {
|
|
|
33788
33817
|
* Streams that already have filters are returned unchanged - this includes
|
|
33789
33818
|
* image formats (DCTDecode, JPXDecode, etc.) that are already compressed.
|
|
33790
33819
|
*/
|
|
33791
|
-
|
|
33820
|
+
const DEFAULT_COMPRESSION_THRESHOLD = 512;
|
|
33821
|
+
function prepareObjectForWrite(obj, compress, compressionThreshold) {
|
|
33792
33822
|
if (!(obj instanceof PdfStream)) return obj;
|
|
33793
33823
|
if (obj.has("Filter")) return obj;
|
|
33794
33824
|
if (!compress) return obj;
|
|
33795
|
-
if (obj.data.length
|
|
33825
|
+
if (obj.data.length < compressionThreshold) return obj;
|
|
33796
33826
|
const compressed = FilterPipeline.encode(obj.data, { name: "FlateDecode" });
|
|
33797
33827
|
if (compressed.length >= obj.data.length) return obj;
|
|
33798
33828
|
const compressedStream = new PdfStream(obj, compressed);
|
|
@@ -33893,6 +33923,7 @@ function collectReachableRefs(registry, root, info, encrypt) {
|
|
|
33893
33923
|
function writeComplete(registry, options) {
|
|
33894
33924
|
const writer = new ByteWriter();
|
|
33895
33925
|
const compress = options.compressStreams ?? true;
|
|
33926
|
+
const threshold = options.compressionThreshold ?? DEFAULT_COMPRESSION_THRESHOLD;
|
|
33896
33927
|
const version$1 = options.version ?? "1.7";
|
|
33897
33928
|
writer.writeAscii(`%PDF-${version$1}\n`);
|
|
33898
33929
|
writer.writeBytes(new Uint8Array([
|
|
@@ -33908,7 +33939,7 @@ function writeComplete(registry, options) {
|
|
|
33908
33939
|
for (const [ref, obj] of registry.entries()) {
|
|
33909
33940
|
const key$1 = `${ref.objectNumber} ${ref.generation}`;
|
|
33910
33941
|
if (!reachableKeys.has(key$1)) continue;
|
|
33911
|
-
let prepared = prepareObjectForWrite(obj, compress);
|
|
33942
|
+
let prepared = prepareObjectForWrite(obj, compress, threshold);
|
|
33912
33943
|
if (options.securityHandler && options.encrypt && ref !== options.encrypt) prepared = encryptObject(prepared, {
|
|
33913
33944
|
handler: options.securityHandler,
|
|
33914
33945
|
objectNumber: ref.objectNumber,
|
|
@@ -33993,12 +34024,13 @@ function writeIncremental(registry, options) {
|
|
|
33993
34024
|
xrefOffset: options.originalXRefOffset
|
|
33994
34025
|
};
|
|
33995
34026
|
const compress = options.compressStreams ?? true;
|
|
34027
|
+
const threshold = options.compressionThreshold ?? DEFAULT_COMPRESSION_THRESHOLD;
|
|
33996
34028
|
const writer = new ByteWriter(options.originalBytes);
|
|
33997
34029
|
const lastByte = options.originalBytes[options.originalBytes.length - 1];
|
|
33998
34030
|
if (lastByte !== LF && lastByte !== CR) writer.writeByte(10);
|
|
33999
34031
|
const offsets = /* @__PURE__ */ new Map();
|
|
34000
34032
|
for (const [ref, obj] of changes.modified) {
|
|
34001
|
-
let prepared = prepareObjectForWrite(obj, compress);
|
|
34033
|
+
let prepared = prepareObjectForWrite(obj, compress, threshold);
|
|
34002
34034
|
if (options.securityHandler && options.encrypt && ref !== options.encrypt) prepared = encryptObject(prepared, {
|
|
34003
34035
|
handler: options.securityHandler,
|
|
34004
34036
|
objectNumber: ref.objectNumber,
|
|
@@ -34011,7 +34043,7 @@ function writeIncremental(registry, options) {
|
|
|
34011
34043
|
writeIndirectObject(writer, ref, prepared);
|
|
34012
34044
|
}
|
|
34013
34045
|
for (const [ref, obj] of changes.created) {
|
|
34014
|
-
let prepared = prepareObjectForWrite(obj, compress);
|
|
34046
|
+
let prepared = prepareObjectForWrite(obj, compress, threshold);
|
|
34015
34047
|
if (options.securityHandler && options.encrypt && ref !== options.encrypt) prepared = encryptObject(prepared, {
|
|
34016
34048
|
handler: options.securityHandler,
|
|
34017
34049
|
objectNumber: ref.objectNumber,
|
|
@@ -36760,6 +36792,7 @@ var PDFFonts = class {
|
|
|
36760
36792
|
const font = EmbeddedFont.fromBytes(data, options);
|
|
36761
36793
|
const ref = this.ctx.registry.allocateRef();
|
|
36762
36794
|
this.embeddedFonts.set(font, ref);
|
|
36795
|
+
font.setRef(ref);
|
|
36763
36796
|
return font;
|
|
36764
36797
|
}
|
|
36765
36798
|
/**
|
|
@@ -37419,13 +37452,7 @@ var PDFForm = class PDFForm {
|
|
|
37419
37452
|
* Register an embedded font in the form's default resources.
|
|
37420
37453
|
*/
|
|
37421
37454
|
registerFontInFormResources(font) {
|
|
37422
|
-
|
|
37423
|
-
Type: PdfName.of("Font"),
|
|
37424
|
-
Subtype: PdfName.of("Type0"),
|
|
37425
|
-
BaseFont: PdfName.of(font.baseFontName),
|
|
37426
|
-
Encoding: PdfName.of("Identity-H")
|
|
37427
|
-
}));
|
|
37428
|
-
this._acroForm.addFontToResources(fontRef);
|
|
37455
|
+
this._acroForm.addFontToResources(font.ref);
|
|
37429
37456
|
}
|
|
37430
37457
|
/**
|
|
37431
37458
|
* Store field styling metadata for appearance generation.
|
|
@@ -40861,7 +40888,7 @@ var PDF = class PDF {
|
|
|
40861
40888
|
for (const index of indices) {
|
|
40862
40889
|
const srcPage = source.getPage(index);
|
|
40863
40890
|
if (!srcPage) throw new Error(`Source page ${index} not found`);
|
|
40864
|
-
const copiedPageRef =
|
|
40891
|
+
const copiedPageRef = copier.copyPage(srcPage.ref);
|
|
40865
40892
|
copiedRefs.push(copiedPageRef);
|
|
40866
40893
|
}
|
|
40867
40894
|
let insertIndex = options.insertAt ?? this.getPageCount();
|
|
@@ -40937,7 +40964,7 @@ var PDF = class PDF {
|
|
|
40937
40964
|
const srcResources = srcPage.dict.getDict("Resources", source.getObject.bind(source));
|
|
40938
40965
|
let resources;
|
|
40939
40966
|
if (srcResources) {
|
|
40940
|
-
const copied =
|
|
40967
|
+
const copied = copier.copyObject(srcResources);
|
|
40941
40968
|
resources = copied instanceof PdfDict ? copied : new PdfDict();
|
|
40942
40969
|
} else resources = new PdfDict();
|
|
40943
40970
|
const mediaBox = srcPage.getMediaBox();
|
|
@@ -41915,7 +41942,9 @@ var PDF = class PDF {
|
|
|
41915
41942
|
encrypt: encryptRef,
|
|
41916
41943
|
id: fileId,
|
|
41917
41944
|
useXRefStream,
|
|
41918
|
-
securityHandler
|
|
41945
|
+
securityHandler,
|
|
41946
|
+
compressStreams: options.compressStreams,
|
|
41947
|
+
compressionThreshold: options.compressionThreshold
|
|
41919
41948
|
});
|
|
41920
41949
|
this._pendingSecurity = { action: "none" };
|
|
41921
41950
|
return result$1;
|
|
@@ -41927,7 +41956,9 @@ var PDF = class PDF {
|
|
|
41927
41956
|
encrypt: encryptRef,
|
|
41928
41957
|
id: fileId,
|
|
41929
41958
|
useXRefStream,
|
|
41930
|
-
securityHandler
|
|
41959
|
+
securityHandler,
|
|
41960
|
+
compressStreams: options.compressStreams,
|
|
41961
|
+
compressionThreshold: options.compressionThreshold
|
|
41931
41962
|
});
|
|
41932
41963
|
this._pendingSecurity = { action: "none" };
|
|
41933
41964
|
return result;
|