@lexmata/micropdf 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +985 -0
  3. package/binding.gyp +73 -0
  4. package/dist/annot.d.ts +458 -0
  5. package/dist/annot.d.ts.map +1 -0
  6. package/dist/annot.js +697 -0
  7. package/dist/annot.js.map +1 -0
  8. package/dist/archive.d.ts +128 -0
  9. package/dist/archive.d.ts.map +1 -0
  10. package/dist/archive.js +268 -0
  11. package/dist/archive.js.map +1 -0
  12. package/dist/buffer.d.ts +572 -0
  13. package/dist/buffer.d.ts.map +1 -0
  14. package/dist/buffer.js +971 -0
  15. package/dist/buffer.js.map +1 -0
  16. package/dist/colorspace.d.ts +287 -0
  17. package/dist/colorspace.d.ts.map +1 -0
  18. package/dist/colorspace.js +542 -0
  19. package/dist/colorspace.js.map +1 -0
  20. package/dist/context.d.ts +184 -0
  21. package/dist/context.d.ts.map +1 -0
  22. package/dist/context.js +320 -0
  23. package/dist/context.js.map +1 -0
  24. package/dist/cookie.d.ts +164 -0
  25. package/dist/cookie.d.ts.map +1 -0
  26. package/dist/cookie.js +306 -0
  27. package/dist/cookie.js.map +1 -0
  28. package/dist/device.d.ts +169 -0
  29. package/dist/device.d.ts.map +1 -0
  30. package/dist/device.js +350 -0
  31. package/dist/device.js.map +1 -0
  32. package/dist/display-list.d.ts +202 -0
  33. package/dist/display-list.d.ts.map +1 -0
  34. package/dist/display-list.js +410 -0
  35. package/dist/display-list.js.map +1 -0
  36. package/dist/document.d.ts +637 -0
  37. package/dist/document.d.ts.map +1 -0
  38. package/dist/document.js +902 -0
  39. package/dist/document.js.map +1 -0
  40. package/dist/easy.d.ts +423 -0
  41. package/dist/easy.d.ts.map +1 -0
  42. package/dist/easy.js +644 -0
  43. package/dist/easy.js.map +1 -0
  44. package/dist/enhanced.d.ts +226 -0
  45. package/dist/enhanced.d.ts.map +1 -0
  46. package/dist/enhanced.js +368 -0
  47. package/dist/enhanced.js.map +1 -0
  48. package/dist/filter.d.ts +51 -0
  49. package/dist/filter.d.ts.map +1 -0
  50. package/dist/filter.js +381 -0
  51. package/dist/filter.js.map +1 -0
  52. package/dist/font.d.ts +222 -0
  53. package/dist/font.d.ts.map +1 -0
  54. package/dist/font.js +381 -0
  55. package/dist/font.js.map +1 -0
  56. package/dist/form.d.ts +214 -0
  57. package/dist/form.d.ts.map +1 -0
  58. package/dist/form.js +497 -0
  59. package/dist/form.js.map +1 -0
  60. package/dist/geometry.d.ts +469 -0
  61. package/dist/geometry.d.ts.map +1 -0
  62. package/dist/geometry.js +780 -0
  63. package/dist/geometry.js.map +1 -0
  64. package/dist/image.d.ts +172 -0
  65. package/dist/image.d.ts.map +1 -0
  66. package/dist/image.js +348 -0
  67. package/dist/image.js.map +1 -0
  68. package/dist/index.d.ts +171 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +339 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/link.d.ts +168 -0
  73. package/dist/link.d.ts.map +1 -0
  74. package/dist/link.js +343 -0
  75. package/dist/link.js.map +1 -0
  76. package/dist/micropdf.d.ts +40 -0
  77. package/dist/micropdf.d.ts.map +1 -0
  78. package/dist/micropdf.js +45 -0
  79. package/dist/micropdf.js.map +1 -0
  80. package/dist/nanopdf.d.ts +40 -0
  81. package/dist/nanopdf.d.ts.map +1 -0
  82. package/dist/nanopdf.js +45 -0
  83. package/dist/nanopdf.js.map +1 -0
  84. package/dist/native.d.ts +242 -0
  85. package/dist/native.d.ts.map +1 -0
  86. package/dist/native.js +509 -0
  87. package/dist/native.js.map +1 -0
  88. package/dist/output.d.ts +166 -0
  89. package/dist/output.d.ts.map +1 -0
  90. package/dist/output.js +365 -0
  91. package/dist/output.js.map +1 -0
  92. package/dist/path.d.ts +420 -0
  93. package/dist/path.d.ts.map +1 -0
  94. package/dist/path.js +687 -0
  95. package/dist/path.js.map +1 -0
  96. package/dist/pdf/object.d.ts +489 -0
  97. package/dist/pdf/object.d.ts.map +1 -0
  98. package/dist/pdf/object.js +1045 -0
  99. package/dist/pdf/object.js.map +1 -0
  100. package/dist/pixmap.d.ts +315 -0
  101. package/dist/pixmap.d.ts.map +1 -0
  102. package/dist/pixmap.js +590 -0
  103. package/dist/pixmap.js.map +1 -0
  104. package/dist/profiler.d.ts +159 -0
  105. package/dist/profiler.d.ts.map +1 -0
  106. package/dist/profiler.js +380 -0
  107. package/dist/profiler.js.map +1 -0
  108. package/dist/render-options.d.ts +227 -0
  109. package/dist/render-options.d.ts.map +1 -0
  110. package/dist/render-options.js +130 -0
  111. package/dist/render-options.js.map +1 -0
  112. package/dist/resource-tracking.d.ts +332 -0
  113. package/dist/resource-tracking.d.ts.map +1 -0
  114. package/dist/resource-tracking.js +653 -0
  115. package/dist/resource-tracking.js.map +1 -0
  116. package/dist/simple.d.ts +276 -0
  117. package/dist/simple.d.ts.map +1 -0
  118. package/dist/simple.js +343 -0
  119. package/dist/simple.js.map +1 -0
  120. package/dist/stext.d.ts +290 -0
  121. package/dist/stext.d.ts.map +1 -0
  122. package/dist/stext.js +312 -0
  123. package/dist/stext.js.map +1 -0
  124. package/dist/stream.d.ts +174 -0
  125. package/dist/stream.d.ts.map +1 -0
  126. package/dist/stream.js +476 -0
  127. package/dist/stream.js.map +1 -0
  128. package/dist/text.d.ts +337 -0
  129. package/dist/text.d.ts.map +1 -0
  130. package/dist/text.js +454 -0
  131. package/dist/text.js.map +1 -0
  132. package/dist/typed-arrays.d.ts +127 -0
  133. package/dist/typed-arrays.d.ts.map +1 -0
  134. package/dist/typed-arrays.js +410 -0
  135. package/dist/typed-arrays.js.map +1 -0
  136. package/dist/types.d.ts +358 -0
  137. package/dist/types.d.ts.map +1 -0
  138. package/dist/types.js +216 -0
  139. package/dist/types.js.map +1 -0
  140. package/native/annot.cc +557 -0
  141. package/native/buffer.cc +204 -0
  142. package/native/colorspace.cc +166 -0
  143. package/native/context.cc +84 -0
  144. package/native/cookie.cc +179 -0
  145. package/native/device.cc +179 -0
  146. package/native/display_list.cc +179 -0
  147. package/native/document.cc +268 -0
  148. package/native/enhanced.cc +70 -0
  149. package/native/font.cc +282 -0
  150. package/native/form.cc +523 -0
  151. package/native/geometry.cc +255 -0
  152. package/native/image.cc +216 -0
  153. package/native/include/micropdf/enhanced.h +38 -0
  154. package/native/include/micropdf/types.h +36 -0
  155. package/native/include/micropdf.h +106 -0
  156. package/native/include/mupdf-ffi.h +39 -0
  157. package/native/include/mupdf.h +11 -0
  158. package/native/include/mupdf_minimal.h +381 -0
  159. package/native/lib/linux-x64/libmicropdf.a +0 -0
  160. package/native/link.cc +234 -0
  161. package/native/micropdf.cc +71 -0
  162. package/native/output.cc +229 -0
  163. package/native/page.cc +572 -0
  164. package/native/path.cc +259 -0
  165. package/native/pixmap.cc +240 -0
  166. package/native/stext.cc +610 -0
  167. package/native/stream.cc +239 -0
  168. package/package.json +120 -0
  169. package/scripts/build-from-rust.js +97 -0
  170. package/scripts/install.js +184 -0
package/native/page.cc ADDED
@@ -0,0 +1,572 @@
1
+ /**
2
+ * Page FFI Bindings
3
+ *
4
+ * Implements N-API bindings for PDF page functions including rendering and text extraction.
5
+ */
6
+
7
+ #include <napi.h>
8
+ #include "include/mupdf_minimal.h"
9
+ #include <vector>
10
+
11
+ /**
12
+ * Helper: Extract context from object
13
+ */
14
+ static fz_context GetContext(const Napi::Object& obj) {
15
+ return obj.Get("_handle").As<Napi::Number>().Int32Value();
16
+ }
17
+
18
+ /**
19
+ * Helper: Extract document handle from object
20
+ */
21
+ static fz_document GetDocument(const Napi::Object& obj) {
22
+ return obj.Get("_handle").As<Napi::Number>().Int32Value();
23
+ }
24
+
25
+ /**
26
+ * Helper: Extract page handle from object
27
+ */
28
+ static fz_page GetPage(const Napi::Object& obj) {
29
+ return obj.Get("_handle").As<Napi::Number>().Int32Value();
30
+ }
31
+
32
+ /**
33
+ * Helper: Extract matrix from object
34
+ */
35
+ static fz_matrix GetMatrix(const Napi::Object& obj) {
36
+ fz_matrix m;
37
+ m.a = obj.Get("a").As<Napi::Number>().FloatValue();
38
+ m.b = obj.Get("b").As<Napi::Number>().FloatValue();
39
+ m.c = obj.Get("c").As<Napi::Number>().FloatValue();
40
+ m.d = obj.Get("d").As<Napi::Number>().FloatValue();
41
+ m.e = obj.Get("e").As<Napi::Number>().FloatValue();
42
+ m.f = obj.Get("f").As<Napi::Number>().FloatValue();
43
+ return m;
44
+ }
45
+
46
+ /**
47
+ * Helper: Create rect object
48
+ */
49
+ static Napi::Object CreateRect(Napi::Env env, const fz_rect& r) {
50
+ Napi::Object obj = Napi::Object::New(env);
51
+ obj.Set("x0", Napi::Number::New(env, r.x0));
52
+ obj.Set("y0", Napi::Number::New(env, r.y0));
53
+ obj.Set("x1", Napi::Number::New(env, r.x1));
54
+ obj.Set("y1", Napi::Number::New(env, r.y1));
55
+ return obj;
56
+ }
57
+
58
+ /**
59
+ * Load page from document
60
+ * JavaScript: loadPage(ctx: NativeContext, doc: NativeDocument, pageNum: number): NativePage
61
+ */
62
+ Napi::Value LoadPage(const Napi::CallbackInfo& info) {
63
+ Napi::Env env = info.Env();
64
+
65
+ if (info.Length() < 3 || !info[0].IsObject() || !info[1].IsObject() || !info[2].IsNumber()) {
66
+ Napi::TypeError::New(env, "Expected (context, document, pageNum)").ThrowAsJavaScriptException();
67
+ return env.Null();
68
+ }
69
+
70
+ fz_context ctx = GetContext(info[0].As<Napi::Object>());
71
+ fz_document doc = GetDocument(info[1].As<Napi::Object>());
72
+ int pageNum = info[2].As<Napi::Number>().Int32Value();
73
+
74
+ fz_page page = fz_load_page(ctx, doc, pageNum);
75
+ if (page == 0) {
76
+ Napi::Error::New(env, "Failed to load page").ThrowAsJavaScriptException();
77
+ return env.Null();
78
+ }
79
+
80
+ Napi::Object obj = Napi::Object::New(env);
81
+ obj.Set("_handle", Napi::Number::New(env, page));
82
+ return obj;
83
+ }
84
+
85
+ /**
86
+ * Drop/free a page
87
+ * JavaScript: dropPage(ctx: NativeContext, page: NativePage): void
88
+ */
89
+ Napi::Value DropPage(const Napi::CallbackInfo& info) {
90
+ Napi::Env env = info.Env();
91
+
92
+ if (info.Length() < 2 || !info[0].IsObject() || !info[1].IsObject()) {
93
+ Napi::TypeError::New(env, "Expected (context, page)").ThrowAsJavaScriptException();
94
+ return env.Undefined();
95
+ }
96
+
97
+ fz_context ctx = GetContext(info[0].As<Napi::Object>());
98
+ fz_page page = GetPage(info[1].As<Napi::Object>());
99
+
100
+ fz_drop_page(ctx, page);
101
+
102
+ return env.Undefined();
103
+ }
104
+
105
+ /**
106
+ * Get page bounds
107
+ * JavaScript: boundPage(ctx: NativeContext, page: NativePage): NativeRect
108
+ */
109
+ Napi::Value BoundPage(const Napi::CallbackInfo& info) {
110
+ Napi::Env env = info.Env();
111
+
112
+ if (info.Length() < 2 || !info[0].IsObject() || !info[1].IsObject()) {
113
+ Napi::TypeError::New(env, "Expected (context, page)").ThrowAsJavaScriptException();
114
+ return env.Null();
115
+ }
116
+
117
+ fz_context ctx = GetContext(info[0].As<Napi::Object>());
118
+ fz_page page = GetPage(info[1].As<Napi::Object>());
119
+
120
+ fz_rect bounds = fz_bound_page(ctx, page);
121
+
122
+ return CreateRect(env, bounds);
123
+ }
124
+
125
+ /**
126
+ * Render page to pixmap
127
+ * JavaScript: renderPage(ctx: NativeContext, page: NativePage, matrix: NativeMatrix, colorspace: NativeColorspace, alpha: boolean): NativePixmap
128
+ */
129
+ Napi::Value RenderPage(const Napi::CallbackInfo& info) {
130
+ Napi::Env env = info.Env();
131
+
132
+ if (info.Length() < 5) {
133
+ Napi::TypeError::New(env, "Expected (context, page, matrix, colorspace, alpha)").ThrowAsJavaScriptException();
134
+ return env.Null();
135
+ }
136
+
137
+ fz_context ctx = GetContext(info[0].As<Napi::Object>());
138
+ fz_page page = GetPage(info[1].As<Napi::Object>());
139
+ fz_matrix matrix = GetMatrix(info[2].As<Napi::Object>());
140
+ bool alpha = info[4].As<Napi::Boolean>().Value();
141
+
142
+ // Get colorspace (simplified - assume RGB for now)
143
+ fz_colorspace cs = fz_device_rgb(ctx);
144
+
145
+ fz_pixmap pix = fz_new_pixmap_from_page(ctx, page, matrix, cs, alpha ? 1 : 0);
146
+ if (pix == 0) {
147
+ Napi::Error::New(env, "Failed to render page").ThrowAsJavaScriptException();
148
+ return env.Null();
149
+ }
150
+
151
+ Napi::Object obj = Napi::Object::New(env);
152
+ obj.Set("_handle", Napi::Number::New(env, pix));
153
+ obj.Set("width", Napi::Number::New(env, fz_pixmap_width(ctx, pix)));
154
+ obj.Set("height", Napi::Number::New(env, fz_pixmap_height(ctx, pix)));
155
+ return obj;
156
+ }
157
+
158
+ /**
159
+ * Render page to PNG buffer
160
+ * JavaScript: renderPageToPNG(ctx: NativeContext, page: NativePage, dpi: number, colorspace: NativeColorspace): Buffer
161
+ */
162
+ Napi::Value RenderPageToPNG(const Napi::CallbackInfo& info) {
163
+ Napi::Env env = info.Env();
164
+
165
+ if (info.Length() < 4) {
166
+ Napi::TypeError::New(env, "Expected (context, page, dpi, colorspace)").ThrowAsJavaScriptException();
167
+ return env.Null();
168
+ }
169
+
170
+ fz_context ctx = GetContext(info[0].As<Napi::Object>());
171
+ fz_page page = GetPage(info[1].As<Napi::Object>());
172
+ float dpi = info[2].As<Napi::Number>().FloatValue();
173
+
174
+ // Create transform matrix for DPI
175
+ fz_matrix matrix = fz_scale(dpi / 72.0f, dpi / 72.0f);
176
+ fz_colorspace cs = fz_device_rgb(ctx);
177
+
178
+ // Render to pixmap
179
+ fz_pixmap pix = fz_new_pixmap_from_page(ctx, page, matrix, cs, 0);
180
+ if (pix == 0) {
181
+ Napi::Error::New(env, "Failed to render page").ThrowAsJavaScriptException();
182
+ return env.Null();
183
+ }
184
+
185
+ // Encode to PNG
186
+ fz_buffer buf = fz_new_buffer_from_pixmap_as_png(ctx, pix, 0);
187
+ fz_drop_pixmap(ctx, pix);
188
+
189
+ if (buf == 0) {
190
+ Napi::Error::New(env, "Failed to encode PNG").ThrowAsJavaScriptException();
191
+ return env.Null();
192
+ }
193
+
194
+ // Get buffer data
195
+ size_t len;
196
+ const unsigned char* data = fz_buffer_data(ctx, buf, &len);
197
+
198
+ // Create Node.js buffer
199
+ Napi::Buffer<uint8_t> result = Napi::Buffer<uint8_t>::Copy(env, data, len);
200
+
201
+ fz_drop_buffer(ctx, buf);
202
+
203
+ return result;
204
+ }
205
+
206
+ /**
207
+ * Extract text from page
208
+ * JavaScript: extractText(ctx: NativeContext, page: NativePage): string
209
+ */
210
+ Napi::Value ExtractText(const Napi::CallbackInfo& info) {
211
+ Napi::Env env = info.Env();
212
+
213
+ if (info.Length() < 2 || !info[0].IsObject() || !info[1].IsObject()) {
214
+ Napi::TypeError::New(env, "Expected (context, page)").ThrowAsJavaScriptException();
215
+ return env.Null();
216
+ }
217
+
218
+ fz_context ctx = GetContext(info[0].As<Napi::Object>());
219
+ fz_page page = GetPage(info[1].As<Napi::Object>());
220
+
221
+ // Create text page
222
+ fz_stext_page stext = fz_new_stext_page_from_page(ctx, page, nullptr);
223
+ if (stext == 0) {
224
+ Napi::Error::New(env, "Failed to extract text").ThrowAsJavaScriptException();
225
+ return env.Null();
226
+ }
227
+
228
+ // Extract text to buffer
229
+ fz_buffer buf = fz_new_buffer_from_stext_page(ctx, stext);
230
+ fz_drop_stext_page(ctx, stext);
231
+
232
+ if (buf == 0) {
233
+ Napi::Error::New(env, "Failed to create text buffer").ThrowAsJavaScriptException();
234
+ return env.Null();
235
+ }
236
+
237
+ size_t len;
238
+ const char* data = (const char*)fz_buffer_data(ctx, buf, &len);
239
+ std::string text(data, len);
240
+
241
+ fz_drop_buffer(ctx, buf);
242
+
243
+ return Napi::String::New(env, text);
244
+ }
245
+
246
+ /**
247
+ * Extract text blocks from page
248
+ * JavaScript: extractTextBlocks(ctx: NativeContext, page: NativePage): Array<{text: string, bbox: NativeRect}>
249
+ */
250
+ Napi::Value ExtractTextBlocks(const Napi::CallbackInfo& info) {
251
+ Napi::Env env = info.Env();
252
+
253
+ if (info.Length() < 2 || !info[0].IsObject() || !info[1].IsObject()) {
254
+ Napi::TypeError::New(env, "Expected (context, page)").ThrowAsJavaScriptException();
255
+ return env.Null();
256
+ }
257
+
258
+ fz_context ctx = GetContext(info[0].As<Napi::Object>());
259
+ fz_page page = GetPage(info[1].As<Napi::Object>());
260
+
261
+ // Create text page
262
+ fz_stext_page stext = fz_new_stext_page_from_page(ctx, page, nullptr);
263
+ if (stext == 0) {
264
+ Napi::Error::New(env, "Failed to extract text").ThrowAsJavaScriptException();
265
+ return env.Null();
266
+ }
267
+
268
+ Napi::Array blocks = Napi::Array::New(env);
269
+ uint32_t blockIndex = 0;
270
+
271
+ // Iterate through blocks (simplified - would need to access stext_page internals)
272
+ // For now, return a single block with all text
273
+ fz_buffer buf = fz_new_buffer_from_stext_page(ctx, stext);
274
+ size_t len;
275
+ const char* data = (const char*)fz_buffer_data(ctx, buf, &len);
276
+
277
+ Napi::Object block = Napi::Object::New(env);
278
+ block.Set("text", Napi::String::New(env, data, len));
279
+ block.Set("bbox", CreateRect(env, fz_bound_page(ctx, page)));
280
+
281
+ blocks[blockIndex++] = block;
282
+
283
+ fz_drop_buffer(ctx, buf);
284
+ fz_drop_stext_page(ctx, stext);
285
+
286
+ return blocks;
287
+ }
288
+
289
+ /**
290
+ * Get page links
291
+ * JavaScript: getPageLinks(ctx: NativeContext, page: NativePage): Array<{rect: NativeRect, uri?: string, page?: number}>
292
+ */
293
+ Napi::Value GetPageLinks(const Napi::CallbackInfo& info) {
294
+ Napi::Env env = info.Env();
295
+
296
+ if (info.Length() < 2 || !info[0].IsObject() || !info[1].IsObject()) {
297
+ Napi::TypeError::New(env, "Expected (context, page)").ThrowAsJavaScriptException();
298
+ return env.Null();
299
+ }
300
+
301
+ fz_context ctx = GetContext(info[0].As<Napi::Object>());
302
+ fz_page page = GetPage(info[1].As<Napi::Object>());
303
+
304
+ Napi::Array links = Napi::Array::New(env);
305
+
306
+ // Get links from page
307
+ fz_link link = fz_load_links(ctx, page);
308
+ uint32_t linkIndex = 0;
309
+
310
+ while (link != 0) {
311
+ Napi::Object linkObj = Napi::Object::New(env);
312
+
313
+ fz_rect rect = fz_link_rect(ctx, link);
314
+ linkObj.Set("rect", CreateRect(env, rect));
315
+
316
+ char uri_buffer[2048];
317
+ fz_link_uri(ctx, link, uri_buffer, sizeof(uri_buffer));
318
+ if (uri_buffer[0] != '\0') {
319
+ linkObj.Set("uri", Napi::String::New(env, uri_buffer));
320
+ }
321
+
322
+ links[linkIndex++] = linkObj;
323
+ link = fz_next_link(ctx, link);
324
+ }
325
+
326
+ return links;
327
+ }
328
+
329
+ /**
330
+ * Search text on page
331
+ * JavaScript: searchText(ctx: NativeContext, page: NativePage, needle: string, hitMax: boolean): Array<NativeRect>
332
+ */
333
+ Napi::Value SearchText(const Napi::CallbackInfo& info) {
334
+ Napi::Env env = info.Env();
335
+
336
+ if (info.Length() < 4) {
337
+ Napi::TypeError::New(env, "Expected (context, page, needle, hitMax)").ThrowAsJavaScriptException();
338
+ return env.Null();
339
+ }
340
+
341
+ fz_context ctx = GetContext(info[0].As<Napi::Object>());
342
+ fz_page page = GetPage(info[1].As<Napi::Object>());
343
+ std::string needle = info[2].As<Napi::String>().Utf8Value();
344
+
345
+ // Create text page for searching
346
+ fz_stext_page stext = fz_new_stext_page_from_page(ctx, page, nullptr);
347
+ if (stext == 0) {
348
+ Napi::Error::New(env, "Failed to create text page").ThrowAsJavaScriptException();
349
+ return env.Null();
350
+ }
351
+
352
+ Napi::Array results = Napi::Array::New(env);
353
+
354
+ // Search for text (simplified - would need proper quad array handling)
355
+ fz_quad hits[512];
356
+ int hitCount = fz_search_stext_page(ctx, stext, needle.c_str(), nullptr, hits, 512);
357
+
358
+ for (int i = 0; i < hitCount; i++) {
359
+ fz_rect rect;
360
+ rect.x0 = hits[i].ll.x;
361
+ rect.y0 = hits[i].ll.y;
362
+ rect.x1 = hits[i].ur.x;
363
+ rect.y1 = hits[i].ur.y;
364
+ results[i] = CreateRect(env, rect);
365
+ }
366
+
367
+ fz_drop_stext_page(ctx, stext);
368
+
369
+ return results;
370
+ }
371
+
372
+ /**
373
+ * Render page with advanced options
374
+ * Supports anti-aliasing, progress callbacks, timeouts, etc.
375
+ *
376
+ * JavaScript: renderPageWithOptions(ctx, page, options): NativePixmap
377
+ *
378
+ * options: {
379
+ * dpi?: number,
380
+ * matrix?: NativeMatrix,
381
+ * colorspace?: NativeColorspace,
382
+ * alpha?: boolean,
383
+ * antiAlias?: number (0=None, 1=Low, 2=Medium, 4=High),
384
+ * timeout?: number,
385
+ * renderAnnotations?: boolean,
386
+ * renderFormFields?: boolean
387
+ * }
388
+ */
389
+ Napi::Value RenderPageWithOptions(const Napi::CallbackInfo& info) {
390
+ Napi::Env env = info.Env();
391
+
392
+ if (info.Length() < 3 || !info[0].IsObject() || !info[1].IsObject() || !info[2].IsObject()) {
393
+ Napi::TypeError::New(env, "Expected (context, page, options)").ThrowAsJavaScriptException();
394
+ return env.Null();
395
+ }
396
+
397
+ fz_context ctx = GetContext(info[0].As<Napi::Object>());
398
+ fz_page page = GetPage(info[1].As<Napi::Object>());
399
+ Napi::Object options = info[2].As<Napi::Object>();
400
+
401
+ // Extract options
402
+ fz_matrix matrix;
403
+ if (options.Has("matrix")) {
404
+ matrix = GetMatrix(options.Get("matrix").As<Napi::Object>());
405
+ } else if (options.Has("dpi")) {
406
+ float dpi = options.Get("dpi").As<Napi::Number>().FloatValue();
407
+ float scale = dpi / 72.0f;
408
+ matrix = fz_scale(scale, scale);
409
+ } else {
410
+ matrix = fz_identity();
411
+ }
412
+
413
+ // Get colorspace
414
+ fz_colorspace cs;
415
+ if (options.Has("colorspace")) {
416
+ // TODO: Parse colorspace from options
417
+ cs = fz_device_rgb(ctx);
418
+ } else {
419
+ cs = fz_device_rgb(ctx);
420
+ }
421
+
422
+ // Get alpha
423
+ bool alpha = true;
424
+ if (options.Has("alpha")) {
425
+ alpha = options.Get("alpha").As<Napi::Boolean>().Value();
426
+ }
427
+
428
+ // Get anti-aliasing level
429
+ // Note: In a full implementation, this would be passed to the rendering device
430
+ // For now, we just validate it
431
+ if (options.Has("antiAlias")) {
432
+ int aa_level = options.Get("antiAlias").As<Napi::Number>().Int32Value();
433
+ // Validate: 0 (None), 1 (Low), 2 (Medium), 4 (High)
434
+ if (aa_level != 0 && aa_level != 1 && aa_level != 2 && aa_level != 4) {
435
+ Napi::TypeError::New(env, "Invalid antiAlias level (must be 0, 1, 2, or 4)")
436
+ .ThrowAsJavaScriptException();
437
+ return env.Null();
438
+ }
439
+ // TODO: Use aa_level when creating rendering device
440
+ }
441
+
442
+ // Get timeout
443
+ if (options.Has("timeout")) {
444
+ int timeout = options.Get("timeout").As<Napi::Number>().Int32Value();
445
+ // TODO: Implement timeout handling with a cookie
446
+ if (timeout > 0) {
447
+ // For now, just validate it
448
+ }
449
+ }
450
+
451
+ // Render flags
452
+ bool render_annots = true;
453
+ bool render_forms = true;
454
+ if (options.Has("renderAnnotations")) {
455
+ render_annots = options.Get("renderAnnotations").As<Napi::Boolean>().Value();
456
+ }
457
+ if (options.Has("renderFormFields")) {
458
+ render_forms = options.Get("renderFormFields").As<Napi::Boolean>().Value();
459
+ }
460
+
461
+ // Render page to pixmap
462
+ fz_pixmap pix = fz_new_pixmap_from_page(ctx, page, matrix, cs, alpha ? 1 : 0);
463
+ if (pix == 0) {
464
+ Napi::Error::New(env, "Failed to render page").ThrowAsJavaScriptException();
465
+ return env.Null();
466
+ }
467
+
468
+ // Wrap in Napi::External with finalizer
469
+ return Napi::External<int32_t>::New(
470
+ env,
471
+ new int32_t(pix),
472
+ [](Napi::Env env, int32_t* data) {
473
+ delete data;
474
+ }
475
+ );
476
+ }
477
+
478
+ /**
479
+ * Render page to PNG with advanced options
480
+ *
481
+ * JavaScript: renderPageToPNGWithOptions(ctx, page, options): Buffer
482
+ *
483
+ * options: {
484
+ * dpi?: number,
485
+ * colorspace?: NativeColorspace,
486
+ * antiAlias?: number,
487
+ * ... (same as renderPageWithOptions)
488
+ * }
489
+ */
490
+ Napi::Value RenderPageToPNGWithOptions(const Napi::CallbackInfo& info) {
491
+ Napi::Env env = info.Env();
492
+
493
+ if (info.Length() < 3 || !info[0].IsObject() || !info[1].IsObject() || !info[2].IsObject()) {
494
+ Napi::TypeError::New(env, "Expected (context, page, options)").ThrowAsJavaScriptException();
495
+ return env.Null();
496
+ }
497
+
498
+ fz_context ctx = GetContext(info[0].As<Napi::Object>());
499
+ fz_page page = GetPage(info[1].As<Napi::Object>());
500
+ Napi::Object options = info[2].As<Napi::Object>();
501
+
502
+ // Extract DPI
503
+ float dpi = 72.0f;
504
+ if (options.Has("dpi")) {
505
+ dpi = options.Get("dpi").As<Napi::Number>().FloatValue();
506
+ }
507
+
508
+ // Create transform matrix
509
+ fz_matrix matrix = fz_scale(dpi / 72.0f, dpi / 72.0f);
510
+
511
+ // Get colorspace
512
+ fz_colorspace cs = fz_device_rgb(ctx);
513
+
514
+ // Get alpha
515
+ bool alpha = options.Has("alpha") ? options.Get("alpha").As<Napi::Boolean>().Value() : false;
516
+
517
+ // Render to pixmap
518
+ fz_pixmap pix = fz_new_pixmap_from_page(ctx, page, matrix, cs, alpha ? 1 : 0);
519
+ if (pix == 0) {
520
+ Napi::Error::New(env, "Failed to render page").ThrowAsJavaScriptException();
521
+ return env.Null();
522
+ }
523
+
524
+ // Convert to PNG
525
+ fz_buffer buf = fz_new_buffer_from_pixmap_as_png(ctx, pix, 0);
526
+ fz_drop_pixmap(ctx, pix);
527
+
528
+ if (buf == 0) {
529
+ Napi::Error::New(env, "Failed to create PNG buffer").ThrowAsJavaScriptException();
530
+ return env.Null();
531
+ }
532
+
533
+ // Get buffer data
534
+ size_t len;
535
+ const unsigned char* data = fz_buffer_data(ctx, buf, &len);
536
+
537
+ // Create Node.js Buffer
538
+ Napi::Buffer<char> result = Napi::Buffer<char>::Copy(env, (const char*)data, len);
539
+
540
+ fz_drop_buffer(ctx, buf);
541
+
542
+ return result;
543
+ }
544
+
545
+ /**
546
+ * Initialize page exports
547
+ */
548
+ Napi::Object InitPage(Napi::Env env, Napi::Object exports) {
549
+ // Basic page operations
550
+ exports.Set("loadPage", Napi::Function::New(env, LoadPage));
551
+ exports.Set("dropPage", Napi::Function::New(env, DropPage));
552
+ exports.Set("boundPage", Napi::Function::New(env, BoundPage));
553
+
554
+ // Basic rendering
555
+ exports.Set("renderPage", Napi::Function::New(env, RenderPage));
556
+ exports.Set("renderPageToPNG", Napi::Function::New(env, RenderPageToPNG));
557
+
558
+ // Advanced rendering with options
559
+ exports.Set("renderPageWithOptions", Napi::Function::New(env, RenderPageWithOptions));
560
+ exports.Set("renderPageToPNGWithOptions", Napi::Function::New(env, RenderPageToPNGWithOptions));
561
+
562
+ // Text extraction
563
+ exports.Set("extractText", Napi::Function::New(env, ExtractText));
564
+ exports.Set("extractTextBlocks", Napi::Function::New(env, ExtractTextBlocks));
565
+
566
+ // Page links and search
567
+ exports.Set("getPageLinks", Napi::Function::New(env, GetPageLinks));
568
+ exports.Set("searchText", Napi::Function::New(env, SearchText));
569
+
570
+ return exports;
571
+ }
572
+