@gjsify/node-gi 0.13.0 → 0.20.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/README.md +818 -5
- package/binding.gyp +55 -19
- package/cairo.d.ts +187 -0
- package/cairo.js +570 -0
- package/gettext.d.ts +65 -0
- package/gettext.js +128 -0
- package/gi.d.ts +178 -0
- package/gi.js +2018 -23
- package/globals.d.ts +7 -33
- package/globals.js +40 -51
- package/gtk-runtime.d.ts +17 -0
- package/gtk-runtime.js +245 -0
- package/index.d.ts +306 -12
- package/index.js +447 -14
- package/overrides/_signals.js +167 -0
- package/overrides/gio-dbus.js +749 -0
- package/overrides/mainloop.js +60 -0
- package/package.json +45 -4
- package/src/addon.cc +98 -2074
- package/src/cairo.cc +926 -0
- package/src/calls.cc +1425 -0
- package/src/class.cc +1170 -0
- package/src/common.h +624 -0
- package/src/loop.cc +766 -0
- package/src/marshal.cc +1738 -0
- package/src/object.cc +814 -0
- package/src/private.cc +351 -0
- package/src/repo.cc +297 -0
- package/src/signals.cc +535 -0
- package/src/template.cc +116 -0
- package/src/toggle.cc +718 -0
- package/src/variant.cc +587 -0
- package/system.d.ts +49 -0
- package/system.js +116 -0
package/src/cairo.cc
ADDED
|
@@ -0,0 +1,926 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// @gjsify/node-gi — the native cairo binding + the foreign-struct seam.
|
|
3
|
+
//
|
|
4
|
+
// cairo's types (cairo_t / cairo_surface_t / cairo_pattern_t) are FOREIGN structs
|
|
5
|
+
// in GObject-Introspection: GI does not know their layout and delegates their
|
|
6
|
+
// marshalling to a module (gi_struct_info_is_foreign → true). GJS ships a
|
|
7
|
+
// hand-written native cairo binding plus a foreign-struct registration so that a
|
|
8
|
+
// GI function taking/returning a cairo pointer (e.g. a Gtk.DrawingArea draw-func's
|
|
9
|
+
// cairo_t) round-trips to/from the JS cairo objects. This is the node-gi port of
|
|
10
|
+
// that binding + registration.
|
|
11
|
+
//
|
|
12
|
+
// Reference: refs/gjs/modules/cairo-*.cpp (Context/Surface/ImageSurface/Pattern/
|
|
13
|
+
// SolidPattern/Gradient/LinearGradient/RadialGradient/SurfacePattern/Path) +
|
|
14
|
+
// refs/gjs/gi/foreign.cpp (the foreign seam). Copyright (c) 2010
|
|
15
|
+
// litl, LLC / GJS contributors, MIT OR LGPL-2.0-or-later. Retargeted to N-API +
|
|
16
|
+
// the node-gi ownership model (adopt-or-ref, no separate release call — matching
|
|
17
|
+
// WrapBoxed; cairo_path_t is NOT ref-counted, so a borrowed path is COPIED via
|
|
18
|
+
// the gjs CairoPath::copy_ptr dummy-context trick instead of ref'd). Slice:
|
|
19
|
+
// Context (drawing ops, path copy/append, dash, matrix point transforms,
|
|
20
|
+
// in-fill/in-stroke, getSource), Surface + ImageSurface (headless pixels),
|
|
21
|
+
// SolidPattern, LinearGradient/RadialGradient (color stops), SurfacePattern
|
|
22
|
+
// (extend/filter), Path handles. Deferred: region, PDF/SVG/PS surfaces,
|
|
23
|
+
// text/font ops (showText/selectFontFace — canvas2d text rides PangoCairo).
|
|
24
|
+
//
|
|
25
|
+
// The JS wrapper classes live in the L1 module (cairo.js); each instance holds
|
|
26
|
+
// this binding's pointer as a type-tagged External under `_ptr`. The foreign
|
|
27
|
+
// from_func wraps a returned/callback cairo pointer by calling the JS factory the
|
|
28
|
+
// L1 module registered via `setup()` (stored per-env). The to_func reads `_ptr`
|
|
29
|
+
// off the JS wrapper.
|
|
30
|
+
|
|
31
|
+
#include "common.h"
|
|
32
|
+
|
|
33
|
+
#include <cairo.h>
|
|
34
|
+
#include <string.h> // memcpy
|
|
35
|
+
|
|
36
|
+
#include <vector>
|
|
37
|
+
|
|
38
|
+
namespace nodegi {
|
|
39
|
+
|
|
40
|
+
// The pointer kind carried by a cairo handle, so the finalizer/dispose picks the
|
|
41
|
+
// matching cairo_*_destroy and the marshaller can typecheck a foreign IN arg.
|
|
42
|
+
enum class CairoKind : uint8_t { Context, Surface, Pattern, Path };
|
|
43
|
+
|
|
44
|
+
// A cairo object handle: the ref-counted cairo pointer + its kind. `owns` is true
|
|
45
|
+
// for every handle this binding creates (constructors own their fresh ref;
|
|
46
|
+
// from_func adopts/references one) — the finalizer drops it via the kind's destroy
|
|
47
|
+
// unless `disposed` already did so eagerly ($dispose).
|
|
48
|
+
struct CairoHandle {
|
|
49
|
+
void* ptr;
|
|
50
|
+
CairoKind kind;
|
|
51
|
+
bool disposed;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Process-unique tag so a cairo handle is never cross-dereferenced with a boxed /
|
|
55
|
+
// GObject / GType External.
|
|
56
|
+
static const napi_type_tag kCairoHandleTag = {0xca140b1a5e7f0011ULL,
|
|
57
|
+
0x9d3c7a25e6f18042ULL};
|
|
58
|
+
|
|
59
|
+
// The property under which a JS cairo wrapper stores its native handle External.
|
|
60
|
+
static const char kCairoPtrProp[] = "_ptr";
|
|
61
|
+
|
|
62
|
+
static void DestroyCairo(CairoHandle* h) {
|
|
63
|
+
if (h->ptr == nullptr) return;
|
|
64
|
+
switch (h->kind) {
|
|
65
|
+
case CairoKind::Context: cairo_destroy(static_cast<cairo_t*>(h->ptr)); break;
|
|
66
|
+
case CairoKind::Surface: cairo_surface_destroy(static_cast<cairo_surface_t*>(h->ptr)); break;
|
|
67
|
+
case CairoKind::Pattern: cairo_pattern_destroy(static_cast<cairo_pattern_t*>(h->ptr)); break;
|
|
68
|
+
case CairoKind::Path: cairo_path_destroy(static_cast<cairo_path_t*>(h->ptr)); break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Wrap a cairo pointer as a type-tagged External. These handles always destroy on
|
|
73
|
+
// GC (unless already $dispose'd) — this binding never wraps a non-owned pointer.
|
|
74
|
+
static Napi::Value MakeCairoHandle(Napi::Env env, void* ptr, CairoKind kind) {
|
|
75
|
+
CairoHandle* h = new CairoHandle{ptr, kind, false};
|
|
76
|
+
Napi::External<CairoHandle> ext =
|
|
77
|
+
Napi::External<CairoHandle>::New(env, h, [](Napi::Env, CairoHandle* d) {
|
|
78
|
+
if (!d->disposed) DestroyCairo(d);
|
|
79
|
+
delete d;
|
|
80
|
+
});
|
|
81
|
+
ext.TypeTag(&kCairoHandleTag);
|
|
82
|
+
return ext;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static CairoHandle* HandleFromExternal(Napi::Value v) {
|
|
86
|
+
if (!v.IsExternal()) return nullptr;
|
|
87
|
+
Napi::External<CairoHandle> ext = v.As<Napi::External<CairoHandle>>();
|
|
88
|
+
if (!ext.CheckTypeTag(&kCairoHandleTag)) return nullptr;
|
|
89
|
+
return ext.Data();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ---- status checking (mirrors gjs_cairo_check_status) -----------------------
|
|
93
|
+
static bool CheckStatus(Napi::Env env, cairo_status_t status, const char* name) {
|
|
94
|
+
if (status != CAIRO_STATUS_SUCCESS) {
|
|
95
|
+
Napi::Error::New(env, std::string("cairo error on ") + name + ": \"" +
|
|
96
|
+
cairo_status_to_string(status) + "\" (" +
|
|
97
|
+
std::to_string(static_cast<int>(status)) + ")")
|
|
98
|
+
.ThrowAsJavaScriptException();
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---- native-method arg unwrapping (the handle External is info[0]) ----------
|
|
105
|
+
static cairo_t* CtxArg(Napi::Env env, const Napi::CallbackInfo& info) {
|
|
106
|
+
CairoHandle* h = HandleFromExternal(info[0]);
|
|
107
|
+
if (h == nullptr || h->kind != CairoKind::Context || h->disposed || h->ptr == nullptr) {
|
|
108
|
+
Napi::TypeError::New(env, "expected a live cairo.Context").ThrowAsJavaScriptException();
|
|
109
|
+
return nullptr;
|
|
110
|
+
}
|
|
111
|
+
return static_cast<cairo_t*>(h->ptr);
|
|
112
|
+
}
|
|
113
|
+
static cairo_surface_t* SfcArg(Napi::Env env, const Napi::CallbackInfo& info) {
|
|
114
|
+
CairoHandle* h = HandleFromExternal(info[0]);
|
|
115
|
+
if (h == nullptr || h->kind != CairoKind::Surface || h->disposed || h->ptr == nullptr) {
|
|
116
|
+
Napi::TypeError::New(env, "expected a live cairo.Surface").ThrowAsJavaScriptException();
|
|
117
|
+
return nullptr;
|
|
118
|
+
}
|
|
119
|
+
return static_cast<cairo_surface_t*>(h->ptr);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Read the cairo pointer from a JS wrapper OBJECT (the `_ptr` External) — used by
|
|
123
|
+
// the foreign to_func, which receives the user's Context/Surface/Pattern instance.
|
|
124
|
+
static void* PtrFromWrapper(Napi::Value v, CairoKind kind) {
|
|
125
|
+
if (!v.IsObject()) return nullptr;
|
|
126
|
+
Napi::Value p = v.As<Napi::Object>().Get(kCairoPtrProp);
|
|
127
|
+
CairoHandle* h = HandleFromExternal(p);
|
|
128
|
+
if (h == nullptr || h->kind != kind || h->disposed) return nullptr;
|
|
129
|
+
return h->ptr;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Read a live cairo pointer from an External handle directly (a nested cairo arg
|
|
133
|
+
// passed from JS as `other._ptr`: contextCreate's surface, setSource's pattern).
|
|
134
|
+
static cairo_surface_t* SurfaceFromExt(Napi::Env env, Napi::Value v) {
|
|
135
|
+
CairoHandle* h = HandleFromExternal(v);
|
|
136
|
+
if (h == nullptr || h->kind != CairoKind::Surface || h->disposed || h->ptr == nullptr) {
|
|
137
|
+
Napi::TypeError::New(env, "expected a live cairo.Surface").ThrowAsJavaScriptException();
|
|
138
|
+
return nullptr;
|
|
139
|
+
}
|
|
140
|
+
return static_cast<cairo_surface_t*>(h->ptr);
|
|
141
|
+
}
|
|
142
|
+
static cairo_pattern_t* PatternFromExt(Napi::Env env, Napi::Value v) {
|
|
143
|
+
CairoHandle* h = HandleFromExternal(v);
|
|
144
|
+
if (h == nullptr || h->kind != CairoKind::Pattern || h->disposed || h->ptr == nullptr) {
|
|
145
|
+
Napi::TypeError::New(env, "expected a live cairo.Pattern").ThrowAsJavaScriptException();
|
|
146
|
+
return nullptr;
|
|
147
|
+
}
|
|
148
|
+
return static_cast<cairo_pattern_t*>(h->ptr);
|
|
149
|
+
}
|
|
150
|
+
static cairo_path_t* PathFromExt(Napi::Env env, Napi::Value v) {
|
|
151
|
+
CairoHandle* h = HandleFromExternal(v);
|
|
152
|
+
if (h == nullptr || h->kind != CairoKind::Path || h->disposed || h->ptr == nullptr) {
|
|
153
|
+
Napi::TypeError::New(env, "expected a live cairo.Path").ThrowAsJavaScriptException();
|
|
154
|
+
return nullptr;
|
|
155
|
+
}
|
|
156
|
+
return static_cast<cairo_path_t*>(h->ptr);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Copy a cairo_path_t (not ref-counted): replay it onto a dummy 0x0 image-surface
|
|
160
|
+
// context and copy it back out. Mirrors gjs CairoPath::copy_ptr (adapted from
|
|
161
|
+
// PyGObject cairo code).
|
|
162
|
+
static cairo_path_t* CopyCairoPath(cairo_path_t* path) {
|
|
163
|
+
cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
|
|
164
|
+
cairo_t* cr = cairo_create(surface);
|
|
165
|
+
cairo_append_path(cr, path);
|
|
166
|
+
cairo_path_t* copy = cairo_copy_path(cr);
|
|
167
|
+
cairo_destroy(cr);
|
|
168
|
+
cairo_surface_destroy(surface);
|
|
169
|
+
return copy;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
static double Num(const Napi::CallbackInfo& info, size_t i) {
|
|
173
|
+
return info[i].ToNumber().DoubleValue();
|
|
174
|
+
}
|
|
175
|
+
static int Int(const Napi::CallbackInfo& info, size_t i) {
|
|
176
|
+
return info[i].ToNumber().Int32Value();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ---- context drawing-method macros ------------------------------------------
|
|
180
|
+
// Every op runs the cairo call then a trailing cairo_status(cr) check → throw on
|
|
181
|
+
// error (the GJS FUNC-macro contract). Setters return undefined.
|
|
182
|
+
#define CTX_0(CFN) \
|
|
183
|
+
+[](const Napi::CallbackInfo& info) -> Napi::Value { \
|
|
184
|
+
Napi::Env env = info.Env(); \
|
|
185
|
+
cairo_t* cr = CtxArg(env, info); \
|
|
186
|
+
if (cr == nullptr) return env.Undefined(); \
|
|
187
|
+
CFN(cr); \
|
|
188
|
+
CheckStatus(env, cairo_status(cr), "context"); \
|
|
189
|
+
return env.Undefined(); \
|
|
190
|
+
}
|
|
191
|
+
#define CTX_1D(CFN) \
|
|
192
|
+
+[](const Napi::CallbackInfo& info) -> Napi::Value { \
|
|
193
|
+
Napi::Env env = info.Env(); \
|
|
194
|
+
cairo_t* cr = CtxArg(env, info); \
|
|
195
|
+
if (cr == nullptr) return env.Undefined(); \
|
|
196
|
+
CFN(cr, Num(info, 1)); \
|
|
197
|
+
CheckStatus(env, cairo_status(cr), "context"); \
|
|
198
|
+
return env.Undefined(); \
|
|
199
|
+
}
|
|
200
|
+
#define CTX_2D(CFN) \
|
|
201
|
+
+[](const Napi::CallbackInfo& info) -> Napi::Value { \
|
|
202
|
+
Napi::Env env = info.Env(); \
|
|
203
|
+
cairo_t* cr = CtxArg(env, info); \
|
|
204
|
+
if (cr == nullptr) return env.Undefined(); \
|
|
205
|
+
CFN(cr, Num(info, 1), Num(info, 2)); \
|
|
206
|
+
CheckStatus(env, cairo_status(cr), "context"); \
|
|
207
|
+
return env.Undefined(); \
|
|
208
|
+
}
|
|
209
|
+
#define CTX_3D(CFN) \
|
|
210
|
+
+[](const Napi::CallbackInfo& info) -> Napi::Value { \
|
|
211
|
+
Napi::Env env = info.Env(); \
|
|
212
|
+
cairo_t* cr = CtxArg(env, info); \
|
|
213
|
+
if (cr == nullptr) return env.Undefined(); \
|
|
214
|
+
CFN(cr, Num(info, 1), Num(info, 2), Num(info, 3)); \
|
|
215
|
+
CheckStatus(env, cairo_status(cr), "context"); \
|
|
216
|
+
return env.Undefined(); \
|
|
217
|
+
}
|
|
218
|
+
#define CTX_4D(CFN) \
|
|
219
|
+
+[](const Napi::CallbackInfo& info) -> Napi::Value { \
|
|
220
|
+
Napi::Env env = info.Env(); \
|
|
221
|
+
cairo_t* cr = CtxArg(env, info); \
|
|
222
|
+
if (cr == nullptr) return env.Undefined(); \
|
|
223
|
+
CFN(cr, Num(info, 1), Num(info, 2), Num(info, 3), Num(info, 4)); \
|
|
224
|
+
CheckStatus(env, cairo_status(cr), "context"); \
|
|
225
|
+
return env.Undefined(); \
|
|
226
|
+
}
|
|
227
|
+
#define CTX_5D(CFN) \
|
|
228
|
+
+[](const Napi::CallbackInfo& info) -> Napi::Value { \
|
|
229
|
+
Napi::Env env = info.Env(); \
|
|
230
|
+
cairo_t* cr = CtxArg(env, info); \
|
|
231
|
+
if (cr == nullptr) return env.Undefined(); \
|
|
232
|
+
CFN(cr, Num(info, 1), Num(info, 2), Num(info, 3), Num(info, 4), Num(info, 5)); \
|
|
233
|
+
CheckStatus(env, cairo_status(cr), "context"); \
|
|
234
|
+
return env.Undefined(); \
|
|
235
|
+
}
|
|
236
|
+
#define CTX_6D(CFN) \
|
|
237
|
+
+[](const Napi::CallbackInfo& info) -> Napi::Value { \
|
|
238
|
+
Napi::Env env = info.Env(); \
|
|
239
|
+
cairo_t* cr = CtxArg(env, info); \
|
|
240
|
+
if (cr == nullptr) return env.Undefined(); \
|
|
241
|
+
CFN(cr, Num(info, 1), Num(info, 2), Num(info, 3), Num(info, 4), Num(info, 5), Num(info, 6)); \
|
|
242
|
+
CheckStatus(env, cairo_status(cr), "context"); \
|
|
243
|
+
return env.Undefined(); \
|
|
244
|
+
}
|
|
245
|
+
// Integer-enum context setter (operator / line cap / line join / fill rule / antialias).
|
|
246
|
+
#define CTX_ENUM(CFN, CTYPE) \
|
|
247
|
+
+[](const Napi::CallbackInfo& info) -> Napi::Value { \
|
|
248
|
+
Napi::Env env = info.Env(); \
|
|
249
|
+
cairo_t* cr = CtxArg(env, info); \
|
|
250
|
+
if (cr == nullptr) return env.Undefined(); \
|
|
251
|
+
CFN(cr, static_cast<CTYPE>(Int(info, 1))); \
|
|
252
|
+
CheckStatus(env, cairo_status(cr), "context"); \
|
|
253
|
+
return env.Undefined(); \
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ---- the foreign-struct converters (Context / Surface / Pattern) ------------
|
|
257
|
+
//
|
|
258
|
+
// from_func: adopt-or-ref (node-gi ownership model). transfer-nothing (a draw-func
|
|
259
|
+
// cairo_t, a borrowed return) → add a ref so the JS wrapper owns one and the
|
|
260
|
+
// caller's ref stays intact; transfer-everything → adopt the handed ref. Then wrap
|
|
261
|
+
// the External via the L1 factory the module registered (per-env) so the result is
|
|
262
|
+
// a real cairo.Context / .Surface (or .ImageSurface) / .Pattern instance.
|
|
263
|
+
static Napi::Value CallWrapper(Napi::Env env, const char* kindKey, Napi::Value handle) {
|
|
264
|
+
NodeGiEnvData* d = EnvData(env);
|
|
265
|
+
if (d == nullptr || d->cairoWrappers == nullptr) {
|
|
266
|
+
Napi::Error::New(env, "cairo module not initialized — import 'cairo' before a GI call "
|
|
267
|
+
"hands you a cairo object")
|
|
268
|
+
.ThrowAsJavaScriptException();
|
|
269
|
+
return env.Undefined();
|
|
270
|
+
}
|
|
271
|
+
napi_value wrappersV = nullptr;
|
|
272
|
+
napi_get_reference_value(env, d->cairoWrappers, &wrappersV);
|
|
273
|
+
Napi::Object wrappers(env, wrappersV);
|
|
274
|
+
Napi::Function fn = wrappers.Get(kindKey).As<Napi::Function>();
|
|
275
|
+
return fn.Call({handle});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
static bool ContextTo(Napi::Env env, Napi::Value v, GITransfer transfer, GIArgument* arg) {
|
|
279
|
+
if (v.IsNull() || v.IsUndefined()) { arg->v_pointer = nullptr; return true; }
|
|
280
|
+
void* p = PtrFromWrapper(v, CairoKind::Context);
|
|
281
|
+
if (p == nullptr) {
|
|
282
|
+
Napi::TypeError::New(env, "expected a cairo.Context").ThrowAsJavaScriptException();
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
if (transfer == GI_TRANSFER_EVERYTHING) cairo_reference(static_cast<cairo_t*>(p));
|
|
286
|
+
arg->v_pointer = p;
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
static Napi::Value ContextFrom(Napi::Env env, gpointer ptr, GITransfer transfer) {
|
|
290
|
+
if (ptr == nullptr) return env.Null();
|
|
291
|
+
if (transfer == GI_TRANSFER_NOTHING) cairo_reference(static_cast<cairo_t*>(ptr));
|
|
292
|
+
return CallWrapper(env, "context", MakeCairoHandle(env, ptr, CairoKind::Context));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
static bool SurfaceTo(Napi::Env env, Napi::Value v, GITransfer transfer, GIArgument* arg) {
|
|
296
|
+
if (v.IsNull() || v.IsUndefined()) { arg->v_pointer = nullptr; return true; }
|
|
297
|
+
void* p = PtrFromWrapper(v, CairoKind::Surface);
|
|
298
|
+
if (p == nullptr) {
|
|
299
|
+
Napi::TypeError::New(env, "expected a cairo.Surface").ThrowAsJavaScriptException();
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
if (transfer == GI_TRANSFER_EVERYTHING) cairo_surface_reference(static_cast<cairo_surface_t*>(p));
|
|
303
|
+
arg->v_pointer = p;
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
static Napi::Value SurfaceFrom(Napi::Env env, gpointer ptr, GITransfer transfer) {
|
|
307
|
+
if (ptr == nullptr) return env.Null();
|
|
308
|
+
if (transfer == GI_TRANSFER_NOTHING) cairo_surface_reference(static_cast<cairo_surface_t*>(ptr));
|
|
309
|
+
return CallWrapper(env, "surface", MakeCairoHandle(env, ptr, CairoKind::Surface));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
static bool PatternTo(Napi::Env env, Napi::Value v, GITransfer transfer, GIArgument* arg) {
|
|
313
|
+
if (v.IsNull() || v.IsUndefined()) { arg->v_pointer = nullptr; return true; }
|
|
314
|
+
void* p = PtrFromWrapper(v, CairoKind::Pattern);
|
|
315
|
+
if (p == nullptr) {
|
|
316
|
+
Napi::TypeError::New(env, "expected a cairo.Pattern").ThrowAsJavaScriptException();
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
if (transfer == GI_TRANSFER_EVERYTHING) cairo_pattern_reference(static_cast<cairo_pattern_t*>(p));
|
|
320
|
+
arg->v_pointer = p;
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
static Napi::Value PatternFrom(Napi::Env env, gpointer ptr, GITransfer transfer) {
|
|
324
|
+
if (ptr == nullptr) return env.Null();
|
|
325
|
+
if (transfer == GI_TRANSFER_NOTHING) cairo_pattern_reference(static_cast<cairo_pattern_t*>(ptr));
|
|
326
|
+
return CallWrapper(env, "pattern", MakeCairoHandle(env, ptr, CairoKind::Pattern));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// cairo_path_t is not ref-counted: a borrowed path (transfer-nothing FROM, or a
|
|
330
|
+
// transfer-everything IN where the callee takes ownership) is COPIED instead of
|
|
331
|
+
// ref'd — same semantics as gjs's path_to/from_gi_argument + CairoPath::copy_ptr.
|
|
332
|
+
static bool PathTo(Napi::Env env, Napi::Value v, GITransfer transfer, GIArgument* arg) {
|
|
333
|
+
if (v.IsNull() || v.IsUndefined()) { arg->v_pointer = nullptr; return true; }
|
|
334
|
+
void* p = PtrFromWrapper(v, CairoKind::Path);
|
|
335
|
+
if (p == nullptr) {
|
|
336
|
+
Napi::TypeError::New(env, "expected a cairo.Path").ThrowAsJavaScriptException();
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
if (transfer == GI_TRANSFER_EVERYTHING) p = CopyCairoPath(static_cast<cairo_path_t*>(p));
|
|
340
|
+
arg->v_pointer = p;
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
static Napi::Value PathFrom(Napi::Env env, gpointer ptr, GITransfer transfer) {
|
|
344
|
+
if (ptr == nullptr) return env.Null();
|
|
345
|
+
cairo_path_t* owned = (transfer == GI_TRANSFER_EVERYTHING)
|
|
346
|
+
? static_cast<cairo_path_t*>(ptr)
|
|
347
|
+
: CopyCairoPath(static_cast<cairo_path_t*>(ptr));
|
|
348
|
+
return CallWrapper(env, "path", MakeCairoHandle(env, owned, CairoKind::Path));
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
static const ForeignStructOps kContextOps{ContextTo, ContextFrom};
|
|
352
|
+
static const ForeignStructOps kSurfaceOps{SurfaceTo, SurfaceFrom};
|
|
353
|
+
static const ForeignStructOps kPatternOps{PatternTo, PatternFrom};
|
|
354
|
+
static const ForeignStructOps kPathOps{PathTo, PathFrom};
|
|
355
|
+
|
|
356
|
+
// setup(wrappers): the L1 module hands its JS wrapper factories ({context, surface,
|
|
357
|
+
// pattern, path}) — each takes the `_ptr` External and returns the class instance.
|
|
358
|
+
// Stored per-env (a napi_ref is env-specific) so the foreign from_func can build
|
|
359
|
+
// wrappers.
|
|
360
|
+
static Napi::Value CairoSetup(const Napi::CallbackInfo& info) {
|
|
361
|
+
Napi::Env env = info.Env();
|
|
362
|
+
NodeGiEnvData* d = EnvData(env);
|
|
363
|
+
if (d == nullptr) return env.Undefined();
|
|
364
|
+
if (!info[0].IsObject()) {
|
|
365
|
+
Napi::TypeError::New(env, "cairo setup expects a wrappers object")
|
|
366
|
+
.ThrowAsJavaScriptException();
|
|
367
|
+
return env.Undefined();
|
|
368
|
+
}
|
|
369
|
+
if (d->cairoWrappers != nullptr) napi_delete_reference(env, d->cairoWrappers);
|
|
370
|
+
napi_create_reference(env, info[0], 1, &d->cairoWrappers);
|
|
371
|
+
return env.Undefined();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
void InitCairo(Napi::Env env, Napi::Object exports) {
|
|
375
|
+
// The foreign converters are static + JS-independent, so register them once at
|
|
376
|
+
// module init (before any GI call marshals a cairo type). The JS wrapper
|
|
377
|
+
// factories the from_func needs are supplied later by cairo.js's setup().
|
|
378
|
+
RegisterForeignStruct("cairo", "Context", &kContextOps);
|
|
379
|
+
RegisterForeignStruct("cairo", "Surface", &kSurfaceOps);
|
|
380
|
+
RegisterForeignStruct("cairo", "Pattern", &kPatternOps);
|
|
381
|
+
RegisterForeignStruct("cairo", "Path", &kPathOps);
|
|
382
|
+
|
|
383
|
+
Napi::Object c = Napi::Object::New(env);
|
|
384
|
+
c.Set("setup", Napi::Function::New(env, CairoSetup));
|
|
385
|
+
|
|
386
|
+
// ---- constructors ----
|
|
387
|
+
c.Set("imageSurfaceCreate", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
388
|
+
Napi::Env env = info.Env();
|
|
389
|
+
cairo_surface_t* s = cairo_image_surface_create(
|
|
390
|
+
static_cast<cairo_format_t>(Int(info, 0)), Int(info, 1), Int(info, 2));
|
|
391
|
+
if (!CheckStatus(env, cairo_surface_status(s), "surface")) {
|
|
392
|
+
cairo_surface_destroy(s);
|
|
393
|
+
return env.Undefined();
|
|
394
|
+
}
|
|
395
|
+
return MakeCairoHandle(env, s, CairoKind::Surface);
|
|
396
|
+
}));
|
|
397
|
+
c.Set("imageSurfaceCreateFromPNG", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
398
|
+
Napi::Env env = info.Env();
|
|
399
|
+
std::string path = info[0].ToString().Utf8Value();
|
|
400
|
+
cairo_surface_t* s = cairo_image_surface_create_from_png(path.c_str());
|
|
401
|
+
if (!CheckStatus(env, cairo_surface_status(s), "surface")) {
|
|
402
|
+
cairo_surface_destroy(s);
|
|
403
|
+
return env.Undefined();
|
|
404
|
+
}
|
|
405
|
+
return MakeCairoHandle(env, s, CairoKind::Surface);
|
|
406
|
+
}));
|
|
407
|
+
c.Set("contextCreate", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
408
|
+
Napi::Env env = info.Env();
|
|
409
|
+
cairo_surface_t* sp = SurfaceFromExt(env, info[0]);
|
|
410
|
+
if (sp == nullptr) return env.Undefined();
|
|
411
|
+
cairo_t* cr = cairo_create(sp);
|
|
412
|
+
if (!CheckStatus(env, cairo_status(cr), "context")) {
|
|
413
|
+
cairo_destroy(cr);
|
|
414
|
+
return env.Undefined();
|
|
415
|
+
}
|
|
416
|
+
return MakeCairoHandle(env, cr, CairoKind::Context);
|
|
417
|
+
}));
|
|
418
|
+
c.Set("solidPatternCreateRGB", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
419
|
+
Napi::Env env = info.Env();
|
|
420
|
+
cairo_pattern_t* p = cairo_pattern_create_rgb(Num(info, 0), Num(info, 1), Num(info, 2));
|
|
421
|
+
if (!CheckStatus(env, cairo_pattern_status(p), "pattern")) {
|
|
422
|
+
cairo_pattern_destroy(p);
|
|
423
|
+
return env.Undefined();
|
|
424
|
+
}
|
|
425
|
+
return MakeCairoHandle(env, p, CairoKind::Pattern);
|
|
426
|
+
}));
|
|
427
|
+
c.Set("solidPatternCreateRGBA", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
428
|
+
Napi::Env env = info.Env();
|
|
429
|
+
cairo_pattern_t* p =
|
|
430
|
+
cairo_pattern_create_rgba(Num(info, 0), Num(info, 1), Num(info, 2), Num(info, 3));
|
|
431
|
+
if (!CheckStatus(env, cairo_pattern_status(p), "pattern")) {
|
|
432
|
+
cairo_pattern_destroy(p);
|
|
433
|
+
return env.Undefined();
|
|
434
|
+
}
|
|
435
|
+
return MakeCairoHandle(env, p, CairoKind::Pattern);
|
|
436
|
+
}));
|
|
437
|
+
c.Set("linearGradientCreate", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
438
|
+
Napi::Env env = info.Env();
|
|
439
|
+
cairo_pattern_t* p =
|
|
440
|
+
cairo_pattern_create_linear(Num(info, 0), Num(info, 1), Num(info, 2), Num(info, 3));
|
|
441
|
+
if (!CheckStatus(env, cairo_pattern_status(p), "pattern")) {
|
|
442
|
+
cairo_pattern_destroy(p);
|
|
443
|
+
return env.Undefined();
|
|
444
|
+
}
|
|
445
|
+
return MakeCairoHandle(env, p, CairoKind::Pattern);
|
|
446
|
+
}));
|
|
447
|
+
c.Set("radialGradientCreate", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
448
|
+
Napi::Env env = info.Env();
|
|
449
|
+
cairo_pattern_t* p = cairo_pattern_create_radial(Num(info, 0), Num(info, 1), Num(info, 2),
|
|
450
|
+
Num(info, 3), Num(info, 4), Num(info, 5));
|
|
451
|
+
if (!CheckStatus(env, cairo_pattern_status(p), "pattern")) {
|
|
452
|
+
cairo_pattern_destroy(p);
|
|
453
|
+
return env.Undefined();
|
|
454
|
+
}
|
|
455
|
+
return MakeCairoHandle(env, p, CairoKind::Pattern);
|
|
456
|
+
}));
|
|
457
|
+
c.Set("surfacePatternCreate", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
458
|
+
Napi::Env env = info.Env();
|
|
459
|
+
cairo_surface_t* s = SurfaceFromExt(env, info[0]);
|
|
460
|
+
if (s == nullptr) return env.Undefined();
|
|
461
|
+
cairo_pattern_t* p = cairo_pattern_create_for_surface(s);
|
|
462
|
+
if (!CheckStatus(env, cairo_pattern_status(p), "pattern")) {
|
|
463
|
+
cairo_pattern_destroy(p);
|
|
464
|
+
return env.Undefined();
|
|
465
|
+
}
|
|
466
|
+
return MakeCairoHandle(env, p, CairoKind::Pattern);
|
|
467
|
+
}));
|
|
468
|
+
|
|
469
|
+
// ---- pattern methods (the pattern handle External is info[0]) ----
|
|
470
|
+
// Gradient color stops (Gradient in gjs terms — Linear/RadialGradient).
|
|
471
|
+
c.Set("patternAddColorStopRGB", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
472
|
+
Napi::Env env = info.Env();
|
|
473
|
+
cairo_pattern_t* p = PatternFromExt(env, info[0]);
|
|
474
|
+
if (p == nullptr) return env.Undefined();
|
|
475
|
+
cairo_pattern_add_color_stop_rgb(p, Num(info, 1), Num(info, 2), Num(info, 3), Num(info, 4));
|
|
476
|
+
CheckStatus(env, cairo_pattern_status(p), "pattern");
|
|
477
|
+
return env.Undefined();
|
|
478
|
+
}));
|
|
479
|
+
c.Set("patternAddColorStopRGBA", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
480
|
+
Napi::Env env = info.Env();
|
|
481
|
+
cairo_pattern_t* p = PatternFromExt(env, info[0]);
|
|
482
|
+
if (p == nullptr) return env.Undefined();
|
|
483
|
+
cairo_pattern_add_color_stop_rgba(p, Num(info, 1), Num(info, 2), Num(info, 3), Num(info, 4),
|
|
484
|
+
Num(info, 5));
|
|
485
|
+
CheckStatus(env, cairo_pattern_status(p), "pattern");
|
|
486
|
+
return env.Undefined();
|
|
487
|
+
}));
|
|
488
|
+
// Extend/filter (SurfacePattern methods in gjs — cairo accepts them on any pattern).
|
|
489
|
+
c.Set("patternSetExtend", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
490
|
+
Napi::Env env = info.Env();
|
|
491
|
+
cairo_pattern_t* p = PatternFromExt(env, info[0]);
|
|
492
|
+
if (p == nullptr) return env.Undefined();
|
|
493
|
+
cairo_pattern_set_extend(p, static_cast<cairo_extend_t>(Int(info, 1)));
|
|
494
|
+
CheckStatus(env, cairo_pattern_status(p), "pattern");
|
|
495
|
+
return env.Undefined();
|
|
496
|
+
}));
|
|
497
|
+
c.Set("patternGetExtend", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
498
|
+
Napi::Env env = info.Env();
|
|
499
|
+
cairo_pattern_t* p = PatternFromExt(env, info[0]);
|
|
500
|
+
if (p == nullptr) return env.Undefined();
|
|
501
|
+
return Napi::Number::New(env, cairo_pattern_get_extend(p));
|
|
502
|
+
}));
|
|
503
|
+
c.Set("patternSetFilter", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
504
|
+
Napi::Env env = info.Env();
|
|
505
|
+
cairo_pattern_t* p = PatternFromExt(env, info[0]);
|
|
506
|
+
if (p == nullptr) return env.Undefined();
|
|
507
|
+
cairo_pattern_set_filter(p, static_cast<cairo_filter_t>(Int(info, 1)));
|
|
508
|
+
CheckStatus(env, cairo_pattern_status(p), "pattern");
|
|
509
|
+
return env.Undefined();
|
|
510
|
+
}));
|
|
511
|
+
c.Set("patternGetFilter", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
512
|
+
Napi::Env env = info.Env();
|
|
513
|
+
cairo_pattern_t* p = PatternFromExt(env, info[0]);
|
|
514
|
+
if (p == nullptr) return env.Undefined();
|
|
515
|
+
return Napi::Number::New(env, cairo_pattern_get_filter(p));
|
|
516
|
+
}));
|
|
517
|
+
// patternGetType/patternStatus back the L1 pattern from_func subclass fan-out
|
|
518
|
+
// (SolidPattern vs base Pattern), mirroring CairoPattern::from_c_ptr.
|
|
519
|
+
c.Set("patternGetType", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
520
|
+
Napi::Env env = info.Env();
|
|
521
|
+
cairo_pattern_t* p = PatternFromExt(env, info[0]);
|
|
522
|
+
if (p == nullptr) return env.Undefined();
|
|
523
|
+
return Napi::Number::New(env, cairo_pattern_get_type(p));
|
|
524
|
+
}));
|
|
525
|
+
c.Set("patternStatus", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
526
|
+
Napi::Env env = info.Env();
|
|
527
|
+
cairo_pattern_t* p = PatternFromExt(env, info[0]);
|
|
528
|
+
if (p == nullptr) return env.Undefined();
|
|
529
|
+
return Napi::Number::New(env, cairo_pattern_status(p));
|
|
530
|
+
}));
|
|
531
|
+
|
|
532
|
+
// ---- surface methods ----
|
|
533
|
+
c.Set("surfaceFlush", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
534
|
+
Napi::Env env = info.Env();
|
|
535
|
+
cairo_surface_t* s = SfcArg(env, info);
|
|
536
|
+
if (s == nullptr) return env.Undefined();
|
|
537
|
+
cairo_surface_flush(s);
|
|
538
|
+
CheckStatus(env, cairo_surface_status(s), "surface");
|
|
539
|
+
return env.Undefined();
|
|
540
|
+
}));
|
|
541
|
+
c.Set("surfaceFinish", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
542
|
+
Napi::Env env = info.Env();
|
|
543
|
+
cairo_surface_t* s = SfcArg(env, info);
|
|
544
|
+
if (s == nullptr) return env.Undefined();
|
|
545
|
+
cairo_surface_finish(s);
|
|
546
|
+
CheckStatus(env, cairo_surface_status(s), "surface");
|
|
547
|
+
return env.Undefined();
|
|
548
|
+
}));
|
|
549
|
+
c.Set("surfaceGetType", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
550
|
+
Napi::Env env = info.Env();
|
|
551
|
+
cairo_surface_t* s = SfcArg(env, info);
|
|
552
|
+
if (s == nullptr) return env.Undefined();
|
|
553
|
+
return Napi::Number::New(env, cairo_surface_get_type(s));
|
|
554
|
+
}));
|
|
555
|
+
c.Set("surfaceStatus", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
556
|
+
Napi::Env env = info.Env();
|
|
557
|
+
cairo_surface_t* s = SfcArg(env, info);
|
|
558
|
+
if (s == nullptr) return env.Undefined();
|
|
559
|
+
return Napi::Number::New(env, cairo_surface_status(s));
|
|
560
|
+
}));
|
|
561
|
+
c.Set("surfaceWriteToPNG", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
562
|
+
Napi::Env env = info.Env();
|
|
563
|
+
cairo_surface_t* s = SfcArg(env, info);
|
|
564
|
+
if (s == nullptr) return env.Undefined();
|
|
565
|
+
std::string path = info[1].ToString().Utf8Value();
|
|
566
|
+
cairo_status_t st = cairo_surface_write_to_png(s, path.c_str());
|
|
567
|
+
CheckStatus(env, st, "surface");
|
|
568
|
+
return env.Undefined();
|
|
569
|
+
}));
|
|
570
|
+
c.Set("imageSurfaceGetWidth", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
571
|
+
Napi::Env env = info.Env();
|
|
572
|
+
cairo_surface_t* s = SfcArg(env, info);
|
|
573
|
+
if (s == nullptr) return env.Undefined();
|
|
574
|
+
return Napi::Number::New(env, cairo_image_surface_get_width(s));
|
|
575
|
+
}));
|
|
576
|
+
c.Set("imageSurfaceGetHeight", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
577
|
+
Napi::Env env = info.Env();
|
|
578
|
+
cairo_surface_t* s = SfcArg(env, info);
|
|
579
|
+
if (s == nullptr) return env.Undefined();
|
|
580
|
+
return Napi::Number::New(env, cairo_image_surface_get_height(s));
|
|
581
|
+
}));
|
|
582
|
+
c.Set("imageSurfaceGetStride", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
583
|
+
Napi::Env env = info.Env();
|
|
584
|
+
cairo_surface_t* s = SfcArg(env, info);
|
|
585
|
+
if (s == nullptr) return env.Undefined();
|
|
586
|
+
return Napi::Number::New(env, cairo_image_surface_get_stride(s));
|
|
587
|
+
}));
|
|
588
|
+
c.Set("imageSurfaceGetFormat", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
589
|
+
Napi::Env env = info.Env();
|
|
590
|
+
cairo_surface_t* s = SfcArg(env, info);
|
|
591
|
+
if (s == nullptr) return env.Undefined();
|
|
592
|
+
return Napi::Number::New(env, cairo_image_surface_get_format(s));
|
|
593
|
+
}));
|
|
594
|
+
// getData: net-new (GJS ships no getData). flush, then COPY the ARGB32 buffer
|
|
595
|
+
// (stride*height) into a fresh Uint8Array — a copy is GC-safe (surface + array
|
|
596
|
+
// then have independent lifetimes).
|
|
597
|
+
c.Set("imageSurfaceGetData", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
598
|
+
Napi::Env env = info.Env();
|
|
599
|
+
cairo_surface_t* s = SfcArg(env, info);
|
|
600
|
+
if (s == nullptr) return env.Undefined();
|
|
601
|
+
cairo_surface_flush(s);
|
|
602
|
+
unsigned char* data = cairo_image_surface_get_data(s);
|
|
603
|
+
int stride = cairo_image_surface_get_stride(s);
|
|
604
|
+
int height = cairo_image_surface_get_height(s);
|
|
605
|
+
size_t len = static_cast<size_t>(stride) * static_cast<size_t>(height);
|
|
606
|
+
Napi::Uint8Array out = Napi::Uint8Array::New(env, len);
|
|
607
|
+
if (data != nullptr && len > 0) memcpy(out.Data(), data, len);
|
|
608
|
+
return out;
|
|
609
|
+
}));
|
|
610
|
+
|
|
611
|
+
// ---- context methods ----
|
|
612
|
+
c.Set("save", Napi::Function::New(env, CTX_0(cairo_save)));
|
|
613
|
+
c.Set("restore", Napi::Function::New(env, CTX_0(cairo_restore)));
|
|
614
|
+
c.Set("newPath", Napi::Function::New(env, CTX_0(cairo_new_path)));
|
|
615
|
+
c.Set("closePath", Napi::Function::New(env, CTX_0(cairo_close_path)));
|
|
616
|
+
c.Set("fill", Napi::Function::New(env, CTX_0(cairo_fill)));
|
|
617
|
+
c.Set("fillPreserve", Napi::Function::New(env, CTX_0(cairo_fill_preserve)));
|
|
618
|
+
c.Set("stroke", Napi::Function::New(env, CTX_0(cairo_stroke)));
|
|
619
|
+
c.Set("strokePreserve", Napi::Function::New(env, CTX_0(cairo_stroke_preserve)));
|
|
620
|
+
c.Set("paint", Napi::Function::New(env, CTX_0(cairo_paint)));
|
|
621
|
+
c.Set("clip", Napi::Function::New(env, CTX_0(cairo_clip)));
|
|
622
|
+
c.Set("clipPreserve", Napi::Function::New(env, CTX_0(cairo_clip_preserve)));
|
|
623
|
+
|
|
624
|
+
c.Set("setLineWidth", Napi::Function::New(env, CTX_1D(cairo_set_line_width)));
|
|
625
|
+
c.Set("paintWithAlpha", Napi::Function::New(env, CTX_1D(cairo_paint_with_alpha)));
|
|
626
|
+
c.Set("rotate", Napi::Function::New(env, CTX_1D(cairo_rotate)));
|
|
627
|
+
c.Set("setMiterLimit", Napi::Function::New(env, CTX_1D(cairo_set_miter_limit)));
|
|
628
|
+
c.Set("setTolerance", Napi::Function::New(env, CTX_1D(cairo_set_tolerance)));
|
|
629
|
+
|
|
630
|
+
c.Set("moveTo", Napi::Function::New(env, CTX_2D(cairo_move_to)));
|
|
631
|
+
c.Set("lineTo", Napi::Function::New(env, CTX_2D(cairo_line_to)));
|
|
632
|
+
c.Set("relMoveTo", Napi::Function::New(env, CTX_2D(cairo_rel_move_to)));
|
|
633
|
+
c.Set("relLineTo", Napi::Function::New(env, CTX_2D(cairo_rel_line_to)));
|
|
634
|
+
c.Set("translate", Napi::Function::New(env, CTX_2D(cairo_translate)));
|
|
635
|
+
c.Set("scale", Napi::Function::New(env, CTX_2D(cairo_scale)));
|
|
636
|
+
|
|
637
|
+
c.Set("setSourceRGB", Napi::Function::New(env, CTX_3D(cairo_set_source_rgb)));
|
|
638
|
+
c.Set("setSourceRGBA", Napi::Function::New(env, CTX_4D(cairo_set_source_rgba)));
|
|
639
|
+
c.Set("rectangle", Napi::Function::New(env, CTX_4D(cairo_rectangle)));
|
|
640
|
+
c.Set("arc", Napi::Function::New(env, CTX_5D(cairo_arc)));
|
|
641
|
+
c.Set("arcNegative", Napi::Function::New(env, CTX_5D(cairo_arc_negative)));
|
|
642
|
+
c.Set("curveTo", Napi::Function::New(env, CTX_6D(cairo_curve_to)));
|
|
643
|
+
|
|
644
|
+
c.Set("setOperator", Napi::Function::New(env, CTX_ENUM(cairo_set_operator, cairo_operator_t)));
|
|
645
|
+
c.Set("setLineCap", Napi::Function::New(env, CTX_ENUM(cairo_set_line_cap, cairo_line_cap_t)));
|
|
646
|
+
c.Set("setLineJoin", Napi::Function::New(env, CTX_ENUM(cairo_set_line_join, cairo_line_join_t)));
|
|
647
|
+
c.Set("setFillRule", Napi::Function::New(env, CTX_ENUM(cairo_set_fill_rule, cairo_fill_rule_t)));
|
|
648
|
+
c.Set("setAntialias", Napi::Function::New(env, CTX_ENUM(cairo_set_antialias, cairo_antialias_t)));
|
|
649
|
+
|
|
650
|
+
c.Set("getLineWidth", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
651
|
+
Napi::Env env = info.Env();
|
|
652
|
+
cairo_t* cr = CtxArg(env, info);
|
|
653
|
+
if (cr == nullptr) return env.Undefined();
|
|
654
|
+
return Napi::Number::New(env, cairo_get_line_width(cr));
|
|
655
|
+
}));
|
|
656
|
+
// Deterministic state getters (font/zlib/display-independent) — mirror GJS's
|
|
657
|
+
// Context getters + drive the cross-runtime conformance program.
|
|
658
|
+
c.Set("getOperator", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
659
|
+
Napi::Env env = info.Env();
|
|
660
|
+
cairo_t* cr = CtxArg(env, info);
|
|
661
|
+
if (cr == nullptr) return env.Undefined();
|
|
662
|
+
return Napi::Number::New(env, cairo_get_operator(cr));
|
|
663
|
+
}));
|
|
664
|
+
c.Set("getLineCap", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
665
|
+
Napi::Env env = info.Env();
|
|
666
|
+
cairo_t* cr = CtxArg(env, info);
|
|
667
|
+
if (cr == nullptr) return env.Undefined();
|
|
668
|
+
return Napi::Number::New(env, cairo_get_line_cap(cr));
|
|
669
|
+
}));
|
|
670
|
+
c.Set("getLineJoin", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
671
|
+
Napi::Env env = info.Env();
|
|
672
|
+
cairo_t* cr = CtxArg(env, info);
|
|
673
|
+
if (cr == nullptr) return env.Undefined();
|
|
674
|
+
return Napi::Number::New(env, cairo_get_line_join(cr));
|
|
675
|
+
}));
|
|
676
|
+
c.Set("getFillRule", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
677
|
+
Napi::Env env = info.Env();
|
|
678
|
+
cairo_t* cr = CtxArg(env, info);
|
|
679
|
+
if (cr == nullptr) return env.Undefined();
|
|
680
|
+
return Napi::Number::New(env, cairo_get_fill_rule(cr));
|
|
681
|
+
}));
|
|
682
|
+
c.Set("getAntialias", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
683
|
+
Napi::Env env = info.Env();
|
|
684
|
+
cairo_t* cr = CtxArg(env, info);
|
|
685
|
+
if (cr == nullptr) return env.Undefined();
|
|
686
|
+
return Napi::Number::New(env, cairo_get_antialias(cr));
|
|
687
|
+
}));
|
|
688
|
+
c.Set("getMiterLimit", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
689
|
+
Napi::Env env = info.Env();
|
|
690
|
+
cairo_t* cr = CtxArg(env, info);
|
|
691
|
+
if (cr == nullptr) return env.Undefined();
|
|
692
|
+
return Napi::Number::New(env, cairo_get_miter_limit(cr));
|
|
693
|
+
}));
|
|
694
|
+
c.Set("getTolerance", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
695
|
+
Napi::Env env = info.Env();
|
|
696
|
+
cairo_t* cr = CtxArg(env, info);
|
|
697
|
+
if (cr == nullptr) return env.Undefined();
|
|
698
|
+
return Napi::Number::New(env, cairo_get_tolerance(cr));
|
|
699
|
+
}));
|
|
700
|
+
c.Set("hasCurrentPoint", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
701
|
+
Napi::Env env = info.Env();
|
|
702
|
+
cairo_t* cr = CtxArg(env, info);
|
|
703
|
+
if (cr == nullptr) return env.Undefined();
|
|
704
|
+
return Napi::Boolean::New(env, cairo_has_current_point(cr));
|
|
705
|
+
}));
|
|
706
|
+
c.Set("getCurrentPoint", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
707
|
+
Napi::Env env = info.Env();
|
|
708
|
+
cairo_t* cr = CtxArg(env, info);
|
|
709
|
+
if (cr == nullptr) return env.Undefined();
|
|
710
|
+
double x = 0, y = 0;
|
|
711
|
+
cairo_get_current_point(cr, &x, &y);
|
|
712
|
+
Napi::Array a = Napi::Array::New(env, 2);
|
|
713
|
+
a.Set(0u, Napi::Number::New(env, x));
|
|
714
|
+
a.Set(1u, Napi::Number::New(env, y));
|
|
715
|
+
return a;
|
|
716
|
+
}));
|
|
717
|
+
// The 4-double extents getters (path/fill/stroke/clip) return [x1, y1, x2, y2].
|
|
718
|
+
#define CTX_EXTENTS(NAME, CFN) \
|
|
719
|
+
c.Set(NAME, Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value { \
|
|
720
|
+
Napi::Env env = info.Env(); \
|
|
721
|
+
cairo_t* cr = CtxArg(env, info); \
|
|
722
|
+
if (cr == nullptr) return env.Undefined(); \
|
|
723
|
+
double x1 = 0, y1 = 0, x2 = 0, y2 = 0; \
|
|
724
|
+
CFN(cr, &x1, &y1, &x2, &y2); \
|
|
725
|
+
Napi::Array a = Napi::Array::New(env, 4); \
|
|
726
|
+
a.Set(0u, Napi::Number::New(env, x1)); \
|
|
727
|
+
a.Set(1u, Napi::Number::New(env, y1)); \
|
|
728
|
+
a.Set(2u, Napi::Number::New(env, x2)); \
|
|
729
|
+
a.Set(3u, Napi::Number::New(env, y2)); \
|
|
730
|
+
return a; \
|
|
731
|
+
}));
|
|
732
|
+
CTX_EXTENTS("pathExtents", cairo_path_extents)
|
|
733
|
+
CTX_EXTENTS("fillExtents", cairo_fill_extents)
|
|
734
|
+
CTX_EXTENTS("strokeExtents", cairo_stroke_extents)
|
|
735
|
+
CTX_EXTENTS("clipExtents", cairo_clip_extents)
|
|
736
|
+
c.Set("status", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
737
|
+
Napi::Env env = info.Env();
|
|
738
|
+
cairo_t* cr = CtxArg(env, info);
|
|
739
|
+
if (cr == nullptr) return env.Undefined();
|
|
740
|
+
return Napi::Number::New(env, cairo_status(cr));
|
|
741
|
+
}));
|
|
742
|
+
c.Set("setSource", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
743
|
+
Napi::Env env = info.Env();
|
|
744
|
+
cairo_t* cr = CtxArg(env, info);
|
|
745
|
+
if (cr == nullptr) return env.Undefined();
|
|
746
|
+
cairo_pattern_t* p = PatternFromExt(env, info[1]);
|
|
747
|
+
if (p == nullptr) return env.Undefined();
|
|
748
|
+
cairo_set_source(cr, p);
|
|
749
|
+
CheckStatus(env, cairo_status(cr), "context");
|
|
750
|
+
return env.Undefined();
|
|
751
|
+
}));
|
|
752
|
+
c.Set("setSourceSurface", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
753
|
+
Napi::Env env = info.Env();
|
|
754
|
+
cairo_t* cr = CtxArg(env, info);
|
|
755
|
+
if (cr == nullptr) return env.Undefined();
|
|
756
|
+
cairo_surface_t* p = SurfaceFromExt(env, info[1]);
|
|
757
|
+
if (p == nullptr) return env.Undefined();
|
|
758
|
+
cairo_set_source_surface(cr, p, Num(info, 2), Num(info, 3));
|
|
759
|
+
CheckStatus(env, cairo_status(cr), "context");
|
|
760
|
+
return env.Undefined();
|
|
761
|
+
}));
|
|
762
|
+
|
|
763
|
+
c.Set("identityMatrix", Napi::Function::New(env, CTX_0(cairo_identity_matrix)));
|
|
764
|
+
c.Set("newSubPath", Napi::Function::New(env, CTX_0(cairo_new_sub_path)));
|
|
765
|
+
|
|
766
|
+
// Point/distance transforms: (x, y) IN-OUT doubles, returned as a 2-array —
|
|
767
|
+
// the GJS FUNC2FFAFF return shape ([x, y]).
|
|
768
|
+
#define CTX_2FFAFF(NAME, CFN) \
|
|
769
|
+
c.Set(NAME, Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value { \
|
|
770
|
+
Napi::Env env = info.Env(); \
|
|
771
|
+
cairo_t* cr = CtxArg(env, info); \
|
|
772
|
+
if (cr == nullptr) return env.Undefined(); \
|
|
773
|
+
double x = Num(info, 1), y = Num(info, 2); \
|
|
774
|
+
CFN(cr, &x, &y); \
|
|
775
|
+
if (!CheckStatus(env, cairo_status(cr), "context")) return env.Undefined(); \
|
|
776
|
+
Napi::Array a = Napi::Array::New(env, 2); \
|
|
777
|
+
a.Set(0u, Napi::Number::New(env, x)); \
|
|
778
|
+
a.Set(1u, Napi::Number::New(env, y)); \
|
|
779
|
+
return a; \
|
|
780
|
+
}));
|
|
781
|
+
CTX_2FFAFF("userToDevice", cairo_user_to_device)
|
|
782
|
+
CTX_2FFAFF("userToDeviceDistance", cairo_user_to_device_distance)
|
|
783
|
+
CTX_2FFAFF("deviceToUser", cairo_device_to_user)
|
|
784
|
+
CTX_2FFAFF("deviceToUserDistance", cairo_device_to_user_distance)
|
|
785
|
+
|
|
786
|
+
c.Set("inFill", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
787
|
+
Napi::Env env = info.Env();
|
|
788
|
+
cairo_t* cr = CtxArg(env, info);
|
|
789
|
+
if (cr == nullptr) return env.Undefined();
|
|
790
|
+
cairo_bool_t ret = cairo_in_fill(cr, Num(info, 1), Num(info, 2));
|
|
791
|
+
if (!CheckStatus(env, cairo_status(cr), "context")) return env.Undefined();
|
|
792
|
+
return Napi::Boolean::New(env, ret);
|
|
793
|
+
}));
|
|
794
|
+
c.Set("inStroke", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
795
|
+
Napi::Env env = info.Env();
|
|
796
|
+
cairo_t* cr = CtxArg(env, info);
|
|
797
|
+
if (cr == nullptr) return env.Undefined();
|
|
798
|
+
cairo_bool_t ret = cairo_in_stroke(cr, Num(info, 1), Num(info, 2));
|
|
799
|
+
if (!CheckStatus(env, cairo_status(cr), "context")) return env.Undefined();
|
|
800
|
+
return Napi::Boolean::New(env, ret);
|
|
801
|
+
}));
|
|
802
|
+
|
|
803
|
+
// Path copy/append — a copied path is an OWNED cairo_path_t handle (destroyed
|
|
804
|
+
// on GC / $dispose via cairo_path_destroy).
|
|
805
|
+
c.Set("copyPath", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
806
|
+
Napi::Env env = info.Env();
|
|
807
|
+
cairo_t* cr = CtxArg(env, info);
|
|
808
|
+
if (cr == nullptr) return env.Undefined();
|
|
809
|
+
cairo_path_t* path = cairo_copy_path(cr);
|
|
810
|
+
if (path->status != CAIRO_STATUS_SUCCESS) {
|
|
811
|
+
cairo_status_t st = path->status;
|
|
812
|
+
cairo_path_destroy(path);
|
|
813
|
+
CheckStatus(env, st, "context");
|
|
814
|
+
return env.Undefined();
|
|
815
|
+
}
|
|
816
|
+
return MakeCairoHandle(env, path, CairoKind::Path);
|
|
817
|
+
}));
|
|
818
|
+
c.Set("copyPathFlat", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
819
|
+
Napi::Env env = info.Env();
|
|
820
|
+
cairo_t* cr = CtxArg(env, info);
|
|
821
|
+
if (cr == nullptr) return env.Undefined();
|
|
822
|
+
cairo_path_t* path = cairo_copy_path_flat(cr);
|
|
823
|
+
if (path->status != CAIRO_STATUS_SUCCESS) {
|
|
824
|
+
cairo_status_t st = path->status;
|
|
825
|
+
cairo_path_destroy(path);
|
|
826
|
+
CheckStatus(env, st, "context");
|
|
827
|
+
return env.Undefined();
|
|
828
|
+
}
|
|
829
|
+
return MakeCairoHandle(env, path, CairoKind::Path);
|
|
830
|
+
}));
|
|
831
|
+
c.Set("appendPath", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
832
|
+
Napi::Env env = info.Env();
|
|
833
|
+
cairo_t* cr = CtxArg(env, info);
|
|
834
|
+
if (cr == nullptr) return env.Undefined();
|
|
835
|
+
cairo_path_t* path = PathFromExt(env, info[1]);
|
|
836
|
+
if (path == nullptr) return env.Undefined();
|
|
837
|
+
cairo_append_path(cr, path);
|
|
838
|
+
CheckStatus(env, cairo_status(cr), "context");
|
|
839
|
+
return env.Undefined();
|
|
840
|
+
}));
|
|
841
|
+
|
|
842
|
+
// setDash mirrors gjs's setDash_func: holes/undefined entries are skipped, a
|
|
843
|
+
// non-positive dash throws, an empty array disables dashing.
|
|
844
|
+
c.Set("setDash", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
845
|
+
Napi::Env env = info.Env();
|
|
846
|
+
cairo_t* cr = CtxArg(env, info);
|
|
847
|
+
if (cr == nullptr) return env.Undefined();
|
|
848
|
+
if (!info[1].IsArray()) {
|
|
849
|
+
Napi::TypeError::New(env, "dashes must be an array").ThrowAsJavaScriptException();
|
|
850
|
+
return env.Undefined();
|
|
851
|
+
}
|
|
852
|
+
Napi::Array dashes = info[1].As<Napi::Array>();
|
|
853
|
+
double offset = Num(info, 2);
|
|
854
|
+
uint32_t len = dashes.Length();
|
|
855
|
+
std::vector<double> dashes_c;
|
|
856
|
+
dashes_c.reserve(len);
|
|
857
|
+
for (uint32_t i = 0; i < len; ++i) {
|
|
858
|
+
Napi::Value elem = dashes.Get(i);
|
|
859
|
+
if (elem.IsUndefined()) continue;
|
|
860
|
+
double b = elem.ToNumber().DoubleValue();
|
|
861
|
+
if (b <= 0) {
|
|
862
|
+
Napi::Error::New(env, "Dash value must be positive").ThrowAsJavaScriptException();
|
|
863
|
+
return env.Undefined();
|
|
864
|
+
}
|
|
865
|
+
dashes_c.push_back(b);
|
|
866
|
+
}
|
|
867
|
+
cairo_set_dash(cr, dashes_c.data(), static_cast<int>(dashes_c.size()), offset);
|
|
868
|
+
CheckStatus(env, cairo_status(cr), "context");
|
|
869
|
+
return env.Undefined();
|
|
870
|
+
}));
|
|
871
|
+
c.Set("getDashCount", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
872
|
+
Napi::Env env = info.Env();
|
|
873
|
+
cairo_t* cr = CtxArg(env, info);
|
|
874
|
+
if (cr == nullptr) return env.Undefined();
|
|
875
|
+
return Napi::Number::New(env, cairo_get_dash_count(cr));
|
|
876
|
+
}));
|
|
877
|
+
// getDash: net-new (GJS ships only getDashCount). Returns [dashes[], offset].
|
|
878
|
+
c.Set("getDash", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
879
|
+
Napi::Env env = info.Env();
|
|
880
|
+
cairo_t* cr = CtxArg(env, info);
|
|
881
|
+
if (cr == nullptr) return env.Undefined();
|
|
882
|
+
int count = cairo_get_dash_count(cr);
|
|
883
|
+
std::vector<double> dashes_c(static_cast<size_t>(count > 0 ? count : 0));
|
|
884
|
+
double offset = 0;
|
|
885
|
+
cairo_get_dash(cr, dashes_c.data(), &offset);
|
|
886
|
+
Napi::Array dashes = Napi::Array::New(env, dashes_c.size());
|
|
887
|
+
for (size_t i = 0; i < dashes_c.size(); ++i) {
|
|
888
|
+
dashes.Set(static_cast<uint32_t>(i), Napi::Number::New(env, dashes_c[i]));
|
|
889
|
+
}
|
|
890
|
+
Napi::Array out = Napi::Array::New(env, 2);
|
|
891
|
+
out.Set(0u, dashes);
|
|
892
|
+
out.Set(1u, Napi::Number::New(env, offset));
|
|
893
|
+
return out;
|
|
894
|
+
}));
|
|
895
|
+
|
|
896
|
+
// getSource: the pattern belongs to the context — take our own ref so the JS
|
|
897
|
+
// wrapper owns one (mirrors gjs_cairo_pattern_from_pattern's kept reference).
|
|
898
|
+
c.Set("getSource", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
899
|
+
Napi::Env env = info.Env();
|
|
900
|
+
cairo_t* cr = CtxArg(env, info);
|
|
901
|
+
if (cr == nullptr) return env.Undefined();
|
|
902
|
+
cairo_pattern_t* p = cairo_get_source(cr);
|
|
903
|
+
if (!CheckStatus(env, cairo_status(cr), "context")) return env.Undefined();
|
|
904
|
+
cairo_pattern_reference(p);
|
|
905
|
+
return MakeCairoHandle(env, p, CairoKind::Pattern);
|
|
906
|
+
}));
|
|
907
|
+
|
|
908
|
+
// ---- $dispose (Context / Surface / Pattern / Path) ----
|
|
909
|
+
// Eagerly destroy the underlying cairo object + mark the handle so the finalizer
|
|
910
|
+
// won't double-free and any further method call throws (a live-handle check).
|
|
911
|
+
c.Set("dispose", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
912
|
+
Napi::Env env = info.Env();
|
|
913
|
+
CairoHandle* h = HandleFromExternal(info[0]);
|
|
914
|
+
if (h == nullptr) return env.Undefined();
|
|
915
|
+
if (!h->disposed) {
|
|
916
|
+
DestroyCairo(h);
|
|
917
|
+
h->disposed = true;
|
|
918
|
+
h->ptr = nullptr;
|
|
919
|
+
}
|
|
920
|
+
return env.Undefined();
|
|
921
|
+
}));
|
|
922
|
+
|
|
923
|
+
exports.Set("__cairo", c);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
} // namespace nodegi
|