@embedpdf/engines 1.0.11 → 1.0.13

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