@web-ts-toolkit/pdf-reader 0.40.1 → 0.42.0
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/README.md +63 -23
- package/index.d.mts +46 -10
- package/index.mjs +372 -179
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,7 +8,9 @@ Resource-safe browser PDF rendering, text extraction, and best-effort embedded-i
|
|
|
8
8
|
pnpm add @web-ts-toolkit/pdf-reader pdfjs-dist
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
`pdfjs-dist` is a peer dependency. This package is browser-oriented, ESM-only, and requires DOM canvas APIs.
|
|
11
|
+
`pdfjs-dist` is a peer dependency. This package is browser-oriented, ESM-only, and requires DOM canvas APIs. Automated browser compatibility coverage currently runs the real PDF.js worker and browser canvas suite in Headless Chromium only. Firefox and WebKit are not claimed by the automated package test boundary until their Playwright browsers are added to `vitest.browser.config.mts` and pass the same fixture suite in CI.
|
|
12
|
+
|
|
13
|
+
The supported PDF.js compatibility contract is the `pdfjs-dist` `~6.2.108` peer minor. The package test and real-browser fixture suite run against `6.2.108`; future `6.x` minors are not admitted until rendering and embedded-image compatibility are exercised in a reproducible browser matrix.
|
|
12
14
|
|
|
13
15
|
Canonical imports use named exports from the package root. There is no default export, no supported deep import, and no supported CommonJS `require()` entry.
|
|
14
16
|
|
|
@@ -16,20 +18,22 @@ Canonical imports use named exports from the package root. There is no default e
|
|
|
16
18
|
|
|
17
19
|
This follow-up release keeps the small named-export API but intentionally does not preserve the older application-local `PDFReader` names and defaults.
|
|
18
20
|
|
|
19
|
-
| Area | Previous contract | New contract and migration
|
|
20
|
-
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
21
|
-
| Imports and worker setup | The application-local reader was typically imported as a default export and could hide PDF.js worker wiring inside app code. | Import named exports from `@web-ts-toolkit/pdf-reader` and call `configurePdfWorker(...)` in application code. There is no default export, no deep import, and no package-owned worker asset emission.
|
|
22
|
-
| Load cancellation and teardown | Concurrent callers could accidentally tear down a shared load, and lifecycle states were mostly implicit. | Concurrent `load()` callers share one PDF.js task, but aborting one caller rejects only that caller. Use `reader.state` for `new` / `loading` / `loaded` / `iterating` / `failed` / `destroyed
|
|
23
|
-
| Load options | `load()` previously accepted only an `AbortSignal`-style cancellation pattern. | `load()` still accepts an `AbortSignal`, and now also accepts `{ signal, deadlineMs }`. `deadlineMs` is caller-local and rejects with `DEADLINE_EXCEEDED` without cancelling unrelated waiters.
|
|
24
|
-
| Page image results | Older code used top-level page result fields such as `dataURL`, `dataUrl`, `mimeType`, and `isPNG`. | Read `page.pageImage` instead. `pageImageOutput: 'data-url'` returns `{ kind: 'data-url', mimeType, dataUrl }`; `pageImageOutput: 'blob'` returns `{ kind: 'blob', mimeType, blob }`.
|
|
25
|
-
| Convert option names | Older application-local option names included `getText`, `getDataURL`, `getImages`, and reader `config`. | Use `includeText`, `includePageImage`, `includeEmbeddedImages`, and constructor `options`. Unsupported legacy names are intentionally rejected by the type surface instead of being kept as aliases.
|
|
21
|
+
| Area | Previous contract | New contract and migration |
|
|
22
|
+
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
23
|
+
| Imports and worker setup | The application-local reader was typically imported as a default export and could hide PDF.js worker wiring inside app code. | Import named exports from `@web-ts-toolkit/pdf-reader` and call `configurePdfWorker(...)` in application code. There is no default export, no deep import, and no package-owned worker asset emission. |
|
|
24
|
+
| Load cancellation and teardown | Concurrent callers could accidentally tear down a shared load, and lifecycle states were mostly implicit. | Concurrent `load()` callers share one PDF.js task, but aborting one caller rejects only that caller. The `PDFDocumentProxy` returned by `load()` is borrowed; inspect it if needed, but call `reader.destroy()` instead of `document.destroy()`. Use `reader.state` for `new` / `loading` / `loaded` / `iterating` / `failed` / `destroyed`. |
|
|
25
|
+
| Load options | `load()` previously accepted only an `AbortSignal`-style cancellation pattern. | `load()` still accepts an `AbortSignal`, and now also accepts `{ signal, deadlineMs }`. `deadlineMs` is caller-local and rejects with `DEADLINE_EXCEEDED` without cancelling unrelated waiters. |
|
|
26
|
+
| Page image results | Older code used top-level page result fields such as `dataURL`, `dataUrl`, `mimeType`, and `isPNG`. | Read `page.pageImage` instead. `pageImageOutput: 'data-url'` returns `{ kind: 'data-url', mimeType, dataUrl }`; `pageImageOutput: 'blob'` returns `{ kind: 'blob', mimeType, blob }`. |
|
|
27
|
+
| Convert option names | Older application-local option names included `getText`, `getDataURL`, `getImages`, and reader `config`. | Use `includeText`, `includePageImage`, `includeEmbeddedImages`, and constructor `options`. Unsupported legacy names are intentionally rejected by the type surface instead of being kept as aliases. |
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
Release-note evidence for this follow-up is recorded in this README, the website package docs, and the task completion record. `CHANGELOG.md` was intentionally not edited for PDFR2-05 per maintainer instruction.
|
|
28
30
|
|
|
29
31
|
## Worker Setup
|
|
30
32
|
|
|
31
33
|
Configure the PDF.js worker explicitly in your application. The package does not use bundler-specific `?url` imports and does not mutate PDF.js globals merely by being imported.
|
|
32
34
|
|
|
35
|
+
`configurePdfWorker(...)` writes PDF.js application-global worker state (`GlobalWorkerOptions.workerSrc` or `workerPort`) for the current JavaScript realm. Use it once at application startup only when every PDF.js consumer can share the same setting. Calling it again replaces the previous global URL-or-port setting and can collide with other direct or transitive PDF.js users.
|
|
36
|
+
|
|
33
37
|
Vite example:
|
|
34
38
|
|
|
35
39
|
```ts
|
|
@@ -47,7 +51,23 @@ import { configurePdfWorker } from '@web-ts-toolkit/pdf-reader';
|
|
|
47
51
|
configurePdfWorker(new Worker(new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url), { type: 'module' }));
|
|
48
52
|
```
|
|
49
53
|
|
|
50
|
-
Worker URL emission differs by bundler. Keep this setup in application code where the bundler owns asset handling.
|
|
54
|
+
Worker URL emission differs by bundler. Keep this setup in application code where the bundler owns asset handling. If you pass an existing `Worker`, you still own terminating it.
|
|
55
|
+
|
|
56
|
+
For per-document isolation, skip the global helper and pass a caller-created PDF.js `PDFWorker` through the source object. The public `PdfSource` type accepts PDF.js `DocumentInitParameters`, including its `worker` field:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { PDFWorker } from 'pdfjs-dist';
|
|
60
|
+
import { PDFReader } from '@web-ts-toolkit/pdf-reader';
|
|
61
|
+
|
|
62
|
+
const worker = new PDFWorker({ name: 'tenant-a' });
|
|
63
|
+
const reader = new PDFReader({ data: bytes, worker });
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
await reader.load();
|
|
67
|
+
} finally {
|
|
68
|
+
await reader.destroy();
|
|
69
|
+
}
|
|
70
|
+
```
|
|
51
71
|
|
|
52
72
|
## Quick Start
|
|
53
73
|
|
|
@@ -88,7 +108,7 @@ const reader = new PDFReader(
|
|
|
88
108
|
);
|
|
89
109
|
```
|
|
90
110
|
|
|
91
|
-
Call `load()` before `pages()` or `convert()`. Concurrent `load()` callers share one PDF.js loading task, but aborting one caller only rejects that caller; call `destroy()` to
|
|
111
|
+
Call `load()` before `pages()` or `convert()`. Concurrent `load()` callers share one PDF.js loading task, but aborting one caller only rejects that caller. The fulfilled `load()` value is the reader-owned PDF.js `PDFDocumentProxy` exposed as a borrowed interoperability handle. You may inspect metadata or call supported PDF.js read methods, but do not call `document.destroy()` on it while the reader owns the lifecycle; call `reader.destroy()` instead. External proxy destruction is unsupported because PDF.js does not provide a reliable ownership notification to keep `reader.state` authoritative after that mutation. Page operations are reader-serial: at most one executing `pages()` iterator or `convert()` call may be active per `PDFReader`. A second overlapping page operation fails fast with `OPERATION_IN_PROGRESS` before acquiring a page proxy or allocating a canvas; create a separate reader if you need independent concurrent conversions. Calling `pages()` only creates an iterator object and does not reserve the reader until the iterator starts executing. `destroy()` is idempotent, permanently closes the reader, cancels active renders, and causes in-flight lifecycle work to reject with `DESTROYED`. It waits for PDF.js loading/document destruction, but active `pages()` or `convert()` calls finish their own page and canvas cleanup as those operation promises settle; await those operations if you need to observe that cleanup.
|
|
92
112
|
|
|
93
113
|
## Reader State And Ownership
|
|
94
114
|
|
|
@@ -98,20 +118,22 @@ Call `load()` before `pages()` or `convert()`. Concurrent `load()` callers share
|
|
|
98
118
|
| ----------- | ------------------------------------------------------------------------------------ | ------------------------------- |
|
|
99
119
|
| `new` | Reader constructed but `load()` has not started. | `loading`, `destroyed` |
|
|
100
120
|
| `loading` | One shared PDF.js loading task is in flight. | `loaded`, `failed`, `destroyed` |
|
|
101
|
-
| `loaded` | The PDF.js document is loaded and no page
|
|
102
|
-
| `iterating` | `pages()` or `convert()`
|
|
121
|
+
| `loaded` | The PDF.js document is loaded and no page operation is active. | `iterating`, `destroyed` |
|
|
122
|
+
| `iterating` | One executing `pages()` iterator or `convert()` call owns the reader page operation. | `loaded`, `destroyed` |
|
|
103
123
|
| `failed` | The most recent load attempt rejected. A later `load()` call starts a fresh attempt. | `loading`, `destroyed` |
|
|
104
124
|
| `destroyed` | `destroy()` completed or irrevocably won a race. The reader cannot be reused. | none |
|
|
105
125
|
|
|
106
126
|
Ownership and lifetime rules:
|
|
107
127
|
|
|
108
|
-
- Source bytes: the package
|
|
109
|
-
-
|
|
128
|
+
- Source bytes: the package snapshots the top-level PDF.js loading parameters once before `sourcePolicy(...)` runs and passes that same snapshot to PDF.js after approval. PDF.js may transfer typed-array ownership to its worker during `load()`.
|
|
129
|
+
- Loaded document proxy: `load()` returns the borrowed `PDFDocumentProxy` owned by the reader. Callers may use supported PDF.js read APIs on it, but external `document.destroy()` is unsupported and can leave `reader.state` reporting `loaded` until a later PDF.js method fails.
|
|
130
|
+
- Public aliases: `LoadedPdfDocument` and `LoadedPdfPage` intentionally expose PDF.js proxy types for adjacent interoperability code. The reader API returns `LoadedPdfDocument` from `load()` and package `PageResult` objects from `pages()`/`convert()`; it does not return raw page proxies.
|
|
131
|
+
- Pages: one executing `pages()` iterator or `convert()` call owns the reader-level page operation at a time. Within that operation, the package owns one live `PDFPageProxy` only while processing the current page and calls `page.cleanup()` before yielding the result and on every error path.
|
|
110
132
|
- Page canvases: the package owns temporary render/encode canvases and always zeroes their dimensions after each settle path.
|
|
111
133
|
- `Blob` page images: the returned `Blob` belongs to the caller. If you create an object URL, revoke it yourself.
|
|
112
134
|
- Data URLs: returned strings belong to the caller. They are copies; the package does not retain the temporary canvas after encoding.
|
|
113
|
-
- Embedded-image data URLs: these are best-effort PNG copies. Unsupported image operators/layouts are skipped with `logger.warn(...)` diagnostics.
|
|
114
|
-
- Documents and workers: the reader owns the loaded `PDFDocumentProxy` and the shared loading task it created. Call `destroy()` in your own `finally` block to terminate document-side resources. When `configurePdfWorker(...)` receives a worker URL, PDF.js owns any worker instances it creates from that URL. When `configurePdfWorker(...)` receives an existing `Worker`, the caller still owns terminating that worker.
|
|
135
|
+
- Embedded-image data URLs: these are best-effort PNG copies. Repeated image XObject placements on one page share one encoded PNG string while each returned image keeps its own transform and coordinates. Unsupported image operators/layouts are skipped with `logger.warn(...)` diagnostics.
|
|
136
|
+
- Documents and workers: the reader owns the loaded `PDFDocumentProxy` and the shared loading task it created. Call `destroy()` in your own `finally` block to terminate document-side resources. `configurePdfWorker(...)` mutates PDF.js application-global worker state; use a per-source PDF.js `PDFWorker` when separate readers need isolated worker configuration. When `configurePdfWorker(...)` receives a worker URL, PDF.js owns any worker instances it creates from that URL. When `configurePdfWorker(...)` receives an existing `Worker`, the caller still owns terminating that worker.
|
|
115
137
|
|
|
116
138
|
## Options
|
|
117
139
|
|
|
@@ -137,6 +159,8 @@ const pages = await reader.convert({
|
|
|
137
159
|
- `includeEmbeddedImages`: inspect the operator stream for raster images; defaults to `false`.
|
|
138
160
|
- `signal`: cancels active rendering and stops before subsequent expensive operations.
|
|
139
161
|
|
|
162
|
+
Constructor `canvasFactory`, when supplied, must create a fresh DOM `HTMLCanvasElement` for each package-owned render or embedded-image copy. Non-DOM canvas objects are not part of the documented runtime contract unless they satisfy the browser `HTMLCanvasElement` behavior used by PDF.js and this package.
|
|
163
|
+
|
|
140
164
|
`pageImage` is a discriminated union so the output mode and payload cannot contradict each other:
|
|
141
165
|
|
|
142
166
|
```ts
|
|
@@ -158,7 +182,7 @@ This package returns `Blob` objects directly rather than object URLs so callers
|
|
|
158
182
|
|
|
159
183
|
## Performance And Concurrency
|
|
160
184
|
|
|
161
|
-
Page work
|
|
185
|
+
Page work is serial per reader in the runtime API. A loaded `PDFReader` permits one executing `pages()` iterator or `convert()` call at a time and rejects overlap with `OPERATION_IN_PROGRESS`; it does not queue page operations. PDFR-07 adds a real-browser benchmark under `packages/pdf-reader/benchmark/` that compares the current `pages()` behavior against an external page-level scheduler with a hard concurrency bound of `2`, deterministic output ordering, and abort handling.
|
|
162
186
|
|
|
163
187
|
Run it with:
|
|
164
188
|
|
|
@@ -170,9 +194,11 @@ The recorded local run on Headless Chromium `151.0.7922.34` showed bounded overl
|
|
|
170
194
|
|
|
171
195
|
The package still does not expose a concurrency option because that evidence is not yet enough to set a safe browser memory/backpressure contract. See the benchmark README and the PDFR-07 completion evidence for the recorded browser/hardware context and full results.
|
|
172
196
|
|
|
197
|
+
Overlapping `pages()` or `convert()` calls on the same reader are newly rejected rather than being allowed to multiply active page/canvas ownership. Sequential reuse after the prior operation finishes is still supported.
|
|
198
|
+
|
|
173
199
|
## Resource Limits
|
|
174
200
|
|
|
175
|
-
Defaults reject documents above 1,000 pages, rendered pages above 40 megapixels,
|
|
201
|
+
Defaults reject documents above 1,000 pages, one page's retained text above 50,000 items or 5,000,000 string code units, embedded-image extraction above 100,000 PDF.js operators, rendered pages above 40 megapixels, one copied embedded image above 25 megapixels, one page above 1,000 extracted embedded images, and one page above 100 megapixels of aggregate decoded embedded-image pixels.
|
|
176
202
|
|
|
177
203
|
```ts
|
|
178
204
|
import { PDFReader, PdfReaderError } from '@web-ts-toolkit/pdf-reader';
|
|
@@ -181,8 +207,13 @@ const reader = new PDFReader(bytes, {
|
|
|
181
207
|
limits: {
|
|
182
208
|
maxSourceBytes: 25_000_000,
|
|
183
209
|
maxDocumentPages: 250,
|
|
210
|
+
maxTextItems: 10_000,
|
|
211
|
+
maxTextCodeUnits: 1_000_000,
|
|
212
|
+
maxOperatorCount: 20_000,
|
|
184
213
|
maxCanvasPixels: 20_000_000,
|
|
185
214
|
maxEmbeddedImagePixels: 10_000_000,
|
|
215
|
+
maxEmbeddedImages: 250,
|
|
216
|
+
maxEmbeddedImagePixelsTotal: 40_000_000,
|
|
186
217
|
},
|
|
187
218
|
sourcePolicy(source) {
|
|
188
219
|
if (source.url && !source.url.startsWith('https://static.example.com/')) {
|
|
@@ -207,24 +238,28 @@ Limits reduce accidental or malicious memory pressure but are not a complete san
|
|
|
207
238
|
|
|
208
239
|
`maxSourceBytes` is enforced only when the package can know the byte length synchronously, including direct typed arrays, array buffers, number arrays, and `DocumentInitParameters.data`. It does not apply to remote downloads.
|
|
209
240
|
|
|
210
|
-
|
|
241
|
+
Text and operator limits are enforced after PDF.js returns a complete page text content or operator list, but before the package retains the text result or traverses the operator list. These checks bound package-owned processing and returned results; they cannot prevent PDF.js from initially constructing those PDF.js structures.
|
|
242
|
+
|
|
243
|
+
Embedded-image limits are enforced before allocating or encoding the next extracted embedded-image canvas. `maxEmbeddedImagePixels` applies to one copied image, `maxEmbeddedImages` applies to extracted images returned for one page, and `maxEmbeddedImagePixelsTotal` applies to aggregate decoded pixels copied for one page, including repeated references to the same underlying PDF image object. Repeated image XObject references reuse one encoded data URL per page only after these aggregate placement limits pass; inline images are not cached by synthetic keys. Unsafe non-finite or overflowing image dimensions fail closed with a resource-limit error. Exact aggregate encoded PNG data-url bytes are not knowable until after browser canvas encoding, so the package does not claim a separate pre-encoding output-byte cap; use the decoded-pixel limits to bound package allocation and encoding work.
|
|
244
|
+
|
|
245
|
+
`sourcePolicy(source)` runs before `getDocument()` and can reject remote URLs, protocols, origins, credentials, headers, or any other caller-supplied loading parameters. The `source.rawSource` value is an immutable shallow snapshot of the PDF.js parameters that will be used for loading after approval. Top-level getters and later source-object mutation cannot change the approved URL, credential flag, headers reference, password, CMap/font/WASM URLs, or caller-created worker. Opaque objects such as caller-created workers and in-memory data references are preserved rather than recursively cloned.
|
|
211
246
|
|
|
212
247
|
## Security Notes
|
|
213
248
|
|
|
214
249
|
- Prefer trusted `Uint8Array` input. PDF.js may transfer ownership of typed-array data to its worker.
|
|
215
250
|
- Use `sourcePolicy` to reject remote URLs, protocols, origins, credentialed requests, or header-bearing sources before PDF.js starts network work.
|
|
216
|
-
- `configurePdfWorker(...)` mutates PDF.js global worker options only when you call it, not at module evaluation. Reconfiguring it clears the previous URL-or-port setting so stale worker globals do not win unexpectedly.
|
|
251
|
+
- `configurePdfWorker(...)` mutates PDF.js application-global worker options only when you call it, not at module evaluation. Reconfiguring it clears the previous URL-or-port setting so stale worker globals do not win unexpectedly, but the replacement can affect other PDF.js consumers in the same realm. Use a per-source PDF.js `PDFWorker` for isolation.
|
|
217
252
|
- Keep `withCredentials` disabled unless cross-origin credentials are explicitly required.
|
|
218
253
|
- Use `maxSourceBytes` for synchronously knowable in-memory sources. Remote response-byte limits still belong to application-controlled fetch/range transport because this package does not intercept PDF.js network I/O.
|
|
219
254
|
- Use `load({ deadlineMs })` or your own `AbortSignal` to bound load wait time. The package clears deadline timers and abort listeners on success, failure, caller abort, and destroy.
|
|
220
|
-
- Keep `pdfjs-dist` current because malformed-document defenses belong primarily to PDF.js.
|
|
255
|
+
- Keep `pdfjs-dist` current within the supported `~6.2.108` peer minor because malformed-document defenses belong primarily to PDF.js. Wider PDF.js minors require new package compatibility evidence before use.
|
|
221
256
|
- Treat extracted text and images as untrusted content. Do not inject text as HTML without escaping or sanitization.
|
|
222
257
|
|
|
223
258
|
## Embedded Images
|
|
224
259
|
|
|
225
260
|
Embedded-image extraction stays on the package root as an opt-in convenience (`includeEmbeddedImages: true`), but the operator traversal now lives behind one private extractor boundary instead of mixing PDF.js internals into reader lifecycle code.
|
|
226
261
|
|
|
227
|
-
The real-browser fixture suite for the supported `pdfjs-dist` peer minor (`~
|
|
262
|
+
The real-browser fixture suite for the supported `pdfjs-dist` peer minor (`~6.2.108`) characterizes inline images, image XObjects, repeated references, nested save/restore transforms, mirrored transforms, grayscale, RGB, RGBA soft-mask images, browser `ImageBitmap`-backed image objects, and nested form XObjects with their own matrices.
|
|
228
263
|
|
|
229
264
|
Standalone image-mask operators, vector graphics, patterns, and unknown PDF.js image-object layouts remain unsupported. They are skipped with `logger.warn(...)` diagnostics rather than failing the entire page. The extractor never calls `ImageBitmap.close()` because PDF.js owns bitmap lifetime through `page.cleanup()` and document destruction.
|
|
230
265
|
|
|
@@ -242,10 +277,15 @@ Validation and resource failures use `PdfReaderError` with stable codes:
|
|
|
242
277
|
- `DESTROYED`
|
|
243
278
|
- `DOCUMENT_NOT_LOADED`
|
|
244
279
|
- `IMAGE_LIMIT_EXCEEDED`
|
|
280
|
+
- `IMAGE_COUNT_LIMIT_EXCEEDED`
|
|
281
|
+
- `IMAGE_TOTAL_PIXELS_LIMIT_EXCEEDED`
|
|
245
282
|
- `INVALID_OPTION`
|
|
283
|
+
- `OPERATOR_LIMIT_EXCEEDED`
|
|
284
|
+
- `OPERATION_IN_PROGRESS`
|
|
246
285
|
- `PAGE_LIMIT_EXCEEDED`
|
|
247
286
|
- `SOURCE_LIMIT_EXCEEDED`
|
|
248
287
|
- `SOURCE_POLICY_VIOLATION`
|
|
288
|
+
- `TEXT_LIMIT_EXCEEDED`
|
|
249
289
|
- `UNSUPPORTED_ENVIRONMENT`
|
|
250
290
|
|
|
251
291
|
Package-owned errors cover invalid options, lifecycle cancellation/destroy/deadline states, resource limits, source-policy/known-byte rejections, and unsupported canvas/runtime features such as missing 2D contexts or missing `toBlob()` support.
|
package/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as pdfjs_dist from 'pdfjs-dist';
|
|
2
2
|
import { PDFDocumentProxy, PDFPageProxy, PageViewport } from 'pdfjs-dist';
|
|
3
3
|
|
|
4
|
-
type PdfReaderErrorCode = 'ABORTED' | 'CANVAS_LIMIT_EXCEEDED' | 'DEADLINE_EXCEEDED' | 'DESTROYED' | 'DOCUMENT_NOT_LOADED' | 'IMAGE_LIMIT_EXCEEDED' | 'INVALID_OPTION' | 'PAGE_LIMIT_EXCEEDED' | 'SOURCE_LIMIT_EXCEEDED' | 'SOURCE_POLICY_VIOLATION' | 'UNSUPPORTED_ENVIRONMENT';
|
|
4
|
+
type PdfReaderErrorCode = 'ABORTED' | 'CANVAS_LIMIT_EXCEEDED' | 'DEADLINE_EXCEEDED' | 'DESTROYED' | 'DOCUMENT_NOT_LOADED' | 'IMAGE_LIMIT_EXCEEDED' | 'IMAGE_COUNT_LIMIT_EXCEEDED' | 'IMAGE_TOTAL_PIXELS_LIMIT_EXCEEDED' | 'INVALID_OPTION' | 'OPERATOR_LIMIT_EXCEEDED' | 'OPERATION_IN_PROGRESS' | 'PAGE_LIMIT_EXCEEDED' | 'SOURCE_LIMIT_EXCEEDED' | 'SOURCE_POLICY_VIOLATION' | 'TEXT_LIMIT_EXCEEDED' | 'UNSUPPORTED_ENVIRONMENT';
|
|
5
5
|
/** An actionable package error with a stable machine-readable code. */
|
|
6
6
|
declare class PdfReaderError extends Error {
|
|
7
7
|
readonly code: PdfReaderErrorCode;
|
|
@@ -36,17 +36,27 @@ interface PdfReaderLimits {
|
|
|
36
36
|
maxSourceBytes?: number;
|
|
37
37
|
/** Maximum number of document pages accepted after loading. Defaults to 1,000. */
|
|
38
38
|
maxDocumentPages?: number;
|
|
39
|
+
/** Maximum PDF.js text items retained for one page. Defaults to 50,000. */
|
|
40
|
+
maxTextItems?: number;
|
|
41
|
+
/** Maximum string code units retained across one page's text items. Defaults to 5,000,000. */
|
|
42
|
+
maxTextCodeUnits?: number;
|
|
43
|
+
/** Maximum PDF.js operators traversed for embedded-image extraction on one page. Defaults to 100,000. */
|
|
44
|
+
maxOperatorCount?: number;
|
|
39
45
|
/** Maximum pixels allocated for one rendered page. Defaults to 40 megapixels. */
|
|
40
46
|
maxCanvasPixels?: number;
|
|
41
47
|
/** Maximum pixels allocated while copying one embedded image. Defaults to 25 megapixels. */
|
|
42
48
|
maxEmbeddedImagePixels?: number;
|
|
49
|
+
/** Maximum extracted embedded images returned for one page. Defaults to 1,000. */
|
|
50
|
+
maxEmbeddedImages?: number;
|
|
51
|
+
/** Maximum decoded embedded-image pixels copied across one page. Defaults to 100 megapixels. */
|
|
52
|
+
maxEmbeddedImagePixelsTotal?: number;
|
|
43
53
|
}
|
|
44
54
|
interface PdfReaderLogger {
|
|
45
55
|
warn(message: string, error?: unknown): void;
|
|
46
56
|
}
|
|
47
57
|
interface PdfReaderSourceInfo {
|
|
48
|
-
/**
|
|
49
|
-
rawSource:
|
|
58
|
+
/** Immutable shallow snapshot that will be passed to PDF.js if policy allows it. */
|
|
59
|
+
rawSource: Readonly<PdfDocumentInitParameters>;
|
|
50
60
|
/** Broad source shape so callers can distinguish direct bytes from URL-based inputs. */
|
|
51
61
|
kind: 'bytes' | 'url' | 'document-init-parameters';
|
|
52
62
|
/** Normalized URL string when one is present on the source. */
|
|
@@ -60,9 +70,9 @@ interface PdfReaderSourceInfo {
|
|
|
60
70
|
/** Whether PDF.js would send credentials for URL-based loading. */
|
|
61
71
|
withCredentials: boolean;
|
|
62
72
|
}
|
|
63
|
-
type PdfReaderSourcePolicy = (source: PdfReaderSourceInfo) => void |
|
|
73
|
+
type PdfReaderSourcePolicy = (source: PdfReaderSourceInfo) => void | PromiseLike<void>;
|
|
64
74
|
interface PdfReaderOptions {
|
|
65
|
-
/**
|
|
75
|
+
/** Creates a fresh DOM `HTMLCanvasElement` for each package-owned page or embedded-image render. */
|
|
66
76
|
canvasFactory?: () => HTMLCanvasElement;
|
|
67
77
|
/** Optional warning sink. The package never writes directly to the console. */
|
|
68
78
|
logger?: PdfReaderLogger;
|
|
@@ -127,7 +137,21 @@ interface PageResult {
|
|
|
127
137
|
/** Best-effort embedded raster images extracted from the current page. */
|
|
128
138
|
images: ExtractedImage[];
|
|
129
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Borrowed PDF.js document proxy returned by `PDFReader.load()`.
|
|
142
|
+
*
|
|
143
|
+
* The reader owns lifecycle teardown. Callers may inspect metadata and call
|
|
144
|
+
* supported PDF.js read methods, but must not call `destroy()` on this proxy
|
|
145
|
+
* while the `PDFReader` owns it. Use `PDFReader.destroy()` instead.
|
|
146
|
+
*/
|
|
130
147
|
type LoadedPdfDocument = PDFDocumentProxy;
|
|
148
|
+
/**
|
|
149
|
+
* PDF.js page proxy alias for consumers interoperating with PDF.js APIs.
|
|
150
|
+
*
|
|
151
|
+
* `PDFReader.pages()` returns package `PageResult` objects, not raw page
|
|
152
|
+
* proxies. This alias is exported so applications can type adjacent PDF.js
|
|
153
|
+
* integration code without relying on unsupported package deep imports.
|
|
154
|
+
*/
|
|
131
155
|
type LoadedPdfPage = PDFPageProxy;
|
|
132
156
|
|
|
133
157
|
/** Browser PDF reader with bounded canvas allocation and deterministic cleanup. */
|
|
@@ -137,7 +161,14 @@ declare class PDFReader {
|
|
|
137
161
|
get numPages(): number | undefined;
|
|
138
162
|
/** Current lifecycle state for load, iteration/rendering, retry, and teardown boundaries. */
|
|
139
163
|
get state(): PdfReaderState;
|
|
140
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* Loads once and returns the borrowed PDF.js document proxy.
|
|
166
|
+
*
|
|
167
|
+
* The returned proxy remains owned by this reader. Callers may inspect it and
|
|
168
|
+
* use supported PDF.js read methods, but must not call `destroy()` on the
|
|
169
|
+
* proxy while the reader owns lifecycle teardown. Call `reader.destroy()` to
|
|
170
|
+
* release document and worker resources.
|
|
171
|
+
*/
|
|
141
172
|
load(signal?: AbortSignal): Promise<PDFDocumentProxy>;
|
|
142
173
|
load(options?: LoadOptions): Promise<PDFDocumentProxy>;
|
|
143
174
|
/** Streams page results so callers do not need to retain the entire document conversion in memory. */
|
|
@@ -183,11 +214,16 @@ type PdfUrlSource = PdfUrlSourceOptions & {
|
|
|
183
214
|
declare function pdfUrlSource(url: string | URL, options?: PdfUrlSourceOptions): PdfUrlSource;
|
|
184
215
|
|
|
185
216
|
/**
|
|
186
|
-
* Configures PDF.js
|
|
187
|
-
*
|
|
217
|
+
* Configures PDF.js application-global worker state.
|
|
218
|
+
*
|
|
219
|
+
* Use this once at application startup when every PDF.js consumer can share the
|
|
220
|
+
* same worker configuration. Calling it again replaces the previous global
|
|
221
|
+
* worker URL or port and can collide with other PDF.js consumers in the same
|
|
222
|
+
* JavaScript realm. For isolation, pass a caller-created PDF.js `PDFWorker` on
|
|
223
|
+
* an individual `DocumentInitParameters` source instead of using this helper.
|
|
188
224
|
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
225
|
+
* When passing an existing DOM `Worker`, the caller retains ownership of
|
|
226
|
+
* terminating it.
|
|
191
227
|
*/
|
|
192
228
|
declare function configurePdfWorker(worker: string | Worker): void;
|
|
193
229
|
|
package/index.mjs
CHANGED
|
@@ -42,94 +42,128 @@ function getTransformedUnitBounds(transform) {
|
|
|
42
42
|
|
|
43
43
|
// src/embeddedImages.ts
|
|
44
44
|
var identityTransform = [1, 0, 0, 1, 0, 0];
|
|
45
|
-
var propagatedErrorCodes = /* @__PURE__ */ new Set([
|
|
45
|
+
var propagatedErrorCodes = /* @__PURE__ */ new Set([
|
|
46
|
+
"ABORTED",
|
|
47
|
+
"DESTROYED",
|
|
48
|
+
"IMAGE_LIMIT_EXCEEDED",
|
|
49
|
+
"IMAGE_COUNT_LIMIT_EXCEEDED",
|
|
50
|
+
"IMAGE_TOTAL_PIXELS_LIMIT_EXCEEDED",
|
|
51
|
+
"OPERATOR_LIMIT_EXCEEDED"
|
|
52
|
+
]);
|
|
46
53
|
async function extractEmbeddedImages(page, viewport, options) {
|
|
47
54
|
const operators = await page.getOperatorList();
|
|
55
|
+
enforceOperatorLimit(operators.fnArray.length, options.maxOperators);
|
|
48
56
|
const images = [];
|
|
49
57
|
const stack = [];
|
|
50
58
|
let transform = identityTransform;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (operation === OPS.save) {
|
|
56
|
-
stack.push([...transform]);
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
if (operation === OPS.restore || operation === OPS.paintFormXObjectEnd) {
|
|
60
|
-
transform = stack.pop() ?? identityTransform;
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
if (operation === OPS.transform) {
|
|
64
|
-
const next = readTransformArgs(operators.argsArray[index]);
|
|
65
|
-
if (next) transform = Util.transform(transform, next);
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
if (operation === OPS.paintFormXObjectBegin) {
|
|
69
|
-
stack.push([...transform]);
|
|
70
|
-
const next = readTransformMatrix(operators.argsArray[index]?.[0]);
|
|
71
|
-
if (next) transform = Util.transform(transform, next);
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (operation === OPS.paintImageMaskXObject) {
|
|
75
|
-
warn(options.logger, "Skipped embedded image operator paintImageMaskXObject: image masks are not supported.");
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (operation !== OPS.paintXObject && operation !== OPS.paintImageXObject && operation !== OPS.paintInlineImageXObject) {
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
try {
|
|
82
|
-
const paintedImage = await resolvePaintedImage(page, operation, operators.argsArray[index], index);
|
|
83
|
-
if (!paintedImage) continue;
|
|
84
|
-
const dataUrl = imageToDataUrl(paintedImage.image, options);
|
|
85
|
-
if (!dataUrl) {
|
|
86
|
-
warn(
|
|
87
|
-
options.logger,
|
|
88
|
-
`Skipped embedded image ${paintedImage.label}: unsupported PDF.js image shape or data layout.`
|
|
89
|
-
);
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
const bounds = getTransformedUnitBounds(transform);
|
|
93
|
-
images.push({
|
|
94
|
-
dataUrl,
|
|
95
|
-
x: bounds.left,
|
|
96
|
-
y: bounds.top,
|
|
97
|
-
width: bounds.width,
|
|
98
|
-
height: bounds.height,
|
|
99
|
-
size: imageByteLength(paintedImage.image),
|
|
100
|
-
mimeType: "image/png",
|
|
101
|
-
pageWidth: viewport.width / viewport.scale,
|
|
102
|
-
pageHeight: viewport.height / viewport.scale,
|
|
103
|
-
transform: [...transform]
|
|
104
|
-
});
|
|
105
|
-
} catch (error) {
|
|
59
|
+
let totalPixels = 0;
|
|
60
|
+
const encodedXObjects = /* @__PURE__ */ new Map();
|
|
61
|
+
try {
|
|
62
|
+
for (let index = 0; index < operators.fnArray.length; index += 1) {
|
|
106
63
|
options.throwIfDestroyed();
|
|
107
64
|
options.throwIfAborted(options.signal);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
65
|
+
const operation = operators.fnArray[index];
|
|
66
|
+
if (operation === OPS.save) {
|
|
67
|
+
stack.push([...transform]);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (operation === OPS.restore || operation === OPS.paintFormXObjectEnd) {
|
|
71
|
+
transform = stack.pop() ?? identityTransform;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (operation === OPS.transform) {
|
|
75
|
+
const next = readTransformArgs(operators.argsArray[index]);
|
|
76
|
+
if (next) transform = Util.transform(transform, next);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (operation === OPS.paintFormXObjectBegin) {
|
|
80
|
+
stack.push([...transform]);
|
|
81
|
+
const next = readTransformMatrix(operators.argsArray[index]?.[0]);
|
|
82
|
+
if (next) transform = Util.transform(transform, next);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (operation === OPS.paintImageMaskXObject) {
|
|
86
|
+
warn(options.logger, "Skipped embedded image operator paintImageMaskXObject: image masks are not supported.");
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (operation !== OPS.paintXObject && operation !== OPS.paintImageXObject && operation !== OPS.paintInlineImageXObject) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const args = operators.argsArray[index];
|
|
94
|
+
const reference = operation === OPS.paintInlineImageXObject ? void 0 : readImageReference(args);
|
|
95
|
+
let encoded = reference ? encodedXObjects.get(reference) : void 0;
|
|
96
|
+
if (encoded) {
|
|
97
|
+
enforceNextImageLimits(images.length, totalPixels, encoded.dimensions.pixels, options);
|
|
98
|
+
} else {
|
|
99
|
+
const paintedImage = await resolvePaintedImage(page, operation, args, index);
|
|
100
|
+
options.throwIfDestroyed();
|
|
101
|
+
options.throwIfAborted(options.signal);
|
|
102
|
+
if (!paintedImage) continue;
|
|
103
|
+
const dimensions = readImageDimensions(paintedImage.image, options.maxPixels);
|
|
104
|
+
if (!dimensions || !isSupportedImageSource(paintedImage.image)) {
|
|
105
|
+
warn(
|
|
106
|
+
options.logger,
|
|
107
|
+
`Skipped embedded image ${paintedImage.label}: unsupported PDF.js image shape or data layout.`
|
|
108
|
+
);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
enforceNextImageLimits(images.length, totalPixels, dimensions.pixels, options);
|
|
112
|
+
const dataUrl = imageToDataUrl(paintedImage.image, dimensions, options);
|
|
113
|
+
if (!dataUrl) {
|
|
114
|
+
warn(
|
|
115
|
+
options.logger,
|
|
116
|
+
`Skipped embedded image ${paintedImage.label}: unsupported PDF.js image shape or data layout.`
|
|
117
|
+
);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
encoded = { dataUrl, dimensions, size: imageByteLength(paintedImage.image) };
|
|
121
|
+
if (paintedImage.reference) encodedXObjects.set(paintedImage.reference, encoded);
|
|
122
|
+
}
|
|
123
|
+
totalPixels += encoded.dimensions.pixels;
|
|
124
|
+
const bounds = getTransformedUnitBounds(transform);
|
|
125
|
+
images.push({
|
|
126
|
+
dataUrl: encoded.dataUrl,
|
|
127
|
+
x: bounds.left,
|
|
128
|
+
y: bounds.top,
|
|
129
|
+
width: bounds.width,
|
|
130
|
+
height: bounds.height,
|
|
131
|
+
size: encoded.size,
|
|
132
|
+
mimeType: "image/png",
|
|
133
|
+
pageWidth: viewport.width / viewport.scale,
|
|
134
|
+
pageHeight: viewport.height / viewport.scale,
|
|
135
|
+
transform: [...transform]
|
|
136
|
+
});
|
|
137
|
+
} catch (error) {
|
|
138
|
+
options.throwIfDestroyed();
|
|
139
|
+
options.throwIfAborted(options.signal);
|
|
140
|
+
if (shouldPropagateEmbeddedImageError(error)) throw error;
|
|
141
|
+
const label = readOperationLabel(operation, operators.argsArray[index], index);
|
|
142
|
+
warn(options.logger, `Failed to extract embedded image ${label}.`, error);
|
|
143
|
+
}
|
|
111
144
|
}
|
|
145
|
+
} finally {
|
|
146
|
+
encodedXObjects.clear();
|
|
112
147
|
}
|
|
113
148
|
return images;
|
|
114
149
|
}
|
|
150
|
+
function readImageReference(args) {
|
|
151
|
+
const reference = Array.isArray(args) ? args[0] : void 0;
|
|
152
|
+
return typeof reference === "string" ? reference : void 0;
|
|
153
|
+
}
|
|
115
154
|
async function resolvePaintedImage(page, operation, args, index) {
|
|
116
155
|
if (operation === OPS.paintInlineImageXObject) {
|
|
117
156
|
const inlineImage = Array.isArray(args) ? args[0] : void 0;
|
|
118
157
|
if (!inlineImage || typeof inlineImage !== "object") return void 0;
|
|
119
158
|
return { image: inlineImage, label: `inline@${index}` };
|
|
120
159
|
}
|
|
121
|
-
const reference =
|
|
122
|
-
if (
|
|
160
|
+
const reference = readImageReference(args);
|
|
161
|
+
if (!reference) return void 0;
|
|
123
162
|
const image = await page.objs.get(reference);
|
|
124
|
-
return { image, label: reference };
|
|
163
|
+
return { image, label: reference, reference };
|
|
125
164
|
}
|
|
126
|
-
function imageToDataUrl(image, options) {
|
|
127
|
-
const
|
|
128
|
-
const height = image.height;
|
|
129
|
-
if (!Number.isSafeInteger(width) || !Number.isSafeInteger(height) || width <= 0 || height <= 0) {
|
|
130
|
-
return void 0;
|
|
131
|
-
}
|
|
132
|
-
const canvas = allocateCanvas(width, height, options.maxPixels, options.createCanvas);
|
|
165
|
+
function imageToDataUrl(image, dimensions, options) {
|
|
166
|
+
const canvas = allocateCanvas(dimensions.width, dimensions.height, options.maxPixels, options.createCanvas);
|
|
133
167
|
try {
|
|
134
168
|
const context = canvas.getContext("2d");
|
|
135
169
|
if (!context) return void 0;
|
|
@@ -139,9 +173,9 @@ function imageToDataUrl(image, options) {
|
|
|
139
173
|
}
|
|
140
174
|
if (!ArrayBuffer.isView(image.data)) return void 0;
|
|
141
175
|
const source = new Uint8ClampedArray(image.data.buffer, image.data.byteOffset, image.data.byteLength);
|
|
142
|
-
const rgba = toRgba(source,
|
|
176
|
+
const rgba = toRgba(source, dimensions);
|
|
143
177
|
if (!rgba) return void 0;
|
|
144
|
-
const pixels = context.createImageData(width, height);
|
|
178
|
+
const pixels = context.createImageData(dimensions.width, dimensions.height);
|
|
145
179
|
pixels.data.set(rgba);
|
|
146
180
|
context.putImageData(pixels, 0, 0);
|
|
147
181
|
return canvas.toDataURL("image/png");
|
|
@@ -149,13 +183,17 @@ function imageToDataUrl(image, options) {
|
|
|
149
183
|
releaseCanvas(canvas);
|
|
150
184
|
}
|
|
151
185
|
}
|
|
152
|
-
function toRgba(data,
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
186
|
+
function toRgba(data, dimensions) {
|
|
187
|
+
const rgbaLength = multiplySafe(dimensions.pixels, 4);
|
|
188
|
+
const rgbLength = multiplySafe(dimensions.pixels, 3);
|
|
189
|
+
if (rgbaLength === void 0 || rgbLength === void 0) {
|
|
190
|
+
throw new PdfReaderError("IMAGE_LIMIT_EXCEEDED", "embedded image has unsafe decoded pixel dimensions.");
|
|
191
|
+
}
|
|
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;
|
|
195
|
+
const rgba = new Uint8ClampedArray(rgbaLength);
|
|
196
|
+
for (let pixel = 0; pixel < dimensions.pixels; pixel += 1) {
|
|
159
197
|
const input = pixel * channels;
|
|
160
198
|
const output = pixel * 4;
|
|
161
199
|
rgba[output] = data[input];
|
|
@@ -165,11 +203,52 @@ function toRgba(data, width, height) {
|
|
|
165
203
|
}
|
|
166
204
|
return rgba;
|
|
167
205
|
}
|
|
206
|
+
function enforceOperatorLimit(count, limit) {
|
|
207
|
+
if (!Number.isSafeInteger(count) || count > limit) {
|
|
208
|
+
throw new PdfReaderError("OPERATOR_LIMIT_EXCEEDED", `PDF page has ${count} operators; limit is ${limit}.`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function enforceNextImageLimits(currentImageCount, currentTotalPixels, nextPixels, options) {
|
|
212
|
+
const nextImageCount = currentImageCount + 1;
|
|
213
|
+
if (!Number.isSafeInteger(nextImageCount) || nextImageCount > options.maxImages) {
|
|
214
|
+
throw new PdfReaderError(
|
|
215
|
+
"IMAGE_COUNT_LIMIT_EXCEEDED",
|
|
216
|
+
`PDF page has more than ${options.maxImages} extractable embedded images.`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
const nextTotalPixels = currentTotalPixels + nextPixels;
|
|
220
|
+
if (!Number.isSafeInteger(nextTotalPixels) || nextTotalPixels > options.maxTotalPixels) {
|
|
221
|
+
throw new PdfReaderError(
|
|
222
|
+
"IMAGE_TOTAL_PIXELS_LIMIT_EXCEEDED",
|
|
223
|
+
`PDF page embedded images require more than ${options.maxTotalPixels} decoded pixels.`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function readImageDimensions(image, limit) {
|
|
228
|
+
const { width, height } = image;
|
|
229
|
+
if (width === void 0 || height === void 0) return void 0;
|
|
230
|
+
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
|
+
}
|
|
237
|
+
return { width: pixelWidth, height: pixelHeight, pixels };
|
|
238
|
+
}
|
|
239
|
+
function isSupportedImageSource(image) {
|
|
240
|
+
return typeof ImageBitmap !== "undefined" && image.bitmap instanceof ImageBitmap || ArrayBuffer.isView(image.data);
|
|
241
|
+
}
|
|
242
|
+
function multiplySafe(left, right) {
|
|
243
|
+
if (!Number.isSafeInteger(left) || !Number.isSafeInteger(right)) return void 0;
|
|
244
|
+
const product = left * right;
|
|
245
|
+
return Number.isSafeInteger(product) ? product : void 0;
|
|
246
|
+
}
|
|
168
247
|
function allocateCanvas(width, height, limit, createCanvas) {
|
|
169
248
|
const pixelWidth = Math.ceil(width);
|
|
170
249
|
const pixelHeight = Math.ceil(height);
|
|
171
|
-
const pixels = pixelWidth
|
|
172
|
-
if (
|
|
250
|
+
const pixels = multiplySafe(pixelWidth, pixelHeight);
|
|
251
|
+
if (pixels === void 0 || pixels > limit) {
|
|
173
252
|
throw new PdfReaderError("IMAGE_LIMIT_EXCEEDED", `embedded image requires ${pixels} pixels; limit is ${limit}.`);
|
|
174
253
|
}
|
|
175
254
|
const canvas = createCanvas();
|
|
@@ -266,10 +345,15 @@ function validatePageRange(pageRange) {
|
|
|
266
345
|
// src/PDFReader.ts
|
|
267
346
|
var defaultLimits = {
|
|
268
347
|
maxDocumentPages: 1e3,
|
|
348
|
+
maxTextItems: 5e4,
|
|
349
|
+
maxTextCodeUnits: 5e6,
|
|
350
|
+
maxOperatorCount: 1e5,
|
|
269
351
|
maxCanvasPixels: 4e7,
|
|
270
|
-
maxEmbeddedImagePixels: 25e6
|
|
352
|
+
maxEmbeddedImagePixels: 25e6,
|
|
353
|
+
maxEmbeddedImages: 1e3,
|
|
354
|
+
maxEmbeddedImagePixelsTotal: 1e8
|
|
271
355
|
};
|
|
272
|
-
var _source, _createCanvas, _logger, _limits, _sourcePolicy, _loadingState, _loadingTask, _document, _destroyPromise, _activeRenderTasks, _activePageWorkCount, _destroyed, _lastLoadFailed, _PDFReader_instances, load_fn, processPage_fn, encodePageImage_fn, resolveViewport_fn, encodeCanvasToBlob_fn, allocateCanvas_fn, waitForRender_fn, releaseCanvas_fn, getOrCreateLoadState_fn, awaitWithSignal_fn, createAbortedError_fn, createDestroyedError_fn, createDeadlineExceededError_fn, createUnsupportedEnvironmentError_fn, rethrowLifecycleError_fn, throwIfDestroyed_fn, throwIfAborted_fn, resolveLoadOptions_fn, isAbortSignal_fn, enforceSourcePolicy_fn,
|
|
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;
|
|
273
357
|
var PDFReader = class {
|
|
274
358
|
constructor(source, options = {}) {
|
|
275
359
|
__privateAdd(this, _PDFReader_instances);
|
|
@@ -282,7 +366,9 @@ var PDFReader = class {
|
|
|
282
366
|
__privateAdd(this, _loadingTask);
|
|
283
367
|
__privateAdd(this, _document);
|
|
284
368
|
__privateAdd(this, _destroyPromise);
|
|
369
|
+
__privateAdd(this, _destroyController, new AbortController());
|
|
285
370
|
__privateAdd(this, _activeRenderTasks, /* @__PURE__ */ new Set());
|
|
371
|
+
__privateAdd(this, _activePageOperation, false);
|
|
286
372
|
__privateAdd(this, _activePageWorkCount, 0);
|
|
287
373
|
__privateAdd(this, _destroyed, false);
|
|
288
374
|
__privateAdd(this, _lastLoadFailed, false);
|
|
@@ -299,7 +385,7 @@ var PDFReader = class {
|
|
|
299
385
|
/** Current lifecycle state for load, iteration/rendering, retry, and teardown boundaries. */
|
|
300
386
|
get state() {
|
|
301
387
|
if (__privateGet(this, _destroyed)) return "destroyed";
|
|
302
|
-
if (__privateGet(this, _activePageWorkCount) > 0) return "iterating";
|
|
388
|
+
if (__privateGet(this, _activePageOperation) || __privateGet(this, _activePageWorkCount) > 0) return "iterating";
|
|
303
389
|
if (__privateGet(this, _document)) return "loaded";
|
|
304
390
|
if (__privateGet(this, _loadingState)) return "loading";
|
|
305
391
|
if (__privateGet(this, _lastLoadFailed)) return "failed";
|
|
@@ -314,37 +400,43 @@ var PDFReader = class {
|
|
|
314
400
|
}
|
|
315
401
|
/** Streams page results so callers do not need to retain the entire document conversion in memory. */
|
|
316
402
|
async *pages(options = {}) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
let
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
ownsActivePageWork =
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
403
|
+
let ownsPageOperation = false;
|
|
404
|
+
try {
|
|
405
|
+
__privateMethod(this, _PDFReader_instances, acquirePageOperation_fn).call(this);
|
|
406
|
+
ownsPageOperation = true;
|
|
407
|
+
const documentProxy = __privateGet(this, _document);
|
|
408
|
+
if (!documentProxy) {
|
|
409
|
+
throw new PdfReaderError("DOCUMENT_NOT_LOADED", "Document not loaded. Call load() first.");
|
|
410
|
+
}
|
|
411
|
+
const resolved = resolveConvertOptions(options);
|
|
412
|
+
const [start, end] = resolvePageNumbers(resolved.pageRange, documentProxy.numPages);
|
|
413
|
+
if (start > documentProxy.numPages) {
|
|
414
|
+
throw new PdfReaderError("INVALID_OPTION", `pageRange starts after the last page (${documentProxy.numPages}).`);
|
|
415
|
+
}
|
|
416
|
+
for (let pageNumber = start; pageNumber <= Math.min(end, documentProxy.numPages); pageNumber += 1) {
|
|
417
|
+
__privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
|
|
418
|
+
__privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, resolved.signal);
|
|
419
|
+
let page;
|
|
420
|
+
let ownsActivePageWork = false;
|
|
421
|
+
try {
|
|
422
|
+
__privateSet(this, _activePageWorkCount, __privateGet(this, _activePageWorkCount) + 1);
|
|
423
|
+
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;
|
|
428
|
+
__privateSet(this, _activePageWorkCount, __privateGet(this, _activePageWorkCount) - 1);
|
|
429
|
+
ownsActivePageWork = false;
|
|
430
|
+
yield result;
|
|
431
|
+
} catch (error) {
|
|
432
|
+
__privateMethod(this, _PDFReader_instances, rethrowLifecycleError_fn).call(this, error, resolved.signal);
|
|
433
|
+
} finally {
|
|
434
|
+
if (ownsActivePageWork) __privateSet(this, _activePageWorkCount, __privateGet(this, _activePageWorkCount) - 1);
|
|
435
|
+
page?.cleanup();
|
|
436
|
+
}
|
|
347
437
|
}
|
|
438
|
+
} finally {
|
|
439
|
+
if (ownsPageOperation) __privateMethod(this, _PDFReader_instances, releasePageOperation_fn).call(this);
|
|
348
440
|
}
|
|
349
441
|
}
|
|
350
442
|
/** Collects `pages()` into an array. Prefer `pages()` for large documents. */
|
|
@@ -357,6 +449,7 @@ var PDFReader = class {
|
|
|
357
449
|
async destroy() {
|
|
358
450
|
if (__privateGet(this, _destroyPromise)) return __privateGet(this, _destroyPromise);
|
|
359
451
|
__privateSet(this, _destroyed, true);
|
|
452
|
+
__privateGet(this, _destroyController).abort();
|
|
360
453
|
const loadingTask = __privateGet(this, _loadingTask);
|
|
361
454
|
const loadingState = __privateGet(this, _loadingState);
|
|
362
455
|
__privateSet(this, _loadingTask, void 0);
|
|
@@ -370,8 +463,7 @@ var PDFReader = class {
|
|
|
370
463
|
}
|
|
371
464
|
if (!loadingState) return;
|
|
372
465
|
loadingState.destroyed = true;
|
|
373
|
-
|
|
374
|
-
await loadingState.destroyPromise;
|
|
466
|
+
await __privateMethod(this, _PDFReader_instances, destroyLoadStateTask_fn).call(this, loadingState);
|
|
375
467
|
})());
|
|
376
468
|
await __privateGet(this, _destroyPromise);
|
|
377
469
|
}
|
|
@@ -385,7 +477,9 @@ _loadingState = new WeakMap();
|
|
|
385
477
|
_loadingTask = new WeakMap();
|
|
386
478
|
_document = new WeakMap();
|
|
387
479
|
_destroyPromise = new WeakMap();
|
|
480
|
+
_destroyController = new WeakMap();
|
|
388
481
|
_activeRenderTasks = new WeakMap();
|
|
482
|
+
_activePageOperation = new WeakMap();
|
|
389
483
|
_activePageWorkCount = new WeakMap();
|
|
390
484
|
_destroyed = new WeakMap();
|
|
391
485
|
_lastLoadFailed = new WeakMap();
|
|
@@ -416,7 +510,9 @@ processPage_fn = async function(page, pageNumber, numPages, options) {
|
|
|
416
510
|
__privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
|
|
417
511
|
if (options.includeText) {
|
|
418
512
|
try {
|
|
419
|
-
|
|
513
|
+
const text = await page.getTextContent();
|
|
514
|
+
__privateMethod(this, _PDFReader_instances, enforceTextLimits_fn).call(this, text);
|
|
515
|
+
result.text = text;
|
|
420
516
|
} catch (error) {
|
|
421
517
|
__privateMethod(this, _PDFReader_instances, rethrowLifecycleError_fn).call(this, error, options.signal);
|
|
422
518
|
}
|
|
@@ -429,6 +525,9 @@ processPage_fn = async function(page, pageNumber, numPages, options) {
|
|
|
429
525
|
signal: options.signal,
|
|
430
526
|
createCanvas: __privateGet(this, _createCanvas),
|
|
431
527
|
maxPixels: __privateGet(this, _limits).maxEmbeddedImagePixels,
|
|
528
|
+
maxImages: __privateGet(this, _limits).maxEmbeddedImages,
|
|
529
|
+
maxTotalPixels: __privateGet(this, _limits).maxEmbeddedImagePixelsTotal,
|
|
530
|
+
maxOperators: __privateGet(this, _limits).maxOperatorCount,
|
|
432
531
|
logger: __privateGet(this, _logger),
|
|
433
532
|
throwIfAborted: (signal) => __privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, signal),
|
|
434
533
|
throwIfDestroyed: () => __privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this)
|
|
@@ -455,6 +554,30 @@ processPage_fn = async function(page, pageNumber, numPages, options) {
|
|
|
455
554
|
}
|
|
456
555
|
return result;
|
|
457
556
|
};
|
|
557
|
+
enforceTextLimits_fn = function(text) {
|
|
558
|
+
const items = text.items;
|
|
559
|
+
if (!Array.isArray(items) || !Number.isSafeInteger(items.length)) {
|
|
560
|
+
throw new PdfReaderError("TEXT_LIMIT_EXCEEDED", "PDF text content has an unsafe item count.");
|
|
561
|
+
}
|
|
562
|
+
if (items.length > __privateGet(this, _limits).maxTextItems) {
|
|
563
|
+
throw new PdfReaderError(
|
|
564
|
+
"TEXT_LIMIT_EXCEEDED",
|
|
565
|
+
`PDF page has ${items.length} text items; limit is ${__privateGet(this, _limits).maxTextItems}.`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
let codeUnits = 0;
|
|
569
|
+
for (const item of items) {
|
|
570
|
+
const value = item && typeof item === "object" ? item.str : void 0;
|
|
571
|
+
if (typeof value !== "string") continue;
|
|
572
|
+
codeUnits += value.length;
|
|
573
|
+
if (!Number.isSafeInteger(codeUnits) || codeUnits > __privateGet(this, _limits).maxTextCodeUnits) {
|
|
574
|
+
throw new PdfReaderError(
|
|
575
|
+
"TEXT_LIMIT_EXCEEDED",
|
|
576
|
+
`PDF page text has more than ${__privateGet(this, _limits).maxTextCodeUnits} string code units.`
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
};
|
|
458
581
|
encodePageImage_fn = async function(canvas, mimeType, output, jpegQuality, signal) {
|
|
459
582
|
__privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
|
|
460
583
|
__privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, signal);
|
|
@@ -481,11 +604,16 @@ encodeCanvasToBlob_fn = async function(canvas, mimeType, quality, signal) {
|
|
|
481
604
|
if (typeof canvas.toBlob !== "function") {
|
|
482
605
|
throw __privateMethod(this, _PDFReader_instances, createUnsupportedEnvironmentError_fn).call(this, "Canvas Blob encoding is not supported by this canvas implementation.");
|
|
483
606
|
}
|
|
607
|
+
__privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
|
|
484
608
|
__privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, signal);
|
|
485
609
|
return await new Promise((resolve, reject) => {
|
|
486
610
|
let settled = false;
|
|
487
611
|
const onAbort = () => settle(() => reject(__privateMethod(this, _PDFReader_instances, createAbortedError_fn).call(this)));
|
|
488
|
-
const
|
|
612
|
+
const onDestroy = () => settle(() => reject(__privateMethod(this, _PDFReader_instances, createDestroyedError_fn).call(this)));
|
|
613
|
+
const cleanup = () => {
|
|
614
|
+
signal?.removeEventListener("abort", onAbort);
|
|
615
|
+
__privateGet(this, _destroyController).signal.removeEventListener("abort", onDestroy);
|
|
616
|
+
};
|
|
489
617
|
const settle = (callback) => {
|
|
490
618
|
if (settled) return;
|
|
491
619
|
settled = true;
|
|
@@ -493,6 +621,7 @@ encodeCanvasToBlob_fn = async function(canvas, mimeType, quality, signal) {
|
|
|
493
621
|
callback();
|
|
494
622
|
};
|
|
495
623
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
624
|
+
__privateGet(this, _destroyController).signal.addEventListener("abort", onDestroy, { once: true });
|
|
496
625
|
try {
|
|
497
626
|
const resolveBlob = (blob) => {
|
|
498
627
|
if (!blob) {
|
|
@@ -531,7 +660,7 @@ waitForRender_fn = async function(renderTask, signal) {
|
|
|
531
660
|
__privateGet(this, _activeRenderTasks).add(renderTask);
|
|
532
661
|
signal?.addEventListener("abort", abort, { once: true });
|
|
533
662
|
try {
|
|
534
|
-
await renderTask.promise;
|
|
663
|
+
await __privateMethod(this, _PDFReader_instances, awaitWithSignal_fn).call(this, renderTask.promise, signal);
|
|
535
664
|
__privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
|
|
536
665
|
__privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, signal);
|
|
537
666
|
} catch (error) {
|
|
@@ -545,6 +674,19 @@ releaseCanvas_fn = function(canvas) {
|
|
|
545
674
|
canvas.width = 0;
|
|
546
675
|
canvas.height = 0;
|
|
547
676
|
};
|
|
677
|
+
acquirePageOperation_fn = function() {
|
|
678
|
+
__privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
|
|
679
|
+
if (__privateGet(this, _activePageOperation)) {
|
|
680
|
+
throw new PdfReaderError(
|
|
681
|
+
"OPERATION_IN_PROGRESS",
|
|
682
|
+
"Another pages() or convert() operation is already active for this PDFReader."
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
__privateSet(this, _activePageOperation, true);
|
|
686
|
+
};
|
|
687
|
+
releasePageOperation_fn = function() {
|
|
688
|
+
__privateSet(this, _activePageOperation, false);
|
|
689
|
+
};
|
|
548
690
|
getOrCreateLoadState_fn = function() {
|
|
549
691
|
const existing = __privateGet(this, _loadingState);
|
|
550
692
|
if (existing) return existing;
|
|
@@ -552,32 +694,45 @@ getOrCreateLoadState_fn = function() {
|
|
|
552
694
|
const state = { destroyed: false };
|
|
553
695
|
state.promise = (async () => {
|
|
554
696
|
try {
|
|
555
|
-
const
|
|
556
|
-
|
|
697
|
+
const source = __privateMethod(this, _PDFReader_instances, createSourceSnapshot_fn).call(this);
|
|
698
|
+
const policyResult = __privateMethod(this, _PDFReader_instances, enforceSourcePolicy_fn).call(this, source.info);
|
|
699
|
+
if (policyResult) await __privateMethod(this, _PDFReader_instances, awaitWithDestroy_fn).call(this, policyResult);
|
|
557
700
|
if (state.destroyed || __privateGet(this, _destroyed)) {
|
|
558
701
|
throw __privateMethod(this, _PDFReader_instances, createDestroyedError_fn).call(this);
|
|
559
702
|
}
|
|
560
|
-
const task = getDocument(
|
|
703
|
+
const task = getDocument(source.pdfJsSource);
|
|
561
704
|
state.task = task;
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
await
|
|
705
|
+
let documentProxy;
|
|
706
|
+
try {
|
|
707
|
+
documentProxy = await __privateMethod(this, _PDFReader_instances, awaitWithDestroy_fn).call(this, task.promise);
|
|
708
|
+
} catch (error) {
|
|
709
|
+
if (state.destroyed || __privateGet(this, _destroyed)) {
|
|
710
|
+
await state.destroyPromise?.catch(() => void 0);
|
|
711
|
+
throw __privateMethod(this, _PDFReader_instances, createDestroyedError_fn).call(this);
|
|
712
|
+
}
|
|
713
|
+
await __privateMethod(this, _PDFReader_instances, destroyLoadStateTask_fn).call(this, state).catch(() => void 0);
|
|
714
|
+
throw error;
|
|
715
|
+
}
|
|
716
|
+
if (state.destroyed) {
|
|
717
|
+
await __privateMethod(this, _PDFReader_instances, destroyLoadStateTask_fn).call(this, state).catch(() => void 0);
|
|
565
718
|
throw __privateMethod(this, _PDFReader_instances, createDestroyedError_fn).call(this);
|
|
566
719
|
}
|
|
567
720
|
if (documentProxy.numPages > __privateGet(this, _limits).maxDocumentPages) {
|
|
568
|
-
|
|
569
|
-
throw new PdfReaderError(
|
|
721
|
+
const error = new PdfReaderError(
|
|
570
722
|
"PAGE_LIMIT_EXCEEDED",
|
|
571
723
|
`PDF has ${documentProxy.numPages} pages; limit is ${__privateGet(this, _limits).maxDocumentPages}.`
|
|
572
724
|
);
|
|
725
|
+
await __privateMethod(this, _PDFReader_instances, destroyLoadStateTask_fn).call(this, state).catch(() => void 0);
|
|
726
|
+
throw error;
|
|
573
727
|
}
|
|
574
728
|
__privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
|
|
729
|
+
state.task = void 0;
|
|
575
730
|
__privateSet(this, _loadingTask, task);
|
|
576
731
|
__privateSet(this, _document, documentProxy);
|
|
577
732
|
__privateSet(this, _lastLoadFailed, false);
|
|
578
733
|
return documentProxy;
|
|
579
734
|
} catch (error) {
|
|
580
|
-
if (state.destroyed ||
|
|
735
|
+
if (state.destroyed || __privateGet(this, _destroyed)) {
|
|
581
736
|
await state.destroyPromise?.catch(() => void 0);
|
|
582
737
|
throw __privateMethod(this, _PDFReader_instances, createDestroyedError_fn).call(this);
|
|
583
738
|
}
|
|
@@ -591,14 +746,23 @@ getOrCreateLoadState_fn = function() {
|
|
|
591
746
|
__privateSet(this, _loadingState, state);
|
|
592
747
|
return state;
|
|
593
748
|
};
|
|
749
|
+
destroyLoadStateTask_fn = function(state) {
|
|
750
|
+
if (state.destroyPromise) return state.destroyPromise;
|
|
751
|
+
const task = state.task;
|
|
752
|
+
state.task = void 0;
|
|
753
|
+
state.destroyPromise = task ? Promise.resolve(task.destroy()) : Promise.resolve();
|
|
754
|
+
return state.destroyPromise;
|
|
755
|
+
};
|
|
594
756
|
awaitWithSignal_fn = async function(promise, signal, deadlineMs) {
|
|
595
757
|
__privateMethod(this, _PDFReader_instances, throwIfAborted_fn).call(this, signal);
|
|
596
|
-
|
|
758
|
+
__privateMethod(this, _PDFReader_instances, throwIfDestroyed_fn).call(this);
|
|
597
759
|
return await new Promise((resolve, reject) => {
|
|
598
760
|
let settled = false;
|
|
599
761
|
const onAbort = () => settle(() => reject(__privateMethod(this, _PDFReader_instances, createAbortedError_fn).call(this)));
|
|
762
|
+
const onDestroy = () => settle(() => reject(__privateMethod(this, _PDFReader_instances, createDestroyedError_fn).call(this)));
|
|
600
763
|
const cleanup = () => {
|
|
601
764
|
signal?.removeEventListener("abort", onAbort);
|
|
765
|
+
__privateGet(this, _destroyController).signal.removeEventListener("abort", onDestroy);
|
|
602
766
|
if (timer !== void 0) clearTimeout(timer);
|
|
603
767
|
};
|
|
604
768
|
const settle = (callback) => {
|
|
@@ -609,12 +773,32 @@ awaitWithSignal_fn = async function(promise, signal, deadlineMs) {
|
|
|
609
773
|
};
|
|
610
774
|
const timer = deadlineMs === void 0 ? void 0 : setTimeout(() => settle(() => reject(__privateMethod(this, _PDFReader_instances, createDeadlineExceededError_fn).call(this))), deadlineMs);
|
|
611
775
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
776
|
+
__privateGet(this, _destroyController).signal.addEventListener("abort", onDestroy, { once: true });
|
|
612
777
|
promise.then(
|
|
613
778
|
(value) => settle(() => resolve(value)),
|
|
614
779
|
(error) => settle(() => reject(error))
|
|
615
780
|
).catch(() => void 0);
|
|
616
781
|
});
|
|
617
782
|
};
|
|
783
|
+
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
|
+
});
|
|
801
|
+
};
|
|
618
802
|
createAbortedError_fn = function() {
|
|
619
803
|
return new PdfReaderError("ABORTED", "PDF operation was aborted.");
|
|
620
804
|
};
|
|
@@ -649,8 +833,7 @@ resolveLoadOptions_fn = function(options) {
|
|
|
649
833
|
isAbortSignal_fn = function(value) {
|
|
650
834
|
return "aborted" in value && "addEventListener" in value && "removeEventListener" in value;
|
|
651
835
|
};
|
|
652
|
-
enforceSourcePolicy_fn = function() {
|
|
653
|
-
const source = __privateMethod(this, _PDFReader_instances, inspectSource_fn).call(this, __privateGet(this, _source));
|
|
836
|
+
enforceSourcePolicy_fn = function(source) {
|
|
654
837
|
if (source.byteLength !== void 0 && __privateGet(this, _limits).maxSourceBytes !== void 0 && source.byteLength > __privateGet(this, _limits).maxSourceBytes) {
|
|
655
838
|
throw new PdfReaderError(
|
|
656
839
|
"SOURCE_LIMIT_EXCEEDED",
|
|
@@ -660,8 +843,8 @@ enforceSourcePolicy_fn = function() {
|
|
|
660
843
|
if (!__privateGet(this, _sourcePolicy)) return;
|
|
661
844
|
try {
|
|
662
845
|
const result = __privateGet(this, _sourcePolicy).call(this, source);
|
|
663
|
-
if (
|
|
664
|
-
return result.catch((error) => {
|
|
846
|
+
if (result === void 0) return;
|
|
847
|
+
return Promise.resolve(result).catch((error) => {
|
|
665
848
|
if (error instanceof PdfReaderError) throw error;
|
|
666
849
|
throw new PdfReaderError("SOURCE_POLICY_VIOLATION", "PDF source rejected by sourcePolicy.");
|
|
667
850
|
});
|
|
@@ -670,56 +853,52 @@ enforceSourcePolicy_fn = function() {
|
|
|
670
853
|
throw new PdfReaderError("SOURCE_POLICY_VIOLATION", "PDF source rejected by sourcePolicy.");
|
|
671
854
|
}
|
|
672
855
|
};
|
|
673
|
-
|
|
674
|
-
|
|
856
|
+
createSourceSnapshot_fn = function() {
|
|
857
|
+
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);
|
|
860
|
+
return { pdfJsSource, info };
|
|
861
|
+
};
|
|
862
|
+
inspectSource_fn = function(source, kind) {
|
|
863
|
+
const headers = __privateMethod(this, _PDFReader_instances, readHttpHeaders_fn).call(this, source.httpHeaders);
|
|
864
|
+
const info = {
|
|
865
|
+
rawSource: source,
|
|
866
|
+
kind,
|
|
867
|
+
url: __privateMethod(this, _PDFReader_instances, readUrl_fn).call(this, source.url),
|
|
868
|
+
byteLength: __privateMethod(this, _PDFReader_instances, knownByteLength_fn).call(this, source.data),
|
|
869
|
+
hasHttpHeaders: headers.hasHttpHeaders,
|
|
870
|
+
httpHeaders: headers.httpHeaders,
|
|
871
|
+
withCredentials: source.withCredentials === true
|
|
872
|
+
};
|
|
873
|
+
return Object.freeze(info);
|
|
675
874
|
};
|
|
676
|
-
|
|
875
|
+
sourceKind_fn = function(source) {
|
|
876
|
+
if (typeof source === "string") return "url";
|
|
877
|
+
if (typeof URL !== "undefined" && source instanceof URL) return "url";
|
|
878
|
+
if (source instanceof ArrayBuffer || __privateMethod(this, _PDFReader_instances, isPdfTypedArray_fn).call(this, source) || Array.isArray(source)) return "bytes";
|
|
879
|
+
return "document-init-parameters";
|
|
880
|
+
};
|
|
881
|
+
toPdfJsSource_fn = function(source) {
|
|
677
882
|
if (typeof source === "string") {
|
|
678
|
-
return {
|
|
679
|
-
rawSource: source,
|
|
680
|
-
kind: "url",
|
|
681
|
-
url: source,
|
|
682
|
-
hasHttpHeaders: false,
|
|
683
|
-
withCredentials: false
|
|
684
|
-
};
|
|
883
|
+
return __privateMethod(this, _PDFReader_instances, freezeSource_fn).call(this, { url: source });
|
|
685
884
|
}
|
|
686
885
|
if (typeof URL !== "undefined" && source instanceof URL) {
|
|
687
|
-
return {
|
|
688
|
-
rawSource: source,
|
|
689
|
-
kind: "url",
|
|
690
|
-
url: source.toString(),
|
|
691
|
-
hasHttpHeaders: false,
|
|
692
|
-
withCredentials: false
|
|
693
|
-
};
|
|
886
|
+
return __privateMethod(this, _PDFReader_instances, freezeSource_fn).call(this, { url: source.toString() });
|
|
694
887
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
return {
|
|
698
|
-
rawSource: source,
|
|
699
|
-
kind: "bytes",
|
|
700
|
-
byteLength,
|
|
701
|
-
hasHttpHeaders: false,
|
|
702
|
-
withCredentials: false
|
|
703
|
-
};
|
|
888
|
+
if (source instanceof ArrayBuffer || __privateMethod(this, _PDFReader_instances, isPdfTypedArray_fn).call(this, source) || Array.isArray(source)) {
|
|
889
|
+
return __privateMethod(this, _PDFReader_instances, freezeSource_fn).call(this, { data: source });
|
|
704
890
|
}
|
|
705
|
-
const
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
httpHeaders: headers,
|
|
714
|
-
withCredentials: candidate.withCredentials === true
|
|
715
|
-
};
|
|
891
|
+
const snapshot = {};
|
|
892
|
+
for (const key of Reflect.ownKeys(source)) {
|
|
893
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
|
894
|
+
if (!descriptor?.enumerable) continue;
|
|
895
|
+
snapshot[key] = source[key];
|
|
896
|
+
}
|
|
897
|
+
if (typeof URL !== "undefined" && snapshot.url instanceof URL) snapshot.url = snapshot.url.toString();
|
|
898
|
+
return __privateMethod(this, _PDFReader_instances, freezeSource_fn).call(this, snapshot);
|
|
716
899
|
};
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
if (source instanceof URL) return { url: source };
|
|
720
|
-
if (source instanceof ArrayBuffer || __privateMethod(this, _PDFReader_instances, isPdfTypedArray_fn).call(this, source) || Array.isArray(source))
|
|
721
|
-
return { data: source };
|
|
722
|
-
return source;
|
|
900
|
+
freezeSource_fn = function(source) {
|
|
901
|
+
return Object.freeze(source);
|
|
723
902
|
};
|
|
724
903
|
isPdfTypedArray_fn = function(source) {
|
|
725
904
|
return ArrayBuffer.isView(source) && !(source instanceof DataView);
|
|
@@ -736,10 +915,24 @@ readUrl_fn = function(value) {
|
|
|
736
915
|
return void 0;
|
|
737
916
|
};
|
|
738
917
|
readHttpHeaders_fn = function(value) {
|
|
739
|
-
if (!value || typeof value !== "object"
|
|
740
|
-
const entries =
|
|
741
|
-
|
|
742
|
-
|
|
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 {
|
|
930
|
+
return { hasHttpHeaders: true };
|
|
931
|
+
}
|
|
932
|
+
return {
|
|
933
|
+
hasHttpHeaders: true,
|
|
934
|
+
httpHeaders: entries.length > 0 ? Object.freeze(Object.fromEntries(entries)) : void 0
|
|
935
|
+
};
|
|
743
936
|
};
|
|
744
937
|
validateLimits_fn = function() {
|
|
745
938
|
for (const [name, value] of Object.entries(__privateGet(this, _limits))) {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@web-ts-toolkit/pdf-reader",
|
|
3
3
|
"description": "Resource-safe browser PDF rendering, text extraction, and embedded-image extraction built on PDF.js",
|
|
4
4
|
"homepage": "https://web-ts-toolkit.pages.dev/docs/packages/pdf-reader",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.42.0",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"keywords": [
|
|
8
8
|
"pdf",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"node": ">=22.13.0"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"pdfjs-dist": "
|
|
29
|
+
"pdfjs-dist": "~6.2.108"
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"**/*",
|