@embedpdf/engines 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/converters.cjs +130 -115
  2. package/dist/converters.cjs.map +1 -1
  3. package/dist/converters.d.ts +7 -12
  4. package/dist/converters.js +125 -86
  5. package/dist/converters.js.map +1 -1
  6. package/dist/index.cjs +5362 -5978
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.ts +1479 -6
  9. package/dist/index.js +5399 -356
  10. package/dist/index.js.map +1 -1
  11. package/dist/pdfium-direct-engine.cjs +4037 -0
  12. package/dist/pdfium-direct-engine.cjs.map +1 -0
  13. package/dist/{pdfium.d.cts → pdfium-direct-engine.d.ts} +7 -99
  14. package/dist/pdfium-direct-engine.js +4035 -0
  15. package/dist/pdfium-direct-engine.js.map +1 -0
  16. package/dist/pdfium-worker-engine.cjs +800 -0
  17. package/dist/pdfium-worker-engine.cjs.map +1 -0
  18. package/dist/{worker.d.cts → pdfium-worker-engine.d.ts} +38 -6
  19. package/dist/pdfium-worker-engine.js +798 -0
  20. package/dist/pdfium-worker-engine.js.map +1 -0
  21. package/dist/pdfium.cjs +4243 -5647
  22. package/dist/pdfium.cjs.map +1 -1
  23. package/dist/pdfium.d.ts +134 -6
  24. package/dist/pdfium.js +4288 -21
  25. package/dist/pdfium.js.map +1 -1
  26. package/dist/preact.cjs +39 -0
  27. package/dist/preact.cjs.map +1 -0
  28. package/dist/preact.d.ts +13 -0
  29. package/dist/preact.js +37 -0
  30. package/dist/preact.js.map +1 -0
  31. package/dist/react.cjs +39 -0
  32. package/dist/react.cjs.map +1 -0
  33. package/dist/react.d.ts +13 -0
  34. package/dist/react.js +37 -0
  35. package/dist/react.js.map +1 -0
  36. package/dist/worker.cjs +771 -1104
  37. package/dist/worker.cjs.map +1 -1
  38. package/dist/worker.d.ts +32 -6
  39. package/dist/worker.js +786 -11
  40. package/dist/worker.js.map +1 -1
  41. package/package.json +42 -8
  42. package/dist/chunk-FXQUMVH5.js +0 -4599
  43. package/dist/chunk-FXQUMVH5.js.map +0 -1
  44. package/dist/chunk-YZLT3A2D.js +0 -1101
  45. package/dist/chunk-YZLT3A2D.js.map +0 -1
  46. package/dist/converters.d.cts +0 -69
  47. package/dist/index.d.cts +0 -32
  48. package/dist/runner-BvRtPCKL.d.cts +0 -131
  49. package/dist/runner-BvRtPCKL.d.ts +0 -131
package/dist/pdfium.js CHANGED
@@ -1,22 +1,4289 @@
1
- import {
2
- BitmapFormat,
3
- PdfiumEngine,
4
- PdfiumEngineRunner,
5
- PdfiumErrorCode,
6
- RenderFlag,
7
- browserImageDataToBlobConverter,
8
- readArrayBuffer,
9
- readString
10
- } from "./chunk-FXQUMVH5.js";
11
- import "./chunk-YZLT3A2D.js";
12
- export {
13
- BitmapFormat,
14
- PdfiumEngine,
15
- PdfiumEngineRunner,
16
- PdfiumErrorCode,
17
- RenderFlag,
18
- browserImageDataToBlobConverter,
19
- readArrayBuffer,
20
- readString
1
+ import { NoopLogger, PdfTaskHelper, PdfErrorCode, Task, Rotation, PdfAnnotationSubtype, PdfPageObjectType, PdfAnnotationObjectStatus, quadToRect, PDF_FORM_FIELD_TYPE, toIntRect, transformRect, toIntSize, transformSize, PdfActionType, PdfZoomMode, AppearanceMode, MatchFlag } from '@embedpdf/models';
2
+ import { init } from '@embedpdf/pdfium';
3
+
4
+ /**
5
+ * Read string from WASM heap
6
+ * @param wasmModule - pdfium wasm module instance
7
+ * @param readChars - function to read chars
8
+ * @param parseChars - function to parse chars
9
+ * @param defaultLength - default length of chars that needs to read
10
+ * @returns string from the heap
11
+ *
12
+ * @public
13
+ */
14
+ function readString(wasmModule, readChars, parseChars, defaultLength = 100) {
15
+ let buffer = wasmModule.wasmExports.malloc(defaultLength);
16
+ for (let i = 0; i < defaultLength; i++) {
17
+ wasmModule.HEAP8[buffer + i] = 0;
18
+ }
19
+ const actualLength = readChars(buffer, defaultLength);
20
+ let str;
21
+ if (actualLength > defaultLength) {
22
+ wasmModule.wasmExports.free(buffer);
23
+ buffer = wasmModule.wasmExports.malloc(actualLength);
24
+ for (let i = 0; i < actualLength; i++) {
25
+ wasmModule.HEAP8[buffer + i] = 0;
26
+ }
27
+ readChars(buffer, actualLength);
28
+ str = parseChars(buffer);
29
+ }
30
+ else {
31
+ str = parseChars(buffer);
32
+ }
33
+ wasmModule.wasmExports.free(buffer);
34
+ return str;
35
+ }
36
+ /**
37
+ * Read arraybyffer from WASM heap
38
+ * @param wasmModule - pdfium wasm module instance
39
+ * @param readChars - function to read chars
40
+ * @returns arraybuffer from the heap
41
+ *
42
+ * @public
43
+ */
44
+ function readArrayBuffer(wasmModule, readChars) {
45
+ const bufferSize = readChars(0, 0);
46
+ const bufferPtr = wasmModule.wasmExports.malloc(bufferSize);
47
+ readChars(bufferPtr, bufferSize);
48
+ const arrayBuffer = new ArrayBuffer(bufferSize);
49
+ const view = new DataView(arrayBuffer);
50
+ for (let i = 0; i < bufferSize; i++) {
51
+ view.setInt8(i, wasmModule.getValue(bufferPtr + i, 'i8'));
52
+ }
53
+ wasmModule.wasmExports.free(bufferPtr);
54
+ return arrayBuffer;
55
+ }
56
+
57
+ class PdfCache {
58
+ constructor(pdfium) {
59
+ this.pdfium = pdfium;
60
+ this.docs = new Map();
61
+ }
62
+ /** Open (or re-use) a document */
63
+ setDocument(id, filePtr, docPtr) {
64
+ let ctx = this.docs.get(id);
65
+ if (!ctx) {
66
+ ctx = new DocumentContext(filePtr, docPtr, this.pdfium);
67
+ this.docs.set(id, ctx);
68
+ }
69
+ }
70
+ /** Retrieve the DocumentContext for a given PdfDocumentObject */
71
+ getContext(docId) {
72
+ return this.docs.get(docId);
73
+ }
74
+ /** Close & fully release a document and all its pages */
75
+ closeDocument(docId) {
76
+ const ctx = this.docs.get(docId);
77
+ if (!ctx)
78
+ return false;
79
+ ctx.dispose(); // tears down pages first, then FPDF_CloseDocument, free()
80
+ this.docs.delete(docId);
81
+ return true;
82
+ }
83
+ }
84
+ class DocumentContext {
85
+ constructor(filePtr, docPtr, pdfium) {
86
+ this.filePtr = filePtr;
87
+ this.docPtr = docPtr;
88
+ this.pageCache = new PageCache(pdfium, docPtr);
89
+ }
90
+ /** Main accessor for pages */
91
+ acquirePage(pageIdx) {
92
+ return this.pageCache.acquire(pageIdx);
93
+ }
94
+ /** Tear down all pages + this document */
95
+ dispose() {
96
+ // 1️⃣ release all pages (with their TTL or immediate)
97
+ this.pageCache.forceReleaseAll();
98
+ // 2️⃣ close the PDFium document
99
+ this.pageCache.pdf.FPDF_CloseDocument(this.docPtr);
100
+ // 3️⃣ free the file handle
101
+ this.pageCache.pdf.pdfium.wasmExports.free(this.filePtr);
102
+ }
103
+ }
104
+ class PageCache {
105
+ constructor(pdf, docPtr) {
106
+ this.pdf = pdf;
107
+ this.docPtr = docPtr;
108
+ this.cache = new Map();
109
+ }
110
+ acquire(pageIdx) {
111
+ let ctx = this.cache.get(pageIdx);
112
+ if (!ctx) {
113
+ const pagePtr = this.pdf.FPDF_LoadPage(this.docPtr, pageIdx);
114
+ ctx = new PageContext(this.pdf, this.docPtr, pageIdx, pagePtr, () => {
115
+ this.cache.delete(pageIdx);
116
+ });
117
+ this.cache.set(pageIdx, ctx);
118
+ }
119
+ ctx.clearExpiryTimer(); // cancel any pending teardown
120
+ ctx.bumpRefCount(); // bump ref‐count
121
+ return ctx;
122
+ }
123
+ forceReleaseAll() {
124
+ for (const ctx of this.cache.values()) {
125
+ ctx.disposeImmediate();
126
+ }
127
+ this.cache.clear();
128
+ }
129
+ }
130
+ const PAGE_TTL = 5000; // 5 seconds
131
+ class PageContext {
132
+ constructor(pdf, docPtr, pageIdx, pagePtr, onFinalDispose) {
133
+ this.pdf = pdf;
134
+ this.docPtr = docPtr;
135
+ this.pageIdx = pageIdx;
136
+ this.pagePtr = pagePtr;
137
+ this.onFinalDispose = onFinalDispose;
138
+ this.refCount = 0;
139
+ this.disposed = false;
140
+ }
141
+ /** Called by PageCache.acquire() */
142
+ bumpRefCount() {
143
+ if (this.disposed)
144
+ throw new Error('Context already disposed');
145
+ this.refCount++;
146
+ }
147
+ /** Called by PageCache.acquire() */
148
+ clearExpiryTimer() {
149
+ if (this.expiryTimer) {
150
+ clearTimeout(this.expiryTimer);
151
+ this.expiryTimer = undefined;
152
+ }
153
+ }
154
+ /** Called by PageCache.release() internally */
155
+ release() {
156
+ if (this.disposed)
157
+ return;
158
+ this.refCount--;
159
+ if (this.refCount === 0) {
160
+ // schedule the one-and-only timer for the page
161
+ this.expiryTimer = setTimeout(() => this.disposeImmediate(), PAGE_TTL);
162
+ }
163
+ }
164
+ /** Tear down _all_ sub-pointers & the page. */
165
+ disposeImmediate() {
166
+ if (this.disposed)
167
+ return;
168
+ this.disposed = true;
169
+ // 2️⃣ close text-page if opened
170
+ if (this.textPagePtr !== undefined) {
171
+ this.pdf.FPDFText_ClosePage(this.textPagePtr);
172
+ }
173
+ // 3️⃣ close form-fill if opened
174
+ if (this.formHandle !== undefined) {
175
+ this.pdf.FORM_OnBeforeClosePage(this.pagePtr, this.formHandle);
176
+ this.pdf.PDFiumExt_ExitFormFillEnvironment(this.formHandle);
177
+ }
178
+ if (this.formInfoPtr !== undefined) {
179
+ this.pdf.PDFiumExt_CloseFormFillInfo(this.formInfoPtr);
180
+ }
181
+ // 4️⃣ finally close the page itself
182
+ this.pdf.FPDF_ClosePage(this.pagePtr);
183
+ // 5️⃣ remove from the cache
184
+ this.onFinalDispose();
185
+ }
186
+ // ── public helpers ──
187
+ /** Always safe: opens (once) and returns the text-page ptr. */
188
+ getTextPage() {
189
+ this.ensureAlive();
190
+ if (this.textPagePtr === undefined) {
191
+ this.textPagePtr = this.pdf.FPDFText_LoadPage(this.pagePtr);
192
+ }
193
+ return this.textPagePtr;
194
+ }
195
+ /** Always safe: opens (once) and returns the form-fill handle. */
196
+ getFormHandle() {
197
+ this.ensureAlive();
198
+ if (this.formHandle === undefined) {
199
+ this.formInfoPtr = this.pdf.PDFiumExt_OpenFormFillInfo();
200
+ this.formHandle = this.pdf.PDFiumExt_InitFormFillEnvironment(this.docPtr, this.formInfoPtr);
201
+ this.pdf.FORM_OnAfterLoadPage(this.pagePtr, this.formHandle);
202
+ }
203
+ return this.formHandle;
204
+ }
205
+ /**
206
+ * Safely execute `fn` with an annotation pointer.
207
+ * Pointer is ALWAYS closed afterwards.
208
+ */
209
+ withAnnotation(annotIdx, fn) {
210
+ this.ensureAlive();
211
+ const annotPtr = this.pdf.FPDFPage_GetAnnot(this.pagePtr, annotIdx);
212
+ try {
213
+ return fn(annotPtr);
214
+ }
215
+ finally {
216
+ this.pdf.FPDFPage_CloseAnnot(annotPtr);
217
+ }
218
+ }
219
+ ensureAlive() {
220
+ if (this.disposed)
221
+ throw new Error('PageContext already disposed');
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Format of bitmap
227
+ */
228
+ var BitmapFormat;
229
+ (function (BitmapFormat) {
230
+ BitmapFormat[BitmapFormat["Bitmap_Gray"] = 1] = "Bitmap_Gray";
231
+ BitmapFormat[BitmapFormat["Bitmap_BGR"] = 2] = "Bitmap_BGR";
232
+ BitmapFormat[BitmapFormat["Bitmap_BGRx"] = 3] = "Bitmap_BGRx";
233
+ BitmapFormat[BitmapFormat["Bitmap_BGRA"] = 4] = "Bitmap_BGRA";
234
+ })(BitmapFormat || (BitmapFormat = {}));
235
+ /**
236
+ * Pdf rendering flag
237
+ */
238
+ var RenderFlag;
239
+ (function (RenderFlag) {
240
+ RenderFlag[RenderFlag["ANNOT"] = 1] = "ANNOT";
241
+ RenderFlag[RenderFlag["LCD_TEXT"] = 2] = "LCD_TEXT";
242
+ RenderFlag[RenderFlag["NO_NATIVETEXT"] = 4] = "NO_NATIVETEXT";
243
+ RenderFlag[RenderFlag["GRAYSCALE"] = 8] = "GRAYSCALE";
244
+ RenderFlag[RenderFlag["DEBUG_INFO"] = 128] = "DEBUG_INFO";
245
+ RenderFlag[RenderFlag["NO_CATCH"] = 256] = "NO_CATCH";
246
+ RenderFlag[RenderFlag["RENDER_LIMITEDIMAGECACHE"] = 512] = "RENDER_LIMITEDIMAGECACHE";
247
+ RenderFlag[RenderFlag["RENDER_FORCEHALFTONE"] = 1024] = "RENDER_FORCEHALFTONE";
248
+ RenderFlag[RenderFlag["PRINTING"] = 2048] = "PRINTING";
249
+ RenderFlag[RenderFlag["REVERSE_BYTE_ORDER"] = 16] = "REVERSE_BYTE_ORDER";
250
+ })(RenderFlag || (RenderFlag = {}));
251
+ const LOG_SOURCE$1 = 'PDFiumEngine';
252
+ const LOG_CATEGORY$1 = 'Engine';
253
+ /**
254
+ * Error code of pdfium library
255
+ */
256
+ var PdfiumErrorCode;
257
+ (function (PdfiumErrorCode) {
258
+ PdfiumErrorCode[PdfiumErrorCode["Success"] = 0] = "Success";
259
+ PdfiumErrorCode[PdfiumErrorCode["Unknown"] = 1] = "Unknown";
260
+ PdfiumErrorCode[PdfiumErrorCode["File"] = 2] = "File";
261
+ PdfiumErrorCode[PdfiumErrorCode["Format"] = 3] = "Format";
262
+ PdfiumErrorCode[PdfiumErrorCode["Password"] = 4] = "Password";
263
+ PdfiumErrorCode[PdfiumErrorCode["Security"] = 5] = "Security";
264
+ PdfiumErrorCode[PdfiumErrorCode["Page"] = 6] = "Page";
265
+ PdfiumErrorCode[PdfiumErrorCode["XFALoad"] = 7] = "XFALoad";
266
+ PdfiumErrorCode[PdfiumErrorCode["XFALayout"] = 8] = "XFALayout";
267
+ })(PdfiumErrorCode || (PdfiumErrorCode = {}));
268
+ const browserImageDataToBlobConverter = (pdfImageData, imageType = 'image/webp') => {
269
+ // Check if we're in a browser environment
270
+ if (typeof OffscreenCanvas === 'undefined') {
271
+ throw new Error('OffscreenCanvas is not available in this environment. ' +
272
+ 'This converter is intended for browser use only. ' +
273
+ 'Please use createNodeImageDataToBlobConverter() or createNodeCanvasImageDataToBlobConverter() for Node.js.');
274
+ }
275
+ const imageData = new ImageData(pdfImageData.data, pdfImageData.width, pdfImageData.height);
276
+ const off = new OffscreenCanvas(imageData.width, imageData.height);
277
+ off.getContext('2d').putImageData(imageData, 0, 0);
278
+ return off.convertToBlob({ type: imageType });
21
279
  };
22
- //# sourceMappingURL=pdfium.js.map
280
+ /**
281
+ * Pdf engine that based on pdfium wasm
282
+ */
283
+ class PdfiumEngine {
284
+ /**
285
+ * Create an instance of PdfiumEngine
286
+ * @param wasmModule - pdfium wasm module
287
+ * @param logger - logger instance
288
+ * @param imageDataToBlobConverter - function to convert ImageData to Blob
289
+ */
290
+ constructor(pdfiumModule, logger = new NoopLogger(), imageDataConverter = browserImageDataToBlobConverter) {
291
+ this.pdfiumModule = pdfiumModule;
292
+ this.logger = logger;
293
+ this.imageDataConverter = imageDataConverter;
294
+ this.cache = new PdfCache(this.pdfiumModule);
295
+ }
296
+ /**
297
+ * {@inheritDoc @embedpdf/models!PdfEngine.initialize}
298
+ *
299
+ * @public
300
+ */
301
+ initialize() {
302
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'initialize');
303
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `Initialize`, 'Begin', 'General');
304
+ this.pdfiumModule.PDFiumExt_Init();
305
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `Initialize`, 'End', 'General');
306
+ return PdfTaskHelper.resolve(true);
307
+ }
308
+ /**
309
+ * {@inheritDoc @embedpdf/models!PdfEngine.destroy}
310
+ *
311
+ * @public
312
+ */
313
+ destroy() {
314
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'destroy');
315
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `Destroy`, 'Begin', 'General');
316
+ this.pdfiumModule.FPDF_DestroyLibrary();
317
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `Destroy`, 'End', 'General');
318
+ return PdfTaskHelper.resolve(true);
319
+ }
320
+ /**
321
+ * {@inheritDoc @embedpdf/models!PdfEngine.openDocumentUrl}
322
+ *
323
+ * @public
324
+ */
325
+ openDocumentUrl(file, options) {
326
+ const mode = options?.mode ?? 'auto';
327
+ const password = options?.password ?? '';
328
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'openDocumentUrl called', file.url, mode);
329
+ // We'll create a task to wrap asynchronous steps
330
+ const task = PdfTaskHelper.create();
331
+ // Start an async procedure
332
+ (async () => {
333
+ try {
334
+ // Decide on approach
335
+ if (mode === 'full-fetch') {
336
+ const fetchFullTask = await this.fetchFullAndOpen(file, password);
337
+ fetchFullTask.wait((doc) => task.resolve(doc), (err) => task.reject(err.reason));
338
+ }
339
+ else if (mode === 'range-request') {
340
+ const openDocumentWithRangeRequestTask = await this.openDocumentWithRangeRequest(file, password);
341
+ openDocumentWithRangeRequestTask.wait((doc) => task.resolve(doc), (err) => task.reject(err.reason));
342
+ }
343
+ else {
344
+ // mode: 'auto'
345
+ const { supportsRanges, fileLength, content } = await this.checkRangeSupport(file.url);
346
+ if (supportsRanges) {
347
+ const openDocumentWithRangeRequestTask = await this.openDocumentWithRangeRequest(file, password, fileLength);
348
+ openDocumentWithRangeRequestTask.wait((doc) => task.resolve(doc), (err) => task.reject(err.reason));
349
+ }
350
+ else if (content) {
351
+ // If we already have the content from the range check, use it
352
+ const pdfFile = { id: file.id, content };
353
+ this.openDocumentFromBuffer(pdfFile, password).wait((doc) => task.resolve(doc), (err) => task.reject(err.reason));
354
+ }
355
+ else {
356
+ const fetchFullTask = await this.fetchFullAndOpen(file, password);
357
+ fetchFullTask.wait((doc) => task.resolve(doc), (err) => task.reject(err.reason));
358
+ }
359
+ }
360
+ }
361
+ catch (err) {
362
+ this.logger.error(LOG_SOURCE$1, LOG_CATEGORY$1, 'openDocumentUrl error', err);
363
+ task.reject({
364
+ code: PdfErrorCode.Unknown,
365
+ message: String(err),
366
+ });
367
+ }
368
+ })();
369
+ return task;
370
+ }
371
+ /**
372
+ * Check if the server supports range requests:
373
+ * Sends a HEAD request and sees if 'Accept-Ranges: bytes'.
374
+ */
375
+ async checkRangeSupport(url) {
376
+ try {
377
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'checkRangeSupport', url);
378
+ // First try HEAD request
379
+ const headResponse = await fetch(url, { method: 'HEAD' });
380
+ const fileLength = headResponse.headers.get('Content-Length');
381
+ const acceptRanges = headResponse.headers.get('Accept-Ranges');
382
+ // If server explicitly supports ranges, we're done
383
+ if (acceptRanges === 'bytes') {
384
+ return {
385
+ supportsRanges: true,
386
+ fileLength: parseInt(fileLength ?? '0'),
387
+ content: null,
388
+ };
389
+ }
390
+ // Test actual range request support
391
+ const testResponse = await fetch(url, {
392
+ headers: { Range: 'bytes=0-1' },
393
+ });
394
+ // If we get 200 instead of 206, server doesn't support ranges
395
+ // Return the full content since we'll need it anyway
396
+ if (testResponse.status === 200) {
397
+ const content = await testResponse.arrayBuffer();
398
+ return {
399
+ supportsRanges: false,
400
+ fileLength: parseInt(fileLength ?? '0'),
401
+ content: content,
402
+ };
403
+ }
404
+ // 206 Partial Content indicates range support
405
+ return {
406
+ supportsRanges: testResponse.status === 206,
407
+ fileLength: parseInt(fileLength ?? '0'),
408
+ content: null,
409
+ };
410
+ }
411
+ catch (e) {
412
+ this.logger.error(LOG_SOURCE$1, LOG_CATEGORY$1, 'checkRangeSupport failed', e);
413
+ throw new Error('Failed to check range support: ' + e);
414
+ }
415
+ }
416
+ /**
417
+ * Fully fetch the file (using fetch) into an ArrayBuffer,
418
+ * then call openDocumentFromBuffer.
419
+ */
420
+ async fetchFullAndOpen(file, password) {
421
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'fetchFullAndOpen', file.url);
422
+ // 1. fetch entire PDF as array buffer
423
+ const response = await fetch(file.url);
424
+ if (!response.ok) {
425
+ throw new Error(`Could not fetch PDF: ${response.statusText}`);
426
+ }
427
+ const arrayBuf = await response.arrayBuffer();
428
+ // 2. create a PdfFile object
429
+ const pdfFile = {
430
+ id: file.id,
431
+ content: arrayBuf,
432
+ };
433
+ // 3. call openDocumentFromBuffer (the method you already have)
434
+ // that returns a PdfTask, but let's wrap it in a Promise
435
+ return this.openDocumentFromBuffer(pdfFile, password);
436
+ }
437
+ /**
438
+ * Use your synchronous partial-loading approach:
439
+ * - In your snippet, it's done via `openDocumentFromLoader`.
440
+ * - We'll do a synchronous XHR read callback that pulls
441
+ * the desired byte ranges.
442
+ */
443
+ async openDocumentWithRangeRequest(file, password, knownFileLength) {
444
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'openDocumentWithRangeRequest', file.url);
445
+ // We first do a HEAD or a partial fetch to get the fileLength:
446
+ const fileLength = knownFileLength ?? (await this.retrieveFileLength(file.url)).fileLength;
447
+ // 2. define the callback function used by openDocumentFromLoader
448
+ const callback = (offset, length) => {
449
+ // Perform synchronous XHR:
450
+ const xhr = new XMLHttpRequest();
451
+ xhr.open('GET', file.url, false); // note: block in the Worker
452
+ xhr.overrideMimeType('text/plain; charset=x-user-defined');
453
+ xhr.setRequestHeader('Range', `bytes=${offset}-${offset + length - 1}`);
454
+ xhr.send(null);
455
+ if (xhr.status === 206 || xhr.status === 200) {
456
+ return this.convertResponseToUint8Array(xhr.responseText);
457
+ }
458
+ throw new Error(`Range request failed with status ${xhr.status}`);
459
+ };
460
+ // 3. call `openDocumentFromLoader`
461
+ return this.openDocumentFromLoader({
462
+ id: file.id,
463
+ fileLength,
464
+ callback,
465
+ }, password);
466
+ }
467
+ /**
468
+ * Helper to do a HEAD request or partial GET to find file length.
469
+ */
470
+ async retrieveFileLength(url) {
471
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'retrieveFileLength', url);
472
+ // We'll do a HEAD request to get Content-Length
473
+ const resp = await fetch(url, { method: 'HEAD' });
474
+ if (!resp.ok) {
475
+ throw new Error(`Failed HEAD request for file length: ${resp.statusText}`);
476
+ }
477
+ const lenStr = resp.headers.get('Content-Length') || '0';
478
+ const fileLength = parseInt(lenStr, 10) || 0;
479
+ if (!fileLength) {
480
+ throw new Error(`Content-Length not found or zero.`);
481
+ }
482
+ return { fileLength };
483
+ }
484
+ /**
485
+ * Convert response text (x-user-defined) to a Uint8Array
486
+ * for partial data.
487
+ */
488
+ convertResponseToUint8Array(text) {
489
+ const array = new Uint8Array(text.length);
490
+ for (let i = 0; i < text.length; i++) {
491
+ // & 0xff ensures we only get the lower 8 bits
492
+ array[i] = text.charCodeAt(i) & 0xff;
493
+ }
494
+ return array;
495
+ }
496
+ /**
497
+ * {@inheritDoc @embedpdf/models!PdfEngine.openDocument}
498
+ *
499
+ * @public
500
+ */
501
+ openDocumentFromBuffer(file, password = '') {
502
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'openDocumentFromBuffer', file, password);
503
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `OpenDocumentFromBuffer`, 'Begin', file.id);
504
+ const array = new Uint8Array(file.content);
505
+ const length = array.length;
506
+ const filePtr = this.malloc(length);
507
+ this.pdfiumModule.pdfium.HEAPU8.set(array, filePtr);
508
+ const docPtr = this.pdfiumModule.FPDF_LoadMemDocument(filePtr, length, password);
509
+ if (!docPtr) {
510
+ const lastError = this.pdfiumModule.FPDF_GetLastError();
511
+ this.logger.error(LOG_SOURCE$1, LOG_CATEGORY$1, `FPDF_LoadMemDocument failed with ${lastError}`);
512
+ this.free(filePtr);
513
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `OpenDocumentFromBuffer`, 'End', file.id);
514
+ return PdfTaskHelper.reject({
515
+ code: lastError,
516
+ message: `FPDF_LoadMemDocument failed`,
517
+ });
518
+ }
519
+ const pageCount = this.pdfiumModule.FPDF_GetPageCount(docPtr);
520
+ const pages = [];
521
+ const sizePtr = this.malloc(8);
522
+ for (let index = 0; index < pageCount; index++) {
523
+ const result = this.pdfiumModule.FPDF_GetPageSizeByIndexF(docPtr, index, sizePtr);
524
+ if (!result) {
525
+ const lastError = this.pdfiumModule.FPDF_GetLastError();
526
+ this.logger.error(LOG_SOURCE$1, LOG_CATEGORY$1, `FPDF_GetPageSizeByIndexF failed with ${lastError}`);
527
+ this.free(sizePtr);
528
+ this.pdfiumModule.FPDF_CloseDocument(docPtr);
529
+ this.free(filePtr);
530
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `OpenDocumentFromBuffer`, 'End', file.id);
531
+ return PdfTaskHelper.reject({
532
+ code: lastError,
533
+ message: `FPDF_GetPageSizeByIndexF failed`,
534
+ });
535
+ }
536
+ const page = {
537
+ index,
538
+ size: {
539
+ width: this.pdfiumModule.pdfium.getValue(sizePtr, 'float'),
540
+ height: this.pdfiumModule.pdfium.getValue(sizePtr + 4, 'float'),
541
+ },
542
+ };
543
+ pages.push(page);
544
+ }
545
+ this.free(sizePtr);
546
+ const pdfDoc = {
547
+ id: file.id,
548
+ pageCount,
549
+ pages,
550
+ };
551
+ this.cache.setDocument(file.id, filePtr, docPtr);
552
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `OpenDocumentFromBuffer`, 'End', file.id);
553
+ return PdfTaskHelper.resolve(pdfDoc);
554
+ }
555
+ /**
556
+ * {@inheritDoc @embedpdf/models!PdfEngine.openDocumentFromLoader}
557
+ *
558
+ * @public
559
+ */
560
+ openDocumentFromLoader(fileLoader, password = '') {
561
+ const { fileLength, callback, ...file } = fileLoader;
562
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'openDocumentFromLoader', file, password);
563
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `OpenDocumentFromLoader`, 'Begin', file.id);
564
+ const readBlock = (_pThis, // Pointer to the FPDF_FILEACCESS structure
565
+ offset, // Pointer to a buffer to receive the data
566
+ pBuf, // Offset position from the beginning of the file
567
+ length) => {
568
+ try {
569
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'readBlock', offset, length, pBuf);
570
+ if (offset < 0 || offset >= fileLength) {
571
+ this.logger.error(LOG_SOURCE$1, LOG_CATEGORY$1, 'Offset out of bounds:', offset);
572
+ return 0;
573
+ }
574
+ // Get data chunk using the callback
575
+ const data = callback(offset, length);
576
+ // Copy the data to PDFium's buffer
577
+ const dest = new Uint8Array(this.pdfiumModule.pdfium.HEAPU8.buffer, pBuf, data.length);
578
+ dest.set(data);
579
+ return data.length;
580
+ }
581
+ catch (error) {
582
+ this.logger.error(LOG_SOURCE$1, LOG_CATEGORY$1, 'ReadBlock error:', error);
583
+ return 0;
584
+ }
585
+ };
586
+ const callbackPtr = this.pdfiumModule.pdfium.addFunction(readBlock, 'iiiii');
587
+ // Create FPDF_FILEACCESS struct
588
+ const structSize = 12;
589
+ const fileAccessPtr = this.malloc(structSize);
590
+ // Set up struct fields
591
+ this.pdfiumModule.pdfium.setValue(fileAccessPtr, fileLength, 'i32');
592
+ this.pdfiumModule.pdfium.setValue(fileAccessPtr + 4, callbackPtr, 'i32');
593
+ this.pdfiumModule.pdfium.setValue(fileAccessPtr + 8, 0, 'i32');
594
+ // Load document
595
+ const docPtr = this.pdfiumModule.FPDF_LoadCustomDocument(fileAccessPtr, password);
596
+ if (!docPtr) {
597
+ const lastError = this.pdfiumModule.FPDF_GetLastError();
598
+ this.logger.error(LOG_SOURCE$1, LOG_CATEGORY$1, `FPDF_LoadCustomDocument failed with ${lastError}`);
599
+ this.free(fileAccessPtr);
600
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `OpenDocumentFromLoader`, 'End', file.id);
601
+ return PdfTaskHelper.reject({
602
+ code: lastError,
603
+ message: `FPDF_LoadCustomDocument failed`,
604
+ });
605
+ }
606
+ const pageCount = this.pdfiumModule.FPDF_GetPageCount(docPtr);
607
+ const pages = [];
608
+ const sizePtr = this.malloc(8);
609
+ for (let index = 0; index < pageCount; index++) {
610
+ const result = this.pdfiumModule.FPDF_GetPageSizeByIndexF(docPtr, index, sizePtr);
611
+ if (!result) {
612
+ const lastError = this.pdfiumModule.FPDF_GetLastError();
613
+ this.logger.error(LOG_SOURCE$1, LOG_CATEGORY$1, `FPDF_GetPageSizeByIndexF failed with ${lastError}`);
614
+ this.free(sizePtr);
615
+ this.pdfiumModule.FPDF_CloseDocument(docPtr);
616
+ this.free(fileAccessPtr);
617
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `OpenDocumentFromLoader`, 'End', file.id);
618
+ return PdfTaskHelper.reject({
619
+ code: lastError,
620
+ message: `FPDF_GetPageSizeByIndexF failed`,
621
+ });
622
+ }
623
+ const page = {
624
+ index,
625
+ size: {
626
+ width: this.pdfiumModule.pdfium.getValue(sizePtr, 'float'),
627
+ height: this.pdfiumModule.pdfium.getValue(sizePtr + 4, 'float'),
628
+ },
629
+ };
630
+ pages.push(page);
631
+ }
632
+ this.free(sizePtr);
633
+ const pdfDoc = {
634
+ id: file.id,
635
+ pageCount,
636
+ pages,
637
+ };
638
+ this.cache.setDocument(file.id, fileAccessPtr, docPtr);
639
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `OpenDocumentFromLoader`, 'End', file.id);
640
+ return PdfTaskHelper.resolve(pdfDoc);
641
+ }
642
+ /**
643
+ * {@inheritDoc @embedpdf/models!PdfEngine.getMetadata}
644
+ *
645
+ * @public
646
+ */
647
+ getMetadata(doc) {
648
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'getMetadata', doc);
649
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetMetadata`, 'Begin', doc.id);
650
+ const ctx = this.cache.getContext(doc.id);
651
+ if (!ctx) {
652
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetMetadata`, 'End', doc.id);
653
+ return PdfTaskHelper.reject({
654
+ code: PdfErrorCode.DocNotOpen,
655
+ message: 'document does not open',
656
+ });
657
+ }
658
+ const metadata = {
659
+ title: this.readMetaText(ctx.docPtr, 'Title'),
660
+ author: this.readMetaText(ctx.docPtr, 'Author'),
661
+ subject: this.readMetaText(ctx.docPtr, 'Subject'),
662
+ keywords: this.readMetaText(ctx.docPtr, 'Keywords'),
663
+ producer: this.readMetaText(ctx.docPtr, 'Producer'),
664
+ creator: this.readMetaText(ctx.docPtr, 'Creator'),
665
+ creationDate: this.readMetaText(ctx.docPtr, 'CreationDate'),
666
+ modificationDate: this.readMetaText(ctx.docPtr, 'ModDate'),
667
+ };
668
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetMetadata`, 'End', doc.id);
669
+ return PdfTaskHelper.resolve(metadata);
670
+ }
671
+ /**
672
+ * {@inheritDoc @embedpdf/models!PdfEngine.getDocPermissions}
673
+ *
674
+ * @public
675
+ */
676
+ getDocPermissions(doc) {
677
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'getDocPermissions', doc);
678
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `getDocPermissions`, 'Begin', doc.id);
679
+ const ctx = this.cache.getContext(doc.id);
680
+ if (!ctx) {
681
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `getDocPermissions`, 'End', doc.id);
682
+ return PdfTaskHelper.reject({
683
+ code: PdfErrorCode.DocNotOpen,
684
+ message: 'document does not open',
685
+ });
686
+ }
687
+ const permissions = this.pdfiumModule.FPDF_GetDocPermissions(ctx.docPtr);
688
+ return PdfTaskHelper.resolve(permissions);
689
+ }
690
+ /**
691
+ * {@inheritDoc @embedpdf/models!PdfEngine.getDocUserPermissions}
692
+ *
693
+ * @public
694
+ */
695
+ getDocUserPermissions(doc) {
696
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'getDocUserPermissions', doc);
697
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `getDocUserPermissions`, 'Begin', doc.id);
698
+ const ctx = this.cache.getContext(doc.id);
699
+ if (!ctx) {
700
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `getDocUserPermissions`, 'End', doc.id);
701
+ return PdfTaskHelper.reject({
702
+ code: PdfErrorCode.DocNotOpen,
703
+ message: 'document does not open',
704
+ });
705
+ }
706
+ const permissions = this.pdfiumModule.FPDF_GetDocUserPermissions(ctx.docPtr);
707
+ return PdfTaskHelper.resolve(permissions);
708
+ }
709
+ /**
710
+ * {@inheritDoc @embedpdf/models!PdfEngine.getSignatures}
711
+ *
712
+ * @public
713
+ */
714
+ getSignatures(doc) {
715
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'getSignatures', doc);
716
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetSignatures`, 'Begin', doc.id);
717
+ const ctx = this.cache.getContext(doc.id);
718
+ if (!ctx) {
719
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetSignatures`, 'End', doc.id);
720
+ return PdfTaskHelper.reject({
721
+ code: PdfErrorCode.DocNotOpen,
722
+ message: 'document does not open',
723
+ });
724
+ }
725
+ const signatures = [];
726
+ const count = this.pdfiumModule.FPDF_GetSignatureCount(ctx.docPtr);
727
+ for (let i = 0; i < count; i++) {
728
+ const signatureObjPtr = this.pdfiumModule.FPDF_GetSignatureObject(ctx.docPtr, i);
729
+ const contents = readArrayBuffer(this.pdfiumModule.pdfium, (buffer, bufferSize) => {
730
+ return this.pdfiumModule.FPDFSignatureObj_GetContents(signatureObjPtr, buffer, bufferSize);
731
+ });
732
+ const byteRange = readArrayBuffer(this.pdfiumModule.pdfium, (buffer, bufferSize) => {
733
+ return (this.pdfiumModule.FPDFSignatureObj_GetByteRange(signatureObjPtr, buffer, bufferSize) * 4);
734
+ });
735
+ const subFilter = readArrayBuffer(this.pdfiumModule.pdfium, (buffer, bufferSize) => {
736
+ return this.pdfiumModule.FPDFSignatureObj_GetSubFilter(signatureObjPtr, buffer, bufferSize);
737
+ });
738
+ const reason = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
739
+ return this.pdfiumModule.FPDFSignatureObj_GetReason(signatureObjPtr, buffer, bufferLength);
740
+ }, this.pdfiumModule.pdfium.UTF16ToString);
741
+ const time = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
742
+ return this.pdfiumModule.FPDFSignatureObj_GetTime(signatureObjPtr, buffer, bufferLength);
743
+ }, this.pdfiumModule.pdfium.UTF8ToString);
744
+ const docMDP = this.pdfiumModule.FPDFSignatureObj_GetDocMDPPermission(signatureObjPtr);
745
+ signatures.push({
746
+ contents,
747
+ byteRange,
748
+ subFilter,
749
+ reason,
750
+ time,
751
+ docMDP,
752
+ });
753
+ }
754
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetSignatures`, 'End', doc.id);
755
+ return PdfTaskHelper.resolve(signatures);
756
+ }
757
+ /**
758
+ * {@inheritDoc @embedpdf/models!PdfEngine.getBookmarks}
759
+ *
760
+ * @public
761
+ */
762
+ getBookmarks(doc) {
763
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'getBookmarks', doc);
764
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetBookmarks`, 'Begin', doc.id);
765
+ const ctx = this.cache.getContext(doc.id);
766
+ if (!ctx) {
767
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `getBookmarks`, 'End', doc.id);
768
+ return PdfTaskHelper.reject({
769
+ code: PdfErrorCode.DocNotOpen,
770
+ message: 'document does not open',
771
+ });
772
+ }
773
+ const bookmarks = this.readPdfBookmarks(ctx.docPtr, 0);
774
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetBookmarks`, 'End', doc.id);
775
+ return PdfTaskHelper.resolve({
776
+ bookmarks,
777
+ });
778
+ }
779
+ /**
780
+ * {@inheritDoc @embedpdf/models!PdfEngine.renderPage}
781
+ *
782
+ * @public
783
+ */
784
+ renderPage(doc, page, scaleFactor = 1, rotation = Rotation.Degree0, dpr = 1, options = { withAnnotations: false }, imageType = 'image/webp') {
785
+ const task = new Task();
786
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'renderPage', doc, page, scaleFactor, rotation, dpr, options);
787
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `RenderPage`, 'Begin', `${doc.id}-${page.index}`);
788
+ const ctx = this.cache.getContext(doc.id);
789
+ if (!ctx) {
790
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `RenderPage`, 'End', `${doc.id}-${page.index}`);
791
+ return PdfTaskHelper.reject({
792
+ code: PdfErrorCode.DocNotOpen,
793
+ message: 'document does not open',
794
+ });
795
+ }
796
+ const imageData = this.renderPageRectToImageData(ctx, page, {
797
+ origin: { x: 0, y: 0 },
798
+ size: page.size,
799
+ }, scaleFactor, rotation, dpr, options);
800
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `RenderPage`, 'End', `${doc.id}-${page.index}`);
801
+ this.imageDataConverter(imageData, imageType).then((blob) => task.resolve(blob));
802
+ return task;
803
+ }
804
+ /**
805
+ * {@inheritDoc @embedpdf/models!PdfEngine.renderPageRect}
806
+ *
807
+ * @public
808
+ */
809
+ renderPageRect(doc, page, scaleFactor, rotation, dpr, rect, options, imageType = 'image/webp') {
810
+ const task = new Task();
811
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'renderPageRect', doc, page, scaleFactor, rotation, dpr, rect, options);
812
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `RenderPageRect`, 'Begin', `${doc.id}-${page.index}`);
813
+ const ctx = this.cache.getContext(doc.id);
814
+ if (!ctx) {
815
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `RenderPageRect`, 'End', `${doc.id}-${page.index}`);
816
+ return PdfTaskHelper.reject({
817
+ code: PdfErrorCode.DocNotOpen,
818
+ message: 'document does not open',
819
+ });
820
+ }
821
+ const imageData = this.renderPageRectToImageData(ctx, page, rect, scaleFactor, rotation, dpr, options);
822
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `RenderPageRect`, 'End', `${doc.id}-${page.index}`);
823
+ this.imageDataConverter(imageData, imageType).then((blob) => task.resolve(blob));
824
+ return task;
825
+ }
826
+ /**
827
+ * {@inheritDoc @embedpdf/models!PdfEngine.getAllAnnotations}
828
+ *
829
+ * @public
830
+ */
831
+ getAllAnnotations(doc) {
832
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'getAllAnnotations', doc);
833
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetAllAnnotations`, 'Begin', doc.id);
834
+ const ctx = this.cache.getContext(doc.id);
835
+ if (!ctx) {
836
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetAllAnnotations`, 'End', doc.id);
837
+ return PdfTaskHelper.reject({
838
+ code: PdfErrorCode.DocNotOpen,
839
+ message: 'document does not open',
840
+ });
841
+ }
842
+ const annotations = this.readAllAnnotations(doc, ctx);
843
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetAllAnnotations`, 'End', doc.id);
844
+ return PdfTaskHelper.resolve(annotations);
845
+ }
846
+ readAllAnnotations(doc, ctx) {
847
+ const annotationsByPage = {};
848
+ for (let i = 0; i < doc.pageCount; i++) {
849
+ const pageAnnotations = this.readPageAnnotations(ctx, doc.pages[i]);
850
+ annotationsByPage[i] = pageAnnotations;
851
+ }
852
+ return annotationsByPage;
853
+ }
854
+ /**
855
+ * {@inheritDoc @embedpdf/models!PdfEngine.getPageAnnotations}
856
+ *
857
+ * @public
858
+ */
859
+ getPageAnnotations(doc, page) {
860
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'getPageAnnotations', doc, page);
861
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetPageAnnotations`, 'Begin', `${doc.id}-${page.index}`);
862
+ const ctx = this.cache.getContext(doc.id);
863
+ if (!ctx) {
864
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetPageAnnotations`, 'End', `${doc.id}-${page.index}`);
865
+ return PdfTaskHelper.reject({
866
+ code: PdfErrorCode.DocNotOpen,
867
+ message: 'document does not open',
868
+ });
869
+ }
870
+ const annotations = this.readPageAnnotations(ctx, page);
871
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetPageAnnotations`, 'End', `${doc.id}-${page.index}`);
872
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, `GetPageAnnotations`, `${doc.id}-${page.index}`, annotations);
873
+ return PdfTaskHelper.resolve(annotations);
874
+ }
875
+ /**
876
+ * {@inheritDoc @embedpdf/models!PdfEngine.createPageAnnotation}
877
+ *
878
+ * @public
879
+ */
880
+ createPageAnnotation(doc, page, annotation) {
881
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'createPageAnnotation', doc, page, annotation);
882
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `CreatePageAnnotation`, 'Begin', `${doc.id}-${page.index}`);
883
+ const ctx = this.cache.getContext(doc.id);
884
+ if (!ctx) {
885
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `CreatePageAnnotation`, 'End', `${doc.id}-${page.index}`);
886
+ return PdfTaskHelper.reject({
887
+ code: PdfErrorCode.DocNotOpen,
888
+ message: 'document does not open',
889
+ });
890
+ }
891
+ const pageCtx = ctx.acquirePage(page.index);
892
+ const annotationPtr = this.pdfiumModule.FPDFPage_CreateAnnot(pageCtx.pagePtr, annotation.type);
893
+ if (!annotationPtr) {
894
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `CreatePageAnnotation`, 'End', `${doc.id}-${page.index}`);
895
+ pageCtx.release();
896
+ return PdfTaskHelper.reject({
897
+ code: PdfErrorCode.CantCreateAnnot,
898
+ message: 'can not create annotation with specified type',
899
+ });
900
+ }
901
+ if (!this.setPageAnnoRect(page, pageCtx.pagePtr, annotationPtr, annotation.rect)) {
902
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
903
+ pageCtx.release();
904
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `CreatePageAnnotation`, 'End', `${doc.id}-${page.index}`);
905
+ return PdfTaskHelper.reject({
906
+ code: PdfErrorCode.CantSetAnnotRect,
907
+ message: 'can not set the rect of the annotation',
908
+ });
909
+ }
910
+ let isSucceed = false;
911
+ switch (annotation.type) {
912
+ case PdfAnnotationSubtype.INK:
913
+ isSucceed = this.addInkStroke(page, pageCtx.pagePtr, annotationPtr, annotation.inkList);
914
+ break;
915
+ case PdfAnnotationSubtype.STAMP:
916
+ isSucceed = this.addStampContent(ctx.docPtr, page, pageCtx.pagePtr, annotationPtr, annotation.rect, annotation.contents);
917
+ break;
918
+ }
919
+ if (!isSucceed) {
920
+ this.pdfiumModule.FPDFPage_RemoveAnnot(pageCtx.pagePtr, annotationPtr);
921
+ pageCtx.release();
922
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `CreatePageAnnotation`, 'End', `${doc.id}-${page.index}`);
923
+ return PdfTaskHelper.reject({
924
+ code: PdfErrorCode.CantSetAnnotContent,
925
+ message: 'can not add content of the annotation',
926
+ });
927
+ }
928
+ this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
929
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
930
+ pageCtx.release();
931
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `CreatePageAnnotation`, 'End', `${doc.id}-${page.index}`);
932
+ return PdfTaskHelper.resolve(true);
933
+ }
934
+ /**
935
+ * {@inheritDoc @embedpdf/models!PdfEngine.transformPageAnnotation}
936
+ *
937
+ * @public
938
+ */
939
+ transformPageAnnotation(doc, page, annotation, transformation) {
940
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'transformPageAnnotation', doc, page, annotation, transformation);
941
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'Begin', `${doc.id}-${page.index}`);
942
+ const ctx = this.cache.getContext(doc.id);
943
+ if (!ctx) {
944
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'End', `${doc.id}-${page.index}`);
945
+ return PdfTaskHelper.reject({
946
+ code: PdfErrorCode.DocNotOpen,
947
+ message: 'document does not open',
948
+ });
949
+ }
950
+ const pageCtx = ctx.acquirePage(page.index);
951
+ const annotationPtr = this.pdfiumModule.FPDFPage_GetAnnot(pageCtx.pagePtr, annotation.id);
952
+ const rect = {
953
+ origin: {
954
+ x: annotation.rect.origin.x + transformation.offset.x,
955
+ y: annotation.rect.origin.y + transformation.offset.y,
956
+ },
957
+ size: {
958
+ width: annotation.rect.size.width * transformation.scale.width,
959
+ height: annotation.rect.size.height * transformation.scale.height,
960
+ },
961
+ };
962
+ if (!this.setPageAnnoRect(page, pageCtx.pagePtr, annotationPtr, rect)) {
963
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
964
+ pageCtx.release();
965
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'End', `${doc.id}-${page.index}`);
966
+ return PdfTaskHelper.reject({
967
+ code: PdfErrorCode.CantSetAnnotRect,
968
+ message: 'can not set the rect of the annotation',
969
+ });
970
+ }
971
+ switch (annotation.type) {
972
+ case PdfAnnotationSubtype.INK:
973
+ {
974
+ if (!this.pdfiumModule.FPDFAnnot_RemoveInkList(annotationPtr)) {
975
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
976
+ pageCtx.release();
977
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'End', `${doc.id}-${page.index}`);
978
+ return PdfTaskHelper.reject({
979
+ code: PdfErrorCode.CantRemoveInkList,
980
+ message: 'can not set the rect of the annotation',
981
+ });
982
+ }
983
+ const inkList = annotation.inkList.map((inkStroke) => {
984
+ return {
985
+ points: inkStroke.points.map((point) => {
986
+ return {
987
+ x: rect.origin.x +
988
+ (point.x - annotation.rect.origin.x) * transformation.scale.width,
989
+ y: rect.origin.y +
990
+ (point.y - annotation.rect.origin.y) * transformation.scale.height,
991
+ };
992
+ }),
993
+ };
994
+ });
995
+ if (!this.addInkStroke(page, pageCtx.pagePtr, annotationPtr, inkList)) {
996
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
997
+ pageCtx.release();
998
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'End', `${doc.id}-${page.index}`);
999
+ return PdfTaskHelper.reject({
1000
+ code: PdfErrorCode.CantAddInkStoke,
1001
+ message: 'can not add stroke to the ink list of annotation',
1002
+ });
1003
+ }
1004
+ }
1005
+ break;
1006
+ }
1007
+ this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
1008
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
1009
+ pageCtx.release();
1010
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'End', `${doc.id}-${page.index}`);
1011
+ return PdfTaskHelper.resolve(true);
1012
+ }
1013
+ /**
1014
+ * {@inheritDoc @embedpdf/models!PdfEngine.removePageAnnotation}
1015
+ *
1016
+ * @public
1017
+ */
1018
+ removePageAnnotation(doc, page, annotation) {
1019
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'removePageAnnotation', doc, page, annotation);
1020
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `RemovePageAnnotation`, 'Begin', `${doc.id}-${page.index}`);
1021
+ const ctx = this.cache.getContext(doc.id);
1022
+ if (!ctx) {
1023
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `RemovePageAnnotation`, 'End', `${doc.id}-${page.index}`);
1024
+ return PdfTaskHelper.reject({
1025
+ code: PdfErrorCode.DocNotOpen,
1026
+ message: 'document does not open',
1027
+ });
1028
+ }
1029
+ const pageCtx = ctx.acquirePage(page.index);
1030
+ let result = false;
1031
+ result = this.pdfiumModule.FPDFPage_RemoveAnnot(pageCtx.pagePtr, annotation.id);
1032
+ if (!result) {
1033
+ this.logger.error(LOG_SOURCE$1, LOG_CATEGORY$1, `FPDFPage_RemoveAnnot Failed`, `${doc.id}-${page.index}`);
1034
+ }
1035
+ else {
1036
+ result = this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
1037
+ if (!result) {
1038
+ this.logger.error(LOG_SOURCE$1, LOG_CATEGORY$1, `FPDFPage_GenerateContent Failed`, `${doc.id}-${page.index}`);
1039
+ }
1040
+ }
1041
+ pageCtx.release();
1042
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `RemovePageAnnotation`, 'End', `${doc.id}-${page.index}`);
1043
+ return PdfTaskHelper.resolve(result);
1044
+ }
1045
+ /**
1046
+ * {@inheritDoc @embedpdf/models!PdfEngine.getPageTextRects}
1047
+ *
1048
+ * @public
1049
+ */
1050
+ getPageTextRects(doc, page, scaleFactor, rotation) {
1051
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'getPageTextRects', doc, page, scaleFactor, rotation);
1052
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetPageTextRects`, 'Begin', `${doc.id}-${page.index}`);
1053
+ const ctx = this.cache.getContext(doc.id);
1054
+ if (!ctx) {
1055
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetPageTextRects`, 'End', `${doc.id}-${page.index}`);
1056
+ return PdfTaskHelper.reject({
1057
+ code: PdfErrorCode.DocNotOpen,
1058
+ message: 'document does not open',
1059
+ });
1060
+ }
1061
+ const pageCtx = ctx.acquirePage(page.index);
1062
+ const textPagePtr = this.pdfiumModule.FPDFText_LoadPage(pageCtx.pagePtr);
1063
+ const textRects = this.readPageTextRects(page, pageCtx.docPtr, pageCtx.pagePtr, textPagePtr);
1064
+ this.pdfiumModule.FPDFText_ClosePage(textPagePtr);
1065
+ pageCtx.release();
1066
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetPageTextRects`, 'End', `${doc.id}-${page.index}`);
1067
+ return PdfTaskHelper.resolve(textRects);
1068
+ }
1069
+ /**
1070
+ * {@inheritDoc @embedpdf/models!PdfEngine.renderThumbnail}
1071
+ *
1072
+ * @public
1073
+ */
1074
+ renderThumbnail(doc, page, scaleFactor, rotation, dpr) {
1075
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'renderThumbnail', doc, page, scaleFactor, rotation, dpr);
1076
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `RenderThumbnail`, 'Begin', `${doc.id}-${page.index}`);
1077
+ const ctx = this.cache.getContext(doc.id);
1078
+ if (!ctx) {
1079
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `RenderThumbnail`, 'End', `${doc.id}-${page.index}`);
1080
+ return PdfTaskHelper.reject({
1081
+ code: PdfErrorCode.DocNotOpen,
1082
+ message: 'document does not open',
1083
+ });
1084
+ }
1085
+ scaleFactor = Math.max(scaleFactor, 0.5);
1086
+ const result = this.renderPage(doc, page, scaleFactor, rotation, dpr, {
1087
+ withAnnotations: true,
1088
+ });
1089
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `RenderThumbnail`, 'End', `${doc.id}-${page.index}`);
1090
+ return result;
1091
+ }
1092
+ /**
1093
+ * {@inheritDoc @embedpdf/models!PdfEngine.getAttachments}
1094
+ *
1095
+ * @public
1096
+ */
1097
+ getAttachments(doc) {
1098
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'getAttachments', doc);
1099
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetAttachments`, 'Begin', doc.id);
1100
+ const ctx = this.cache.getContext(doc.id);
1101
+ if (!ctx) {
1102
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetAttachments`, 'End', doc.id);
1103
+ return PdfTaskHelper.reject({
1104
+ code: PdfErrorCode.DocNotOpen,
1105
+ message: 'document does not open',
1106
+ });
1107
+ }
1108
+ const attachments = [];
1109
+ const count = this.pdfiumModule.FPDFDoc_GetAttachmentCount(ctx.docPtr);
1110
+ for (let i = 0; i < count; i++) {
1111
+ const attachment = this.readPdfAttachment(ctx.docPtr, i);
1112
+ attachments.push(attachment);
1113
+ }
1114
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `GetAttachments`, 'End', doc.id);
1115
+ return PdfTaskHelper.resolve(attachments);
1116
+ }
1117
+ /**
1118
+ * {@inheritDoc @embedpdf/models!PdfEngine.readAttachmentContent}
1119
+ *
1120
+ * @public
1121
+ */
1122
+ readAttachmentContent(doc, attachment) {
1123
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'readAttachmentContent', doc, attachment);
1124
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `ReadAttachmentContent`, 'Begin', doc.id);
1125
+ const ctx = this.cache.getContext(doc.id);
1126
+ if (!ctx) {
1127
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `ReadAttachmentContent`, 'End', doc.id);
1128
+ return PdfTaskHelper.reject({
1129
+ code: PdfErrorCode.DocNotOpen,
1130
+ message: 'document does not open',
1131
+ });
1132
+ }
1133
+ const attachmentPtr = this.pdfiumModule.FPDFDoc_GetAttachment(ctx.docPtr, attachment.index);
1134
+ const sizePtr = this.malloc(8);
1135
+ if (!this.pdfiumModule.FPDFAttachment_GetFile(attachmentPtr, 0, 0, sizePtr)) {
1136
+ this.free(sizePtr);
1137
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `ReadAttachmentContent`, 'End', doc.id);
1138
+ return PdfTaskHelper.reject({
1139
+ code: PdfErrorCode.CantReadAttachmentSize,
1140
+ message: 'can not read attachment size',
1141
+ });
1142
+ }
1143
+ const size = this.pdfiumModule.pdfium.getValue(sizePtr, 'i64');
1144
+ const contentPtr = this.malloc(size);
1145
+ if (!this.pdfiumModule.FPDFAttachment_GetFile(attachmentPtr, contentPtr, size, sizePtr)) {
1146
+ this.free(sizePtr);
1147
+ this.free(contentPtr);
1148
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `ReadAttachmentContent`, 'End', doc.id);
1149
+ return PdfTaskHelper.reject({
1150
+ code: PdfErrorCode.CantReadAttachmentContent,
1151
+ message: 'can not read attachment content',
1152
+ });
1153
+ }
1154
+ const buffer = new ArrayBuffer(size);
1155
+ const view = new DataView(buffer);
1156
+ for (let i = 0; i < size; i++) {
1157
+ view.setInt8(i, this.pdfiumModule.pdfium.getValue(contentPtr + i, 'i8'));
1158
+ }
1159
+ this.free(sizePtr);
1160
+ this.free(contentPtr);
1161
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `ReadAttachmentContent`, 'End', doc.id);
1162
+ return PdfTaskHelper.resolve(buffer);
1163
+ }
1164
+ /**
1165
+ * {@inheritDoc @embedpdf/models!PdfEngine.setFormFieldValue}
1166
+ *
1167
+ * @public
1168
+ */
1169
+ setFormFieldValue(doc, page, annotation, value) {
1170
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'SetFormFieldValue', doc, annotation, value);
1171
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `SetFormFieldValue`, 'Begin', `${doc.id}-${annotation.id}`);
1172
+ const ctx = this.cache.getContext(doc.id);
1173
+ if (!ctx) {
1174
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'SetFormFieldValue', 'document is not opened');
1175
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `SetFormFieldValue`, 'End', `${doc.id}-${annotation.id}`);
1176
+ return PdfTaskHelper.reject({
1177
+ code: PdfErrorCode.DocNotOpen,
1178
+ message: 'document does not open',
1179
+ });
1180
+ }
1181
+ const formFillInfoPtr = this.pdfiumModule.PDFiumExt_OpenFormFillInfo();
1182
+ const formHandle = this.pdfiumModule.PDFiumExt_InitFormFillEnvironment(ctx.docPtr, formFillInfoPtr);
1183
+ const pageCtx = ctx.acquirePage(page.index);
1184
+ this.pdfiumModule.FORM_OnAfterLoadPage(pageCtx.pagePtr, formHandle);
1185
+ const annotationPtr = this.pdfiumModule.FPDFPage_GetAnnot(pageCtx.pagePtr, annotation.id);
1186
+ if (!this.pdfiumModule.FORM_SetFocusedAnnot(formHandle, annotationPtr)) {
1187
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'SetFormFieldValue', 'failed to set focused annotation');
1188
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `SetFormFieldValue`, 'End', `${doc.id}-${annotation.id}`);
1189
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
1190
+ this.pdfiumModule.FORM_OnBeforeClosePage(pageCtx.pagePtr, formHandle);
1191
+ pageCtx.release();
1192
+ this.pdfiumModule.PDFiumExt_ExitFormFillEnvironment(formHandle);
1193
+ this.pdfiumModule.PDFiumExt_CloseFormFillInfo(formFillInfoPtr);
1194
+ return PdfTaskHelper.reject({
1195
+ code: PdfErrorCode.CantFocusAnnot,
1196
+ message: 'failed to set focused annotation',
1197
+ });
1198
+ }
1199
+ switch (value.kind) {
1200
+ case 'text':
1201
+ {
1202
+ if (!this.pdfiumModule.FORM_SelectAllText(formHandle, pageCtx.pagePtr)) {
1203
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'SetFormFieldValue', 'failed to select all text');
1204
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `SetFormFieldValue`, 'End', `${doc.id}-${annotation.id}`);
1205
+ this.pdfiumModule.FORM_ForceToKillFocus(formHandle);
1206
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
1207
+ this.pdfiumModule.FORM_OnBeforeClosePage(pageCtx.pagePtr, formHandle);
1208
+ pageCtx.release();
1209
+ this.pdfiumModule.PDFiumExt_ExitFormFillEnvironment(formHandle);
1210
+ this.pdfiumModule.PDFiumExt_CloseFormFillInfo(formFillInfoPtr);
1211
+ return PdfTaskHelper.reject({
1212
+ code: PdfErrorCode.CantSelectText,
1213
+ message: 'failed to select all text',
1214
+ });
1215
+ }
1216
+ const length = 2 * (value.text.length + 1);
1217
+ const textPtr = this.malloc(length);
1218
+ this.pdfiumModule.pdfium.stringToUTF16(value.text, textPtr, length);
1219
+ this.pdfiumModule.FORM_ReplaceSelection(formHandle, pageCtx.pagePtr, textPtr);
1220
+ this.free(textPtr);
1221
+ }
1222
+ break;
1223
+ case 'selection':
1224
+ {
1225
+ if (!this.pdfiumModule.FORM_SetIndexSelected(formHandle, pageCtx.pagePtr, value.index, value.isSelected)) {
1226
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'SetFormFieldValue', 'failed to set index selected');
1227
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `SetFormFieldValue`, 'End', `${doc.id}-${annotation.id}`);
1228
+ this.pdfiumModule.FORM_ForceToKillFocus(formHandle);
1229
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
1230
+ this.pdfiumModule.FORM_OnBeforeClosePage(pageCtx.pagePtr, formHandle);
1231
+ pageCtx.release();
1232
+ this.pdfiumModule.PDFiumExt_ExitFormFillEnvironment(formHandle);
1233
+ this.pdfiumModule.PDFiumExt_CloseFormFillInfo(formFillInfoPtr);
1234
+ return PdfTaskHelper.reject({
1235
+ code: PdfErrorCode.CantSelectOption,
1236
+ message: 'failed to set index selected',
1237
+ });
1238
+ }
1239
+ }
1240
+ break;
1241
+ case 'checked':
1242
+ {
1243
+ const kReturn = 0x0d;
1244
+ if (!this.pdfiumModule.FORM_OnChar(formHandle, pageCtx.pagePtr, kReturn, 0)) {
1245
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'SetFormFieldValue', 'failed to set field checked');
1246
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `SetFormFieldValue`, 'End', `${doc.id}-${annotation.id}`);
1247
+ this.pdfiumModule.FORM_ForceToKillFocus(formHandle);
1248
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
1249
+ this.pdfiumModule.FORM_OnBeforeClosePage(pageCtx.pagePtr, formHandle);
1250
+ pageCtx.release();
1251
+ this.pdfiumModule.PDFiumExt_ExitFormFillEnvironment(formHandle);
1252
+ this.pdfiumModule.PDFiumExt_CloseFormFillInfo(formFillInfoPtr);
1253
+ return PdfTaskHelper.reject({
1254
+ code: PdfErrorCode.CantCheckField,
1255
+ message: 'failed to set field checked',
1256
+ });
1257
+ }
1258
+ }
1259
+ break;
1260
+ }
1261
+ this.pdfiumModule.FORM_ForceToKillFocus(formHandle);
1262
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
1263
+ this.pdfiumModule.FORM_OnBeforeClosePage(pageCtx.pagePtr, formHandle);
1264
+ pageCtx.release();
1265
+ this.pdfiumModule.PDFiumExt_ExitFormFillEnvironment(formHandle);
1266
+ this.pdfiumModule.PDFiumExt_CloseFormFillInfo(formFillInfoPtr);
1267
+ return PdfTaskHelper.resolve(true);
1268
+ }
1269
+ /**
1270
+ * {@inheritDoc @embedpdf/models!PdfEngine.flattenPage}
1271
+ *
1272
+ * @public
1273
+ */
1274
+ flattenPage(doc, page, flag) {
1275
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'flattenPage', doc, page, flag);
1276
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `flattenPage`, 'Begin', doc.id);
1277
+ const ctx = this.cache.getContext(doc.id);
1278
+ if (!ctx) {
1279
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `flattenPage`, 'End', doc.id);
1280
+ return PdfTaskHelper.reject({
1281
+ code: PdfErrorCode.DocNotOpen,
1282
+ message: 'document does not open',
1283
+ });
1284
+ }
1285
+ const pageCtx = ctx.acquirePage(page.index);
1286
+ const result = this.pdfiumModule.FPDFPage_Flatten(pageCtx.pagePtr, flag);
1287
+ pageCtx.release();
1288
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `flattenPage`, 'End', doc.id);
1289
+ return PdfTaskHelper.resolve(result);
1290
+ }
1291
+ /**
1292
+ * {@inheritDoc @embedpdf/models!PdfEngine.extractPages}
1293
+ *
1294
+ * @public
1295
+ */
1296
+ extractPages(doc, pageIndexes) {
1297
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'extractPages', doc, pageIndexes);
1298
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `ExtractPages`, 'Begin', doc.id);
1299
+ const ctx = this.cache.getContext(doc.id);
1300
+ if (!ctx) {
1301
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `ExtractPages`, 'End', doc.id);
1302
+ return PdfTaskHelper.reject({
1303
+ code: PdfErrorCode.DocNotOpen,
1304
+ message: 'document does not open',
1305
+ });
1306
+ }
1307
+ const newDocPtr = this.pdfiumModule.FPDF_CreateNewDocument();
1308
+ if (!newDocPtr) {
1309
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `ExtractPages`, 'End', doc.id);
1310
+ return PdfTaskHelper.reject({
1311
+ code: PdfErrorCode.CantCreateNewDoc,
1312
+ message: 'can not create new document',
1313
+ });
1314
+ }
1315
+ const pageIndexesPtr = this.malloc(pageIndexes.length * 4);
1316
+ for (let i = 0; i < pageIndexes.length; i++) {
1317
+ this.pdfiumModule.pdfium.setValue(pageIndexesPtr + i * 4, pageIndexes[i], 'i32');
1318
+ }
1319
+ if (!this.pdfiumModule.FPDF_ImportPagesByIndex(newDocPtr, ctx.docPtr, pageIndexesPtr, pageIndexes.length, 0)) {
1320
+ this.pdfiumModule.FPDF_CloseDocument(newDocPtr);
1321
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `ExtractPages`, 'End', doc.id);
1322
+ return PdfTaskHelper.reject({
1323
+ code: PdfErrorCode.CantImportPages,
1324
+ message: 'can not import pages to new document',
1325
+ });
1326
+ }
1327
+ const buffer = this.saveDocument(newDocPtr);
1328
+ this.pdfiumModule.FPDF_CloseDocument(newDocPtr);
1329
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `ExtractPages`, 'End', doc.id);
1330
+ return PdfTaskHelper.resolve(buffer);
1331
+ }
1332
+ /**
1333
+ * {@inheritDoc @embedpdf/models!PdfEngine.extractText}
1334
+ *
1335
+ * @public
1336
+ */
1337
+ extractText(doc, pageIndexes) {
1338
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'extractText', doc, pageIndexes);
1339
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `ExtractText`, 'Begin', doc.id);
1340
+ const ctx = this.cache.getContext(doc.id);
1341
+ if (!ctx) {
1342
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `ExtractText`, 'End', doc.id);
1343
+ return PdfTaskHelper.reject({
1344
+ code: PdfErrorCode.DocNotOpen,
1345
+ message: 'document does not open',
1346
+ });
1347
+ }
1348
+ const strings = [];
1349
+ for (let i = 0; i < pageIndexes.length; i++) {
1350
+ const pageCtx = ctx.acquirePage(pageIndexes[i]);
1351
+ const textPagePtr = this.pdfiumModule.FPDFText_LoadPage(pageCtx.pagePtr);
1352
+ const charCount = this.pdfiumModule.FPDFText_CountChars(textPagePtr);
1353
+ const bufferPtr = this.malloc((charCount + 1) * 2);
1354
+ this.pdfiumModule.FPDFText_GetText(textPagePtr, 0, charCount, bufferPtr);
1355
+ const text = this.pdfiumModule.pdfium.UTF16ToString(bufferPtr);
1356
+ this.free(bufferPtr);
1357
+ strings.push(text);
1358
+ this.pdfiumModule.FPDFText_ClosePage(textPagePtr);
1359
+ pageCtx.release();
1360
+ }
1361
+ const text = strings.join('\n\n');
1362
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `ExtractText`, 'End', doc.id);
1363
+ return PdfTaskHelper.resolve(text);
1364
+ }
1365
+ /**
1366
+ * {@inheritDoc @embedpdf/models!PdfEngine.merge}
1367
+ *
1368
+ * @public
1369
+ */
1370
+ merge(files) {
1371
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'merge', files);
1372
+ const fileIds = files.map((file) => file.id).join('.');
1373
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `Merge`, 'Begin', fileIds);
1374
+ const newDocPtr = this.pdfiumModule.FPDF_CreateNewDocument();
1375
+ if (!newDocPtr) {
1376
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `Merge`, 'End', fileIds);
1377
+ return PdfTaskHelper.reject({
1378
+ code: PdfErrorCode.CantCreateNewDoc,
1379
+ message: 'can not create new document',
1380
+ });
1381
+ }
1382
+ const ptrs = [];
1383
+ for (const file of files.reverse()) {
1384
+ const array = new Uint8Array(file.content);
1385
+ const length = array.length;
1386
+ const filePtr = this.malloc(length);
1387
+ this.pdfiumModule.pdfium.HEAPU8.set(array, filePtr);
1388
+ const docPtr = this.pdfiumModule.FPDF_LoadMemDocument(filePtr, length, '');
1389
+ if (!docPtr) {
1390
+ const lastError = this.pdfiumModule.FPDF_GetLastError();
1391
+ this.logger.error(LOG_SOURCE$1, LOG_CATEGORY$1, `FPDF_LoadMemDocument failed with ${lastError}`);
1392
+ this.free(filePtr);
1393
+ for (const ptr of ptrs) {
1394
+ this.pdfiumModule.FPDF_CloseDocument(ptr.docPtr);
1395
+ this.free(ptr.filePtr);
1396
+ }
1397
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `Merge`, 'End', fileIds);
1398
+ return PdfTaskHelper.reject({
1399
+ code: lastError,
1400
+ message: `FPDF_LoadMemDocument failed`,
1401
+ });
1402
+ }
1403
+ ptrs.push({ filePtr, docPtr });
1404
+ if (!this.pdfiumModule.FPDF_ImportPages(newDocPtr, docPtr, '', 0)) {
1405
+ this.pdfiumModule.FPDF_CloseDocument(newDocPtr);
1406
+ for (const ptr of ptrs) {
1407
+ this.pdfiumModule.FPDF_CloseDocument(ptr.docPtr);
1408
+ this.free(ptr.filePtr);
1409
+ }
1410
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `Merge`, 'End', fileIds);
1411
+ return PdfTaskHelper.reject({
1412
+ code: PdfErrorCode.CantImportPages,
1413
+ message: 'can not import pages to new document',
1414
+ });
1415
+ }
1416
+ }
1417
+ const buffer = this.saveDocument(newDocPtr);
1418
+ this.pdfiumModule.FPDF_CloseDocument(newDocPtr);
1419
+ for (const ptr of ptrs) {
1420
+ this.pdfiumModule.FPDF_CloseDocument(ptr.docPtr);
1421
+ this.free(ptr.filePtr);
1422
+ }
1423
+ const file = {
1424
+ id: `${Math.random()}`,
1425
+ content: buffer,
1426
+ };
1427
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `Merge`, 'End', fileIds);
1428
+ return PdfTaskHelper.resolve(file);
1429
+ }
1430
+ /**
1431
+ * Merges specific pages from multiple PDF documents in a custom order
1432
+ *
1433
+ * @param mergeConfigs Array of configurations specifying which pages to merge from which documents
1434
+ * @returns A PdfTask that resolves with the merged PDF file
1435
+ * @public
1436
+ */
1437
+ mergePages(mergeConfigs) {
1438
+ const configIds = mergeConfigs
1439
+ .map((config) => `${config.docId}:${config.pageIndices.join(',')}`)
1440
+ .join('|');
1441
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'mergePages', mergeConfigs);
1442
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `MergePages`, 'Begin', configIds);
1443
+ // Create a new document to import pages into
1444
+ const newDocPtr = this.pdfiumModule.FPDF_CreateNewDocument();
1445
+ if (!newDocPtr) {
1446
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `MergePages`, 'End', configIds);
1447
+ return PdfTaskHelper.reject({
1448
+ code: PdfErrorCode.CantCreateNewDoc,
1449
+ message: 'Cannot create new document',
1450
+ });
1451
+ }
1452
+ try {
1453
+ // Process each merge configuration in reverse order (since we're inserting at position 0)
1454
+ // This ensures the final document has pages in the order specified by the user
1455
+ for (const config of [...mergeConfigs].reverse()) {
1456
+ // Check if the document is open
1457
+ const ctx = this.cache.getContext(config.docId);
1458
+ if (!ctx) {
1459
+ this.logger.warn(LOG_SOURCE$1, LOG_CATEGORY$1, `Document ${config.docId} is not open, skipping`);
1460
+ continue;
1461
+ }
1462
+ // Get the page count for this document
1463
+ const pageCount = this.pdfiumModule.FPDF_GetPageCount(ctx.docPtr);
1464
+ // Filter out invalid page indices
1465
+ const validPageIndices = config.pageIndices.filter((index) => index >= 0 && index < pageCount);
1466
+ if (validPageIndices.length === 0) {
1467
+ continue; // No valid pages to import
1468
+ }
1469
+ // Convert 0-based indices to 1-based for PDFium and join with commas
1470
+ const pageString = validPageIndices.map((index) => index + 1).join(',');
1471
+ try {
1472
+ // Import all specified pages at once from this document
1473
+ if (!this.pdfiumModule.FPDF_ImportPages(newDocPtr, ctx.docPtr, pageString, 0)) {
1474
+ throw new Error(`Failed to import pages ${pageString} from document ${config.docId}`);
1475
+ }
1476
+ }
1477
+ finally {
1478
+ }
1479
+ }
1480
+ // Save the new document to buffer
1481
+ const buffer = this.saveDocument(newDocPtr);
1482
+ const file = {
1483
+ id: `${Math.random()}`,
1484
+ content: buffer,
1485
+ };
1486
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `MergePages`, 'End', configIds);
1487
+ return PdfTaskHelper.resolve(file);
1488
+ }
1489
+ catch (error) {
1490
+ this.logger.error(LOG_SOURCE$1, LOG_CATEGORY$1, 'mergePages failed', error);
1491
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `MergePages`, 'End', configIds);
1492
+ return PdfTaskHelper.reject({
1493
+ code: PdfErrorCode.CantImportPages,
1494
+ message: error instanceof Error ? error.message : 'Failed to merge pages',
1495
+ });
1496
+ }
1497
+ finally {
1498
+ // Clean up the new document
1499
+ if (newDocPtr) {
1500
+ this.pdfiumModule.FPDF_CloseDocument(newDocPtr);
1501
+ }
1502
+ }
1503
+ }
1504
+ /**
1505
+ * {@inheritDoc @embedpdf/models!PdfEngine.saveAsCopy}
1506
+ *
1507
+ * @public
1508
+ */
1509
+ saveAsCopy(doc) {
1510
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'saveAsCopy', doc);
1511
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `SaveAsCopy`, 'Begin', doc.id);
1512
+ const ctx = this.cache.getContext(doc.id);
1513
+ if (!ctx) {
1514
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `SaveAsCopy`, 'End', doc.id);
1515
+ return PdfTaskHelper.reject({
1516
+ code: PdfErrorCode.DocNotOpen,
1517
+ message: 'document does not open',
1518
+ });
1519
+ }
1520
+ const buffer = this.saveDocument(ctx.docPtr);
1521
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `SaveAsCopy`, 'End', doc.id);
1522
+ return PdfTaskHelper.resolve(buffer);
1523
+ }
1524
+ /**
1525
+ * {@inheritDoc @embedpdf/models!PdfEngine.closeDocument}
1526
+ *
1527
+ * @public
1528
+ */
1529
+ closeDocument(doc) {
1530
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'closeDocument', doc);
1531
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `CloseDocument`, 'Begin', doc.id);
1532
+ const ctx = this.cache.getContext(doc.id);
1533
+ if (!ctx) {
1534
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `CloseDocument`, 'End', doc.id);
1535
+ return PdfTaskHelper.reject({
1536
+ code: PdfErrorCode.DocNotOpen,
1537
+ message: 'document does not open',
1538
+ });
1539
+ }
1540
+ ctx.dispose();
1541
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `CloseDocument`, 'End', doc.id);
1542
+ return PdfTaskHelper.resolve(true);
1543
+ }
1544
+ /**
1545
+ * Memory allocation
1546
+ * @param size - size of memory space
1547
+ * @returns pointer to memory space
1548
+ *
1549
+ * @public
1550
+ */
1551
+ malloc(size) {
1552
+ const ptr = this.pdfiumModule.pdfium.wasmExports.malloc(size);
1553
+ for (let i = 0; i < size; i++) {
1554
+ this.pdfiumModule.pdfium.HEAP8[ptr + i] = 0;
1555
+ }
1556
+ return ptr;
1557
+ }
1558
+ /**
1559
+ * Free memory space
1560
+ * @param ptr pointer to memory space
1561
+ *
1562
+ * @public
1563
+ */
1564
+ free(ptr) {
1565
+ this.pdfiumModule.pdfium.wasmExports.free(ptr);
1566
+ }
1567
+ /**
1568
+ * Set the rect of specified annotation
1569
+ * @param page - page info that the annotation is belonged to
1570
+ * @param pagePtr - pointer of page object
1571
+ * @param annotationPtr - pointer to annotation object
1572
+ * @param inkList - ink lists that added to the annotation
1573
+ * @returns whether the ink lists is setted
1574
+ *
1575
+ * @private
1576
+ */
1577
+ addInkStroke(page, pagePtr, annotationPtr, inkList) {
1578
+ for (const inkStroke of inkList) {
1579
+ const inkPointsCount = inkStroke.points.length;
1580
+ const inkPointsPtr = this.malloc(inkPointsCount * 8);
1581
+ for (let i = 0; i < inkPointsCount; i++) {
1582
+ const point = inkStroke.points[i];
1583
+ const { x, y } = this.convertDevicePointToPagePoint(page, point);
1584
+ this.pdfiumModule.pdfium.setValue(inkPointsPtr + i * 8, x, 'float');
1585
+ this.pdfiumModule.pdfium.setValue(inkPointsPtr + i * 8 + 4, y, 'float');
1586
+ }
1587
+ if (this.pdfiumModule.FPDFAnnot_AddInkStroke(annotationPtr, inkPointsPtr, inkPointsCount) === -1) {
1588
+ this.free(inkPointsPtr);
1589
+ return false;
1590
+ }
1591
+ this.free(inkPointsPtr);
1592
+ }
1593
+ return true;
1594
+ }
1595
+ /**
1596
+ * Add contents to stamp annotation
1597
+ * @param docPtr - pointer to pdf document object
1598
+ * @param page - page info
1599
+ * @param pagePtr - pointer to page object
1600
+ * @param annotationPtr - pointer to stamp annotation
1601
+ * @param rect - rect of stamp annotation
1602
+ * @param contents - contents of stamp annotation
1603
+ * @returns whether contents is added to annotation
1604
+ *
1605
+ * @private
1606
+ */
1607
+ addStampContent(docPtr, page, pagePtr, annotationPtr, rect, contents) {
1608
+ for (const content of contents) {
1609
+ switch (content.type) {
1610
+ case PdfPageObjectType.IMAGE:
1611
+ return this.addImageObject(docPtr, page, pagePtr, annotationPtr, rect.origin, content.imageData);
1612
+ }
1613
+ }
1614
+ return false;
1615
+ }
1616
+ /**
1617
+ * Add image object to annotation
1618
+ * @param docPtr - pointer to pdf document object
1619
+ * @param page - page info
1620
+ * @param pagePtr - pointer to page object
1621
+ * @param annotationPtr - pointer to stamp annotation
1622
+ * @param position - position of image
1623
+ * @param imageData - data of image
1624
+ * @returns whether image is added to annotation
1625
+ *
1626
+ * @private
1627
+ */
1628
+ addImageObject(docPtr, page, pagePtr, annotationPtr, position, imageData) {
1629
+ const bytesPerPixel = 4;
1630
+ const pixelCount = imageData.width * imageData.height;
1631
+ const bitmapBufferPtr = this.malloc(bytesPerPixel * pixelCount);
1632
+ if (!bitmapBufferPtr) {
1633
+ return false;
1634
+ }
1635
+ for (let i = 0; i < pixelCount; i++) {
1636
+ const red = imageData.data[i * bytesPerPixel];
1637
+ const green = imageData.data[i * bytesPerPixel + 1];
1638
+ const blue = imageData.data[i * bytesPerPixel + 2];
1639
+ const alpha = imageData.data[i * bytesPerPixel + 3];
1640
+ this.pdfiumModule.pdfium.setValue(bitmapBufferPtr + i * bytesPerPixel, blue, 'i8');
1641
+ this.pdfiumModule.pdfium.setValue(bitmapBufferPtr + i * bytesPerPixel + 1, green, 'i8');
1642
+ this.pdfiumModule.pdfium.setValue(bitmapBufferPtr + i * bytesPerPixel + 2, red, 'i8');
1643
+ this.pdfiumModule.pdfium.setValue(bitmapBufferPtr + i * bytesPerPixel + 3, alpha, 'i8');
1644
+ }
1645
+ const format = BitmapFormat.Bitmap_BGRA;
1646
+ const bitmapPtr = this.pdfiumModule.FPDFBitmap_CreateEx(imageData.width, imageData.height, format, bitmapBufferPtr, 0);
1647
+ if (!bitmapPtr) {
1648
+ this.free(bitmapBufferPtr);
1649
+ return false;
1650
+ }
1651
+ const imageObjectPtr = this.pdfiumModule.FPDFPageObj_NewImageObj(docPtr);
1652
+ if (!imageObjectPtr) {
1653
+ this.pdfiumModule.FPDFBitmap_Destroy(bitmapPtr);
1654
+ this.free(bitmapBufferPtr);
1655
+ return false;
1656
+ }
1657
+ if (!this.pdfiumModule.FPDFImageObj_SetBitmap(pagePtr, 0, imageObjectPtr, bitmapPtr)) {
1658
+ this.pdfiumModule.FPDFBitmap_Destroy(bitmapPtr);
1659
+ this.pdfiumModule.FPDFPageObj_Destroy(imageObjectPtr);
1660
+ this.free(bitmapBufferPtr);
1661
+ return false;
1662
+ }
1663
+ const matrixPtr = this.malloc(6 * 4);
1664
+ this.pdfiumModule.pdfium.setValue(matrixPtr, imageData.width, 'float');
1665
+ this.pdfiumModule.pdfium.setValue(matrixPtr + 4, 0, 'float');
1666
+ this.pdfiumModule.pdfium.setValue(matrixPtr + 8, 0, 'float');
1667
+ this.pdfiumModule.pdfium.setValue(matrixPtr + 12, imageData.height, 'float');
1668
+ this.pdfiumModule.pdfium.setValue(matrixPtr + 16, 0, 'float');
1669
+ this.pdfiumModule.pdfium.setValue(matrixPtr + 20, 0, 'float');
1670
+ if (!this.pdfiumModule.FPDFPageObj_SetMatrix(imageObjectPtr, matrixPtr)) {
1671
+ this.free(matrixPtr);
1672
+ this.pdfiumModule.FPDFBitmap_Destroy(bitmapPtr);
1673
+ this.pdfiumModule.FPDFPageObj_Destroy(imageObjectPtr);
1674
+ this.free(bitmapBufferPtr);
1675
+ return false;
1676
+ }
1677
+ this.free(matrixPtr);
1678
+ this.pdfiumModule.FPDFPageObj_Transform(imageObjectPtr, 1, 0, 0, 1, position.x, position.y);
1679
+ if (!this.pdfiumModule.FPDFAnnot_AppendObject(annotationPtr, imageObjectPtr)) {
1680
+ this.pdfiumModule.FPDFBitmap_Destroy(bitmapPtr);
1681
+ this.pdfiumModule.FPDFPageObj_Destroy(imageObjectPtr);
1682
+ this.free(bitmapBufferPtr);
1683
+ return false;
1684
+ }
1685
+ this.pdfiumModule.FPDFPage_GenerateContent(pagePtr);
1686
+ this.pdfiumModule.FPDFBitmap_Destroy(bitmapPtr);
1687
+ this.free(bitmapBufferPtr);
1688
+ return true;
1689
+ }
1690
+ /**
1691
+ * Save document to array buffer
1692
+ * @param docPtr - pointer to pdf document
1693
+ * @returns array buffer contains the pdf content
1694
+ *
1695
+ * @private
1696
+ */
1697
+ saveDocument(docPtr) {
1698
+ const writerPtr = this.pdfiumModule.PDFiumExt_OpenFileWriter();
1699
+ this.pdfiumModule.PDFiumExt_SaveAsCopy(docPtr, writerPtr);
1700
+ const size = this.pdfiumModule.PDFiumExt_GetFileWriterSize(writerPtr);
1701
+ const dataPtr = this.malloc(size);
1702
+ this.pdfiumModule.PDFiumExt_GetFileWriterData(writerPtr, dataPtr, size);
1703
+ const buffer = new ArrayBuffer(size);
1704
+ const view = new DataView(buffer);
1705
+ for (let i = 0; i < size; i++) {
1706
+ view.setInt8(i, this.pdfiumModule.pdfium.getValue(dataPtr + i, 'i8'));
1707
+ }
1708
+ this.free(dataPtr);
1709
+ this.pdfiumModule.PDFiumExt_CloseFileWriter(writerPtr);
1710
+ return buffer;
1711
+ }
1712
+ /**
1713
+ * Read metadata from pdf document
1714
+ * @param docPtr - pointer to pdf document
1715
+ * @param key - key of metadata field
1716
+ * @returns metadata value
1717
+ *
1718
+ * @private
1719
+ */
1720
+ readMetaText(docPtr, key) {
1721
+ return readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
1722
+ return this.pdfiumModule.FPDF_GetMetaText(docPtr, key, buffer, bufferLength);
1723
+ }, this.pdfiumModule.pdfium.UTF16ToString);
1724
+ }
1725
+ /**
1726
+ * Read bookmarks in the pdf document
1727
+ * @param docPtr - pointer to pdf document
1728
+ * @param rootBookmarkPtr - pointer to root bookmark
1729
+ * @returns bookmarks in the pdf document
1730
+ *
1731
+ * @private
1732
+ */
1733
+ readPdfBookmarks(docPtr, rootBookmarkPtr = 0) {
1734
+ let bookmarkPtr = this.pdfiumModule.FPDFBookmark_GetFirstChild(docPtr, rootBookmarkPtr);
1735
+ const bookmarks = [];
1736
+ while (bookmarkPtr) {
1737
+ const bookmark = this.readPdfBookmark(docPtr, bookmarkPtr);
1738
+ bookmarks.push(bookmark);
1739
+ const nextBookmarkPtr = this.pdfiumModule.FPDFBookmark_GetNextSibling(docPtr, bookmarkPtr);
1740
+ bookmarkPtr = nextBookmarkPtr;
1741
+ }
1742
+ return bookmarks;
1743
+ }
1744
+ /**
1745
+ * Read bookmark in the pdf document
1746
+ * @param docPtr - pointer to pdf document
1747
+ * @param bookmarkPtr - pointer to bookmark object
1748
+ * @returns pdf bookmark object
1749
+ *
1750
+ * @private
1751
+ */
1752
+ readPdfBookmark(docPtr, bookmarkPtr) {
1753
+ const title = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
1754
+ return this.pdfiumModule.FPDFBookmark_GetTitle(bookmarkPtr, buffer, bufferLength);
1755
+ }, this.pdfiumModule.pdfium.UTF16ToString);
1756
+ const bookmarks = this.readPdfBookmarks(docPtr, bookmarkPtr);
1757
+ const target = this.readPdfBookmarkTarget(docPtr, () => {
1758
+ return this.pdfiumModule.FPDFBookmark_GetAction(bookmarkPtr);
1759
+ }, () => {
1760
+ return this.pdfiumModule.FPDFBookmark_GetDest(docPtr, bookmarkPtr);
1761
+ });
1762
+ return {
1763
+ title,
1764
+ target,
1765
+ children: bookmarks,
1766
+ };
1767
+ }
1768
+ /**
1769
+ * Read text rects in pdf page
1770
+ * @param page - pdf page info
1771
+ * @param docPtr - pointer to pdf document
1772
+ * @param pagePtr - pointer to pdf page
1773
+ * @param textPagePtr - pointer to pdf text page
1774
+ * @returns text rects in the pdf page
1775
+ *
1776
+ * @public
1777
+ */
1778
+ readPageTextRects(page, docPtr, pagePtr, textPagePtr) {
1779
+ const rectsCount = this.pdfiumModule.FPDFText_CountRects(textPagePtr, 0, -1);
1780
+ const textRects = [];
1781
+ for (let i = 0; i < rectsCount; i++) {
1782
+ const topPtr = this.malloc(8);
1783
+ const leftPtr = this.malloc(8);
1784
+ const rightPtr = this.malloc(8);
1785
+ const bottomPtr = this.malloc(8);
1786
+ const isSucceed = this.pdfiumModule.FPDFText_GetRect(textPagePtr, i, leftPtr, topPtr, rightPtr, bottomPtr);
1787
+ if (!isSucceed) {
1788
+ this.free(leftPtr);
1789
+ this.free(topPtr);
1790
+ this.free(rightPtr);
1791
+ this.free(bottomPtr);
1792
+ continue;
1793
+ }
1794
+ const left = this.pdfiumModule.pdfium.getValue(leftPtr, 'double');
1795
+ const top = this.pdfiumModule.pdfium.getValue(topPtr, 'double');
1796
+ const right = this.pdfiumModule.pdfium.getValue(rightPtr, 'double');
1797
+ const bottom = this.pdfiumModule.pdfium.getValue(bottomPtr, 'double');
1798
+ this.free(leftPtr);
1799
+ this.free(topPtr);
1800
+ this.free(rightPtr);
1801
+ this.free(bottomPtr);
1802
+ const deviceXPtr = this.malloc(4);
1803
+ const deviceYPtr = this.malloc(4);
1804
+ this.pdfiumModule.FPDF_PageToDevice(pagePtr, 0, 0, page.size.width, page.size.height, 0, left, top, deviceXPtr, deviceYPtr);
1805
+ const x = this.pdfiumModule.pdfium.getValue(deviceXPtr, 'i32');
1806
+ const y = this.pdfiumModule.pdfium.getValue(deviceYPtr, 'i32');
1807
+ this.free(deviceXPtr);
1808
+ this.free(deviceYPtr);
1809
+ const rect = {
1810
+ origin: {
1811
+ x,
1812
+ y,
1813
+ },
1814
+ size: {
1815
+ width: Math.ceil(Math.abs(right - left)),
1816
+ height: Math.ceil(Math.abs(top - bottom)),
1817
+ },
1818
+ };
1819
+ const utf16Length = this.pdfiumModule.FPDFText_GetBoundedText(textPagePtr, left, top, right, bottom, 0, 0);
1820
+ const bytesCount = (utf16Length + 1) * 2; // include NIL
1821
+ const textBuffer = this.malloc(bytesCount);
1822
+ this.pdfiumModule.FPDFText_GetBoundedText(textPagePtr, left, top, right, bottom, textBuffer, utf16Length);
1823
+ const content = this.pdfiumModule.pdfium.UTF16ToString(textBuffer);
1824
+ this.free(textBuffer);
1825
+ const charIndex = this.pdfiumModule.FPDFText_GetCharIndexAtPos(textPagePtr, left, top, 2, 2);
1826
+ let fontFamily = '';
1827
+ let fontSize = rect.size.height;
1828
+ if (charIndex >= 0) {
1829
+ fontSize = this.pdfiumModule.FPDFText_GetFontSize(textPagePtr, charIndex);
1830
+ const fontNameLength = this.pdfiumModule.FPDFText_GetFontInfo(textPagePtr, charIndex, 0, 0, 0);
1831
+ const bytesCount = fontNameLength + 1; // include NIL
1832
+ const textBufferPtr = this.malloc(bytesCount);
1833
+ const flagsPtr = this.malloc(4);
1834
+ this.pdfiumModule.FPDFText_GetFontInfo(textPagePtr, charIndex, textBufferPtr, bytesCount, flagsPtr);
1835
+ fontFamily = this.pdfiumModule.pdfium.UTF8ToString(textBufferPtr);
1836
+ this.free(textBufferPtr);
1837
+ this.free(flagsPtr);
1838
+ }
1839
+ const textRect = {
1840
+ content,
1841
+ rect,
1842
+ font: {
1843
+ family: fontFamily,
1844
+ size: fontSize,
1845
+ },
1846
+ };
1847
+ textRects.push(textRect);
1848
+ }
1849
+ return textRects;
1850
+ }
1851
+ /**
1852
+ * Return geometric + logical text layout for one page
1853
+ * (glyph-only implementation, no FPDFText_GetRect).
1854
+ *
1855
+ * @public
1856
+ */
1857
+ getPageGeometry(doc, page) {
1858
+ const label = 'getPageGeometry';
1859
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, label, 'Begin', doc.id);
1860
+ /* ── guards ───────────────────────────────────────────── */
1861
+ const ctx = this.cache.getContext(doc.id);
1862
+ if (!ctx) {
1863
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, label, 'End', doc.id);
1864
+ return PdfTaskHelper.reject({
1865
+ code: PdfErrorCode.DocNotOpen,
1866
+ message: 'document does not open',
1867
+ });
1868
+ }
1869
+ /* ── native handles ──────────────────────────────────── */
1870
+ const pageCtx = ctx.acquirePage(page.index);
1871
+ const textPagePtr = pageCtx.getTextPage();
1872
+ /* ── 1. read ALL glyphs in logical order ─────────────── */
1873
+ const glyphCount = this.pdfiumModule.FPDFText_CountChars(textPagePtr);
1874
+ const glyphs = [];
1875
+ for (let i = 0; i < glyphCount; i++) {
1876
+ const g = this.readGlyphInfo(page, pageCtx.pagePtr, textPagePtr, i);
1877
+ glyphs.push(g);
1878
+ }
1879
+ /* ── 2. build visual runs from glyph stream ───────────── */
1880
+ const runs = this.buildRunsFromGlyphs(glyphs, textPagePtr);
1881
+ /* ── 3. cleanup & resolve task ───────────────────────── */
1882
+ pageCtx.release();
1883
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, label, 'End', doc.id);
1884
+ return PdfTaskHelper.resolve({ runs });
1885
+ }
1886
+ /**
1887
+ * Group consecutive glyphs that belong to the same CPDF_TextObject
1888
+ * using FPDFText_GetTextObject(), and calculate rotation from glyph positions.
1889
+ */
1890
+ buildRunsFromGlyphs(glyphs, textPagePtr) {
1891
+ const runs = [];
1892
+ let current = null;
1893
+ let curObjPtr = null;
1894
+ /** ── main loop ──────────────────────────────────────────── */
1895
+ for (let i = 0; i < glyphs.length; i++) {
1896
+ const g = glyphs[i];
1897
+ /* 1 — find the CPDF_TextObject this glyph belongs to */
1898
+ const objPtr = this.pdfiumModule.FPDFText_GetTextObject(textPagePtr, i);
1899
+ if (g.isEmpty) {
1900
+ continue;
1901
+ }
1902
+ /* 2 — start a new run when the text object changes */
1903
+ if (objPtr !== curObjPtr) {
1904
+ curObjPtr = objPtr;
1905
+ current = {
1906
+ rect: {
1907
+ x: g.origin.x,
1908
+ y: g.origin.y,
1909
+ width: g.size.width,
1910
+ height: g.size.height,
1911
+ },
1912
+ charStart: i,
1913
+ glyphs: [],
1914
+ };
1915
+ runs.push(current);
1916
+ }
1917
+ /* 3 — append the slim glyph record */
1918
+ current.glyphs.push({
1919
+ x: g.origin.x,
1920
+ y: g.origin.y,
1921
+ width: g.size.width,
1922
+ height: g.size.height,
1923
+ flags: g.isSpace ? 1 : 0,
1924
+ });
1925
+ /* 4 — expand the run's bounding rect */
1926
+ const right = g.origin.x + g.size.width;
1927
+ const bottom = g.origin.y + g.size.height;
1928
+ current.rect.width =
1929
+ Math.max(current.rect.x + current.rect.width, right) - current.rect.x;
1930
+ current.rect.y = Math.min(current.rect.y, g.origin.y);
1931
+ current.rect.height =
1932
+ Math.max(current.rect.y + current.rect.height, bottom) - current.rect.y;
1933
+ }
1934
+ return runs;
1935
+ }
1936
+ /**
1937
+ * Extract glyph geometry + metadata for `charIndex`
1938
+ *
1939
+ * Returns device–space coordinates:
1940
+ * x,y → **top-left** corner (integer-pixels)
1941
+ * w,h → width / height (integer-pixels, ≥ 1)
1942
+ *
1943
+ * And two flags:
1944
+ * isSpace → true if the glyph's Unicode code-point is U+0020
1945
+ */
1946
+ readGlyphInfo(page, pagePtr, textPagePtr, charIndex) {
1947
+ // ── native stack temp pointers ──────────────────────────────
1948
+ const dx1Ptr = this.malloc(4);
1949
+ const dy1Ptr = this.malloc(4);
1950
+ const dx2Ptr = this.malloc(4);
1951
+ const dy2Ptr = this.malloc(4);
1952
+ const rectPtr = this.malloc(16); // 4 floats = 16 bytes
1953
+ let x = 0, y = 0, width = 0, height = 0, isSpace = false;
1954
+ // ── 1) raw glyph bbox in page-user-space
1955
+ if (this.pdfiumModule.FPDFText_GetLooseCharBox(textPagePtr, charIndex, rectPtr)) {
1956
+ const left = this.pdfiumModule.pdfium.getValue(rectPtr, 'float');
1957
+ const top = this.pdfiumModule.pdfium.getValue(rectPtr + 4, 'float');
1958
+ const right = this.pdfiumModule.pdfium.getValue(rectPtr + 8, 'float');
1959
+ const bottom = this.pdfiumModule.pdfium.getValue(rectPtr + 12, 'float');
1960
+ if (left === right || top === bottom) {
1961
+ return {
1962
+ origin: { x: 0, y: 0 },
1963
+ size: { width: 0, height: 0 },
1964
+ isEmpty: true,
1965
+ };
1966
+ }
1967
+ // ── 2) map 2 opposite corners to device-space
1968
+ this.pdfiumModule.FPDF_PageToDevice(pagePtr, 0, 0, page.size.width, page.size.height,
1969
+ /*rotate=*/ 0, left, top, dx1Ptr, dy1Ptr);
1970
+ this.pdfiumModule.FPDF_PageToDevice(pagePtr, 0, 0, page.size.width, page.size.height,
1971
+ /*rotate=*/ 0, right, bottom, dx2Ptr, dy2Ptr);
1972
+ const x1 = this.pdfiumModule.pdfium.getValue(dx1Ptr, 'i32');
1973
+ const y1 = this.pdfiumModule.pdfium.getValue(dy1Ptr, 'i32');
1974
+ const x2 = this.pdfiumModule.pdfium.getValue(dx2Ptr, 'i32');
1975
+ const y2 = this.pdfiumModule.pdfium.getValue(dy2Ptr, 'i32');
1976
+ x = Math.min(x1, x2);
1977
+ y = Math.min(y1, y2);
1978
+ width = Math.max(1, Math.abs(x2 - x1));
1979
+ height = Math.max(1, Math.abs(y2 - y1));
1980
+ // ── 3) extra flags ───────────────────────────────────────
1981
+ const uc = this.pdfiumModule.FPDFText_GetUnicode(textPagePtr, charIndex);
1982
+ isSpace = uc === 32;
1983
+ }
1984
+ // ── free tmps ───────────────────────────────────────────────
1985
+ [rectPtr, dx1Ptr, dy1Ptr, dx2Ptr, dy2Ptr].forEach((p) => this.free(p));
1986
+ return {
1987
+ origin: { x, y },
1988
+ size: { width, height },
1989
+ ...(isSpace && { isSpace }),
1990
+ };
1991
+ }
1992
+ /**
1993
+ * Geometry-only text extraction
1994
+ * ------------------------------------------
1995
+ * Returns every glyph on the requested page
1996
+ * in the logical order delivered by PDFium.
1997
+ *
1998
+ * The promise resolves to an array of objects:
1999
+ * {
2000
+ * idx: number; // glyph index on the page (0…n-1)
2001
+ * origin: { x: number; y: number };
2002
+ * size: { width: number; height: number };
2003
+ * angle: number; // degrees, counter-clock-wise
2004
+ * isSpace: boolean; // true → U+0020
2005
+ * }
2006
+ *
2007
+ * No Unicode is included; front-end decides whether to hydrate it.
2008
+ */
2009
+ getPageGlyphs(doc, page) {
2010
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'getPageGlyphs', doc, page);
2011
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, 'getPageGlyphs', 'Begin', doc.id);
2012
+ // ── 1) safety: document handle must be alive ───────────────
2013
+ const ctx = this.cache.getContext(doc.id);
2014
+ if (!ctx) {
2015
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, 'getPageGlyphs', 'End', doc.id);
2016
+ return PdfTaskHelper.reject({
2017
+ code: PdfErrorCode.DocNotOpen,
2018
+ message: 'document does not open',
2019
+ });
2020
+ }
2021
+ // ── 2) load page + text page handles ───────────────────────
2022
+ const pageCtx = ctx.acquirePage(page.index);
2023
+ const textPagePtr = pageCtx.getTextPage();
2024
+ // ── 3) iterate all glyphs in logical order ─────────────────
2025
+ const total = this.pdfiumModule.FPDFText_CountChars(textPagePtr);
2026
+ const glyphs = new Array(total);
2027
+ for (let i = 0; i < total; i++) {
2028
+ const g = this.readGlyphInfo(page, pageCtx.pagePtr, textPagePtr, i);
2029
+ if (g.isEmpty) {
2030
+ continue;
2031
+ }
2032
+ glyphs[i] = { ...g };
2033
+ }
2034
+ // ── 4) clean-up native handles ─────────────────────────────
2035
+ pageCtx.release();
2036
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, 'getPageGlyphs', 'End', doc.id);
2037
+ return PdfTaskHelper.resolve(glyphs);
2038
+ }
2039
+ readCharBox(page, pagePtr, textPagePtr, charIndex) {
2040
+ const topPtr = this.malloc(8);
2041
+ const leftPtr = this.malloc(8);
2042
+ const bottomPtr = this.malloc(8);
2043
+ const rightPtr = this.malloc(8);
2044
+ let x = 0;
2045
+ let y = 0;
2046
+ let width = 0;
2047
+ let height = 0;
2048
+ if (this.pdfiumModule.FPDFText_GetCharBox(textPagePtr, charIndex, leftPtr, rightPtr, bottomPtr, topPtr)) {
2049
+ const top = this.pdfiumModule.pdfium.getValue(topPtr, 'double');
2050
+ const left = this.pdfiumModule.pdfium.getValue(leftPtr, 'double');
2051
+ const bottom = this.pdfiumModule.pdfium.getValue(bottomPtr, 'double');
2052
+ const right = this.pdfiumModule.pdfium.getValue(rightPtr, 'double');
2053
+ const deviceXPtr = this.malloc(4);
2054
+ const deviceYPtr = this.malloc(4);
2055
+ this.pdfiumModule.FPDF_PageToDevice(pagePtr, 0, 0, page.size.width, page.size.height, 0, left, top, deviceXPtr, deviceYPtr);
2056
+ x = this.pdfiumModule.pdfium.getValue(deviceXPtr, 'i32');
2057
+ y = this.pdfiumModule.pdfium.getValue(deviceYPtr, 'i32');
2058
+ this.free(deviceXPtr);
2059
+ this.free(deviceYPtr);
2060
+ width = Math.ceil(Math.abs(right - left));
2061
+ height = Math.ceil(Math.abs(top - bottom));
2062
+ }
2063
+ this.free(topPtr);
2064
+ this.free(leftPtr);
2065
+ this.free(bottomPtr);
2066
+ this.free(rightPtr);
2067
+ return {
2068
+ origin: {
2069
+ x,
2070
+ y,
2071
+ },
2072
+ size: {
2073
+ width,
2074
+ height,
2075
+ },
2076
+ };
2077
+ }
2078
+ /**
2079
+ * Read page annotations
2080
+ * @param page - page info
2081
+ * @param docPtr - pointer to pdf document
2082
+ * @param pagePtr - pointer to pdf page
2083
+ * @param textPagePtr - pointe to pdf text page
2084
+ * @param scaleFactor - scale factor
2085
+ * @param rotation - rotation angle
2086
+ * @returns annotations on the pdf page
2087
+ *
2088
+ * @private
2089
+ */
2090
+ readPageAnnotations(ctx, page) {
2091
+ const pageCtx = ctx.acquirePage(page.index);
2092
+ const annotationCount = this.pdfiumModule.FPDFPage_GetAnnotCount(pageCtx.pagePtr);
2093
+ const annotations = [];
2094
+ for (let i = 0; i < annotationCount; i++) {
2095
+ pageCtx.withAnnotation(i, (annotPtr) => {
2096
+ const annotation = this.readPageAnnotation(page, pageCtx, annotPtr, i);
2097
+ if (annotation) {
2098
+ annotations.push(annotation);
2099
+ }
2100
+ });
2101
+ }
2102
+ return annotations;
2103
+ }
2104
+ /**
2105
+ * Read pdf annotation from pdf document
2106
+ * @param page - pdf page infor
2107
+ * @param docPtr - pointer to pdf document object
2108
+ * @param pagePtr - pointer to pdf page object
2109
+ * @param textPagePtr - pointer to pdf text page object
2110
+ * @param formHandle - form handle
2111
+ * @param index - index of annotation in the pdf page
2112
+ * @param scaleFactor - factor of scalling
2113
+ * @param rotation - rotation angle
2114
+ * @returns pdf annotation
2115
+ *
2116
+ * @private
2117
+ */
2118
+ readPageAnnotation(page, pageCtx, annotationPtr, index) {
2119
+ const subType = this.pdfiumModule.FPDFAnnot_GetSubtype(annotationPtr);
2120
+ let annotation;
2121
+ switch (subType) {
2122
+ case PdfAnnotationSubtype.TEXT:
2123
+ {
2124
+ annotation = this.readPdfTextAnno(page, pageCtx.pagePtr, annotationPtr, index);
2125
+ }
2126
+ break;
2127
+ case PdfAnnotationSubtype.FREETEXT:
2128
+ {
2129
+ annotation = this.readPdfFreeTextAnno(page, pageCtx.pagePtr, annotationPtr, index);
2130
+ }
2131
+ break;
2132
+ case PdfAnnotationSubtype.LINK:
2133
+ {
2134
+ annotation = this.readPdfLinkAnno(page, pageCtx.docPtr, pageCtx.pagePtr, pageCtx.getTextPage(), annotationPtr, index);
2135
+ }
2136
+ break;
2137
+ case PdfAnnotationSubtype.WIDGET:
2138
+ {
2139
+ annotation = this.readPdfWidgetAnno(page, pageCtx.pagePtr, annotationPtr, pageCtx.getFormHandle(), index);
2140
+ }
2141
+ break;
2142
+ case PdfAnnotationSubtype.FILEATTACHMENT:
2143
+ {
2144
+ annotation = this.readPdfFileAttachmentAnno(page, pageCtx.pagePtr, annotationPtr, index);
2145
+ }
2146
+ break;
2147
+ case PdfAnnotationSubtype.INK:
2148
+ {
2149
+ annotation = this.readPdfInkAnno(page, pageCtx.pagePtr, annotationPtr, index);
2150
+ }
2151
+ break;
2152
+ case PdfAnnotationSubtype.POLYGON:
2153
+ {
2154
+ annotation = this.readPdfPolygonAnno(page, pageCtx.pagePtr, annotationPtr, index);
2155
+ }
2156
+ break;
2157
+ case PdfAnnotationSubtype.POLYLINE:
2158
+ {
2159
+ annotation = this.readPdfPolylineAnno(page, pageCtx.pagePtr, annotationPtr, index);
2160
+ }
2161
+ break;
2162
+ case PdfAnnotationSubtype.LINE:
2163
+ {
2164
+ annotation = this.readPdfLineAnno(page, pageCtx.pagePtr, annotationPtr, index);
2165
+ }
2166
+ break;
2167
+ case PdfAnnotationSubtype.HIGHLIGHT:
2168
+ annotation = this.readPdfHighlightAnno(page, pageCtx.pagePtr, annotationPtr, index);
2169
+ break;
2170
+ case PdfAnnotationSubtype.STAMP:
2171
+ {
2172
+ annotation = this.readPdfStampAnno(pageCtx.docPtr, page, pageCtx.pagePtr, annotationPtr, index);
2173
+ }
2174
+ break;
2175
+ case PdfAnnotationSubtype.SQUARE:
2176
+ {
2177
+ annotation = this.readPdfSquareAnno(page, pageCtx.pagePtr, annotationPtr, index);
2178
+ }
2179
+ break;
2180
+ case PdfAnnotationSubtype.CIRCLE:
2181
+ {
2182
+ annotation = this.readPdfCircleAnno(page, pageCtx.pagePtr, annotationPtr, index);
2183
+ }
2184
+ break;
2185
+ case PdfAnnotationSubtype.UNDERLINE:
2186
+ {
2187
+ annotation = this.readPdfUnderlineAnno(page, pageCtx.pagePtr, annotationPtr, index);
2188
+ }
2189
+ break;
2190
+ case PdfAnnotationSubtype.SQUIGGLY:
2191
+ {
2192
+ annotation = this.readPdfSquigglyAnno(page, pageCtx.pagePtr, annotationPtr, index);
2193
+ }
2194
+ break;
2195
+ case PdfAnnotationSubtype.STRIKEOUT:
2196
+ {
2197
+ annotation = this.readPdfStrikeOutAnno(page, pageCtx.pagePtr, annotationPtr, index);
2198
+ }
2199
+ break;
2200
+ case PdfAnnotationSubtype.CARET:
2201
+ {
2202
+ annotation = this.readPdfCaretAnno(page, pageCtx.pagePtr, annotationPtr, index);
2203
+ }
2204
+ break;
2205
+ case PdfAnnotationSubtype.POPUP:
2206
+ break;
2207
+ default:
2208
+ {
2209
+ annotation = this.readPdfAnno(page, pageCtx.pagePtr, subType, annotationPtr, index);
2210
+ }
2211
+ break;
2212
+ }
2213
+ return annotation;
2214
+ }
2215
+ /**
2216
+ * Return the colour stored directly in the annotation dictionary's `/C` entry.
2217
+ *
2218
+ * Most PDFs created by Acrobat, Microsoft Office, LaTeX, etc. include this entry.
2219
+ * When the key is absent (common in macOS Preview, Chrome, Drawboard) the call
2220
+ * fails and the function returns `undefined`.
2221
+ *
2222
+ * @param annotationPtr - pointer to an `FPDF_ANNOTATION`
2223
+ * @returns An RGBA tuple (0-255 channels) or `undefined` if no `/C` entry exists
2224
+ *
2225
+ * @private
2226
+ */
2227
+ readAnnotationColor(annotationPtr) {
2228
+ const rPtr = this.malloc(4);
2229
+ const gPtr = this.malloc(4);
2230
+ const bPtr = this.malloc(4);
2231
+ const aPtr = this.malloc(4);
2232
+ // colourType 0 = "colour" (stroke/fill); other types are interior/border
2233
+ const ok = this.pdfiumModule.FPDFAnnot_GetColor(annotationPtr,
2234
+ /* colorType = */ 0, rPtr, gPtr, bPtr, aPtr);
2235
+ let colour;
2236
+ if (ok) {
2237
+ colour = {
2238
+ red: this.pdfiumModule.pdfium.getValue(rPtr, 'i32') & 0xff,
2239
+ green: this.pdfiumModule.pdfium.getValue(gPtr, 'i32') & 0xff,
2240
+ blue: this.pdfiumModule.pdfium.getValue(bPtr, 'i32') & 0xff,
2241
+ alpha: this.pdfiumModule.pdfium.getValue(aPtr, 'i32') & 0xff, // 0 = transparent, 255 = opaque
2242
+ };
2243
+ }
2244
+ this.free(rPtr);
2245
+ this.free(gPtr);
2246
+ this.free(bPtr);
2247
+ this.free(aPtr);
2248
+ return colour;
2249
+ }
2250
+ /* --------------------------------------------------------------------------- */
2251
+ /**
2252
+ * Extract the fill (or, if absent, the stroke) colour from a **path object**
2253
+ * inside an appearance stream.
2254
+ *
2255
+ * Works for simple highlights produced by Chrome, Preview, etc. that paint a
2256
+ * single filled rectangle with the desired tint.
2257
+ *
2258
+ * @param pathPtr - pointer to a `FPDF_PAGEOBJECT` of type **PATH**
2259
+ * @returns RGBA tuple or `undefined` when no colour is set on the path
2260
+ *
2261
+ * @private
2262
+ */
2263
+ getColorFromPath(pathPtr) {
2264
+ const r = this.malloc(4), g = this.malloc(4), b = this.malloc(4), a = this.malloc(4);
2265
+ const fillOk = this.pdfiumModule.FPDFPageObj_GetFillColor(pathPtr, r, g, b, a);
2266
+ const strokeOk = !fillOk && // try stroke only if fill failed
2267
+ this.pdfiumModule.FPDFPageObj_GetStrokeColor(pathPtr, r, g, b, a);
2268
+ const ok = fillOk || strokeOk;
2269
+ let c;
2270
+ if (ok) {
2271
+ c = {
2272
+ red: this.pdfiumModule.pdfium.getValue(r, 'i32') & 0xff,
2273
+ green: this.pdfiumModule.pdfium.getValue(g, 'i32') & 0xff,
2274
+ blue: this.pdfiumModule.pdfium.getValue(b, 'i32') & 0xff,
2275
+ alpha: this.pdfiumModule.pdfium.getValue(a, 'i32') & 0xff,
2276
+ };
2277
+ }
2278
+ this.free(r);
2279
+ this.free(g);
2280
+ this.free(b);
2281
+ this.free(a);
2282
+ return c;
2283
+ }
2284
+ /* --------------------------------------------------------------------------- */
2285
+ /**
2286
+ * Recursively walk a page-object tree (PATHs and nested FORM XObjects) until
2287
+ * a colour can be extracted.
2288
+ *
2289
+ * Acrobat often wraps its highlight rectangle in a Form XObject referenced by
2290
+ * the "Do" operator, so this function drills down unlimited depth.
2291
+ *
2292
+ * @param objPtr - pointer to a `FPDF_PAGEOBJECT`
2293
+ * @returns First RGBA tint found, or `undefined` if none of the descendants
2294
+ * carry an explicit fill/stroke colour
2295
+ *
2296
+ * @private
2297
+ */
2298
+ walkPageObjTree(objPtr) {
2299
+ const type = this.pdfiumModule.FPDFPageObj_GetType(objPtr);
2300
+ if (type === PdfPageObjectType.PATH)
2301
+ return this.getColorFromPath(objPtr);
2302
+ if (type !== PdfPageObjectType.FORM)
2303
+ return undefined;
2304
+ const cnt = this.pdfiumModule.FPDFFormObj_CountObjects(objPtr);
2305
+ for (let i = 0; i < cnt; i++) {
2306
+ const child = this.pdfiumModule.FPDFFormObj_GetObject(objPtr, i);
2307
+ if (!child)
2308
+ continue;
2309
+ const c = this.walkPageObjTree(child);
2310
+ if (c)
2311
+ return c;
2312
+ }
2313
+ return undefined;
2314
+ }
2315
+ /* --------------------------------------------------------------------------- */
2316
+ /**
2317
+ * Iterate over every top-level object in the annotation's **appearance stream**
2318
+ * and invoke {@link walkPageObjTree} to locate a usable tint.
2319
+ *
2320
+ * Catches:
2321
+ * • Simple filled path (Preview, Chrome)
2322
+ * • Form XObject containing the path (Acrobat)
2323
+ *
2324
+ * @param annotPtr - pointer to an `FPDF_ANNOTATION`
2325
+ * @returns RGBA tuple or `undefined` when no colour can be resolved from AP
2326
+ *
2327
+ * @private
2328
+ */
2329
+ colorFromAppearance(annotPtr) {
2330
+ const n = this.pdfiumModule.FPDFAnnot_GetObjectCount(annotPtr);
2331
+ for (let i = 0; i < n; i++) {
2332
+ const obj = this.pdfiumModule.FPDFAnnot_GetObject(annotPtr, i);
2333
+ if (!obj)
2334
+ continue;
2335
+ const c = this.walkPageObjTree(obj);
2336
+ if (c)
2337
+ return c;
2338
+ }
2339
+ return undefined;
2340
+ }
2341
+ /* --------------------------------------------------------------------------- */
2342
+ /**
2343
+ * Resolve the visible fill colour for **Highlight / Underline / StrikeOut /
2344
+ * Squiggly** markup annotations.
2345
+ *
2346
+ * Resolution order (first non-`undefined` wins):
2347
+ * 1. `/C` dictionary entry – fast, present in Acrobat / Office PDFs
2348
+ * 2. Appearance-stream objects – drills into paths & nested forms
2349
+ * 3. Hard-coded fallback (Acrobat-style opaque yellow)
2350
+ *
2351
+ * @param annotationPtr - pointer to an `FPDF_ANNOTATION`
2352
+ * @param fallback - colour to use when the PDF stores no tint at all
2353
+ * @returns Guaranteed RGBA tuple (never `undefined`)
2354
+ *
2355
+ * @private
2356
+ */
2357
+ resolveAnnotationColor(annotationPtr, fallback = { red: 255, green: 245, blue: 155, alpha: 255 }) {
2358
+ return (this.readAnnotationColor(annotationPtr) ?? // 1 – /C entry
2359
+ this.colorFromAppearance(annotationPtr) ?? // 2 – AP stream walk
2360
+ fallback // 3 – default
2361
+ );
2362
+ }
2363
+ /**
2364
+ * Read `/QuadPoints` from any annotation and convert each quadrilateral to
2365
+ * device-space coordinates.
2366
+ *
2367
+ * The four points are returned in natural reading order:
2368
+ * `p1 → p2` (top edge) and `p4 → p3` (bottom edge).
2369
+ * This preserves the true shape for rotated / skewed text, whereas callers
2370
+ * that only need axis-aligned boxes can collapse each quad themselves.
2371
+ *
2372
+ * @param page - logical page info object (`PdfPageObject`)
2373
+ * @param annotationPtr - pointer to the annotation whose quads are needed
2374
+ * @returns Array of `Quad` objects (`[]` if the annotation has no quads)
2375
+ *
2376
+ * @private
2377
+ */
2378
+ readAnnotationQuads(page, annotationPtr) {
2379
+ const quadCount = this.pdfiumModule.FPDFAnnot_CountAttachmentPoints(annotationPtr);
2380
+ if (quadCount === 0)
2381
+ return [];
2382
+ const FS_QUADPOINTSF_SIZE = 8 * 4; // eight floats, 32 bytes
2383
+ const quads = [];
2384
+ for (let qi = 0; qi < quadCount; qi++) {
2385
+ const quadPtr = this.malloc(FS_QUADPOINTSF_SIZE);
2386
+ const ok = this.pdfiumModule.FPDFAnnot_GetAttachmentPoints(annotationPtr, qi, quadPtr);
2387
+ if (ok) {
2388
+ // read the eight floats
2389
+ const xs = [];
2390
+ const ys = [];
2391
+ for (let i = 0; i < 4; i++) {
2392
+ const base = quadPtr + i * 8; // 8 bytes per point (x+y)
2393
+ xs.push(this.pdfiumModule.pdfium.getValue(base, 'float'));
2394
+ ys.push(this.pdfiumModule.pdfium.getValue(base + 4, 'float'));
2395
+ }
2396
+ // convert to device-space
2397
+ const p1 = this.convertPagePointToDevicePoint(page, { x: xs[0], y: ys[0] });
2398
+ const p2 = this.convertPagePointToDevicePoint(page, { x: xs[1], y: ys[1] });
2399
+ const p3 = this.convertPagePointToDevicePoint(page, { x: xs[2], y: ys[2] });
2400
+ const p4 = this.convertPagePointToDevicePoint(page, { x: xs[3], y: ys[3] });
2401
+ quads.push({ p1, p2, p3, p4 });
2402
+ }
2403
+ this.free(quadPtr);
2404
+ }
2405
+ return quads;
2406
+ }
2407
+ /**
2408
+ * Read pdf text annotation
2409
+ * @param page - pdf page infor
2410
+ * @param pagePtr - pointer to pdf page object
2411
+ * @param annotationPtr - pointer to pdf annotation
2412
+ * @param index - index of annotation in the pdf page
2413
+ * @returns pdf text annotation
2414
+ *
2415
+ * @private
2416
+ */
2417
+ readPdfTextAnno(page, pagePtr, annotationPtr, index) {
2418
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2419
+ const annoRect = this.readPageAnnoRect(annotationPtr);
2420
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, annoRect);
2421
+ const author = this.getAnnotString(annotationPtr, 'T');
2422
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2423
+ const modified = this.toIsoDate(modifiedRaw);
2424
+ const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
2425
+ const state = this.getAnnotString(annotationPtr, 'State');
2426
+ const stateModel = this.getAnnotString(annotationPtr, 'StateModel');
2427
+ const color = this.resolveAnnotationColor(annotationPtr);
2428
+ const inReplyToId = this.getInReplyToId(pagePtr, annotationPtr);
2429
+ const popup = !inReplyToId
2430
+ ? this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index)
2431
+ : undefined;
2432
+ return {
2433
+ status: PdfAnnotationObjectStatus.Committed,
2434
+ pageIndex: page.index,
2435
+ id: index,
2436
+ type: PdfAnnotationSubtype.TEXT,
2437
+ contents,
2438
+ color,
2439
+ rect,
2440
+ popup,
2441
+ appearances,
2442
+ inReplyToId,
2443
+ author,
2444
+ modified,
2445
+ state,
2446
+ stateModel,
2447
+ };
2448
+ }
2449
+ /**
2450
+ * Read pdf freetext annotation
2451
+ * @param page - pdf page infor
2452
+ * @param pagePtr - pointer to pdf page object
2453
+ * @param annotationPtr - pointer to pdf annotation
2454
+ * @param index - index of annotation in the pdf page
2455
+ * @returns pdf freetext annotation
2456
+ *
2457
+ * @private
2458
+ */
2459
+ readPdfFreeTextAnno(page, pagePtr, annotationPtr, index) {
2460
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2461
+ const annoRect = this.readPageAnnoRect(annotationPtr);
2462
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, annoRect);
2463
+ const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
2464
+ const author = this.getAnnotString(annotationPtr, 'T');
2465
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2466
+ const modified = this.toIsoDate(modifiedRaw);
2467
+ const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2468
+ return {
2469
+ status: PdfAnnotationObjectStatus.Committed,
2470
+ pageIndex: page.index,
2471
+ id: index,
2472
+ type: PdfAnnotationSubtype.FREETEXT,
2473
+ contents,
2474
+ author,
2475
+ modified,
2476
+ rect,
2477
+ popup,
2478
+ appearances,
2479
+ };
2480
+ }
2481
+ /**
2482
+ * Read pdf link annotation from pdf document
2483
+ * @param page - pdf page infor
2484
+ * @param docPtr - pointer to pdf document object
2485
+ * @param pagePtr - pointer to pdf page object
2486
+ * @param textPagePtr - pointer to pdf text page object
2487
+ * @param annotationPtr - pointer to pdf annotation
2488
+ * @param index - index of annotation in the pdf page
2489
+ * @returns pdf link annotation
2490
+ *
2491
+ * @private
2492
+ */
2493
+ readPdfLinkAnno(page, docPtr, pagePtr, textPagePtr, annotationPtr, index) {
2494
+ const linkPtr = this.pdfiumModule.FPDFAnnot_GetLink(annotationPtr);
2495
+ if (!linkPtr) {
2496
+ return;
2497
+ }
2498
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2499
+ const annoRect = this.readPageAnnoRect(annotationPtr);
2500
+ const { left, top, right, bottom } = annoRect;
2501
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, annoRect);
2502
+ const author = this.getAnnotString(annotationPtr, 'T');
2503
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2504
+ const modified = this.toIsoDate(modifiedRaw);
2505
+ const utf16Length = this.pdfiumModule.FPDFText_GetBoundedText(textPagePtr, left, top, right, bottom, 0, 0);
2506
+ const bytesCount = (utf16Length + 1) * 2; // include NIL
2507
+ const textBufferPtr = this.malloc(bytesCount);
2508
+ this.pdfiumModule.FPDFText_GetBoundedText(textPagePtr, left, top, right, bottom, textBufferPtr, utf16Length);
2509
+ const text = this.pdfiumModule.pdfium.UTF16ToString(textBufferPtr);
2510
+ this.free(textBufferPtr);
2511
+ const target = this.readPdfLinkAnnoTarget(docPtr, () => {
2512
+ return this.pdfiumModule.FPDFLink_GetAction(linkPtr);
2513
+ }, () => {
2514
+ return this.pdfiumModule.FPDFLink_GetDest(docPtr, linkPtr);
2515
+ });
2516
+ const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2517
+ return {
2518
+ status: PdfAnnotationObjectStatus.Committed,
2519
+ pageIndex: page.index,
2520
+ id: index,
2521
+ type: PdfAnnotationSubtype.LINK,
2522
+ text,
2523
+ target,
2524
+ rect,
2525
+ popup,
2526
+ appearances,
2527
+ author,
2528
+ modified,
2529
+ };
2530
+ }
2531
+ /**
2532
+ * Read pdf widget annotation
2533
+ * @param page - pdf page infor
2534
+ * @param pagePtr - pointer to pdf page object
2535
+ * @param annotationPtr - pointer to pdf annotation
2536
+ * @param formHandle - form handle
2537
+ * @param index - index of annotation in the pdf page
2538
+ * @returns pdf widget annotation
2539
+ *
2540
+ * @private
2541
+ */
2542
+ readPdfWidgetAnno(page, pagePtr, annotationPtr, formHandle, index) {
2543
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2544
+ const pageRect = this.readPageAnnoRect(annotationPtr);
2545
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2546
+ const author = this.getAnnotString(annotationPtr, 'T');
2547
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2548
+ const modified = this.toIsoDate(modifiedRaw);
2549
+ const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2550
+ const field = this.readPdfWidgetAnnoField(formHandle, annotationPtr);
2551
+ return {
2552
+ status: PdfAnnotationObjectStatus.Committed,
2553
+ pageIndex: page.index,
2554
+ id: index,
2555
+ type: PdfAnnotationSubtype.WIDGET,
2556
+ rect,
2557
+ field,
2558
+ popup,
2559
+ appearances,
2560
+ author,
2561
+ modified,
2562
+ };
2563
+ }
2564
+ /**
2565
+ * Read pdf file attachment annotation
2566
+ * @param page - pdf page infor
2567
+ * @param pagePtr - pointer to pdf page object
2568
+ * @param annotationPtr - pointer to pdf annotation
2569
+ * @param index - index of annotation in the pdf page
2570
+ * @returns pdf file attachment annotation
2571
+ *
2572
+ * @private
2573
+ */
2574
+ readPdfFileAttachmentAnno(page, pagePtr, annotationPtr, index) {
2575
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2576
+ const pageRect = this.readPageAnnoRect(annotationPtr);
2577
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2578
+ const author = this.getAnnotString(annotationPtr, 'T');
2579
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2580
+ const modified = this.toIsoDate(modifiedRaw);
2581
+ const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2582
+ return {
2583
+ status: PdfAnnotationObjectStatus.Committed,
2584
+ pageIndex: page.index,
2585
+ id: index,
2586
+ type: PdfAnnotationSubtype.FILEATTACHMENT,
2587
+ rect,
2588
+ popup,
2589
+ appearances,
2590
+ author,
2591
+ modified,
2592
+ };
2593
+ }
2594
+ /**
2595
+ * Read pdf ink annotation
2596
+ * @param page - pdf page infor
2597
+ * @param pagePtr - pointer to pdf page object
2598
+ * @param annotationPtr - pointer to pdf annotation
2599
+ * @param index - index of annotation in the pdf page
2600
+ * @returns pdf ink annotation
2601
+ *
2602
+ * @private
2603
+ */
2604
+ readPdfInkAnno(page, pagePtr, annotationPtr, index) {
2605
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2606
+ const pageRect = this.readPageAnnoRect(annotationPtr);
2607
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2608
+ const author = this.getAnnotString(annotationPtr, 'T');
2609
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2610
+ const modified = this.toIsoDate(modifiedRaw);
2611
+ const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2612
+ const inkList = [];
2613
+ const count = this.pdfiumModule.FPDFAnnot_GetInkListCount(annotationPtr);
2614
+ for (let i = 0; i < count; i++) {
2615
+ const points = [];
2616
+ const pointsCount = this.pdfiumModule.FPDFAnnot_GetInkListPath(annotationPtr, i, 0, 0);
2617
+ if (pointsCount > 0) {
2618
+ const pointMemorySize = 8;
2619
+ const pointsPtr = this.malloc(pointsCount * pointMemorySize);
2620
+ this.pdfiumModule.FPDFAnnot_GetInkListPath(annotationPtr, i, pointsPtr, pointsCount);
2621
+ for (let j = 0; j < pointsCount; j++) {
2622
+ const pointX = this.pdfiumModule.pdfium.getValue(pointsPtr + j * 8, 'float');
2623
+ const pointY = this.pdfiumModule.pdfium.getValue(pointsPtr + j * 8 + 4, 'float');
2624
+ const { x, y } = this.convertPagePointToDevicePoint(page, {
2625
+ x: pointX,
2626
+ y: pointY,
2627
+ });
2628
+ points.push({ x, y });
2629
+ }
2630
+ this.free(pointsPtr);
2631
+ }
2632
+ inkList.push({ points });
2633
+ }
2634
+ return {
2635
+ status: PdfAnnotationObjectStatus.Committed,
2636
+ pageIndex: page.index,
2637
+ id: index,
2638
+ type: PdfAnnotationSubtype.INK,
2639
+ rect,
2640
+ popup,
2641
+ inkList,
2642
+ appearances,
2643
+ author,
2644
+ modified,
2645
+ };
2646
+ }
2647
+ /**
2648
+ * Read pdf polygon annotation
2649
+ * @param page - pdf page infor
2650
+ * @param pagePtr - pointer to pdf page object
2651
+ * @param annotationPtr - pointer to pdf annotation
2652
+ * @param index - index of annotation in the pdf page
2653
+ * @returns pdf polygon annotation
2654
+ *
2655
+ * @private
2656
+ */
2657
+ readPdfPolygonAnno(page, pagePtr, annotationPtr, index) {
2658
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2659
+ const pageRect = this.readPageAnnoRect(annotationPtr);
2660
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2661
+ const author = this.getAnnotString(annotationPtr, 'T');
2662
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2663
+ const modified = this.toIsoDate(modifiedRaw);
2664
+ const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2665
+ const vertices = this.readPdfAnnoVertices(page, pagePtr, annotationPtr);
2666
+ return {
2667
+ status: PdfAnnotationObjectStatus.Committed,
2668
+ pageIndex: page.index,
2669
+ id: index,
2670
+ type: PdfAnnotationSubtype.POLYGON,
2671
+ rect,
2672
+ popup,
2673
+ vertices,
2674
+ appearances,
2675
+ author,
2676
+ modified,
2677
+ };
2678
+ }
2679
+ /**
2680
+ * Read pdf polyline annotation
2681
+ * @param page - pdf page infor
2682
+ * @param pagePtr - pointer to pdf page object
2683
+ * @param annotationPtr - pointer to pdf annotation
2684
+ * @param index - index of annotation in the pdf page
2685
+ * @returns pdf polyline annotation
2686
+ *
2687
+ * @private
2688
+ */
2689
+ readPdfPolylineAnno(page, pagePtr, annotationPtr, index) {
2690
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2691
+ const pageRect = this.readPageAnnoRect(annotationPtr);
2692
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2693
+ const author = this.getAnnotString(annotationPtr, 'T');
2694
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2695
+ const modified = this.toIsoDate(modifiedRaw);
2696
+ const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2697
+ const vertices = this.readPdfAnnoVertices(page, pagePtr, annotationPtr);
2698
+ return {
2699
+ status: PdfAnnotationObjectStatus.Committed,
2700
+ pageIndex: page.index,
2701
+ id: index,
2702
+ type: PdfAnnotationSubtype.POLYLINE,
2703
+ rect,
2704
+ popup,
2705
+ vertices,
2706
+ appearances,
2707
+ author,
2708
+ modified,
2709
+ };
2710
+ }
2711
+ /**
2712
+ * Read pdf line annotation
2713
+ * @param page - pdf page infor
2714
+ * @param pagePtr - pointer to pdf page object
2715
+ * @param annotationPtr - pointer to pdf annotation
2716
+ * @param index - index of annotation in the pdf page
2717
+ * @returns pdf line annotation
2718
+ *
2719
+ * @private
2720
+ */
2721
+ readPdfLineAnno(page, pagePtr, annotationPtr, index) {
2722
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2723
+ const pageRect = this.readPageAnnoRect(annotationPtr);
2724
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2725
+ const author = this.getAnnotString(annotationPtr, 'T');
2726
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2727
+ const modified = this.toIsoDate(modifiedRaw);
2728
+ const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2729
+ const startPointPtr = this.malloc(8);
2730
+ const endPointPtr = this.malloc(8);
2731
+ this.pdfiumModule.FPDFAnnot_GetLine(annotationPtr, startPointPtr, endPointPtr);
2732
+ const startPointX = this.pdfiumModule.pdfium.getValue(startPointPtr, 'float');
2733
+ const startPointY = this.pdfiumModule.pdfium.getValue(startPointPtr + 4, 'float');
2734
+ const startPoint = this.convertPagePointToDevicePoint(page, {
2735
+ x: startPointX,
2736
+ y: startPointY,
2737
+ });
2738
+ const endPointX = this.pdfiumModule.pdfium.getValue(endPointPtr, 'float');
2739
+ const endPointY = this.pdfiumModule.pdfium.getValue(endPointPtr + 4, 'float');
2740
+ const endPoint = this.convertPagePointToDevicePoint(page, {
2741
+ x: endPointX,
2742
+ y: endPointY,
2743
+ });
2744
+ this.free(startPointPtr);
2745
+ this.free(endPointPtr);
2746
+ return {
2747
+ status: PdfAnnotationObjectStatus.Committed,
2748
+ pageIndex: page.index,
2749
+ id: index,
2750
+ type: PdfAnnotationSubtype.LINE,
2751
+ rect,
2752
+ popup,
2753
+ startPoint,
2754
+ endPoint,
2755
+ appearances,
2756
+ author,
2757
+ modified,
2758
+ };
2759
+ }
2760
+ /**
2761
+ * Read pdf highlight annotation
2762
+ * @param page - pdf page infor
2763
+ * @param pagePtr - pointer to pdf page object
2764
+ * @param annotationPtr - pointer to pdf annotation
2765
+ * @param index - index of annotation in the pdf page
2766
+ * @returns pdf highlight annotation
2767
+ *
2768
+ * @private
2769
+ */
2770
+ readPdfHighlightAnno(page, pagePtr, annotationPtr, index) {
2771
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2772
+ const pageRect = this.readPageAnnoRect(annotationPtr);
2773
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2774
+ const quads = this.readAnnotationQuads(page, annotationPtr);
2775
+ const color = this.resolveAnnotationColor(annotationPtr);
2776
+ const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2777
+ const author = this.getAnnotString(annotationPtr, 'T');
2778
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2779
+ const modified = this.toIsoDate(modifiedRaw);
2780
+ return {
2781
+ status: PdfAnnotationObjectStatus.Committed,
2782
+ pageIndex: page.index,
2783
+ id: index,
2784
+ type: PdfAnnotationSubtype.HIGHLIGHT,
2785
+ rect,
2786
+ popup,
2787
+ appearances,
2788
+ segmentRects: quads.map(quadToRect),
2789
+ color,
2790
+ author,
2791
+ modified,
2792
+ };
2793
+ }
2794
+ /**
2795
+ * Read pdf underline annotation
2796
+ * @param page - pdf page infor
2797
+ * @param pagePtr - pointer to pdf page object
2798
+ * @param annotationPtr - pointer to pdf annotation
2799
+ * @param index - index of annotation in the pdf page
2800
+ * @returns pdf underline annotation
2801
+ *
2802
+ * @private
2803
+ */
2804
+ readPdfUnderlineAnno(page, pagePtr, annotationPtr, index) {
2805
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2806
+ const pageRect = this.readPageAnnoRect(annotationPtr);
2807
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2808
+ const author = this.getAnnotString(annotationPtr, 'T');
2809
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2810
+ const modified = this.toIsoDate(modifiedRaw);
2811
+ const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2812
+ return {
2813
+ status: PdfAnnotationObjectStatus.Committed,
2814
+ pageIndex: page.index,
2815
+ id: index,
2816
+ type: PdfAnnotationSubtype.UNDERLINE,
2817
+ rect,
2818
+ popup,
2819
+ appearances,
2820
+ author,
2821
+ modified,
2822
+ };
2823
+ }
2824
+ /**
2825
+ * Read strikeout annotation
2826
+ * @param page - pdf page infor
2827
+ * @param pagePtr - pointer to pdf page object
2828
+ * @param annotationPtr - pointer to pdf annotation
2829
+ * @param index - index of annotation in the pdf page
2830
+ * @returns pdf strikeout annotation
2831
+ *
2832
+ * @private
2833
+ */
2834
+ readPdfStrikeOutAnno(page, pagePtr, annotationPtr, index) {
2835
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2836
+ const pageRect = this.readPageAnnoRect(annotationPtr);
2837
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2838
+ const author = this.getAnnotString(annotationPtr, 'T');
2839
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2840
+ const modified = this.toIsoDate(modifiedRaw);
2841
+ const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2842
+ return {
2843
+ status: PdfAnnotationObjectStatus.Committed,
2844
+ pageIndex: page.index,
2845
+ id: index,
2846
+ type: PdfAnnotationSubtype.STRIKEOUT,
2847
+ rect,
2848
+ popup,
2849
+ appearances,
2850
+ author,
2851
+ modified,
2852
+ };
2853
+ }
2854
+ /**
2855
+ * Read pdf squiggly annotation
2856
+ * @param page - pdf page infor
2857
+ * @param pagePtr - pointer to pdf page object
2858
+ * @param annotationPtr - pointer to pdf annotation
2859
+ * @param index - index of annotation in the pdf page
2860
+ * @returns pdf squiggly annotation
2861
+ *
2862
+ * @private
2863
+ */
2864
+ readPdfSquigglyAnno(page, pagePtr, annotationPtr, index) {
2865
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2866
+ const pageRect = this.readPageAnnoRect(annotationPtr);
2867
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2868
+ const author = this.getAnnotString(annotationPtr, 'T');
2869
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2870
+ const modified = this.toIsoDate(modifiedRaw);
2871
+ const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2872
+ return {
2873
+ status: PdfAnnotationObjectStatus.Committed,
2874
+ pageIndex: page.index,
2875
+ id: index,
2876
+ type: PdfAnnotationSubtype.SQUIGGLY,
2877
+ rect,
2878
+ popup,
2879
+ appearances,
2880
+ author,
2881
+ modified,
2882
+ };
2883
+ }
2884
+ /**
2885
+ * Read pdf caret annotation
2886
+ * @param page - pdf page infor
2887
+ * @param pagePtr - pointer to pdf page object
2888
+ * @param annotationPtr - pointer to pdf annotation
2889
+ * @param index - index of annotation in the pdf page
2890
+ * @returns pdf caret annotation
2891
+ *
2892
+ * @private
2893
+ */
2894
+ readPdfCaretAnno(page, pagePtr, annotationPtr, index) {
2895
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2896
+ const pageRect = this.readPageAnnoRect(annotationPtr);
2897
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2898
+ const author = this.getAnnotString(annotationPtr, 'T');
2899
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2900
+ const modified = this.toIsoDate(modifiedRaw);
2901
+ const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2902
+ return {
2903
+ status: PdfAnnotationObjectStatus.Committed,
2904
+ pageIndex: page.index,
2905
+ id: index,
2906
+ type: PdfAnnotationSubtype.CARET,
2907
+ rect,
2908
+ popup,
2909
+ appearances,
2910
+ author,
2911
+ modified,
2912
+ };
2913
+ }
2914
+ /**
2915
+ * Read pdf stamp annotation
2916
+ * @param docPtr - pointer to pdf document object
2917
+ * @param page - pdf page infor
2918
+ * @param pagePtr - pointer to pdf page object
2919
+ * @param annotationPtr - pointer to pdf annotation
2920
+ * @param index - index of annotation in the pdf page
2921
+ * @returns pdf stamp annotation
2922
+ *
2923
+ * @private
2924
+ */
2925
+ readPdfStampAnno(docPtr, page, pagePtr, annotationPtr, index) {
2926
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2927
+ const pageRect = this.readPageAnnoRect(annotationPtr);
2928
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2929
+ const author = this.getAnnotString(annotationPtr, 'T');
2930
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2931
+ const modified = this.toIsoDate(modifiedRaw);
2932
+ const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2933
+ const contents = [];
2934
+ const objectCount = this.pdfiumModule.FPDFAnnot_GetObjectCount(annotationPtr);
2935
+ for (let i = 0; i < objectCount; i++) {
2936
+ const annotationObjectPtr = this.pdfiumModule.FPDFAnnot_GetObject(annotationPtr, i);
2937
+ const pageObj = this.readPdfPageObject(annotationObjectPtr);
2938
+ if (pageObj) {
2939
+ contents.push(pageObj);
2940
+ }
2941
+ }
2942
+ return {
2943
+ status: PdfAnnotationObjectStatus.Committed,
2944
+ pageIndex: page.index,
2945
+ id: index,
2946
+ type: PdfAnnotationSubtype.STAMP,
2947
+ rect,
2948
+ popup,
2949
+ contents,
2950
+ appearances,
2951
+ author,
2952
+ modified,
2953
+ };
2954
+ }
2955
+ /**
2956
+ * Read pdf object in pdf page
2957
+ * @param pageObjectPtr - pointer to pdf object in page
2958
+ * @returns pdf object in page
2959
+ *
2960
+ * @private
2961
+ */
2962
+ readPdfPageObject(pageObjectPtr) {
2963
+ const type = this.pdfiumModule.FPDFPageObj_GetType(pageObjectPtr);
2964
+ switch (type) {
2965
+ case PdfPageObjectType.PATH:
2966
+ return this.readPathObject(pageObjectPtr);
2967
+ case PdfPageObjectType.IMAGE:
2968
+ return this.readImageObject(pageObjectPtr);
2969
+ case PdfPageObjectType.FORM:
2970
+ return this.readFormObject(pageObjectPtr);
2971
+ }
2972
+ }
2973
+ /**
2974
+ * Read pdf path object
2975
+ * @param pathObjectPtr - pointer to pdf path object in page
2976
+ * @returns pdf path object
2977
+ *
2978
+ * @private
2979
+ */
2980
+ readPathObject(pathObjectPtr) {
2981
+ const segmentCount = this.pdfiumModule.FPDFPath_CountSegments(pathObjectPtr);
2982
+ const leftPtr = this.malloc(4);
2983
+ const bottomPtr = this.malloc(4);
2984
+ const rightPtr = this.malloc(4);
2985
+ const topPtr = this.malloc(4);
2986
+ this.pdfiumModule.FPDFPageObj_GetBounds(pathObjectPtr, leftPtr, bottomPtr, rightPtr, topPtr);
2987
+ const left = this.pdfiumModule.pdfium.getValue(leftPtr, 'float');
2988
+ const bottom = this.pdfiumModule.pdfium.getValue(bottomPtr, 'float');
2989
+ const right = this.pdfiumModule.pdfium.getValue(rightPtr, 'float');
2990
+ const top = this.pdfiumModule.pdfium.getValue(topPtr, 'float');
2991
+ const bounds = { left, bottom, right, top };
2992
+ this.free(leftPtr);
2993
+ this.free(bottomPtr);
2994
+ this.free(rightPtr);
2995
+ this.free(topPtr);
2996
+ const segments = [];
2997
+ for (let i = 0; i < segmentCount; i++) {
2998
+ const segment = this.readPdfSegment(pathObjectPtr, i);
2999
+ segments.push(segment);
3000
+ }
3001
+ const matrix = this.readPdfPageObjectTransformMatrix(pathObjectPtr);
3002
+ return {
3003
+ type: PdfPageObjectType.PATH,
3004
+ bounds,
3005
+ segments,
3006
+ matrix,
3007
+ };
3008
+ }
3009
+ /**
3010
+ * Read segment of pdf path object
3011
+ * @param annotationObjectPtr - pointer to pdf path object
3012
+ * @param segmentIndex - index of segment
3013
+ * @returns pdf segment in pdf path
3014
+ *
3015
+ * @private
3016
+ */
3017
+ readPdfSegment(annotationObjectPtr, segmentIndex) {
3018
+ const segmentPtr = this.pdfiumModule.FPDFPath_GetPathSegment(annotationObjectPtr, segmentIndex);
3019
+ const segmentType = this.pdfiumModule.FPDFPathSegment_GetType(segmentPtr);
3020
+ const isClosed = this.pdfiumModule.FPDFPathSegment_GetClose(segmentPtr);
3021
+ const pointXPtr = this.malloc(4);
3022
+ const pointYPtr = this.malloc(4);
3023
+ this.pdfiumModule.FPDFPathSegment_GetPoint(segmentPtr, pointXPtr, pointYPtr);
3024
+ const pointX = this.pdfiumModule.pdfium.getValue(pointXPtr, 'float');
3025
+ const pointY = this.pdfiumModule.pdfium.getValue(pointYPtr, 'float');
3026
+ this.free(pointXPtr);
3027
+ this.free(pointYPtr);
3028
+ return {
3029
+ type: segmentType,
3030
+ point: { x: pointX, y: pointY },
3031
+ isClosed,
3032
+ };
3033
+ }
3034
+ /**
3035
+ * Read pdf image object from pdf document
3036
+ * @param pageObjectPtr - pointer to pdf image object in page
3037
+ * @returns pdf image object
3038
+ *
3039
+ * @private
3040
+ */
3041
+ readImageObject(imageObjectPtr) {
3042
+ const bitmapPtr = this.pdfiumModule.FPDFImageObj_GetBitmap(imageObjectPtr);
3043
+ const bitmapBufferPtr = this.pdfiumModule.FPDFBitmap_GetBuffer(bitmapPtr);
3044
+ const bitmapWidth = this.pdfiumModule.FPDFBitmap_GetWidth(bitmapPtr);
3045
+ const bitmapHeight = this.pdfiumModule.FPDFBitmap_GetHeight(bitmapPtr);
3046
+ const format = this.pdfiumModule.FPDFBitmap_GetFormat(bitmapPtr);
3047
+ const pixelCount = bitmapWidth * bitmapHeight;
3048
+ const bytesPerPixel = 4;
3049
+ const array = new Uint8ClampedArray(pixelCount * bytesPerPixel);
3050
+ for (let i = 0; i < pixelCount; i++) {
3051
+ switch (format) {
3052
+ case BitmapFormat.Bitmap_BGR:
3053
+ {
3054
+ const blue = this.pdfiumModule.pdfium.getValue(bitmapBufferPtr + i * 3, 'i8');
3055
+ const green = this.pdfiumModule.pdfium.getValue(bitmapBufferPtr + i * 3 + 1, 'i8');
3056
+ const red = this.pdfiumModule.pdfium.getValue(bitmapBufferPtr + i * 3 + 2, 'i8');
3057
+ array[i * bytesPerPixel] = red;
3058
+ array[i * bytesPerPixel + 1] = green;
3059
+ array[i * bytesPerPixel + 2] = blue;
3060
+ array[i * bytesPerPixel + 3] = 100;
3061
+ }
3062
+ break;
3063
+ }
3064
+ }
3065
+ const imageData = new ImageData(array, bitmapWidth, bitmapHeight);
3066
+ const matrix = this.readPdfPageObjectTransformMatrix(imageObjectPtr);
3067
+ return {
3068
+ type: PdfPageObjectType.IMAGE,
3069
+ imageData,
3070
+ matrix,
3071
+ };
3072
+ }
3073
+ /**
3074
+ * Read form object from pdf document
3075
+ * @param formObjectPtr - pointer to pdf form object in page
3076
+ * @returns pdf form object
3077
+ *
3078
+ * @private
3079
+ */
3080
+ readFormObject(formObjectPtr) {
3081
+ const objectCount = this.pdfiumModule.FPDFFormObj_CountObjects(formObjectPtr);
3082
+ const objects = [];
3083
+ for (let i = 0; i < objectCount; i++) {
3084
+ const pageObjectPtr = this.pdfiumModule.FPDFFormObj_GetObject(formObjectPtr, i);
3085
+ const pageObj = this.readPdfPageObject(pageObjectPtr);
3086
+ if (pageObj) {
3087
+ objects.push(pageObj);
3088
+ }
3089
+ }
3090
+ const matrix = this.readPdfPageObjectTransformMatrix(formObjectPtr);
3091
+ return {
3092
+ type: PdfPageObjectType.FORM,
3093
+ objects,
3094
+ matrix,
3095
+ };
3096
+ }
3097
+ /**
3098
+ * Read pdf object in pdf page
3099
+ * @param pageObjectPtr - pointer to pdf object in page
3100
+ * @returns pdf object in page
3101
+ *
3102
+ * @private
3103
+ */
3104
+ readPdfPageObjectTransformMatrix(pageObjectPtr) {
3105
+ const matrixPtr = this.malloc(4 * 6);
3106
+ if (this.pdfiumModule.FPDFPageObj_GetMatrix(pageObjectPtr, matrixPtr)) {
3107
+ const a = this.pdfiumModule.pdfium.getValue(matrixPtr, 'float');
3108
+ const b = this.pdfiumModule.pdfium.getValue(matrixPtr + 4, 'float');
3109
+ const c = this.pdfiumModule.pdfium.getValue(matrixPtr + 8, 'float');
3110
+ const d = this.pdfiumModule.pdfium.getValue(matrixPtr + 12, 'float');
3111
+ const e = this.pdfiumModule.pdfium.getValue(matrixPtr + 16, 'float');
3112
+ const f = this.pdfiumModule.pdfium.getValue(matrixPtr + 20, 'float');
3113
+ this.free(matrixPtr);
3114
+ return { a, b, c, d, e, f };
3115
+ }
3116
+ this.free(matrixPtr);
3117
+ return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
3118
+ }
3119
+ /**
3120
+ * Read circle annotation
3121
+ * @param page - pdf page infor
3122
+ * @param pagePtr - pointer to pdf page object
3123
+ * @param annotationPtr - pointer to pdf annotation
3124
+ * @param index - index of annotation in the pdf page
3125
+ * @returns pdf circle annotation
3126
+ *
3127
+ * @private
3128
+ */
3129
+ readPdfCircleAnno(page, pagePtr, annotationPtr, index) {
3130
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
3131
+ const pageRect = this.readPageAnnoRect(annotationPtr);
3132
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3133
+ const author = this.getAnnotString(annotationPtr, 'T');
3134
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3135
+ const modified = this.toIsoDate(modifiedRaw);
3136
+ const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
3137
+ return {
3138
+ status: PdfAnnotationObjectStatus.Committed,
3139
+ pageIndex: page.index,
3140
+ id: index,
3141
+ type: PdfAnnotationSubtype.CIRCLE,
3142
+ rect,
3143
+ popup,
3144
+ appearances,
3145
+ author,
3146
+ modified,
3147
+ };
3148
+ }
3149
+ /**
3150
+ * Read square annotation
3151
+ * @param page - pdf page infor
3152
+ * @param pagePtr - pointer to pdf page object
3153
+ * @param annotationPtr - pointer to pdf annotation
3154
+ * @param index - index of annotation in the pdf page
3155
+ * @returns pdf square annotation
3156
+ *
3157
+ * @private
3158
+ */
3159
+ readPdfSquareAnno(page, pagePtr, annotationPtr, index) {
3160
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
3161
+ const pageRect = this.readPageAnnoRect(annotationPtr);
3162
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3163
+ const author = this.getAnnotString(annotationPtr, 'T');
3164
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3165
+ const modified = this.toIsoDate(modifiedRaw);
3166
+ const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
3167
+ return {
3168
+ status: PdfAnnotationObjectStatus.Committed,
3169
+ pageIndex: page.index,
3170
+ id: index,
3171
+ type: PdfAnnotationSubtype.SQUARE,
3172
+ rect,
3173
+ popup,
3174
+ appearances,
3175
+ author,
3176
+ modified,
3177
+ };
3178
+ }
3179
+ /**
3180
+ * Read basic info of unsupported pdf annotation
3181
+ * @param page - pdf page infor
3182
+ * @param pagePtr - pointer to pdf page object
3183
+ * @param type - type of annotation
3184
+ * @param annotationPtr - pointer to pdf annotation
3185
+ * @param index - index of annotation in the pdf page
3186
+ * @returns pdf annotation
3187
+ *
3188
+ * @private
3189
+ */
3190
+ readPdfAnno(page, pagePtr, type, annotationPtr, index) {
3191
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
3192
+ const pageRect = this.readPageAnnoRect(annotationPtr);
3193
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3194
+ const author = this.getAnnotString(annotationPtr, 'T');
3195
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3196
+ const modified = this.toIsoDate(modifiedRaw);
3197
+ const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
3198
+ return {
3199
+ status: PdfAnnotationObjectStatus.Committed,
3200
+ pageIndex: page.index,
3201
+ id: index,
3202
+ type,
3203
+ rect,
3204
+ popup,
3205
+ appearances,
3206
+ author,
3207
+ modified,
3208
+ };
3209
+ }
3210
+ /**
3211
+ * Resolve `/IRT` → parent-annotation index on the same page.
3212
+ *
3213
+ * @param pagePtr - pointer to FPDF_PAGE
3214
+ * @param annotationPtr - pointer to FPDF_ANNOTATION
3215
+ * @returns index (`0…count-1`) or `undefined` when the annotation is *not* a reply
3216
+ *
3217
+ * @private
3218
+ */
3219
+ getInReplyToId(pagePtr, annotationPtr) {
3220
+ const parentPtr = this.pdfiumModule.FPDFAnnot_GetLinkedAnnot(annotationPtr, 'IRT');
3221
+ if (!parentPtr)
3222
+ return;
3223
+ // PDFium ≥ 5100 – returns −1 when annot not found on page
3224
+ const idx = this.pdfiumModule.FPDFPage_GetAnnotIndex(pagePtr, parentPtr);
3225
+ return idx >= 0 ? idx : undefined;
3226
+ }
3227
+ /**
3228
+ * Parse a PDF date string **D:YYYYMMDDHHmmSSOHH'mm'** to ISO-8601.
3229
+ *
3230
+ * Returns `undefined` if the input is malformed.
3231
+ *
3232
+ * @private
3233
+ */
3234
+ toIsoDate(pdfDate) {
3235
+ if (!pdfDate?.startsWith('D:'))
3236
+ return;
3237
+ // Minimal parse – ignore timezone for brevity
3238
+ const y = pdfDate.substring(2, 6);
3239
+ const m = pdfDate.substring(6, 8) || '01';
3240
+ const d = pdfDate.substring(8, 10) || '01';
3241
+ const H = pdfDate.substring(10, 12) || '00';
3242
+ const M = pdfDate.substring(12, 14) || '00';
3243
+ const S = pdfDate.substring(14, 16) || '00';
3244
+ return `${y}-${m}-${d}T${H}:${M}:${S}`;
3245
+ }
3246
+ /**
3247
+ * Fetch a string value (`/T`, `/M`, `/State`, …) from an annotation.
3248
+ *
3249
+ * @returns decoded UTF-8 string or `undefined` when the key is absent
3250
+ *
3251
+ * @private
3252
+ */
3253
+ getAnnotString(annotationPtr, key) {
3254
+ const len = this.pdfiumModule.FPDFAnnot_GetStringValue(annotationPtr, key, 0, 0);
3255
+ if (len === 0)
3256
+ return;
3257
+ const bytes = (len + 1) * 2;
3258
+ const ptr = this.malloc(bytes);
3259
+ this.pdfiumModule.FPDFAnnot_GetStringValue(annotationPtr, key, ptr, bytes);
3260
+ const value = this.pdfiumModule.pdfium.UTF16ToString(ptr);
3261
+ this.free(ptr);
3262
+ return value || undefined;
3263
+ }
3264
+ /**
3265
+ * Read linked popup of pdf annotation
3266
+ * @param page - pdf page infor
3267
+ * @param pagePtr - pointer to pdf page object
3268
+ * @param annotationPtr - pointer to pdf annotation
3269
+ * @param index - index of annotation in the pdf page
3270
+ * @returns pdf popup linked to annotation
3271
+ *
3272
+ * @private
3273
+ */
3274
+ readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index) {
3275
+ const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
3276
+ const popupAnnotationPtr = this.pdfiumModule.FPDFAnnot_GetLinkedAnnot(annotationPtr, 'Popup');
3277
+ if (!popupAnnotationPtr) {
3278
+ return;
3279
+ }
3280
+ const pageRect = this.readPageAnnoRect(popupAnnotationPtr);
3281
+ const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3282
+ const author = this.getAnnotString(annotationPtr, 'T');
3283
+ const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3284
+ const modified = this.toIsoDate(modifiedRaw);
3285
+ const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
3286
+ const open = this.getAnnotString(annotationPtr, 'Open') || 'false';
3287
+ this.pdfiumModule.FPDFPage_CloseAnnot(popupAnnotationPtr);
3288
+ return {
3289
+ status: PdfAnnotationObjectStatus.Committed,
3290
+ pageIndex: page.index,
3291
+ id: index,
3292
+ type: PdfAnnotationSubtype.POPUP,
3293
+ rect,
3294
+ contents,
3295
+ open: open === 'true',
3296
+ appearances,
3297
+ author,
3298
+ modified,
3299
+ };
3300
+ }
3301
+ /**
3302
+ * Read vertices of pdf annotation
3303
+ * @param page - pdf page infor
3304
+ * @param pagePtr - pointer to pdf page object
3305
+ * @param annotationPtr - pointer to pdf annotation
3306
+ * @returns vertices of pdf annotation
3307
+ *
3308
+ * @private
3309
+ */
3310
+ readPdfAnnoVertices(page, pagePtr, annotationPtr) {
3311
+ const vertices = [];
3312
+ const count = this.pdfiumModule.FPDFAnnot_GetVertices(annotationPtr, 0, 0);
3313
+ const pointMemorySize = 8;
3314
+ const pointsPtr = this.malloc(count * pointMemorySize);
3315
+ this.pdfiumModule.FPDFAnnot_GetVertices(annotationPtr, pointsPtr, count);
3316
+ for (let i = 0; i < count; i++) {
3317
+ const pointX = this.pdfiumModule.pdfium.getValue(pointsPtr + i * pointMemorySize, 'float');
3318
+ const pointY = this.pdfiumModule.pdfium.getValue(pointsPtr + i * pointMemorySize + 4, 'float');
3319
+ const { x, y } = this.convertPagePointToDevicePoint(page, {
3320
+ x: pointX,
3321
+ y: pointY,
3322
+ });
3323
+ vertices.push({
3324
+ x,
3325
+ y,
3326
+ });
3327
+ }
3328
+ this.free(pointsPtr);
3329
+ return vertices;
3330
+ }
3331
+ /**
3332
+ * Read the target of pdf bookmark
3333
+ * @param docPtr - pointer to pdf document object
3334
+ * @param getActionPtr - callback function to retrive the pointer of action
3335
+ * @param getDestinationPtr - callback function to retrive the pointer of destination
3336
+ * @returns target of pdf bookmark
3337
+ *
3338
+ * @private
3339
+ */
3340
+ readPdfBookmarkTarget(docPtr, getActionPtr, getDestinationPtr) {
3341
+ const actionPtr = getActionPtr();
3342
+ if (actionPtr) {
3343
+ const action = this.readPdfAction(docPtr, actionPtr);
3344
+ return {
3345
+ type: 'action',
3346
+ action,
3347
+ };
3348
+ }
3349
+ else {
3350
+ const destinationPtr = getDestinationPtr();
3351
+ if (destinationPtr) {
3352
+ const destination = this.readPdfDestination(docPtr, destinationPtr);
3353
+ return {
3354
+ type: 'destination',
3355
+ destination,
3356
+ };
3357
+ }
3358
+ }
3359
+ }
3360
+ /**
3361
+ * Read field of pdf widget annotation
3362
+ * @param formHandle - form handle
3363
+ * @param annotationPtr - pointer to pdf annotation
3364
+ * @returns field of pdf widget annotation
3365
+ *
3366
+ * @private
3367
+ */
3368
+ readPdfWidgetAnnoField(formHandle, annotationPtr) {
3369
+ const flag = this.pdfiumModule.FPDFAnnot_GetFormFieldFlags(formHandle, annotationPtr);
3370
+ const type = this.pdfiumModule.FPDFAnnot_GetFormFieldType(formHandle, annotationPtr);
3371
+ const name = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3372
+ return this.pdfiumModule.FPDFAnnot_GetFormFieldName(formHandle, annotationPtr, buffer, bufferLength);
3373
+ }, this.pdfiumModule.pdfium.UTF16ToString);
3374
+ const alternateName = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3375
+ return this.pdfiumModule.FPDFAnnot_GetFormFieldAlternateName(formHandle, annotationPtr, buffer, bufferLength);
3376
+ }, this.pdfiumModule.pdfium.UTF16ToString);
3377
+ const value = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3378
+ return this.pdfiumModule.FPDFAnnot_GetFormFieldValue(formHandle, annotationPtr, buffer, bufferLength);
3379
+ }, this.pdfiumModule.pdfium.UTF16ToString);
3380
+ const options = [];
3381
+ if (type === PDF_FORM_FIELD_TYPE.COMBOBOX || type === PDF_FORM_FIELD_TYPE.LISTBOX) {
3382
+ const count = this.pdfiumModule.FPDFAnnot_GetOptionCount(formHandle, annotationPtr);
3383
+ for (let i = 0; i < count; i++) {
3384
+ const label = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3385
+ return this.pdfiumModule.FPDFAnnot_GetOptionLabel(formHandle, annotationPtr, i, buffer, bufferLength);
3386
+ }, this.pdfiumModule.pdfium.UTF16ToString);
3387
+ const isSelected = this.pdfiumModule.FPDFAnnot_IsOptionSelected(formHandle, annotationPtr, i);
3388
+ options.push({
3389
+ label,
3390
+ isSelected,
3391
+ });
3392
+ }
3393
+ }
3394
+ let isChecked = false;
3395
+ if (type === PDF_FORM_FIELD_TYPE.CHECKBOX || type === PDF_FORM_FIELD_TYPE.RADIOBUTTON) {
3396
+ isChecked = this.pdfiumModule.FPDFAnnot_IsChecked(formHandle, annotationPtr);
3397
+ }
3398
+ return {
3399
+ flag,
3400
+ type,
3401
+ name,
3402
+ alternateName,
3403
+ value,
3404
+ isChecked,
3405
+ options,
3406
+ };
3407
+ }
3408
+ /**
3409
+ * render rectangle of pdf page to image
3410
+ * @param docPtr - pointer to pdf document object
3411
+ * @param page - pdf page infor
3412
+ * @param rect - rectangle info
3413
+ * @param scaleFactor - factor of scalling
3414
+ * @param rotation - rotation angle
3415
+ * @param options - render options
3416
+ * @returns image data
3417
+ *
3418
+ * @private
3419
+ */
3420
+ renderPageRectToImageData(ctx, page, rect, scaleFactor, rotation, dpr, options) {
3421
+ const format = BitmapFormat.Bitmap_BGRA;
3422
+ const bytesPerPixel = 4;
3423
+ // Round the transformed dimensions to whole pixels
3424
+ const rectSize = toIntRect(transformRect(page.size, rect, rotation, scaleFactor * dpr));
3425
+ const pageSize = toIntSize(transformSize(page.size, rotation, scaleFactor * dpr));
3426
+ const bitmapHeapLength = rectSize.size.width * rectSize.size.height * bytesPerPixel;
3427
+ const bitmapHeapPtr = this.malloc(bitmapHeapLength);
3428
+ const bitmapPtr = this.pdfiumModule.FPDFBitmap_CreateEx(rectSize.size.width, rectSize.size.height, format, bitmapHeapPtr, rectSize.size.width * bytesPerPixel);
3429
+ this.pdfiumModule.FPDFBitmap_FillRect(bitmapPtr, 0, 0, rectSize.size.width, rectSize.size.height, 0xffffffff);
3430
+ let flags = RenderFlag.REVERSE_BYTE_ORDER;
3431
+ if (options?.withAnnotations) {
3432
+ flags = flags | RenderFlag.ANNOT;
3433
+ }
3434
+ const pageCtx = ctx.acquirePage(page.index);
3435
+ this.pdfiumModule.FPDF_RenderPageBitmap(bitmapPtr, pageCtx.pagePtr, -rectSize.origin.x, -rectSize.origin.y, pageSize.width, pageSize.height, rotation, flags);
3436
+ this.pdfiumModule.FPDFBitmap_Destroy(bitmapPtr);
3437
+ pageCtx.release();
3438
+ const data = this.pdfiumModule.pdfium.HEAPU8.subarray(bitmapHeapPtr, bitmapHeapPtr + bitmapHeapLength);
3439
+ const imageData = {
3440
+ data: new Uint8ClampedArray(data),
3441
+ width: rectSize.size.width,
3442
+ height: rectSize.size.height,
3443
+ };
3444
+ this.free(bitmapHeapPtr);
3445
+ return imageData;
3446
+ }
3447
+ /**
3448
+ * Read the target of pdf link annotation
3449
+ * @param docPtr - pointer to pdf document object
3450
+ * @param getActionPtr - callback function to retrive the pointer of action
3451
+ * @param getDestinationPtr - callback function to retrive the pointer of destination
3452
+ * @returns target of link
3453
+ *
3454
+ * @private
3455
+ */
3456
+ readPdfLinkAnnoTarget(docPtr, getActionPtr, getDestinationPtr) {
3457
+ const destinationPtr = getDestinationPtr();
3458
+ if (destinationPtr) {
3459
+ const destination = this.readPdfDestination(docPtr, destinationPtr);
3460
+ return {
3461
+ type: 'destination',
3462
+ destination,
3463
+ };
3464
+ }
3465
+ else {
3466
+ const actionPtr = getActionPtr();
3467
+ if (actionPtr) {
3468
+ const action = this.readPdfAction(docPtr, actionPtr);
3469
+ return {
3470
+ type: 'action',
3471
+ action,
3472
+ };
3473
+ }
3474
+ }
3475
+ }
3476
+ /**
3477
+ * Read pdf action from pdf document
3478
+ * @param docPtr - pointer to pdf document object
3479
+ * @param actionPtr - pointer to pdf action object
3480
+ * @returns pdf action object
3481
+ *
3482
+ * @private
3483
+ */
3484
+ readPdfAction(docPtr, actionPtr) {
3485
+ const actionType = this.pdfiumModule.FPDFAction_GetType(actionPtr);
3486
+ let action;
3487
+ switch (actionType) {
3488
+ case PdfActionType.Unsupported:
3489
+ action = {
3490
+ type: PdfActionType.Unsupported,
3491
+ };
3492
+ break;
3493
+ case PdfActionType.Goto:
3494
+ {
3495
+ const destinationPtr = this.pdfiumModule.FPDFAction_GetDest(docPtr, actionPtr);
3496
+ if (destinationPtr) {
3497
+ const destination = this.readPdfDestination(docPtr, destinationPtr);
3498
+ action = {
3499
+ type: PdfActionType.Goto,
3500
+ destination,
3501
+ };
3502
+ }
3503
+ else {
3504
+ action = {
3505
+ type: PdfActionType.Unsupported,
3506
+ };
3507
+ }
3508
+ }
3509
+ break;
3510
+ case PdfActionType.RemoteGoto:
3511
+ {
3512
+ // In case of remote goto action,
3513
+ // the application should first use FPDFAction_GetFilePath
3514
+ // to get file path, then load that particular document,
3515
+ // and use its document handle to call this
3516
+ action = {
3517
+ type: PdfActionType.Unsupported,
3518
+ };
3519
+ }
3520
+ break;
3521
+ case PdfActionType.URI:
3522
+ {
3523
+ const uri = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3524
+ return this.pdfiumModule.FPDFAction_GetURIPath(docPtr, actionPtr, buffer, bufferLength);
3525
+ }, this.pdfiumModule.pdfium.UTF8ToString);
3526
+ action = {
3527
+ type: PdfActionType.URI,
3528
+ uri,
3529
+ };
3530
+ }
3531
+ break;
3532
+ case PdfActionType.LaunchAppOrOpenFile:
3533
+ {
3534
+ const path = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3535
+ return this.pdfiumModule.FPDFAction_GetFilePath(actionPtr, buffer, bufferLength);
3536
+ }, this.pdfiumModule.pdfium.UTF8ToString);
3537
+ action = {
3538
+ type: PdfActionType.LaunchAppOrOpenFile,
3539
+ path,
3540
+ };
3541
+ }
3542
+ break;
3543
+ }
3544
+ return action;
3545
+ }
3546
+ /**
3547
+ * Read pdf destination object
3548
+ * @param docPtr - pointer to pdf document object
3549
+ * @param destinationPtr - pointer to pdf destination
3550
+ * @returns pdf destination object
3551
+ *
3552
+ * @private
3553
+ */
3554
+ readPdfDestination(docPtr, destinationPtr) {
3555
+ const pageIndex = this.pdfiumModule.FPDFDest_GetDestPageIndex(docPtr, destinationPtr);
3556
+ // Every params is a float value
3557
+ const maxParmamsCount = 4;
3558
+ const paramsCountPtr = this.malloc(maxParmamsCount);
3559
+ const paramsPtr = this.malloc(maxParmamsCount * 4);
3560
+ const zoomMode = this.pdfiumModule.FPDFDest_GetView(destinationPtr, paramsCountPtr, paramsPtr);
3561
+ const paramsCount = this.pdfiumModule.pdfium.getValue(paramsCountPtr, 'i32');
3562
+ const view = [];
3563
+ for (let i = 0; i < paramsCount; i++) {
3564
+ const paramPtr = paramsPtr + i * 4;
3565
+ view.push(this.pdfiumModule.pdfium.getValue(paramPtr, 'float'));
3566
+ }
3567
+ this.free(paramsCountPtr);
3568
+ this.free(paramsPtr);
3569
+ if (zoomMode === PdfZoomMode.XYZ) {
3570
+ const hasXPtr = this.malloc(1);
3571
+ const hasYPtr = this.malloc(1);
3572
+ const hasZPtr = this.malloc(1);
3573
+ const xPtr = this.malloc(4);
3574
+ const yPtr = this.malloc(4);
3575
+ const zPtr = this.malloc(4);
3576
+ const isSucceed = this.pdfiumModule.FPDFDest_GetLocationInPage(destinationPtr, hasXPtr, hasYPtr, hasZPtr, xPtr, yPtr, zPtr);
3577
+ if (isSucceed) {
3578
+ const hasX = this.pdfiumModule.pdfium.getValue(hasXPtr, 'i8');
3579
+ const hasY = this.pdfiumModule.pdfium.getValue(hasYPtr, 'i8');
3580
+ const hasZ = this.pdfiumModule.pdfium.getValue(hasZPtr, 'i8');
3581
+ const x = hasX ? this.pdfiumModule.pdfium.getValue(xPtr, 'float') : 0;
3582
+ const y = hasY ? this.pdfiumModule.pdfium.getValue(yPtr, 'float') : 0;
3583
+ const zoom = hasZ ? this.pdfiumModule.pdfium.getValue(zPtr, 'float') : 0;
3584
+ this.free(hasXPtr);
3585
+ this.free(hasYPtr);
3586
+ this.free(hasZPtr);
3587
+ this.free(xPtr);
3588
+ this.free(yPtr);
3589
+ this.free(zPtr);
3590
+ return {
3591
+ pageIndex,
3592
+ zoom: {
3593
+ mode: zoomMode,
3594
+ params: {
3595
+ x,
3596
+ y,
3597
+ zoom,
3598
+ },
3599
+ },
3600
+ view,
3601
+ };
3602
+ }
3603
+ this.free(hasXPtr);
3604
+ this.free(hasYPtr);
3605
+ this.free(hasZPtr);
3606
+ this.free(xPtr);
3607
+ this.free(yPtr);
3608
+ this.free(zPtr);
3609
+ return {
3610
+ pageIndex,
3611
+ zoom: {
3612
+ mode: zoomMode,
3613
+ params: {
3614
+ x: 0,
3615
+ y: 0,
3616
+ zoom: 0,
3617
+ },
3618
+ },
3619
+ view,
3620
+ };
3621
+ }
3622
+ return {
3623
+ pageIndex,
3624
+ zoom: {
3625
+ mode: zoomMode,
3626
+ },
3627
+ view,
3628
+ };
3629
+ }
3630
+ /**
3631
+ * Read attachmet from pdf document
3632
+ * @param docPtr - pointer to pdf document object
3633
+ * @param index - index of attachment
3634
+ * @returns attachment content
3635
+ *
3636
+ * @private
3637
+ */
3638
+ readPdfAttachment(docPtr, index) {
3639
+ const attachmentPtr = this.pdfiumModule.FPDFDoc_GetAttachment(docPtr, index);
3640
+ const name = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3641
+ return this.pdfiumModule.FPDFAttachment_GetName(attachmentPtr, buffer, bufferLength);
3642
+ }, this.pdfiumModule.pdfium.UTF16ToString);
3643
+ const creationDate = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3644
+ return this.pdfiumModule.FPDFAttachment_GetStringValue(attachmentPtr, 'CreationDate', buffer, bufferLength);
3645
+ }, this.pdfiumModule.pdfium.UTF16ToString);
3646
+ const checksum = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3647
+ return this.pdfiumModule.FPDFAttachment_GetStringValue(attachmentPtr, 'Checksum', buffer, bufferLength);
3648
+ }, this.pdfiumModule.pdfium.UTF16ToString);
3649
+ return {
3650
+ index,
3651
+ name,
3652
+ creationDate,
3653
+ checksum,
3654
+ };
3655
+ }
3656
+ /**
3657
+ * Convert coordinate of point from device coordinate to page coordinate
3658
+ * @param page - pdf page infor
3659
+ * @param position - position of point
3660
+ * @returns converted position
3661
+ *
3662
+ * @private
3663
+ */
3664
+ convertDevicePointToPagePoint(page, position) {
3665
+ const x = position.x;
3666
+ const y = page.size.height - position.y;
3667
+ return { x, y };
3668
+ }
3669
+ /**
3670
+ * Convert coordinate of point from page coordinate to device coordinate
3671
+ * @param page - pdf page infor
3672
+ * @param position - position of point
3673
+ * @returns converted position
3674
+ *
3675
+ * @private
3676
+ */
3677
+ convertPagePointToDevicePoint(page, position) {
3678
+ const x = position.x;
3679
+ const y = page.size.height - position.y;
3680
+ return { x, y };
3681
+ }
3682
+ /**
3683
+ * Convert coordinate of rectangle from page coordinate to device coordinate
3684
+ * @param page - pdf page infor
3685
+ * @param pagePtr - pointer to pdf page object
3686
+ * @param pageRect - rectangle that needs to be converted
3687
+ * @returns converted rectangle
3688
+ *
3689
+ * @private
3690
+ */
3691
+ convertPageRectToDeviceRect(page, pagePtr, pageRect) {
3692
+ const { x, y } = this.convertPagePointToDevicePoint(page, {
3693
+ x: pageRect.left,
3694
+ y: pageRect.top,
3695
+ });
3696
+ const rect = {
3697
+ origin: {
3698
+ x,
3699
+ y,
3700
+ },
3701
+ size: {
3702
+ width: Math.abs(pageRect.right - pageRect.left),
3703
+ height: Math.abs(pageRect.top - pageRect.bottom),
3704
+ },
3705
+ };
3706
+ return rect;
3707
+ }
3708
+ /**
3709
+ * Read the appearance stream of annotation
3710
+ * @param annotationPtr - pointer to pdf annotation
3711
+ * @param mode - appearance mode
3712
+ * @returns appearance stream
3713
+ *
3714
+ * @private
3715
+ */
3716
+ readPageAnnoAppearanceStreams(annotationPtr) {
3717
+ return {
3718
+ normal: this.readPageAnnoAppearanceStream(annotationPtr, AppearanceMode.Normal),
3719
+ rollover: this.readPageAnnoAppearanceStream(annotationPtr, AppearanceMode.Rollover),
3720
+ down: this.readPageAnnoAppearanceStream(annotationPtr, AppearanceMode.Down),
3721
+ };
3722
+ }
3723
+ /**
3724
+ * Read the appearance stream of annotation
3725
+ * @param annotationPtr - pointer to pdf annotation
3726
+ * @param mode - appearance mode
3727
+ * @returns appearance stream
3728
+ *
3729
+ * @private
3730
+ */
3731
+ readPageAnnoAppearanceStream(annotationPtr, mode = AppearanceMode.Normal) {
3732
+ const utf16Length = this.pdfiumModule.FPDFAnnot_GetAP(annotationPtr, mode, 0, 0);
3733
+ const bytesCount = (utf16Length + 1) * 2; // include NIL
3734
+ const bufferPtr = this.malloc(bytesCount);
3735
+ this.pdfiumModule.FPDFAnnot_GetAP(annotationPtr, mode, bufferPtr, bytesCount);
3736
+ const ap = this.pdfiumModule.pdfium.UTF16ToString(bufferPtr);
3737
+ this.free(bufferPtr);
3738
+ return ap;
3739
+ }
3740
+ /**
3741
+ * Set the rect of specified annotation
3742
+ * @param page - page info that the annotation is belonged to
3743
+ * @param pagePtr - pointer of page object
3744
+ * @param annotationPtr - pointer to annotation object
3745
+ * @param rect - target rectangle
3746
+ * @returns whether the rect is setted
3747
+ *
3748
+ * @private
3749
+ */
3750
+ setPageAnnoRect(page, pagePtr, annotationPtr, rect) {
3751
+ const pageXPtr = this.malloc(8);
3752
+ const pageYPtr = this.malloc(8);
3753
+ if (!this.pdfiumModule.FPDF_DeviceToPage(pagePtr, 0, 0, page.size.width, page.size.height, 0, rect.origin.x, rect.origin.y, pageXPtr, pageYPtr)) {
3754
+ this.free(pageXPtr);
3755
+ this.free(pageYPtr);
3756
+ return false;
3757
+ }
3758
+ const pageX = this.pdfiumModule.pdfium.getValue(pageXPtr, 'double');
3759
+ const pageY = this.pdfiumModule.pdfium.getValue(pageYPtr, 'double');
3760
+ this.free(pageXPtr);
3761
+ this.free(pageYPtr);
3762
+ const pageRectPtr = this.malloc(4 * 4);
3763
+ this.pdfiumModule.pdfium.setValue(pageRectPtr, pageX, 'float');
3764
+ this.pdfiumModule.pdfium.setValue(pageRectPtr + 4, pageY, 'float');
3765
+ this.pdfiumModule.pdfium.setValue(pageRectPtr + 8, pageX + rect.size.width, 'float');
3766
+ this.pdfiumModule.pdfium.setValue(pageRectPtr + 12, pageY - rect.size.height, 'float');
3767
+ if (!this.pdfiumModule.FPDFAnnot_SetRect(annotationPtr, pageRectPtr)) {
3768
+ this.free(pageRectPtr);
3769
+ return false;
3770
+ }
3771
+ this.free(pageRectPtr);
3772
+ return true;
3773
+ }
3774
+ /**
3775
+ * Read the rectangle of annotation
3776
+ * @param annotationPtr - pointer to pdf annotation
3777
+ * @returns rectangle of annotation
3778
+ *
3779
+ * @private
3780
+ */
3781
+ readPageAnnoRect(annotationPtr) {
3782
+ const pageRectPtr = this.malloc(4 * 4);
3783
+ const pageRect = {
3784
+ left: 0,
3785
+ top: 0,
3786
+ right: 0,
3787
+ bottom: 0,
3788
+ };
3789
+ if (this.pdfiumModule.FPDFAnnot_GetRect(annotationPtr, pageRectPtr)) {
3790
+ pageRect.left = this.pdfiumModule.pdfium.getValue(pageRectPtr, 'float');
3791
+ pageRect.top = this.pdfiumModule.pdfium.getValue(pageRectPtr + 4, 'float');
3792
+ pageRect.right = this.pdfiumModule.pdfium.getValue(pageRectPtr + 8, 'float');
3793
+ pageRect.bottom = this.pdfiumModule.pdfium.getValue(pageRectPtr + 12, 'float');
3794
+ }
3795
+ this.free(pageRectPtr);
3796
+ return pageRect;
3797
+ }
3798
+ /**
3799
+ * Get highlight rects for a specific character range (for search highlighting)
3800
+ * @param page - pdf page info
3801
+ * @param pagePtr - pointer to pdf page
3802
+ * @param textPagePtr - pointer to pdf text page
3803
+ * @param startIndex - starting character index
3804
+ * @param charCount - number of characters in the range
3805
+ * @returns array of rectangles for highlighting the specified character range
3806
+ *
3807
+ * @private
3808
+ */
3809
+ getHighlightRects(page, pagePtr, textPagePtr, startIndex, charCount) {
3810
+ const rectsCount = this.pdfiumModule.FPDFText_CountRects(textPagePtr, startIndex, charCount);
3811
+ const highlightRects = [];
3812
+ for (let i = 0; i < rectsCount; i++) {
3813
+ const topPtr = this.malloc(8);
3814
+ const leftPtr = this.malloc(8);
3815
+ const rightPtr = this.malloc(8);
3816
+ const bottomPtr = this.malloc(8);
3817
+ const isSucceed = this.pdfiumModule.FPDFText_GetRect(textPagePtr, i, leftPtr, topPtr, rightPtr, bottomPtr);
3818
+ if (!isSucceed) {
3819
+ this.free(leftPtr);
3820
+ this.free(topPtr);
3821
+ this.free(rightPtr);
3822
+ this.free(bottomPtr);
3823
+ continue;
3824
+ }
3825
+ const left = this.pdfiumModule.pdfium.getValue(leftPtr, 'double');
3826
+ const top = this.pdfiumModule.pdfium.getValue(topPtr, 'double');
3827
+ const right = this.pdfiumModule.pdfium.getValue(rightPtr, 'double');
3828
+ const bottom = this.pdfiumModule.pdfium.getValue(bottomPtr, 'double');
3829
+ this.free(leftPtr);
3830
+ this.free(topPtr);
3831
+ this.free(rightPtr);
3832
+ this.free(bottomPtr);
3833
+ const deviceXPtr = this.malloc(4);
3834
+ const deviceYPtr = this.malloc(4);
3835
+ this.pdfiumModule.FPDF_PageToDevice(pagePtr, 0, 0, page.size.width, page.size.height, 0, left, top, deviceXPtr, deviceYPtr);
3836
+ const x = this.pdfiumModule.pdfium.getValue(deviceXPtr, 'i32');
3837
+ const y = this.pdfiumModule.pdfium.getValue(deviceYPtr, 'i32');
3838
+ this.free(deviceXPtr);
3839
+ this.free(deviceYPtr);
3840
+ // Convert the bottom-right coordinates to width/height
3841
+ const width = Math.ceil(Math.abs(right - left));
3842
+ const height = Math.ceil(Math.abs(top - bottom));
3843
+ highlightRects.push({
3844
+ origin: { x, y },
3845
+ size: { width, height },
3846
+ });
3847
+ }
3848
+ return highlightRects;
3849
+ }
3850
+ /**
3851
+ * Search for a keyword across all pages in the document
3852
+ * Returns all search results throughout the entire document
3853
+ *
3854
+ * @param doc - Pdf document object
3855
+ * @param keyword - Search keyword
3856
+ * @param flags - Match flags for search
3857
+ * @returns Promise of all search results in the document
3858
+ *
3859
+ * @public
3860
+ */
3861
+ searchAllPages(doc, keyword, flags = []) {
3862
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'searchAllPages', doc, keyword, flags);
3863
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `SearchAllPages`, 'Begin', doc.id);
3864
+ const ctx = this.cache.getContext(doc.id);
3865
+ if (!ctx) {
3866
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `SearchAllPages`, 'End', doc.id);
3867
+ return PdfTaskHelper.resolve({ results: [], total: 0 });
3868
+ }
3869
+ const length = 2 * (keyword.length + 1);
3870
+ const keywordPtr = this.malloc(length);
3871
+ this.pdfiumModule.pdfium.stringToUTF16(keyword, keywordPtr, length);
3872
+ const flag = flags.reduce((flag, currFlag) => {
3873
+ return flag | currFlag;
3874
+ }, MatchFlag.None);
3875
+ const results = [];
3876
+ // Search through all pages
3877
+ const searchAllPagesTask = PdfTaskHelper.create();
3878
+ // Execute search in a separate function to avoid issues with resolve parameter
3879
+ const executeSearch = async () => {
3880
+ for (let pageIndex = 0; pageIndex < doc.pageCount; pageIndex++) {
3881
+ // Get all results for the current page efficiently (load page only once)
3882
+ const pageResults = this.searchAllInPage(ctx, doc.pages[pageIndex], keywordPtr, flag);
3883
+ results.push(...pageResults);
3884
+ }
3885
+ this.free(keywordPtr);
3886
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `SearchAllPages`, 'End', doc.id);
3887
+ searchAllPagesTask.resolve({
3888
+ results,
3889
+ total: results.length,
3890
+ });
3891
+ };
3892
+ // Start the search process
3893
+ executeSearch().catch((error) => {
3894
+ this.free(keywordPtr);
3895
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `SearchAllPages`, 'End', doc.id);
3896
+ searchAllPagesTask.reject({
3897
+ code: PdfErrorCode.Unknown,
3898
+ message: `Error searching document: ${error}`,
3899
+ });
3900
+ });
3901
+ return searchAllPagesTask;
3902
+ }
3903
+ /**
3904
+ * Extract word-aligned context for a search hit.
3905
+ *
3906
+ * @param fullText full UTF-16 page text (fetch this once per page!)
3907
+ * @param start index of 1st char that matched
3908
+ * @param count number of chars in the match
3909
+ * @param windowChars minimum context chars to keep left & right
3910
+ */
3911
+ buildContext(fullText, start, count, windowChars = 30) {
3912
+ const WORD_BREAK = /[\s\u00A0.,;:!?()\[\]{}<>/\\\-"'`"”\u2013\u2014]/;
3913
+ // Find the start of a word moving left
3914
+ const findWordStart = (index) => {
3915
+ while (index > 0 && !WORD_BREAK.test(fullText[index - 1]))
3916
+ index--;
3917
+ return index;
3918
+ };
3919
+ // Find the end of a word moving right
3920
+ const findWordEnd = (index) => {
3921
+ while (index < fullText.length && !WORD_BREAK.test(fullText[index]))
3922
+ index++;
3923
+ return index;
3924
+ };
3925
+ // Move left to build context
3926
+ let left = start;
3927
+ while (left > 0 && WORD_BREAK.test(fullText[left - 1]))
3928
+ left--; // Skip blanks
3929
+ let collected = 0;
3930
+ while (left > 0 && collected < windowChars) {
3931
+ left--;
3932
+ if (!WORD_BREAK.test(fullText[left]))
3933
+ collected++;
3934
+ }
3935
+ left = findWordStart(left);
3936
+ // Move right to build context
3937
+ let right = start + count;
3938
+ while (right < fullText.length && WORD_BREAK.test(fullText[right]))
3939
+ right++; // Skip blanks
3940
+ collected = 0;
3941
+ while (right < fullText.length && collected < windowChars) {
3942
+ if (!WORD_BREAK.test(fullText[right]))
3943
+ collected++;
3944
+ right++;
3945
+ }
3946
+ right = findWordEnd(right);
3947
+ // Compose the context
3948
+ const before = fullText.slice(left, start).replace(/\s+/g, ' ').trimStart();
3949
+ const match = fullText.slice(start, start + count);
3950
+ const after = fullText
3951
+ .slice(start + count, right)
3952
+ .replace(/\s+/g, ' ')
3953
+ .trimEnd();
3954
+ return {
3955
+ before: this.tidy(before),
3956
+ match: this.tidy(match),
3957
+ after: this.tidy(after),
3958
+ truncatedLeft: left > 0,
3959
+ truncatedRight: right < fullText.length,
3960
+ };
3961
+ }
3962
+ /**
3963
+ * Tidy the text to remove any non-printable characters and whitespace
3964
+ * @param s - text to tidy
3965
+ * @returns tidied text
3966
+ *
3967
+ * @private
3968
+ */
3969
+ tidy(s) {
3970
+ return (s
3971
+ /* 1️⃣ join words split by hyphen + U+FFFE + whitespace */
3972
+ .replace(/-\uFFFE\s*/g, '')
3973
+ /* 2️⃣ drop any remaining U+FFFE, soft-hyphen, zero-width chars */
3974
+ .replace(/[\uFFFE\u00AD\u200B\u2060\uFEFF]/g, '')
3975
+ /* 3️⃣ collapse whitespace so we stay on one line */
3976
+ .replace(/\s+/g, ' '));
3977
+ }
3978
+ /**
3979
+ * Search for all occurrences of a keyword on a single page
3980
+ * This method efficiently loads the page only once and finds all matches
3981
+ *
3982
+ * @param docPtr - pointer to pdf document
3983
+ * @param page - pdf page object
3984
+ * @param pageIndex - index of the page
3985
+ * @param keywordPtr - pointer to the search keyword
3986
+ * @param flag - search flags
3987
+ * @returns array of search results on this page
3988
+ *
3989
+ * @private
3990
+ */
3991
+ searchAllInPage(ctx, page, keywordPtr, flag) {
3992
+ const pageIndex = page.index;
3993
+ // Load the page and text page only once
3994
+ const pageCtx = ctx.acquirePage(pageIndex);
3995
+ const textPagePtr = pageCtx.getTextPage();
3996
+ // Load the full text of the page once
3997
+ const total = this.pdfiumModule.FPDFText_CountChars(textPagePtr);
3998
+ const bufPtr = this.malloc(2 * (total + 1));
3999
+ this.pdfiumModule.FPDFText_GetText(textPagePtr, 0, total, bufPtr);
4000
+ const fullText = this.pdfiumModule.pdfium.UTF16ToString(bufPtr);
4001
+ this.free(bufPtr);
4002
+ const pageResults = [];
4003
+ // Initialize search handle once for the page
4004
+ const searchHandle = this.pdfiumModule.FPDFText_FindStart(textPagePtr, keywordPtr, flag, 0);
4005
+ // Call FindNext repeatedly to get all matches on the page
4006
+ while (this.pdfiumModule.FPDFText_FindNext(searchHandle)) {
4007
+ const charIndex = this.pdfiumModule.FPDFText_GetSchResultIndex(searchHandle);
4008
+ const charCount = this.pdfiumModule.FPDFText_GetSchCount(searchHandle);
4009
+ const rects = this.getHighlightRects(page, pageCtx.pagePtr, textPagePtr, charIndex, charCount);
4010
+ const context = this.buildContext(fullText, charIndex, charCount);
4011
+ pageResults.push({
4012
+ pageIndex,
4013
+ charIndex,
4014
+ charCount,
4015
+ rects,
4016
+ context,
4017
+ });
4018
+ }
4019
+ // Close the search handle only once after finding all results
4020
+ this.pdfiumModule.FPDFText_FindClose(searchHandle);
4021
+ // Close the text page and page only once
4022
+ pageCtx.release();
4023
+ return pageResults;
4024
+ }
4025
+ }
4026
+
4027
+ const LOG_SOURCE = 'WebWorkerEngineRunner';
4028
+ const LOG_CATEGORY = 'Engine';
4029
+ /**
4030
+ * Pdf engine runner, it will execute pdf engine based on the request it received and
4031
+ * send back the response with post message
4032
+ */
4033
+ class EngineRunner {
4034
+ /**
4035
+ * Create instance of EngineRunnder
4036
+ * @param logger - logger instance
4037
+ */
4038
+ constructor(logger = new NoopLogger()) {
4039
+ this.logger = logger;
4040
+ /**
4041
+ * Execute the request
4042
+ * @param request - request that represent the pdf engine call
4043
+ * @returns
4044
+ *
4045
+ * @protected
4046
+ */
4047
+ this.execute = (request) => {
4048
+ this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'runner start exeucte request');
4049
+ if (!this.engine) {
4050
+ const error = {
4051
+ type: 'reject',
4052
+ reason: {
4053
+ code: PdfErrorCode.NotReady,
4054
+ message: 'engine has not started yet',
4055
+ },
4056
+ };
4057
+ const response = {
4058
+ id: request.id,
4059
+ type: 'ExecuteResponse',
4060
+ data: {
4061
+ type: 'error',
4062
+ value: error,
4063
+ },
4064
+ };
4065
+ this.respond(response);
4066
+ return;
4067
+ }
4068
+ const engine = this.engine;
4069
+ const { name, args } = request.data;
4070
+ if (!engine[name]) {
4071
+ const error = {
4072
+ type: 'reject',
4073
+ reason: {
4074
+ code: PdfErrorCode.NotSupport,
4075
+ message: 'engine method has not supported yet',
4076
+ },
4077
+ };
4078
+ const response = {
4079
+ id: request.id,
4080
+ type: 'ExecuteResponse',
4081
+ data: {
4082
+ type: 'error',
4083
+ value: error,
4084
+ },
4085
+ };
4086
+ this.respond(response);
4087
+ return;
4088
+ }
4089
+ let task;
4090
+ switch (name) {
4091
+ case 'isSupport':
4092
+ task = this.engine[name](...args);
4093
+ break;
4094
+ case 'initialize':
4095
+ task = this.engine[name](...args);
4096
+ break;
4097
+ case 'destroy':
4098
+ task = this.engine[name](...args);
4099
+ break;
4100
+ case 'openDocumentUrl':
4101
+ task = this.engine[name](...args);
4102
+ break;
4103
+ case 'openDocumentFromBuffer':
4104
+ task = this.engine[name](...args);
4105
+ break;
4106
+ case 'openDocumentFromLoader':
4107
+ task = this.engine[name](...args);
4108
+ break;
4109
+ case 'getDocPermissions':
4110
+ task = this.engine[name](...args);
4111
+ break;
4112
+ case 'getDocUserPermissions':
4113
+ task = this.engine[name](...args);
4114
+ break;
4115
+ case 'getMetadata':
4116
+ task = this.engine[name](...args);
4117
+ break;
4118
+ case 'getBookmarks':
4119
+ task = this.engine[name](...args);
4120
+ break;
4121
+ case 'getSignatures':
4122
+ task = this.engine[name](...args);
4123
+ break;
4124
+ case 'renderPage':
4125
+ task = this.engine[name](...args);
4126
+ break;
4127
+ case 'renderPageRect':
4128
+ task = this.engine[name](...args);
4129
+ break;
4130
+ case 'renderThumbnail':
4131
+ task = this.engine[name](...args);
4132
+ break;
4133
+ case 'getAllAnnotations':
4134
+ task = this.engine[name](...args);
4135
+ break;
4136
+ case 'getPageAnnotations':
4137
+ task = this.engine[name](...args);
4138
+ break;
4139
+ case 'createPageAnnotation':
4140
+ task = this.engine[name](...args);
4141
+ break;
4142
+ case 'transformPageAnnotation':
4143
+ task = this.engine[name](...args);
4144
+ break;
4145
+ case 'removePageAnnotation':
4146
+ task = this.engine[name](...args);
4147
+ break;
4148
+ case 'getPageTextRects':
4149
+ task = this.engine[name](...args);
4150
+ break;
4151
+ case 'searchAllPages':
4152
+ task = this.engine[name](...args);
4153
+ break;
4154
+ case 'closeDocument':
4155
+ task = this.engine[name](...args);
4156
+ break;
4157
+ case 'saveAsCopy':
4158
+ task = this.engine[name](...args);
4159
+ break;
4160
+ case 'getAttachments':
4161
+ task = this.engine[name](...args);
4162
+ break;
4163
+ case 'readAttachmentContent':
4164
+ task = this.engine[name](...args);
4165
+ break;
4166
+ case 'setFormFieldValue':
4167
+ task = this.engine[name](...args);
4168
+ break;
4169
+ case 'flattenPage':
4170
+ task = this.engine[name](...args);
4171
+ break;
4172
+ case 'extractPages':
4173
+ task = this.engine[name](...args);
4174
+ break;
4175
+ case 'extractText':
4176
+ task = this.engine[name](...args);
4177
+ break;
4178
+ case 'getPageGlyphs':
4179
+ task = this.engine[name](...args);
4180
+ break;
4181
+ case 'getPageGeometry':
4182
+ task = this.engine[name](...args);
4183
+ break;
4184
+ case 'merge':
4185
+ task = this.engine[name](...args);
4186
+ break;
4187
+ case 'mergePages':
4188
+ task = this.engine[name](...args);
4189
+ break;
4190
+ }
4191
+ task.wait((result) => {
4192
+ const response = {
4193
+ id: request.id,
4194
+ type: 'ExecuteResponse',
4195
+ data: {
4196
+ type: 'result',
4197
+ value: result,
4198
+ },
4199
+ };
4200
+ this.respond(response);
4201
+ }, (error) => {
4202
+ const response = {
4203
+ id: request.id,
4204
+ type: 'ExecuteResponse',
4205
+ data: {
4206
+ type: 'error',
4207
+ value: error,
4208
+ },
4209
+ };
4210
+ this.respond(response);
4211
+ });
4212
+ };
4213
+ }
4214
+ /**
4215
+ * Listening on post message
4216
+ */
4217
+ listen() {
4218
+ self.onmessage = (evt) => {
4219
+ return this.handle(evt);
4220
+ };
4221
+ }
4222
+ /**
4223
+ * Handle post message
4224
+ */
4225
+ handle(evt) {
4226
+ this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'webworker receive message event: ', evt.data);
4227
+ try {
4228
+ const request = evt.data;
4229
+ switch (request.type) {
4230
+ case 'ExecuteRequest':
4231
+ this.execute(request);
4232
+ break;
4233
+ }
4234
+ }
4235
+ catch (e) {
4236
+ this.logger.info(LOG_SOURCE, LOG_CATEGORY, 'webworker met error when processing message event:', e);
4237
+ }
4238
+ }
4239
+ /**
4240
+ * Send the ready response when pdf engine is ready
4241
+ * @returns
4242
+ *
4243
+ * @protected
4244
+ */
4245
+ ready() {
4246
+ this.listen();
4247
+ this.respond({
4248
+ id: '0',
4249
+ type: 'ReadyResponse',
4250
+ });
4251
+ this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'runner is ready');
4252
+ }
4253
+ /**
4254
+ * Send back the response
4255
+ * @param response - response that needs sent back
4256
+ *
4257
+ * @protected
4258
+ */
4259
+ respond(response) {
4260
+ this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'runner respond: ', response);
4261
+ self.postMessage(response);
4262
+ }
4263
+ }
4264
+
4265
+ /**
4266
+ * EngineRunner for pdfium-based wasm engine
4267
+ */
4268
+ class PdfiumEngineRunner extends EngineRunner {
4269
+ /**
4270
+ * Create an instance of PdfiumEngineRunner
4271
+ * @param wasmBinary - wasm binary that contains the pdfium wasm file
4272
+ */
4273
+ constructor(wasmBinary) {
4274
+ super();
4275
+ this.wasmBinary = wasmBinary;
4276
+ }
4277
+ /**
4278
+ * Initialize runner
4279
+ */
4280
+ async prepare() {
4281
+ const wasmBinary = this.wasmBinary;
4282
+ const wasmModule = await init({ wasmBinary });
4283
+ this.engine = new PdfiumEngine(wasmModule);
4284
+ this.ready();
4285
+ }
4286
+ }
4287
+
4288
+ export { BitmapFormat, PdfiumEngine, PdfiumEngineRunner, PdfiumErrorCode, RenderFlag, browserImageDataToBlobConverter, readArrayBuffer, readString };
4289
+ //# sourceMappingURL=pdfium.js.map