@web-ts-toolkit/pdf-reader 0.42.2 → 0.47.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +172 -38
  2. package/index.d.mts +163 -12
  3. package/index.mjs +642 -153
  4. package/package.json +1 -1
package/index.mjs CHANGED
@@ -22,6 +22,41 @@ import { getDocument } from "pdfjs-dist";
22
22
  // src/embeddedImages.ts
23
23
  import { OPS, Util } from "pdfjs-dist";
24
24
 
25
+ // src/canvasGuards.ts
26
+ var MAX_TIMER_MS = 2147483647;
27
+ function resolveSafeCanvasDimensions(width, height, limit, code, subject) {
28
+ const pixelWidth = typeof width === "number" ? Math.ceil(width) : Number.NaN;
29
+ const pixelHeight = typeof height === "number" ? Math.ceil(height) : Number.NaN;
30
+ if (!Number.isSafeInteger(pixelWidth) || pixelWidth <= 0 || !Number.isSafeInteger(pixelHeight) || pixelHeight <= 0) {
31
+ throw new PdfReaderError(code, `${subject} has invalid canvas dimensions.`);
32
+ }
33
+ const pixels = pixelWidth * pixelHeight;
34
+ if (!Number.isSafeInteger(pixels) || pixels <= 0 || pixels > limit) {
35
+ const detail = Number.isSafeInteger(pixels) ? `${pixels}` : "unsafe";
36
+ throw new PdfReaderError(code, `${subject} requires ${detail} pixels; limit is ${limit}.`);
37
+ }
38
+ return { pixelWidth, pixelHeight, pixels };
39
+ }
40
+ function isValidDataUrlForMime(dataUrl, mimeType) {
41
+ if (typeof dataUrl !== "string") return false;
42
+ if (dataUrl === "data:,") return false;
43
+ if (!dataUrl.startsWith(`data:${mimeType}`)) return false;
44
+ const comma = dataUrl.indexOf(",");
45
+ if (comma === -1) return false;
46
+ if (comma === dataUrl.length - 1) return false;
47
+ const prefix = `data:${mimeType}`;
48
+ if (dataUrl.length <= prefix.length + 1) return false;
49
+ return true;
50
+ }
51
+ function isValidBlobForMime(blob, mimeType) {
52
+ if (blob === null || typeof blob !== "object") return false;
53
+ const candidate = blob;
54
+ if (typeof candidate.size !== "number" || typeof candidate.type !== "string") return false;
55
+ if (candidate.size <= 0) return false;
56
+ if (candidate.type !== mimeType) return false;
57
+ return true;
58
+ }
59
+
25
60
  // src/geometry.ts
26
61
  function getTransformedUnitBounds(transform) {
27
62
  const [a, b, c, d, e, f] = transform;
@@ -50,8 +85,13 @@ var propagatedErrorCodes = /* @__PURE__ */ new Set([
50
85
  "IMAGE_TOTAL_PIXELS_LIMIT_EXCEEDED",
51
86
  "OPERATOR_LIMIT_EXCEEDED"
52
87
  ]);
88
+ var ImageKind = {
89
+ GRAYSCALE_1BPP: 1,
90
+ RGB_24BPP: 2,
91
+ RGBA_32BPP: 3
92
+ };
53
93
  async function extractEmbeddedImages(page, viewport, options) {
54
- const operators = await page.getOperatorList();
94
+ const operators = await options.awaitWithCancellation(page.getOperatorList());
55
95
  enforceOperatorLimit(operators.fnArray.length, options.maxOperators);
56
96
  const images = [];
57
97
  const stack = [];
@@ -96,12 +136,12 @@ async function extractEmbeddedImages(page, viewport, options) {
96
136
  if (encoded) {
97
137
  enforceNextImageLimits(images.length, totalPixels, encoded.dimensions.pixels, options);
98
138
  } else {
99
- const paintedImage = await resolvePaintedImage(page, operation, args, index);
139
+ const paintedImage = await resolvePaintedImage(page, operation, args, index, options);
100
140
  options.throwIfDestroyed();
101
141
  options.throwIfAborted(options.signal);
102
142
  if (!paintedImage) continue;
103
143
  const dimensions = readImageDimensions(paintedImage.image, options.maxPixels);
104
- if (!dimensions || !isSupportedImageSource(paintedImage.image)) {
144
+ if (!dimensions) {
105
145
  warn(
106
146
  options.logger,
107
147
  `Skipped embedded image ${paintedImage.label}: unsupported PDF.js image shape or data layout.`
@@ -109,7 +149,15 @@ async function extractEmbeddedImages(page, viewport, options) {
109
149
  continue;
110
150
  }
111
151
  enforceNextImageLimits(images.length, totalPixels, dimensions.pixels, options);
112
- const dataUrl = imageToDataUrl(paintedImage.image, dimensions, options);
152
+ const normalized = normalizeImageSource(paintedImage.image, dimensions);
153
+ if (!normalized) {
154
+ warn(
155
+ options.logger,
156
+ `Skipped embedded image ${paintedImage.label}: unsupported PDF.js image shape or data layout.`
157
+ );
158
+ continue;
159
+ }
160
+ const dataUrl = imageToDataUrl(normalized, dimensions, options);
113
161
  if (!dataUrl) {
114
162
  warn(
115
163
  options.logger,
@@ -151,7 +199,7 @@ function readImageReference(args) {
151
199
  const reference = Array.isArray(args) ? args[0] : void 0;
152
200
  return typeof reference === "string" ? reference : void 0;
153
201
  }
154
- async function resolvePaintedImage(page, operation, args, index) {
202
+ async function resolvePaintedImage(page, operation, args, index, options) {
155
203
  if (operation === OPS.paintInlineImageXObject) {
156
204
  const inlineImage = Array.isArray(args) ? args[0] : void 0;
157
205
  if (!inlineImage || typeof inlineImage !== "object") return void 0;
@@ -159,47 +207,126 @@ async function resolvePaintedImage(page, operation, args, index) {
159
207
  }
160
208
  const reference = readImageReference(args);
161
209
  if (!reference) return void 0;
162
- const image = await page.objs.get(reference);
210
+ const image = await awaitResolvedImageObject(selectImageStore(page, reference), reference, options);
211
+ if (!image || typeof image !== "object") return void 0;
163
212
  return { image, label: reference, reference };
164
213
  }
165
- function imageToDataUrl(image, dimensions, options) {
214
+ function selectImageStore(page, reference) {
215
+ if (reference.startsWith("g_") && page.commonObjs) return page.commonObjs;
216
+ return page.objs;
217
+ }
218
+ async function awaitResolvedImageObject(store, reference, options) {
219
+ if (typeof store?.has === "function" && store.has(reference)) {
220
+ return store.get(reference);
221
+ }
222
+ if (typeof store?.has !== "function") {
223
+ return store.get(reference);
224
+ }
225
+ return options.awaitWithCancellation(
226
+ new Promise((resolve, reject) => {
227
+ try {
228
+ store.get(reference, (data) => resolve(data));
229
+ } catch (error) {
230
+ reject(error);
231
+ }
232
+ })
233
+ );
234
+ }
235
+ function imageToDataUrl(normalized, dimensions, options) {
166
236
  const canvas = allocateCanvas(dimensions.width, dimensions.height, options.maxPixels, options.createCanvas);
167
237
  try {
168
238
  const context = canvas.getContext("2d");
169
239
  if (!context) return void 0;
170
- if (typeof ImageBitmap !== "undefined" && image.bitmap instanceof ImageBitmap) {
171
- context.drawImage(image.bitmap, 0, 0);
172
- return canvas.toDataURL("image/png");
240
+ if (normalized.format === "bitmap") {
241
+ context.drawImage(normalized.bitmap, 0, 0);
242
+ const dataUrl2 = canvas.toDataURL("image/png");
243
+ return isValidDataUrlForMime(dataUrl2, "image/png") ? dataUrl2 : void 0;
173
244
  }
174
- if (!ArrayBuffer.isView(image.data)) return void 0;
175
- const source = new Uint8ClampedArray(image.data.buffer, image.data.byteOffset, image.data.byteLength);
176
- const rgba = toRgba(source, dimensions);
177
- if (!rgba) return void 0;
178
245
  const pixels = context.createImageData(dimensions.width, dimensions.height);
179
- pixels.data.set(rgba);
246
+ pixels.data.set(normalized.rgba);
180
247
  context.putImageData(pixels, 0, 0);
181
- return canvas.toDataURL("image/png");
248
+ const dataUrl = canvas.toDataURL("image/png");
249
+ return isValidDataUrlForMime(dataUrl, "image/png") ? dataUrl : void 0;
182
250
  } finally {
183
251
  releaseCanvas(canvas);
184
252
  }
185
253
  }
186
- function toRgba(data, dimensions) {
254
+ function normalizeImageSource(image, dimensions) {
255
+ if (typeof ImageBitmap !== "undefined" && image.bitmap instanceof ImageBitmap) {
256
+ return { format: "bitmap", bitmap: image.bitmap };
257
+ }
258
+ if (!ArrayBuffer.isView(image.data)) return void 0;
259
+ const source = new Uint8ClampedArray(image.data.buffer, image.data.byteOffset, image.data.byteLength);
260
+ const kind = typeof image.kind === "number" ? image.kind : void 0;
261
+ if (kind === ImageKind.RGBA_32BPP) {
262
+ const rgbaLength2 = multiplySafe(dimensions.pixels, 4);
263
+ if (rgbaLength2 === void 0 || source.length !== rgbaLength2) return void 0;
264
+ return { format: "rgba", rgba: source };
265
+ }
266
+ if (kind === ImageKind.RGB_24BPP) {
267
+ const rgbLength2 = multiplySafe(dimensions.pixels, 3);
268
+ const rgbaLength2 = multiplySafe(dimensions.pixels, 4);
269
+ if (rgbLength2 === void 0 || rgbaLength2 === void 0 || source.length !== rgbLength2) return void 0;
270
+ return { format: "rgba", rgba: expandRgbToRgba(source, dimensions.pixels, rgbaLength2) };
271
+ }
272
+ if (kind === ImageKind.GRAYSCALE_1BPP) {
273
+ const rowBytes = Math.ceil(dimensions.width / 8);
274
+ const packedLength = multiplySafe(rowBytes, dimensions.height);
275
+ const rgbaLength2 = multiplySafe(dimensions.pixels, 4);
276
+ if (packedLength === void 0 || rgbaLength2 === void 0 || source.length !== packedLength) return void 0;
277
+ return { format: "rgba", rgba: unpackOneBitToRgba(source, dimensions, rowBytes, rgbaLength2) };
278
+ }
279
+ if (kind !== void 0) return void 0;
187
280
  const rgbaLength = multiplySafe(dimensions.pixels, 4);
188
281
  const rgbLength = multiplySafe(dimensions.pixels, 3);
189
282
  if (rgbaLength === void 0 || rgbLength === void 0) {
190
283
  throw new PdfReaderError("IMAGE_LIMIT_EXCEEDED", "embedded image has unsafe decoded pixel dimensions.");
191
284
  }
192
- if (data.length === rgbaLength) return data;
193
- if (data.length !== dimensions.pixels && data.length !== rgbLength) return void 0;
194
- const channels = data.length / dimensions.pixels;
285
+ if (source.length === rgbaLength) return { format: "rgba", rgba: source };
286
+ if (source.length === rgbLength)
287
+ return { format: "rgba", rgba: expandRgbToRgba(source, dimensions.pixels, rgbaLength) };
288
+ if (source.length === dimensions.pixels) {
289
+ return { format: "rgba", rgba: expandGray8ToRgba(source, dimensions.pixels, rgbaLength) };
290
+ }
291
+ return void 0;
292
+ }
293
+ function expandRgbToRgba(source, pixels, rgbaLength) {
294
+ const rgba = new Uint8ClampedArray(rgbaLength);
295
+ for (let pixel = 0; pixel < pixels; pixel += 1) {
296
+ const input = pixel * 3;
297
+ const output = pixel * 4;
298
+ rgba[output] = source[input] ?? 0;
299
+ rgba[output + 1] = source[input + 1] ?? 0;
300
+ rgba[output + 2] = source[input + 2] ?? 0;
301
+ rgba[output + 3] = 255;
302
+ }
303
+ return rgba;
304
+ }
305
+ function expandGray8ToRgba(source, pixels, rgbaLength) {
195
306
  const rgba = new Uint8ClampedArray(rgbaLength);
196
- for (let pixel = 0; pixel < dimensions.pixels; pixel += 1) {
197
- const input = pixel * channels;
307
+ for (let pixel = 0; pixel < pixels; pixel += 1) {
198
308
  const output = pixel * 4;
199
- rgba[output] = data[input];
200
- rgba[output + 1] = channels === 1 ? data[input] : data[input + 1];
201
- rgba[output + 2] = channels === 1 ? data[input] : data[input + 2];
202
- rgba[output + 3] = channels === 4 ? data[input + 3] : 255;
309
+ const gray = source[pixel] ?? 0;
310
+ rgba[output] = gray;
311
+ rgba[output + 1] = gray;
312
+ rgba[output + 2] = gray;
313
+ rgba[output + 3] = 255;
314
+ }
315
+ return rgba;
316
+ }
317
+ function unpackOneBitToRgba(packed, dimensions, rowBytes, rgbaLength) {
318
+ const rgba = new Uint8ClampedArray(rgbaLength);
319
+ for (let row = 0; row < dimensions.height; row += 1) {
320
+ for (let column = 0; column < dimensions.width; column += 1) {
321
+ const byte = packed[row * rowBytes + (column >> 3)] ?? 0;
322
+ const bit = byte >> 7 - (column & 7) & 1;
323
+ const value = bit === 1 ? 255 : 0;
324
+ const output = (row * dimensions.width + column) * 4;
325
+ rgba[output] = value;
326
+ rgba[output + 1] = value;
327
+ rgba[output + 2] = value;
328
+ rgba[output + 3] = 255;
329
+ }
203
330
  }
204
331
  return rgba;
205
332
  }
@@ -228,32 +355,39 @@ function readImageDimensions(image, limit) {
228
355
  const { width, height } = image;
229
356
  if (width === void 0 || height === void 0) return void 0;
230
357
  if (typeof width !== "number" || typeof height !== "number") return void 0;
231
- const pixelWidth = Math.ceil(width);
232
- const pixelHeight = Math.ceil(height);
233
- const pixels = multiplySafe(pixelWidth, pixelHeight);
234
- if (pixelWidth <= 0 || pixelHeight <= 0 || pixels === void 0 || pixels > limit) {
235
- throw new PdfReaderError("IMAGE_LIMIT_EXCEEDED", `embedded image requires ${pixels} pixels; limit is ${limit}.`);
236
- }
358
+ const { pixelWidth, pixelHeight, pixels } = resolveSafeCanvasDimensions(
359
+ width,
360
+ height,
361
+ limit,
362
+ "IMAGE_LIMIT_EXCEEDED",
363
+ "embedded image"
364
+ );
237
365
  return { width: pixelWidth, height: pixelHeight, pixels };
238
366
  }
239
- function isSupportedImageSource(image) {
240
- return typeof ImageBitmap !== "undefined" && image.bitmap instanceof ImageBitmap || ArrayBuffer.isView(image.data);
241
- }
242
367
  function multiplySafe(left, right) {
243
368
  if (!Number.isSafeInteger(left) || !Number.isSafeInteger(right)) return void 0;
244
369
  const product = left * right;
245
370
  return Number.isSafeInteger(product) ? product : void 0;
246
371
  }
247
372
  function allocateCanvas(width, height, limit, createCanvas) {
248
- const pixelWidth = Math.ceil(width);
249
- const pixelHeight = Math.ceil(height);
250
- const pixels = multiplySafe(pixelWidth, pixelHeight);
251
- if (pixels === void 0 || pixels > limit) {
252
- throw new PdfReaderError("IMAGE_LIMIT_EXCEEDED", `embedded image requires ${pixels} pixels; limit is ${limit}.`);
253
- }
373
+ const { pixelWidth, pixelHeight } = resolveSafeCanvasDimensions(
374
+ width,
375
+ height,
376
+ limit,
377
+ "IMAGE_LIMIT_EXCEEDED",
378
+ "embedded image"
379
+ );
254
380
  const canvas = createCanvas();
255
- canvas.width = pixelWidth;
256
- canvas.height = pixelHeight;
381
+ try {
382
+ canvas.width = pixelWidth;
383
+ canvas.height = pixelHeight;
384
+ } catch (error) {
385
+ try {
386
+ releaseCanvas(canvas);
387
+ } catch {
388
+ }
389
+ throw error;
390
+ }
257
391
  return canvas;
258
392
  }
259
393
  function imageByteLength(image) {
@@ -299,8 +433,34 @@ function warn(logger, message, error) {
299
433
  logger?.warn(message, error);
300
434
  }
301
435
 
436
+ // src/signal.ts
437
+ function resolveSignal(value) {
438
+ if (value === void 0) return void 0;
439
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
440
+ const candidate = value;
441
+ if (typeof candidate.aborted === "boolean" && typeof candidate.addEventListener === "function" && typeof candidate.removeEventListener === "function") {
442
+ return candidate;
443
+ }
444
+ }
445
+ throw new PdfReaderError(
446
+ "INVALID_OPTION",
447
+ "signal must have a boolean aborted and callable addEventListener/removeEventListener methods; omit it or use undefined for no signal."
448
+ );
449
+ }
450
+
302
451
  // src/options.ts
303
452
  function resolveConvertOptions(options) {
453
+ if (options === null || typeof options !== "object" || Array.isArray(options)) {
454
+ throw new PdfReaderError("INVALID_OPTION", "Convert options must be an object.");
455
+ }
456
+ const raw = options;
457
+ if (raw.pageRange !== void 0) validatePageRange(raw.pageRange);
458
+ if (raw.viewportScale !== void 0) validateViewportScaleShape(raw.viewportScale);
459
+ if (raw.imageFormat !== void 0) validateImageFormat(raw.imageFormat);
460
+ if (raw.pageImageOutput !== void 0) validatePageImageOutput(raw.pageImageOutput);
461
+ validateBooleanFlag(raw.includePageImage, "includePageImage");
462
+ validateBooleanFlag(raw.includeText, "includeText");
463
+ validateBooleanFlag(raw.includeEmbeddedImages, "includeEmbeddedImages");
304
464
  const resolved = {
305
465
  pageRange: options.pageRange,
306
466
  viewportScale: options.viewportScale ?? 1.5,
@@ -310,10 +470,12 @@ function resolveConvertOptions(options) {
310
470
  pageImageOutput: options.pageImageOutput ?? "data-url",
311
471
  includeText: options.includeText ?? true,
312
472
  includeEmbeddedImages: options.includeEmbeddedImages ?? false,
313
- signal: options.signal
473
+ signal: resolveSignal(raw.signal)
314
474
  };
315
475
  if (typeof resolved.viewportScale === "number") {
316
476
  assertPositiveFinite(resolved.viewportScale, "viewportScale");
477
+ } else if (typeof resolved.viewportScale !== "function") {
478
+ throw new PdfReaderError("INVALID_OPTION", "viewportScale must be a positive finite number or a function.");
317
479
  }
318
480
  if (!Number.isFinite(resolved.jpegQuality) || resolved.jpegQuality < 0 || resolved.jpegQuality > 1) {
319
481
  throw new PdfReaderError("INVALID_OPTION", "jpegQuality must be a finite number from 0 through 1.");
@@ -321,13 +483,22 @@ function resolveConvertOptions(options) {
321
483
  if (resolved.pageImageOutput !== "data-url" && resolved.pageImageOutput !== "blob") {
322
484
  throw new PdfReaderError("INVALID_OPTION", 'pageImageOutput must be either "data-url" or "blob".');
323
485
  }
486
+ if (resolved.imageFormat !== "image/png" && resolved.imageFormat !== "image/jpeg") {
487
+ throw new PdfReaderError("INVALID_OPTION", 'imageFormat must be either "image/png" or "image/jpeg".');
488
+ }
324
489
  validatePageRange(resolved.pageRange);
325
490
  return resolved;
326
491
  }
327
492
  function resolvePageNumbers(pageRange, numPages) {
328
493
  if (pageRange === void 0) return [1, numPages];
329
494
  if (typeof pageRange === "number") return [pageRange, pageRange];
330
- return [Math.min(...pageRange), Math.max(...pageRange)];
495
+ if (!Array.isArray(pageRange) || pageRange.length !== 2) {
496
+ throw new PdfReaderError("INVALID_OPTION", "pageRange tuple must have exactly two page numbers.");
497
+ }
498
+ return [
499
+ Math.min(pageRange[0], pageRange[1]),
500
+ Math.max(pageRange[0], pageRange[1])
501
+ ];
331
502
  }
332
503
  function assertPositiveFinite(value, option) {
333
504
  if (!Number.isFinite(value) || value <= 0) {
@@ -336,11 +507,130 @@ function assertPositiveFinite(value, option) {
336
507
  }
337
508
  function validatePageRange(pageRange) {
338
509
  if (pageRange === void 0) return;
339
- const values = typeof pageRange === "number" ? [pageRange] : pageRange;
340
- if (values.some((value) => !Number.isSafeInteger(value) || value < 1)) {
510
+ if (typeof pageRange === "number") {
511
+ if (!Number.isSafeInteger(pageRange) || pageRange < 1) {
512
+ throw new PdfReaderError("INVALID_OPTION", "pageRange values must be positive safe integers.");
513
+ }
514
+ return;
515
+ }
516
+ if (!Array.isArray(pageRange)) {
517
+ throw new PdfReaderError("INVALID_OPTION", "pageRange must be a page number or a two-element tuple.");
518
+ }
519
+ if (pageRange.length !== 2) {
520
+ throw new PdfReaderError("INVALID_OPTION", "pageRange tuple must have exactly two page numbers.");
521
+ }
522
+ if (!(0 in pageRange) || !(1 in pageRange)) {
523
+ throw new PdfReaderError("INVALID_OPTION", "pageRange tuple must not be sparse.");
524
+ }
525
+ if (pageRange.some((value) => !Number.isSafeInteger(value) || value < 1)) {
341
526
  throw new PdfReaderError("INVALID_OPTION", "pageRange values must be positive safe integers.");
342
527
  }
343
528
  }
529
+ function validateViewportScaleShape(value) {
530
+ if (typeof value === "number" || typeof value === "function") return;
531
+ throw new PdfReaderError("INVALID_OPTION", "viewportScale must be a positive finite number or a function.");
532
+ }
533
+ function validateImageFormat(value) {
534
+ if (value === "image/png" || value === "image/jpeg") return;
535
+ throw new PdfReaderError("INVALID_OPTION", 'imageFormat must be either "image/png" or "image/jpeg".');
536
+ }
537
+ function validatePageImageOutput(value) {
538
+ if (value === "data-url" || value === "blob") return;
539
+ throw new PdfReaderError("INVALID_OPTION", 'pageImageOutput must be either "data-url" or "blob".');
540
+ }
541
+ function validateBooleanFlag(value, option) {
542
+ if (value === void 0) return;
543
+ if (typeof value !== "boolean") {
544
+ throw new PdfReaderError("INVALID_OPTION", `${option} must be a boolean.`);
545
+ }
546
+ }
547
+
548
+ // src/pageResources.ts
549
+ var _owners, _closed, _pages, _cleanedAfterClose, _PageLifetime_instances, cleanup_fn;
550
+ var PageLifetime = class {
551
+ constructor(onIdle) {
552
+ this.onIdle = onIdle;
553
+ __privateAdd(this, _PageLifetime_instances);
554
+ __privateAdd(this, _owners, 0);
555
+ __privateAdd(this, _closed, false);
556
+ __privateAdd(this, _pages, /* @__PURE__ */ new Set());
557
+ __privateAdd(this, _cleanedAfterClose, /* @__PURE__ */ new WeakSet());
558
+ }
559
+ retain() {
560
+ __privateSet(this, _owners, __privateGet(this, _owners) + 1);
561
+ let released = false;
562
+ return () => {
563
+ if (released) return;
564
+ released = true;
565
+ __privateSet(this, _owners, __privateGet(this, _owners) - 1);
566
+ if (__privateGet(this, _owners) === 0 && !__privateGet(this, _closed)) {
567
+ this.onIdle();
568
+ this.close();
569
+ }
570
+ };
571
+ }
572
+ /** Observe the original promise, not the caller's cancellation race. */
573
+ track(pending, accept) {
574
+ const release = this.retain();
575
+ pending.then((value) => {
576
+ try {
577
+ accept?.(value);
578
+ } finally {
579
+ release();
580
+ }
581
+ }, release).catch(() => void 0);
582
+ return pending;
583
+ }
584
+ acquire(start) {
585
+ return this.track(start(), (page) => {
586
+ if (__privateGet(this, _closed)) __privateMethod(this, _PageLifetime_instances, cleanup_fn).call(this, page);
587
+ else __privateGet(this, _pages).add(page);
588
+ });
589
+ }
590
+ close() {
591
+ __privateSet(this, _closed, true);
592
+ for (const page of __privateGet(this, _pages)) __privateMethod(this, _PageLifetime_instances, cleanup_fn).call(this, page);
593
+ __privateGet(this, _pages).clear();
594
+ }
595
+ };
596
+ _owners = new WeakMap();
597
+ _closed = new WeakMap();
598
+ _pages = new WeakMap();
599
+ _cleanedAfterClose = new WeakMap();
600
+ _PageLifetime_instances = new WeakSet();
601
+ cleanup_fn = function(page) {
602
+ if (__privateGet(this, _cleanedAfterClose).has(page)) return;
603
+ __privateGet(this, _cleanedAfterClose).add(page);
604
+ try {
605
+ page.cleanup();
606
+ } catch {
607
+ }
608
+ };
609
+ var _pages2;
610
+ var PageResources = class {
611
+ constructor() {
612
+ __privateAdd(this, _pages2, /* @__PURE__ */ new Map());
613
+ }
614
+ acquire(pageNumber) {
615
+ let lifetime = __privateGet(this, _pages2).get(pageNumber);
616
+ if (!lifetime) {
617
+ lifetime = new PageLifetime(() => __privateGet(this, _pages2).delete(pageNumber));
618
+ __privateGet(this, _pages2).set(pageNumber, lifetime);
619
+ }
620
+ const release = lifetime.retain();
621
+ return {
622
+ acquire: (start) => lifetime.acquire(start),
623
+ track: (pending) => lifetime.track(pending),
624
+ release
625
+ };
626
+ }
627
+ /** Document destruction is authoritative; do not await orphaned promises. */
628
+ destroy() {
629
+ for (const lifetime of __privateGet(this, _pages2).values()) lifetime.close();
630
+ __privateGet(this, _pages2).clear();
631
+ }
632
+ };
633
+ _pages2 = new WeakMap();
344
634
 
345
635
  // src/PDFReader.ts
346
636
  var defaultLimits = {
@@ -353,7 +643,31 @@ var defaultLimits = {
353
643
  maxEmbeddedImages: 1e3,
354
644
  maxEmbeddedImagePixelsTotal: 1e8
355
645
  };
356
- var _source, _createCanvas, _logger, _limits, _sourcePolicy, _loadingState, _loadingTask, _document, _destroyPromise, _destroyController, _activeRenderTasks, _activePageOperation, _activePageWorkCount, _destroyed, _lastLoadFailed, _PDFReader_instances, load_fn, processPage_fn, enforceTextLimits_fn, encodePageImage_fn, resolveViewport_fn, encodeCanvasToBlob_fn, allocateCanvas_fn, waitForRender_fn, releaseCanvas_fn, acquirePageOperation_fn, releasePageOperation_fn, getOrCreateLoadState_fn, destroyLoadStateTask_fn, awaitWithSignal_fn, awaitWithDestroy_fn, createAbortedError_fn, createDestroyedError_fn, createDeadlineExceededError_fn, createUnsupportedEnvironmentError_fn, rethrowLifecycleError_fn, throwIfDestroyed_fn, throwIfAborted_fn, resolveLoadOptions_fn, isAbortSignal_fn, enforceSourcePolicy_fn, createSourceSnapshot_fn, inspectSource_fn, sourceKind_fn, toPdfJsSource_fn, freezeSource_fn, isPdfTypedArray_fn, knownByteLength_fn, readUrl_fn, readHttpHeaders_fn, validateLimits_fn;
646
+ function resolveLimits(limits) {
647
+ const resolved = { ...defaultLimits };
648
+ if (limits === void 0 || limits === null) return resolved;
649
+ for (const name of Object.keys(defaultLimits)) {
650
+ const supplied = limits[name];
651
+ if (supplied === void 0) continue;
652
+ if (!Number.isSafeInteger(supplied) || supplied <= 0) {
653
+ throw new PdfReaderError("INVALID_OPTION", `${name} must be a positive safe integer.`);
654
+ }
655
+ resolved[name] = supplied;
656
+ }
657
+ const maxSourceBytes = limits.maxSourceBytes;
658
+ if (maxSourceBytes === void 0) return resolved;
659
+ if (!Number.isSafeInteger(maxSourceBytes) || maxSourceBytes <= 0) {
660
+ throw new PdfReaderError("INVALID_OPTION", "maxSourceBytes must be a positive safe integer.");
661
+ }
662
+ resolved.maxSourceBytes = maxSourceBytes;
663
+ return resolved;
664
+ }
665
+ var InterruptedRender = class {
666
+ constructor(reason) {
667
+ this.reason = reason;
668
+ }
669
+ };
670
+ var _source, _createCanvas, _logger, _limits, _sourcePolicy, _loadingState, _loadingTask, _document, _destroyPromise, _destroyController, _activeRenderTasks, _pageResources, _activePageOperation, _activePageWorkCount, _destroyed, _lastLoadFailed, _PDFReader_instances, load_fn, processPage_fn, enforceTextLimits_fn, encodePageImage_fn, resolveViewport_fn, encodeCanvasToBlob_fn, createCanvasWithSignal_fn, allocateCanvas_fn, waitForRender_fn, releaseCanvas_fn, acquirePageOperation_fn, releasePageOperation_fn, getOrCreateLoadState_fn, destroyLoadStateTask_fn, awaitWithSignal_fn, awaitWithDestroy_fn, createAbortedError_fn, createDestroyedError_fn, createDeadlineExceededError_fn, createUnsupportedEnvironmentError_fn, rethrowLifecycleError_fn, throwIfDestroyed_fn, throwIfAborted_fn, resolveLoadOptions_fn, enforceSourcePolicy_fn, throwIfOverSourceLimit_fn, createSourceSnapshot_fn, inspectSource_fn, sourceKind_fn, toPdfJsSource_fn, freezeSource_fn, isFetchHeaders_fn, isPdfTypedArray_fn, knownByteLength_fn, readUrl_fn, readRawHttpHeaders_fn, snapshotHttpHeaders_fn;
357
671
  var PDFReader = class {
358
672
  constructor(source, options = {}) {
359
673
  __privateAdd(this, _PDFReader_instances);
@@ -368,6 +682,7 @@ var PDFReader = class {
368
682
  __privateAdd(this, _destroyPromise);
369
683
  __privateAdd(this, _destroyController, new AbortController());
370
684
  __privateAdd(this, _activeRenderTasks, /* @__PURE__ */ new Set());
685
+ __privateAdd(this, _pageResources, new PageResources());
371
686
  __privateAdd(this, _activePageOperation, false);
372
687
  __privateAdd(this, _activePageWorkCount, 0);
373
688
  __privateAdd(this, _destroyed, false);
@@ -375,9 +690,8 @@ var PDFReader = class {
375
690
  __privateSet(this, _source, source);
376
691
  __privateSet(this, _createCanvas, options.canvasFactory ?? (() => document.createElement("canvas")));
377
692
  __privateSet(this, _logger, options.logger);
378
- __privateSet(this, _limits, { ...defaultLimits, ...options.limits });
693
+ __privateSet(this, _limits, resolveLimits(options.limits));
379
694
  __privateSet(this, _sourcePolicy, options.sourcePolicy);
380
- __privateMethod(this, _PDFReader_instances, validateLimits_fn).call(this);
381
695
  }
382
696
  get numPages() {
383
697
  return __privateGet(this, _document)?.numPages;
@@ -392,13 +706,37 @@ var PDFReader = class {
392
706
  return "new";
393
707
  }
394
708
  load(options) {
709
+ __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
395
710
  const resolved = __privateMethod(this, _PDFReader_instances, resolveLoadOptions_fn).call(this, options);
396
711
  __privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, resolved.signal);
397
- __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
398
712
  if (__privateGet(this, _document)) return Promise.resolve(__privateGet(this, _document));
399
713
  return __privateMethod(this, _PDFReader_instances, load_fn).call(this, resolved);
400
714
  }
401
- /** Streams page results so callers do not need to retain the entire document conversion in memory. */
715
+ /**
716
+ * Streams page results so callers do not need to retain the entire document conversion in memory.
717
+ *
718
+ * Page-stage waits (`getPage`, text, operator list, render, encoding) settle
719
+ * promptly with `ABORTED`/`DESTROYED` via the shared cancellation/wait
720
+ * contract. Active renders are cancelled; racing other PDF.js work does not
721
+ * stop it. Already-started promises remain observed even if a synchronous
722
+ * callback aborts/destroys before wait registration. Late `getPage()` values
723
+ * are never processed; late text/operator values are dropped. Page cleanup
724
+ * is deferred until package-owned acquisitions, processing, and upstream
725
+ * stage promises for that cached page are idle, including safe retries.
726
+ * Canvas factories and viewport callbacks are followed by lifecycle checks
727
+ * before subsequent allocation/rendering. Caller settlement releases the
728
+ * operation lock, but does not imply upstream work has finished.
729
+ * Cancellation/failure of an already-created render permanently closes the
730
+ * reader and starts public PDF.js teardown. The initiating call retains
731
+ * `ABORTED` or the native error; later operations reject `DESTROYED`. Retry
732
+ * with a fresh reader and fresh bytes (PDF.js may have transferred the input).
733
+ * Pre-render and non-render failures, and encoding cancellation/failure after
734
+ * successful rendering, do not automatically close the reader.
735
+ * A suspended generator only observes abort/destroy on the next
736
+ * `next()`/`return()`; destruction cannot force suspended consumer code to run.
737
+ * Malformed signals (including `null`) reject with `INVALID_OPTION` before
738
+ * page acquisition. Omitted/`undefined` signals are accepted.
739
+ */
402
740
  async *pages(options = {}) {
403
741
  let ownsPageOperation = false;
404
742
  try {
@@ -416,23 +754,27 @@ var PDFReader = class {
416
754
  for (let pageNumber = start; pageNumber <= Math.min(end, documentProxy.numPages); pageNumber += 1) {
417
755
  __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
418
756
  __privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, resolved.signal);
419
- let page;
757
+ const lease = __privateGet(this, _pageResources).acquire(pageNumber);
420
758
  let ownsActivePageWork = false;
421
759
  try {
422
760
  __privateSet(this, _activePageWorkCount, __privateGet(this, _activePageWorkCount) + 1);
423
761
  ownsActivePageWork = true;
424
- page = await documentProxy.getPage(pageNumber);
425
- const result = await __privateMethod(this, _PDFReader_instances, processPage_fn).call(this, page, pageNumber, documentProxy.numPages, resolved);
426
- page.cleanup();
427
- page = void 0;
762
+ const page = await __privateMethod(this, _PDFReader_instances, awaitWithSignal_fn).call(this, lease.acquire(() => documentProxy.getPage(pageNumber)), resolved.signal);
763
+ const result = await __privateMethod(this, _PDFReader_instances, processPage_fn).call(this, page, pageNumber, documentProxy.numPages, resolved, lease);
764
+ lease.release();
428
765
  __privateSet(this, _activePageWorkCount, __privateGet(this, _activePageWorkCount) - 1);
429
766
  ownsActivePageWork = false;
430
767
  yield result;
431
768
  } catch (error) {
769
+ if (error instanceof InterruptedRender) {
770
+ const reason = __privateGet(this, _destroyed) ? __privateMethod(this, _PDFReader_instances, createDestroyedError_fn).call(this) : resolved.signal?.aborted ? __privateMethod(this, _PDFReader_instances, createAbortedError_fn).call(this) : error.reason;
771
+ void this.destroy().catch(() => void 0);
772
+ throw reason;
773
+ }
432
774
  __privateMethod(this, _PDFReader_instances, rethrowLifecycleError_fn).call(this, error, resolved.signal);
433
775
  } finally {
434
776
  if (ownsActivePageWork) __privateSet(this, _activePageWorkCount, __privateGet(this, _activePageWorkCount) - 1);
435
- page?.cleanup();
777
+ lease.release();
436
778
  }
437
779
  }
438
780
  } finally {
@@ -445,17 +787,45 @@ var PDFReader = class {
445
787
  for await (const page of this.pages(options)) results.push(page);
446
788
  return results;
447
789
  }
448
- /** Terminates the loading task and document resources. The reader cannot be reused afterwards. */
449
- async destroy() {
790
+ /**
791
+ * Terminates the loading task and document resources. The reader cannot be reused afterwards.
792
+ *
793
+ * A caller-created PDF.js `PDFWorker` passed on the source stays
794
+ * caller-owned: neither `destroy()` nor PDF.js task teardown destroys it.
795
+ * Destroy it explicitly in your own `finally` block, even if this method
796
+ * rejects.
797
+ *
798
+ * In-flight page-stage waiters settle promptly with `DESTROYED`, but their
799
+ * uncancellable getPage/text/operator work stays observed in the background.
800
+ * Active renders are cancelled. This method waits for PDF.js loading/document
801
+ * destruction, not for orphaned `getPage`/text/operator/image-object
802
+ * continuations, which never start later processing. It clears retained
803
+ * page ownership; late acquisitions receive best-effort cleanup. Await
804
+ * active operations to observe their temporary canvas release. It cannot
805
+ * force a suspended `pages()` consumer to resume; that consumer observes
806
+ * destruction on next resume.
807
+ * Render cancellation/failure also starts this teardown automatically without
808
+ * awaiting it or replacing the initiating error. This method always returns
809
+ * the same teardown promise, including its rejection; call it to observe that
810
+ * outcome. Automatic teardown rejection is observed even if you never do.
811
+ */
812
+ destroy() {
450
813
  if (__privateGet(this, _destroyPromise)) return __privateGet(this, _destroyPromise);
814
+ let resolveDestroy;
815
+ let rejectDestroy;
816
+ __privateSet(this, _destroyPromise, new Promise((resolve, reject) => {
817
+ resolveDestroy = resolve;
818
+ rejectDestroy = reject;
819
+ }));
451
820
  __privateSet(this, _destroyed, true);
452
- __privateGet(this, _destroyController).abort();
453
821
  const loadingTask = __privateGet(this, _loadingTask);
454
822
  const loadingState = __privateGet(this, _loadingState);
455
823
  __privateSet(this, _loadingTask, void 0);
456
824
  __privateSet(this, _document, void 0);
457
825
  __privateSet(this, _loadingState, void 0);
458
- __privateSet(this, _destroyPromise, (async () => {
826
+ void (async () => {
827
+ __privateGet(this, _destroyController).abort();
828
+ __privateGet(this, _pageResources).destroy();
459
829
  for (const renderTask of __privateGet(this, _activeRenderTasks)) renderTask.cancel();
460
830
  if (loadingTask) {
461
831
  await loadingTask.destroy();
@@ -464,8 +834,8 @@ var PDFReader = class {
464
834
  if (!loadingState) return;
465
835
  loadingState.destroyed = true;
466
836
  await __privateMethod(this, _PDFReader_instances, destroyLoadStateTask_fn).call(this, loadingState);
467
- })());
468
- await __privateGet(this, _destroyPromise);
837
+ })().then(resolveDestroy, rejectDestroy);
838
+ return __privateGet(this, _destroyPromise);
469
839
  }
470
840
  };
471
841
  _source = new WeakMap();
@@ -479,6 +849,7 @@ _document = new WeakMap();
479
849
  _destroyPromise = new WeakMap();
480
850
  _destroyController = new WeakMap();
481
851
  _activeRenderTasks = new WeakMap();
852
+ _pageResources = new WeakMap();
482
853
  _activePageOperation = new WeakMap();
483
854
  _activePageWorkCount = new WeakMap();
484
855
  _destroyed = new WeakMap();
@@ -497,8 +868,10 @@ load_fn = async function(options) {
497
868
  throw error;
498
869
  }
499
870
  };
500
- processPage_fn = async function(page, pageNumber, numPages, options) {
501
- const viewport = __privateMethod(this, _PDFReader_instances, resolveViewport_fn).call(this, page, options.viewportScale);
871
+ processPage_fn = async function(page, pageNumber, numPages, options, lease) {
872
+ __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
873
+ __privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, options.signal);
874
+ const viewport = __privateMethod(this, _PDFReader_instances, resolveViewport_fn).call(this, page, options.viewportScale, options.signal);
502
875
  const result = {
503
876
  numPages,
504
877
  pageNumber,
@@ -510,7 +883,7 @@ processPage_fn = async function(page, pageNumber, numPages, options) {
510
883
  __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
511
884
  if (options.includeText) {
512
885
  try {
513
- const text = await page.getTextContent();
886
+ const text = await __privateMethod(this, _PDFReader_instances, awaitWithSignal_fn).call(this, lease.track(page.getTextContent()), options.signal);
514
887
  __privateMethod(this, _PDFReader_instances, enforceTextLimits_fn).call(this, text);
515
888
  result.text = text;
516
889
  } catch (error) {
@@ -523,14 +896,17 @@ processPage_fn = async function(page, pageNumber, numPages, options) {
523
896
  try {
524
897
  result.images = await extractEmbeddedImages(page, viewport, {
525
898
  signal: options.signal,
526
- createCanvas: __privateGet(this, _createCanvas),
899
+ createCanvas: () => __privateMethod(this, _PDFReader_instances, createCanvasWithSignal_fn).call(this, options.signal),
527
900
  maxPixels: __privateGet(this, _limits).maxEmbeddedImagePixels,
528
901
  maxImages: __privateGet(this, _limits).maxEmbeddedImages,
529
902
  maxTotalPixels: __privateGet(this, _limits).maxEmbeddedImagePixelsTotal,
530
903
  maxOperators: __privateGet(this, _limits).maxOperatorCount,
531
904
  logger: __privateGet(this, _logger),
532
905
  throwIfAborted: (signal) => __privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, signal),
533
- throwIfDestroyed: () => __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this)
906
+ throwIfDestroyed: () => __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this),
907
+ // Reuses the reader's single cancellation/wait contract so operator
908
+ // retrieval settles promptly without cancelling upstream PDF.js work.
909
+ awaitWithCancellation: (pending) => __privateMethod(this, _PDFReader_instances, awaitWithSignal_fn).call(this, lease.track(pending), options.signal)
534
910
  });
535
911
  } catch (error) {
536
912
  __privateMethod(this, _PDFReader_instances, rethrowLifecycleError_fn).call(this, error, options.signal);
@@ -539,14 +915,18 @@ processPage_fn = async function(page, pageNumber, numPages, options) {
539
915
  __privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, options.signal);
540
916
  __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
541
917
  if (options.includePageImage) {
542
- const canvas = __privateMethod(this, _PDFReader_instances, allocateCanvas_fn).call(this, viewport.width, viewport.height, __privateGet(this, _limits).maxCanvasPixels, "page");
918
+ const canvas = __privateMethod(this, _PDFReader_instances, allocateCanvas_fn).call(this, viewport.width, viewport.height, __privateGet(this, _limits).maxCanvasPixels, "page", options.signal);
543
919
  try {
920
+ __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
921
+ __privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, options.signal);
544
922
  const context = canvas.getContext("2d");
923
+ __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
924
+ __privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, options.signal);
545
925
  if (!context) {
546
926
  throw __privateMethod(this, _PDFReader_instances, createUnsupportedEnvironmentError_fn).call(this, `Failed to create a 2D canvas context for page ${pageNumber}.`);
547
927
  }
548
928
  const renderTask = page.render({ canvas, canvasContext: context, viewport });
549
- await __privateMethod(this, _PDFReader_instances, waitForRender_fn).call(this, renderTask, options.signal);
929
+ await __privateMethod(this, _PDFReader_instances, waitForRender_fn).call(this, renderTask, lease, options.signal);
550
930
  result.pageImage = await __privateMethod(this, _PDFReader_instances, encodePageImage_fn).call(this, canvas, options.imageFormat, options.pageImageOutput, options.jpegQuality, options.signal);
551
931
  } finally {
552
932
  __privateMethod(this, _PDFReader_instances, releaseCanvas_fn).call(this, canvas);
@@ -586,17 +966,27 @@ encodePageImage_fn = async function(canvas, mimeType, output, jpegQuality, signa
586
966
  const dataUrl = quality === void 0 ? canvas.toDataURL(mimeType) : canvas.toDataURL(mimeType, quality);
587
967
  __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
588
968
  __privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, signal);
969
+ if (!isValidDataUrlForMime(dataUrl, mimeType)) {
970
+ throw __privateMethod(this, _PDFReader_instances, createUnsupportedEnvironmentError_fn).call(this, `Canvas encoding produced an empty or mismatched result for ${mimeType}.`);
971
+ }
589
972
  return { kind: "data-url", mimeType, dataUrl };
590
973
  }
591
974
  const blob = await __privateMethod(this, _PDFReader_instances, encodeCanvasToBlob_fn).call(this, canvas, mimeType, quality, signal);
592
975
  __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
593
976
  __privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, signal);
977
+ if (!isValidBlobForMime(blob, mimeType)) {
978
+ throw __privateMethod(this, _PDFReader_instances, createUnsupportedEnvironmentError_fn).call(this, `Canvas Blob encoding produced an empty or mismatched result for ${mimeType}.`);
979
+ }
594
980
  return { kind: "blob", mimeType, blob };
595
981
  };
596
- resolveViewport_fn = function(page, viewportScale) {
982
+ resolveViewport_fn = function(page, viewportScale, signal) {
597
983
  if (typeof viewportScale === "number") return page.getViewport({ scale: viewportScale });
598
984
  const base = page.getViewport({ scale: 1 });
985
+ __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
986
+ __privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, signal);
599
987
  const scale = viewportScale(base.width, base.height);
988
+ __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
989
+ __privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, signal);
600
990
  assertPositiveFinite(scale, "viewportScale callback result");
601
991
  return page.getViewport({ scale });
602
992
  };
@@ -642,29 +1032,58 @@ encodeCanvasToBlob_fn = async function(canvas, mimeType, quality, signal) {
642
1032
  }
643
1033
  });
644
1034
  };
645
- allocateCanvas_fn = function(width, height, limit, subject) {
646
- const pixelWidth = Math.ceil(width);
647
- const pixelHeight = Math.ceil(height);
648
- const pixels = pixelWidth * pixelHeight;
649
- if (!Number.isSafeInteger(pixels) || pixels > limit) {
650
- const code = subject === "page" ? "CANVAS_LIMIT_EXCEEDED" : "IMAGE_LIMIT_EXCEEDED";
651
- throw new PdfReaderError(code, `${subject} requires ${pixels} pixels; limit is ${limit}.`);
652
- }
1035
+ createCanvasWithSignal_fn = function(signal) {
1036
+ __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
1037
+ __privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, signal);
653
1038
  const canvas = __privateGet(this, _createCanvas).call(this);
654
- canvas.width = pixelWidth;
655
- canvas.height = pixelHeight;
1039
+ try {
1040
+ __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
1041
+ __privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, signal);
1042
+ } catch (error) {
1043
+ try {
1044
+ __privateMethod(this, _PDFReader_instances, releaseCanvas_fn).call(this, canvas);
1045
+ } catch {
1046
+ }
1047
+ throw error;
1048
+ }
656
1049
  return canvas;
657
1050
  };
658
- waitForRender_fn = async function(renderTask, signal) {
1051
+ allocateCanvas_fn = function(width, height, limit, subject, signal) {
1052
+ const { pixelWidth, pixelHeight } = resolveSafeCanvasDimensions(
1053
+ width,
1054
+ height,
1055
+ limit,
1056
+ "CANVAS_LIMIT_EXCEEDED",
1057
+ subject
1058
+ );
1059
+ const canvas = __privateMethod(this, _PDFReader_instances, createCanvasWithSignal_fn).call(this, signal);
1060
+ try {
1061
+ canvas.width = pixelWidth;
1062
+ canvas.height = pixelHeight;
1063
+ } catch (error) {
1064
+ try {
1065
+ __privateMethod(this, _PDFReader_instances, releaseCanvas_fn).call(this, canvas);
1066
+ } catch {
1067
+ }
1068
+ throw error;
1069
+ }
1070
+ return canvas;
1071
+ };
1072
+ waitForRender_fn = async function(renderTask, lease, signal) {
659
1073
  const abort = () => renderTask.cancel();
660
1074
  __privateGet(this, _activeRenderTasks).add(renderTask);
661
1075
  signal?.addEventListener("abort", abort, { once: true });
662
1076
  try {
663
- await __privateMethod(this, _PDFReader_instances, awaitWithSignal_fn).call(this, renderTask.promise, signal);
1077
+ const waiting = __privateMethod(this, _PDFReader_instances, awaitWithSignal_fn).call(this, lease.track(renderTask.promise), signal);
1078
+ try {
1079
+ if (signal?.aborted || __privateGet(this, _destroyed)) abort();
1080
+ } finally {
1081
+ await waiting;
1082
+ }
664
1083
  __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
665
1084
  __privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, signal);
666
1085
  } catch (error) {
667
- __privateMethod(this, _PDFReader_instances, rethrowLifecycleError_fn).call(this, error, signal);
1086
+ throw new InterruptedRender(error);
668
1087
  } finally {
669
1088
  signal?.removeEventListener("abort", abort);
670
1089
  __privateGet(this, _activeRenderTasks).delete(renderTask);
@@ -692,11 +1111,20 @@ getOrCreateLoadState_fn = function() {
692
1111
  if (existing) return existing;
693
1112
  __privateSet(this, _lastLoadFailed, false);
694
1113
  const state = { destroyed: false };
695
- state.promise = (async () => {
1114
+ let resolveLoad;
1115
+ let rejectLoad;
1116
+ state.promise = new Promise((resolve, reject) => {
1117
+ resolveLoad = resolve;
1118
+ rejectLoad = reject;
1119
+ });
1120
+ state.promise.catch(() => void 0);
1121
+ __privateSet(this, _loadingState, state);
1122
+ const taskPromise = (async () => {
696
1123
  try {
697
1124
  const source = __privateMethod(this, _PDFReader_instances, createSourceSnapshot_fn).call(this);
698
1125
  const policyResult = __privateMethod(this, _PDFReader_instances, enforceSourcePolicy_fn).call(this, source.info);
699
1126
  if (policyResult) await __privateMethod(this, _PDFReader_instances, awaitWithDestroy_fn).call(this, policyResult);
1127
+ __privateMethod(this, _PDFReader_instances, throwIfOverSourceLimit_fn).call(this, source.pdfJsSource.data);
700
1128
  if (state.destroyed || __privateGet(this, _destroyed)) {
701
1129
  throw __privateMethod(this, _PDFReader_instances, createDestroyedError_fn).call(this);
702
1130
  }
@@ -742,8 +1170,7 @@ getOrCreateLoadState_fn = function() {
742
1170
  if (__privateGet(this, _loadingState) === state) __privateSet(this, _loadingState, void 0);
743
1171
  }
744
1172
  })();
745
- state.promise.catch(() => void 0);
746
- __privateSet(this, _loadingState, state);
1173
+ taskPromise.then(resolveLoad, rejectLoad);
747
1174
  return state;
748
1175
  };
749
1176
  destroyLoadStateTask_fn = function(state) {
@@ -754,10 +1181,9 @@ destroyLoadStateTask_fn = function(state) {
754
1181
  return state.destroyPromise;
755
1182
  };
756
1183
  awaitWithSignal_fn = async function(promise, signal, deadlineMs) {
757
- __privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, signal);
758
- __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
759
1184
  return await new Promise((resolve, reject) => {
760
1185
  let settled = false;
1186
+ let timer;
761
1187
  const onAbort = () => settle(() => reject(__privateMethod(this, _PDFReader_instances, createAbortedError_fn).call(this)));
762
1188
  const onDestroy = () => settle(() => reject(__privateMethod(this, _PDFReader_instances, createDestroyedError_fn).call(this)));
763
1189
  const cleanup = () => {
@@ -771,33 +1197,27 @@ awaitWithSignal_fn = async function(promise, signal, deadlineMs) {
771
1197
  cleanup();
772
1198
  callback();
773
1199
  };
774
- const timer = deadlineMs === void 0 ? void 0 : setTimeout(() => settle(() => reject(__privateMethod(this, _PDFReader_instances, createDeadlineExceededError_fn).call(this))), deadlineMs);
775
- signal?.addEventListener("abort", onAbort, { once: true });
776
- __privateGet(this, _destroyController).signal.addEventListener("abort", onDestroy, { once: true });
777
1200
  promise.then(
778
- (value) => settle(() => resolve(value)),
779
- (error) => settle(() => reject(error))
1201
+ (value) => {
1202
+ if (settled) return;
1203
+ settle(() => resolve(value));
1204
+ },
1205
+ (error) => {
1206
+ if (settled) return;
1207
+ settle(() => reject(error));
1208
+ }
780
1209
  ).catch(() => void 0);
1210
+ signal?.addEventListener("abort", onAbort, { once: true });
1211
+ __privateGet(this, _destroyController).signal.addEventListener("abort", onDestroy, { once: true });
1212
+ if (signal?.aborted) onAbort();
1213
+ else if (__privateGet(this, _destroyed)) onDestroy();
1214
+ if (!settled && deadlineMs !== void 0) {
1215
+ timer = setTimeout(() => settle(() => reject(__privateMethod(this, _PDFReader_instances, createDeadlineExceededError_fn).call(this))), deadlineMs);
1216
+ }
781
1217
  });
782
1218
  };
783
1219
  awaitWithDestroy_fn = async function(promise) {
784
- __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
785
- return await new Promise((resolve, reject) => {
786
- let settled = false;
787
- const onDestroy = () => settle(() => reject(__privateMethod(this, _PDFReader_instances, createDestroyedError_fn).call(this)));
788
- const cleanup = () => __privateGet(this, _destroyController).signal.removeEventListener("abort", onDestroy);
789
- const settle = (callback) => {
790
- if (settled) return;
791
- settled = true;
792
- cleanup();
793
- callback();
794
- };
795
- __privateGet(this, _destroyController).signal.addEventListener("abort", onDestroy, { once: true });
796
- Promise.resolve(promise).then(
797
- (value) => settle(() => resolve(value)),
798
- (error) => settle(() => reject(error))
799
- );
800
- });
1220
+ return __privateMethod(this, _PDFReader_instances, awaitWithSignal_fn).call(this, Promise.resolve(promise));
801
1221
  };
802
1222
  createAbortedError_fn = function() {
803
1223
  return new PdfReaderError("ABORTED", "PDF operation was aborted.");
@@ -823,23 +1243,28 @@ throwIfAborted_fn = function(signal) {
823
1243
  if (signal?.aborted) throw __privateMethod(this, _PDFReader_instances, createAbortedError_fn).call(this);
824
1244
  };
825
1245
  resolveLoadOptions_fn = function(options) {
826
- if (!options) return {};
827
- if (__privateMethod(this, _PDFReader_instances, isAbortSignal_fn).call(this, options)) return { signal: options };
828
- if (options.deadlineMs !== void 0 && (!Number.isFinite(options.deadlineMs) || options.deadlineMs <= 0)) {
829
- throw new PdfReaderError("INVALID_OPTION", "deadlineMs must be a positive finite number.");
1246
+ if (options === void 0) return {};
1247
+ if (options === null || typeof options !== "object" || Array.isArray(options)) {
1248
+ throw new PdfReaderError("INVALID_OPTION", "Load options must be an object or an AbortSignal.");
830
1249
  }
831
- return options;
832
- };
833
- isAbortSignal_fn = function(value) {
834
- return "aborted" in value && "addEventListener" in value && "removeEventListener" in value;
1250
+ if ("aborted" in options || "addEventListener" in options || "removeEventListener" in options) {
1251
+ return { signal: resolveSignal(options) };
1252
+ }
1253
+ const candidate = options;
1254
+ const signal = resolveSignal(candidate.signal);
1255
+ const deadlineMs = candidate.deadlineMs;
1256
+ if (deadlineMs !== void 0) {
1257
+ if (!Number.isFinite(deadlineMs) || deadlineMs <= 0 || deadlineMs > MAX_TIMER_MS) {
1258
+ throw new PdfReaderError(
1259
+ "INVALID_OPTION",
1260
+ `deadlineMs must be a positive finite number not exceeding ${MAX_TIMER_MS}.`
1261
+ );
1262
+ }
1263
+ }
1264
+ return { signal, deadlineMs };
835
1265
  };
836
1266
  enforceSourcePolicy_fn = function(source) {
837
- if (source.byteLength !== void 0 && __privateGet(this, _limits).maxSourceBytes !== void 0 && source.byteLength > __privateGet(this, _limits).maxSourceBytes) {
838
- throw new PdfReaderError(
839
- "SOURCE_LIMIT_EXCEEDED",
840
- `PDF source is ${source.byteLength} bytes; limit is ${__privateGet(this, _limits).maxSourceBytes}.`
841
- );
842
- }
1267
+ __privateMethod(this, _PDFReader_instances, throwIfOverSourceLimit_fn).call(this, source.rawSource.data);
843
1268
  if (!__privateGet(this, _sourcePolicy)) return;
844
1269
  try {
845
1270
  const result = __privateGet(this, _sourcePolicy).call(this, source);
@@ -853,14 +1278,30 @@ enforceSourcePolicy_fn = function(source) {
853
1278
  throw new PdfReaderError("SOURCE_POLICY_VIOLATION", "PDF source rejected by sourcePolicy.");
854
1279
  }
855
1280
  };
1281
+ /**
1282
+ * Rejects a synchronously measurable over-limit byte source.
1283
+ *
1284
+ * Length is read without scanning or copying element content, so an
1285
+ * oversized array fails on its `length` before any entry is visited.
1286
+ */
1287
+ throwIfOverSourceLimit_fn = function(data) {
1288
+ if (__privateGet(this, _limits).maxSourceBytes === void 0) return;
1289
+ const byteLength = __privateMethod(this, _PDFReader_instances, knownByteLength_fn).call(this, data);
1290
+ if (byteLength !== void 0 && byteLength > __privateGet(this, _limits).maxSourceBytes) {
1291
+ throw new PdfReaderError(
1292
+ "SOURCE_LIMIT_EXCEEDED",
1293
+ `PDF source is ${byteLength} bytes; limit is ${__privateGet(this, _limits).maxSourceBytes}.`
1294
+ );
1295
+ }
1296
+ };
856
1297
  createSourceSnapshot_fn = function() {
857
1298
  const kind = __privateMethod(this, _PDFReader_instances, sourceKind_fn).call(this, __privateGet(this, _source));
858
- const pdfJsSource = __privateMethod(this, _PDFReader_instances, toPdfJsSource_fn).call(this, __privateGet(this, _source));
859
- const info = __privateMethod(this, _PDFReader_instances, inspectSource_fn).call(this, pdfJsSource, kind);
1299
+ const headers = __privateMethod(this, _PDFReader_instances, snapshotHttpHeaders_fn).call(this, __privateMethod(this, _PDFReader_instances, readRawHttpHeaders_fn).call(this, __privateGet(this, _source)));
1300
+ const pdfJsSource = __privateMethod(this, _PDFReader_instances, toPdfJsSource_fn).call(this, __privateGet(this, _source), headers);
1301
+ const info = __privateMethod(this, _PDFReader_instances, inspectSource_fn).call(this, pdfJsSource, kind, headers);
860
1302
  return { pdfJsSource, info };
861
1303
  };
862
- inspectSource_fn = function(source, kind) {
863
- const headers = __privateMethod(this, _PDFReader_instances, readHttpHeaders_fn).call(this, source.httpHeaders);
1304
+ inspectSource_fn = function(source, kind, headers) {
864
1305
  const info = {
865
1306
  rawSource: source,
866
1307
  kind,
@@ -878,7 +1319,7 @@ sourceKind_fn = function(source) {
878
1319
  if (source instanceof ArrayBuffer || __privateMethod(this, _PDFReader_instances, isPdfTypedArray_fn).call(this, source) || Array.isArray(source)) return "bytes";
879
1320
  return "document-init-parameters";
880
1321
  };
881
- toPdfJsSource_fn = function(source) {
1322
+ toPdfJsSource_fn = function(source, headers) {
882
1323
  if (typeof source === "string") {
883
1324
  return __privateMethod(this, _PDFReader_instances, freezeSource_fn).call(this, { url: source });
884
1325
  }
@@ -890,23 +1331,47 @@ toPdfJsSource_fn = function(source) {
890
1331
  }
891
1332
  const snapshot = {};
892
1333
  for (const key of Reflect.ownKeys(source)) {
1334
+ if (key === "httpHeaders") continue;
893
1335
  const descriptor = Object.getOwnPropertyDescriptor(source, key);
894
1336
  if (!descriptor?.enumerable) continue;
895
- snapshot[key] = source[key];
1337
+ const value = source[key];
1338
+ Object.defineProperty(snapshot, key, {
1339
+ value,
1340
+ writable: true,
1341
+ enumerable: true,
1342
+ configurable: true
1343
+ });
896
1344
  }
897
1345
  if (typeof URL !== "undefined" && snapshot.url instanceof URL) snapshot.url = snapshot.url.toString();
1346
+ if (headers.httpHeaders !== void 0) {
1347
+ Object.defineProperty(snapshot, "httpHeaders", {
1348
+ value: headers.httpHeaders,
1349
+ writable: false,
1350
+ enumerable: true,
1351
+ configurable: false
1352
+ });
1353
+ }
898
1354
  return __privateMethod(this, _PDFReader_instances, freezeSource_fn).call(this, snapshot);
899
1355
  };
900
1356
  freezeSource_fn = function(source) {
901
1357
  return Object.freeze(source);
902
1358
  };
1359
+ isFetchHeaders_fn = function(value) {
1360
+ if (typeof Headers !== "undefined" && value instanceof Headers) return true;
1361
+ try {
1362
+ if (Object.prototype.toString.call(value) === "[object Headers]") return true;
1363
+ } catch {
1364
+ }
1365
+ return false;
1366
+ };
903
1367
  isPdfTypedArray_fn = function(source) {
904
1368
  return ArrayBuffer.isView(source) && !(source instanceof DataView);
905
1369
  };
906
1370
  knownByteLength_fn = function(value) {
1371
+ if (typeof value === "string") return value.length;
907
1372
  if (value instanceof ArrayBuffer) return value.byteLength;
908
1373
  if (ArrayBuffer.isView(value)) return value.byteLength;
909
- if (Array.isArray(value) && value.every((entry) => Number.isFinite(entry))) return value.length;
1374
+ if (Array.isArray(value)) return value.length;
910
1375
  return void 0;
911
1376
  };
912
1377
  readUrl_fn = function(value) {
@@ -914,34 +1379,57 @@ readUrl_fn = function(value) {
914
1379
  if (typeof URL !== "undefined" && value instanceof URL) return value.toString();
915
1380
  return void 0;
916
1381
  };
917
- readHttpHeaders_fn = function(value) {
918
- if (!value || typeof value !== "object") return { hasHttpHeaders: false };
919
- const entries = [];
920
- try {
921
- for (const [name, headerValue] of Object.entries(value)) {
922
- if (typeof headerValue === "string") entries.push([name, headerValue]);
923
- }
924
- if ("forEach" in value && typeof value.forEach === "function") {
925
- value.forEach((headerValue, name) => {
926
- if (typeof name === "string" && typeof headerValue === "string") entries.push([name, headerValue]);
927
- });
928
- }
929
- } catch {
1382
+ readRawHttpHeaders_fn = function(source) {
1383
+ if (typeof source !== "object" || source === null) return void 0;
1384
+ if (source instanceof ArrayBuffer || ArrayBuffer.isView(source) || Array.isArray(source)) return void 0;
1385
+ return source.httpHeaders;
1386
+ };
1387
+ /**
1388
+ * Stabilizes effective HTTP headers into one owned, frozen record.
1389
+ *
1390
+ * Enumeration mirrors the installed PDF.js `createHeaders` transport
1391
+ * (`for...in`, inherited enumerable string keys, `undefined` skipped) so
1392
+ * the approved diagnostic and the value handed to `getDocument()` agree.
1393
+ * Remaining values are coerced with `String()` as `Headers.append` would.
1394
+ * Non-plain containers (`Headers`, `Map`, class instances) intentionally
1395
+ * yield no value entries — matching what PDF.js `for...in` consumption
1396
+ * observes — while `hasHttpHeaders` stays `true` so policies gating only
1397
+ * on header presence keep blocking. A throwing getter propagates so the
1398
+ * load fails closed before policy approval. The caller's container is
1399
+ * never frozen or mutated.
1400
+ */
1401
+ snapshotHttpHeaders_fn = function(value) {
1402
+ if (value === null || value === void 0) return { hasHttpHeaders: false };
1403
+ if (typeof value !== "object" && typeof value !== "function") return { hasHttpHeaders: false };
1404
+ if (__privateMethod(this, _PDFReader_instances, isFetchHeaders_fn).call(this, value)) {
930
1405
  return { hasHttpHeaders: true };
931
1406
  }
1407
+ const entries = [];
1408
+ for (const name in value) {
1409
+ const headerValue = value[name];
1410
+ if (headerValue === void 0) continue;
1411
+ entries.push([name, String(headerValue)]);
1412
+ }
932
1413
  return {
933
1414
  hasHttpHeaders: true,
1415
+ // Object.fromEntries installs `__proto__` as an own data property
1416
+ // (no prototype mutation); the frozen record is shared by policy info
1417
+ // and PDF.js loading params. Opaque worker/factory fields elsewhere in
1418
+ // the snapshot stay borrowed by reference — only headers stabilize.
934
1419
  httpHeaders: entries.length > 0 ? Object.freeze(Object.fromEntries(entries)) : void 0
935
1420
  };
936
1421
  };
937
- validateLimits_fn = function() {
938
- for (const [name, value] of Object.entries(__privateGet(this, _limits))) {
939
- if (value === void 0) continue;
940
- if (!Number.isSafeInteger(value) || value <= 0) {
941
- throw new PdfReaderError("INVALID_OPTION", `${name} must be a positive safe integer.`);
942
- }
1422
+
1423
+ // src/pdfTextToString.ts
1424
+ function pdfTextToString(content) {
1425
+ const fragments = [];
1426
+ for (const item of content.items) {
1427
+ if (!("str" in item)) continue;
1428
+ fragments.push(item.str);
1429
+ if (item.hasEOL === true) fragments.push("\n");
943
1430
  }
944
- };
1431
+ return fragments.join("");
1432
+ }
945
1433
 
946
1434
  // src/source.ts
947
1435
  function pdfUrlSource(url, options = {}) {
@@ -963,5 +1451,6 @@ export {
963
1451
  PDFReader,
964
1452
  PdfReaderError,
965
1453
  configurePdfWorker,
1454
+ pdfTextToString,
966
1455
  pdfUrlSource
967
1456
  };