@embedpdf/engines 1.0.10 → 1.0.12

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 (104) hide show
  1. package/dist/engine-B7CS6Qyp.cjs +2 -0
  2. package/dist/engine-B7CS6Qyp.cjs.map +1 -0
  3. package/dist/engine-M0_XZhss.js +5043 -0
  4. package/dist/engine-M0_XZhss.js.map +1 -0
  5. package/dist/index.cjs +1 -5846
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.ts +1 -1613
  8. package/dist/index.js +339 -5824
  9. package/dist/index.js.map +1 -1
  10. package/dist/lib/converters/index.cjs +2 -0
  11. package/dist/lib/converters/index.cjs.map +1 -0
  12. package/dist/{converters.d.ts → lib/converters/index.d.ts} +7 -10
  13. package/dist/lib/converters/index.js +80 -0
  14. package/dist/{converters.cjs.map → lib/converters/index.js.map} +1 -1
  15. package/dist/lib/index.d.ts +7 -0
  16. package/dist/lib/mock/index.d.ts +25 -0
  17. package/dist/lib/pdfium/cache.d.ts +62 -0
  18. package/dist/{pdfium-direct-engine.d.ts → lib/pdfium/engine.d.ts} +73 -8
  19. package/dist/lib/pdfium/helper.d.ts +21 -0
  20. package/dist/lib/pdfium/index.cjs +2 -0
  21. package/dist/lib/pdfium/index.cjs.map +1 -0
  22. package/dist/lib/pdfium/index.d.ts +3 -0
  23. package/dist/lib/pdfium/index.js +13 -0
  24. package/dist/lib/pdfium/index.js.map +1 -0
  25. package/dist/lib/pdfium/runner.d.ts +16 -0
  26. package/dist/lib/pdfium/web/direct-engine.cjs +2 -0
  27. package/dist/lib/pdfium/web/direct-engine.cjs.map +1 -0
  28. package/dist/lib/pdfium/web/direct-engine.d.ts +3 -0
  29. package/dist/lib/pdfium/web/direct-engine.js +12 -0
  30. package/dist/lib/pdfium/web/direct-engine.js.map +1 -0
  31. package/dist/lib/pdfium/web/worker-engine.cjs +2 -0
  32. package/dist/lib/pdfium/web/worker-engine.cjs.map +1 -0
  33. package/dist/lib/pdfium/web/worker-engine.d.ts +7 -0
  34. package/dist/lib/pdfium/web/worker-engine.js +15 -0
  35. package/dist/lib/pdfium/web/worker-engine.js.map +1 -0
  36. package/dist/lib/pdfium/worker.d.ts +1 -0
  37. package/dist/lib/pdfium-engine.d.ts +1 -0
  38. package/dist/lib/webworker/engine.cjs +2 -0
  39. package/dist/lib/webworker/engine.cjs.map +1 -0
  40. package/dist/{worker.d.ts → lib/webworker/engine.d.ts} +4 -34
  41. package/dist/lib/webworker/engine.js +887 -0
  42. package/dist/lib/webworker/engine.js.map +1 -0
  43. package/dist/lib/webworker/index.d.ts +2 -0
  44. package/dist/lib/webworker/runner.d.ts +128 -0
  45. package/dist/lib/webworker-engine.d.ts +1 -0
  46. package/dist/preact/adapter.d.ts +1 -0
  47. package/dist/preact/index.cjs +2 -0
  48. package/dist/preact/index.cjs.map +1 -0
  49. package/dist/preact/index.d.ts +1 -0
  50. package/dist/preact/index.js +37 -0
  51. package/dist/preact/index.js.map +1 -0
  52. package/dist/react/adapter.d.ts +1 -0
  53. package/dist/react/index.cjs +2 -0
  54. package/dist/react/index.cjs.map +1 -0
  55. package/dist/react/index.d.ts +1 -0
  56. package/dist/react/index.js +37 -0
  57. package/dist/react/index.js.map +1 -0
  58. package/dist/runner-BcS-WEof.cjs +2 -0
  59. package/dist/runner-BcS-WEof.cjs.map +1 -0
  60. package/dist/runner-DUp_7Uu_.js +269 -0
  61. package/dist/runner-DUp_7Uu_.js.map +1 -0
  62. package/dist/shared-preact/hooks/index.d.ts +1 -0
  63. package/dist/{preact.d.ts → shared-preact/hooks/use-pdfium-engine.d.ts} +3 -5
  64. package/dist/shared-preact/index.d.ts +1 -0
  65. package/dist/shared-react/hooks/index.d.ts +1 -0
  66. package/dist/{react.d.ts → shared-react/hooks/use-pdfium-engine.d.ts} +3 -5
  67. package/dist/shared-react/index.d.ts +1 -0
  68. package/dist/vue/hooks/index.d.ts +1 -0
  69. package/dist/vue/hooks/use-pdfium-engine.d.ts +18 -0
  70. package/dist/vue/index.cjs +2 -0
  71. package/dist/vue/index.cjs.map +1 -0
  72. package/dist/vue/index.d.ts +1 -0
  73. package/dist/vue/index.js +39 -0
  74. package/dist/vue/index.js.map +1 -0
  75. package/package.json +41 -41
  76. package/dist/converters.cjs +0 -139
  77. package/dist/converters.js +0 -133
  78. package/dist/converters.js.map +0 -1
  79. package/dist/pdfium-direct-engine.cjs +0 -4400
  80. package/dist/pdfium-direct-engine.cjs.map +0 -1
  81. package/dist/pdfium-direct-engine.js +0 -4398
  82. package/dist/pdfium-direct-engine.js.map +0 -1
  83. package/dist/pdfium-worker-engine.cjs +0 -851
  84. package/dist/pdfium-worker-engine.cjs.map +0 -1
  85. package/dist/pdfium-worker-engine.d.ts +0 -314
  86. package/dist/pdfium-worker-engine.js +0 -849
  87. package/dist/pdfium-worker-engine.js.map +0 -1
  88. package/dist/pdfium.cjs +0 -4667
  89. package/dist/pdfium.cjs.map +0 -1
  90. package/dist/pdfium.d.ts +0 -1311
  91. package/dist/pdfium.js +0 -4661
  92. package/dist/pdfium.js.map +0 -1
  93. package/dist/preact.cjs +0 -41
  94. package/dist/preact.cjs.map +0 -1
  95. package/dist/preact.js +0 -39
  96. package/dist/preact.js.map +0 -1
  97. package/dist/react.cjs +0 -41
  98. package/dist/react.cjs.map +0 -1
  99. package/dist/react.js +0 -39
  100. package/dist/react.js.map +0 -1
  101. package/dist/worker.cjs +0 -839
  102. package/dist/worker.cjs.map +0 -1
  103. package/dist/worker.js +0 -836
  104. package/dist/worker.js.map +0 -1
@@ -1,4398 +0,0 @@
1
- import { init } from '@embedpdf/pdfium';
2
- import { NoopLogger, PdfTaskHelper, PdfErrorCode, Task, Rotation, PdfAnnotationSubtype, stripPdfUnwantedMarkers, PdfAnnotationBorderStyle, dateToPdfDate, PdfAnnotationColorType, PdfPageObjectType, pdfAlphaColorToWebAlphaColor, webAlphaColorToPdfAlphaColor, quadToRect, pdfDateToDate, flagsToNames, PDF_FORM_FIELD_TYPE, toIntRect, transformRect, makeMatrix, AppearanceMode, toIntSize, transformSize, PdfActionType, PdfZoomMode, MatchFlag, rectToQuad } from '@embedpdf/models';
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 = 'PDFiumEngine';
252
- const LOG_CATEGORY = '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 });
279
- };
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, LOG_CATEGORY, 'initialize');
303
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `Initialize`, 'Begin', 'General');
304
- this.pdfiumModule.PDFiumExt_Init();
305
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `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, LOG_CATEGORY, 'destroy');
315
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `Destroy`, 'Begin', 'General');
316
- this.pdfiumModule.FPDF_DestroyLibrary();
317
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `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, LOG_CATEGORY, '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, LOG_CATEGORY, '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, LOG_CATEGORY, '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, LOG_CATEGORY, '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, LOG_CATEGORY, '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, LOG_CATEGORY, '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, LOG_CATEGORY, '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, LOG_CATEGORY, 'openDocumentFromBuffer', file, password);
503
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `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, LOG_CATEGORY, `FPDF_LoadMemDocument failed with ${lastError}`);
512
- this.free(filePtr);
513
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `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, LOG_CATEGORY, `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, LOG_CATEGORY, `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, LOG_CATEGORY, `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, LOG_CATEGORY, 'openDocumentFromLoader', file, password);
563
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `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, LOG_CATEGORY, 'readBlock', offset, length, pBuf);
570
- if (offset < 0 || offset >= fileLength) {
571
- this.logger.error(LOG_SOURCE, LOG_CATEGORY, '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, LOG_CATEGORY, '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, LOG_CATEGORY, `FPDF_LoadCustomDocument failed with ${lastError}`);
599
- this.free(fileAccessPtr);
600
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `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, LOG_CATEGORY, `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, LOG_CATEGORY, `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, LOG_CATEGORY, `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, LOG_CATEGORY, 'getMetadata', doc);
649
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `GetMetadata`, 'Begin', doc.id);
650
- const ctx = this.cache.getContext(doc.id);
651
- if (!ctx) {
652
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `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, LOG_CATEGORY, `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, LOG_CATEGORY, 'getDocPermissions', doc);
678
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `getDocPermissions`, 'Begin', doc.id);
679
- const ctx = this.cache.getContext(doc.id);
680
- if (!ctx) {
681
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `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, LOG_CATEGORY, 'getDocUserPermissions', doc);
697
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `getDocUserPermissions`, 'Begin', doc.id);
698
- const ctx = this.cache.getContext(doc.id);
699
- if (!ctx) {
700
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `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, LOG_CATEGORY, 'getSignatures', doc);
716
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `GetSignatures`, 'Begin', doc.id);
717
- const ctx = this.cache.getContext(doc.id);
718
- if (!ctx) {
719
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `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, LOG_CATEGORY, `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, LOG_CATEGORY, 'getBookmarks', doc);
764
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `GetBookmarks`, 'Begin', doc.id);
765
- const ctx = this.cache.getContext(doc.id);
766
- if (!ctx) {
767
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `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, LOG_CATEGORY, `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, LOG_CATEGORY, 'renderPage', doc, page, scaleFactor, rotation, dpr, options);
787
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `RenderPage`, 'Begin', `${doc.id}-${page.index}`);
788
- const ctx = this.cache.getContext(doc.id);
789
- if (!ctx) {
790
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `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, LOG_CATEGORY, `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, LOG_CATEGORY, 'renderPageRect', doc, page, scaleFactor, rotation, dpr, rect, options);
812
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `RenderPageRect`, 'Begin', `${doc.id}-${page.index}`);
813
- const ctx = this.cache.getContext(doc.id);
814
- if (!ctx) {
815
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `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, LOG_CATEGORY, `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, LOG_CATEGORY, 'getAllAnnotations', doc);
833
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `GetAllAnnotations`, 'Begin', doc.id);
834
- const ctx = this.cache.getContext(doc.id);
835
- if (!ctx) {
836
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `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, LOG_CATEGORY, `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, LOG_CATEGORY, 'getPageAnnotations', doc, page);
861
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `GetPageAnnotations`, 'Begin', `${doc.id}-${page.index}`);
862
- const ctx = this.cache.getContext(doc.id);
863
- if (!ctx) {
864
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `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, LOG_CATEGORY, `GetPageAnnotations`, 'End', `${doc.id}-${page.index}`);
872
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, `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, LOG_CATEGORY, 'createPageAnnotation', doc, page, annotation);
882
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `CreatePageAnnotation`, 'Begin', `${doc.id}-${page.index}`);
883
- const ctx = this.cache.getContext(doc.id);
884
- if (!ctx) {
885
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `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, LOG_CATEGORY, `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, LOG_CATEGORY, `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);
914
- break;
915
- case PdfAnnotationSubtype.STAMP:
916
- isSucceed = this.addStampContent(ctx.docPtr, page, pageCtx.pagePtr, annotationPtr, annotation.rect, annotation.contents);
917
- break;
918
- case PdfAnnotationSubtype.UNDERLINE:
919
- case PdfAnnotationSubtype.STRIKEOUT:
920
- case PdfAnnotationSubtype.SQUIGGLY:
921
- case PdfAnnotationSubtype.HIGHLIGHT:
922
- isSucceed = this.addTextMarkupContent(page, pageCtx.pagePtr, annotationPtr, annotation);
923
- break;
924
- }
925
- if (!isSucceed) {
926
- this.pdfiumModule.FPDFPage_RemoveAnnot(pageCtx.pagePtr, annotationPtr);
927
- pageCtx.release();
928
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `CreatePageAnnotation`, 'End', `${doc.id}-${page.index}`);
929
- return PdfTaskHelper.reject({
930
- code: PdfErrorCode.CantSetAnnotContent,
931
- message: 'can not add content of the annotation',
932
- });
933
- }
934
- this.pdfiumModule.EPDFAnnot_GenerateAppearance(annotationPtr);
935
- this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
936
- const annotId = this.pdfiumModule.FPDFPage_GetAnnotIndex(pageCtx.pagePtr, annotationPtr);
937
- this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
938
- pageCtx.release();
939
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `CreatePageAnnotation`, 'End', `${doc.id}-${page.index}`);
940
- return annotId >= 0
941
- ? PdfTaskHelper.resolve(annotId)
942
- : PdfTaskHelper.reject({
943
- code: PdfErrorCode.CantCreateAnnot,
944
- message: 'annotation created but index could not be determined',
945
- });
946
- }
947
- /**
948
- * Update an existing page annotation in-place
949
- *
950
- * • Locates the annot by page-local index (`annotation.id`)
951
- * • Re-writes its /Rect and type-specific payload
952
- * • Calls FPDFPage_GenerateContent so the new appearance is rendered
953
- *
954
- * @returns PdfTask<boolean> – true on success
955
- */
956
- updatePageAnnotation(doc, page, annotation) {
957
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'updatePageAnnotation', doc, page, annotation);
958
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'UpdatePageAnnotation', 'Begin', `${doc.id}-${page.index}`);
959
- const ctx = this.cache.getContext(doc.id);
960
- if (!ctx) {
961
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'UpdatePageAnnotation', 'End', `${doc.id}-${page.index}`);
962
- return PdfTaskHelper.reject({
963
- code: PdfErrorCode.DocNotOpen,
964
- message: 'document does not open',
965
- });
966
- }
967
- const pageCtx = ctx.acquirePage(page.index);
968
- const annotPtr = this.pdfiumModule.FPDFPage_GetAnnot(pageCtx.pagePtr, annotation.id);
969
- if (!annotPtr) {
970
- pageCtx.release();
971
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'UpdatePageAnnotation', 'End', `${doc.id}-${page.index}`);
972
- return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: 'annotation not found' });
973
- }
974
- /* 1 ── (re)set bounding-box ────────────────────────────────────────────── */
975
- if (!this.setPageAnnoRect(page, pageCtx.pagePtr, annotPtr, annotation.rect)) {
976
- this.pdfiumModule.FPDFPage_CloseAnnot(annotPtr);
977
- pageCtx.release();
978
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'UpdatePageAnnotation', 'End', `${doc.id}-${page.index}`);
979
- return PdfTaskHelper.reject({
980
- code: PdfErrorCode.CantSetAnnotRect,
981
- message: 'failed to move annotation',
982
- });
983
- }
984
- /* 2 ── wipe previous payload and rebuild fresh one ─────────────────────── */
985
- let ok = false;
986
- switch (annotation.type) {
987
- /* ── Ink ─────────────────────────────────────────────────────────────── */
988
- case PdfAnnotationSubtype.INK: {
989
- /* clear every existing stroke first */
990
- if (!this.pdfiumModule.FPDFAnnot_RemoveInkList(annotPtr))
991
- break;
992
- ok = this.addInkStroke(page, pageCtx.pagePtr, annotPtr, annotation);
993
- break;
994
- }
995
- /* ── Stamp ───────────────────────────────────────────────────────────── */
996
- case PdfAnnotationSubtype.STAMP: {
997
- /* drop every page-object inside the annot */
998
- for (let i = this.pdfiumModule.FPDFAnnot_GetObjectCount(annotPtr) - 1; i >= 0; i--) {
999
- this.pdfiumModule.FPDFAnnot_RemoveObject(annotPtr, i);
1000
- }
1001
- ok = this.addStampContent(ctx.docPtr, page, pageCtx.pagePtr, annotPtr, annotation.rect, annotation.contents);
1002
- break;
1003
- }
1004
- /* ── Text-markup family ──────────────────────────────────────────────── */
1005
- case PdfAnnotationSubtype.HIGHLIGHT:
1006
- case PdfAnnotationSubtype.UNDERLINE:
1007
- case PdfAnnotationSubtype.STRIKEOUT:
1008
- case PdfAnnotationSubtype.SQUIGGLY: {
1009
- /* replace quad-points / colour / strings in one go */
1010
- ok = this.addTextMarkupContent(page, pageCtx.pagePtr, annotPtr, annotation);
1011
- break;
1012
- }
1013
- /* ── Unsupported edits – fall through to error ───────────────────────── */
1014
- default:
1015
- ok = false;
1016
- }
1017
- /* 3 ── regenerate appearance if payload was changed ───────────────────── */
1018
- if (ok) {
1019
- this.pdfiumModule.EPDFAnnot_GenerateAppearance(annotPtr);
1020
- this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
1021
- }
1022
- /* 4 ── tidy-up native handles ──────────────────────────────────────────── */
1023
- this.pdfiumModule.FPDFPage_CloseAnnot(annotPtr);
1024
- pageCtx.release();
1025
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'UpdatePageAnnotation', 'End', `${doc.id}-${page.index}`);
1026
- return ok
1027
- ? PdfTaskHelper.resolve(true)
1028
- : PdfTaskHelper.reject({
1029
- code: PdfErrorCode.CantSetAnnotContent,
1030
- message: 'failed to update annotation',
1031
- });
1032
- }
1033
- /**
1034
- * {@inheritDoc @embedpdf/models!PdfEngine.removePageAnnotation}
1035
- *
1036
- * @public
1037
- */
1038
- removePageAnnotation(doc, page, annotation) {
1039
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'removePageAnnotation', doc, page, annotation);
1040
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `RemovePageAnnotation`, 'Begin', `${doc.id}-${page.index}`);
1041
- const ctx = this.cache.getContext(doc.id);
1042
- if (!ctx) {
1043
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `RemovePageAnnotation`, 'End', `${doc.id}-${page.index}`);
1044
- return PdfTaskHelper.reject({
1045
- code: PdfErrorCode.DocNotOpen,
1046
- message: 'document does not open',
1047
- });
1048
- }
1049
- const pageCtx = ctx.acquirePage(page.index);
1050
- let result = false;
1051
- result = this.pdfiumModule.FPDFPage_RemoveAnnot(pageCtx.pagePtr, annotation.id);
1052
- if (!result) {
1053
- this.logger.error(LOG_SOURCE, LOG_CATEGORY, `FPDFPage_RemoveAnnot Failed`, `${doc.id}-${page.index}`);
1054
- }
1055
- else {
1056
- result = this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
1057
- if (!result) {
1058
- this.logger.error(LOG_SOURCE, LOG_CATEGORY, `FPDFPage_GenerateContent Failed`, `${doc.id}-${page.index}`);
1059
- }
1060
- }
1061
- pageCtx.release();
1062
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `RemovePageAnnotation`, 'End', `${doc.id}-${page.index}`);
1063
- return PdfTaskHelper.resolve(result);
1064
- }
1065
- /**
1066
- * {@inheritDoc @embedpdf/models!PdfEngine.getPageTextRects}
1067
- *
1068
- * @public
1069
- */
1070
- getPageTextRects(doc, page, scaleFactor, rotation) {
1071
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'getPageTextRects', doc, page, scaleFactor, rotation);
1072
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `GetPageTextRects`, 'Begin', `${doc.id}-${page.index}`);
1073
- const ctx = this.cache.getContext(doc.id);
1074
- if (!ctx) {
1075
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `GetPageTextRects`, 'End', `${doc.id}-${page.index}`);
1076
- return PdfTaskHelper.reject({
1077
- code: PdfErrorCode.DocNotOpen,
1078
- message: 'document does not open',
1079
- });
1080
- }
1081
- const pageCtx = ctx.acquirePage(page.index);
1082
- const textPagePtr = this.pdfiumModule.FPDFText_LoadPage(pageCtx.pagePtr);
1083
- const textRects = this.readPageTextRects(page, pageCtx.docPtr, pageCtx.pagePtr, textPagePtr);
1084
- this.pdfiumModule.FPDFText_ClosePage(textPagePtr);
1085
- pageCtx.release();
1086
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `GetPageTextRects`, 'End', `${doc.id}-${page.index}`);
1087
- return PdfTaskHelper.resolve(textRects);
1088
- }
1089
- /**
1090
- * {@inheritDoc @embedpdf/models!PdfEngine.renderThumbnail}
1091
- *
1092
- * @public
1093
- */
1094
- renderThumbnail(doc, page, scaleFactor, rotation, dpr) {
1095
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'renderThumbnail', doc, page, scaleFactor, rotation, dpr);
1096
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `RenderThumbnail`, 'Begin', `${doc.id}-${page.index}`);
1097
- const ctx = this.cache.getContext(doc.id);
1098
- if (!ctx) {
1099
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `RenderThumbnail`, 'End', `${doc.id}-${page.index}`);
1100
- return PdfTaskHelper.reject({
1101
- code: PdfErrorCode.DocNotOpen,
1102
- message: 'document does not open',
1103
- });
1104
- }
1105
- scaleFactor = Math.max(scaleFactor, 0.5);
1106
- const result = this.renderPage(doc, page, scaleFactor, rotation, dpr, {
1107
- withAnnotations: true,
1108
- });
1109
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `RenderThumbnail`, 'End', `${doc.id}-${page.index}`);
1110
- return result;
1111
- }
1112
- /**
1113
- * {@inheritDoc @embedpdf/models!PdfEngine.getAttachments}
1114
- *
1115
- * @public
1116
- */
1117
- getAttachments(doc) {
1118
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'getAttachments', doc);
1119
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `GetAttachments`, 'Begin', doc.id);
1120
- const ctx = this.cache.getContext(doc.id);
1121
- if (!ctx) {
1122
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `GetAttachments`, 'End', doc.id);
1123
- return PdfTaskHelper.reject({
1124
- code: PdfErrorCode.DocNotOpen,
1125
- message: 'document does not open',
1126
- });
1127
- }
1128
- const attachments = [];
1129
- const count = this.pdfiumModule.FPDFDoc_GetAttachmentCount(ctx.docPtr);
1130
- for (let i = 0; i < count; i++) {
1131
- const attachment = this.readPdfAttachment(ctx.docPtr, i);
1132
- attachments.push(attachment);
1133
- }
1134
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `GetAttachments`, 'End', doc.id);
1135
- return PdfTaskHelper.resolve(attachments);
1136
- }
1137
- /**
1138
- * {@inheritDoc @embedpdf/models!PdfEngine.readAttachmentContent}
1139
- *
1140
- * @public
1141
- */
1142
- readAttachmentContent(doc, attachment) {
1143
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'readAttachmentContent', doc, attachment);
1144
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `ReadAttachmentContent`, 'Begin', doc.id);
1145
- const ctx = this.cache.getContext(doc.id);
1146
- if (!ctx) {
1147
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `ReadAttachmentContent`, 'End', doc.id);
1148
- return PdfTaskHelper.reject({
1149
- code: PdfErrorCode.DocNotOpen,
1150
- message: 'document does not open',
1151
- });
1152
- }
1153
- const attachmentPtr = this.pdfiumModule.FPDFDoc_GetAttachment(ctx.docPtr, attachment.index);
1154
- const sizePtr = this.malloc(8);
1155
- if (!this.pdfiumModule.FPDFAttachment_GetFile(attachmentPtr, 0, 0, sizePtr)) {
1156
- this.free(sizePtr);
1157
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `ReadAttachmentContent`, 'End', doc.id);
1158
- return PdfTaskHelper.reject({
1159
- code: PdfErrorCode.CantReadAttachmentSize,
1160
- message: 'can not read attachment size',
1161
- });
1162
- }
1163
- const size = this.pdfiumModule.pdfium.getValue(sizePtr, 'i64');
1164
- const contentPtr = this.malloc(size);
1165
- if (!this.pdfiumModule.FPDFAttachment_GetFile(attachmentPtr, contentPtr, size, sizePtr)) {
1166
- this.free(sizePtr);
1167
- this.free(contentPtr);
1168
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `ReadAttachmentContent`, 'End', doc.id);
1169
- return PdfTaskHelper.reject({
1170
- code: PdfErrorCode.CantReadAttachmentContent,
1171
- message: 'can not read attachment content',
1172
- });
1173
- }
1174
- const buffer = new ArrayBuffer(size);
1175
- const view = new DataView(buffer);
1176
- for (let i = 0; i < size; i++) {
1177
- view.setInt8(i, this.pdfiumModule.pdfium.getValue(contentPtr + i, 'i8'));
1178
- }
1179
- this.free(sizePtr);
1180
- this.free(contentPtr);
1181
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `ReadAttachmentContent`, 'End', doc.id);
1182
- return PdfTaskHelper.resolve(buffer);
1183
- }
1184
- /**
1185
- * {@inheritDoc @embedpdf/models!PdfEngine.setFormFieldValue}
1186
- *
1187
- * @public
1188
- */
1189
- setFormFieldValue(doc, page, annotation, value) {
1190
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'SetFormFieldValue', doc, annotation, value);
1191
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `SetFormFieldValue`, 'Begin', `${doc.id}-${annotation.id}`);
1192
- const ctx = this.cache.getContext(doc.id);
1193
- if (!ctx) {
1194
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'SetFormFieldValue', 'document is not opened');
1195
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `SetFormFieldValue`, 'End', `${doc.id}-${annotation.id}`);
1196
- return PdfTaskHelper.reject({
1197
- code: PdfErrorCode.DocNotOpen,
1198
- message: 'document does not open',
1199
- });
1200
- }
1201
- const formFillInfoPtr = this.pdfiumModule.PDFiumExt_OpenFormFillInfo();
1202
- const formHandle = this.pdfiumModule.PDFiumExt_InitFormFillEnvironment(ctx.docPtr, formFillInfoPtr);
1203
- const pageCtx = ctx.acquirePage(page.index);
1204
- this.pdfiumModule.FORM_OnAfterLoadPage(pageCtx.pagePtr, formHandle);
1205
- const annotationPtr = this.pdfiumModule.FPDFPage_GetAnnot(pageCtx.pagePtr, annotation.id);
1206
- if (!this.pdfiumModule.FORM_SetFocusedAnnot(formHandle, annotationPtr)) {
1207
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'SetFormFieldValue', 'failed to set focused annotation');
1208
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `SetFormFieldValue`, 'End', `${doc.id}-${annotation.id}`);
1209
- this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
1210
- this.pdfiumModule.FORM_OnBeforeClosePage(pageCtx.pagePtr, formHandle);
1211
- pageCtx.release();
1212
- this.pdfiumModule.PDFiumExt_ExitFormFillEnvironment(formHandle);
1213
- this.pdfiumModule.PDFiumExt_CloseFormFillInfo(formFillInfoPtr);
1214
- return PdfTaskHelper.reject({
1215
- code: PdfErrorCode.CantFocusAnnot,
1216
- message: 'failed to set focused annotation',
1217
- });
1218
- }
1219
- switch (value.kind) {
1220
- case 'text':
1221
- {
1222
- if (!this.pdfiumModule.FORM_SelectAllText(formHandle, pageCtx.pagePtr)) {
1223
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'SetFormFieldValue', 'failed to select all text');
1224
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `SetFormFieldValue`, 'End', `${doc.id}-${annotation.id}`);
1225
- this.pdfiumModule.FORM_ForceToKillFocus(formHandle);
1226
- this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
1227
- this.pdfiumModule.FORM_OnBeforeClosePage(pageCtx.pagePtr, formHandle);
1228
- pageCtx.release();
1229
- this.pdfiumModule.PDFiumExt_ExitFormFillEnvironment(formHandle);
1230
- this.pdfiumModule.PDFiumExt_CloseFormFillInfo(formFillInfoPtr);
1231
- return PdfTaskHelper.reject({
1232
- code: PdfErrorCode.CantSelectText,
1233
- message: 'failed to select all text',
1234
- });
1235
- }
1236
- const length = 2 * (value.text.length + 1);
1237
- const textPtr = this.malloc(length);
1238
- this.pdfiumModule.pdfium.stringToUTF16(value.text, textPtr, length);
1239
- this.pdfiumModule.FORM_ReplaceSelection(formHandle, pageCtx.pagePtr, textPtr);
1240
- this.free(textPtr);
1241
- }
1242
- break;
1243
- case 'selection':
1244
- {
1245
- if (!this.pdfiumModule.FORM_SetIndexSelected(formHandle, pageCtx.pagePtr, value.index, value.isSelected)) {
1246
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'SetFormFieldValue', 'failed to set index selected');
1247
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `SetFormFieldValue`, 'End', `${doc.id}-${annotation.id}`);
1248
- this.pdfiumModule.FORM_ForceToKillFocus(formHandle);
1249
- this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
1250
- this.pdfiumModule.FORM_OnBeforeClosePage(pageCtx.pagePtr, formHandle);
1251
- pageCtx.release();
1252
- this.pdfiumModule.PDFiumExt_ExitFormFillEnvironment(formHandle);
1253
- this.pdfiumModule.PDFiumExt_CloseFormFillInfo(formFillInfoPtr);
1254
- return PdfTaskHelper.reject({
1255
- code: PdfErrorCode.CantSelectOption,
1256
- message: 'failed to set index selected',
1257
- });
1258
- }
1259
- }
1260
- break;
1261
- case 'checked':
1262
- {
1263
- const kReturn = 0x0d;
1264
- if (!this.pdfiumModule.FORM_OnChar(formHandle, pageCtx.pagePtr, kReturn, 0)) {
1265
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'SetFormFieldValue', 'failed to set field checked');
1266
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `SetFormFieldValue`, 'End', `${doc.id}-${annotation.id}`);
1267
- this.pdfiumModule.FORM_ForceToKillFocus(formHandle);
1268
- this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
1269
- this.pdfiumModule.FORM_OnBeforeClosePage(pageCtx.pagePtr, formHandle);
1270
- pageCtx.release();
1271
- this.pdfiumModule.PDFiumExt_ExitFormFillEnvironment(formHandle);
1272
- this.pdfiumModule.PDFiumExt_CloseFormFillInfo(formFillInfoPtr);
1273
- return PdfTaskHelper.reject({
1274
- code: PdfErrorCode.CantCheckField,
1275
- message: 'failed to set field checked',
1276
- });
1277
- }
1278
- }
1279
- break;
1280
- }
1281
- this.pdfiumModule.FORM_ForceToKillFocus(formHandle);
1282
- this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
1283
- this.pdfiumModule.FORM_OnBeforeClosePage(pageCtx.pagePtr, formHandle);
1284
- pageCtx.release();
1285
- this.pdfiumModule.PDFiumExt_ExitFormFillEnvironment(formHandle);
1286
- this.pdfiumModule.PDFiumExt_CloseFormFillInfo(formFillInfoPtr);
1287
- return PdfTaskHelper.resolve(true);
1288
- }
1289
- /**
1290
- * {@inheritDoc @embedpdf/models!PdfEngine.flattenPage}
1291
- *
1292
- * @public
1293
- */
1294
- flattenPage(doc, page, flag) {
1295
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'flattenPage', doc, page, flag);
1296
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `flattenPage`, 'Begin', doc.id);
1297
- const ctx = this.cache.getContext(doc.id);
1298
- if (!ctx) {
1299
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `flattenPage`, 'End', doc.id);
1300
- return PdfTaskHelper.reject({
1301
- code: PdfErrorCode.DocNotOpen,
1302
- message: 'document does not open',
1303
- });
1304
- }
1305
- const pageCtx = ctx.acquirePage(page.index);
1306
- const result = this.pdfiumModule.FPDFPage_Flatten(pageCtx.pagePtr, flag);
1307
- pageCtx.release();
1308
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `flattenPage`, 'End', doc.id);
1309
- return PdfTaskHelper.resolve(result);
1310
- }
1311
- /**
1312
- * {@inheritDoc @embedpdf/models!PdfEngine.extractPages}
1313
- *
1314
- * @public
1315
- */
1316
- extractPages(doc, pageIndexes) {
1317
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'extractPages', doc, pageIndexes);
1318
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `ExtractPages`, 'Begin', doc.id);
1319
- const ctx = this.cache.getContext(doc.id);
1320
- if (!ctx) {
1321
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `ExtractPages`, 'End', doc.id);
1322
- return PdfTaskHelper.reject({
1323
- code: PdfErrorCode.DocNotOpen,
1324
- message: 'document does not open',
1325
- });
1326
- }
1327
- const newDocPtr = this.pdfiumModule.FPDF_CreateNewDocument();
1328
- if (!newDocPtr) {
1329
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `ExtractPages`, 'End', doc.id);
1330
- return PdfTaskHelper.reject({
1331
- code: PdfErrorCode.CantCreateNewDoc,
1332
- message: 'can not create new document',
1333
- });
1334
- }
1335
- const pageIndexesPtr = this.malloc(pageIndexes.length * 4);
1336
- for (let i = 0; i < pageIndexes.length; i++) {
1337
- this.pdfiumModule.pdfium.setValue(pageIndexesPtr + i * 4, pageIndexes[i], 'i32');
1338
- }
1339
- if (!this.pdfiumModule.FPDF_ImportPagesByIndex(newDocPtr, ctx.docPtr, pageIndexesPtr, pageIndexes.length, 0)) {
1340
- this.pdfiumModule.FPDF_CloseDocument(newDocPtr);
1341
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `ExtractPages`, 'End', doc.id);
1342
- return PdfTaskHelper.reject({
1343
- code: PdfErrorCode.CantImportPages,
1344
- message: 'can not import pages to new document',
1345
- });
1346
- }
1347
- const buffer = this.saveDocument(newDocPtr);
1348
- this.pdfiumModule.FPDF_CloseDocument(newDocPtr);
1349
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `ExtractPages`, 'End', doc.id);
1350
- return PdfTaskHelper.resolve(buffer);
1351
- }
1352
- /**
1353
- * {@inheritDoc @embedpdf/models!PdfEngine.extractText}
1354
- *
1355
- * @public
1356
- */
1357
- extractText(doc, pageIndexes) {
1358
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'extractText', doc, pageIndexes);
1359
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `ExtractText`, 'Begin', doc.id);
1360
- const ctx = this.cache.getContext(doc.id);
1361
- if (!ctx) {
1362
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `ExtractText`, 'End', doc.id);
1363
- return PdfTaskHelper.reject({
1364
- code: PdfErrorCode.DocNotOpen,
1365
- message: 'document does not open',
1366
- });
1367
- }
1368
- const strings = [];
1369
- for (let i = 0; i < pageIndexes.length; i++) {
1370
- const pageCtx = ctx.acquirePage(pageIndexes[i]);
1371
- const textPagePtr = this.pdfiumModule.FPDFText_LoadPage(pageCtx.pagePtr);
1372
- const charCount = this.pdfiumModule.FPDFText_CountChars(textPagePtr);
1373
- const bufferPtr = this.malloc((charCount + 1) * 2);
1374
- this.pdfiumModule.FPDFText_GetText(textPagePtr, 0, charCount, bufferPtr);
1375
- const text = this.pdfiumModule.pdfium.UTF16ToString(bufferPtr);
1376
- this.free(bufferPtr);
1377
- strings.push(text);
1378
- this.pdfiumModule.FPDFText_ClosePage(textPagePtr);
1379
- pageCtx.release();
1380
- }
1381
- const text = strings.join('\n\n');
1382
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `ExtractText`, 'End', doc.id);
1383
- return PdfTaskHelper.resolve(text);
1384
- }
1385
- /**
1386
- * {@inheritDoc @embedpdf/models!PdfEngine.getTextSlices}
1387
- *
1388
- * @public
1389
- */
1390
- getTextSlices(doc, slices) {
1391
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'getTextSlices', doc, slices);
1392
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'GetTextSlices', 'Begin', doc.id);
1393
- /* ⚠︎ 1 — trivial case */
1394
- if (slices.length === 0) {
1395
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'GetTextSlices', 'End', doc.id);
1396
- return PdfTaskHelper.resolve([]);
1397
- }
1398
- /* ⚠︎ 2 — document must be open */
1399
- const ctx = this.cache.getContext(doc.id);
1400
- if (!ctx) {
1401
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'GetTextSlices', 'End', doc.id);
1402
- return PdfTaskHelper.reject({
1403
- code: PdfErrorCode.DocNotOpen,
1404
- message: 'document does not open',
1405
- });
1406
- }
1407
- try {
1408
- /* keep caller order */
1409
- const out = new Array(slices.length);
1410
- /* group → open each page once */
1411
- const byPage = new Map();
1412
- slices.forEach((s, i) => {
1413
- (byPage.get(s.pageIndex) ?? byPage.set(s.pageIndex, []).get(s.pageIndex)).push({
1414
- slice: s,
1415
- pos: i,
1416
- });
1417
- });
1418
- for (const [pageIdx, list] of byPage) {
1419
- const pageCtx = ctx.acquirePage(pageIdx);
1420
- const textPagePtr = pageCtx.getTextPage();
1421
- for (const { slice, pos } of list) {
1422
- const bufPtr = this.malloc(2 * (slice.charCount + 1)); // UTF-16 + NIL
1423
- this.pdfiumModule.FPDFText_GetText(textPagePtr, slice.charIndex, slice.charCount, bufPtr);
1424
- out[pos] = stripPdfUnwantedMarkers(this.pdfiumModule.pdfium.UTF16ToString(bufPtr));
1425
- this.free(bufPtr);
1426
- }
1427
- pageCtx.release();
1428
- }
1429
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'GetTextSlices', 'End', doc.id);
1430
- return PdfTaskHelper.resolve(out);
1431
- }
1432
- catch (e) {
1433
- this.logger.error(LOG_SOURCE, LOG_CATEGORY, 'getTextSlices error', e);
1434
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'GetTextSlices', 'End', doc.id);
1435
- return PdfTaskHelper.reject({
1436
- code: PdfErrorCode.Unknown,
1437
- message: String(e),
1438
- });
1439
- }
1440
- }
1441
- /**
1442
- * {@inheritDoc @embedpdf/models!PdfEngine.merge}
1443
- *
1444
- * @public
1445
- */
1446
- merge(files) {
1447
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'merge', files);
1448
- const fileIds = files.map((file) => file.id).join('.');
1449
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `Merge`, 'Begin', fileIds);
1450
- const newDocPtr = this.pdfiumModule.FPDF_CreateNewDocument();
1451
- if (!newDocPtr) {
1452
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `Merge`, 'End', fileIds);
1453
- return PdfTaskHelper.reject({
1454
- code: PdfErrorCode.CantCreateNewDoc,
1455
- message: 'can not create new document',
1456
- });
1457
- }
1458
- const ptrs = [];
1459
- for (const file of files.reverse()) {
1460
- const array = new Uint8Array(file.content);
1461
- const length = array.length;
1462
- const filePtr = this.malloc(length);
1463
- this.pdfiumModule.pdfium.HEAPU8.set(array, filePtr);
1464
- const docPtr = this.pdfiumModule.FPDF_LoadMemDocument(filePtr, length, '');
1465
- if (!docPtr) {
1466
- const lastError = this.pdfiumModule.FPDF_GetLastError();
1467
- this.logger.error(LOG_SOURCE, LOG_CATEGORY, `FPDF_LoadMemDocument failed with ${lastError}`);
1468
- this.free(filePtr);
1469
- for (const ptr of ptrs) {
1470
- this.pdfiumModule.FPDF_CloseDocument(ptr.docPtr);
1471
- this.free(ptr.filePtr);
1472
- }
1473
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `Merge`, 'End', fileIds);
1474
- return PdfTaskHelper.reject({
1475
- code: lastError,
1476
- message: `FPDF_LoadMemDocument failed`,
1477
- });
1478
- }
1479
- ptrs.push({ filePtr, docPtr });
1480
- if (!this.pdfiumModule.FPDF_ImportPages(newDocPtr, docPtr, '', 0)) {
1481
- this.pdfiumModule.FPDF_CloseDocument(newDocPtr);
1482
- for (const ptr of ptrs) {
1483
- this.pdfiumModule.FPDF_CloseDocument(ptr.docPtr);
1484
- this.free(ptr.filePtr);
1485
- }
1486
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `Merge`, 'End', fileIds);
1487
- return PdfTaskHelper.reject({
1488
- code: PdfErrorCode.CantImportPages,
1489
- message: 'can not import pages to new document',
1490
- });
1491
- }
1492
- }
1493
- const buffer = this.saveDocument(newDocPtr);
1494
- this.pdfiumModule.FPDF_CloseDocument(newDocPtr);
1495
- for (const ptr of ptrs) {
1496
- this.pdfiumModule.FPDF_CloseDocument(ptr.docPtr);
1497
- this.free(ptr.filePtr);
1498
- }
1499
- const file = {
1500
- id: `${Math.random()}`,
1501
- content: buffer,
1502
- };
1503
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `Merge`, 'End', fileIds);
1504
- return PdfTaskHelper.resolve(file);
1505
- }
1506
- /**
1507
- * Merges specific pages from multiple PDF documents in a custom order
1508
- *
1509
- * @param mergeConfigs Array of configurations specifying which pages to merge from which documents
1510
- * @returns A PdfTask that resolves with the merged PDF file
1511
- * @public
1512
- */
1513
- mergePages(mergeConfigs) {
1514
- const configIds = mergeConfigs
1515
- .map((config) => `${config.docId}:${config.pageIndices.join(',')}`)
1516
- .join('|');
1517
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'mergePages', mergeConfigs);
1518
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `MergePages`, 'Begin', configIds);
1519
- // Create a new document to import pages into
1520
- const newDocPtr = this.pdfiumModule.FPDF_CreateNewDocument();
1521
- if (!newDocPtr) {
1522
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `MergePages`, 'End', configIds);
1523
- return PdfTaskHelper.reject({
1524
- code: PdfErrorCode.CantCreateNewDoc,
1525
- message: 'Cannot create new document',
1526
- });
1527
- }
1528
- try {
1529
- // Process each merge configuration in reverse order (since we're inserting at position 0)
1530
- // This ensures the final document has pages in the order specified by the user
1531
- for (const config of [...mergeConfigs].reverse()) {
1532
- // Check if the document is open
1533
- const ctx = this.cache.getContext(config.docId);
1534
- if (!ctx) {
1535
- this.logger.warn(LOG_SOURCE, LOG_CATEGORY, `Document ${config.docId} is not open, skipping`);
1536
- continue;
1537
- }
1538
- // Get the page count for this document
1539
- const pageCount = this.pdfiumModule.FPDF_GetPageCount(ctx.docPtr);
1540
- // Filter out invalid page indices
1541
- const validPageIndices = config.pageIndices.filter((index) => index >= 0 && index < pageCount);
1542
- if (validPageIndices.length === 0) {
1543
- continue; // No valid pages to import
1544
- }
1545
- // Convert 0-based indices to 1-based for PDFium and join with commas
1546
- const pageString = validPageIndices.map((index) => index + 1).join(',');
1547
- try {
1548
- // Import all specified pages at once from this document
1549
- if (!this.pdfiumModule.FPDF_ImportPages(newDocPtr, ctx.docPtr, pageString, 0)) {
1550
- throw new Error(`Failed to import pages ${pageString} from document ${config.docId}`);
1551
- }
1552
- }
1553
- finally {
1554
- }
1555
- }
1556
- // Save the new document to buffer
1557
- const buffer = this.saveDocument(newDocPtr);
1558
- const file = {
1559
- id: `${Math.random()}`,
1560
- content: buffer,
1561
- };
1562
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `MergePages`, 'End', configIds);
1563
- return PdfTaskHelper.resolve(file);
1564
- }
1565
- catch (error) {
1566
- this.logger.error(LOG_SOURCE, LOG_CATEGORY, 'mergePages failed', error);
1567
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `MergePages`, 'End', configIds);
1568
- return PdfTaskHelper.reject({
1569
- code: PdfErrorCode.CantImportPages,
1570
- message: error instanceof Error ? error.message : 'Failed to merge pages',
1571
- });
1572
- }
1573
- finally {
1574
- // Clean up the new document
1575
- if (newDocPtr) {
1576
- this.pdfiumModule.FPDF_CloseDocument(newDocPtr);
1577
- }
1578
- }
1579
- }
1580
- /**
1581
- * {@inheritDoc @embedpdf/models!PdfEngine.saveAsCopy}
1582
- *
1583
- * @public
1584
- */
1585
- saveAsCopy(doc) {
1586
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'saveAsCopy', doc);
1587
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `SaveAsCopy`, 'Begin', doc.id);
1588
- const ctx = this.cache.getContext(doc.id);
1589
- if (!ctx) {
1590
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `SaveAsCopy`, 'End', doc.id);
1591
- return PdfTaskHelper.reject({
1592
- code: PdfErrorCode.DocNotOpen,
1593
- message: 'document does not open',
1594
- });
1595
- }
1596
- const buffer = this.saveDocument(ctx.docPtr);
1597
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `SaveAsCopy`, 'End', doc.id);
1598
- return PdfTaskHelper.resolve(buffer);
1599
- }
1600
- /**
1601
- * {@inheritDoc @embedpdf/models!PdfEngine.closeDocument}
1602
- *
1603
- * @public
1604
- */
1605
- closeDocument(doc) {
1606
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'closeDocument', doc);
1607
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `CloseDocument`, 'Begin', doc.id);
1608
- const ctx = this.cache.getContext(doc.id);
1609
- if (!ctx) {
1610
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `CloseDocument`, 'End', doc.id);
1611
- return PdfTaskHelper.reject({
1612
- code: PdfErrorCode.DocNotOpen,
1613
- message: 'document does not open',
1614
- });
1615
- }
1616
- ctx.dispose();
1617
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `CloseDocument`, 'End', doc.id);
1618
- return PdfTaskHelper.resolve(true);
1619
- }
1620
- /**
1621
- * Memory allocation
1622
- * @param size - size of memory space
1623
- * @returns pointer to memory space
1624
- *
1625
- * @public
1626
- */
1627
- malloc(size) {
1628
- const ptr = this.pdfiumModule.pdfium.wasmExports.malloc(size);
1629
- for (let i = 0; i < size; i++) {
1630
- this.pdfiumModule.pdfium.HEAP8[ptr + i] = 0;
1631
- }
1632
- return ptr;
1633
- }
1634
- /**
1635
- * Free memory space
1636
- * @param ptr pointer to memory space
1637
- *
1638
- * @public
1639
- */
1640
- free(ptr) {
1641
- this.pdfiumModule.pdfium.wasmExports.free(ptr);
1642
- }
1643
- /**
1644
- * Set the rect of specified annotation
1645
- * @param page - page info that the annotation is belonged to
1646
- * @param pagePtr - pointer of page object
1647
- * @param annotationPtr - pointer to annotation object
1648
- * @param inkList - ink lists that added to the annotation
1649
- * @returns whether the ink lists is setted
1650
- *
1651
- * @private
1652
- */
1653
- addInkStroke(page, pagePtr, annotationPtr, annotation) {
1654
- if (!this.setBorderStyle(annotationPtr, PdfAnnotationBorderStyle.SOLID, annotation.strokeWidth)) {
1655
- return false;
1656
- }
1657
- if (!this.setPageAnnoRect(page, pagePtr, annotationPtr, annotation.rect)) {
1658
- return false;
1659
- }
1660
- if (!this.setInkList(page, annotationPtr, annotation.inkList)) {
1661
- return false;
1662
- }
1663
- if (!this.setAnnotString(annotationPtr, 'T', annotation.author || '')) {
1664
- return false;
1665
- }
1666
- if (!this.setAnnotString(annotationPtr, 'M', dateToPdfDate(annotation.modified))) {
1667
- return false;
1668
- }
1669
- if (!this.setAnnotationColor(annotationPtr, {
1670
- color: annotation.color ?? '#FFFF00',
1671
- opacity: annotation.opacity ?? 1,
1672
- }, PdfAnnotationColorType.Color)) {
1673
- return false;
1674
- }
1675
- return true;
1676
- }
1677
- /**
1678
- * Add highlight content to annotation
1679
- * @param page - page info
1680
- * @param annotationPtr - pointer to highlight annotation
1681
- * @param annotation - highlight annotation
1682
- * @returns whether highlight content is added to annotation
1683
- *
1684
- * @private
1685
- */
1686
- addTextMarkupContent(page, pagePtr, annotationPtr, annotation) {
1687
- if (!this.setPageAnnoRect(page, pagePtr, annotationPtr, annotation.rect)) {
1688
- return false;
1689
- }
1690
- if (!this.syncQuadPointsAnno(page, annotationPtr, annotation.segmentRects)) {
1691
- return false;
1692
- }
1693
- if (!this.setAnnotString(annotationPtr, 'Contents', annotation.contents ?? '')) {
1694
- return false;
1695
- }
1696
- if (!this.setAnnotString(annotationPtr, 'T', annotation.author || '')) {
1697
- return false;
1698
- }
1699
- if (!this.setAnnotString(annotationPtr, 'M', dateToPdfDate(annotation.modified))) {
1700
- return false;
1701
- }
1702
- if (!this.setAnnotationColor(annotationPtr, {
1703
- color: annotation.color ?? '#FFFF00',
1704
- opacity: annotation.opacity ?? 1,
1705
- }, PdfAnnotationColorType.Color)) {
1706
- return false;
1707
- }
1708
- return true;
1709
- }
1710
- /**
1711
- * Add contents to stamp annotation
1712
- * @param docPtr - pointer to pdf document object
1713
- * @param page - page info
1714
- * @param pagePtr - pointer to page object
1715
- * @param annotationPtr - pointer to stamp annotation
1716
- * @param rect - rect of stamp annotation
1717
- * @param contents - contents of stamp annotation
1718
- * @returns whether contents is added to annotation
1719
- *
1720
- * @private
1721
- */
1722
- addStampContent(docPtr, page, pagePtr, annotationPtr, rect, contents) {
1723
- for (const content of contents) {
1724
- switch (content.type) {
1725
- case PdfPageObjectType.IMAGE:
1726
- return this.addImageObject(docPtr, page, pagePtr, annotationPtr, rect.origin, content.imageData);
1727
- }
1728
- }
1729
- return false;
1730
- }
1731
- /**
1732
- * Add image object to annotation
1733
- * @param docPtr - pointer to pdf document object
1734
- * @param page - page info
1735
- * @param pagePtr - pointer to page object
1736
- * @param annotationPtr - pointer to stamp annotation
1737
- * @param position - position of image
1738
- * @param imageData - data of image
1739
- * @returns whether image is added to annotation
1740
- *
1741
- * @private
1742
- */
1743
- addImageObject(docPtr, page, pagePtr, annotationPtr, position, imageData) {
1744
- const bytesPerPixel = 4;
1745
- const pixelCount = imageData.width * imageData.height;
1746
- const bitmapBufferPtr = this.malloc(bytesPerPixel * pixelCount);
1747
- if (!bitmapBufferPtr) {
1748
- return false;
1749
- }
1750
- for (let i = 0; i < pixelCount; i++) {
1751
- const red = imageData.data[i * bytesPerPixel];
1752
- const green = imageData.data[i * bytesPerPixel + 1];
1753
- const blue = imageData.data[i * bytesPerPixel + 2];
1754
- const alpha = imageData.data[i * bytesPerPixel + 3];
1755
- this.pdfiumModule.pdfium.setValue(bitmapBufferPtr + i * bytesPerPixel, blue, 'i8');
1756
- this.pdfiumModule.pdfium.setValue(bitmapBufferPtr + i * bytesPerPixel + 1, green, 'i8');
1757
- this.pdfiumModule.pdfium.setValue(bitmapBufferPtr + i * bytesPerPixel + 2, red, 'i8');
1758
- this.pdfiumModule.pdfium.setValue(bitmapBufferPtr + i * bytesPerPixel + 3, alpha, 'i8');
1759
- }
1760
- const format = BitmapFormat.Bitmap_BGRA;
1761
- const bitmapPtr = this.pdfiumModule.FPDFBitmap_CreateEx(imageData.width, imageData.height, format, bitmapBufferPtr, 0);
1762
- if (!bitmapPtr) {
1763
- this.free(bitmapBufferPtr);
1764
- return false;
1765
- }
1766
- const imageObjectPtr = this.pdfiumModule.FPDFPageObj_NewImageObj(docPtr);
1767
- if (!imageObjectPtr) {
1768
- this.pdfiumModule.FPDFBitmap_Destroy(bitmapPtr);
1769
- this.free(bitmapBufferPtr);
1770
- return false;
1771
- }
1772
- if (!this.pdfiumModule.FPDFImageObj_SetBitmap(pagePtr, 0, imageObjectPtr, bitmapPtr)) {
1773
- this.pdfiumModule.FPDFBitmap_Destroy(bitmapPtr);
1774
- this.pdfiumModule.FPDFPageObj_Destroy(imageObjectPtr);
1775
- this.free(bitmapBufferPtr);
1776
- return false;
1777
- }
1778
- const matrixPtr = this.malloc(6 * 4);
1779
- this.pdfiumModule.pdfium.setValue(matrixPtr, imageData.width, 'float');
1780
- this.pdfiumModule.pdfium.setValue(matrixPtr + 4, 0, 'float');
1781
- this.pdfiumModule.pdfium.setValue(matrixPtr + 8, 0, 'float');
1782
- this.pdfiumModule.pdfium.setValue(matrixPtr + 12, imageData.height, 'float');
1783
- this.pdfiumModule.pdfium.setValue(matrixPtr + 16, 0, 'float');
1784
- this.pdfiumModule.pdfium.setValue(matrixPtr + 20, 0, 'float');
1785
- if (!this.pdfiumModule.FPDFPageObj_SetMatrix(imageObjectPtr, matrixPtr)) {
1786
- this.free(matrixPtr);
1787
- this.pdfiumModule.FPDFBitmap_Destroy(bitmapPtr);
1788
- this.pdfiumModule.FPDFPageObj_Destroy(imageObjectPtr);
1789
- this.free(bitmapBufferPtr);
1790
- return false;
1791
- }
1792
- this.free(matrixPtr);
1793
- this.pdfiumModule.FPDFPageObj_Transform(imageObjectPtr, 1, 0, 0, 1, position.x, position.y);
1794
- if (!this.pdfiumModule.FPDFAnnot_AppendObject(annotationPtr, imageObjectPtr)) {
1795
- this.pdfiumModule.FPDFBitmap_Destroy(bitmapPtr);
1796
- this.pdfiumModule.FPDFPageObj_Destroy(imageObjectPtr);
1797
- this.free(bitmapBufferPtr);
1798
- return false;
1799
- }
1800
- this.pdfiumModule.FPDFPage_GenerateContent(pagePtr);
1801
- this.pdfiumModule.FPDFBitmap_Destroy(bitmapPtr);
1802
- this.free(bitmapBufferPtr);
1803
- return true;
1804
- }
1805
- /**
1806
- * Save document to array buffer
1807
- * @param docPtr - pointer to pdf document
1808
- * @returns array buffer contains the pdf content
1809
- *
1810
- * @private
1811
- */
1812
- saveDocument(docPtr) {
1813
- const writerPtr = this.pdfiumModule.PDFiumExt_OpenFileWriter();
1814
- this.pdfiumModule.PDFiumExt_SaveAsCopy(docPtr, writerPtr);
1815
- const size = this.pdfiumModule.PDFiumExt_GetFileWriterSize(writerPtr);
1816
- const dataPtr = this.malloc(size);
1817
- this.pdfiumModule.PDFiumExt_GetFileWriterData(writerPtr, dataPtr, size);
1818
- const buffer = new ArrayBuffer(size);
1819
- const view = new DataView(buffer);
1820
- for (let i = 0; i < size; i++) {
1821
- view.setInt8(i, this.pdfiumModule.pdfium.getValue(dataPtr + i, 'i8'));
1822
- }
1823
- this.free(dataPtr);
1824
- this.pdfiumModule.PDFiumExt_CloseFileWriter(writerPtr);
1825
- return buffer;
1826
- }
1827
- /**
1828
- * Read metadata from pdf document
1829
- * @param docPtr - pointer to pdf document
1830
- * @param key - key of metadata field
1831
- * @returns metadata value
1832
- *
1833
- * @private
1834
- */
1835
- readMetaText(docPtr, key) {
1836
- return readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
1837
- return this.pdfiumModule.FPDF_GetMetaText(docPtr, key, buffer, bufferLength);
1838
- }, this.pdfiumModule.pdfium.UTF16ToString);
1839
- }
1840
- /**
1841
- * Read bookmarks in the pdf document
1842
- * @param docPtr - pointer to pdf document
1843
- * @param rootBookmarkPtr - pointer to root bookmark
1844
- * @returns bookmarks in the pdf document
1845
- *
1846
- * @private
1847
- */
1848
- readPdfBookmarks(docPtr, rootBookmarkPtr = 0) {
1849
- let bookmarkPtr = this.pdfiumModule.FPDFBookmark_GetFirstChild(docPtr, rootBookmarkPtr);
1850
- const bookmarks = [];
1851
- while (bookmarkPtr) {
1852
- const bookmark = this.readPdfBookmark(docPtr, bookmarkPtr);
1853
- bookmarks.push(bookmark);
1854
- const nextBookmarkPtr = this.pdfiumModule.FPDFBookmark_GetNextSibling(docPtr, bookmarkPtr);
1855
- bookmarkPtr = nextBookmarkPtr;
1856
- }
1857
- return bookmarks;
1858
- }
1859
- /**
1860
- * Read bookmark in the pdf document
1861
- * @param docPtr - pointer to pdf document
1862
- * @param bookmarkPtr - pointer to bookmark object
1863
- * @returns pdf bookmark object
1864
- *
1865
- * @private
1866
- */
1867
- readPdfBookmark(docPtr, bookmarkPtr) {
1868
- const title = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
1869
- return this.pdfiumModule.FPDFBookmark_GetTitle(bookmarkPtr, buffer, bufferLength);
1870
- }, this.pdfiumModule.pdfium.UTF16ToString);
1871
- const bookmarks = this.readPdfBookmarks(docPtr, bookmarkPtr);
1872
- const target = this.readPdfBookmarkTarget(docPtr, () => {
1873
- return this.pdfiumModule.FPDFBookmark_GetAction(bookmarkPtr);
1874
- }, () => {
1875
- return this.pdfiumModule.FPDFBookmark_GetDest(docPtr, bookmarkPtr);
1876
- });
1877
- return {
1878
- title,
1879
- target,
1880
- children: bookmarks,
1881
- };
1882
- }
1883
- /**
1884
- * Read text rects in pdf page
1885
- * @param page - pdf page info
1886
- * @param docPtr - pointer to pdf document
1887
- * @param pagePtr - pointer to pdf page
1888
- * @param textPagePtr - pointer to pdf text page
1889
- * @returns text rects in the pdf page
1890
- *
1891
- * @public
1892
- */
1893
- readPageTextRects(page, docPtr, pagePtr, textPagePtr) {
1894
- const rectsCount = this.pdfiumModule.FPDFText_CountRects(textPagePtr, 0, -1);
1895
- const textRects = [];
1896
- for (let i = 0; i < rectsCount; i++) {
1897
- const topPtr = this.malloc(8);
1898
- const leftPtr = this.malloc(8);
1899
- const rightPtr = this.malloc(8);
1900
- const bottomPtr = this.malloc(8);
1901
- const isSucceed = this.pdfiumModule.FPDFText_GetRect(textPagePtr, i, leftPtr, topPtr, rightPtr, bottomPtr);
1902
- if (!isSucceed) {
1903
- this.free(leftPtr);
1904
- this.free(topPtr);
1905
- this.free(rightPtr);
1906
- this.free(bottomPtr);
1907
- continue;
1908
- }
1909
- const left = this.pdfiumModule.pdfium.getValue(leftPtr, 'double');
1910
- const top = this.pdfiumModule.pdfium.getValue(topPtr, 'double');
1911
- const right = this.pdfiumModule.pdfium.getValue(rightPtr, 'double');
1912
- const bottom = this.pdfiumModule.pdfium.getValue(bottomPtr, 'double');
1913
- this.free(leftPtr);
1914
- this.free(topPtr);
1915
- this.free(rightPtr);
1916
- this.free(bottomPtr);
1917
- const deviceXPtr = this.malloc(4);
1918
- const deviceYPtr = this.malloc(4);
1919
- this.pdfiumModule.FPDF_PageToDevice(pagePtr, 0, 0, page.size.width, page.size.height, 0, left, top, deviceXPtr, deviceYPtr);
1920
- const x = this.pdfiumModule.pdfium.getValue(deviceXPtr, 'i32');
1921
- const y = this.pdfiumModule.pdfium.getValue(deviceYPtr, 'i32');
1922
- this.free(deviceXPtr);
1923
- this.free(deviceYPtr);
1924
- const rect = {
1925
- origin: {
1926
- x,
1927
- y,
1928
- },
1929
- size: {
1930
- width: Math.ceil(Math.abs(right - left)),
1931
- height: Math.ceil(Math.abs(top - bottom)),
1932
- },
1933
- };
1934
- const utf16Length = this.pdfiumModule.FPDFText_GetBoundedText(textPagePtr, left, top, right, bottom, 0, 0);
1935
- const bytesCount = (utf16Length + 1) * 2; // include NIL
1936
- const textBuffer = this.malloc(bytesCount);
1937
- this.pdfiumModule.FPDFText_GetBoundedText(textPagePtr, left, top, right, bottom, textBuffer, utf16Length);
1938
- const content = this.pdfiumModule.pdfium.UTF16ToString(textBuffer);
1939
- this.free(textBuffer);
1940
- const charIndex = this.pdfiumModule.FPDFText_GetCharIndexAtPos(textPagePtr, left, top, 2, 2);
1941
- let fontFamily = '';
1942
- let fontSize = rect.size.height;
1943
- if (charIndex >= 0) {
1944
- fontSize = this.pdfiumModule.FPDFText_GetFontSize(textPagePtr, charIndex);
1945
- const fontNameLength = this.pdfiumModule.FPDFText_GetFontInfo(textPagePtr, charIndex, 0, 0, 0);
1946
- const bytesCount = fontNameLength + 1; // include NIL
1947
- const textBufferPtr = this.malloc(bytesCount);
1948
- const flagsPtr = this.malloc(4);
1949
- this.pdfiumModule.FPDFText_GetFontInfo(textPagePtr, charIndex, textBufferPtr, bytesCount, flagsPtr);
1950
- fontFamily = this.pdfiumModule.pdfium.UTF8ToString(textBufferPtr);
1951
- this.free(textBufferPtr);
1952
- this.free(flagsPtr);
1953
- }
1954
- const textRect = {
1955
- content,
1956
- rect,
1957
- font: {
1958
- family: fontFamily,
1959
- size: fontSize,
1960
- },
1961
- };
1962
- textRects.push(textRect);
1963
- }
1964
- return textRects;
1965
- }
1966
- /**
1967
- * Return geometric + logical text layout for one page
1968
- * (glyph-only implementation, no FPDFText_GetRect).
1969
- *
1970
- * @public
1971
- */
1972
- getPageGeometry(doc, page) {
1973
- const label = 'getPageGeometry';
1974
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, label, 'Begin', doc.id);
1975
- /* ── guards ───────────────────────────────────────────── */
1976
- const ctx = this.cache.getContext(doc.id);
1977
- if (!ctx) {
1978
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, label, 'End', doc.id);
1979
- return PdfTaskHelper.reject({
1980
- code: PdfErrorCode.DocNotOpen,
1981
- message: 'document does not open',
1982
- });
1983
- }
1984
- /* ── native handles ──────────────────────────────────── */
1985
- const pageCtx = ctx.acquirePage(page.index);
1986
- const textPagePtr = pageCtx.getTextPage();
1987
- /* ── 1. read ALL glyphs in logical order ─────────────── */
1988
- const glyphCount = this.pdfiumModule.FPDFText_CountChars(textPagePtr);
1989
- const glyphs = [];
1990
- for (let i = 0; i < glyphCount; i++) {
1991
- const g = this.readGlyphInfo(page, pageCtx.pagePtr, textPagePtr, i);
1992
- glyphs.push(g);
1993
- }
1994
- /* ── 2. build visual runs from glyph stream ───────────── */
1995
- const runs = this.buildRunsFromGlyphs(glyphs, textPagePtr);
1996
- /* ── 3. cleanup & resolve task ───────────────────────── */
1997
- pageCtx.release();
1998
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, label, 'End', doc.id);
1999
- return PdfTaskHelper.resolve({ runs });
2000
- }
2001
- /**
2002
- * Group consecutive glyphs that belong to the same CPDF_TextObject
2003
- * using FPDFText_GetTextObject(), and calculate rotation from glyph positions.
2004
- */
2005
- buildRunsFromGlyphs(glyphs, textPagePtr) {
2006
- const runs = [];
2007
- let current = null;
2008
- let curObjPtr = null;
2009
- let bounds = null;
2010
- /** ── main loop ──────────────────────────────────────────── */
2011
- for (let i = 0; i < glyphs.length; i++) {
2012
- const g = glyphs[i];
2013
- /* 1 — find the CPDF_TextObject this glyph belongs to */
2014
- const objPtr = this.pdfiumModule.FPDFText_GetTextObject(textPagePtr, i);
2015
- /* 2 — start a new run when the text object changes */
2016
- if (objPtr !== curObjPtr) {
2017
- curObjPtr = objPtr;
2018
- current = {
2019
- rect: {
2020
- x: g.origin.x,
2021
- y: g.origin.y,
2022
- width: g.size.width,
2023
- height: g.size.height,
2024
- },
2025
- charStart: i,
2026
- glyphs: [],
2027
- };
2028
- bounds = {
2029
- minX: g.origin.x,
2030
- minY: g.origin.y,
2031
- maxX: g.origin.x + g.size.width,
2032
- maxY: g.origin.y + g.size.height,
2033
- };
2034
- runs.push(current);
2035
- }
2036
- /* 3 — append the slim glyph record */
2037
- current.glyphs.push({
2038
- x: g.origin.x,
2039
- y: g.origin.y,
2040
- width: g.size.width,
2041
- height: g.size.height,
2042
- flags: g.isEmpty ? 2 : g.isSpace ? 1 : 0,
2043
- });
2044
- /* 4 — expand the run's bounding rect */
2045
- if (g.isEmpty) {
2046
- continue;
2047
- }
2048
- const right = g.origin.x + g.size.width;
2049
- const bottom = g.origin.y + g.size.height;
2050
- // Update bounds
2051
- bounds.minX = Math.min(bounds.minX, g.origin.x);
2052
- bounds.minY = Math.min(bounds.minY, g.origin.y);
2053
- bounds.maxX = Math.max(bounds.maxX, right);
2054
- bounds.maxY = Math.max(bounds.maxY, bottom);
2055
- // Calculate final rect from bounds
2056
- current.rect.x = bounds.minX;
2057
- current.rect.y = bounds.minY;
2058
- current.rect.width = bounds.maxX - bounds.minX;
2059
- current.rect.height = bounds.maxY - bounds.minY;
2060
- }
2061
- return runs;
2062
- }
2063
- /**
2064
- * Extract glyph geometry + metadata for `charIndex`
2065
- *
2066
- * Returns device–space coordinates:
2067
- * x,y → **top-left** corner (integer-pixels)
2068
- * w,h → width / height (integer-pixels, ≥ 1)
2069
- *
2070
- * And two flags:
2071
- * isSpace → true if the glyph's Unicode code-point is U+0020
2072
- */
2073
- readGlyphInfo(page, pagePtr, textPagePtr, charIndex) {
2074
- // ── native stack temp pointers ──────────────────────────────
2075
- const dx1Ptr = this.malloc(4);
2076
- const dy1Ptr = this.malloc(4);
2077
- const dx2Ptr = this.malloc(4);
2078
- const dy2Ptr = this.malloc(4);
2079
- const rectPtr = this.malloc(16); // 4 floats = 16 bytes
2080
- let x = 0, y = 0, width = 0, height = 0, isSpace = false;
2081
- // ── 1) raw glyph bbox in page-user-space
2082
- if (this.pdfiumModule.FPDFText_GetLooseCharBox(textPagePtr, charIndex, rectPtr)) {
2083
- const left = this.pdfiumModule.pdfium.getValue(rectPtr, 'float');
2084
- const top = this.pdfiumModule.pdfium.getValue(rectPtr + 4, 'float');
2085
- const right = this.pdfiumModule.pdfium.getValue(rectPtr + 8, 'float');
2086
- const bottom = this.pdfiumModule.pdfium.getValue(rectPtr + 12, 'float');
2087
- if (left === right || top === bottom) {
2088
- return {
2089
- origin: { x: 0, y: 0 },
2090
- size: { width: 0, height: 0 },
2091
- isEmpty: true,
2092
- };
2093
- }
2094
- // ── 2) map 2 opposite corners to device-space
2095
- this.pdfiumModule.FPDF_PageToDevice(pagePtr, 0, 0, page.size.width, page.size.height,
2096
- /*rotate=*/ 0, left, top, dx1Ptr, dy1Ptr);
2097
- this.pdfiumModule.FPDF_PageToDevice(pagePtr, 0, 0, page.size.width, page.size.height,
2098
- /*rotate=*/ 0, right, bottom, dx2Ptr, dy2Ptr);
2099
- const x1 = this.pdfiumModule.pdfium.getValue(dx1Ptr, 'i32');
2100
- const y1 = this.pdfiumModule.pdfium.getValue(dy1Ptr, 'i32');
2101
- const x2 = this.pdfiumModule.pdfium.getValue(dx2Ptr, 'i32');
2102
- const y2 = this.pdfiumModule.pdfium.getValue(dy2Ptr, 'i32');
2103
- x = Math.min(x1, x2);
2104
- y = Math.min(y1, y2);
2105
- width = Math.max(1, Math.abs(x2 - x1));
2106
- height = Math.max(1, Math.abs(y2 - y1));
2107
- // ── 3) extra flags ───────────────────────────────────────
2108
- const uc = this.pdfiumModule.FPDFText_GetUnicode(textPagePtr, charIndex);
2109
- isSpace = uc === 32;
2110
- }
2111
- // ── free tmps ───────────────────────────────────────────────
2112
- [rectPtr, dx1Ptr, dy1Ptr, dx2Ptr, dy2Ptr].forEach((p) => this.free(p));
2113
- return {
2114
- origin: { x, y },
2115
- size: { width, height },
2116
- ...(isSpace && { isSpace }),
2117
- };
2118
- }
2119
- /**
2120
- * Geometry-only text extraction
2121
- * ------------------------------------------
2122
- * Returns every glyph on the requested page
2123
- * in the logical order delivered by PDFium.
2124
- *
2125
- * The promise resolves to an array of objects:
2126
- * {
2127
- * idx: number; // glyph index on the page (0…n-1)
2128
- * origin: { x: number; y: number };
2129
- * size: { width: number; height: number };
2130
- * angle: number; // degrees, counter-clock-wise
2131
- * isSpace: boolean; // true → U+0020
2132
- * }
2133
- *
2134
- * No Unicode is included; front-end decides whether to hydrate it.
2135
- */
2136
- getPageGlyphs(doc, page) {
2137
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'getPageGlyphs', doc, page);
2138
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'getPageGlyphs', 'Begin', doc.id);
2139
- // ── 1) safety: document handle must be alive ───────────────
2140
- const ctx = this.cache.getContext(doc.id);
2141
- if (!ctx) {
2142
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'getPageGlyphs', 'End', doc.id);
2143
- return PdfTaskHelper.reject({
2144
- code: PdfErrorCode.DocNotOpen,
2145
- message: 'document does not open',
2146
- });
2147
- }
2148
- // ── 2) load page + text page handles ───────────────────────
2149
- const pageCtx = ctx.acquirePage(page.index);
2150
- const textPagePtr = pageCtx.getTextPage();
2151
- // ── 3) iterate all glyphs in logical order ─────────────────
2152
- const total = this.pdfiumModule.FPDFText_CountChars(textPagePtr);
2153
- const glyphs = new Array(total);
2154
- for (let i = 0; i < total; i++) {
2155
- const g = this.readGlyphInfo(page, pageCtx.pagePtr, textPagePtr, i);
2156
- if (g.isEmpty) {
2157
- continue;
2158
- }
2159
- glyphs[i] = { ...g };
2160
- }
2161
- // ── 4) clean-up native handles ─────────────────────────────
2162
- pageCtx.release();
2163
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'getPageGlyphs', 'End', doc.id);
2164
- return PdfTaskHelper.resolve(glyphs);
2165
- }
2166
- readCharBox(page, pagePtr, textPagePtr, charIndex) {
2167
- const topPtr = this.malloc(8);
2168
- const leftPtr = this.malloc(8);
2169
- const bottomPtr = this.malloc(8);
2170
- const rightPtr = this.malloc(8);
2171
- let x = 0;
2172
- let y = 0;
2173
- let width = 0;
2174
- let height = 0;
2175
- if (this.pdfiumModule.FPDFText_GetCharBox(textPagePtr, charIndex, leftPtr, rightPtr, bottomPtr, topPtr)) {
2176
- const top = this.pdfiumModule.pdfium.getValue(topPtr, 'double');
2177
- const left = this.pdfiumModule.pdfium.getValue(leftPtr, 'double');
2178
- const bottom = this.pdfiumModule.pdfium.getValue(bottomPtr, 'double');
2179
- const right = this.pdfiumModule.pdfium.getValue(rightPtr, 'double');
2180
- const deviceXPtr = this.malloc(4);
2181
- const deviceYPtr = this.malloc(4);
2182
- this.pdfiumModule.FPDF_PageToDevice(pagePtr, 0, 0, page.size.width, page.size.height, 0, left, top, deviceXPtr, deviceYPtr);
2183
- x = this.pdfiumModule.pdfium.getValue(deviceXPtr, 'i32');
2184
- y = this.pdfiumModule.pdfium.getValue(deviceYPtr, 'i32');
2185
- this.free(deviceXPtr);
2186
- this.free(deviceYPtr);
2187
- width = Math.ceil(Math.abs(right - left));
2188
- height = Math.ceil(Math.abs(top - bottom));
2189
- }
2190
- this.free(topPtr);
2191
- this.free(leftPtr);
2192
- this.free(bottomPtr);
2193
- this.free(rightPtr);
2194
- return {
2195
- origin: {
2196
- x,
2197
- y,
2198
- },
2199
- size: {
2200
- width,
2201
- height,
2202
- },
2203
- };
2204
- }
2205
- /**
2206
- * Read page annotations
2207
- * @param page - page info
2208
- * @param docPtr - pointer to pdf document
2209
- * @param pagePtr - pointer to pdf page
2210
- * @param textPagePtr - pointe to pdf text page
2211
- * @param scaleFactor - scale factor
2212
- * @param rotation - rotation angle
2213
- * @returns annotations on the pdf page
2214
- *
2215
- * @private
2216
- */
2217
- readPageAnnotations(ctx, page) {
2218
- const pageCtx = ctx.acquirePage(page.index);
2219
- const annotationCount = this.pdfiumModule.FPDFPage_GetAnnotCount(pageCtx.pagePtr);
2220
- const annotations = [];
2221
- for (let i = 0; i < annotationCount; i++) {
2222
- pageCtx.withAnnotation(i, (annotPtr) => {
2223
- const annotation = this.readPageAnnotation(page, pageCtx, annotPtr, i);
2224
- if (annotation) {
2225
- annotations.push(annotation);
2226
- }
2227
- });
2228
- }
2229
- return annotations;
2230
- }
2231
- /**
2232
- * Read pdf annotation from pdf document
2233
- * @param page - pdf page infor
2234
- * @param docPtr - pointer to pdf document object
2235
- * @param pagePtr - pointer to pdf page object
2236
- * @param textPagePtr - pointer to pdf text page object
2237
- * @param formHandle - form handle
2238
- * @param index - index of annotation in the pdf page
2239
- * @param scaleFactor - factor of scalling
2240
- * @param rotation - rotation angle
2241
- * @returns pdf annotation
2242
- *
2243
- * @private
2244
- */
2245
- readPageAnnotation(page, pageCtx, annotationPtr, index) {
2246
- const subType = this.pdfiumModule.FPDFAnnot_GetSubtype(annotationPtr);
2247
- let annotation;
2248
- switch (subType) {
2249
- case PdfAnnotationSubtype.TEXT:
2250
- {
2251
- annotation = this.readPdfTextAnno(page, pageCtx.pagePtr, annotationPtr, index);
2252
- }
2253
- break;
2254
- case PdfAnnotationSubtype.FREETEXT:
2255
- {
2256
- annotation = this.readPdfFreeTextAnno(page, pageCtx.pagePtr, annotationPtr, index);
2257
- }
2258
- break;
2259
- case PdfAnnotationSubtype.LINK:
2260
- {
2261
- annotation = this.readPdfLinkAnno(page, pageCtx.docPtr, pageCtx.pagePtr, pageCtx.getTextPage(), annotationPtr, index);
2262
- }
2263
- break;
2264
- case PdfAnnotationSubtype.WIDGET:
2265
- {
2266
- annotation = this.readPdfWidgetAnno(page, pageCtx.pagePtr, annotationPtr, pageCtx.getFormHandle(), index);
2267
- }
2268
- break;
2269
- case PdfAnnotationSubtype.FILEATTACHMENT:
2270
- {
2271
- annotation = this.readPdfFileAttachmentAnno(page, pageCtx.pagePtr, annotationPtr, index);
2272
- }
2273
- break;
2274
- case PdfAnnotationSubtype.INK:
2275
- {
2276
- annotation = this.readPdfInkAnno(page, pageCtx.pagePtr, annotationPtr, index);
2277
- }
2278
- break;
2279
- case PdfAnnotationSubtype.POLYGON:
2280
- {
2281
- annotation = this.readPdfPolygonAnno(page, pageCtx.pagePtr, annotationPtr, index);
2282
- }
2283
- break;
2284
- case PdfAnnotationSubtype.POLYLINE:
2285
- {
2286
- annotation = this.readPdfPolylineAnno(page, pageCtx.pagePtr, annotationPtr, index);
2287
- }
2288
- break;
2289
- case PdfAnnotationSubtype.LINE:
2290
- {
2291
- annotation = this.readPdfLineAnno(page, pageCtx.pagePtr, annotationPtr, index);
2292
- }
2293
- break;
2294
- case PdfAnnotationSubtype.HIGHLIGHT:
2295
- annotation = this.readPdfHighlightAnno(page, pageCtx.pagePtr, annotationPtr, index);
2296
- break;
2297
- case PdfAnnotationSubtype.STAMP:
2298
- {
2299
- annotation = this.readPdfStampAnno(pageCtx.docPtr, page, pageCtx.pagePtr, annotationPtr, index);
2300
- }
2301
- break;
2302
- case PdfAnnotationSubtype.SQUARE:
2303
- {
2304
- annotation = this.readPdfSquareAnno(page, pageCtx.pagePtr, annotationPtr, index);
2305
- }
2306
- break;
2307
- case PdfAnnotationSubtype.CIRCLE:
2308
- {
2309
- annotation = this.readPdfCircleAnno(page, pageCtx.pagePtr, annotationPtr, index);
2310
- }
2311
- break;
2312
- case PdfAnnotationSubtype.UNDERLINE:
2313
- {
2314
- annotation = this.readPdfUnderlineAnno(page, pageCtx.pagePtr, annotationPtr, index);
2315
- }
2316
- break;
2317
- case PdfAnnotationSubtype.SQUIGGLY:
2318
- {
2319
- annotation = this.readPdfSquigglyAnno(page, pageCtx.pagePtr, annotationPtr, index);
2320
- }
2321
- break;
2322
- case PdfAnnotationSubtype.STRIKEOUT:
2323
- {
2324
- annotation = this.readPdfStrikeOutAnno(page, pageCtx.pagePtr, annotationPtr, index);
2325
- }
2326
- break;
2327
- case PdfAnnotationSubtype.CARET:
2328
- {
2329
- annotation = this.readPdfCaretAnno(page, pageCtx.pagePtr, annotationPtr, index);
2330
- }
2331
- break;
2332
- default:
2333
- {
2334
- annotation = this.readPdfAnno(page, pageCtx.pagePtr, subType, annotationPtr, index);
2335
- }
2336
- break;
2337
- }
2338
- return annotation;
2339
- }
2340
- /**
2341
- * Return the colour stored directly in the annotation dictionary's `/C` entry.
2342
- *
2343
- * Most PDFs created by Acrobat, Microsoft Office, LaTeX, etc. include this entry.
2344
- * When the key is absent (common in macOS Preview, Chrome, Drawboard) the call
2345
- * fails and the function returns `undefined`.
2346
- *
2347
- * @param annotationPtr - pointer to an `FPDF_ANNOTATION`
2348
- * @returns An RGBA tuple (0-255 channels) or `undefined` if no `/C` entry exists
2349
- *
2350
- * @private
2351
- */
2352
- readAnnotationColor(annotationPtr, colorType = PdfAnnotationColorType.Color) {
2353
- const rPtr = this.malloc(4);
2354
- const gPtr = this.malloc(4);
2355
- const bPtr = this.malloc(4);
2356
- const aPtr = this.malloc(4);
2357
- // colourType 0 = "colour" (stroke/fill); other types are interior/border
2358
- const ok = this.pdfiumModule.EPDFAnnot_GetColor(annotationPtr, colorType, rPtr, gPtr, bPtr, aPtr);
2359
- let colour;
2360
- if (ok) {
2361
- colour = {
2362
- red: this.pdfiumModule.pdfium.getValue(rPtr, 'i32') & 0xff,
2363
- green: this.pdfiumModule.pdfium.getValue(gPtr, 'i32') & 0xff,
2364
- blue: this.pdfiumModule.pdfium.getValue(bPtr, 'i32') & 0xff,
2365
- alpha: this.pdfiumModule.pdfium.getValue(aPtr, 'i32') & 0xff, // 0 = transparent, 255 = opaque
2366
- };
2367
- }
2368
- this.free(rPtr);
2369
- this.free(gPtr);
2370
- this.free(bPtr);
2371
- this.free(aPtr);
2372
- return colour;
2373
- }
2374
- /* --------------------------------------------------------------------------- */
2375
- /**
2376
- * Resolve the visible fill colour for **Highlight / Underline / StrikeOut /
2377
- * Squiggly** markup annotations.
2378
- *
2379
- * Resolution order (first non-`undefined` wins):
2380
- * 1. `/C` dictionary entry – fast, present in Acrobat / Office PDFs
2381
- * 2. Appearance-stream objects – drills into paths & nested forms
2382
- * 3. Hard-coded fallback (Acrobat-style opaque yellow)
2383
- *
2384
- * @param annotationPtr - pointer to an `FPDF_ANNOTATION`
2385
- * @param fallback - colour to use when the PDF stores no tint at all
2386
- * @returns WebAlphaColor with hex color and opacity (0-1)
2387
- *
2388
- * @private
2389
- */
2390
- resolveAnnotationColor(annotationPtr, colorType = PdfAnnotationColorType.Color, fallback = { red: 255, green: 245, blue: 155, alpha: 255 }) {
2391
- const pdfColor = this.readAnnotationColor(annotationPtr, colorType) ?? fallback;
2392
- return pdfAlphaColorToWebAlphaColor(pdfColor);
2393
- }
2394
- /**
2395
- * Set the fill/stroke colour for a **Highlight / Underline / StrikeOut / Squiggly** markup annotation.
2396
- *
2397
- * @param annotationPtr - pointer to the annotation whose colour is being set
2398
- * @param webAlphaColor - WebAlphaColor with hex color and opacity (0-1)
2399
- * @param shouldClearAP - whether to clear the /AP entry
2400
- * @param which - which colour to set (0 = fill, 1 = stroke)
2401
- * @returns `true` if the operation was successful
2402
- *
2403
- * @private
2404
- */
2405
- setAnnotationColor(annotationPtr, webAlphaColor, colorType = PdfAnnotationColorType.Color) {
2406
- const pdfAlphaColor = webAlphaColorToPdfAlphaColor(webAlphaColor);
2407
- return this.pdfiumModule.EPDFAnnot_SetColor(annotationPtr, colorType, pdfAlphaColor.red & 0xff, pdfAlphaColor.green & 0xff, pdfAlphaColor.blue & 0xff, (pdfAlphaColor.alpha ?? 255) & 0xff);
2408
- }
2409
- /**
2410
- * Border‐style + width helper
2411
- *
2412
- * Tries the new PDFium helper `EPDFAnnot_GetBorderStyle()` (patch series
2413
- * 9 July 2025).
2414
- *
2415
- * @param annotationPtr pointer to an `FPDF_ANNOTATION`
2416
- * @returns `{ ok, style, width }`
2417
- * • `ok` – `true` when the call succeeded
2418
- * • `style` – `PdfAnnotationBorderStyle` enum
2419
- * • `width` – stroke-width in points (defaults to 0 pt)
2420
- */
2421
- getBorderStyle(annotationPtr) {
2422
- /* 1 ── allocate tmp storage for the returned width ─────────────── */
2423
- const widthPtr = this.malloc(4);
2424
- let width = 0;
2425
- let style = PdfAnnotationBorderStyle.UNKNOWN;
2426
- let ok = false;
2427
- style = this.pdfiumModule.EPDFAnnot_GetBorderStyle(annotationPtr, widthPtr);
2428
- width = this.pdfiumModule.pdfium.getValue(widthPtr, 'float');
2429
- ok = style !== PdfAnnotationBorderStyle.UNKNOWN;
2430
- this.free(widthPtr);
2431
- return { ok, style, width };
2432
- }
2433
- setBorderStyle(annotationPtr, style, width) {
2434
- return this.pdfiumModule.EPDFAnnot_SetBorderStyle(annotationPtr, style, width);
2435
- }
2436
- /**
2437
- * Border-effect (“cloudy”) helper
2438
- *
2439
- * Calls the new PDFium function `EPDFAnnot_GetBorderEffect()` (July 2025).
2440
- *
2441
- * @param annotationPtr pointer to an `FPDF_ANNOTATION`
2442
- * @returns `{ ok, intensity }`
2443
- * • `ok` – `true` when the annotation *does* have a
2444
- * valid cloudy-border effect
2445
- * • `intensity` – radius/intensity value (0 when `ok` is false)
2446
- */
2447
- getBorderEffect(annotationPtr) {
2448
- const intensityPtr = this.malloc(4);
2449
- const ok = !!this.pdfiumModule.EPDFAnnot_GetBorderEffect(annotationPtr, intensityPtr);
2450
- const intensity = ok ? this.pdfiumModule.pdfium.getValue(intensityPtr, 'float') : 0;
2451
- this.free(intensityPtr);
2452
- return { ok, intensity };
2453
- }
2454
- /**
2455
- * Rectangle-differences helper ( /RD array on Square / Circle annots )
2456
- *
2457
- * Calls `EPDFAnnot_GetRectangleDifferences()` introduced in July 2025.
2458
- *
2459
- * @param annotationPtr pointer to an `FPDF_ANNOTATION`
2460
- * @returns `{ ok, left, top, right, bottom }`
2461
- * • `ok` – `true` when the annotation *has* an /RD entry
2462
- * • the four floats are 0 when `ok` is false
2463
- */
2464
- getRectangleDifferences(annotationPtr) {
2465
- /* tmp storage ─────────────────────────────────────────── */
2466
- const lPtr = this.malloc(4);
2467
- const tPtr = this.malloc(4);
2468
- const rPtr = this.malloc(4);
2469
- const bPtr = this.malloc(4);
2470
- const ok = !!this.pdfiumModule.EPDFAnnot_GetRectangleDifferences(annotationPtr, lPtr, tPtr, rPtr, bPtr);
2471
- const pdf = this.pdfiumModule.pdfium;
2472
- const left = pdf.getValue(lPtr, 'float');
2473
- const top = pdf.getValue(tPtr, 'float');
2474
- const right = pdf.getValue(rPtr, 'float');
2475
- const bottom = pdf.getValue(bPtr, 'float');
2476
- /* cleanup ─────────────────────────────────────────────── */
2477
- this.free(lPtr);
2478
- this.free(tPtr);
2479
- this.free(rPtr);
2480
- this.free(bPtr);
2481
- return { ok, left, top, right, bottom };
2482
- }
2483
- /**
2484
- * Dash-pattern helper ( /BS → /D array, dashed borders only )
2485
- *
2486
- * Uses the two new PDFium helpers:
2487
- * • `EPDFAnnot_GetBorderDashPatternCount`
2488
- * • `EPDFAnnot_GetBorderDashPattern`
2489
- *
2490
- * @param annotationPtr pointer to an `FPDF_ANNOTATION`
2491
- * @returns `{ ok, pattern }`
2492
- * • `ok` – `true` when the annot is dashed *and* the array
2493
- * was retrieved successfully
2494
- * • `pattern` – numeric array of dash/space lengths (empty when `ok` is false)
2495
- */
2496
- getBorderDashPattern(annotationPtr) {
2497
- const count = this.pdfiumModule.EPDFAnnot_GetBorderDashPatternCount(annotationPtr);
2498
- if (count === 0) {
2499
- return { ok: false, pattern: [] };
2500
- }
2501
- /* allocate `count` floats on the WASM heap */
2502
- const arrPtr = this.malloc(4 * count);
2503
- const okNative = !!this.pdfiumModule.EPDFAnnot_GetBorderDashPattern(annotationPtr, arrPtr, count);
2504
- /* copy out */
2505
- const pattern = [];
2506
- if (okNative) {
2507
- const pdf = this.pdfiumModule.pdfium;
2508
- for (let i = 0; i < count; i++) {
2509
- pattern.push(pdf.getValue(arrPtr + 4 * i, 'float'));
2510
- }
2511
- }
2512
- this.free(arrPtr);
2513
- return { ok: okNative, pattern };
2514
- }
2515
- /**
2516
- * Read `/QuadPoints` from any annotation and convert each quadrilateral to
2517
- * device-space coordinates.
2518
- *
2519
- * The four points are returned in natural reading order:
2520
- * `p1 → p2` (top edge) and `p4 → p3` (bottom edge).
2521
- * This preserves the true shape for rotated / skewed text, whereas callers
2522
- * that only need axis-aligned boxes can collapse each quad themselves.
2523
- *
2524
- * @param page - logical page info object (`PdfPageObject`)
2525
- * @param annotationPtr - pointer to the annotation whose quads are needed
2526
- * @returns Array of `Rect` objects (`[]` if the annotation has no quads)
2527
- *
2528
- * @private
2529
- */
2530
- getQuadPointsAnno(page, annotationPtr) {
2531
- const quadCount = this.pdfiumModule.FPDFAnnot_CountAttachmentPoints(annotationPtr);
2532
- if (quadCount === 0)
2533
- return [];
2534
- const FS_QUADPOINTSF_SIZE = 8 * 4; // eight floats, 32 bytes
2535
- const quads = [];
2536
- for (let qi = 0; qi < quadCount; qi++) {
2537
- const quadPtr = this.malloc(FS_QUADPOINTSF_SIZE);
2538
- const ok = this.pdfiumModule.FPDFAnnot_GetAttachmentPoints(annotationPtr, qi, quadPtr);
2539
- if (ok) {
2540
- // read the eight floats
2541
- const xs = [];
2542
- const ys = [];
2543
- for (let i = 0; i < 4; i++) {
2544
- const base = quadPtr + i * 8; // 8 bytes per point (x+y)
2545
- xs.push(this.pdfiumModule.pdfium.getValue(base, 'float'));
2546
- ys.push(this.pdfiumModule.pdfium.getValue(base + 4, 'float'));
2547
- }
2548
- // convert to device-space
2549
- const p1 = this.convertPagePointToDevicePoint(page, { x: xs[0], y: ys[0] });
2550
- const p2 = this.convertPagePointToDevicePoint(page, { x: xs[1], y: ys[1] });
2551
- const p3 = this.convertPagePointToDevicePoint(page, { x: xs[2], y: ys[2] });
2552
- const p4 = this.convertPagePointToDevicePoint(page, { x: xs[3], y: ys[3] });
2553
- quads.push({ p1, p2, p3, p4 });
2554
- }
2555
- this.free(quadPtr);
2556
- }
2557
- return quads.map(quadToRect);
2558
- }
2559
- /**
2560
- * Set the quadrilaterals for a **Highlight / Underline / StrikeOut / Squiggly** markup annotation.
2561
- *
2562
- * @param page - logical page info object (`PdfPageObject`)
2563
- * @param annotationPtr - pointer to the annotation whose quads are needed
2564
- * @param rects - array of `Rect` objects (`[]` if the annotation has no quads)
2565
- * @returns `true` if the operation was successful
2566
- *
2567
- * @private
2568
- */
2569
- syncQuadPointsAnno(page, annotPtr, rects) {
2570
- const FS_QUADPOINTSF_SIZE = 8 * 4; // eight floats, 32 bytes
2571
- const pdf = this.pdfiumModule.pdfium;
2572
- const count = this.pdfiumModule.FPDFAnnot_CountAttachmentPoints(annotPtr);
2573
- const buf = this.malloc(FS_QUADPOINTSF_SIZE);
2574
- /** write one quad into `buf` in annotation space */
2575
- const writeQuad = (r) => {
2576
- const q = rectToQuad(r); // TL, TR, BR, BL
2577
- const p1 = this.convertDevicePointToPagePoint(page, q.p1);
2578
- const p2 = this.convertDevicePointToPagePoint(page, q.p2);
2579
- const p3 = this.convertDevicePointToPagePoint(page, q.p3); // BR
2580
- const p4 = this.convertDevicePointToPagePoint(page, q.p4); // BL
2581
- // PDF QuadPoints order: BL, BR, TL, TR (bottom-left, bottom-right, top-left, top-right)
2582
- pdf.setValue(buf + 0, p1.x, 'float'); // BL (bottom-left)
2583
- pdf.setValue(buf + 4, p1.y, 'float');
2584
- pdf.setValue(buf + 8, p2.x, 'float'); // BR (bottom-right)
2585
- pdf.setValue(buf + 12, p2.y, 'float');
2586
- pdf.setValue(buf + 16, p4.x, 'float'); // TL (top-left)
2587
- pdf.setValue(buf + 20, p4.y, 'float');
2588
- pdf.setValue(buf + 24, p3.x, 'float'); // TR (top-right)
2589
- pdf.setValue(buf + 28, p3.y, 'float');
2590
- };
2591
- /* ----------------------------------------------------------------------- */
2592
- /* 1. overwrite the quads that already exist */
2593
- const min = Math.min(count, rects.length);
2594
- for (let i = 0; i < min; i++) {
2595
- writeQuad(rects[i]);
2596
- if (!this.pdfiumModule.FPDFAnnot_SetAttachmentPoints(annotPtr, i, buf)) {
2597
- this.free(buf);
2598
- return false;
2599
- }
2600
- }
2601
- /* 2. append new quads if rects.length > count */
2602
- for (let i = count; i < rects.length; i++) {
2603
- writeQuad(rects[i]);
2604
- if (!this.pdfiumModule.FPDFAnnot_AppendAttachmentPoints(annotPtr, buf)) {
2605
- this.free(buf);
2606
- return false;
2607
- }
2608
- }
2609
- this.free(buf);
2610
- return true;
2611
- }
2612
- /**
2613
- * Read ink list from annotation
2614
- * @param page - logical page info object (`PdfPageObject`)
2615
- * @param annotationPtr - pointer to the annotation whose ink list is needed
2616
- * @returns ink list
2617
- */
2618
- getInkList(page, annotationPtr) {
2619
- const inkList = [];
2620
- const count = this.pdfiumModule.FPDFAnnot_GetInkListCount(annotationPtr);
2621
- for (let i = 0; i < count; i++) {
2622
- const points = [];
2623
- const pointsCount = this.pdfiumModule.FPDFAnnot_GetInkListPath(annotationPtr, i, 0, 0);
2624
- if (pointsCount > 0) {
2625
- const pointMemorySize = 8;
2626
- const pointsPtr = this.malloc(pointsCount * pointMemorySize);
2627
- this.pdfiumModule.FPDFAnnot_GetInkListPath(annotationPtr, i, pointsPtr, pointsCount);
2628
- for (let j = 0; j < pointsCount; j++) {
2629
- const pointX = this.pdfiumModule.pdfium.getValue(pointsPtr + j * 8, 'float');
2630
- const pointY = this.pdfiumModule.pdfium.getValue(pointsPtr + j * 8 + 4, 'float');
2631
- const { x, y } = this.convertPagePointToDevicePoint(page, {
2632
- x: pointX,
2633
- y: pointY,
2634
- });
2635
- points.push({ x, y });
2636
- }
2637
- this.free(pointsPtr);
2638
- }
2639
- inkList.push({ points });
2640
- }
2641
- return inkList;
2642
- }
2643
- /**
2644
- * Add ink list to annotation
2645
- * @param page - logical page info object (`PdfPageObject`)
2646
- * @param annotationPtr - pointer to the annotation whose ink list is needed
2647
- * @param annotation - annotation object (`PdfInkAnnoObject`)
2648
- * @returns `true` if the operation was successful
2649
- */
2650
- setInkList(page, annotationPtr, inkList) {
2651
- for (const inkStroke of inkList) {
2652
- const inkPointsCount = inkStroke.points.length;
2653
- const inkPointsPtr = this.malloc(inkPointsCount * 8);
2654
- for (let i = 0; i < inkPointsCount; i++) {
2655
- const point = inkStroke.points[i];
2656
- const { x, y } = this.convertDevicePointToPagePoint(page, point);
2657
- this.pdfiumModule.pdfium.setValue(inkPointsPtr + i * 8, x, 'float');
2658
- this.pdfiumModule.pdfium.setValue(inkPointsPtr + i * 8 + 4, y, 'float');
2659
- }
2660
- if (this.pdfiumModule.FPDFAnnot_AddInkStroke(annotationPtr, inkPointsPtr, inkPointsCount) === -1) {
2661
- this.free(inkPointsPtr);
2662
- return false;
2663
- }
2664
- this.free(inkPointsPtr);
2665
- }
2666
- return true;
2667
- }
2668
- /**
2669
- * Read pdf text annotation
2670
- * @param page - pdf page infor
2671
- * @param pagePtr - pointer to pdf page object
2672
- * @param annotationPtr - pointer to pdf annotation
2673
- * @param index - index of annotation in the pdf page
2674
- * @returns pdf text annotation
2675
- *
2676
- * @private
2677
- */
2678
- readPdfTextAnno(page, pagePtr, annotationPtr, index) {
2679
- const annoRect = this.readPageAnnoRect(annotationPtr);
2680
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, annoRect);
2681
- const author = this.getAnnotString(annotationPtr, 'T');
2682
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2683
- const modified = pdfDateToDate(modifiedRaw);
2684
- const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
2685
- const state = this.getAnnotString(annotationPtr, 'State');
2686
- const stateModel = this.getAnnotString(annotationPtr, 'StateModel');
2687
- const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
2688
- const inReplyToId = this.getInReplyToId(pagePtr, annotationPtr);
2689
- return {
2690
- pageIndex: page.index,
2691
- id: index,
2692
- type: PdfAnnotationSubtype.TEXT,
2693
- contents,
2694
- ...webAlphaColor,
2695
- rect,
2696
- inReplyToId,
2697
- author,
2698
- modified,
2699
- state,
2700
- stateModel,
2701
- };
2702
- }
2703
- /**
2704
- * Read pdf freetext annotation
2705
- * @param page - pdf page infor
2706
- * @param pagePtr - pointer to pdf page object
2707
- * @param annotationPtr - pointer to pdf annotation
2708
- * @param index - index of annotation in the pdf page
2709
- * @returns pdf freetext annotation
2710
- *
2711
- * @private
2712
- */
2713
- readPdfFreeTextAnno(page, pagePtr, annotationPtr, index) {
2714
- const annoRect = this.readPageAnnoRect(annotationPtr);
2715
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, annoRect);
2716
- const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
2717
- const author = this.getAnnotString(annotationPtr, 'T');
2718
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2719
- const modified = pdfDateToDate(modifiedRaw);
2720
- return {
2721
- pageIndex: page.index,
2722
- id: index,
2723
- type: PdfAnnotationSubtype.FREETEXT,
2724
- contents,
2725
- author,
2726
- modified,
2727
- rect,
2728
- };
2729
- }
2730
- /**
2731
- * Read pdf link annotation from pdf document
2732
- * @param page - pdf page infor
2733
- * @param docPtr - pointer to pdf document object
2734
- * @param pagePtr - pointer to pdf page object
2735
- * @param textPagePtr - pointer to pdf text page object
2736
- * @param annotationPtr - pointer to pdf annotation
2737
- * @param index - index of annotation in the pdf page
2738
- * @returns pdf link annotation
2739
- *
2740
- * @private
2741
- */
2742
- readPdfLinkAnno(page, docPtr, pagePtr, textPagePtr, annotationPtr, index) {
2743
- const linkPtr = this.pdfiumModule.FPDFAnnot_GetLink(annotationPtr);
2744
- if (!linkPtr) {
2745
- return;
2746
- }
2747
- const annoRect = this.readPageAnnoRect(annotationPtr);
2748
- const { left, top, right, bottom } = annoRect;
2749
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, annoRect);
2750
- const author = this.getAnnotString(annotationPtr, 'T');
2751
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2752
- const modified = pdfDateToDate(modifiedRaw);
2753
- const utf16Length = this.pdfiumModule.FPDFText_GetBoundedText(textPagePtr, left, top, right, bottom, 0, 0);
2754
- const bytesCount = (utf16Length + 1) * 2; // include NIL
2755
- const textBufferPtr = this.malloc(bytesCount);
2756
- this.pdfiumModule.FPDFText_GetBoundedText(textPagePtr, left, top, right, bottom, textBufferPtr, utf16Length);
2757
- const text = this.pdfiumModule.pdfium.UTF16ToString(textBufferPtr);
2758
- this.free(textBufferPtr);
2759
- const target = this.readPdfLinkAnnoTarget(docPtr, () => {
2760
- return this.pdfiumModule.FPDFLink_GetAction(linkPtr);
2761
- }, () => {
2762
- return this.pdfiumModule.FPDFLink_GetDest(docPtr, linkPtr);
2763
- });
2764
- return {
2765
- pageIndex: page.index,
2766
- id: index,
2767
- type: PdfAnnotationSubtype.LINK,
2768
- text,
2769
- target,
2770
- rect,
2771
- author,
2772
- modified,
2773
- };
2774
- }
2775
- /**
2776
- * Read pdf widget annotation
2777
- * @param page - pdf page infor
2778
- * @param pagePtr - pointer to pdf page object
2779
- * @param annotationPtr - pointer to pdf annotation
2780
- * @param formHandle - form handle
2781
- * @param index - index of annotation in the pdf page
2782
- * @returns pdf widget annotation
2783
- *
2784
- * @private
2785
- */
2786
- readPdfWidgetAnno(page, pagePtr, annotationPtr, formHandle, index) {
2787
- const pageRect = this.readPageAnnoRect(annotationPtr);
2788
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2789
- const author = this.getAnnotString(annotationPtr, 'T');
2790
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2791
- const modified = pdfDateToDate(modifiedRaw);
2792
- const field = this.readPdfWidgetAnnoField(formHandle, annotationPtr);
2793
- return {
2794
- pageIndex: page.index,
2795
- id: index,
2796
- type: PdfAnnotationSubtype.WIDGET,
2797
- rect,
2798
- field,
2799
- author,
2800
- modified,
2801
- };
2802
- }
2803
- /**
2804
- * Read pdf file attachment annotation
2805
- * @param page - pdf page infor
2806
- * @param pagePtr - pointer to pdf page object
2807
- * @param annotationPtr - pointer to pdf annotation
2808
- * @param index - index of annotation in the pdf page
2809
- * @returns pdf file attachment annotation
2810
- *
2811
- * @private
2812
- */
2813
- readPdfFileAttachmentAnno(page, pagePtr, annotationPtr, index) {
2814
- const pageRect = this.readPageAnnoRect(annotationPtr);
2815
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2816
- const author = this.getAnnotString(annotationPtr, 'T');
2817
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2818
- const modified = pdfDateToDate(modifiedRaw);
2819
- return {
2820
- pageIndex: page.index,
2821
- id: index,
2822
- type: PdfAnnotationSubtype.FILEATTACHMENT,
2823
- rect,
2824
- author,
2825
- modified,
2826
- };
2827
- }
2828
- /**
2829
- * Read pdf ink annotation
2830
- * @param page - pdf page infor
2831
- * @param pagePtr - pointer to pdf page object
2832
- * @param annotationPtr - pointer to pdf annotation
2833
- * @param index - index of annotation in the pdf page
2834
- * @returns pdf ink annotation
2835
- *
2836
- * @private
2837
- */
2838
- readPdfInkAnno(page, pagePtr, annotationPtr, index) {
2839
- const pageRect = this.readPageAnnoRect(annotationPtr);
2840
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2841
- const author = this.getAnnotString(annotationPtr, 'T');
2842
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2843
- const modified = pdfDateToDate(modifiedRaw);
2844
- const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
2845
- const { width: strokeWidth } = this.getBorderStyle(annotationPtr);
2846
- const inkList = this.getInkList(page, annotationPtr);
2847
- return {
2848
- pageIndex: page.index,
2849
- id: index,
2850
- type: PdfAnnotationSubtype.INK,
2851
- ...webAlphaColor,
2852
- strokeWidth,
2853
- rect,
2854
- inkList,
2855
- author,
2856
- modified,
2857
- };
2858
- }
2859
- /**
2860
- * Read pdf polygon annotation
2861
- * @param page - pdf page infor
2862
- * @param pagePtr - pointer to pdf page object
2863
- * @param annotationPtr - pointer to pdf annotation
2864
- * @param index - index of annotation in the pdf page
2865
- * @returns pdf polygon annotation
2866
- *
2867
- * @private
2868
- */
2869
- readPdfPolygonAnno(page, pagePtr, annotationPtr, index) {
2870
- const pageRect = this.readPageAnnoRect(annotationPtr);
2871
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2872
- const author = this.getAnnotString(annotationPtr, 'T');
2873
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2874
- const modified = pdfDateToDate(modifiedRaw);
2875
- const vertices = this.readPdfAnnoVertices(page, pagePtr, annotationPtr);
2876
- return {
2877
- pageIndex: page.index,
2878
- id: index,
2879
- type: PdfAnnotationSubtype.POLYGON,
2880
- rect,
2881
- vertices,
2882
- author,
2883
- modified,
2884
- };
2885
- }
2886
- /**
2887
- * Read pdf polyline annotation
2888
- * @param page - pdf page infor
2889
- * @param pagePtr - pointer to pdf page object
2890
- * @param annotationPtr - pointer to pdf annotation
2891
- * @param index - index of annotation in the pdf page
2892
- * @returns pdf polyline annotation
2893
- *
2894
- * @private
2895
- */
2896
- readPdfPolylineAnno(page, pagePtr, annotationPtr, index) {
2897
- const pageRect = this.readPageAnnoRect(annotationPtr);
2898
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2899
- const author = this.getAnnotString(annotationPtr, 'T');
2900
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2901
- const modified = pdfDateToDate(modifiedRaw);
2902
- const vertices = this.readPdfAnnoVertices(page, pagePtr, annotationPtr);
2903
- return {
2904
- pageIndex: page.index,
2905
- id: index,
2906
- type: PdfAnnotationSubtype.POLYLINE,
2907
- rect,
2908
- vertices,
2909
- author,
2910
- modified,
2911
- };
2912
- }
2913
- /**
2914
- * Read pdf line annotation
2915
- * @param page - pdf page infor
2916
- * @param pagePtr - pointer to pdf page object
2917
- * @param annotationPtr - pointer to pdf annotation
2918
- * @param index - index of annotation in the pdf page
2919
- * @returns pdf line annotation
2920
- *
2921
- * @private
2922
- */
2923
- readPdfLineAnno(page, pagePtr, annotationPtr, index) {
2924
- const pageRect = this.readPageAnnoRect(annotationPtr);
2925
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2926
- const author = this.getAnnotString(annotationPtr, 'T');
2927
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2928
- const modified = pdfDateToDate(modifiedRaw);
2929
- const startPointPtr = this.malloc(8);
2930
- const endPointPtr = this.malloc(8);
2931
- this.pdfiumModule.FPDFAnnot_GetLine(annotationPtr, startPointPtr, endPointPtr);
2932
- const startPointX = this.pdfiumModule.pdfium.getValue(startPointPtr, 'float');
2933
- const startPointY = this.pdfiumModule.pdfium.getValue(startPointPtr + 4, 'float');
2934
- const startPoint = this.convertPagePointToDevicePoint(page, {
2935
- x: startPointX,
2936
- y: startPointY,
2937
- });
2938
- const endPointX = this.pdfiumModule.pdfium.getValue(endPointPtr, 'float');
2939
- const endPointY = this.pdfiumModule.pdfium.getValue(endPointPtr + 4, 'float');
2940
- const endPoint = this.convertPagePointToDevicePoint(page, {
2941
- x: endPointX,
2942
- y: endPointY,
2943
- });
2944
- this.free(startPointPtr);
2945
- this.free(endPointPtr);
2946
- return {
2947
- pageIndex: page.index,
2948
- id: index,
2949
- type: PdfAnnotationSubtype.LINE,
2950
- rect,
2951
- startPoint,
2952
- endPoint,
2953
- author,
2954
- modified,
2955
- };
2956
- }
2957
- /**
2958
- * Read pdf highlight annotation
2959
- * @param page - pdf page infor
2960
- * @param pagePtr - pointer to pdf page object
2961
- * @param annotationPtr - pointer to pdf annotation
2962
- * @param index - index of annotation in the pdf page
2963
- * @returns pdf highlight annotation
2964
- *
2965
- * @private
2966
- */
2967
- readPdfHighlightAnno(page, pagePtr, annotationPtr, index) {
2968
- const pageRect = this.readPageAnnoRect(annotationPtr);
2969
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2970
- const segmentRects = this.getQuadPointsAnno(page, annotationPtr);
2971
- const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
2972
- const author = this.getAnnotString(annotationPtr, 'T');
2973
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2974
- const modified = pdfDateToDate(modifiedRaw);
2975
- const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
2976
- return {
2977
- pageIndex: page.index,
2978
- id: index,
2979
- type: PdfAnnotationSubtype.HIGHLIGHT,
2980
- rect,
2981
- contents,
2982
- segmentRects,
2983
- ...webAlphaColor,
2984
- author,
2985
- modified,
2986
- };
2987
- }
2988
- /**
2989
- * Read pdf underline annotation
2990
- * @param page - pdf page infor
2991
- * @param pagePtr - pointer to pdf page object
2992
- * @param annotationPtr - pointer to pdf annotation
2993
- * @param index - index of annotation in the pdf page
2994
- * @returns pdf underline annotation
2995
- *
2996
- * @private
2997
- */
2998
- readPdfUnderlineAnno(page, pagePtr, annotationPtr, index) {
2999
- const pageRect = this.readPageAnnoRect(annotationPtr);
3000
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3001
- const author = this.getAnnotString(annotationPtr, 'T');
3002
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3003
- const modified = pdfDateToDate(modifiedRaw);
3004
- const segmentRects = this.getQuadPointsAnno(page, annotationPtr);
3005
- const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
3006
- const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
3007
- return {
3008
- pageIndex: page.index,
3009
- id: index,
3010
- type: PdfAnnotationSubtype.UNDERLINE,
3011
- rect,
3012
- contents,
3013
- segmentRects,
3014
- ...webAlphaColor,
3015
- author,
3016
- modified,
3017
- };
3018
- }
3019
- /**
3020
- * Read strikeout annotation
3021
- * @param page - pdf page infor
3022
- * @param pagePtr - pointer to pdf page object
3023
- * @param annotationPtr - pointer to pdf annotation
3024
- * @param index - index of annotation in the pdf page
3025
- * @returns pdf strikeout annotation
3026
- *
3027
- * @private
3028
- */
3029
- readPdfStrikeOutAnno(page, pagePtr, annotationPtr, index) {
3030
- const pageRect = this.readPageAnnoRect(annotationPtr);
3031
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3032
- const author = this.getAnnotString(annotationPtr, 'T');
3033
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3034
- const modified = pdfDateToDate(modifiedRaw);
3035
- const segmentRects = this.getQuadPointsAnno(page, annotationPtr);
3036
- const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
3037
- const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
3038
- return {
3039
- pageIndex: page.index,
3040
- id: index,
3041
- type: PdfAnnotationSubtype.STRIKEOUT,
3042
- rect,
3043
- contents,
3044
- segmentRects,
3045
- ...webAlphaColor,
3046
- author,
3047
- modified,
3048
- };
3049
- }
3050
- /**
3051
- * Read pdf squiggly annotation
3052
- * @param page - pdf page infor
3053
- * @param pagePtr - pointer to pdf page object
3054
- * @param annotationPtr - pointer to pdf annotation
3055
- * @param index - index of annotation in the pdf page
3056
- * @returns pdf squiggly annotation
3057
- *
3058
- * @private
3059
- */
3060
- readPdfSquigglyAnno(page, pagePtr, annotationPtr, index) {
3061
- const pageRect = this.readPageAnnoRect(annotationPtr);
3062
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3063
- const author = this.getAnnotString(annotationPtr, 'T');
3064
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3065
- const modified = pdfDateToDate(modifiedRaw);
3066
- const segmentRects = this.getQuadPointsAnno(page, annotationPtr);
3067
- const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
3068
- const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
3069
- return {
3070
- pageIndex: page.index,
3071
- id: index,
3072
- type: PdfAnnotationSubtype.SQUIGGLY,
3073
- rect,
3074
- contents,
3075
- segmentRects,
3076
- ...webAlphaColor,
3077
- author,
3078
- modified,
3079
- };
3080
- }
3081
- /**
3082
- * Read pdf caret annotation
3083
- * @param page - pdf page infor
3084
- * @param pagePtr - pointer to pdf page object
3085
- * @param annotationPtr - pointer to pdf annotation
3086
- * @param index - index of annotation in the pdf page
3087
- * @returns pdf caret annotation
3088
- *
3089
- * @private
3090
- */
3091
- readPdfCaretAnno(page, pagePtr, annotationPtr, index) {
3092
- const pageRect = this.readPageAnnoRect(annotationPtr);
3093
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3094
- const author = this.getAnnotString(annotationPtr, 'T');
3095
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3096
- const modified = pdfDateToDate(modifiedRaw);
3097
- return {
3098
- pageIndex: page.index,
3099
- id: index,
3100
- type: PdfAnnotationSubtype.CARET,
3101
- rect,
3102
- author,
3103
- modified,
3104
- };
3105
- }
3106
- /**
3107
- * Read pdf stamp annotation
3108
- * @param docPtr - pointer to pdf document object
3109
- * @param page - pdf page infor
3110
- * @param pagePtr - pointer to pdf page object
3111
- * @param annotationPtr - pointer to pdf annotation
3112
- * @param index - index of annotation in the pdf page
3113
- * @returns pdf stamp annotation
3114
- *
3115
- * @private
3116
- */
3117
- readPdfStampAnno(docPtr, page, pagePtr, annotationPtr, index) {
3118
- const pageRect = this.readPageAnnoRect(annotationPtr);
3119
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3120
- const author = this.getAnnotString(annotationPtr, 'T');
3121
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3122
- const modified = pdfDateToDate(modifiedRaw);
3123
- const contents = [];
3124
- const objectCount = this.pdfiumModule.FPDFAnnot_GetObjectCount(annotationPtr);
3125
- for (let i = 0; i < objectCount; i++) {
3126
- const annotationObjectPtr = this.pdfiumModule.FPDFAnnot_GetObject(annotationPtr, i);
3127
- const pageObj = this.readPdfPageObject(annotationObjectPtr);
3128
- if (pageObj) {
3129
- contents.push(pageObj);
3130
- }
3131
- }
3132
- return {
3133
- pageIndex: page.index,
3134
- id: index,
3135
- type: PdfAnnotationSubtype.STAMP,
3136
- rect,
3137
- contents,
3138
- author,
3139
- modified,
3140
- };
3141
- }
3142
- /**
3143
- * Read pdf object in pdf page
3144
- * @param pageObjectPtr - pointer to pdf object in page
3145
- * @returns pdf object in page
3146
- *
3147
- * @private
3148
- */
3149
- readPdfPageObject(pageObjectPtr) {
3150
- const type = this.pdfiumModule.FPDFPageObj_GetType(pageObjectPtr);
3151
- switch (type) {
3152
- case PdfPageObjectType.PATH:
3153
- return this.readPathObject(pageObjectPtr);
3154
- case PdfPageObjectType.IMAGE:
3155
- return this.readImageObject(pageObjectPtr);
3156
- case PdfPageObjectType.FORM:
3157
- return this.readFormObject(pageObjectPtr);
3158
- }
3159
- }
3160
- /**
3161
- * Read pdf path object
3162
- * @param pathObjectPtr - pointer to pdf path object in page
3163
- * @returns pdf path object
3164
- *
3165
- * @private
3166
- */
3167
- readPathObject(pathObjectPtr) {
3168
- const segmentCount = this.pdfiumModule.FPDFPath_CountSegments(pathObjectPtr);
3169
- const leftPtr = this.malloc(4);
3170
- const bottomPtr = this.malloc(4);
3171
- const rightPtr = this.malloc(4);
3172
- const topPtr = this.malloc(4);
3173
- this.pdfiumModule.FPDFPageObj_GetBounds(pathObjectPtr, leftPtr, bottomPtr, rightPtr, topPtr);
3174
- const left = this.pdfiumModule.pdfium.getValue(leftPtr, 'float');
3175
- const bottom = this.pdfiumModule.pdfium.getValue(bottomPtr, 'float');
3176
- const right = this.pdfiumModule.pdfium.getValue(rightPtr, 'float');
3177
- const top = this.pdfiumModule.pdfium.getValue(topPtr, 'float');
3178
- const bounds = { left, bottom, right, top };
3179
- this.free(leftPtr);
3180
- this.free(bottomPtr);
3181
- this.free(rightPtr);
3182
- this.free(topPtr);
3183
- const segments = [];
3184
- for (let i = 0; i < segmentCount; i++) {
3185
- const segment = this.readPdfSegment(pathObjectPtr, i);
3186
- segments.push(segment);
3187
- }
3188
- const matrix = this.readPdfPageObjectTransformMatrix(pathObjectPtr);
3189
- return {
3190
- type: PdfPageObjectType.PATH,
3191
- bounds,
3192
- segments,
3193
- matrix,
3194
- };
3195
- }
3196
- /**
3197
- * Read segment of pdf path object
3198
- * @param annotationObjectPtr - pointer to pdf path object
3199
- * @param segmentIndex - index of segment
3200
- * @returns pdf segment in pdf path
3201
- *
3202
- * @private
3203
- */
3204
- readPdfSegment(annotationObjectPtr, segmentIndex) {
3205
- const segmentPtr = this.pdfiumModule.FPDFPath_GetPathSegment(annotationObjectPtr, segmentIndex);
3206
- const segmentType = this.pdfiumModule.FPDFPathSegment_GetType(segmentPtr);
3207
- const isClosed = this.pdfiumModule.FPDFPathSegment_GetClose(segmentPtr);
3208
- const pointXPtr = this.malloc(4);
3209
- const pointYPtr = this.malloc(4);
3210
- this.pdfiumModule.FPDFPathSegment_GetPoint(segmentPtr, pointXPtr, pointYPtr);
3211
- const pointX = this.pdfiumModule.pdfium.getValue(pointXPtr, 'float');
3212
- const pointY = this.pdfiumModule.pdfium.getValue(pointYPtr, 'float');
3213
- this.free(pointXPtr);
3214
- this.free(pointYPtr);
3215
- return {
3216
- type: segmentType,
3217
- point: { x: pointX, y: pointY },
3218
- isClosed,
3219
- };
3220
- }
3221
- /**
3222
- * Read pdf image object from pdf document
3223
- * @param pageObjectPtr - pointer to pdf image object in page
3224
- * @returns pdf image object
3225
- *
3226
- * @private
3227
- */
3228
- readImageObject(imageObjectPtr) {
3229
- const bitmapPtr = this.pdfiumModule.FPDFImageObj_GetBitmap(imageObjectPtr);
3230
- const bitmapBufferPtr = this.pdfiumModule.FPDFBitmap_GetBuffer(bitmapPtr);
3231
- const bitmapWidth = this.pdfiumModule.FPDFBitmap_GetWidth(bitmapPtr);
3232
- const bitmapHeight = this.pdfiumModule.FPDFBitmap_GetHeight(bitmapPtr);
3233
- const format = this.pdfiumModule.FPDFBitmap_GetFormat(bitmapPtr);
3234
- const pixelCount = bitmapWidth * bitmapHeight;
3235
- const bytesPerPixel = 4;
3236
- const array = new Uint8ClampedArray(pixelCount * bytesPerPixel);
3237
- for (let i = 0; i < pixelCount; i++) {
3238
- switch (format) {
3239
- case BitmapFormat.Bitmap_BGR:
3240
- {
3241
- const blue = this.pdfiumModule.pdfium.getValue(bitmapBufferPtr + i * 3, 'i8');
3242
- const green = this.pdfiumModule.pdfium.getValue(bitmapBufferPtr + i * 3 + 1, 'i8');
3243
- const red = this.pdfiumModule.pdfium.getValue(bitmapBufferPtr + i * 3 + 2, 'i8');
3244
- array[i * bytesPerPixel] = red;
3245
- array[i * bytesPerPixel + 1] = green;
3246
- array[i * bytesPerPixel + 2] = blue;
3247
- array[i * bytesPerPixel + 3] = 100;
3248
- }
3249
- break;
3250
- }
3251
- }
3252
- const imageData = new ImageData(array, bitmapWidth, bitmapHeight);
3253
- const matrix = this.readPdfPageObjectTransformMatrix(imageObjectPtr);
3254
- return {
3255
- type: PdfPageObjectType.IMAGE,
3256
- imageData,
3257
- matrix,
3258
- };
3259
- }
3260
- /**
3261
- * Read form object from pdf document
3262
- * @param formObjectPtr - pointer to pdf form object in page
3263
- * @returns pdf form object
3264
- *
3265
- * @private
3266
- */
3267
- readFormObject(formObjectPtr) {
3268
- const objectCount = this.pdfiumModule.FPDFFormObj_CountObjects(formObjectPtr);
3269
- const objects = [];
3270
- for (let i = 0; i < objectCount; i++) {
3271
- const pageObjectPtr = this.pdfiumModule.FPDFFormObj_GetObject(formObjectPtr, i);
3272
- const pageObj = this.readPdfPageObject(pageObjectPtr);
3273
- if (pageObj) {
3274
- objects.push(pageObj);
3275
- }
3276
- }
3277
- const matrix = this.readPdfPageObjectTransformMatrix(formObjectPtr);
3278
- return {
3279
- type: PdfPageObjectType.FORM,
3280
- objects,
3281
- matrix,
3282
- };
3283
- }
3284
- /**
3285
- * Read pdf object in pdf page
3286
- * @param pageObjectPtr - pointer to pdf object in page
3287
- * @returns pdf object in page
3288
- *
3289
- * @private
3290
- */
3291
- readPdfPageObjectTransformMatrix(pageObjectPtr) {
3292
- const matrixPtr = this.malloc(4 * 6);
3293
- if (this.pdfiumModule.FPDFPageObj_GetMatrix(pageObjectPtr, matrixPtr)) {
3294
- const a = this.pdfiumModule.pdfium.getValue(matrixPtr, 'float');
3295
- const b = this.pdfiumModule.pdfium.getValue(matrixPtr + 4, 'float');
3296
- const c = this.pdfiumModule.pdfium.getValue(matrixPtr + 8, 'float');
3297
- const d = this.pdfiumModule.pdfium.getValue(matrixPtr + 12, 'float');
3298
- const e = this.pdfiumModule.pdfium.getValue(matrixPtr + 16, 'float');
3299
- const f = this.pdfiumModule.pdfium.getValue(matrixPtr + 20, 'float');
3300
- this.free(matrixPtr);
3301
- return { a, b, c, d, e, f };
3302
- }
3303
- this.free(matrixPtr);
3304
- return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
3305
- }
3306
- /**
3307
- * Return the stroke-width declared in the annotation’s /Border or /BS entry.
3308
- * Falls back to 1 pt when nothing is defined.
3309
- *
3310
- * @param annotationPtr - pointer to pdf annotation
3311
- * @returns stroke-width
3312
- *
3313
- * @private
3314
- */
3315
- getStrokeWidth(annotationPtr) {
3316
- // FPDFAnnot_GetBorder(annot, &hRadius, &vRadius, &borderWidth)
3317
- const hPtr = this.malloc(4);
3318
- const vPtr = this.malloc(4);
3319
- const wPtr = this.malloc(4);
3320
- const ok = this.pdfiumModule.FPDFAnnot_GetBorder(annotationPtr, hPtr, vPtr, wPtr);
3321
- const width = ok ? this.pdfiumModule.pdfium.getValue(wPtr, 'float') : 1; // default 1 pt
3322
- this.free(hPtr);
3323
- this.free(vPtr);
3324
- this.free(wPtr);
3325
- return width;
3326
- }
3327
- /**
3328
- * Fetches the `/F` flag bit-field from an annotation.
3329
- *
3330
- * @param annotationPtr pointer to an `FPDF_ANNOTATION`
3331
- * @returns `{ raw, flags }`
3332
- * • `raw` – the 32-bit integer returned by PDFium
3333
- * • `flags` – object with individual booleans
3334
- */
3335
- getAnnotationFlags(annotationPtr) {
3336
- const rawFlags = this.pdfiumModule.FPDFAnnot_GetFlags(annotationPtr); // number
3337
- return flagsToNames(rawFlags);
3338
- }
3339
- /**
3340
- * Read circle annotation
3341
- * @param page - pdf page infor
3342
- * @param pagePtr - pointer to pdf page object
3343
- * @param annotationPtr - pointer to pdf annotation
3344
- * @param index - index of annotation in the pdf page
3345
- * @returns pdf circle annotation
3346
- *
3347
- * @private
3348
- */
3349
- readPdfCircleAnno(page, pagePtr, annotationPtr, index) {
3350
- const flags = this.getAnnotationFlags(annotationPtr);
3351
- const pageRect = this.readPageAnnoRect(annotationPtr);
3352
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3353
- const author = this.getAnnotString(annotationPtr, 'T');
3354
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3355
- const modified = pdfDateToDate(modifiedRaw);
3356
- const { color, opacity } = this.resolveAnnotationColor(annotationPtr, PdfAnnotationColorType.InteriorColor);
3357
- const { color: strokeColor } = this.resolveAnnotationColor(annotationPtr);
3358
- let { style: strokeStyle, width: strokeWidth } = this.getBorderStyle(annotationPtr);
3359
- let cloudyBorderIntensity;
3360
- let cloudyBorderInset;
3361
- if (strokeStyle === PdfAnnotationBorderStyle.CLOUDY ||
3362
- strokeStyle === PdfAnnotationBorderStyle.UNKNOWN) {
3363
- const { ok: hasEffect, intensity } = this.getBorderEffect(annotationPtr);
3364
- if (hasEffect) {
3365
- cloudyBorderIntensity = intensity;
3366
- strokeStyle = PdfAnnotationBorderStyle.CLOUDY;
3367
- const { ok: hasInset, left, top, right, bottom, } = this.getRectangleDifferences(annotationPtr);
3368
- if (hasInset)
3369
- cloudyBorderInset = [left, top, right, bottom];
3370
- }
3371
- }
3372
- let strokeDashArray;
3373
- if (strokeStyle === PdfAnnotationBorderStyle.DASHED) {
3374
- const { ok, pattern } = this.getBorderDashPattern(annotationPtr);
3375
- if (ok) {
3376
- strokeDashArray = pattern;
3377
- }
3378
- }
3379
- return {
3380
- pageIndex: page.index,
3381
- id: index,
3382
- type: PdfAnnotationSubtype.CIRCLE,
3383
- flags,
3384
- color,
3385
- opacity,
3386
- strokeWidth,
3387
- strokeColor,
3388
- strokeStyle,
3389
- rect,
3390
- author,
3391
- modified,
3392
- ...(cloudyBorderIntensity !== undefined && { cloudyBorderIntensity }),
3393
- ...(cloudyBorderInset !== undefined && { cloudyBorderInset }),
3394
- ...(strokeDashArray !== undefined && { strokeDashArray }),
3395
- };
3396
- }
3397
- /**
3398
- * Read square annotation
3399
- * @param page - pdf page infor
3400
- * @param pagePtr - pointer to pdf page object
3401
- * @param annotationPtr - pointer to pdf annotation
3402
- * @param index - index of annotation in the pdf page
3403
- * @returns pdf square annotation
3404
- *
3405
- * @private
3406
- */
3407
- readPdfSquareAnno(page, pagePtr, annotationPtr, index) {
3408
- const flags = this.getAnnotationFlags(annotationPtr);
3409
- const pageRect = this.readPageAnnoRect(annotationPtr);
3410
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3411
- const author = this.getAnnotString(annotationPtr, 'T');
3412
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3413
- const modified = pdfDateToDate(modifiedRaw);
3414
- const { color, opacity } = this.resolveAnnotationColor(annotationPtr, PdfAnnotationColorType.InteriorColor);
3415
- const { color: strokeColor } = this.resolveAnnotationColor(annotationPtr);
3416
- let { style: strokeStyle, width: strokeWidth } = this.getBorderStyle(annotationPtr);
3417
- let cloudyBorderIntensity;
3418
- let cloudyBorderInset;
3419
- if (strokeStyle === PdfAnnotationBorderStyle.CLOUDY ||
3420
- strokeStyle === PdfAnnotationBorderStyle.UNKNOWN) {
3421
- const { ok: hasEffect, intensity } = this.getBorderEffect(annotationPtr);
3422
- if (hasEffect) {
3423
- cloudyBorderIntensity = intensity;
3424
- strokeStyle = PdfAnnotationBorderStyle.CLOUDY;
3425
- const { ok: hasInset, left, top, right, bottom, } = this.getRectangleDifferences(annotationPtr);
3426
- if (hasInset)
3427
- cloudyBorderInset = [left, top, right, bottom];
3428
- }
3429
- }
3430
- let strokeDashArray;
3431
- if (strokeStyle === PdfAnnotationBorderStyle.DASHED) {
3432
- const { ok, pattern } = this.getBorderDashPattern(annotationPtr);
3433
- if (ok) {
3434
- strokeDashArray = pattern;
3435
- }
3436
- }
3437
- return {
3438
- pageIndex: page.index,
3439
- id: index,
3440
- type: PdfAnnotationSubtype.SQUARE,
3441
- flags,
3442
- color,
3443
- opacity,
3444
- strokeColor,
3445
- strokeWidth,
3446
- strokeStyle,
3447
- rect,
3448
- author,
3449
- modified,
3450
- ...(cloudyBorderIntensity !== undefined && { cloudyBorderIntensity }),
3451
- ...(cloudyBorderInset !== undefined && { cloudyBorderInset }),
3452
- ...(strokeDashArray !== undefined && { strokeDashArray }),
3453
- };
3454
- }
3455
- /**
3456
- * Read basic info of unsupported pdf annotation
3457
- * @param page - pdf page infor
3458
- * @param pagePtr - pointer to pdf page object
3459
- * @param type - type of annotation
3460
- * @param annotationPtr - pointer to pdf annotation
3461
- * @param index - index of annotation in the pdf page
3462
- * @returns pdf annotation
3463
- *
3464
- * @private
3465
- */
3466
- readPdfAnno(page, pagePtr, type, annotationPtr, index) {
3467
- const pageRect = this.readPageAnnoRect(annotationPtr);
3468
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3469
- const author = this.getAnnotString(annotationPtr, 'T');
3470
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3471
- const modified = pdfDateToDate(modifiedRaw);
3472
- return {
3473
- pageIndex: page.index,
3474
- id: index,
3475
- type,
3476
- rect,
3477
- author,
3478
- modified,
3479
- };
3480
- }
3481
- /**
3482
- * Resolve `/IRT` → parent-annotation index on the same page.
3483
- *
3484
- * @param pagePtr - pointer to FPDF_PAGE
3485
- * @param annotationPtr - pointer to FPDF_ANNOTATION
3486
- * @returns index (`0…count-1`) or `undefined` when the annotation is *not* a reply
3487
- *
3488
- * @private
3489
- */
3490
- getInReplyToId(pagePtr, annotationPtr) {
3491
- const parentPtr = this.pdfiumModule.FPDFAnnot_GetLinkedAnnot(annotationPtr, 'IRT');
3492
- if (!parentPtr)
3493
- return;
3494
- // PDFium ≥ 5100 – returns −1 when annot not found on page
3495
- const idx = this.pdfiumModule.FPDFPage_GetAnnotIndex(pagePtr, parentPtr);
3496
- return idx >= 0 ? idx : undefined;
3497
- }
3498
- /**
3499
- * Fetch a string value (`/T`, `/M`, `/State`, …) from an annotation.
3500
- *
3501
- * @returns decoded UTF-8 string or `undefined` when the key is absent
3502
- *
3503
- * @private
3504
- */
3505
- getAnnotString(annotationPtr, key) {
3506
- const len = this.pdfiumModule.FPDFAnnot_GetStringValue(annotationPtr, key, 0, 0);
3507
- if (len === 0)
3508
- return;
3509
- const bytes = (len + 1) * 2;
3510
- const ptr = this.malloc(bytes);
3511
- this.pdfiumModule.FPDFAnnot_GetStringValue(annotationPtr, key, ptr, bytes);
3512
- const value = this.pdfiumModule.pdfium.UTF16ToString(ptr);
3513
- this.free(ptr);
3514
- return value || undefined;
3515
- }
3516
- /**
3517
- * Set a string value (`/T`, `/M`, `/State`, …) to an annotation.
3518
- *
3519
- * @returns `true` if the operation was successful
3520
- *
3521
- * @private
3522
- */
3523
- setAnnotString(annotationPtr, key, value) {
3524
- const bytes = 2 * (value.length + 1);
3525
- const ptr = this.malloc(bytes);
3526
- this.pdfiumModule.pdfium.stringToUTF16(value, ptr, bytes);
3527
- const ok = this.pdfiumModule.FPDFAnnot_SetStringValue(annotationPtr, key, ptr);
3528
- this.free(ptr);
3529
- return ok;
3530
- }
3531
- /**
3532
- * Read vertices of pdf annotation
3533
- * @param page - pdf page infor
3534
- * @param pagePtr - pointer to pdf page object
3535
- * @param annotationPtr - pointer to pdf annotation
3536
- * @returns vertices of pdf annotation
3537
- *
3538
- * @private
3539
- */
3540
- readPdfAnnoVertices(page, pagePtr, annotationPtr) {
3541
- const vertices = [];
3542
- const count = this.pdfiumModule.FPDFAnnot_GetVertices(annotationPtr, 0, 0);
3543
- const pointMemorySize = 8;
3544
- const pointsPtr = this.malloc(count * pointMemorySize);
3545
- this.pdfiumModule.FPDFAnnot_GetVertices(annotationPtr, pointsPtr, count);
3546
- for (let i = 0; i < count; i++) {
3547
- const pointX = this.pdfiumModule.pdfium.getValue(pointsPtr + i * pointMemorySize, 'float');
3548
- const pointY = this.pdfiumModule.pdfium.getValue(pointsPtr + i * pointMemorySize + 4, 'float');
3549
- const { x, y } = this.convertPagePointToDevicePoint(page, {
3550
- x: pointX,
3551
- y: pointY,
3552
- });
3553
- vertices.push({
3554
- x,
3555
- y,
3556
- });
3557
- }
3558
- this.free(pointsPtr);
3559
- return vertices;
3560
- }
3561
- /**
3562
- * Read the target of pdf bookmark
3563
- * @param docPtr - pointer to pdf document object
3564
- * @param getActionPtr - callback function to retrive the pointer of action
3565
- * @param getDestinationPtr - callback function to retrive the pointer of destination
3566
- * @returns target of pdf bookmark
3567
- *
3568
- * @private
3569
- */
3570
- readPdfBookmarkTarget(docPtr, getActionPtr, getDestinationPtr) {
3571
- const actionPtr = getActionPtr();
3572
- if (actionPtr) {
3573
- const action = this.readPdfAction(docPtr, actionPtr);
3574
- return {
3575
- type: 'action',
3576
- action,
3577
- };
3578
- }
3579
- else {
3580
- const destinationPtr = getDestinationPtr();
3581
- if (destinationPtr) {
3582
- const destination = this.readPdfDestination(docPtr, destinationPtr);
3583
- return {
3584
- type: 'destination',
3585
- destination,
3586
- };
3587
- }
3588
- }
3589
- }
3590
- /**
3591
- * Read field of pdf widget annotation
3592
- * @param formHandle - form handle
3593
- * @param annotationPtr - pointer to pdf annotation
3594
- * @returns field of pdf widget annotation
3595
- *
3596
- * @private
3597
- */
3598
- readPdfWidgetAnnoField(formHandle, annotationPtr) {
3599
- const flag = this.pdfiumModule.FPDFAnnot_GetFormFieldFlags(formHandle, annotationPtr);
3600
- const type = this.pdfiumModule.FPDFAnnot_GetFormFieldType(formHandle, annotationPtr);
3601
- const name = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3602
- return this.pdfiumModule.FPDFAnnot_GetFormFieldName(formHandle, annotationPtr, buffer, bufferLength);
3603
- }, this.pdfiumModule.pdfium.UTF16ToString);
3604
- const alternateName = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3605
- return this.pdfiumModule.FPDFAnnot_GetFormFieldAlternateName(formHandle, annotationPtr, buffer, bufferLength);
3606
- }, this.pdfiumModule.pdfium.UTF16ToString);
3607
- const value = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3608
- return this.pdfiumModule.FPDFAnnot_GetFormFieldValue(formHandle, annotationPtr, buffer, bufferLength);
3609
- }, this.pdfiumModule.pdfium.UTF16ToString);
3610
- const options = [];
3611
- if (type === PDF_FORM_FIELD_TYPE.COMBOBOX || type === PDF_FORM_FIELD_TYPE.LISTBOX) {
3612
- const count = this.pdfiumModule.FPDFAnnot_GetOptionCount(formHandle, annotationPtr);
3613
- for (let i = 0; i < count; i++) {
3614
- const label = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3615
- return this.pdfiumModule.FPDFAnnot_GetOptionLabel(formHandle, annotationPtr, i, buffer, bufferLength);
3616
- }, this.pdfiumModule.pdfium.UTF16ToString);
3617
- const isSelected = this.pdfiumModule.FPDFAnnot_IsOptionSelected(formHandle, annotationPtr, i);
3618
- options.push({
3619
- label,
3620
- isSelected,
3621
- });
3622
- }
3623
- }
3624
- let isChecked = false;
3625
- if (type === PDF_FORM_FIELD_TYPE.CHECKBOX || type === PDF_FORM_FIELD_TYPE.RADIOBUTTON) {
3626
- isChecked = this.pdfiumModule.FPDFAnnot_IsChecked(formHandle, annotationPtr);
3627
- }
3628
- return {
3629
- flag,
3630
- type,
3631
- name,
3632
- alternateName,
3633
- value,
3634
- isChecked,
3635
- options,
3636
- };
3637
- }
3638
- /**
3639
- * {@inheritDoc @embedpdf/models!PdfEngine.renderAnnotation}
3640
- *
3641
- * @public
3642
- */
3643
- renderAnnotation(doc, page, annotation, scaleFactor, rotation, dpr = 1, // device-pixel-ratio (canvas)
3644
- mode = AppearanceMode.Normal, imageType = 'image/webp') {
3645
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'renderAnnotation', doc, page, annotation, scaleFactor, rotation, dpr, mode, imageType);
3646
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `RenderAnnotation`, 'Begin', `${doc.id}-${page.index}-${annotation.id}`);
3647
- const task = new Task();
3648
- const ctx = this.cache.getContext(doc.id);
3649
- if (!ctx) {
3650
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `RenderAnnotation`, 'End', `${doc.id}-${page.index}-${annotation.id}`);
3651
- return PdfTaskHelper.reject({
3652
- code: PdfErrorCode.DocNotOpen,
3653
- message: 'document does not open',
3654
- });
3655
- }
3656
- /* ── 1. grab native handles ───────────────────────────────────────── */
3657
- const pageCtx = ctx.acquirePage(page.index);
3658
- const annotPtr = this.pdfiumModule.FPDFPage_GetAnnot(pageCtx.pagePtr, annotation.id);
3659
- if (!annotPtr) {
3660
- pageCtx.release();
3661
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `RenderAnnotation`, 'End', `${doc.id}-${page.index}-${annotation.id}`);
3662
- return PdfTaskHelper.reject({
3663
- code: PdfErrorCode.NotFound,
3664
- message: 'annotation not found',
3665
- });
3666
- }
3667
- const finalScale = scaleFactor * dpr;
3668
- /* ── 2. decide bitmap size (integer pixels) ──────────────────────── */
3669
- const annotRect = annotation.rect;
3670
- const bitmapRect = toIntRect(transformRect(page.size, annotRect, rotation, finalScale));
3671
- const format = BitmapFormat.Bitmap_BGRA;
3672
- const bytesPerPixel = 4;
3673
- const bitmapHeapLength = bitmapRect.size.width * bitmapRect.size.height * bytesPerPixel;
3674
- const bitmapHeapPtr = this.malloc(bitmapHeapLength);
3675
- const bitmapPtr = this.pdfiumModule.FPDFBitmap_CreateEx(bitmapRect.size.width, bitmapRect.size.height, format, bitmapHeapPtr, bitmapRect.size.width * bytesPerPixel);
3676
- this.pdfiumModule.FPDFBitmap_FillRect(bitmapPtr, 0, 0, bitmapRect.size.width, bitmapRect.size.height, 0x00000000);
3677
- const matrix = makeMatrix(annotation.rect, rotation, finalScale);
3678
- // Allocate memory for the matrix on the wasm heap and write to it
3679
- const matrixSize = 6 * 4;
3680
- const matrixPtr = this.malloc(matrixSize);
3681
- const matrixView = new Float32Array(this.pdfiumModule.pdfium.HEAPF32.buffer, matrixPtr, 6);
3682
- matrixView.set([matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f]);
3683
- /* ── 5. call the native helper with the new matrix ───────────────── */
3684
- const FLAGS = RenderFlag.REVERSE_BYTE_ORDER;
3685
- const ok = !!this.pdfiumModule.EPDF_RenderAnnotBitmap(bitmapPtr, pageCtx.pagePtr, annotPtr, mode, matrixPtr, FLAGS);
3686
- /* ── 6. tear down native resources ───────────────────────────────── */
3687
- this.free(matrixPtr); // Free the matrix memory
3688
- this.pdfiumModule.FPDFBitmap_Destroy(bitmapPtr);
3689
- this.pdfiumModule.FPDFPage_CloseAnnot(annotPtr);
3690
- pageCtx.release();
3691
- if (!ok) {
3692
- this.free(bitmapHeapPtr);
3693
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `RenderAnnotation`, 'End', `${doc.id}-${page.index}-${annotation.id}`);
3694
- return PdfTaskHelper.reject({
3695
- code: PdfErrorCode.Unknown,
3696
- message: 'EPDF_RenderAnnotBitmap failed',
3697
- });
3698
- }
3699
- /* ── 6. copy out + convert to Blob (reuse existing converter) ─────── */
3700
- const data = this.pdfiumModule.pdfium.HEAPU8.subarray(bitmapHeapPtr, bitmapHeapPtr + bitmapHeapLength);
3701
- const imageData = {
3702
- data: new Uint8ClampedArray(data),
3703
- width: bitmapRect.size.width,
3704
- height: bitmapRect.size.height,
3705
- };
3706
- this.free(bitmapHeapPtr);
3707
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `RenderAnnotation`, 'End', `${doc.id}-${page.index}-${annotation.id}`);
3708
- this.imageDataConverter(imageData, imageType)
3709
- .then((blob) => task.resolve(blob))
3710
- .catch((err) => task.reject({ code: PdfErrorCode.Unknown, message: String(err) }));
3711
- return task;
3712
- }
3713
- /**
3714
- * render rectangle of pdf page to image
3715
- * @param docPtr - pointer to pdf document object
3716
- * @param page - pdf page infor
3717
- * @param rect - rectangle info
3718
- * @param scaleFactor - factor of scalling
3719
- * @param rotation - rotation angle
3720
- * @param options - render options
3721
- * @returns image data
3722
- *
3723
- * @private
3724
- */
3725
- renderPageRectToImageData(ctx, page, rect, scaleFactor, rotation, dpr, options) {
3726
- const format = BitmapFormat.Bitmap_BGRA;
3727
- const bytesPerPixel = 4;
3728
- // Round the transformed dimensions to whole pixels
3729
- const rectSize = toIntRect(transformRect(page.size, rect, rotation, scaleFactor * dpr));
3730
- const pageSize = toIntSize(transformSize(page.size, rotation, scaleFactor * dpr));
3731
- const bitmapHeapLength = rectSize.size.width * rectSize.size.height * bytesPerPixel;
3732
- const bitmapHeapPtr = this.malloc(bitmapHeapLength);
3733
- const bitmapPtr = this.pdfiumModule.FPDFBitmap_CreateEx(rectSize.size.width, rectSize.size.height, format, bitmapHeapPtr, rectSize.size.width * bytesPerPixel);
3734
- this.pdfiumModule.FPDFBitmap_FillRect(bitmapPtr, 0, 0, rectSize.size.width, rectSize.size.height, 0xffffffff);
3735
- let flags = RenderFlag.REVERSE_BYTE_ORDER;
3736
- if (options?.withAnnotations) {
3737
- flags = flags | RenderFlag.ANNOT;
3738
- }
3739
- const pageCtx = ctx.acquirePage(page.index);
3740
- this.pdfiumModule.FPDF_RenderPageBitmap(bitmapPtr, pageCtx.pagePtr, -rectSize.origin.x, -rectSize.origin.y, pageSize.width, pageSize.height, rotation, flags);
3741
- this.pdfiumModule.FPDFBitmap_Destroy(bitmapPtr);
3742
- pageCtx.release();
3743
- const data = this.pdfiumModule.pdfium.HEAPU8.subarray(bitmapHeapPtr, bitmapHeapPtr + bitmapHeapLength);
3744
- const imageData = {
3745
- data: new Uint8ClampedArray(data),
3746
- width: rectSize.size.width,
3747
- height: rectSize.size.height,
3748
- };
3749
- this.free(bitmapHeapPtr);
3750
- return imageData;
3751
- }
3752
- /**
3753
- * Read the target of pdf link annotation
3754
- * @param docPtr - pointer to pdf document object
3755
- * @param getActionPtr - callback function to retrive the pointer of action
3756
- * @param getDestinationPtr - callback function to retrive the pointer of destination
3757
- * @returns target of link
3758
- *
3759
- * @private
3760
- */
3761
- readPdfLinkAnnoTarget(docPtr, getActionPtr, getDestinationPtr) {
3762
- const destinationPtr = getDestinationPtr();
3763
- if (destinationPtr) {
3764
- const destination = this.readPdfDestination(docPtr, destinationPtr);
3765
- return {
3766
- type: 'destination',
3767
- destination,
3768
- };
3769
- }
3770
- else {
3771
- const actionPtr = getActionPtr();
3772
- if (actionPtr) {
3773
- const action = this.readPdfAction(docPtr, actionPtr);
3774
- return {
3775
- type: 'action',
3776
- action,
3777
- };
3778
- }
3779
- }
3780
- }
3781
- /**
3782
- * Read pdf action from pdf document
3783
- * @param docPtr - pointer to pdf document object
3784
- * @param actionPtr - pointer to pdf action object
3785
- * @returns pdf action object
3786
- *
3787
- * @private
3788
- */
3789
- readPdfAction(docPtr, actionPtr) {
3790
- const actionType = this.pdfiumModule.FPDFAction_GetType(actionPtr);
3791
- let action;
3792
- switch (actionType) {
3793
- case PdfActionType.Unsupported:
3794
- action = {
3795
- type: PdfActionType.Unsupported,
3796
- };
3797
- break;
3798
- case PdfActionType.Goto:
3799
- {
3800
- const destinationPtr = this.pdfiumModule.FPDFAction_GetDest(docPtr, actionPtr);
3801
- if (destinationPtr) {
3802
- const destination = this.readPdfDestination(docPtr, destinationPtr);
3803
- action = {
3804
- type: PdfActionType.Goto,
3805
- destination,
3806
- };
3807
- }
3808
- else {
3809
- action = {
3810
- type: PdfActionType.Unsupported,
3811
- };
3812
- }
3813
- }
3814
- break;
3815
- case PdfActionType.RemoteGoto:
3816
- {
3817
- // In case of remote goto action,
3818
- // the application should first use FPDFAction_GetFilePath
3819
- // to get file path, then load that particular document,
3820
- // and use its document handle to call this
3821
- action = {
3822
- type: PdfActionType.Unsupported,
3823
- };
3824
- }
3825
- break;
3826
- case PdfActionType.URI:
3827
- {
3828
- const uri = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3829
- return this.pdfiumModule.FPDFAction_GetURIPath(docPtr, actionPtr, buffer, bufferLength);
3830
- }, this.pdfiumModule.pdfium.UTF8ToString);
3831
- action = {
3832
- type: PdfActionType.URI,
3833
- uri,
3834
- };
3835
- }
3836
- break;
3837
- case PdfActionType.LaunchAppOrOpenFile:
3838
- {
3839
- const path = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3840
- return this.pdfiumModule.FPDFAction_GetFilePath(actionPtr, buffer, bufferLength);
3841
- }, this.pdfiumModule.pdfium.UTF8ToString);
3842
- action = {
3843
- type: PdfActionType.LaunchAppOrOpenFile,
3844
- path,
3845
- };
3846
- }
3847
- break;
3848
- }
3849
- return action;
3850
- }
3851
- /**
3852
- * Read pdf destination object
3853
- * @param docPtr - pointer to pdf document object
3854
- * @param destinationPtr - pointer to pdf destination
3855
- * @returns pdf destination object
3856
- *
3857
- * @private
3858
- */
3859
- readPdfDestination(docPtr, destinationPtr) {
3860
- const pageIndex = this.pdfiumModule.FPDFDest_GetDestPageIndex(docPtr, destinationPtr);
3861
- // Every params is a float value
3862
- const maxParmamsCount = 4;
3863
- const paramsCountPtr = this.malloc(maxParmamsCount);
3864
- const paramsPtr = this.malloc(maxParmamsCount * 4);
3865
- const zoomMode = this.pdfiumModule.FPDFDest_GetView(destinationPtr, paramsCountPtr, paramsPtr);
3866
- const paramsCount = this.pdfiumModule.pdfium.getValue(paramsCountPtr, 'i32');
3867
- const view = [];
3868
- for (let i = 0; i < paramsCount; i++) {
3869
- const paramPtr = paramsPtr + i * 4;
3870
- view.push(this.pdfiumModule.pdfium.getValue(paramPtr, 'float'));
3871
- }
3872
- this.free(paramsCountPtr);
3873
- this.free(paramsPtr);
3874
- if (zoomMode === PdfZoomMode.XYZ) {
3875
- const hasXPtr = this.malloc(1);
3876
- const hasYPtr = this.malloc(1);
3877
- const hasZPtr = this.malloc(1);
3878
- const xPtr = this.malloc(4);
3879
- const yPtr = this.malloc(4);
3880
- const zPtr = this.malloc(4);
3881
- const isSucceed = this.pdfiumModule.FPDFDest_GetLocationInPage(destinationPtr, hasXPtr, hasYPtr, hasZPtr, xPtr, yPtr, zPtr);
3882
- if (isSucceed) {
3883
- const hasX = this.pdfiumModule.pdfium.getValue(hasXPtr, 'i8');
3884
- const hasY = this.pdfiumModule.pdfium.getValue(hasYPtr, 'i8');
3885
- const hasZ = this.pdfiumModule.pdfium.getValue(hasZPtr, 'i8');
3886
- const x = hasX ? this.pdfiumModule.pdfium.getValue(xPtr, 'float') : 0;
3887
- const y = hasY ? this.pdfiumModule.pdfium.getValue(yPtr, 'float') : 0;
3888
- const zoom = hasZ ? this.pdfiumModule.pdfium.getValue(zPtr, 'float') : 0;
3889
- this.free(hasXPtr);
3890
- this.free(hasYPtr);
3891
- this.free(hasZPtr);
3892
- this.free(xPtr);
3893
- this.free(yPtr);
3894
- this.free(zPtr);
3895
- return {
3896
- pageIndex,
3897
- zoom: {
3898
- mode: zoomMode,
3899
- params: {
3900
- x,
3901
- y,
3902
- zoom,
3903
- },
3904
- },
3905
- view,
3906
- };
3907
- }
3908
- this.free(hasXPtr);
3909
- this.free(hasYPtr);
3910
- this.free(hasZPtr);
3911
- this.free(xPtr);
3912
- this.free(yPtr);
3913
- this.free(zPtr);
3914
- return {
3915
- pageIndex,
3916
- zoom: {
3917
- mode: zoomMode,
3918
- params: {
3919
- x: 0,
3920
- y: 0,
3921
- zoom: 0,
3922
- },
3923
- },
3924
- view,
3925
- };
3926
- }
3927
- return {
3928
- pageIndex,
3929
- zoom: {
3930
- mode: zoomMode,
3931
- },
3932
- view,
3933
- };
3934
- }
3935
- /**
3936
- * Read attachmet from pdf document
3937
- * @param docPtr - pointer to pdf document object
3938
- * @param index - index of attachment
3939
- * @returns attachment content
3940
- *
3941
- * @private
3942
- */
3943
- readPdfAttachment(docPtr, index) {
3944
- const attachmentPtr = this.pdfiumModule.FPDFDoc_GetAttachment(docPtr, index);
3945
- const name = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3946
- return this.pdfiumModule.FPDFAttachment_GetName(attachmentPtr, buffer, bufferLength);
3947
- }, this.pdfiumModule.pdfium.UTF16ToString);
3948
- const creationDate = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3949
- return this.pdfiumModule.FPDFAttachment_GetStringValue(attachmentPtr, 'CreationDate', buffer, bufferLength);
3950
- }, this.pdfiumModule.pdfium.UTF16ToString);
3951
- const checksum = readString(this.pdfiumModule.pdfium, (buffer, bufferLength) => {
3952
- return this.pdfiumModule.FPDFAttachment_GetStringValue(attachmentPtr, 'Checksum', buffer, bufferLength);
3953
- }, this.pdfiumModule.pdfium.UTF16ToString);
3954
- return {
3955
- index,
3956
- name,
3957
- creationDate,
3958
- checksum,
3959
- };
3960
- }
3961
- /**
3962
- * Convert coordinate of point from device coordinate to page coordinate
3963
- * @param page - pdf page infor
3964
- * @param position - position of point
3965
- * @returns converted position
3966
- *
3967
- * @private
3968
- */
3969
- convertDevicePointToPagePoint(page, position) {
3970
- const x = position.x;
3971
- const y = page.size.height - position.y;
3972
- return { x, y };
3973
- }
3974
- /**
3975
- * Convert coordinate of point from page coordinate to device coordinate
3976
- * @param page - pdf page infor
3977
- * @param position - position of point
3978
- * @returns converted position
3979
- *
3980
- * @private
3981
- */
3982
- convertPagePointToDevicePoint(page, position) {
3983
- const x = position.x;
3984
- const y = page.size.height - position.y;
3985
- return { x, y };
3986
- }
3987
- /**
3988
- * Convert coordinate of rectangle from page coordinate to device coordinate
3989
- * @param page - pdf page infor
3990
- * @param pagePtr - pointer to pdf page object
3991
- * @param pageRect - rectangle that needs to be converted
3992
- * @returns converted rectangle
3993
- *
3994
- * @private
3995
- */
3996
- convertPageRectToDeviceRect(page, pagePtr, pageRect) {
3997
- const { x, y } = this.convertPagePointToDevicePoint(page, {
3998
- x: pageRect.left,
3999
- y: pageRect.top,
4000
- });
4001
- const rect = {
4002
- origin: {
4003
- x,
4004
- y,
4005
- },
4006
- size: {
4007
- width: Math.abs(pageRect.right - pageRect.left),
4008
- height: Math.abs(pageRect.top - pageRect.bottom),
4009
- },
4010
- };
4011
- return rect;
4012
- }
4013
- /**
4014
- * Read the appearance stream of annotation
4015
- * @param annotationPtr - pointer to pdf annotation
4016
- * @param mode - appearance mode
4017
- * @returns appearance stream
4018
- *
4019
- * @private
4020
- */
4021
- readPageAnnoAppearanceStreams(annotationPtr) {
4022
- return {
4023
- normal: this.readPageAnnoAppearanceStream(annotationPtr, AppearanceMode.Normal),
4024
- rollover: this.readPageAnnoAppearanceStream(annotationPtr, AppearanceMode.Rollover),
4025
- down: this.readPageAnnoAppearanceStream(annotationPtr, AppearanceMode.Down),
4026
- };
4027
- }
4028
- /**
4029
- * Read the appearance stream of annotation
4030
- * @param annotationPtr - pointer to pdf annotation
4031
- * @param mode - appearance mode
4032
- * @returns appearance stream
4033
- *
4034
- * @private
4035
- */
4036
- readPageAnnoAppearanceStream(annotationPtr, mode = AppearanceMode.Normal) {
4037
- const utf16Length = this.pdfiumModule.FPDFAnnot_GetAP(annotationPtr, mode, 0, 0);
4038
- const bytesCount = (utf16Length + 1) * 2; // include NIL
4039
- const bufferPtr = this.malloc(bytesCount);
4040
- this.pdfiumModule.FPDFAnnot_GetAP(annotationPtr, mode, bufferPtr, bytesCount);
4041
- const ap = this.pdfiumModule.pdfium.UTF16ToString(bufferPtr);
4042
- this.free(bufferPtr);
4043
- return ap;
4044
- }
4045
- /**
4046
- * Change the visible colour (and opacity) of an existing annotation.
4047
- *
4048
- * For markup annotations (highlight / underline / strikeout / squiggly) we
4049
- * first clear the AP dictionary entry, otherwise the stored appearance stream
4050
- * will override the new tint. For all other sub-types we keep the existing
4051
- * AP so custom artwork isn't lost.
4052
- *
4053
- * @param doc logical document object
4054
- * @param page logical page object
4055
- * @param annotation the annotation we want to recolour
4056
- * @param colour RGBA tuple (0-255 per channel)
4057
- * @param which 0 = stroke/fill colour (PDFium's "colourType" param)
4058
- *
4059
- * @returns `true` when the operation succeeded
4060
- */
4061
- updateAnnotationColor(doc, page, annotation, color, which = 0) {
4062
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor', doc, page, annotation, color, which);
4063
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor', 'Begin', doc.id);
4064
- const task = PdfTaskHelper.create();
4065
- try {
4066
- /* 1 ── sanity & native handles ────────────────────────────────────────── */
4067
- const ctx = this.cache.getContext(doc.id);
4068
- if (!ctx) {
4069
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor', 'End', doc.id);
4070
- this.logger.warn(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor: doc closed');
4071
- task.resolve(false);
4072
- return task;
4073
- }
4074
- const pageCtx = ctx.acquirePage(page.index);
4075
- const annotPtr = this.pdfiumModule.FPDFPage_GetAnnot(pageCtx.pagePtr, annotation.id);
4076
- if (!annotPtr) {
4077
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor', 'End', doc.id);
4078
- this.logger.warn(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor: annot not found');
4079
- pageCtx.release();
4080
- task.resolve(false);
4081
- return task;
4082
- }
4083
- const ok = this.setAnnotationColor(annotPtr, color, which);
4084
- /* 4 ── regenerate appearance & clean-up ───────────────────────────────── */
4085
- if (ok) {
4086
- this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
4087
- }
4088
- this.pdfiumModule.FPDFPage_CloseAnnot(annotPtr);
4089
- pageCtx.release();
4090
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor', 'End', doc.id);
4091
- task.resolve(!!ok);
4092
- }
4093
- catch (error) {
4094
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor', 'End', doc.id);
4095
- this.logger.error(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor: error', error);
4096
- task.reject({
4097
- code: PdfErrorCode.Unknown,
4098
- message: `Failed to set annotation color: ${error instanceof Error ? error.message : String(error)}`,
4099
- });
4100
- }
4101
- return task;
4102
- }
4103
- /**
4104
- * Set the rect of specified annotation
4105
- * @param page - page info that the annotation is belonged to
4106
- * @param pagePtr - pointer of page object
4107
- * @param annotationPtr - pointer to annotation object
4108
- * @param rect - target rectangle
4109
- * @returns whether the rect is setted
4110
- *
4111
- * @private
4112
- */
4113
- setPageAnnoRect(page, pagePtr, annotationPtr, rect) {
4114
- const pageXPtr = this.malloc(8);
4115
- const pageYPtr = this.malloc(8);
4116
- if (!this.pdfiumModule.FPDF_DeviceToPage(pagePtr, 0, 0, page.size.width, page.size.height, 0, rect.origin.x, rect.origin.y, pageXPtr, pageYPtr)) {
4117
- this.free(pageXPtr);
4118
- this.free(pageYPtr);
4119
- return false;
4120
- }
4121
- const pageX = this.pdfiumModule.pdfium.getValue(pageXPtr, 'double');
4122
- const pageY = this.pdfiumModule.pdfium.getValue(pageYPtr, 'double');
4123
- this.free(pageXPtr);
4124
- this.free(pageYPtr);
4125
- const pageRectPtr = this.malloc(4 * 4);
4126
- this.pdfiumModule.pdfium.setValue(pageRectPtr, pageX, 'float');
4127
- this.pdfiumModule.pdfium.setValue(pageRectPtr + 4, pageY, 'float');
4128
- this.pdfiumModule.pdfium.setValue(pageRectPtr + 8, pageX + rect.size.width, 'float');
4129
- this.pdfiumModule.pdfium.setValue(pageRectPtr + 12, pageY - rect.size.height, 'float');
4130
- if (!this.pdfiumModule.FPDFAnnot_SetRect(annotationPtr, pageRectPtr)) {
4131
- this.free(pageRectPtr);
4132
- return false;
4133
- }
4134
- this.free(pageRectPtr);
4135
- return true;
4136
- }
4137
- /**
4138
- * Read the rectangle of annotation
4139
- * @param annotationPtr - pointer to pdf annotation
4140
- * @returns rectangle of annotation
4141
- *
4142
- * @private
4143
- */
4144
- readPageAnnoRect(annotationPtr) {
4145
- const pageRectPtr = this.malloc(4 * 4);
4146
- const pageRect = {
4147
- left: 0,
4148
- top: 0,
4149
- right: 0,
4150
- bottom: 0,
4151
- };
4152
- if (this.pdfiumModule.FPDFAnnot_GetRect(annotationPtr, pageRectPtr)) {
4153
- pageRect.left = this.pdfiumModule.pdfium.getValue(pageRectPtr, 'float');
4154
- pageRect.top = this.pdfiumModule.pdfium.getValue(pageRectPtr + 4, 'float');
4155
- pageRect.right = this.pdfiumModule.pdfium.getValue(pageRectPtr + 8, 'float');
4156
- pageRect.bottom = this.pdfiumModule.pdfium.getValue(pageRectPtr + 12, 'float');
4157
- }
4158
- this.free(pageRectPtr);
4159
- return pageRect;
4160
- }
4161
- /**
4162
- * Get highlight rects for a specific character range (for search highlighting)
4163
- * @param page - pdf page info
4164
- * @param pagePtr - pointer to pdf page
4165
- * @param textPagePtr - pointer to pdf text page
4166
- * @param startIndex - starting character index
4167
- * @param charCount - number of characters in the range
4168
- * @returns array of rectangles for highlighting the specified character range
4169
- *
4170
- * @private
4171
- */
4172
- getHighlightRects(page, pagePtr, textPagePtr, startIndex, charCount) {
4173
- const rectsCount = this.pdfiumModule.FPDFText_CountRects(textPagePtr, startIndex, charCount);
4174
- const highlightRects = [];
4175
- for (let i = 0; i < rectsCount; i++) {
4176
- const topPtr = this.malloc(8);
4177
- const leftPtr = this.malloc(8);
4178
- const rightPtr = this.malloc(8);
4179
- const bottomPtr = this.malloc(8);
4180
- const isSucceed = this.pdfiumModule.FPDFText_GetRect(textPagePtr, i, leftPtr, topPtr, rightPtr, bottomPtr);
4181
- if (!isSucceed) {
4182
- this.free(leftPtr);
4183
- this.free(topPtr);
4184
- this.free(rightPtr);
4185
- this.free(bottomPtr);
4186
- continue;
4187
- }
4188
- const left = this.pdfiumModule.pdfium.getValue(leftPtr, 'double');
4189
- const top = this.pdfiumModule.pdfium.getValue(topPtr, 'double');
4190
- const right = this.pdfiumModule.pdfium.getValue(rightPtr, 'double');
4191
- const bottom = this.pdfiumModule.pdfium.getValue(bottomPtr, 'double');
4192
- this.free(leftPtr);
4193
- this.free(topPtr);
4194
- this.free(rightPtr);
4195
- this.free(bottomPtr);
4196
- const deviceXPtr = this.malloc(4);
4197
- const deviceYPtr = this.malloc(4);
4198
- this.pdfiumModule.FPDF_PageToDevice(pagePtr, 0, 0, page.size.width, page.size.height, 0, left, top, deviceXPtr, deviceYPtr);
4199
- const x = this.pdfiumModule.pdfium.getValue(deviceXPtr, 'i32');
4200
- const y = this.pdfiumModule.pdfium.getValue(deviceYPtr, 'i32');
4201
- this.free(deviceXPtr);
4202
- this.free(deviceYPtr);
4203
- // Convert the bottom-right coordinates to width/height
4204
- const width = Math.ceil(Math.abs(right - left));
4205
- const height = Math.ceil(Math.abs(top - bottom));
4206
- highlightRects.push({
4207
- origin: { x, y },
4208
- size: { width, height },
4209
- });
4210
- }
4211
- return highlightRects;
4212
- }
4213
- /**
4214
- * Search for a keyword across all pages in the document
4215
- * Returns all search results throughout the entire document
4216
- *
4217
- * @param doc - Pdf document object
4218
- * @param keyword - Search keyword
4219
- * @param flags - Match flags for search
4220
- * @returns Promise of all search results in the document
4221
- *
4222
- * @public
4223
- */
4224
- searchAllPages(doc, keyword, flags = []) {
4225
- this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'searchAllPages', doc, keyword, flags);
4226
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `SearchAllPages`, 'Begin', doc.id);
4227
- const ctx = this.cache.getContext(doc.id);
4228
- if (!ctx) {
4229
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `SearchAllPages`, 'End', doc.id);
4230
- return PdfTaskHelper.resolve({ results: [], total: 0 });
4231
- }
4232
- const length = 2 * (keyword.length + 1);
4233
- const keywordPtr = this.malloc(length);
4234
- this.pdfiumModule.pdfium.stringToUTF16(keyword, keywordPtr, length);
4235
- const flag = flags.reduce((flag, currFlag) => {
4236
- return flag | currFlag;
4237
- }, MatchFlag.None);
4238
- const results = [];
4239
- // Search through all pages
4240
- const searchAllPagesTask = PdfTaskHelper.create();
4241
- // Execute search in a separate function to avoid issues with resolve parameter
4242
- const executeSearch = async () => {
4243
- for (let pageIndex = 0; pageIndex < doc.pageCount; pageIndex++) {
4244
- // Get all results for the current page efficiently (load page only once)
4245
- const pageResults = this.searchAllInPage(ctx, doc.pages[pageIndex], keywordPtr, flag);
4246
- results.push(...pageResults);
4247
- }
4248
- this.free(keywordPtr);
4249
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `SearchAllPages`, 'End', doc.id);
4250
- searchAllPagesTask.resolve({
4251
- results,
4252
- total: results.length,
4253
- });
4254
- };
4255
- // Start the search process
4256
- executeSearch().catch((error) => {
4257
- this.free(keywordPtr);
4258
- this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `SearchAllPages`, 'End', doc.id);
4259
- searchAllPagesTask.reject({
4260
- code: PdfErrorCode.Unknown,
4261
- message: `Error searching document: ${error}`,
4262
- });
4263
- });
4264
- return searchAllPagesTask;
4265
- }
4266
- /**
4267
- * Extract word-aligned context for a search hit.
4268
- *
4269
- * @param fullText full UTF-16 page text (fetch this once per page!)
4270
- * @param start index of 1st char that matched
4271
- * @param count number of chars in the match
4272
- * @param windowChars minimum context chars to keep left & right
4273
- */
4274
- buildContext(fullText, start, count, windowChars = 30) {
4275
- const WORD_BREAK = /[\s\u00A0.,;:!?()\[\]{}<>/\\\-"'`"”\u2013\u2014]/;
4276
- // Find the start of a word moving left
4277
- const findWordStart = (index) => {
4278
- while (index > 0 && !WORD_BREAK.test(fullText[index - 1]))
4279
- index--;
4280
- return index;
4281
- };
4282
- // Find the end of a word moving right
4283
- const findWordEnd = (index) => {
4284
- while (index < fullText.length && !WORD_BREAK.test(fullText[index]))
4285
- index++;
4286
- return index;
4287
- };
4288
- // Move left to build context
4289
- let left = start;
4290
- while (left > 0 && WORD_BREAK.test(fullText[left - 1]))
4291
- left--; // Skip blanks
4292
- let collected = 0;
4293
- while (left > 0 && collected < windowChars) {
4294
- left--;
4295
- if (!WORD_BREAK.test(fullText[left]))
4296
- collected++;
4297
- }
4298
- left = findWordStart(left);
4299
- // Move right to build context
4300
- let right = start + count;
4301
- while (right < fullText.length && WORD_BREAK.test(fullText[right]))
4302
- right++; // Skip blanks
4303
- collected = 0;
4304
- while (right < fullText.length && collected < windowChars) {
4305
- if (!WORD_BREAK.test(fullText[right]))
4306
- collected++;
4307
- right++;
4308
- }
4309
- right = findWordEnd(right);
4310
- // Compose the context
4311
- const before = fullText.slice(left, start).replace(/\s+/g, ' ').trimStart();
4312
- const match = fullText.slice(start, start + count);
4313
- const after = fullText
4314
- .slice(start + count, right)
4315
- .replace(/\s+/g, ' ')
4316
- .trimEnd();
4317
- return {
4318
- before: this.tidy(before),
4319
- match: this.tidy(match),
4320
- after: this.tidy(after),
4321
- truncatedLeft: left > 0,
4322
- truncatedRight: right < fullText.length,
4323
- };
4324
- }
4325
- /**
4326
- * Tidy the text to remove any non-printable characters and whitespace
4327
- * @param s - text to tidy
4328
- * @returns tidied text
4329
- *
4330
- * @private
4331
- */
4332
- tidy(s) {
4333
- return (s
4334
- /* 1️⃣ join words split by hyphen + U+FFFE + whitespace */
4335
- .replace(/-\uFFFE\s*/g, '')
4336
- /* 2️⃣ drop any remaining U+FFFE, soft-hyphen, zero-width chars */
4337
- .replace(/[\uFFFE\u00AD\u200B\u2060\uFEFF]/g, '')
4338
- /* 3️⃣ collapse whitespace so we stay on one line */
4339
- .replace(/\s+/g, ' '));
4340
- }
4341
- /**
4342
- * Search for all occurrences of a keyword on a single page
4343
- * This method efficiently loads the page only once and finds all matches
4344
- *
4345
- * @param docPtr - pointer to pdf document
4346
- * @param page - pdf page object
4347
- * @param pageIndex - index of the page
4348
- * @param keywordPtr - pointer to the search keyword
4349
- * @param flag - search flags
4350
- * @returns array of search results on this page
4351
- *
4352
- * @private
4353
- */
4354
- searchAllInPage(ctx, page, keywordPtr, flag) {
4355
- const pageIndex = page.index;
4356
- // Load the page and text page only once
4357
- const pageCtx = ctx.acquirePage(pageIndex);
4358
- const textPagePtr = pageCtx.getTextPage();
4359
- // Load the full text of the page once
4360
- const total = this.pdfiumModule.FPDFText_CountChars(textPagePtr);
4361
- const bufPtr = this.malloc(2 * (total + 1));
4362
- this.pdfiumModule.FPDFText_GetText(textPagePtr, 0, total, bufPtr);
4363
- const fullText = this.pdfiumModule.pdfium.UTF16ToString(bufPtr);
4364
- this.free(bufPtr);
4365
- const pageResults = [];
4366
- // Initialize search handle once for the page
4367
- const searchHandle = this.pdfiumModule.FPDFText_FindStart(textPagePtr, keywordPtr, flag, 0);
4368
- // Call FindNext repeatedly to get all matches on the page
4369
- while (this.pdfiumModule.FPDFText_FindNext(searchHandle)) {
4370
- const charIndex = this.pdfiumModule.FPDFText_GetSchResultIndex(searchHandle);
4371
- const charCount = this.pdfiumModule.FPDFText_GetSchCount(searchHandle);
4372
- const rects = this.getHighlightRects(page, pageCtx.pagePtr, textPagePtr, charIndex, charCount);
4373
- const context = this.buildContext(fullText, charIndex, charCount);
4374
- pageResults.push({
4375
- pageIndex,
4376
- charIndex,
4377
- charCount,
4378
- rects,
4379
- context,
4380
- });
4381
- }
4382
- // Close the search handle only once after finding all results
4383
- this.pdfiumModule.FPDFText_FindClose(searchHandle);
4384
- // Close the text page and page only once
4385
- pageCtx.release();
4386
- return pageResults;
4387
- }
4388
- }
4389
-
4390
- async function createPdfiumEngine(wasmUrl, logger) {
4391
- const response = await fetch(wasmUrl);
4392
- const wasmBinary = await response.arrayBuffer();
4393
- const wasmModule = await init({ wasmBinary });
4394
- return new PdfiumEngine(wasmModule, logger);
4395
- }
4396
-
4397
- export { createPdfiumEngine };
4398
- //# sourceMappingURL=pdfium-direct-engine.js.map