Branch data Line data Source code
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/Object.h> // for GetClass
15 : : #include <js/PropertyDescriptor.h> // for JSPROP_READONLY
16 : : #include <js/PropertySpec.h>
17 : : #include <js/RootingAPI.h>
18 : : #include <js/TypeDecls.h>
19 : : #include <js/Value.h>
20 : : #include <js/ValueArray.h>
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
43 : 0 : writeToPNG_func(JSContext *context,
44 : : unsigned argc,
45 : : JS::Value *vp)
46 : : {
47 [ # # ]: 0 : GJS_GET_THIS(context, argc, vp, argv, obj);
48 : 0 : GjsAutoChar filename;
49 : :
50 [ # # ]: 0 : if (!gjs_parse_call_args(context, "writeToPNG", argv, "F",
51 : : "filename", &filename))
52 : 0 : return false;
53 : :
54 : 0 : cairo_surface_t* surface = CairoSurface::for_js(context, obj);
55 [ # # ]: 0 : if (!surface)
56 : 0 : return false;
57 : :
58 : 0 : cairo_surface_write_to_png(surface, filename);
59 [ # # ]: 0 : if (!gjs_cairo_check_status(context, cairo_surface_status(surface),
60 : : "surface"))
61 : 0 : return false;
62 : 0 : argv.rval().setUndefined();
63 : 0 : return true;
64 : 0 : }
65 : :
66 : : GJS_JSAPI_RETURN_CONVENTION
67 : 1 : bool flush_func(JSContext* cx,
68 : : unsigned argc,
69 : : JS::Value* vp) {
70 [ - + ]: 1 : GJS_GET_THIS(cx, argc, vp, argv, obj);
71 : :
72 [ - + ]: 1 : if (argc > 1) {
73 : 0 : gjs_throw(cx, "Surface.flush() takes no arguments");
74 : 0 : return false;
75 : : }
76 : :
77 : 1 : cairo_surface_t* surface = CairoSurface::for_js(cx, obj);
78 [ - + ]: 1 : if (!surface)
79 : 0 : return false;
80 : :
81 : 1 : cairo_surface_flush(surface);
82 : :
83 [ - + ]: 1 : if (!gjs_cairo_check_status(cx, cairo_surface_status(surface), "surface"))
84 : 0 : return false;
85 : :
86 : 1 : argv.rval().setUndefined();
87 : 1 : return true;
88 : 1 : }
89 : :
90 : : GJS_JSAPI_RETURN_CONVENTION
91 : 2 : bool finish_func(JSContext* cx,
92 : : unsigned argc,
93 : : JS::Value* vp) {
94 [ - + ]: 2 : GJS_GET_THIS(cx, argc, vp, argv, obj);
95 : :
96 [ - + ]: 2 : if (argc > 1) {
97 : 0 : gjs_throw(cx, "Surface.finish() takes no arguments");
98 : 0 : return false;
99 : : }
100 : :
101 : 2 : cairo_surface_t* surface = CairoSurface::for_js(cx, obj);
102 [ - + ]: 2 : if (!surface)
103 : 0 : return false;
104 : :
105 : 2 : cairo_surface_finish(surface);
106 : :
107 [ - + ]: 2 : if (!gjs_cairo_check_status(cx, cairo_surface_status(surface), "surface"))
108 : 0 : return false;
109 : :
110 : 2 : argv.rval().setUndefined();
111 : 2 : return true;
112 : 2 : }
113 : :
114 : : GJS_JSAPI_RETURN_CONVENTION
115 : 0 : bool CairoSurface::getType_func(JSContext* context, unsigned argc,
116 : : JS::Value* vp) {
117 [ # # ]: 0 : GJS_GET_THIS(context, argc, vp, rec, obj);
118 : : cairo_surface_type_t type;
119 : :
120 [ # # ]: 0 : if (argc > 1) {
121 : 0 : gjs_throw(context, "Surface.getType() takes no arguments");
122 : 0 : return false;
123 : : }
124 : :
125 : 0 : cairo_surface_t* surface = CairoSurface::for_js(context, obj);
126 [ # # ]: 0 : if (!surface)
127 : 0 : return false;
128 : :
129 : 0 : type = cairo_surface_get_type(surface);
130 [ # # ]: 0 : if (!gjs_cairo_check_status(context, cairo_surface_status(surface),
131 : : "surface"))
132 : 0 : return false;
133 : :
134 : 0 : rec.rval().setInt32(type);
135 : 0 : return true;
136 : 0 : }
137 : :
138 : : GJS_JSAPI_RETURN_CONVENTION
139 : 1 : static bool setDeviceOffset_func(JSContext* cx, unsigned argc, JS::Value* vp) {
140 [ - + ]: 1 : GJS_GET_THIS(cx, argc, vp, args, obj);
141 : 1 : double x_offset = 0.0, y_offset = 0.0;
142 [ - + ]: 1 : if (!gjs_parse_call_args(cx, "setDeviceOffset", args, "ff", "x_offset",
143 : : &x_offset, "y_offset", &y_offset))
144 : 0 : return false;
145 : :
146 : 1 : cairo_surface_t* surface = CairoSurface::for_js(cx, obj);
147 [ - + ]: 1 : if (!surface)
148 : 0 : return false;
149 : :
150 : 1 : cairo_surface_set_device_offset(surface, x_offset, y_offset);
151 [ - + ]: 1 : if (!gjs_cairo_check_status(cx, cairo_surface_status(surface), "surface"))
152 : 0 : return false;
153 : :
154 : 1 : args.rval().setUndefined();
155 : 1 : return true;
156 : 1 : }
157 : :
158 : : GJS_JSAPI_RETURN_CONVENTION
159 : 2 : static bool getDeviceOffset_func(JSContext* cx, unsigned argc, JS::Value* vp) {
160 [ - + ]: 2 : GJS_GET_THIS(cx, argc, vp, args, obj);
161 : :
162 [ - + ]: 2 : if (argc > 0) {
163 : 0 : gjs_throw(cx, "Surface.getDeviceOffset() takes no arguments");
164 : 0 : return false;
165 : : }
166 : :
167 : 2 : cairo_surface_t* surface = CairoSurface::for_js(cx, obj);
168 [ - + ]: 2 : if (!surface)
169 : 0 : return false;
170 : :
171 : : double x_offset, y_offset;
172 : 2 : cairo_surface_get_device_offset(surface, &x_offset, &y_offset);
173 : : // cannot error
174 : :
175 : 2 : JS::RootedValueArray<2> elements(cx);
176 : 2 : elements[0].setNumber(JS::CanonicalizeNaN(x_offset));
177 : 2 : elements[1].setNumber(JS::CanonicalizeNaN(y_offset));
178 : 2 : JS::RootedObject retval(cx, JS::NewArrayObject(cx, elements));
179 [ - + ]: 2 : if (!retval)
180 : 0 : return false;
181 : :
182 : 2 : args.rval().setObject(*retval);
183 : 2 : return true;
184 : 2 : }
185 : :
186 : : GJS_JSAPI_RETURN_CONVENTION
187 : 1 : static bool setDeviceScale_func(JSContext* cx, unsigned argc, JS::Value* vp) {
188 [ - + ]: 1 : GJS_GET_THIS(cx, argc, vp, args, obj);
189 : 1 : double x_scale = 1.0, y_scale = 1.0;
190 : :
191 [ - + ]: 1 : if (!gjs_parse_call_args(cx, "setDeviceScale", args, "ff", "x_scale",
192 : : &x_scale, "y_scale", &y_scale))
193 : 0 : return false;
194 : :
195 : 1 : cairo_surface_t* surface = CairoSurface::for_js(cx, obj);
196 [ - + ]: 1 : if (!surface)
197 : 0 : return false;
198 : :
199 : 1 : cairo_surface_set_device_scale(surface, x_scale, y_scale);
200 [ - + ]: 1 : if (!gjs_cairo_check_status(cx, cairo_surface_status(surface),
201 : : "surface"))
202 : 0 : return false;
203 : :
204 : 1 : args.rval().setUndefined();
205 : 1 : return true;
206 : 1 : }
207 : :
208 : : GJS_JSAPI_RETURN_CONVENTION
209 : 2 : static bool getDeviceScale_func(JSContext* cx, unsigned argc, JS::Value* vp) {
210 [ - + ]: 2 : GJS_GET_THIS(cx, argc, vp, args, obj);
211 : :
212 [ - + ]: 2 : if (argc > 0) {
213 : 0 : gjs_throw(cx, "Surface.getDeviceScale() takes no arguments");
214 : 0 : return false;
215 : : }
216 : :
217 : 2 : cairo_surface_t* surface = CairoSurface::for_js(cx, obj);
218 [ - + ]: 2 : if (!surface)
219 : 0 : return false;
220 : :
221 : : double x_scale, y_scale;
222 : 2 : cairo_surface_get_device_scale(surface, &x_scale, &y_scale);
223 : : // cannot error
224 : :
225 : 2 : JS::RootedValueArray<2> elements(cx);
226 : 2 : elements[0].setNumber(JS::CanonicalizeNaN(x_scale));
227 : 2 : elements[1].setNumber(JS::CanonicalizeNaN(y_scale));
228 : 2 : JS::RootedObject retval(cx, JS::NewArrayObject(cx, elements));
229 [ - + ]: 2 : if (!retval)
230 : 0 : return false;
231 : :
232 : 2 : args.rval().setObject(*retval);
233 : 2 : return true;
234 : 2 : }
235 : :
236 : : const JSFunctionSpec CairoSurface::proto_funcs[] = {
237 : : JS_FN("flush", flush_func, 0, 0),
238 : : JS_FN("finish", finish_func, 0, 0),
239 : : // getContent
240 : : // getFontOptions
241 : : JS_FN("getType", getType_func, 0, 0),
242 : : // markDirty
243 : : // markDirtyRectangle
244 : : JS_FN("setDeviceOffset", setDeviceOffset_func, 2, 0),
245 : : JS_FN("getDeviceOffset", getDeviceOffset_func, 0, 0),
246 : : JS_FN("setDeviceScale", setDeviceScale_func, 2, 0),
247 : : JS_FN("getDeviceScale", getDeviceScale_func, 0, 0),
248 : : // setFallbackResolution
249 : : // getFallbackResolution
250 : : // copyPage
251 : : // showPage
252 : : // hasShowTextGlyphs
253 : : JS_FN("writeToPNG", writeToPNG_func, 0, 0), JS_FS_END};
254 : :
255 : : /* Public API */
256 : :
257 : : /**
258 : : * CairoSurface::finalize_impl:
259 : : * @surface: the pointer to finalize
260 : : *
261 : : * Destroys the resources associated with a surface wrapper.
262 : : *
263 : : * This is mainly used for subclasses.
264 : : */
265 : 51 : void CairoSurface::finalize_impl(JS::GCContext*, cairo_surface_t* surface) {
266 [ - + ]: 51 : if (!surface)
267 : 0 : return;
268 : 51 : cairo_surface_destroy(surface);
269 : : }
270 : :
271 : : /**
272 : : * CairoSurface::from_c_ptr:
273 : : * @context: the context
274 : : * @surface: cairo_surface to attach to the object
275 : : *
276 : : * Constructs a surface wrapper given cairo surface.
277 : : * A reference to @surface will be taken.
278 : : *
279 : : */
280 : 6 : JSObject* CairoSurface::from_c_ptr(JSContext* context,
281 : : cairo_surface_t* surface) {
282 : 6 : g_return_val_if_fail(context, nullptr);
283 : 6 : g_return_val_if_fail(surface, nullptr);
284 : :
285 : 6 : cairo_surface_type_t type = cairo_surface_get_type(surface);
286 [ + + ]: 6 : if (type == CAIRO_SURFACE_TYPE_IMAGE)
287 : 5 : return CairoImageSurface::from_c_ptr(context, surface);
288 [ - + ]: 1 : if (type == CAIRO_SURFACE_TYPE_PDF)
289 : 0 : return CairoPDFSurface::from_c_ptr(context, surface);
290 [ - + ]: 1 : if (type == CAIRO_SURFACE_TYPE_PS)
291 : 0 : return CairoPSSurface::from_c_ptr(context, surface);
292 [ - + ]: 1 : if (type == CAIRO_SURFACE_TYPE_SVG)
293 : 0 : return CairoSVGSurface::from_c_ptr(context, surface);
294 : 1 : return CairoSurface::CWrapper::from_c_ptr(context, surface);
295 : : }
296 : :
297 : : /**
298 : : * CairoSurface::for_js:
299 : : * @cx: the context
300 : : * @surface_wrapper: surface wrapper
301 : : *
302 : : * Overrides NativeObject::for_js().
303 : : *
304 : : * Returns: the surface attached to the wrapper.
305 : : */
306 : 73 : cairo_surface_t* CairoSurface::for_js(JSContext* cx,
307 : : JS::HandleObject surface_wrapper) {
308 : 73 : g_return_val_if_fail(cx, nullptr);
309 : 73 : g_return_val_if_fail(surface_wrapper, nullptr);
310 : :
311 : 73 : JS::RootedObject proto(cx, CairoSurface::prototype(cx));
312 : :
313 : 73 : bool is_surface_subclass = false;
314 [ - + ]: 73 : if (!gjs_object_in_prototype_chain(cx, proto, surface_wrapper,
315 : : &is_surface_subclass))
316 : 0 : return nullptr;
317 [ + + ]: 73 : if (!is_surface_subclass) {
318 : 2 : gjs_throw(cx, "Expected Cairo.Surface but got %s",
319 : 2 : JS::GetClass(surface_wrapper)->name);
320 : 2 : return nullptr;
321 : : }
322 : :
323 : 71 : return JS::GetMaybePtrFromReservedSlot<cairo_surface_t>(
324 : 71 : surface_wrapper, CairoSurface::POINTER);
325 : 73 : }
326 : :
327 : 2 : [[nodiscard]] static bool surface_to_gi_argument(
328 : : JSContext* context, JS::Value value, const char* arg_name,
329 : : GjsArgumentType argument_type, GITransfer transfer, GjsArgumentFlags flags,
330 : : GIArgument* arg) {
331 [ - + ]: 2 : if (value.isNull()) {
332 [ # # ]: 0 : if (!(flags & GjsArgumentFlags::MAY_BE_NULL)) {
333 : : GjsAutoChar display_name =
334 : 0 : gjs_argument_display_name(arg_name, argument_type);
335 : 0 : gjs_throw(context, "%s may not be null", display_name.get());
336 : 0 : return false;
337 : 0 : }
338 : :
339 : 0 : gjs_arg_unset<void*>(arg);
340 : 0 : return true;
341 : : }
342 : :
343 [ - + ]: 2 : if (!value.isObject()) {
344 : : GjsAutoChar display_name =
345 : 0 : gjs_argument_display_name(arg_name, argument_type);
346 : 0 : gjs_throw(context, "%s is not a Cairo.Surface", display_name.get());
347 : 0 : return false;
348 : 0 : }
349 : :
350 : 2 : JS::RootedObject surface_wrapper(context, &value.toObject());
351 : 2 : cairo_surface_t* s = CairoSurface::for_js(context, surface_wrapper);
352 [ - + ]: 2 : if (!s)
353 : 0 : return false;
354 [ - + ]: 2 : if (transfer == GI_TRANSFER_EVERYTHING)
355 : 0 : cairo_surface_destroy(s);
356 : :
357 : 2 : gjs_arg_set(arg, s);
358 : 2 : return true;
359 : 2 : }
360 : :
361 : : GJS_JSAPI_RETURN_CONVENTION
362 : 3 : static bool surface_from_gi_argument(JSContext* cx,
363 : : JS::MutableHandleValue value_p,
364 : : GIArgument* arg) {
365 : : JSObject* obj =
366 : 3 : CairoSurface::from_c_ptr(cx, gjs_arg_get<cairo_surface_t*>(arg));
367 [ - + ]: 3 : if (!obj)
368 : 0 : return false;
369 : :
370 : 3 : value_p.setObject(*obj);
371 : 3 : return true;
372 : : }
373 : :
374 : 3 : static bool surface_release_argument(JSContext*, GITransfer transfer,
375 : : GIArgument* arg) {
376 [ + + ]: 3 : if (transfer != GI_TRANSFER_NOTHING)
377 : 2 : cairo_surface_destroy(gjs_arg_get<cairo_surface_t*>(arg));
378 : 3 : return true;
379 : : }
380 : :
381 : 2 : void gjs_cairo_surface_init(void) {
382 : : static GjsForeignInfo foreign_info = {surface_to_gi_argument,
383 : : surface_from_gi_argument,
384 : : surface_release_argument};
385 : 2 : gjs_struct_foreign_register("cairo", "Surface", &foreign_info);
386 : 2 : }
|