1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
3 // SPDX-FileCopyrightText: 2010 litl, LLC.
4
5 #include <config.h>
6
7 #include <cairo.h>
8 #include <girepository.h>
9 #include <glib.h>
10
11 #include <js/Array.h>
12 #include <js/CallArgs.h>
13 #include <js/Class.h>
14 #include <js/PropertyDescriptor.h> // for JSPROP_READONLY
15 #include <js/PropertySpec.h>
16 #include <js/RootingAPI.h>
17 #include <js/TypeDecls.h>
18 #include <js/Value.h>
19 #include <js/ValueArray.h>
20 #include <jsapi.h> // for JS_GetPrivate, JS_GetClass, ...
21
22 #include "gi/arg-inl.h"
23 #include "gi/arg.h"
24 #include "gi/cwrapper.h"
25 #include "gi/foreign.h"
26 #include "gjs/enum-utils.h"
27 #include "gjs/jsapi-class.h"
28 #include "gjs/jsapi-util-args.h"
29 #include "gjs/jsapi-util.h"
30 #include "gjs/macros.h"
31 #include "modules/cairo-private.h"
32
33 /* Properties */
34 // clang-format off
35 const JSPropertySpec CairoSurface::proto_props[] = {
36 JS_STRING_SYM_PS(toStringTag, "Surface", JSPROP_READONLY),
37 JS_PS_END};
38 // clang-format on
39
40 /* Methods */
41 GJS_JSAPI_RETURN_CONVENTION
42 static bool
writeToPNG_func(JSContext * context,unsigned argc,JS::Value * vp)43 writeToPNG_func(JSContext *context,
44 unsigned argc,
45 JS::Value *vp)
46 {
47 GJS_GET_THIS(context, argc, vp, argv, obj);
48 GjsAutoChar filename;
49
50 if (!gjs_parse_call_args(context, "writeToPNG", argv, "F",
51 "filename", &filename))
52 return false;
53
54 cairo_surface_t* surface = CairoSurface::for_js(context, obj);
55 if (!surface)
56 return false;
57
58 cairo_surface_write_to_png(surface, filename);
59 if (!gjs_cairo_check_status(context, cairo_surface_status(surface),
60 "surface"))
61 return false;
62 argv.rval().setUndefined();
63 return true;
64 }
65
66 GJS_JSAPI_RETURN_CONVENTION
getType_func(JSContext * context,unsigned argc,JS::Value * vp)67 bool CairoSurface::getType_func(JSContext* context, unsigned argc,
68 JS::Value* vp) {
69 GJS_GET_THIS(context, argc, vp, rec, obj);
70 cairo_surface_type_t type;
71
72 if (argc > 1) {
73 gjs_throw(context, "Surface.getType() takes no arguments");
74 return false;
75 }
76
77 cairo_surface_t* surface = CairoSurface::for_js(context, obj);
78 if (!surface)
79 return false;
80
81 type = cairo_surface_get_type(surface);
82 if (!gjs_cairo_check_status(context, cairo_surface_status(surface),
83 "surface"))
84 return false;
85
86 rec.rval().setInt32(type);
87 return true;
88 }
89
90 GJS_JSAPI_RETURN_CONVENTION
setDeviceOffset_func(JSContext * cx,unsigned argc,JS::Value * vp)91 static bool setDeviceOffset_func(JSContext* cx, unsigned argc, JS::Value* vp) {
92 GJS_GET_THIS(cx, argc, vp, args, obj);
93 double x_offset = 0.0, y_offset = 0.0;
94 if (!gjs_parse_call_args(cx, "setDeviceOffset", args, "ff", "x_offset",
95 &x_offset, "y_offset", &y_offset))
96 return false;
97
98 cairo_surface_t* surface = CairoSurface::for_js(cx, obj);
99 if (!surface)
100 return false;
101
102 cairo_surface_set_device_offset(surface, x_offset, y_offset);
103 if (!gjs_cairo_check_status(cx, cairo_surface_status(surface), "surface"))
104 return false;
105
106 args.rval().setUndefined();
107 return true;
108 }
109
110 GJS_JSAPI_RETURN_CONVENTION
getDeviceOffset_func(JSContext * cx,unsigned argc,JS::Value * vp)111 static bool getDeviceOffset_func(JSContext* cx, unsigned argc, JS::Value* vp) {
112 GJS_GET_THIS(cx, argc, vp, args, obj);
113
114 if (argc > 0) {
115 gjs_throw(cx, "Surface.getDeviceOffset() takes no arguments");
116 return false;
117 }
118
119 cairo_surface_t* surface = CairoSurface::for_js(cx, obj);
120 if (!surface)
121 return false;
122
123 double x_offset, y_offset;
124 cairo_surface_get_device_offset(surface, &x_offset, &y_offset);
125 // cannot error
126
127 JS::RootedValueArray<2> elements(cx);
128 elements[0].setNumber(x_offset);
129 elements[1].setNumber(y_offset);
130 JS::RootedObject retval(cx, JS::NewArrayObject(cx, elements));
131 if (!retval)
132 return false;
133
134 args.rval().setObject(*retval);
135 return true;
136 }
137
138 GJS_JSAPI_RETURN_CONVENTION
setDeviceScale_func(JSContext * cx,unsigned argc,JS::Value * vp)139 static bool setDeviceScale_func(JSContext* cx, unsigned argc, JS::Value* vp) {
140 GJS_GET_THIS(cx, argc, vp, args, obj);
141 double x_scale = 1.0, y_scale = 1.0;
142
143 if (!gjs_parse_call_args(cx, "setDeviceScale", args, "ff", "x_scale",
144 &x_scale, "y_scale", &y_scale))
145 return false;
146
147 cairo_surface_t* surface = CairoSurface::for_js(cx, obj);
148 if (!surface)
149 return false;
150
151 cairo_surface_set_device_scale(surface, x_scale, y_scale);
152 if (!gjs_cairo_check_status(cx, cairo_surface_status(surface),
153 "surface"))
154 return false;
155
156 args.rval().setUndefined();
157 return true;
158 }
159
160 GJS_JSAPI_RETURN_CONVENTION
getDeviceScale_func(JSContext * cx,unsigned argc,JS::Value * vp)161 static bool getDeviceScale_func(JSContext* cx, unsigned argc, JS::Value* vp) {
162 GJS_GET_THIS(cx, argc, vp, args, obj);
163
164 if (argc > 0) {
165 gjs_throw(cx, "Surface.getDeviceScale() takes no arguments");
166 return false;
167 }
168
169 cairo_surface_t* surface = CairoSurface::for_js(cx, obj);
170 if (!surface)
171 return false;
172
173 double x_scale, y_scale;
174 cairo_surface_get_device_scale(surface, &x_scale, &y_scale);
175 // cannot error
176
177 JS::RootedValueArray<2> elements(cx);
178 elements[0].setNumber(x_scale);
179 elements[1].setNumber(y_scale);
180 JS::RootedObject retval(cx, JS::NewArrayObject(cx, elements));
181 if (!retval)
182 return false;
183
184 args.rval().setObject(*retval);
185 return true;
186 }
187
188 const JSFunctionSpec CairoSurface::proto_funcs[] = {
189 // flush
190 // getContent
191 // getFontOptions
192 JS_FN("getType", getType_func, 0, 0),
193 // markDirty
194 // markDirtyRectangle
195 JS_FN("setDeviceOffset", setDeviceOffset_func, 2, 0),
196 JS_FN("getDeviceOffset", getDeviceOffset_func, 0, 0),
197 JS_FN("setDeviceScale", setDeviceScale_func, 2, 0),
198 JS_FN("getDeviceScale", getDeviceScale_func, 0, 0),
199 // setFallbackResolution
200 // getFallbackResolution
201 // copyPage
202 // showPage
203 // hasShowTextGlyphs
204 JS_FN("writeToPNG", writeToPNG_func, 0, 0), JS_FS_END};
205
206 /* Public API */
207
208 /**
209 * CairoSurface::finalize_impl:
210 * @fop: the free op
211 * @surface: the pointer to finalize
212 *
213 * Destroys the resources associated with a surface wrapper.
214 *
215 * This is mainly used for subclasses.
216 */
finalize_impl(JSFreeOp *,cairo_surface_t * surface)217 void CairoSurface::finalize_impl(JSFreeOp*, cairo_surface_t* surface) {
218 if (!surface)
219 return;
220 cairo_surface_destroy(surface);
221 }
222
223 /**
224 * CairoSurface::from_c_ptr:
225 * @context: the context
226 * @surface: cairo_surface to attach to the object
227 *
228 * Constructs a surface wrapper given cairo surface.
229 * A reference to @surface will be taken.
230 *
231 */
from_c_ptr(JSContext * context,cairo_surface_t * surface)232 JSObject* CairoSurface::from_c_ptr(JSContext* context,
233 cairo_surface_t* surface) {
234 g_return_val_if_fail(context, nullptr);
235 g_return_val_if_fail(surface, nullptr);
236
237 cairo_surface_type_t type = cairo_surface_get_type(surface);
238 if (type == CAIRO_SURFACE_TYPE_IMAGE)
239 return CairoImageSurface::from_c_ptr(context, surface);
240 if (type == CAIRO_SURFACE_TYPE_PDF)
241 return CairoPDFSurface::from_c_ptr(context, surface);
242 if (type == CAIRO_SURFACE_TYPE_PS)
243 return CairoPSSurface::from_c_ptr(context, surface);
244 if (type == CAIRO_SURFACE_TYPE_SVG)
245 return CairoSVGSurface::from_c_ptr(context, surface);
246 return CairoSurface::CWrapper::from_c_ptr(context, surface);
247 }
248
249 /**
250 * CairoSurface::for_js:
251 * @cx: the context
252 * @surface_wrapper: surface wrapper
253 *
254 * Overrides NativeObject::for_js().
255 *
256 * Returns: the surface attached to the wrapper.
257 */
for_js(JSContext * cx,JS::HandleObject surface_wrapper)258 cairo_surface_t* CairoSurface::for_js(JSContext* cx,
259 JS::HandleObject surface_wrapper) {
260 g_return_val_if_fail(cx, nullptr);
261 g_return_val_if_fail(surface_wrapper, nullptr);
262
263 JS::RootedObject proto(cx, CairoSurface::prototype(cx));
264
265 bool is_surface_subclass = false;
266 if (!gjs_object_in_prototype_chain(cx, proto, surface_wrapper,
267 &is_surface_subclass))
268 return nullptr;
269 if (!is_surface_subclass) {
270 gjs_throw(cx, "Expected Cairo.Surface but got %s",
271 JS_GetClass(surface_wrapper)->name);
272 return nullptr;
273 }
274
275 return static_cast<cairo_surface_t*>(JS_GetPrivate(surface_wrapper));
276 }
277
surface_to_g_argument(JSContext * context,JS::Value value,const char * arg_name,GjsArgumentType argument_type,GITransfer transfer,GjsArgumentFlags flags,GIArgument * arg)278 [[nodiscard]] static bool surface_to_g_argument(
279 JSContext* context, JS::Value value, const char* arg_name,
280 GjsArgumentType argument_type, GITransfer transfer, GjsArgumentFlags flags,
281 GIArgument* arg) {
282 if (value.isNull()) {
283 if (!(flags & GjsArgumentFlags::MAY_BE_NULL)) {
284 GjsAutoChar display_name =
285 gjs_argument_display_name(arg_name, argument_type);
286 gjs_throw(context, "%s may not be null", display_name.get());
287 return false;
288 }
289
290 gjs_arg_unset<void*>(arg);
291 return true;
292 }
293
294 if (!value.isObject()) {
295 GjsAutoChar display_name =
296 gjs_argument_display_name(arg_name, argument_type);
297 gjs_throw(context, "%s is not a Cairo.Surface", display_name.get());
298 return false;
299 }
300
301 JS::RootedObject surface_wrapper(context, &value.toObject());
302 cairo_surface_t* s = CairoSurface::for_js(context, surface_wrapper);
303 if (!s)
304 return false;
305 if (transfer == GI_TRANSFER_EVERYTHING)
306 cairo_surface_destroy(s);
307
308 gjs_arg_set(arg, s);
309 return true;
310 }
311
312 GJS_JSAPI_RETURN_CONVENTION
surface_from_g_argument(JSContext * cx,JS::MutableHandleValue value_p,GIArgument * arg)313 static bool surface_from_g_argument(JSContext* cx,
314 JS::MutableHandleValue value_p,
315 GIArgument* arg) {
316 JSObject* obj =
317 CairoSurface::from_c_ptr(cx, gjs_arg_get<cairo_surface_t*>(arg));
318 if (!obj)
319 return false;
320
321 value_p.setObject(*obj);
322 return true;
323 }
324
surface_release_argument(JSContext *,GITransfer transfer,GIArgument * arg)325 static bool surface_release_argument(JSContext*, GITransfer transfer,
326 GIArgument* arg) {
327 if (transfer != GI_TRANSFER_NOTHING)
328 cairo_surface_destroy(gjs_arg_get<cairo_surface_t*>(arg));
329 return true;
330 }
331
gjs_cairo_surface_init(void)332 void gjs_cairo_surface_init(void) {
333 static GjsForeignInfo foreign_info = {surface_to_g_argument,
334 surface_from_g_argument,
335 surface_release_argument};
336 gjs_struct_foreign_register("cairo", "Surface", &foreign_info);
337 }
338