@lexmata/micropdf 0.4.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/LICENSE +191 -0
- package/README.md +985 -0
- package/binding.gyp +73 -0
- package/dist/annot.d.ts +458 -0
- package/dist/annot.d.ts.map +1 -0
- package/dist/annot.js +697 -0
- package/dist/annot.js.map +1 -0
- package/dist/archive.d.ts +128 -0
- package/dist/archive.d.ts.map +1 -0
- package/dist/archive.js +268 -0
- package/dist/archive.js.map +1 -0
- package/dist/buffer.d.ts +572 -0
- package/dist/buffer.d.ts.map +1 -0
- package/dist/buffer.js +971 -0
- package/dist/buffer.js.map +1 -0
- package/dist/colorspace.d.ts +287 -0
- package/dist/colorspace.d.ts.map +1 -0
- package/dist/colorspace.js +542 -0
- package/dist/colorspace.js.map +1 -0
- package/dist/context.d.ts +184 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +320 -0
- package/dist/context.js.map +1 -0
- package/dist/cookie.d.ts +164 -0
- package/dist/cookie.d.ts.map +1 -0
- package/dist/cookie.js +306 -0
- package/dist/cookie.js.map +1 -0
- package/dist/device.d.ts +169 -0
- package/dist/device.d.ts.map +1 -0
- package/dist/device.js +350 -0
- package/dist/device.js.map +1 -0
- package/dist/display-list.d.ts +202 -0
- package/dist/display-list.d.ts.map +1 -0
- package/dist/display-list.js +410 -0
- package/dist/display-list.js.map +1 -0
- package/dist/document.d.ts +637 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +902 -0
- package/dist/document.js.map +1 -0
- package/dist/easy.d.ts +423 -0
- package/dist/easy.d.ts.map +1 -0
- package/dist/easy.js +644 -0
- package/dist/easy.js.map +1 -0
- package/dist/enhanced.d.ts +226 -0
- package/dist/enhanced.d.ts.map +1 -0
- package/dist/enhanced.js +368 -0
- package/dist/enhanced.js.map +1 -0
- package/dist/filter.d.ts +51 -0
- package/dist/filter.d.ts.map +1 -0
- package/dist/filter.js +381 -0
- package/dist/filter.js.map +1 -0
- package/dist/font.d.ts +222 -0
- package/dist/font.d.ts.map +1 -0
- package/dist/font.js +381 -0
- package/dist/font.js.map +1 -0
- package/dist/form.d.ts +214 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +497 -0
- package/dist/form.js.map +1 -0
- package/dist/geometry.d.ts +469 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/geometry.js +780 -0
- package/dist/geometry.js.map +1 -0
- package/dist/image.d.ts +172 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +348 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +171 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +339 -0
- package/dist/index.js.map +1 -0
- package/dist/link.d.ts +168 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +343 -0
- package/dist/link.js.map +1 -0
- package/dist/micropdf.d.ts +40 -0
- package/dist/micropdf.d.ts.map +1 -0
- package/dist/micropdf.js +45 -0
- package/dist/micropdf.js.map +1 -0
- package/dist/nanopdf.d.ts +40 -0
- package/dist/nanopdf.d.ts.map +1 -0
- package/dist/nanopdf.js +45 -0
- package/dist/nanopdf.js.map +1 -0
- package/dist/native.d.ts +242 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +509 -0
- package/dist/native.js.map +1 -0
- package/dist/output.d.ts +166 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +365 -0
- package/dist/output.js.map +1 -0
- package/dist/path.d.ts +420 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +687 -0
- package/dist/path.js.map +1 -0
- package/dist/pdf/object.d.ts +489 -0
- package/dist/pdf/object.d.ts.map +1 -0
- package/dist/pdf/object.js +1045 -0
- package/dist/pdf/object.js.map +1 -0
- package/dist/pixmap.d.ts +315 -0
- package/dist/pixmap.d.ts.map +1 -0
- package/dist/pixmap.js +590 -0
- package/dist/pixmap.js.map +1 -0
- package/dist/profiler.d.ts +159 -0
- package/dist/profiler.d.ts.map +1 -0
- package/dist/profiler.js +380 -0
- package/dist/profiler.js.map +1 -0
- package/dist/render-options.d.ts +227 -0
- package/dist/render-options.d.ts.map +1 -0
- package/dist/render-options.js +130 -0
- package/dist/render-options.js.map +1 -0
- package/dist/resource-tracking.d.ts +332 -0
- package/dist/resource-tracking.d.ts.map +1 -0
- package/dist/resource-tracking.js +653 -0
- package/dist/resource-tracking.js.map +1 -0
- package/dist/simple.d.ts +276 -0
- package/dist/simple.d.ts.map +1 -0
- package/dist/simple.js +343 -0
- package/dist/simple.js.map +1 -0
- package/dist/stext.d.ts +290 -0
- package/dist/stext.d.ts.map +1 -0
- package/dist/stext.js +312 -0
- package/dist/stext.js.map +1 -0
- package/dist/stream.d.ts +174 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +476 -0
- package/dist/stream.js.map +1 -0
- package/dist/text.d.ts +337 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +454 -0
- package/dist/text.js.map +1 -0
- package/dist/typed-arrays.d.ts +127 -0
- package/dist/typed-arrays.d.ts.map +1 -0
- package/dist/typed-arrays.js +410 -0
- package/dist/typed-arrays.js.map +1 -0
- package/dist/types.d.ts +358 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +216 -0
- package/dist/types.js.map +1 -0
- package/native/annot.cc +557 -0
- package/native/buffer.cc +204 -0
- package/native/colorspace.cc +166 -0
- package/native/context.cc +84 -0
- package/native/cookie.cc +179 -0
- package/native/device.cc +179 -0
- package/native/display_list.cc +179 -0
- package/native/document.cc +268 -0
- package/native/enhanced.cc +70 -0
- package/native/font.cc +282 -0
- package/native/form.cc +523 -0
- package/native/geometry.cc +255 -0
- package/native/image.cc +216 -0
- package/native/include/micropdf/enhanced.h +38 -0
- package/native/include/micropdf/types.h +36 -0
- package/native/include/micropdf.h +106 -0
- package/native/include/mupdf-ffi.h +39 -0
- package/native/include/mupdf.h +11 -0
- package/native/include/mupdf_minimal.h +381 -0
- package/native/lib/linux-x64/libmicropdf.a +0 -0
- package/native/link.cc +234 -0
- package/native/micropdf.cc +71 -0
- package/native/output.cc +229 -0
- package/native/page.cc +572 -0
- package/native/path.cc +259 -0
- package/native/pixmap.cc +240 -0
- package/native/stext.cc +610 -0
- package/native/stream.cc +239 -0
- package/package.json +120 -0
- package/scripts/build-from-rust.js +97 -0
- package/scripts/install.js +184 -0
package/dist/document.js
ADDED
|
@@ -0,0 +1,902 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document - PDF document handling
|
|
3
|
+
*
|
|
4
|
+
* This module provides the primary API for working with PDF documents. It handles
|
|
5
|
+
* document lifecycle, page access, metadata, security, and rendering operations.
|
|
6
|
+
*
|
|
7
|
+
* This implementation mirrors the Rust `fitz::document::Document` for 100% API compatibility.
|
|
8
|
+
*
|
|
9
|
+
* @module document
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { Document } from 'micropdf';
|
|
13
|
+
*
|
|
14
|
+
* // Open a PDF from a file
|
|
15
|
+
* const doc = Document.open('document.pdf');
|
|
16
|
+
*
|
|
17
|
+
* // Check if password is required
|
|
18
|
+
* if (doc.needsPassword()) {
|
|
19
|
+
* doc.authenticate('password');
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* // Get page count
|
|
23
|
+
* console.log(`Pages: ${doc.pageCount}`);
|
|
24
|
+
*
|
|
25
|
+
* // Load and render a page
|
|
26
|
+
* const page = doc.loadPage(0);
|
|
27
|
+
* const pixmap = page.toPixmap(Matrix.identity());
|
|
28
|
+
*
|
|
29
|
+
* // Extract text
|
|
30
|
+
* const text = page.extractText();
|
|
31
|
+
*
|
|
32
|
+
* // Clean up
|
|
33
|
+
* page.drop();
|
|
34
|
+
* doc.close();
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
import { Buffer } from './buffer.js';
|
|
38
|
+
import { Colorspace } from './colorspace.js';
|
|
39
|
+
import { Rect, Matrix, Quad } from './geometry.js';
|
|
40
|
+
import { native } from './native.js';
|
|
41
|
+
import { Pixmap } from './pixmap.js';
|
|
42
|
+
import { mergeRenderOptions, validateRenderOptions, dpiToScale } from './render-options.js';
|
|
43
|
+
import { MicroPDFError, LinkDestType } from './types.js';
|
|
44
|
+
/**
|
|
45
|
+
* An item in the document outline (table of contents / bookmarks).
|
|
46
|
+
*
|
|
47
|
+
* Outline items form a tree structure representing the document's navigation hierarchy.
|
|
48
|
+
* Each item can link to a page number, URI, or have child items.
|
|
49
|
+
*
|
|
50
|
+
* @class OutlineItem
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const outline = doc.getOutline();
|
|
54
|
+
* for (const item of outline) {
|
|
55
|
+
* console.log(`${item.title} -> Page ${item.page}`);
|
|
56
|
+
* for (const child of item.children) {
|
|
57
|
+
* console.log(` ${child.title} -> Page ${child.page}`);
|
|
58
|
+
* }
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export class OutlineItem {
|
|
63
|
+
/**
|
|
64
|
+
* The title/label of this outline item.
|
|
65
|
+
* @readonly
|
|
66
|
+
* @type {string}
|
|
67
|
+
*/
|
|
68
|
+
title;
|
|
69
|
+
/**
|
|
70
|
+
* The destination page number (0-indexed), if this item links to a page.
|
|
71
|
+
* Undefined if this item links to a URI instead.
|
|
72
|
+
* @readonly
|
|
73
|
+
* @type {number | undefined}
|
|
74
|
+
*/
|
|
75
|
+
page;
|
|
76
|
+
/**
|
|
77
|
+
* The destination URI, if this item links to an external resource.
|
|
78
|
+
* Undefined if this item links to a page instead.
|
|
79
|
+
* @readonly
|
|
80
|
+
* @type {string | undefined}
|
|
81
|
+
*/
|
|
82
|
+
uri;
|
|
83
|
+
/**
|
|
84
|
+
* Child outline items nested under this item.
|
|
85
|
+
* @readonly
|
|
86
|
+
* @type {OutlineItem[]}
|
|
87
|
+
*/
|
|
88
|
+
children;
|
|
89
|
+
/**
|
|
90
|
+
* Creates a new outline item.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} title - The title of the outline item
|
|
93
|
+
* @param {number} [page] - The destination page number (0-indexed)
|
|
94
|
+
* @param {string} [uri] - The destination URI
|
|
95
|
+
*/
|
|
96
|
+
constructor(title, page, uri) {
|
|
97
|
+
this.title = title;
|
|
98
|
+
this.page = page;
|
|
99
|
+
this.uri = uri;
|
|
100
|
+
this.children = [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* A page in a PDF document.
|
|
105
|
+
*
|
|
106
|
+
* Represents a single page within a PDF document with methods for rendering,
|
|
107
|
+
* text extraction, search, and link retrieval. Pages are loaded from a Document
|
|
108
|
+
* using `Document.loadPage()`.
|
|
109
|
+
*
|
|
110
|
+
* **Important**: Pages must be explicitly freed using `drop()` when no longer needed
|
|
111
|
+
* to prevent memory leaks.
|
|
112
|
+
*
|
|
113
|
+
* @class Page
|
|
114
|
+
* @example
|
|
115
|
+
* ```typescript
|
|
116
|
+
* const doc = Document.open('document.pdf');
|
|
117
|
+
* const page = doc.loadPage(0); // Load first page
|
|
118
|
+
*
|
|
119
|
+
* try {
|
|
120
|
+
* // Get page dimensions
|
|
121
|
+
* console.log(`Size: ${page.bounds.width} x ${page.bounds.height}`);
|
|
122
|
+
* console.log(`Rotation: ${page.rotation}°`);
|
|
123
|
+
*
|
|
124
|
+
* // Extract text
|
|
125
|
+
* const text = page.extractText();
|
|
126
|
+
* console.log(text);
|
|
127
|
+
*
|
|
128
|
+
* // Search for text
|
|
129
|
+
* const hits = page.searchText('hello');
|
|
130
|
+
* console.log(`Found ${hits.length} occurrences`);
|
|
131
|
+
*
|
|
132
|
+
* // Render to pixmap
|
|
133
|
+
* const matrix = Matrix.scale(2, 2); // 2x zoom
|
|
134
|
+
* const pixmap = page.toPixmap(matrix);
|
|
135
|
+
* } finally {
|
|
136
|
+
* page.drop(); // Always clean up!
|
|
137
|
+
* }
|
|
138
|
+
*
|
|
139
|
+
* doc.close();
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export class Page {
|
|
143
|
+
/** @internal - Native context handle */
|
|
144
|
+
_ctx;
|
|
145
|
+
/** @internal - Native page handle */
|
|
146
|
+
_page;
|
|
147
|
+
_pageNumber;
|
|
148
|
+
_bounds;
|
|
149
|
+
_mediaBox;
|
|
150
|
+
_rotation;
|
|
151
|
+
/** @internal */
|
|
152
|
+
constructor(_document, pageNumber, bounds, mediaBox, rotation, ctx, page) {
|
|
153
|
+
this._ctx = ctx;
|
|
154
|
+
this._page = page;
|
|
155
|
+
this._pageNumber = pageNumber;
|
|
156
|
+
this._bounds = bounds;
|
|
157
|
+
this._mediaBox = mediaBox;
|
|
158
|
+
this._rotation = rotation;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Get the page number (0-based)
|
|
162
|
+
*/
|
|
163
|
+
get pageNumber() {
|
|
164
|
+
return this._pageNumber;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get the page bounds
|
|
168
|
+
*/
|
|
169
|
+
get bounds() {
|
|
170
|
+
return this._bounds;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get the media box
|
|
174
|
+
*/
|
|
175
|
+
get mediaBox() {
|
|
176
|
+
return this._mediaBox;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get the crop box
|
|
180
|
+
*/
|
|
181
|
+
get cropBox() {
|
|
182
|
+
// Default to media box if no crop box specified
|
|
183
|
+
return this._mediaBox;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get the page rotation (0, 90, 180, or 270)
|
|
187
|
+
*/
|
|
188
|
+
get rotation() {
|
|
189
|
+
return this._rotation;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Render the page to a pixmap using FFI
|
|
193
|
+
* @throws Error when native bindings are not available
|
|
194
|
+
*/
|
|
195
|
+
toPixmap(matrix = Matrix.IDENTITY, colorspace = Colorspace.deviceRGB(), alpha = true) {
|
|
196
|
+
if (!this._ctx || !this._page) {
|
|
197
|
+
throw new Error('Page rendering requires native FFI bindings (fz_run_page, fz_new_bbox_device)');
|
|
198
|
+
}
|
|
199
|
+
const m = Matrix.from(matrix);
|
|
200
|
+
const nativeMatrix = {
|
|
201
|
+
a: m.a,
|
|
202
|
+
b: m.b,
|
|
203
|
+
c: m.c,
|
|
204
|
+
d: m.d,
|
|
205
|
+
e: m.e,
|
|
206
|
+
f: m.f
|
|
207
|
+
};
|
|
208
|
+
const nativeColorspace = {
|
|
209
|
+
name: colorspace.name,
|
|
210
|
+
n: colorspace.n,
|
|
211
|
+
type: colorspace.type.toString()
|
|
212
|
+
};
|
|
213
|
+
const nativePixmap = native.renderPage(this._ctx, this._page, nativeMatrix, nativeColorspace, alpha);
|
|
214
|
+
// Convert native pixmap to TypeScript Pixmap
|
|
215
|
+
return Pixmap.create(colorspace, nativePixmap.width, nativePixmap.height, alpha);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Render the page to PNG using FFI
|
|
219
|
+
* @throws Error when native bindings are not available
|
|
220
|
+
*/
|
|
221
|
+
toPNG(dpi = 72) {
|
|
222
|
+
if (!this._ctx || !this._page) {
|
|
223
|
+
throw new Error('PNG encoding requires native FFI bindings (fz_save_pixmap_as_png)');
|
|
224
|
+
}
|
|
225
|
+
const nativeColorspace = {
|
|
226
|
+
name: 'DeviceRGB',
|
|
227
|
+
n: 3,
|
|
228
|
+
type: 'RGB'
|
|
229
|
+
};
|
|
230
|
+
const pngBuffer = native.renderPageToPNG(this._ctx, this._page, dpi, nativeColorspace);
|
|
231
|
+
return new Uint8Array(pngBuffer);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Render page with advanced options
|
|
235
|
+
*
|
|
236
|
+
* Provides fine-grained control over rendering quality, colorspace,
|
|
237
|
+
* anti-aliasing, and other rendering parameters.
|
|
238
|
+
*
|
|
239
|
+
* @param options - Rendering options
|
|
240
|
+
* @returns Pixmap containing the rendered page
|
|
241
|
+
* @throws Error if native bindings are not available
|
|
242
|
+
* @throws Error if options are invalid
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```typescript
|
|
246
|
+
* // High-quality print rendering
|
|
247
|
+
* const pixmap = page.renderWithOptions({
|
|
248
|
+
* dpi: 300,
|
|
249
|
+
* colorspace: Colorspace.deviceRGB(),
|
|
250
|
+
* alpha: true,
|
|
251
|
+
* antiAlias: AntiAliasLevel.High
|
|
252
|
+
* });
|
|
253
|
+
*
|
|
254
|
+
* // Fast preview rendering
|
|
255
|
+
* const preview = page.renderWithOptions({
|
|
256
|
+
* dpi: 72,
|
|
257
|
+
* antiAlias: AntiAliasLevel.Low
|
|
258
|
+
* });
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
renderWithOptions(options = {}) {
|
|
262
|
+
if (!this._ctx || !this._page) {
|
|
263
|
+
throw new Error('Rendering requires native FFI bindings');
|
|
264
|
+
}
|
|
265
|
+
// Validate options
|
|
266
|
+
validateRenderOptions(options);
|
|
267
|
+
// Merge with defaults
|
|
268
|
+
const opts = mergeRenderOptions(options);
|
|
269
|
+
// Calculate transform matrix from DPI or use provided transform
|
|
270
|
+
const matrix = opts.transform
|
|
271
|
+
? opts.transform
|
|
272
|
+
: Matrix.scale(dpiToScale(opts.dpi), dpiToScale(opts.dpi));
|
|
273
|
+
// Use existing toPixmap method with derived parameters
|
|
274
|
+
// TODO: In future, pass antiAlias level, renderAnnotations, renderFormFields through FFI
|
|
275
|
+
return this.toPixmap(matrix, opts.colorspace, opts.alpha);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Render page with progress tracking
|
|
279
|
+
*
|
|
280
|
+
* Extended rendering with progress callbacks and timeout support.
|
|
281
|
+
* Useful for long-running renders of complex pages.
|
|
282
|
+
*
|
|
283
|
+
* @param options - Extended rendering options with callbacks
|
|
284
|
+
* @returns Promise resolving to the rendered pixmap
|
|
285
|
+
* @throws Error if rendering fails or times out
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```typescript
|
|
289
|
+
* const pixmap = await page.renderWithProgress({
|
|
290
|
+
* dpi: 600,
|
|
291
|
+
* onProgress: (current, total) => {
|
|
292
|
+
* console.log(`Rendering: ${Math.round(current/total * 100)}%`);
|
|
293
|
+
* return true; // Continue rendering
|
|
294
|
+
* },
|
|
295
|
+
* onError: (error) => {
|
|
296
|
+
* console.error('Render error:', error);
|
|
297
|
+
* },
|
|
298
|
+
* timeout: 30000 // 30 second timeout
|
|
299
|
+
* });
|
|
300
|
+
* ```
|
|
301
|
+
*/
|
|
302
|
+
async renderWithProgress(options = {}) {
|
|
303
|
+
return new Promise((resolve, reject) => {
|
|
304
|
+
// const startTime = Date.now(); // TODO: Use for progress tracking
|
|
305
|
+
let timeoutId;
|
|
306
|
+
// Set up timeout if specified
|
|
307
|
+
if (options.timeout) {
|
|
308
|
+
timeoutId = setTimeout(() => {
|
|
309
|
+
const error = 'Rendering timeout exceeded';
|
|
310
|
+
if (options.onError) {
|
|
311
|
+
options.onError(error);
|
|
312
|
+
}
|
|
313
|
+
reject(new Error(error));
|
|
314
|
+
}, options.timeout);
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
// For now, perform synchronous render
|
|
318
|
+
// TODO: Implement async rendering with FFI progress callbacks
|
|
319
|
+
const pixmap = this.renderWithOptions(options);
|
|
320
|
+
// Simulate progress callback
|
|
321
|
+
if (options.onProgress) {
|
|
322
|
+
options.onProgress(100, 100);
|
|
323
|
+
}
|
|
324
|
+
if (timeoutId) {
|
|
325
|
+
clearTimeout(timeoutId);
|
|
326
|
+
}
|
|
327
|
+
resolve(pixmap);
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
if (timeoutId) {
|
|
331
|
+
clearTimeout(timeoutId);
|
|
332
|
+
}
|
|
333
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
334
|
+
if (options.onError) {
|
|
335
|
+
options.onError(errorMsg);
|
|
336
|
+
}
|
|
337
|
+
reject(error);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Extract text from the page using FFI
|
|
343
|
+
* @throws Error when native bindings are not available
|
|
344
|
+
*/
|
|
345
|
+
getText() {
|
|
346
|
+
if (!this._ctx || !this._page) {
|
|
347
|
+
throw new Error('Text extraction requires native FFI bindings (fz_new_stext_page_from_page)');
|
|
348
|
+
}
|
|
349
|
+
return native.extractText(this._ctx, this._page);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Get text blocks from the page using FFI
|
|
353
|
+
* @throws Error when native bindings are not available
|
|
354
|
+
*/
|
|
355
|
+
getTextBlocks() {
|
|
356
|
+
if (!this._ctx || !this._page) {
|
|
357
|
+
throw new Error('Text block extraction requires native FFI bindings (fz_new_stext_page_from_page)');
|
|
358
|
+
}
|
|
359
|
+
const blocks = native.extractTextBlocks(this._ctx, this._page);
|
|
360
|
+
return blocks.map((block) => ({
|
|
361
|
+
text: block.text,
|
|
362
|
+
bbox: Rect.from(block.bbox),
|
|
363
|
+
lines: [] // Lines will be populated when FFI provides detailed text structure
|
|
364
|
+
}));
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Get links from the page using FFI
|
|
368
|
+
* @throws Error when native bindings are not available
|
|
369
|
+
*/
|
|
370
|
+
getLinks() {
|
|
371
|
+
if (!this._ctx || !this._page) {
|
|
372
|
+
throw new Error('Link extraction requires native FFI bindings (fz_load_links, pdf_annot_type)');
|
|
373
|
+
}
|
|
374
|
+
const links = native.getPageLinks(this._ctx, this._page);
|
|
375
|
+
return links.map((link) => {
|
|
376
|
+
const bounds = Rect.from(link.rect);
|
|
377
|
+
if (link.uri) {
|
|
378
|
+
return { bounds, dest: LinkDestType.URI, uri: link.uri };
|
|
379
|
+
}
|
|
380
|
+
else if (link.page !== undefined) {
|
|
381
|
+
return { bounds, dest: LinkDestType.Goto, page: link.page };
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
return { bounds, dest: LinkDestType.None };
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Search for text on the page using FFI
|
|
390
|
+
* @throws Error when native bindings are not available
|
|
391
|
+
*/
|
|
392
|
+
search(needle) {
|
|
393
|
+
if (!this._ctx || !this._page) {
|
|
394
|
+
throw new Error('Text search requires native FFI bindings (fz_search_stext_page)');
|
|
395
|
+
}
|
|
396
|
+
const rects = native.searchText(this._ctx, this._page, needle, false);
|
|
397
|
+
// Convert Rects to Quads (each rect becomes a quad)
|
|
398
|
+
return rects.map((rect) => {
|
|
399
|
+
const r = Rect.from(rect);
|
|
400
|
+
return new Quad({ x: r.x0, y: r.y0 }, { x: r.x1, y: r.y0 }, { x: r.x0, y: r.y1 }, { x: r.x1, y: r.y1 });
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Search for text on the page (alias for search)
|
|
405
|
+
*/
|
|
406
|
+
searchText(needle) {
|
|
407
|
+
return this.search(needle);
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Extract text from the page (alias for getText for MuPDF API compatibility)
|
|
411
|
+
*/
|
|
412
|
+
extractText() {
|
|
413
|
+
return this.getText();
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Extract text blocks from the page (alias for getTextBlocks for MuPDF API compatibility)
|
|
417
|
+
*/
|
|
418
|
+
extractTextBlocks() {
|
|
419
|
+
return this.getTextBlocks();
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Drop/release the page resources
|
|
423
|
+
*/
|
|
424
|
+
drop() {
|
|
425
|
+
// Clean up native resources if available
|
|
426
|
+
if (this._ctx && this._page) {
|
|
427
|
+
// Native cleanup would go here
|
|
428
|
+
delete this._page;
|
|
429
|
+
delete this._ctx;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* A PDF or other supported document format.
|
|
435
|
+
*
|
|
436
|
+
* The Document class is the main entry point for working with PDF files. It provides
|
|
437
|
+
* methods for opening documents from files or memory, accessing pages, checking security,
|
|
438
|
+
* reading metadata, and performing document-level operations.
|
|
439
|
+
*
|
|
440
|
+
* **Supported formats**: PDF, XPS, CBZ, and other formats supported by MuPDF.
|
|
441
|
+
*
|
|
442
|
+
* **Resource Management**: Documents must be explicitly closed using `close()` when
|
|
443
|
+
* done to free native resources and prevent memory leaks.
|
|
444
|
+
*
|
|
445
|
+
* @class Document
|
|
446
|
+
* @example
|
|
447
|
+
* ```typescript
|
|
448
|
+
* // Open from file
|
|
449
|
+
* const doc = Document.open('document.pdf');
|
|
450
|
+
*
|
|
451
|
+
* // Open from buffer
|
|
452
|
+
* const buffer = fs.readFileSync('document.pdf');
|
|
453
|
+
* const doc2 = Document.openFromBuffer(buffer);
|
|
454
|
+
*
|
|
455
|
+
* // Check basic info
|
|
456
|
+
* console.log(`Pages: ${doc.pageCount}`);
|
|
457
|
+
* console.log(`Title: ${doc.getMetadata('Title')}`);
|
|
458
|
+
* console.log(`Author: ${doc.getMetadata('Author')}`);
|
|
459
|
+
*
|
|
460
|
+
* // Handle password-protected PDFs
|
|
461
|
+
* if (doc.needsPassword()) {
|
|
462
|
+
* const success = doc.authenticate('password123');
|
|
463
|
+
* if (!success) {
|
|
464
|
+
* throw new Error('Invalid password');
|
|
465
|
+
* }
|
|
466
|
+
* }
|
|
467
|
+
*
|
|
468
|
+
* // Check permissions
|
|
469
|
+
* if (!doc.hasPermission(4)) { // FZ_PERMISSION_PRINT
|
|
470
|
+
* console.warn('Printing is not allowed');
|
|
471
|
+
* }
|
|
472
|
+
*
|
|
473
|
+
* // Work with pages
|
|
474
|
+
* for (let i = 0; i < doc.pageCount; i++) {
|
|
475
|
+
* const page = doc.loadPage(i);
|
|
476
|
+
* const text = page.extractText();
|
|
477
|
+
* console.log(`Page ${i + 1}: ${text.substring(0, 100)}...`);
|
|
478
|
+
* page.drop();
|
|
479
|
+
* }
|
|
480
|
+
*
|
|
481
|
+
* // Save modified document
|
|
482
|
+
* doc.save('output.pdf');
|
|
483
|
+
*
|
|
484
|
+
* // Always clean up
|
|
485
|
+
* doc.close();
|
|
486
|
+
* ```
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* ```typescript
|
|
490
|
+
* // Using try-finally for proper cleanup
|
|
491
|
+
* const doc = Document.open('document.pdf');
|
|
492
|
+
* try {
|
|
493
|
+
* // Work with document
|
|
494
|
+
* const page = doc.loadPage(0);
|
|
495
|
+
* try {
|
|
496
|
+
* const text = page.extractText();
|
|
497
|
+
* console.log(text);
|
|
498
|
+
* } finally {
|
|
499
|
+
* page.drop();
|
|
500
|
+
* }
|
|
501
|
+
* } finally {
|
|
502
|
+
* doc.close();
|
|
503
|
+
* }
|
|
504
|
+
* ```
|
|
505
|
+
*/
|
|
506
|
+
export class Document {
|
|
507
|
+
_ctx;
|
|
508
|
+
_doc;
|
|
509
|
+
_pages;
|
|
510
|
+
_format;
|
|
511
|
+
_metadata;
|
|
512
|
+
_outline;
|
|
513
|
+
_needsPassword;
|
|
514
|
+
_isAuthenticated;
|
|
515
|
+
constructor(_buffer, pages, format, needsPassword, isAuthenticated) {
|
|
516
|
+
this._pages = pages;
|
|
517
|
+
this._format = format;
|
|
518
|
+
this._needsPassword = needsPassword;
|
|
519
|
+
this._isAuthenticated = isAuthenticated;
|
|
520
|
+
this._metadata = new Map();
|
|
521
|
+
this._outline = [];
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Open a document from a file path
|
|
525
|
+
* Uses native FFI bindings when available for full rendering support
|
|
526
|
+
*/
|
|
527
|
+
static open(path, password) {
|
|
528
|
+
// Try native bindings first for full rendering support
|
|
529
|
+
try {
|
|
530
|
+
const ctx = native.createContext();
|
|
531
|
+
const nativeDoc = native.openDocumentFromPath(ctx, path);
|
|
532
|
+
const pageCount = native.countPages(ctx, nativeDoc);
|
|
533
|
+
// Parse PDF header for version info
|
|
534
|
+
const fs = require('node:fs');
|
|
535
|
+
const data = fs.readFileSync(path);
|
|
536
|
+
const header = new TextDecoder().decode(data.slice(0, Math.min(1024, data.length)));
|
|
537
|
+
const versionMatch = header.match(/%PDF-(\d+\.\d+)/);
|
|
538
|
+
const version = versionMatch ? versionMatch[1] : '1.4';
|
|
539
|
+
const format = `PDF ${version}`;
|
|
540
|
+
const doc = new Document(Buffer.fromBuffer(data), [], format, false, true);
|
|
541
|
+
doc._ctx = ctx;
|
|
542
|
+
doc._doc = nativeDoc;
|
|
543
|
+
// Load pages with native bounds
|
|
544
|
+
const pages = [];
|
|
545
|
+
for (let i = 0; i < pageCount; i++) {
|
|
546
|
+
const nativePage = native.loadPage(ctx, nativeDoc, i);
|
|
547
|
+
const bounds = native.boundPage(ctx, nativePage);
|
|
548
|
+
const rect = new Rect(bounds.x0, bounds.y0, bounds.x1, bounds.y1);
|
|
549
|
+
// Pass native handles through constructor
|
|
550
|
+
const page = new Page(doc, i, rect, rect, 0, ctx, nativePage);
|
|
551
|
+
pages.push(page);
|
|
552
|
+
}
|
|
553
|
+
doc._pages = pages;
|
|
554
|
+
// Authenticate if password provided
|
|
555
|
+
if (password !== undefined) {
|
|
556
|
+
doc.authenticate(password);
|
|
557
|
+
}
|
|
558
|
+
return doc;
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
// Fall back to pure TypeScript implementation
|
|
562
|
+
const fs = require('node:fs');
|
|
563
|
+
const data = fs.readFileSync(path);
|
|
564
|
+
return Document.fromBuffer(Buffer.fromBuffer(data), password);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Open a document from a buffer
|
|
569
|
+
* Uses native FFI bindings when available for full rendering support
|
|
570
|
+
*/
|
|
571
|
+
static fromBuffer(buffer, password) {
|
|
572
|
+
// Parse PDF header to get version
|
|
573
|
+
const data = buffer.toUint8Array();
|
|
574
|
+
const header = new TextDecoder().decode(data.slice(0, Math.min(1024, data.length)));
|
|
575
|
+
// Check if it's a PDF
|
|
576
|
+
if (!header.startsWith('%PDF-')) {
|
|
577
|
+
throw MicroPDFError.argument('Not a PDF document');
|
|
578
|
+
}
|
|
579
|
+
// Extract version
|
|
580
|
+
const versionMatch = header.match(/%PDF-(\d+\.\d+)/);
|
|
581
|
+
const version = versionMatch ? versionMatch[1] : '1.4';
|
|
582
|
+
const format = `PDF ${version}`;
|
|
583
|
+
// Try native bindings first for full rendering support
|
|
584
|
+
try {
|
|
585
|
+
const ctx = native.createContext();
|
|
586
|
+
const nativeDoc = native.openDocument(ctx, globalThis.Buffer.from(data));
|
|
587
|
+
const pageCount = native.countPages(ctx, nativeDoc);
|
|
588
|
+
const doc = new Document(buffer, [], format, false, true);
|
|
589
|
+
doc._ctx = ctx;
|
|
590
|
+
doc._doc = nativeDoc;
|
|
591
|
+
// Load pages with native bounds
|
|
592
|
+
const pages = [];
|
|
593
|
+
for (let i = 0; i < pageCount; i++) {
|
|
594
|
+
const nativePage = native.loadPage(ctx, nativeDoc, i);
|
|
595
|
+
const bounds = native.boundPage(ctx, nativePage);
|
|
596
|
+
const rect = new Rect(bounds.x0, bounds.y0, bounds.x1, bounds.y1);
|
|
597
|
+
// Pass native handles through constructor
|
|
598
|
+
const page = new Page(doc, i, rect, rect, 0, ctx, nativePage);
|
|
599
|
+
pages.push(page);
|
|
600
|
+
}
|
|
601
|
+
doc._pages = pages;
|
|
602
|
+
// Authenticate if password provided
|
|
603
|
+
if (password !== undefined) {
|
|
604
|
+
doc.authenticate(password);
|
|
605
|
+
}
|
|
606
|
+
return doc;
|
|
607
|
+
}
|
|
608
|
+
catch {
|
|
609
|
+
// Fall back to pure TypeScript implementation
|
|
610
|
+
}
|
|
611
|
+
// Parse pages (simplified - just count /Type /Page objects)
|
|
612
|
+
const content = new TextDecoder().decode(data);
|
|
613
|
+
const pageMatches = content.match(/\/Type\s*\/Page\b/g);
|
|
614
|
+
const pageCount = pageMatches ? pageMatches.length : 0;
|
|
615
|
+
// Create placeholder pages
|
|
616
|
+
const pages = [];
|
|
617
|
+
const defaultBounds = new Rect(0, 0, 612, 792); // US Letter size
|
|
618
|
+
// Parse MediaBox if present
|
|
619
|
+
const mediaBoxMatch = content.match(/\/MediaBox\s*\[\s*([\d.-]+)\s+([\d.-]+)\s+([\d.-]+)\s+([\d.-]+)\s*]/);
|
|
620
|
+
let mediaBox = defaultBounds;
|
|
621
|
+
if (mediaBoxMatch) {
|
|
622
|
+
mediaBox = new Rect(Number.parseFloat(mediaBoxMatch[1] ?? '0'), Number.parseFloat(mediaBoxMatch[2] ?? '0'), Number.parseFloat(mediaBoxMatch[3] ?? '612'), Number.parseFloat(mediaBoxMatch[4] ?? '792'));
|
|
623
|
+
}
|
|
624
|
+
const doc = new Document(buffer, [], format, false, true);
|
|
625
|
+
for (let i = 0; i < pageCount; i++) {
|
|
626
|
+
pages.push(new Page(doc, i, mediaBox, mediaBox, 0));
|
|
627
|
+
}
|
|
628
|
+
// Update pages array
|
|
629
|
+
doc._pages = pages;
|
|
630
|
+
// Try to authenticate if password provided
|
|
631
|
+
if (password !== undefined) {
|
|
632
|
+
doc.authenticate(password);
|
|
633
|
+
}
|
|
634
|
+
return doc;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Open a document from a Uint8Array
|
|
638
|
+
*/
|
|
639
|
+
static fromUint8Array(data, password) {
|
|
640
|
+
return Document.fromBuffer(Buffer.fromUint8Array(data), password);
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Get the page count
|
|
644
|
+
*/
|
|
645
|
+
get pageCount() {
|
|
646
|
+
return this._pages.length;
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Get the document format (e.g., "PDF 1.4")
|
|
650
|
+
*/
|
|
651
|
+
get format() {
|
|
652
|
+
return this._format;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Check if the document needs a password
|
|
656
|
+
*/
|
|
657
|
+
get needsPassword() {
|
|
658
|
+
return this._needsPassword;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Check if the document is authenticated
|
|
662
|
+
*/
|
|
663
|
+
get isAuthenticated() {
|
|
664
|
+
return this._isAuthenticated;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Check if the document is a PDF
|
|
668
|
+
*/
|
|
669
|
+
get isPDF() {
|
|
670
|
+
return this._format.startsWith('PDF');
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Check if the document is reflowable (e.g., EPUB)
|
|
674
|
+
*/
|
|
675
|
+
get isReflowable() {
|
|
676
|
+
return false; // PDFs are not reflowable
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Authenticate with a password using FFI
|
|
680
|
+
* @returns true if authentication successful
|
|
681
|
+
* @throws Error when native bindings are not available
|
|
682
|
+
*/
|
|
683
|
+
authenticate(password) {
|
|
684
|
+
if (!this._ctx || !this._doc) {
|
|
685
|
+
throw new Error('Authentication requires native FFI bindings (pdf_authenticate_password)');
|
|
686
|
+
}
|
|
687
|
+
const result = native.authenticatePassword(this._ctx, this._doc, password);
|
|
688
|
+
if (result) {
|
|
689
|
+
this._isAuthenticated = true;
|
|
690
|
+
}
|
|
691
|
+
return result;
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Check if the document has a specific permission using FFI
|
|
695
|
+
* @param permission The permission to check (print, edit, copy, annotate)
|
|
696
|
+
* @throws Error when native bindings are not available
|
|
697
|
+
*/
|
|
698
|
+
hasPermission(permission) {
|
|
699
|
+
if (!this._ctx || !this._doc) {
|
|
700
|
+
throw new Error('Permission check requires native FFI bindings (pdf_has_permission)');
|
|
701
|
+
}
|
|
702
|
+
// Map permission string to permission bits
|
|
703
|
+
const permissionMap = {
|
|
704
|
+
print: 4,
|
|
705
|
+
edit: 8,
|
|
706
|
+
copy: 16,
|
|
707
|
+
annotate: 32
|
|
708
|
+
};
|
|
709
|
+
const permissionBit = permissionMap[permission] ?? 0;
|
|
710
|
+
return native.hasPermission(this._ctx, this._doc, permissionBit);
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Get page label for a given page number
|
|
714
|
+
* @param pageNum Page number (0-based)
|
|
715
|
+
* @returns Page label (e.g., "i", "ii", "1", "2", "A-1")
|
|
716
|
+
*/
|
|
717
|
+
getPageLabel(pageNum) {
|
|
718
|
+
// Default to simple page numbering if no label scheme defined
|
|
719
|
+
return String(pageNum + 1);
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Get page number from a page label
|
|
723
|
+
* @param label Page label to look up
|
|
724
|
+
* @returns Page number (0-based) or -1 if not found
|
|
725
|
+
*/
|
|
726
|
+
getPageFromLabel(label) {
|
|
727
|
+
// Simple implementation: try to parse as number
|
|
728
|
+
const num = Number.parseInt(label, 10);
|
|
729
|
+
if (!isNaN(num) && num > 0 && num <= this.pageCount) {
|
|
730
|
+
return num - 1;
|
|
731
|
+
}
|
|
732
|
+
return -1;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Check if the document is valid (not corrupted)
|
|
736
|
+
*/
|
|
737
|
+
isValid() {
|
|
738
|
+
return this._pages.length > 0;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Resolve a named destination to a page location using FFI
|
|
742
|
+
* @param name Named destination (e.g., "section1", "chapter2")
|
|
743
|
+
* @returns Page number (0-based) or undefined if not found
|
|
744
|
+
* @throws Error when native bindings are not available
|
|
745
|
+
*/
|
|
746
|
+
resolveNamedDest(name) {
|
|
747
|
+
if (!this._ctx || !this._doc) {
|
|
748
|
+
throw new Error('Named destination lookup requires native FFI bindings (pdf_lookup_dest)');
|
|
749
|
+
}
|
|
750
|
+
const result = native.resolveLink(this._ctx, this._doc, name);
|
|
751
|
+
return result !== null ? result : undefined;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Count chapters in the document (for structured documents)
|
|
755
|
+
*/
|
|
756
|
+
countChapters() {
|
|
757
|
+
// For PDFs without chapter structure, treat as single chapter
|
|
758
|
+
return 1;
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Count pages in a specific chapter
|
|
762
|
+
* @param chapterIndex Chapter index (0-based)
|
|
763
|
+
*/
|
|
764
|
+
countChapterPages(chapterIndex) {
|
|
765
|
+
if (chapterIndex === 0) {
|
|
766
|
+
return this.pageCount;
|
|
767
|
+
}
|
|
768
|
+
return 0;
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Get page number from a chapter and page location
|
|
772
|
+
* @param chapter Chapter index (0-based)
|
|
773
|
+
* @param page Page within chapter (0-based)
|
|
774
|
+
*/
|
|
775
|
+
pageNumberFromLocation(chapter, page) {
|
|
776
|
+
// Simple implementation: just return page for single chapter
|
|
777
|
+
if (chapter === 0) {
|
|
778
|
+
return page;
|
|
779
|
+
}
|
|
780
|
+
return -1;
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Layout the document with specific width and height (for reflowable documents)
|
|
784
|
+
* @param width Target width
|
|
785
|
+
* @param height Target height
|
|
786
|
+
* @param em Font size in points
|
|
787
|
+
*/
|
|
788
|
+
layout(_width, _height, _em = 12) {
|
|
789
|
+
// No-op for non-reflowable PDFs
|
|
790
|
+
// This is used for EPUB and other reflowable formats
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Clone the document (create a copy)
|
|
794
|
+
* Note: This creates a shallow copy sharing the same data
|
|
795
|
+
*/
|
|
796
|
+
clone() {
|
|
797
|
+
const cloned = Object.create(Document.prototype);
|
|
798
|
+
cloned._pages = this._pages;
|
|
799
|
+
cloned._format = this._format;
|
|
800
|
+
cloned._needsPassword = this._needsPassword;
|
|
801
|
+
cloned._isAuthenticated = this._isAuthenticated;
|
|
802
|
+
cloned._metadata = new Map(this._metadata);
|
|
803
|
+
cloned._outline = [...this._outline];
|
|
804
|
+
return cloned;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Get a page by index
|
|
808
|
+
*/
|
|
809
|
+
getPage(index) {
|
|
810
|
+
if (index < 0 || index >= this._pages.length) {
|
|
811
|
+
throw MicroPDFError.argument(`Page index ${index} out of bounds (0..${this._pages.length})`);
|
|
812
|
+
}
|
|
813
|
+
return this._pages[index];
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Load a page by index (alias for getPage for MuPDF API compatibility)
|
|
817
|
+
*/
|
|
818
|
+
loadPage(index) {
|
|
819
|
+
return this.getPage(index);
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Check if the document needs a password (method form for API compatibility)
|
|
823
|
+
*/
|
|
824
|
+
needsPasswordCheck() {
|
|
825
|
+
return this._needsPassword;
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Close the document and release resources
|
|
829
|
+
*/
|
|
830
|
+
close() {
|
|
831
|
+
// Clean up native resources if available
|
|
832
|
+
if (this._ctx && this._doc) {
|
|
833
|
+
// Native cleanup would go here
|
|
834
|
+
delete this._doc;
|
|
835
|
+
delete this._ctx;
|
|
836
|
+
}
|
|
837
|
+
this._pages = [];
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Iterate over all pages
|
|
841
|
+
*/
|
|
842
|
+
*pages() {
|
|
843
|
+
for (const page of this._pages) {
|
|
844
|
+
yield page;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Get the document outline (table of contents)
|
|
849
|
+
*/
|
|
850
|
+
getOutline() {
|
|
851
|
+
return this._outline;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Get metadata value
|
|
855
|
+
*/
|
|
856
|
+
getMetadata(key) {
|
|
857
|
+
return this._metadata.get(key);
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Set metadata value
|
|
861
|
+
*/
|
|
862
|
+
setMetadata(key, value) {
|
|
863
|
+
this._metadata.set(key, value);
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Get the title
|
|
867
|
+
*/
|
|
868
|
+
get title() {
|
|
869
|
+
return this._metadata.get('info:Title');
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Get the author
|
|
873
|
+
*/
|
|
874
|
+
get author() {
|
|
875
|
+
return this._metadata.get('info:Author');
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Get the subject
|
|
879
|
+
*/
|
|
880
|
+
get subject() {
|
|
881
|
+
return this._metadata.get('info:Subject');
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Get the keywords
|
|
885
|
+
*/
|
|
886
|
+
get keywords() {
|
|
887
|
+
return this._metadata.get('info:Keywords');
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Get the creator application
|
|
891
|
+
*/
|
|
892
|
+
get creator() {
|
|
893
|
+
return this._metadata.get('info:Creator');
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Get the producer application
|
|
897
|
+
*/
|
|
898
|
+
get producer() {
|
|
899
|
+
return this._metadata.get('info:Producer');
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
//# sourceMappingURL=document.js.map
|