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