@lexmata/micropdf 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +191 -0
- package/README.md +985 -0
- package/binding.gyp +73 -0
- package/dist/annot.d.ts +458 -0
- package/dist/annot.d.ts.map +1 -0
- package/dist/annot.js +697 -0
- package/dist/annot.js.map +1 -0
- package/dist/archive.d.ts +128 -0
- package/dist/archive.d.ts.map +1 -0
- package/dist/archive.js +268 -0
- package/dist/archive.js.map +1 -0
- package/dist/buffer.d.ts +572 -0
- package/dist/buffer.d.ts.map +1 -0
- package/dist/buffer.js +971 -0
- package/dist/buffer.js.map +1 -0
- package/dist/colorspace.d.ts +287 -0
- package/dist/colorspace.d.ts.map +1 -0
- package/dist/colorspace.js +542 -0
- package/dist/colorspace.js.map +1 -0
- package/dist/context.d.ts +184 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +320 -0
- package/dist/context.js.map +1 -0
- package/dist/cookie.d.ts +164 -0
- package/dist/cookie.d.ts.map +1 -0
- package/dist/cookie.js +306 -0
- package/dist/cookie.js.map +1 -0
- package/dist/device.d.ts +169 -0
- package/dist/device.d.ts.map +1 -0
- package/dist/device.js +350 -0
- package/dist/device.js.map +1 -0
- package/dist/display-list.d.ts +202 -0
- package/dist/display-list.d.ts.map +1 -0
- package/dist/display-list.js +410 -0
- package/dist/display-list.js.map +1 -0
- package/dist/document.d.ts +637 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +902 -0
- package/dist/document.js.map +1 -0
- package/dist/easy.d.ts +423 -0
- package/dist/easy.d.ts.map +1 -0
- package/dist/easy.js +644 -0
- package/dist/easy.js.map +1 -0
- package/dist/enhanced.d.ts +226 -0
- package/dist/enhanced.d.ts.map +1 -0
- package/dist/enhanced.js +368 -0
- package/dist/enhanced.js.map +1 -0
- package/dist/filter.d.ts +51 -0
- package/dist/filter.d.ts.map +1 -0
- package/dist/filter.js +381 -0
- package/dist/filter.js.map +1 -0
- package/dist/font.d.ts +222 -0
- package/dist/font.d.ts.map +1 -0
- package/dist/font.js +381 -0
- package/dist/font.js.map +1 -0
- package/dist/form.d.ts +214 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +497 -0
- package/dist/form.js.map +1 -0
- package/dist/geometry.d.ts +469 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/geometry.js +780 -0
- package/dist/geometry.js.map +1 -0
- package/dist/image.d.ts +172 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +348 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +171 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +339 -0
- package/dist/index.js.map +1 -0
- package/dist/link.d.ts +168 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +343 -0
- package/dist/link.js.map +1 -0
- package/dist/micropdf.d.ts +40 -0
- package/dist/micropdf.d.ts.map +1 -0
- package/dist/micropdf.js +45 -0
- package/dist/micropdf.js.map +1 -0
- package/dist/nanopdf.d.ts +40 -0
- package/dist/nanopdf.d.ts.map +1 -0
- package/dist/nanopdf.js +45 -0
- package/dist/nanopdf.js.map +1 -0
- package/dist/native.d.ts +242 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +509 -0
- package/dist/native.js.map +1 -0
- package/dist/output.d.ts +166 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +365 -0
- package/dist/output.js.map +1 -0
- package/dist/path.d.ts +420 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +687 -0
- package/dist/path.js.map +1 -0
- package/dist/pdf/object.d.ts +489 -0
- package/dist/pdf/object.d.ts.map +1 -0
- package/dist/pdf/object.js +1045 -0
- package/dist/pdf/object.js.map +1 -0
- package/dist/pixmap.d.ts +315 -0
- package/dist/pixmap.d.ts.map +1 -0
- package/dist/pixmap.js +590 -0
- package/dist/pixmap.js.map +1 -0
- package/dist/profiler.d.ts +159 -0
- package/dist/profiler.d.ts.map +1 -0
- package/dist/profiler.js +380 -0
- package/dist/profiler.js.map +1 -0
- package/dist/render-options.d.ts +227 -0
- package/dist/render-options.d.ts.map +1 -0
- package/dist/render-options.js +130 -0
- package/dist/render-options.js.map +1 -0
- package/dist/resource-tracking.d.ts +332 -0
- package/dist/resource-tracking.d.ts.map +1 -0
- package/dist/resource-tracking.js +653 -0
- package/dist/resource-tracking.js.map +1 -0
- package/dist/simple.d.ts +276 -0
- package/dist/simple.d.ts.map +1 -0
- package/dist/simple.js +343 -0
- package/dist/simple.js.map +1 -0
- package/dist/stext.d.ts +290 -0
- package/dist/stext.d.ts.map +1 -0
- package/dist/stext.js +312 -0
- package/dist/stext.js.map +1 -0
- package/dist/stream.d.ts +174 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +476 -0
- package/dist/stream.js.map +1 -0
- package/dist/text.d.ts +337 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +454 -0
- package/dist/text.js.map +1 -0
- package/dist/typed-arrays.d.ts +127 -0
- package/dist/typed-arrays.d.ts.map +1 -0
- package/dist/typed-arrays.js +410 -0
- package/dist/typed-arrays.js.map +1 -0
- package/dist/types.d.ts +358 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +216 -0
- package/dist/types.js.map +1 -0
- package/native/annot.cc +557 -0
- package/native/buffer.cc +204 -0
- package/native/colorspace.cc +166 -0
- package/native/context.cc +84 -0
- package/native/cookie.cc +179 -0
- package/native/device.cc +179 -0
- package/native/display_list.cc +179 -0
- package/native/document.cc +268 -0
- package/native/enhanced.cc +70 -0
- package/native/font.cc +282 -0
- package/native/form.cc +523 -0
- package/native/geometry.cc +255 -0
- package/native/image.cc +216 -0
- package/native/include/micropdf/enhanced.h +38 -0
- package/native/include/micropdf/types.h +36 -0
- package/native/include/micropdf.h +106 -0
- package/native/include/mupdf-ffi.h +39 -0
- package/native/include/mupdf.h +11 -0
- package/native/include/mupdf_minimal.h +381 -0
- package/native/lib/linux-x64/libmicropdf.a +0 -0
- package/native/link.cc +234 -0
- package/native/micropdf.cc +71 -0
- package/native/output.cc +229 -0
- package/native/page.cc +572 -0
- package/native/path.cc +259 -0
- package/native/pixmap.cc +240 -0
- package/native/stext.cc +610 -0
- package/native/stream.cc +239 -0
- package/package.json +120 -0
- package/scripts/build-from-rust.js +97 -0
- package/scripts/install.js +184 -0
package/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
|
+
|