1 | /* |
2 | * Copyright © 2019 Benjamin Otte |
3 | * Timm Bäder |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Lesser General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2.1 of the License, or (at your option) any later version. |
9 | * |
10 | * This library is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Lesser General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Lesser General Public |
16 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
17 | * |
18 | * Authors: Benjamin Otte <otte@gnome.org> |
19 | * Timm Bäder <mail@baedert.org> |
20 | */ |
21 | |
22 | #include "config.h" |
23 | |
24 | #include "gskrendernodeparserprivate.h" |
25 | |
26 | #include "gskroundedrectprivate.h" |
27 | #include "gskrendernodeprivate.h" |
28 | #include "gsktransformprivate.h" |
29 | |
30 | #include "gdk/gdkrgbaprivate.h" |
31 | #include "gdk/gdktextureprivate.h" |
32 | #include <gtk/css/gtkcss.h> |
33 | #include "gtk/css/gtkcssdataurlprivate.h" |
34 | #include "gtk/css/gtkcssparserprivate.h" |
35 | #include "gtk/css/gtkcssserializerprivate.h" |
36 | |
37 | #ifdef CAIRO_HAS_SCRIPT_SURFACE |
38 | #include <cairo-script.h> |
39 | #endif |
40 | #ifdef HAVE_CAIRO_SCRIPT_INTERPRETER |
41 | #include <cairo-script-interpreter.h> |
42 | #endif |
43 | |
44 | typedef struct _Declaration Declaration; |
45 | |
46 | struct _Declaration |
47 | { |
48 | const char *name; |
49 | gboolean (* parse_func) (GtkCssParser *parser, gpointer result); |
50 | void (* clear_func) (gpointer data); |
51 | gpointer result; |
52 | }; |
53 | |
54 | static gboolean |
55 | parse_rect (GtkCssParser *parser, |
56 | gpointer out_rect) |
57 | { |
58 | double numbers[4]; |
59 | |
60 | if (!gtk_css_parser_consume_number (self: parser, number: &numbers[0]) || |
61 | !gtk_css_parser_consume_number (self: parser, number: &numbers[1]) || |
62 | !gtk_css_parser_consume_number (self: parser, number: &numbers[2]) || |
63 | !gtk_css_parser_consume_number (self: parser, number: &numbers[3])) |
64 | return FALSE; |
65 | |
66 | graphene_rect_init (r: out_rect, x: numbers[0], y: numbers[1], width: numbers[2], height: numbers[3]); |
67 | |
68 | return TRUE; |
69 | } |
70 | |
71 | static gboolean |
72 | parse_texture (GtkCssParser *parser, |
73 | gpointer out_data) |
74 | { |
75 | GdkTexture *texture; |
76 | GError *error = NULL; |
77 | GtkCssLocation start_location; |
78 | char *url, *scheme; |
79 | |
80 | start_location = *gtk_css_parser_get_start_location (self: parser); |
81 | url = gtk_css_parser_consume_url (self: parser); |
82 | if (url == NULL) |
83 | return FALSE; |
84 | |
85 | scheme = g_uri_parse_scheme (uri: url); |
86 | if (scheme && g_ascii_strcasecmp (s1: scheme, s2: "data" ) == 0) |
87 | { |
88 | GBytes *bytes; |
89 | |
90 | bytes = gtk_css_data_url_parse (url, NULL, error: &error); |
91 | if (bytes) |
92 | { |
93 | texture = gdk_texture_new_from_bytes (bytes, error: &error); |
94 | g_bytes_unref (bytes); |
95 | } |
96 | else |
97 | { |
98 | texture = NULL; |
99 | } |
100 | } |
101 | else |
102 | { |
103 | GFile *file; |
104 | |
105 | file = gtk_css_parser_resolve_url (self: parser, url); |
106 | |
107 | if (file) |
108 | { |
109 | texture = gdk_texture_new_from_file (file, error: &error); |
110 | g_object_unref (object: file); |
111 | } |
112 | else |
113 | { |
114 | texture = NULL; |
115 | } |
116 | } |
117 | |
118 | g_free (mem: scheme); |
119 | g_free (mem: url); |
120 | |
121 | if (texture == NULL) |
122 | { |
123 | if (error) |
124 | { |
125 | gtk_css_parser_emit_error (self: parser, |
126 | start: &start_location, |
127 | end: gtk_css_parser_get_end_location (self: parser), |
128 | error); |
129 | g_clear_error (err: &error); |
130 | } |
131 | return FALSE; |
132 | } |
133 | |
134 | *(GdkTexture **) out_data = texture; |
135 | return TRUE; |
136 | } |
137 | |
138 | static void |
139 | clear_texture (gpointer inout_texture) |
140 | { |
141 | g_clear_object ((GdkTexture **) inout_texture); |
142 | } |
143 | |
144 | static cairo_surface_t * |
145 | csi_hooks_surface_create (void *closure, |
146 | cairo_content_t content, |
147 | double width, |
148 | double height, |
149 | long uid) |
150 | { |
151 | return cairo_surface_create_similar (other: closure, content, width, height); |
152 | } |
153 | |
154 | static const cairo_user_data_key_t csi_hooks_key; |
155 | |
156 | static cairo_t * |
157 | csi_hooks_context_create (void *closure, |
158 | cairo_surface_t *surface) |
159 | { |
160 | cairo_t *cr = cairo_create (target: surface); |
161 | |
162 | cairo_set_user_data (cr, |
163 | key: &csi_hooks_key, |
164 | user_data: cairo_surface_reference (surface), |
165 | destroy: (cairo_destroy_func_t) cairo_surface_destroy); |
166 | |
167 | return cr; |
168 | } |
169 | |
170 | static void |
171 | csi_hooks_context_destroy (void *closure, |
172 | void *ptr) |
173 | { |
174 | cairo_surface_t *surface; |
175 | cairo_t *cr; |
176 | |
177 | surface = cairo_get_user_data (cr: ptr, key: &csi_hooks_key); |
178 | cr = cairo_create (target: closure); |
179 | cairo_set_source_surface (cr, surface, x: 0, y: 0); |
180 | cairo_paint (cr); |
181 | cairo_destroy (cr); |
182 | } |
183 | |
184 | static gboolean |
185 | parse_script (GtkCssParser *parser, |
186 | gpointer out_data) |
187 | { |
188 | #ifdef HAVE_CAIRO_SCRIPT_INTERPRETER |
189 | GError *error = NULL; |
190 | GBytes *bytes; |
191 | GtkCssLocation start_location; |
192 | char *url, *scheme; |
193 | cairo_script_interpreter_t *csi; |
194 | cairo_script_interpreter_hooks_t hooks = { |
195 | .surface_create = csi_hooks_surface_create, |
196 | .context_create = csi_hooks_context_create, |
197 | .context_destroy = csi_hooks_context_destroy, |
198 | }; |
199 | |
200 | start_location = *gtk_css_parser_get_start_location (self: parser); |
201 | url = gtk_css_parser_consume_url (self: parser); |
202 | if (url == NULL) |
203 | return FALSE; |
204 | |
205 | scheme = g_uri_parse_scheme (uri: url); |
206 | if (scheme && g_ascii_strcasecmp (s1: scheme, s2: "data" ) == 0) |
207 | { |
208 | bytes = gtk_css_data_url_parse (url, NULL, error: &error); |
209 | } |
210 | else |
211 | { |
212 | GFile *file; |
213 | |
214 | file = gtk_css_parser_resolve_url (self: parser, url); |
215 | bytes = g_file_load_bytes (file, NULL, NULL, error: &error); |
216 | g_object_unref (object: file); |
217 | } |
218 | |
219 | g_free (mem: scheme); |
220 | g_free (mem: url); |
221 | |
222 | if (bytes == NULL) |
223 | { |
224 | gtk_css_parser_emit_error (self: parser, |
225 | start: &start_location, |
226 | end: gtk_css_parser_get_end_location (self: parser), |
227 | error); |
228 | g_clear_error (err: &error); |
229 | return FALSE; |
230 | } |
231 | |
232 | hooks.closure = cairo_recording_surface_create (content: CAIRO_CONTENT_COLOR_ALPHA, NULL); |
233 | csi = cairo_script_interpreter_create (); |
234 | cairo_script_interpreter_install_hooks (ctx: csi, hooks: &hooks); |
235 | cairo_script_interpreter_feed_string (ctx: csi, line: g_bytes_get_data (bytes, NULL), len: g_bytes_get_size (bytes)); |
236 | g_bytes_unref (bytes); |
237 | if (cairo_surface_status (surface: hooks.closure) != CAIRO_STATUS_SUCCESS) |
238 | { |
239 | gtk_css_parser_error_value (self: parser, format: "Invalid Cairo script: %s" , cairo_status_to_string (status: cairo_surface_status (surface: hooks.closure))); |
240 | cairo_script_interpreter_destroy (ctx: csi); |
241 | return FALSE; |
242 | } |
243 | if (cairo_script_interpreter_destroy (ctx: csi) != CAIRO_STATUS_SUCCESS) |
244 | { |
245 | gtk_css_parser_error_value (self: parser, format: "Invalid Cairo script" ); |
246 | cairo_surface_destroy (surface: hooks.closure); |
247 | return FALSE; |
248 | } |
249 | |
250 | *(cairo_surface_t **) out_data = hooks.closure; |
251 | return TRUE; |
252 | #else |
253 | gtk_css_parser_warn (parser, |
254 | GTK_CSS_PARSER_WARNING_UNIMPLEMENTED, |
255 | gtk_css_parser_get_block_location (parser), |
256 | gtk_css_parser_get_start_location (parser), |
257 | "GTK was compiled with script interpreter support. Using fallback pixel data for Cairo node." ); |
258 | *(cairo_surface_t **) out_data = NULL; |
259 | return TRUE; |
260 | #endif |
261 | } |
262 | |
263 | static void |
264 | clear_surface (gpointer inout_surface) |
265 | { |
266 | g_clear_pointer ((cairo_surface_t **) inout_surface, cairo_surface_destroy); |
267 | } |
268 | |
269 | static gboolean |
270 | parse_rounded_rect (GtkCssParser *parser, |
271 | gpointer out_rect) |
272 | { |
273 | graphene_rect_t r; |
274 | graphene_size_t corners[4]; |
275 | double d; |
276 | guint i; |
277 | |
278 | if (!parse_rect (parser, out_rect: &r)) |
279 | return FALSE; |
280 | |
281 | if (!gtk_css_parser_try_delim (self: parser, codepoint: '/')) |
282 | { |
283 | gsk_rounded_rect_init_from_rect (self: out_rect, bounds: &r, radius: 0); |
284 | return TRUE; |
285 | } |
286 | |
287 | for (i = 0; i < 4; i++) |
288 | { |
289 | if (!gtk_css_parser_has_number (self: parser)) |
290 | break; |
291 | if (!gtk_css_parser_consume_number (self: parser, number: &d)) |
292 | return FALSE; |
293 | corners[i].width = d; |
294 | } |
295 | |
296 | if (i == 0) |
297 | { |
298 | gtk_css_parser_error_syntax (self: parser, format: "Expected a number" ); |
299 | return FALSE; |
300 | } |
301 | |
302 | /* The magic (i - 1) >> 1 below makes it take the correct value |
303 | * according to spec. Feel free to check the 4 cases |
304 | */ |
305 | for (; i < 4; i++) |
306 | corners[i].width = corners[(i - 1) >> 1].width; |
307 | |
308 | if (gtk_css_parser_try_delim (self: parser, codepoint: '/')) |
309 | { |
310 | gtk_css_parser_consume_token (self: parser); |
311 | |
312 | for (i = 0; i < 4; i++) |
313 | { |
314 | if (!gtk_css_parser_has_number (self: parser)) |
315 | break; |
316 | if (!gtk_css_parser_consume_number (self: parser, number: &d)) |
317 | return FALSE; |
318 | corners[i].height = d; |
319 | } |
320 | |
321 | if (i == 0) |
322 | { |
323 | gtk_css_parser_error_syntax (self: parser, format: "Expected a number" ); |
324 | return FALSE; |
325 | } |
326 | |
327 | for (; i < 4; i++) |
328 | corners[i].height = corners[(i - 1) >> 1].height; |
329 | } |
330 | else |
331 | { |
332 | for (i = 0; i < 4; i++) |
333 | corners[i].height = corners[i].width; |
334 | } |
335 | |
336 | gsk_rounded_rect_init (self: out_rect, bounds: &r, top_left: &corners[0], top_right: &corners[1], bottom_right: &corners[2], bottom_left: &corners[3]); |
337 | |
338 | return TRUE; |
339 | } |
340 | |
341 | static gboolean |
342 | parse_color (GtkCssParser *parser, |
343 | gpointer out_color) |
344 | { |
345 | return gdk_rgba_parser_parse (parser, rgba: out_color); |
346 | } |
347 | |
348 | static gboolean |
349 | parse_double (GtkCssParser *parser, |
350 | gpointer out_double) |
351 | { |
352 | return gtk_css_parser_consume_number (self: parser, number: out_double); |
353 | } |
354 | |
355 | static gboolean |
356 | parse_point (GtkCssParser *parser, |
357 | gpointer out_point) |
358 | { |
359 | double x, y; |
360 | |
361 | if (!gtk_css_parser_consume_number (self: parser, number: &x) || |
362 | !gtk_css_parser_consume_number (self: parser, number: &y)) |
363 | return FALSE; |
364 | |
365 | graphene_point_init (p: out_point, x, y); |
366 | |
367 | return TRUE; |
368 | } |
369 | |
370 | static gboolean |
371 | parse_transform (GtkCssParser *parser, |
372 | gpointer out_transform) |
373 | { |
374 | GskTransform *transform; |
375 | |
376 | if (!gsk_transform_parser_parse (parser, out_transform: &transform)) |
377 | { |
378 | gsk_transform_unref (self: transform); |
379 | return FALSE; |
380 | } |
381 | |
382 | *(GskTransform **) out_transform = transform; |
383 | |
384 | return TRUE; |
385 | } |
386 | |
387 | static void |
388 | clear_transform (gpointer inout_transform) |
389 | { |
390 | g_clear_pointer ((GskTransform **) inout_transform, gsk_transform_unref); |
391 | } |
392 | |
393 | static gboolean |
394 | parse_string (GtkCssParser *parser, |
395 | gpointer out_string) |
396 | { |
397 | const GtkCssToken *token; |
398 | char *s; |
399 | |
400 | token = gtk_css_parser_get_token (self: parser); |
401 | if (!gtk_css_token_is (token, GTK_CSS_TOKEN_STRING)) |
402 | return FALSE; |
403 | |
404 | s = g_strdup (str: token->string.string); |
405 | gtk_css_parser_consume_token (self: parser); |
406 | |
407 | g_free (mem: *(char **) out_string); |
408 | *(char **) out_string = s; |
409 | |
410 | return TRUE; |
411 | } |
412 | |
413 | static void |
414 | clear_string (gpointer inout_string) |
415 | { |
416 | g_clear_pointer ((char **) inout_string, g_free); |
417 | } |
418 | |
419 | static gboolean |
420 | parse_stops (GtkCssParser *parser, |
421 | gpointer out_stops) |
422 | { |
423 | GArray *stops; |
424 | GskColorStop stop; |
425 | |
426 | stops = g_array_new (FALSE, FALSE, element_size: sizeof (GskColorStop)); |
427 | |
428 | for (;;) |
429 | { |
430 | double dval; |
431 | |
432 | if (!gtk_css_parser_consume_number (self: parser, number: &dval)) |
433 | goto error; |
434 | |
435 | stop.offset = dval; |
436 | |
437 | if (!gdk_rgba_parser_parse (parser, rgba: &stop.color)) |
438 | goto error; |
439 | |
440 | if (stops->len == 0 && stop.offset < 0) |
441 | gtk_css_parser_error_value (self: parser, format: "Color stop offset must be >= 0" ); |
442 | else if (stops->len > 0 && stop.offset < g_array_index (stops, GskColorStop, stops->len - 1).offset) |
443 | gtk_css_parser_error_value (self: parser, format: "Color stop offset must be >= previous value" ); |
444 | else if (stop.offset > 1) |
445 | gtk_css_parser_error_value (self: parser, format: "Color stop offset must be <= 1" ); |
446 | else |
447 | g_array_append_val (stops, stop); |
448 | |
449 | if (gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_COMMA)) |
450 | gtk_css_parser_skip (self: parser); |
451 | else |
452 | break; |
453 | } |
454 | |
455 | if (stops->len < 2) |
456 | { |
457 | gtk_css_parser_error_value (self: parser, format: "At least 2 color stops need to be specified" ); |
458 | g_array_free (array: stops, TRUE); |
459 | return FALSE; |
460 | } |
461 | |
462 | if (*(GArray **) out_stops) |
463 | g_array_free (array: *(GArray **) out_stops, TRUE); |
464 | *(GArray **) out_stops = stops; |
465 | |
466 | return TRUE; |
467 | |
468 | error: |
469 | g_array_free (array: stops, TRUE); |
470 | return FALSE; |
471 | } |
472 | |
473 | static void |
474 | clear_stops (gpointer inout_stops) |
475 | { |
476 | GArray **stops = (GArray **) inout_stops; |
477 | |
478 | if (*stops) |
479 | { |
480 | g_array_free (array: *stops, TRUE); |
481 | *stops = NULL; |
482 | } |
483 | } |
484 | |
485 | static gboolean |
486 | parse_float4 (GtkCssParser *parser, |
487 | gpointer out_floats) |
488 | { |
489 | float *floats = (float *) out_floats; |
490 | double d[4]; |
491 | int i; |
492 | |
493 | for (i = 0; i < 4 && !gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_EOF); i ++) |
494 | { |
495 | if (!gtk_css_parser_consume_number (self: parser, number: &d[i])) |
496 | return FALSE; |
497 | } |
498 | if (i == 0) |
499 | { |
500 | gtk_css_parser_error_syntax (self: parser, format: "Expected a color" ); |
501 | return FALSE; |
502 | } |
503 | for (; i < 4; i++) |
504 | { |
505 | d[i] = d[(i - 1) >> 1]; |
506 | } |
507 | |
508 | for (i = 0; i < 4; i++) |
509 | { |
510 | floats[i] = d[i]; |
511 | } |
512 | |
513 | return TRUE; |
514 | } |
515 | |
516 | static gboolean |
517 | parse_colors4 (GtkCssParser *parser, |
518 | gpointer out_colors) |
519 | { |
520 | GdkRGBA colors[4]; |
521 | int i; |
522 | |
523 | for (i = 0; i < 4 && !gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_EOF); i ++) |
524 | { |
525 | if (!gdk_rgba_parser_parse (parser, rgba: &colors[i])) |
526 | return FALSE; |
527 | } |
528 | if (i == 0) |
529 | { |
530 | gtk_css_parser_error_syntax (self: parser, format: "Expected a color" ); |
531 | return FALSE; |
532 | } |
533 | for (; i < 4; i++) |
534 | { |
535 | colors[i] = colors[(i - 1) >> 1]; |
536 | } |
537 | |
538 | memcpy (dest: out_colors, src: colors, n: sizeof (GdkRGBA) * 4); |
539 | |
540 | return TRUE; |
541 | } |
542 | |
543 | static gboolean |
544 | parse_shadows (GtkCssParser *parser, |
545 | gpointer out_shadows) |
546 | { |
547 | GArray *shadows = out_shadows; |
548 | |
549 | do |
550 | { |
551 | GskShadow shadow = { GDK_RGBA("000000" ), 0, 0, 0 }; |
552 | double dx = 0, dy = 0, radius = 0; |
553 | |
554 | if (!gdk_rgba_parser_parse (parser, rgba: &shadow.color)) |
555 | gtk_css_parser_error_value (self: parser, format: "Expected shadow color" ); |
556 | |
557 | if (!gtk_css_parser_consume_number (self: parser, number: &dx)) |
558 | gtk_css_parser_error_value (self: parser, format: "Expected shadow x offset" ); |
559 | |
560 | if (!gtk_css_parser_consume_number (self: parser, number: &dy)) |
561 | gtk_css_parser_error_value (self: parser, format: "Expected shadow y offset" ); |
562 | |
563 | if (gtk_css_parser_has_number (self: parser)) |
564 | { |
565 | if (!gtk_css_parser_consume_number (self: parser, number: &radius)) |
566 | gtk_css_parser_error_value (self: parser, format: "Expected shadow blur radius" ); |
567 | } |
568 | |
569 | shadow.dx = dx; |
570 | shadow.dy = dy; |
571 | shadow.radius = radius; |
572 | |
573 | g_array_append_val (shadows, shadow); |
574 | } |
575 | while (gtk_css_parser_try_token (self: parser, token_type: GTK_CSS_TOKEN_COMMA)); |
576 | |
577 | return TRUE; |
578 | } |
579 | |
580 | static void |
581 | clear_shadows (gpointer inout_shadows) |
582 | { |
583 | g_array_set_size (array: *(GArray **) inout_shadows, length: 0); |
584 | } |
585 | |
586 | static const struct |
587 | { |
588 | GskBlendMode mode; |
589 | const char *name; |
590 | } blend_modes[] = { |
591 | { GSK_BLEND_MODE_DEFAULT, "normal" }, |
592 | { GSK_BLEND_MODE_MULTIPLY, "multiply" }, |
593 | { GSK_BLEND_MODE_SCREEN, "screen" }, |
594 | { GSK_BLEND_MODE_OVERLAY, "overlay" }, |
595 | { GSK_BLEND_MODE_DARKEN, "darken" }, |
596 | { GSK_BLEND_MODE_LIGHTEN, "lighten" }, |
597 | { GSK_BLEND_MODE_COLOR_DODGE, "color-dodge" }, |
598 | { GSK_BLEND_MODE_COLOR_BURN, "color-burn" }, |
599 | { GSK_BLEND_MODE_HARD_LIGHT, "hard-light" }, |
600 | { GSK_BLEND_MODE_SOFT_LIGHT, "soft-light" }, |
601 | { GSK_BLEND_MODE_DIFFERENCE, "difference" }, |
602 | { GSK_BLEND_MODE_EXCLUSION, "exclusion" }, |
603 | { GSK_BLEND_MODE_COLOR, "color" }, |
604 | { GSK_BLEND_MODE_HUE, "hue" }, |
605 | { GSK_BLEND_MODE_SATURATION, "saturation" }, |
606 | { GSK_BLEND_MODE_LUMINOSITY, "luminosity" } |
607 | }; |
608 | |
609 | static gboolean |
610 | parse_blend_mode (GtkCssParser *parser, |
611 | gpointer out_mode) |
612 | { |
613 | guint i; |
614 | |
615 | for (i = 0; i < G_N_ELEMENTS (blend_modes); i++) |
616 | { |
617 | if (gtk_css_parser_try_ident (self: parser, ident: blend_modes[i].name)) |
618 | { |
619 | *(GskBlendMode *) out_mode = blend_modes[i].mode; |
620 | return TRUE; |
621 | } |
622 | } |
623 | |
624 | return FALSE; |
625 | } |
626 | |
627 | static PangoFont * |
628 | font_from_string (const char *string) |
629 | { |
630 | PangoFontDescription *desc; |
631 | PangoFontMap *font_map; |
632 | PangoContext *context; |
633 | PangoFont *font; |
634 | |
635 | desc = pango_font_description_from_string (str: string); |
636 | font_map = pango_cairo_font_map_get_default (); |
637 | context = pango_font_map_create_context (fontmap: font_map); |
638 | font = pango_font_map_load_font (fontmap: font_map, context, desc); |
639 | |
640 | pango_font_description_free (desc); |
641 | g_object_unref (object: context); |
642 | |
643 | return font; |
644 | } |
645 | |
646 | #define MIN_ASCII_GLYPH 32 |
647 | #define MAX_ASCII_GLYPH 127 /* exclusive */ |
648 | #define N_ASCII_GLYPHS (MAX_ASCII_GLYPH - MIN_ASCII_GLYPH) |
649 | |
650 | static PangoGlyphString * |
651 | create_ascii_glyphs (PangoFont *font) |
652 | { |
653 | PangoLanguage *language = pango_language_from_string (language: "en_US" ); /* just pick one */ |
654 | PangoCoverage *coverage; |
655 | PangoAnalysis not_a_hack = { |
656 | .shape_engine = NULL, /* unused */ |
657 | .lang_engine = NULL, /* unused by pango_shape() */ |
658 | .font = font, |
659 | .level = 0, |
660 | .gravity = PANGO_GRAVITY_SOUTH, |
661 | .flags = 0, |
662 | .script = PANGO_SCRIPT_COMMON, |
663 | .language = language, |
664 | .extra_attrs = NULL |
665 | }; |
666 | PangoGlyphString *result, *glyph_string; |
667 | guint i; |
668 | |
669 | coverage = pango_font_get_coverage (font, language); |
670 | for (i = MIN_ASCII_GLYPH; i < MAX_ASCII_GLYPH; i++) |
671 | { |
672 | if (!pango_coverage_get (coverage, index_: i)) |
673 | break; |
674 | } |
675 | pango_coverage_unref (coverage); |
676 | if (i < MAX_ASCII_GLYPH) |
677 | return NULL; |
678 | |
679 | result = pango_glyph_string_new (); |
680 | pango_glyph_string_set_size (string: result, N_ASCII_GLYPHS); |
681 | glyph_string = pango_glyph_string_new (); |
682 | for (i = MIN_ASCII_GLYPH; i < MAX_ASCII_GLYPH; i++) |
683 | { |
684 | const char text[2] = { i, 0 }; |
685 | PangoShapeFlags flags = 0; |
686 | |
687 | if (cairo_version () < CAIRO_VERSION_ENCODE (1, 17, 4)) |
688 | flags = PANGO_SHAPE_ROUND_POSITIONS; |
689 | |
690 | pango_shape_with_flags (item_text: text, item_length: 1, |
691 | paragraph_text: text, paragraph_length: 1, |
692 | analysis: ¬_a_hack, |
693 | glyphs: glyph_string, |
694 | flags); |
695 | |
696 | if (glyph_string->num_glyphs != 1) |
697 | { |
698 | pango_glyph_string_free (string: glyph_string); |
699 | pango_glyph_string_free (string: result); |
700 | return NULL; |
701 | } |
702 | result->glyphs[i - MIN_ASCII_GLYPH] = glyph_string->glyphs[0]; |
703 | } |
704 | |
705 | pango_glyph_string_free (string: glyph_string); |
706 | |
707 | return result; |
708 | } |
709 | |
710 | static gboolean |
711 | parse_font (GtkCssParser *parser, |
712 | gpointer out_font) |
713 | { |
714 | PangoFont *font; |
715 | char *s; |
716 | |
717 | s = gtk_css_parser_consume_string (self: parser); |
718 | if (s == NULL) |
719 | return FALSE; |
720 | |
721 | font = font_from_string (string: s); |
722 | if (font == NULL) |
723 | { |
724 | gtk_css_parser_error_syntax (self: parser, format: "This font does not exist." ); |
725 | return FALSE; |
726 | } |
727 | |
728 | *((PangoFont**)out_font) = font; |
729 | |
730 | g_free (mem: s); |
731 | |
732 | return TRUE; |
733 | } |
734 | |
735 | static void |
736 | clear_font (gpointer inout_font) |
737 | { |
738 | g_clear_object ((PangoFont **) inout_font); |
739 | } |
740 | |
741 | static gboolean |
742 | parse_glyphs (GtkCssParser *parser, |
743 | gpointer out_glyphs) |
744 | { |
745 | PangoGlyphString *glyph_string; |
746 | |
747 | glyph_string = pango_glyph_string_new (); |
748 | |
749 | do |
750 | { |
751 | PangoGlyphInfo gi = { 0, { 0, 0, 0}, { 1 } }; |
752 | double d, d2; |
753 | int i; |
754 | |
755 | if (gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_STRING)) |
756 | { |
757 | char *s = gtk_css_parser_consume_string (self: parser); |
758 | |
759 | for (i = 0; s[i] != 0; i++) |
760 | { |
761 | if (s[i] < MIN_ASCII_GLYPH || s[i] >= MAX_ASCII_GLYPH) |
762 | { |
763 | gtk_css_parser_error_value (self: parser, format: "Unsupported character %d in string" , i); |
764 | } |
765 | gi.glyph = PANGO_GLYPH_INVALID_INPUT - MAX_ASCII_GLYPH + s[i]; |
766 | pango_glyph_string_set_size (string: glyph_string, new_len: glyph_string->num_glyphs + 1); |
767 | glyph_string->glyphs[glyph_string->num_glyphs - 1] = gi; |
768 | } |
769 | |
770 | g_free (mem: s); |
771 | } |
772 | else |
773 | { |
774 | if (!gtk_css_parser_consume_integer (self: parser, number: &i) || |
775 | !gtk_css_parser_consume_number (self: parser, number: &d)) |
776 | { |
777 | pango_glyph_string_free (string: glyph_string); |
778 | return FALSE; |
779 | } |
780 | gi.glyph = i; |
781 | gi.geometry.width = (int) (d * PANGO_SCALE); |
782 | |
783 | if (gtk_css_parser_has_number (self: parser)) |
784 | { |
785 | if (!gtk_css_parser_consume_number (self: parser, number: &d) || |
786 | !gtk_css_parser_consume_number (self: parser, number: &d2)) |
787 | { |
788 | pango_glyph_string_free (string: glyph_string); |
789 | return FALSE; |
790 | } |
791 | gi.geometry.x_offset = (int) (d * PANGO_SCALE); |
792 | gi.geometry.y_offset = (int) (d2 * PANGO_SCALE); |
793 | |
794 | if (gtk_css_parser_try_ident (self: parser, ident: "same-cluster" )) |
795 | gi.attr.is_cluster_start = 0; |
796 | else |
797 | gi.attr.is_cluster_start = 1; |
798 | |
799 | if (gtk_css_parser_try_ident (self: parser, ident: "color" )) |
800 | gi.attr.is_color = 1; |
801 | else |
802 | gi.attr.is_color = 0; |
803 | } |
804 | |
805 | pango_glyph_string_set_size (string: glyph_string, new_len: glyph_string->num_glyphs + 1); |
806 | glyph_string->glyphs[glyph_string->num_glyphs - 1] = gi; |
807 | } |
808 | } |
809 | while (gtk_css_parser_try_token (self: parser, token_type: GTK_CSS_TOKEN_COMMA)); |
810 | |
811 | *((PangoGlyphString **)out_glyphs) = glyph_string; |
812 | |
813 | return TRUE; |
814 | } |
815 | |
816 | static void |
817 | clear_glyphs (gpointer inout_glyphs) |
818 | { |
819 | g_clear_pointer ((PangoGlyphString **) inout_glyphs, pango_glyph_string_free); |
820 | } |
821 | |
822 | static gboolean |
823 | parse_node (GtkCssParser *parser, gpointer out_node); |
824 | |
825 | static void |
826 | clear_node (gpointer inout_node) |
827 | { |
828 | g_clear_pointer ((GskRenderNode **) inout_node, gsk_render_node_unref); |
829 | } |
830 | |
831 | static GskRenderNode * |
832 | parse_container_node (GtkCssParser *parser) |
833 | { |
834 | GPtrArray *nodes; |
835 | const GtkCssToken *token; |
836 | GskRenderNode *node; |
837 | |
838 | nodes = g_ptr_array_new_with_free_func (element_free_func: (GDestroyNotify) gsk_render_node_unref); |
839 | |
840 | for (token = gtk_css_parser_get_token (self: parser); |
841 | !gtk_css_token_is (token, GTK_CSS_TOKEN_EOF); |
842 | token = gtk_css_parser_get_token (self: parser)) |
843 | { |
844 | node = NULL; |
845 | /* We don't want a semicolon here, but the parse_node function will figure |
846 | * that out itself and return an error if we encounter one. |
847 | */ |
848 | gtk_css_parser_start_semicolon_block (self: parser, alternative_token: GTK_CSS_TOKEN_OPEN_CURLY); |
849 | |
850 | if (parse_node (parser, out_node: &node)) |
851 | g_ptr_array_add (array: nodes, data: node); |
852 | |
853 | gtk_css_parser_end_block (self: parser); |
854 | } |
855 | |
856 | node = gsk_container_node_new (children: (GskRenderNode **) nodes->pdata, n_children: nodes->len); |
857 | |
858 | g_ptr_array_unref (array: nodes); |
859 | |
860 | return node; |
861 | } |
862 | |
863 | static guint |
864 | parse_declarations (GtkCssParser *parser, |
865 | const Declaration *declarations, |
866 | guint n_declarations) |
867 | { |
868 | guint parsed = 0; |
869 | guint i; |
870 | |
871 | g_assert (n_declarations < 8 * sizeof (guint)); |
872 | |
873 | while (!gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_EOF)) |
874 | { |
875 | gtk_css_parser_start_semicolon_block (self: parser, alternative_token: GTK_CSS_TOKEN_OPEN_CURLY); |
876 | |
877 | for (i = 0; i < n_declarations; i++) |
878 | { |
879 | if (gtk_css_parser_try_ident (self: parser, ident: declarations[i].name)) |
880 | { |
881 | if (!gtk_css_parser_try_token (self: parser, token_type: GTK_CSS_TOKEN_COLON)) |
882 | { |
883 | gtk_css_parser_error_syntax (self: parser, format: "Expected ':' after variable declaration" ); |
884 | } |
885 | else |
886 | { |
887 | if (parsed & (1 << i)) |
888 | { |
889 | gtk_css_parser_warn_syntax (self: parser, format: "Variable \"%s\" defined multiple times" , declarations[i].name); |
890 | /* Unset, just to be sure */ |
891 | parsed &= ~(1 << i); |
892 | if (declarations[i].clear_func) |
893 | declarations[i].clear_func (declarations[i].result); |
894 | } |
895 | if (!declarations[i].parse_func (parser, declarations[i].result)) |
896 | { |
897 | /* nothing to do */ |
898 | } |
899 | else if (!gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_EOF)) |
900 | { |
901 | gtk_css_parser_error_syntax (self: parser, format: "Expected ';' at end of statement" ); |
902 | if (declarations[i].clear_func) |
903 | declarations[i].clear_func (declarations[i].result); |
904 | } |
905 | else |
906 | { |
907 | parsed |= (1 << i); |
908 | } |
909 | } |
910 | break; |
911 | } |
912 | } |
913 | if (i == n_declarations) |
914 | { |
915 | if (gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_IDENT)) |
916 | gtk_css_parser_error_syntax (self: parser, format: "No variable named \"%s\"" , |
917 | gtk_css_parser_get_token (self: parser)->string.string); |
918 | else |
919 | gtk_css_parser_error_syntax (self: parser, format: "Expected a variable name" ); |
920 | } |
921 | |
922 | gtk_css_parser_end_block (self: parser); |
923 | } |
924 | |
925 | return parsed; |
926 | } |
927 | |
928 | static GdkTexture * |
929 | create_default_texture (void) |
930 | { |
931 | static const guint32 pixels[100] = { |
932 | 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0, 0, 0, 0, 0, |
933 | 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0, 0, 0, 0, 0, |
934 | 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0, 0, 0, 0, 0, |
935 | 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0, 0, 0, 0, 0, |
936 | 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0, 0, 0, 0, 0, |
937 | 0, 0, 0, 0, 0, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, |
938 | 0, 0, 0, 0, 0, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, |
939 | 0, 0, 0, 0, 0, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, |
940 | 0, 0, 0, 0, 0, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, |
941 | 0, 0, 0, 0, 0, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC }; |
942 | GBytes *bytes; |
943 | GdkTexture *texture; |
944 | |
945 | bytes = g_bytes_new_static (data: (guchar *) pixels, size: 400); |
946 | texture = gdk_memory_texture_new (width: 10, height: 10, GDK_MEMORY_DEFAULT, bytes, stride: 40); |
947 | g_bytes_unref (bytes); |
948 | |
949 | return texture; |
950 | } |
951 | |
952 | static GskRenderNode * |
953 | create_default_render_node (void) |
954 | { |
955 | return gsk_color_node_new (rgba: &GDK_RGBA("FF00CC" ), bounds: &GRAPHENE_RECT_INIT (0, 0, 50, 50)); |
956 | } |
957 | |
958 | static GskRenderNode * |
959 | parse_color_node (GtkCssParser *parser) |
960 | { |
961 | graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50); |
962 | GdkRGBA color = GDK_RGBA("FF00CC" ); |
963 | const Declaration declarations[] = { |
964 | { "bounds" , parse_rect, NULL, &bounds }, |
965 | { "color" , parse_color, NULL, &color }, |
966 | }; |
967 | |
968 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
969 | |
970 | return gsk_color_node_new (rgba: &color, bounds: &bounds); |
971 | } |
972 | |
973 | static GskRenderNode * |
974 | parse_linear_gradient_node_internal (GtkCssParser *parser, |
975 | gboolean repeating) |
976 | { |
977 | graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50); |
978 | graphene_point_t start = GRAPHENE_POINT_INIT (0, 0); |
979 | graphene_point_t end = GRAPHENE_POINT_INIT (0, 50); |
980 | GArray *stops = NULL; |
981 | const Declaration declarations[] = { |
982 | { "bounds" , parse_rect, NULL, &bounds }, |
983 | { "start" , parse_point, NULL, &start }, |
984 | { "end" , parse_point, NULL, &end }, |
985 | { "stops" , parse_stops, clear_stops, &stops }, |
986 | }; |
987 | GskRenderNode *result; |
988 | |
989 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
990 | if (stops == NULL) |
991 | { |
992 | GskColorStop from = { 0.0, GDK_RGBA("AAFF00" ) }; |
993 | GskColorStop to = { 1.0, GDK_RGBA("FF00CC" ) }; |
994 | |
995 | stops = g_array_new (FALSE, FALSE, element_size: sizeof (GskColorStop)); |
996 | g_array_append_val (stops, from); |
997 | g_array_append_val (stops, to); |
998 | } |
999 | |
1000 | if (repeating) |
1001 | result = gsk_repeating_linear_gradient_node_new (bounds: &bounds, start: &start, end: &end, color_stops: (GskColorStop *) stops->data, n_color_stops: stops->len); |
1002 | else |
1003 | result = gsk_linear_gradient_node_new (bounds: &bounds, start: &start, end: &end, color_stops: (GskColorStop *) stops->data, n_color_stops: stops->len); |
1004 | |
1005 | g_array_free (array: stops, TRUE); |
1006 | |
1007 | return result; |
1008 | } |
1009 | |
1010 | static GskRenderNode * |
1011 | parse_linear_gradient_node (GtkCssParser *parser) |
1012 | { |
1013 | return parse_linear_gradient_node_internal (parser, FALSE); |
1014 | } |
1015 | |
1016 | static GskRenderNode * |
1017 | parse_repeating_linear_gradient_node (GtkCssParser *parser) |
1018 | { |
1019 | return parse_linear_gradient_node_internal (parser, TRUE); |
1020 | } |
1021 | |
1022 | static GskRenderNode * |
1023 | parse_radial_gradient_node_internal (GtkCssParser *parser, |
1024 | gboolean repeating) |
1025 | { |
1026 | graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50); |
1027 | graphene_point_t center = GRAPHENE_POINT_INIT (25, 25); |
1028 | double hradius = 25.0; |
1029 | double vradius = 25.0; |
1030 | double start = 0; |
1031 | double end = 1.0; |
1032 | GArray *stops = NULL; |
1033 | const Declaration declarations[] = { |
1034 | { "bounds" , parse_rect, NULL, &bounds }, |
1035 | { "center" , parse_point, NULL, ¢er }, |
1036 | { "hradius" , parse_double, NULL, &hradius }, |
1037 | { "vradius" , parse_double, NULL, &vradius }, |
1038 | { "start" , parse_double, NULL, &start }, |
1039 | { "end" , parse_double, NULL, &end }, |
1040 | { "stops" , parse_stops, clear_stops, &stops }, |
1041 | }; |
1042 | GskRenderNode *result; |
1043 | |
1044 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1045 | if (stops == NULL) |
1046 | { |
1047 | GskColorStop from = { 0.0, GDK_RGBA("AAFF00" ) }; |
1048 | GskColorStop to = { 1.0, GDK_RGBA("FF00CC" ) }; |
1049 | |
1050 | stops = g_array_new (FALSE, FALSE, element_size: sizeof (GskColorStop)); |
1051 | g_array_append_val (stops, from); |
1052 | g_array_append_val (stops, to); |
1053 | } |
1054 | |
1055 | if (repeating) |
1056 | result = gsk_repeating_radial_gradient_node_new (bounds: &bounds, center: ¢er, hradius, vradius, start, end, |
1057 | color_stops: (GskColorStop *) stops->data, n_color_stops: stops->len); |
1058 | else |
1059 | result = gsk_radial_gradient_node_new (bounds: &bounds, center: ¢er, hradius, vradius, start, end, |
1060 | color_stops: (GskColorStop *) stops->data, n_color_stops: stops->len); |
1061 | |
1062 | g_array_free (array: stops, TRUE); |
1063 | |
1064 | return result; |
1065 | } |
1066 | |
1067 | static GskRenderNode * |
1068 | parse_radial_gradient_node (GtkCssParser *parser) |
1069 | { |
1070 | return parse_radial_gradient_node_internal (parser, FALSE); |
1071 | } |
1072 | |
1073 | static GskRenderNode * |
1074 | parse_repeating_radial_gradient_node (GtkCssParser *parser) |
1075 | { |
1076 | return parse_radial_gradient_node_internal (parser, TRUE); |
1077 | } |
1078 | |
1079 | static GskRenderNode * |
1080 | parse_conic_gradient_node (GtkCssParser *parser) |
1081 | { |
1082 | graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50); |
1083 | graphene_point_t center = GRAPHENE_POINT_INIT (25, 25); |
1084 | double rotation = 0.0; |
1085 | GArray *stops = NULL; |
1086 | const Declaration declarations[] = { |
1087 | { "bounds" , parse_rect, NULL, &bounds }, |
1088 | { "center" , parse_point, NULL, ¢er }, |
1089 | { "rotation" , parse_double, NULL, &rotation }, |
1090 | { "stops" , parse_stops, clear_stops, &stops }, |
1091 | }; |
1092 | GskRenderNode *result; |
1093 | |
1094 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1095 | if (stops == NULL) |
1096 | { |
1097 | GskColorStop from = { 0.0, GDK_RGBA("AAFF00" ) }; |
1098 | GskColorStop to = { 1.0, GDK_RGBA("FF00CC" ) }; |
1099 | |
1100 | stops = g_array_new (FALSE, FALSE, element_size: sizeof (GskColorStop)); |
1101 | g_array_append_val (stops, from); |
1102 | g_array_append_val (stops, to); |
1103 | } |
1104 | |
1105 | result = gsk_conic_gradient_node_new (bounds: &bounds, center: ¢er, rotation, |
1106 | color_stops: (GskColorStop *) stops->data, n_color_stops: stops->len); |
1107 | |
1108 | g_array_free (array: stops, TRUE); |
1109 | |
1110 | return result; |
1111 | } |
1112 | |
1113 | static GskRenderNode * |
1114 | parse_inset_shadow_node (GtkCssParser *parser) |
1115 | { |
1116 | GskRoundedRect outline = GSK_ROUNDED_RECT_INIT (0, 0, 50, 50); |
1117 | GdkRGBA color = GDK_RGBA("000000" ); |
1118 | double dx = 1, dy = 1, blur = 0, spread = 0; |
1119 | const Declaration declarations[] = { |
1120 | { "outline" , parse_rounded_rect, NULL, &outline }, |
1121 | { "color" , parse_color, NULL, &color }, |
1122 | { "dx" , parse_double, NULL, &dx }, |
1123 | { "dy" , parse_double, NULL, &dy }, |
1124 | { "spread" , parse_double, NULL, &spread }, |
1125 | { "blur" , parse_double, NULL, &blur } |
1126 | }; |
1127 | |
1128 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1129 | |
1130 | return gsk_inset_shadow_node_new (outline: &outline, color: &color, dx, dy, spread, blur_radius: blur); |
1131 | } |
1132 | |
1133 | typedef union { |
1134 | gint32 i; |
1135 | double v[4]; |
1136 | } UniformValue; |
1137 | |
1138 | typedef struct { |
1139 | GskGLShader *shader; |
1140 | GskShaderArgsBuilder *args; |
1141 | } ShaderInfo; |
1142 | |
1143 | static void |
1144 | clear_shader_info (gpointer data) |
1145 | { |
1146 | ShaderInfo *info = data; |
1147 | g_clear_object (&info->shader); |
1148 | g_clear_pointer (&info->args, gsk_shader_args_builder_unref); |
1149 | } |
1150 | |
1151 | static gboolean |
1152 | parse_shader (GtkCssParser *parser, |
1153 | gpointer out_shader_info) |
1154 | { |
1155 | ShaderInfo *shader_info = out_shader_info; |
1156 | char *sourcecode = NULL; |
1157 | GBytes *bytes; |
1158 | GskGLShader *shader; |
1159 | |
1160 | if (!parse_string (parser, out_string: &sourcecode)) |
1161 | return FALSE; |
1162 | |
1163 | bytes = g_bytes_new_take (data: sourcecode, size: strlen (s: sourcecode)); |
1164 | shader = gsk_gl_shader_new_from_bytes (sourcecode: bytes); |
1165 | g_bytes_unref (bytes); |
1166 | |
1167 | shader_info->shader = shader; |
1168 | |
1169 | return TRUE; |
1170 | } |
1171 | |
1172 | static gboolean |
1173 | parse_uniform_value (GtkCssParser *parser, |
1174 | int idx, |
1175 | ShaderInfo *shader_info) |
1176 | { |
1177 | switch (gsk_gl_shader_get_uniform_type (shader: shader_info->shader, idx)) |
1178 | { |
1179 | case GSK_GL_UNIFORM_TYPE_FLOAT: |
1180 | { |
1181 | double f; |
1182 | if (!gtk_css_parser_consume_number (self: parser, number: &f)) |
1183 | return FALSE; |
1184 | gsk_shader_args_builder_set_float (builder: shader_info->args, idx, value: f); |
1185 | } |
1186 | break; |
1187 | |
1188 | case GSK_GL_UNIFORM_TYPE_INT: |
1189 | { |
1190 | int i; |
1191 | if (!gtk_css_parser_consume_integer (self: parser, number: &i)) |
1192 | return FALSE; |
1193 | gsk_shader_args_builder_set_int (builder: shader_info->args, idx, value: i); |
1194 | } |
1195 | break; |
1196 | |
1197 | case GSK_GL_UNIFORM_TYPE_UINT: |
1198 | { |
1199 | int i; |
1200 | if (!gtk_css_parser_consume_integer (self: parser, number: &i) || i < 0) |
1201 | return FALSE; |
1202 | gsk_shader_args_builder_set_uint (builder: shader_info->args, idx, value: i); |
1203 | } |
1204 | break; |
1205 | |
1206 | case GSK_GL_UNIFORM_TYPE_BOOL: |
1207 | { |
1208 | int i; |
1209 | if (!gtk_css_parser_consume_integer (self: parser, number: &i) || (i != 0 && i != 1)) |
1210 | return FALSE; |
1211 | gsk_shader_args_builder_set_bool (builder: shader_info->args, idx, value: i); |
1212 | } |
1213 | break; |
1214 | |
1215 | case GSK_GL_UNIFORM_TYPE_VEC2: |
1216 | { |
1217 | double f0, f1; |
1218 | graphene_vec2_t v; |
1219 | if (!gtk_css_parser_consume_number (self: parser, number: &f0) || |
1220 | !gtk_css_parser_consume_number (self: parser, number: &f1)) |
1221 | return FALSE; |
1222 | graphene_vec2_init (v: &v, x: f0, y: f1); |
1223 | gsk_shader_args_builder_set_vec2 (builder: shader_info->args, idx, value: &v); |
1224 | } |
1225 | break; |
1226 | |
1227 | case GSK_GL_UNIFORM_TYPE_VEC3: |
1228 | { |
1229 | double f0, f1, f2; |
1230 | graphene_vec3_t v; |
1231 | if (!gtk_css_parser_consume_number (self: parser, number: &f0) || |
1232 | !gtk_css_parser_consume_number (self: parser, number: &f1) || |
1233 | !gtk_css_parser_consume_number (self: parser, number: &f2)) |
1234 | return FALSE; |
1235 | graphene_vec3_init (v: &v, x: f0, y: f1, z: f2); |
1236 | gsk_shader_args_builder_set_vec3 (builder: shader_info->args, idx, value: &v); |
1237 | } |
1238 | break; |
1239 | |
1240 | case GSK_GL_UNIFORM_TYPE_VEC4: |
1241 | { |
1242 | double f0, f1, f2, f3; |
1243 | graphene_vec4_t v; |
1244 | if (!gtk_css_parser_consume_number (self: parser, number: &f0) || |
1245 | !gtk_css_parser_consume_number (self: parser, number: &f1) || |
1246 | !gtk_css_parser_consume_number (self: parser, number: &f2) || |
1247 | !gtk_css_parser_consume_number (self: parser, number: &f3)) |
1248 | return FALSE; |
1249 | graphene_vec4_init (v: &v, x: f0, y: f1, z: f2, w: f3); |
1250 | gsk_shader_args_builder_set_vec4 (builder: shader_info->args, idx, value: &v); |
1251 | } |
1252 | break; |
1253 | |
1254 | case GSK_GL_UNIFORM_TYPE_NONE: |
1255 | default: |
1256 | g_assert_not_reached (); |
1257 | break; |
1258 | } |
1259 | |
1260 | if (idx < gsk_gl_shader_get_n_uniforms (shader: shader_info->shader)) |
1261 | { |
1262 | if (!gtk_css_parser_try_token (self: parser, token_type: GTK_CSS_TOKEN_COMMA)) |
1263 | return FALSE; |
1264 | } |
1265 | |
1266 | return TRUE; |
1267 | } |
1268 | |
1269 | static gboolean |
1270 | parse_shader_args (GtkCssParser *parser, gpointer data) |
1271 | { |
1272 | ShaderInfo *shader_info = data; |
1273 | int n_uniforms; |
1274 | int i; |
1275 | |
1276 | shader_info->args = gsk_shader_args_builder_new (shader: shader_info->shader, NULL); |
1277 | n_uniforms = gsk_gl_shader_get_n_uniforms (shader: shader_info->shader); |
1278 | |
1279 | for (i = 0; i < n_uniforms; i++) |
1280 | { |
1281 | if (!parse_uniform_value (parser, idx: i, shader_info)) |
1282 | return FALSE; |
1283 | } |
1284 | |
1285 | return TRUE; |
1286 | } |
1287 | |
1288 | static GskRenderNode * |
1289 | parse_glshader_node (GtkCssParser *parser) |
1290 | { |
1291 | graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50); |
1292 | GskRenderNode *child[4] = { NULL, }; |
1293 | ShaderInfo shader_info = { |
1294 | NULL, |
1295 | NULL, |
1296 | }; |
1297 | const Declaration declarations[] = { |
1298 | { "bounds" , parse_rect, NULL, &bounds }, |
1299 | { "sourcecode" , parse_shader, NULL, &shader_info }, |
1300 | { "args" , parse_shader_args, clear_shader_info, &shader_info }, |
1301 | { "child1" , parse_node, clear_node, &child[0] }, |
1302 | { "child2" , parse_node, clear_node, &child[1] }, |
1303 | { "child3" , parse_node, clear_node, &child[2] }, |
1304 | { "child4" , parse_node, clear_node, &child[3] }, |
1305 | }; |
1306 | GskGLShader *shader; |
1307 | GskRenderNode *node; |
1308 | GBytes *args = NULL; |
1309 | int len, i; |
1310 | |
1311 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1312 | |
1313 | for (len = 0; len < 4; len++) |
1314 | { |
1315 | if (child[len] == NULL) |
1316 | break; |
1317 | } |
1318 | |
1319 | shader = shader_info.shader; |
1320 | args = gsk_shader_args_builder_free_to_args (builder: shader_info.args); |
1321 | |
1322 | node = gsk_gl_shader_node_new (shader, bounds: &bounds, args, children: child, n_children: len); |
1323 | |
1324 | g_bytes_unref (bytes: args); |
1325 | g_object_unref (object: shader); |
1326 | |
1327 | for (i = 0; i < 4; i++) |
1328 | { |
1329 | if (child[i]) |
1330 | gsk_render_node_unref (node: child[i]); |
1331 | } |
1332 | |
1333 | return node; |
1334 | } |
1335 | |
1336 | static GskRenderNode * |
1337 | parse_border_node (GtkCssParser *parser) |
1338 | { |
1339 | GskRoundedRect outline = GSK_ROUNDED_RECT_INIT (0, 0, 50, 50); |
1340 | float widths[4] = { 1, 1, 1, 1 }; |
1341 | GdkRGBA colors[4] = { GDK_RGBA("000" ), GDK_RGBA("000" ), GDK_RGBA("000" ), GDK_RGBA("000" ) }; |
1342 | const Declaration declarations[] = { |
1343 | { "outline" , parse_rounded_rect, NULL, &outline }, |
1344 | { "widths" , parse_float4, NULL, &widths }, |
1345 | { "colors" , parse_colors4, NULL, &colors } |
1346 | }; |
1347 | |
1348 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1349 | |
1350 | return gsk_border_node_new (outline: &outline, border_width: widths, border_color: colors); |
1351 | } |
1352 | |
1353 | static GskRenderNode * |
1354 | parse_texture_node (GtkCssParser *parser) |
1355 | { |
1356 | graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50); |
1357 | GdkTexture *texture = NULL; |
1358 | const Declaration declarations[] = { |
1359 | { "bounds" , parse_rect, NULL, &bounds }, |
1360 | { "texture" , parse_texture, clear_texture, &texture } |
1361 | }; |
1362 | GskRenderNode *node; |
1363 | |
1364 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1365 | |
1366 | if (texture == NULL) |
1367 | texture = create_default_texture (); |
1368 | |
1369 | node = gsk_texture_node_new (texture, bounds: &bounds); |
1370 | g_object_unref (object: texture); |
1371 | |
1372 | return node; |
1373 | } |
1374 | |
1375 | static GskRenderNode * |
1376 | parse_cairo_node (GtkCssParser *parser) |
1377 | { |
1378 | graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50); |
1379 | GdkTexture *pixels = NULL; |
1380 | cairo_surface_t *surface = NULL; |
1381 | const Declaration declarations[] = { |
1382 | { "bounds" , parse_rect, NULL, &bounds }, |
1383 | { "pixels" , parse_texture, clear_texture, &pixels }, |
1384 | { "script" , parse_script, clear_surface, &surface } |
1385 | }; |
1386 | GskRenderNode *node; |
1387 | |
1388 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1389 | |
1390 | node = gsk_cairo_node_new (bounds: &bounds); |
1391 | |
1392 | if (surface != NULL) |
1393 | { |
1394 | cairo_t *cr = gsk_cairo_node_get_draw_context (node); |
1395 | cairo_set_source_surface (cr, surface, x: 0, y: 0); |
1396 | cairo_paint (cr); |
1397 | cairo_destroy (cr); |
1398 | } |
1399 | else if (pixels != NULL) |
1400 | { |
1401 | cairo_t *cr = gsk_cairo_node_get_draw_context (node); |
1402 | surface = gdk_texture_download_surface (texture: pixels); |
1403 | cairo_set_source_surface (cr, surface, x: 0, y: 0); |
1404 | cairo_paint (cr); |
1405 | cairo_destroy (cr); |
1406 | } |
1407 | else |
1408 | { |
1409 | /* do nothing */ |
1410 | } |
1411 | |
1412 | g_clear_object (&pixels); |
1413 | g_clear_pointer (&surface, cairo_surface_destroy); |
1414 | |
1415 | return node; |
1416 | } |
1417 | |
1418 | static GskRenderNode * |
1419 | parse_outset_shadow_node (GtkCssParser *parser) |
1420 | { |
1421 | GskRoundedRect outline = GSK_ROUNDED_RECT_INIT (0, 0, 50, 50); |
1422 | GdkRGBA color = GDK_RGBA("000000" ); |
1423 | double dx = 1, dy = 1, blur = 0, spread = 0; |
1424 | const Declaration declarations[] = { |
1425 | { "outline" , parse_rounded_rect, NULL, &outline }, |
1426 | { "color" , parse_color, NULL, &color }, |
1427 | { "dx" , parse_double, NULL, &dx }, |
1428 | { "dy" , parse_double, NULL, &dy }, |
1429 | { "spread" , parse_double, NULL, &spread }, |
1430 | { "blur" , parse_double, NULL, &blur } |
1431 | }; |
1432 | |
1433 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1434 | |
1435 | return gsk_outset_shadow_node_new (outline: &outline, color: &color, dx, dy, spread, blur_radius: blur); |
1436 | } |
1437 | |
1438 | static GskRenderNode * |
1439 | parse_transform_node (GtkCssParser *parser) |
1440 | { |
1441 | GskRenderNode *child = NULL; |
1442 | GskTransform *transform = NULL; |
1443 | const Declaration declarations[] = { |
1444 | { "transform" , parse_transform, clear_transform, &transform }, |
1445 | { "child" , parse_node, clear_node, &child }, |
1446 | }; |
1447 | GskRenderNode *result; |
1448 | |
1449 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1450 | if (child == NULL) |
1451 | child = create_default_render_node (); |
1452 | |
1453 | /* This is very much cheating, isn't it? */ |
1454 | if (transform == NULL) |
1455 | transform = gsk_transform_new (); |
1456 | |
1457 | result = gsk_transform_node_new (child, transform); |
1458 | |
1459 | gsk_render_node_unref (node: child); |
1460 | gsk_transform_unref (self: transform); |
1461 | |
1462 | return result; |
1463 | } |
1464 | |
1465 | static GskRenderNode * |
1466 | parse_opacity_node (GtkCssParser *parser) |
1467 | { |
1468 | GskRenderNode *child = NULL; |
1469 | double opacity = 0.5; |
1470 | const Declaration declarations[] = { |
1471 | { "opacity" , parse_double, NULL, &opacity }, |
1472 | { "child" , parse_node, clear_node, &child }, |
1473 | }; |
1474 | GskRenderNode *result; |
1475 | |
1476 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1477 | if (child == NULL) |
1478 | child = create_default_render_node (); |
1479 | |
1480 | result = gsk_opacity_node_new (child, opacity); |
1481 | |
1482 | gsk_render_node_unref (node: child); |
1483 | |
1484 | return result; |
1485 | } |
1486 | |
1487 | static GskRenderNode * |
1488 | parse_color_matrix_node (GtkCssParser *parser) |
1489 | { |
1490 | GskRenderNode *child = NULL; |
1491 | graphene_matrix_t matrix; |
1492 | GskTransform *transform = NULL; |
1493 | graphene_rect_t offset_rect = GRAPHENE_RECT_INIT (0, 0, 0, 0); |
1494 | graphene_vec4_t offset; |
1495 | const Declaration declarations[] = { |
1496 | { "matrix" , parse_transform, clear_transform, &transform }, |
1497 | { "offset" , parse_rect, NULL, &offset_rect }, |
1498 | { "child" , parse_node, clear_node, &child } |
1499 | }; |
1500 | GskRenderNode *result; |
1501 | |
1502 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1503 | if (child == NULL) |
1504 | child = create_default_render_node (); |
1505 | |
1506 | graphene_vec4_init (v: &offset, |
1507 | x: offset_rect.origin.x, y: offset_rect.origin.y, |
1508 | z: offset_rect.size.width, w: offset_rect.size.height); |
1509 | |
1510 | gsk_transform_to_matrix (self: transform, out_matrix: &matrix); |
1511 | |
1512 | result = gsk_color_matrix_node_new (child, color_matrix: &matrix, color_offset: &offset); |
1513 | |
1514 | gsk_transform_unref (self: transform); |
1515 | gsk_render_node_unref (node: child); |
1516 | |
1517 | return result; |
1518 | } |
1519 | |
1520 | static GskRenderNode * |
1521 | parse_cross_fade_node (GtkCssParser *parser) |
1522 | { |
1523 | GskRenderNode *start = NULL; |
1524 | GskRenderNode *end = NULL; |
1525 | double progress = 0.5; |
1526 | const Declaration declarations[] = { |
1527 | { "progress" , parse_double, NULL, &progress }, |
1528 | { "start" , parse_node, clear_node, &start }, |
1529 | { "end" , parse_node, clear_node, &end }, |
1530 | }; |
1531 | GskRenderNode *result; |
1532 | |
1533 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1534 | if (start == NULL) |
1535 | start = gsk_color_node_new (rgba: &GDK_RGBA("AAFF00" ), bounds: &GRAPHENE_RECT_INIT (0, 0, 50, 50)); |
1536 | if (end == NULL) |
1537 | end = create_default_render_node (); |
1538 | |
1539 | result = gsk_cross_fade_node_new (start, end, progress); |
1540 | |
1541 | gsk_render_node_unref (node: start); |
1542 | gsk_render_node_unref (node: end); |
1543 | |
1544 | return result; |
1545 | } |
1546 | |
1547 | static GskRenderNode * |
1548 | parse_blend_node (GtkCssParser *parser) |
1549 | { |
1550 | GskRenderNode *bottom = NULL; |
1551 | GskRenderNode *top = NULL; |
1552 | GskBlendMode mode = GSK_BLEND_MODE_DEFAULT; |
1553 | const Declaration declarations[] = { |
1554 | { "mode" , parse_blend_mode, NULL, &mode }, |
1555 | { "bottom" , parse_node, clear_node, &bottom }, |
1556 | { "top" , parse_node, clear_node, &top }, |
1557 | }; |
1558 | GskRenderNode *result; |
1559 | |
1560 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1561 | if (bottom == NULL) |
1562 | bottom = gsk_color_node_new (rgba: &GDK_RGBA("AAFF00" ), bounds: &GRAPHENE_RECT_INIT (0, 0, 50, 50)); |
1563 | if (top == NULL) |
1564 | top = create_default_render_node (); |
1565 | |
1566 | result = gsk_blend_node_new (bottom, top, blend_mode: mode); |
1567 | |
1568 | gsk_render_node_unref (node: bottom); |
1569 | gsk_render_node_unref (node: top); |
1570 | |
1571 | return result; |
1572 | } |
1573 | |
1574 | static GskRenderNode * |
1575 | parse_repeat_node (GtkCssParser *parser) |
1576 | { |
1577 | GskRenderNode *child = NULL; |
1578 | graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 0, 0); |
1579 | graphene_rect_t child_bounds = GRAPHENE_RECT_INIT (0, 0, 0, 0); |
1580 | const Declaration declarations[] = { |
1581 | { "child" , parse_node, clear_node, &child }, |
1582 | { "bounds" , parse_rect, NULL, &bounds }, |
1583 | { "child-bounds" , parse_rect, NULL, &child_bounds }, |
1584 | }; |
1585 | GskRenderNode *result; |
1586 | guint parse_result; |
1587 | |
1588 | parse_result = parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1589 | if (child == NULL) |
1590 | child = create_default_render_node (); |
1591 | |
1592 | if (!(parse_result & (1 << 1))) |
1593 | gsk_render_node_get_bounds (node: child, bounds: &bounds); |
1594 | if (!(parse_result & (1 << 2))) |
1595 | gsk_render_node_get_bounds (node: child, bounds: &child_bounds); |
1596 | |
1597 | result = gsk_repeat_node_new (bounds: &bounds, child, child_bounds: &child_bounds); |
1598 | |
1599 | gsk_render_node_unref (node: child); |
1600 | |
1601 | return result; |
1602 | } |
1603 | |
1604 | static gboolean |
1605 | unpack_glyphs (PangoFont *font, |
1606 | PangoGlyphString *glyphs) |
1607 | { |
1608 | PangoGlyphString *ascii = NULL; |
1609 | guint i; |
1610 | |
1611 | for (i = 0; i < glyphs->num_glyphs; i++) |
1612 | { |
1613 | PangoGlyph glyph = glyphs->glyphs[i].glyph; |
1614 | |
1615 | if (glyph < PANGO_GLYPH_INVALID_INPUT - MAX_ASCII_GLYPH || |
1616 | glyph >= PANGO_GLYPH_INVALID_INPUT) |
1617 | continue; |
1618 | |
1619 | glyph = glyph - (PANGO_GLYPH_INVALID_INPUT - MAX_ASCII_GLYPH) - MIN_ASCII_GLYPH; |
1620 | |
1621 | if (ascii == NULL) |
1622 | { |
1623 | ascii = create_ascii_glyphs (font); |
1624 | if (ascii == NULL) |
1625 | return FALSE; |
1626 | } |
1627 | |
1628 | glyphs->glyphs[i].glyph = ascii->glyphs[glyph].glyph; |
1629 | glyphs->glyphs[i].geometry.width = ascii->glyphs[glyph].geometry.width; |
1630 | } |
1631 | |
1632 | g_clear_pointer (&ascii, pango_glyph_string_free); |
1633 | |
1634 | return TRUE; |
1635 | } |
1636 | |
1637 | static GskRenderNode * |
1638 | parse_text_node (GtkCssParser *parser) |
1639 | { |
1640 | PangoFont *font = NULL; |
1641 | graphene_point_t offset = GRAPHENE_POINT_INIT (0, 0); |
1642 | GdkRGBA color = GDK_RGBA("000000" ); |
1643 | PangoGlyphString *glyphs = NULL; |
1644 | const Declaration declarations[] = { |
1645 | { "font" , parse_font, clear_font, &font }, |
1646 | { "offset" , parse_point, NULL, &offset }, |
1647 | { "color" , parse_color, NULL, &color }, |
1648 | { "glyphs" , parse_glyphs, clear_glyphs, &glyphs } |
1649 | }; |
1650 | GskRenderNode *result; |
1651 | |
1652 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1653 | |
1654 | if (font == NULL) |
1655 | { |
1656 | font = font_from_string (string: "Cantarell 11" ); |
1657 | g_assert (font); |
1658 | } |
1659 | |
1660 | if (!glyphs) |
1661 | { |
1662 | const char *text = "Hello" ; |
1663 | PangoGlyphInfo gi = { 0, { 0, 0, 0}, { 1 } }; |
1664 | guint i; |
1665 | |
1666 | glyphs = pango_glyph_string_new (); |
1667 | pango_glyph_string_set_size (string: glyphs, new_len: strlen (s: text)); |
1668 | for (i = 0; i < strlen (s: text); i++) |
1669 | { |
1670 | gi.glyph = PANGO_GLYPH_INVALID_INPUT - MAX_ASCII_GLYPH + text[i]; |
1671 | glyphs->glyphs[i] = gi; |
1672 | } |
1673 | } |
1674 | |
1675 | if (!unpack_glyphs (font, glyphs)) |
1676 | { |
1677 | gtk_css_parser_error_value (self: parser, format: "Given font cannot decode the glyph text" ); |
1678 | result = NULL; |
1679 | } |
1680 | else |
1681 | { |
1682 | result = gsk_text_node_new (font, glyphs, color: &color, offset: &offset); |
1683 | if (result == NULL) |
1684 | { |
1685 | gtk_css_parser_error_value (self: parser, format: "Glyphs result in empty text" ); |
1686 | } |
1687 | } |
1688 | |
1689 | g_object_unref (object: font); |
1690 | pango_glyph_string_free (string: glyphs); |
1691 | |
1692 | /* return anything, whatever, just not NULL */ |
1693 | if (result == NULL) |
1694 | result = create_default_render_node (); |
1695 | |
1696 | return result; |
1697 | } |
1698 | |
1699 | static GskRenderNode * |
1700 | parse_blur_node (GtkCssParser *parser) |
1701 | { |
1702 | GskRenderNode *child = NULL; |
1703 | double blur_radius = 1.0; |
1704 | const Declaration declarations[] = { |
1705 | { "blur" , parse_double, NULL, &blur_radius }, |
1706 | { "child" , parse_node, clear_node, &child }, |
1707 | }; |
1708 | GskRenderNode *result; |
1709 | |
1710 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1711 | if (child == NULL) |
1712 | child = create_default_render_node (); |
1713 | |
1714 | result = gsk_blur_node_new (child, radius: blur_radius); |
1715 | |
1716 | gsk_render_node_unref (node: child); |
1717 | |
1718 | return result; |
1719 | } |
1720 | |
1721 | static GskRenderNode * |
1722 | parse_clip_node (GtkCssParser *parser) |
1723 | { |
1724 | GskRoundedRect clip = GSK_ROUNDED_RECT_INIT (0, 0, 50, 50); |
1725 | GskRenderNode *child = NULL; |
1726 | const Declaration declarations[] = { |
1727 | { "clip" , parse_rounded_rect, NULL, &clip }, |
1728 | { "child" , parse_node, clear_node, &child }, |
1729 | }; |
1730 | GskRenderNode *result; |
1731 | |
1732 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1733 | if (child == NULL) |
1734 | child = create_default_render_node (); |
1735 | |
1736 | if (gsk_rounded_rect_is_rectilinear (self: &clip)) |
1737 | result = gsk_clip_node_new (child, clip: &clip.bounds); |
1738 | else |
1739 | result = gsk_rounded_clip_node_new (child, clip: &clip); |
1740 | |
1741 | gsk_render_node_unref (node: child); |
1742 | |
1743 | return result; |
1744 | } |
1745 | |
1746 | static GskRenderNode * |
1747 | parse_rounded_clip_node (GtkCssParser *parser) |
1748 | { |
1749 | GskRoundedRect clip = GSK_ROUNDED_RECT_INIT (0, 0, 50, 50); |
1750 | GskRenderNode *child = NULL; |
1751 | const Declaration declarations[] = { |
1752 | { "clip" , parse_rounded_rect, NULL, &clip }, |
1753 | { "child" , parse_node, clear_node, &child }, |
1754 | }; |
1755 | GskRenderNode *result; |
1756 | |
1757 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1758 | if (child == NULL) |
1759 | child = create_default_render_node (); |
1760 | |
1761 | result = gsk_rounded_clip_node_new (child, clip: &clip); |
1762 | |
1763 | gsk_render_node_unref (node: child); |
1764 | |
1765 | return result; |
1766 | } |
1767 | |
1768 | static GskRenderNode * |
1769 | parse_shadow_node (GtkCssParser *parser) |
1770 | { |
1771 | GskRenderNode *child = NULL; |
1772 | GArray *shadows = g_array_new (FALSE, TRUE, element_size: sizeof (GskShadow)); |
1773 | const Declaration declarations[] = { |
1774 | { "child" , parse_node, clear_node, &child }, |
1775 | { "shadows" , parse_shadows, clear_shadows, shadows } |
1776 | }; |
1777 | GskRenderNode *result; |
1778 | |
1779 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1780 | if (child == NULL) |
1781 | child = create_default_render_node (); |
1782 | |
1783 | if (shadows->len == 0) |
1784 | { |
1785 | GskShadow default_shadow = { GDK_RGBA("000000" ), 1, 1, 0 }; |
1786 | g_array_append_val (shadows, default_shadow); |
1787 | } |
1788 | |
1789 | result = gsk_shadow_node_new (child, shadows: (GskShadow *)shadows->data, n_shadows: shadows->len); |
1790 | |
1791 | g_array_free (array: shadows, TRUE); |
1792 | gsk_render_node_unref (node: child); |
1793 | |
1794 | return result; |
1795 | } |
1796 | |
1797 | static GskRenderNode * |
1798 | parse_debug_node (GtkCssParser *parser) |
1799 | { |
1800 | char *message = NULL; |
1801 | GskRenderNode *child = NULL; |
1802 | const Declaration declarations[] = { |
1803 | { "message" , parse_string, clear_string, &message}, |
1804 | { "child" , parse_node, clear_node, &child }, |
1805 | }; |
1806 | GskRenderNode *result; |
1807 | |
1808 | parse_declarations (parser, declarations, G_N_ELEMENTS(declarations)); |
1809 | if (child == NULL) |
1810 | child = create_default_render_node (); |
1811 | |
1812 | result = gsk_debug_node_new (child, message); |
1813 | |
1814 | gsk_render_node_unref (node: child); |
1815 | |
1816 | return result; |
1817 | } |
1818 | |
1819 | static gboolean |
1820 | parse_node (GtkCssParser *parser, |
1821 | gpointer out_node) |
1822 | { |
1823 | static struct { |
1824 | const char *name; |
1825 | GskRenderNode * (* func) (GtkCssParser *); |
1826 | } node_parsers[] = { |
1827 | { "blend" , parse_blend_node }, |
1828 | { "blur" , parse_blur_node }, |
1829 | { "border" , parse_border_node }, |
1830 | { "cairo" , parse_cairo_node }, |
1831 | { "clip" , parse_clip_node }, |
1832 | { "color" , parse_color_node }, |
1833 | { "color-matrix" , parse_color_matrix_node }, |
1834 | { "container" , parse_container_node }, |
1835 | { "cross-fade" , parse_cross_fade_node }, |
1836 | { "debug" , parse_debug_node }, |
1837 | { "inset-shadow" , parse_inset_shadow_node }, |
1838 | { "linear-gradient" , parse_linear_gradient_node }, |
1839 | { "radial-gradient" , parse_radial_gradient_node }, |
1840 | { "conic-gradient" , parse_conic_gradient_node }, |
1841 | { "opacity" , parse_opacity_node }, |
1842 | { "outset-shadow" , parse_outset_shadow_node }, |
1843 | { "repeat" , parse_repeat_node }, |
1844 | { "repeating-linear-gradient" , parse_repeating_linear_gradient_node }, |
1845 | { "repeating-radial-gradient" , parse_repeating_radial_gradient_node }, |
1846 | { "rounded-clip" , parse_rounded_clip_node }, |
1847 | { "shadow" , parse_shadow_node }, |
1848 | { "text" , parse_text_node }, |
1849 | { "texture" , parse_texture_node }, |
1850 | { "transform" , parse_transform_node }, |
1851 | { "glshader" , parse_glshader_node }, |
1852 | }; |
1853 | GskRenderNode **node_p = out_node; |
1854 | guint i; |
1855 | |
1856 | for (i = 0; i < G_N_ELEMENTS (node_parsers); i++) |
1857 | { |
1858 | if (gtk_css_parser_try_ident (self: parser, ident: node_parsers[i].name)) |
1859 | { |
1860 | GskRenderNode *node; |
1861 | |
1862 | if (!gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_EOF)) |
1863 | { |
1864 | gtk_css_parser_error_syntax (self: parser, format: "Expected '{' after node name" ); |
1865 | return FALSE; |
1866 | } |
1867 | gtk_css_parser_end_block_prelude (self: parser); |
1868 | node = node_parsers[i].func (parser); |
1869 | if (node) |
1870 | { |
1871 | if (!gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_EOF)) |
1872 | gtk_css_parser_error_syntax (self: parser, format: "Expected '}' at end of node definition" ); |
1873 | g_clear_pointer (node_p, gsk_render_node_unref); |
1874 | *node_p = node; |
1875 | } |
1876 | |
1877 | return node != NULL; |
1878 | } |
1879 | } |
1880 | |
1881 | if (gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_IDENT)) |
1882 | gtk_css_parser_error_value (self: parser, format: "\"%s\" is not a valid node name" , |
1883 | gtk_css_parser_get_token (self: parser)->string.string); |
1884 | else |
1885 | gtk_css_parser_error_syntax (self: parser, format: "Expected a node name" ); |
1886 | |
1887 | return FALSE; |
1888 | } |
1889 | |
1890 | static void |
1891 | gsk_render_node_parser_error (GtkCssParser *parser, |
1892 | const GtkCssLocation *start, |
1893 | const GtkCssLocation *end, |
1894 | const GError *error, |
1895 | gpointer user_data) |
1896 | { |
1897 | struct { |
1898 | GskParseErrorFunc error_func; |
1899 | gpointer user_data; |
1900 | } *error_func_pair = user_data; |
1901 | |
1902 | if (error_func_pair->error_func) |
1903 | error_func_pair->error_func ((const GskParseLocation *)start, |
1904 | (const GskParseLocation *)end, |
1905 | error, |
1906 | error_func_pair->user_data); |
1907 | } |
1908 | |
1909 | GskRenderNode * |
1910 | gsk_render_node_deserialize_from_bytes (GBytes *bytes, |
1911 | GskParseErrorFunc error_func, |
1912 | gpointer user_data) |
1913 | { |
1914 | GskRenderNode *root = NULL; |
1915 | GtkCssParser *parser; |
1916 | struct { |
1917 | GskParseErrorFunc error_func; |
1918 | gpointer user_data; |
1919 | } error_func_pair = { error_func, user_data }; |
1920 | |
1921 | parser = gtk_css_parser_new_for_bytes (bytes, NULL, error_func: gsk_render_node_parser_error, |
1922 | user_data: &error_func_pair, NULL); |
1923 | root = parse_container_node (parser); |
1924 | |
1925 | if (root && gsk_container_node_get_n_children (node: root) == 1) |
1926 | { |
1927 | GskRenderNode *child = gsk_container_node_get_child (node: root, idx: 0); |
1928 | |
1929 | gsk_render_node_ref (node: child); |
1930 | gsk_render_node_unref (node: root); |
1931 | root = child; |
1932 | } |
1933 | |
1934 | gtk_css_parser_unref (self: parser); |
1935 | |
1936 | return root; |
1937 | } |
1938 | |
1939 | |
1940 | typedef struct |
1941 | { |
1942 | int indentation_level; |
1943 | GString *str; |
1944 | } Printer; |
1945 | |
1946 | static void |
1947 | printer_init (Printer *self) |
1948 | { |
1949 | self->indentation_level = 0; |
1950 | self->str = g_string_new (NULL); |
1951 | } |
1952 | |
1953 | #define IDENT_LEVEL 2 /* Spaces per level */ |
1954 | static void |
1955 | _indent (Printer *self) |
1956 | { |
1957 | if (self->indentation_level > 0) |
1958 | g_string_append_printf (string: self->str, format: "%*s" , self->indentation_level * IDENT_LEVEL, " " ); |
1959 | } |
1960 | #undef IDENT_LEVEL |
1961 | |
1962 | static void |
1963 | start_node (Printer *self, |
1964 | const char *node_name) |
1965 | { |
1966 | g_string_append_printf (string: self->str, format: "%s {\n" , node_name); |
1967 | self->indentation_level ++; |
1968 | } |
1969 | |
1970 | static void |
1971 | end_node (Printer *self) |
1972 | { |
1973 | self->indentation_level --; |
1974 | _indent (self); |
1975 | g_string_append (string: self->str, val: "}\n" ); |
1976 | } |
1977 | |
1978 | static void |
1979 | string_append_double (GString *string, |
1980 | double d) |
1981 | { |
1982 | char buf[G_ASCII_DTOSTR_BUF_SIZE]; |
1983 | |
1984 | g_ascii_formatd (buffer: buf, G_ASCII_DTOSTR_BUF_SIZE, format: "%g" , d); |
1985 | g_string_append (string, val: buf); |
1986 | } |
1987 | |
1988 | |
1989 | static void |
1990 | append_rect (GString *str, |
1991 | const graphene_rect_t *r) |
1992 | { |
1993 | string_append_double (string: str, d: r->origin.x); |
1994 | g_string_append_c (str, ' '); |
1995 | string_append_double (string: str, d: r->origin.y); |
1996 | g_string_append_c (str, ' '); |
1997 | string_append_double (string: str, d: r->size.width); |
1998 | g_string_append_c (str, ' '); |
1999 | string_append_double (string: str, d: r->size.height); |
2000 | } |
2001 | |
2002 | static void |
2003 | append_rounded_rect (GString *str, |
2004 | const GskRoundedRect *r) |
2005 | { |
2006 | append_rect (str, r: &r->bounds); |
2007 | |
2008 | if (!gsk_rounded_rect_is_rectilinear (self: r)) |
2009 | { |
2010 | gboolean all_the_same = TRUE; |
2011 | gboolean all_square = TRUE; |
2012 | float w = r->corner[0].width; |
2013 | float h = r->corner[0].height; |
2014 | int i; |
2015 | |
2016 | for (i = 1; i < 4; i ++) |
2017 | { |
2018 | if (r->corner[i].width != w || |
2019 | r->corner[i].height != h) |
2020 | all_the_same = FALSE; |
2021 | |
2022 | if (r->corner[i].width != r->corner[i].height) |
2023 | all_square = FALSE; |
2024 | |
2025 | } |
2026 | |
2027 | g_string_append (string: str, val: " / " ); |
2028 | |
2029 | if (all_the_same) |
2030 | { |
2031 | string_append_double (string: str, d: w); |
2032 | } |
2033 | else if (all_square) |
2034 | { |
2035 | string_append_double (string: str, d: r->corner[0].width); |
2036 | g_string_append_c (str, ' '); |
2037 | string_append_double (string: str, d: r->corner[1].width); |
2038 | g_string_append_c (str, ' '); |
2039 | string_append_double (string: str, d: r->corner[2].width); |
2040 | g_string_append_c (str, ' '); |
2041 | string_append_double (string: str, d: r->corner[3].width); |
2042 | } |
2043 | else |
2044 | { |
2045 | for (i = 0; i < 4; i ++) |
2046 | { |
2047 | string_append_double (string: str, d: r->corner[i].width); |
2048 | g_string_append_c (str, ' '); |
2049 | } |
2050 | |
2051 | g_string_append (string: str, val: "/ " ); |
2052 | |
2053 | for (i = 0; i < 3; i ++) |
2054 | { |
2055 | string_append_double (string: str, d: r->corner[i].height); |
2056 | g_string_append_c (str, ' '); |
2057 | } |
2058 | |
2059 | string_append_double (string: str, d: r->corner[3].height); |
2060 | } |
2061 | } |
2062 | } |
2063 | |
2064 | static void |
2065 | append_rgba (GString *str, |
2066 | const GdkRGBA *rgba) |
2067 | { |
2068 | char *rgba_str = gdk_rgba_to_string (rgba); |
2069 | |
2070 | g_string_append (string: str, val: rgba_str); |
2071 | |
2072 | g_free (mem: rgba_str); |
2073 | } |
2074 | |
2075 | static void |
2076 | append_point (GString *str, |
2077 | const graphene_point_t *p) |
2078 | { |
2079 | string_append_double (string: str, d: p->x); |
2080 | g_string_append_c (str, ' '); |
2081 | string_append_double (string: str, d: p->y); |
2082 | } |
2083 | |
2084 | static void |
2085 | append_vec4 (GString *str, |
2086 | const graphene_vec4_t *v) |
2087 | { |
2088 | string_append_double (string: str, d: graphene_vec4_get_x (v)); |
2089 | g_string_append_c (str, ' '); |
2090 | string_append_double (string: str, d: graphene_vec4_get_y (v)); |
2091 | g_string_append_c (str, ' '); |
2092 | string_append_double (string: str, d: graphene_vec4_get_z (v)); |
2093 | g_string_append_c (str, ' '); |
2094 | string_append_double (string: str, d: graphene_vec4_get_w (v)); |
2095 | } |
2096 | |
2097 | static void |
2098 | append_float_param (Printer *p, |
2099 | const char *param_name, |
2100 | float value, |
2101 | float default_value) |
2102 | { |
2103 | /* Don't approximate-compare here, better be topo verbose */ |
2104 | if (value == default_value) |
2105 | return; |
2106 | |
2107 | _indent (self: p); |
2108 | g_string_append_printf (string: p->str, format: "%s: " , param_name); |
2109 | string_append_double (string: p->str, d: value); |
2110 | g_string_append (string: p->str, val: ";\n" ); |
2111 | } |
2112 | |
2113 | static void |
2114 | append_rgba_param (Printer *p, |
2115 | const char *param_name, |
2116 | const GdkRGBA *value) |
2117 | { |
2118 | _indent (self: p); |
2119 | g_string_append_printf (string: p->str, format: "%s: " , param_name); |
2120 | append_rgba (str: p->str, rgba: value); |
2121 | g_string_append_c (p->str, ';'); |
2122 | g_string_append_c (p->str, '\n'); |
2123 | } |
2124 | |
2125 | static void |
2126 | append_rect_param (Printer *p, |
2127 | const char *param_name, |
2128 | const graphene_rect_t *value) |
2129 | { |
2130 | _indent (self: p); |
2131 | g_string_append_printf (string: p->str, format: "%s: " , param_name); |
2132 | append_rect (str: p->str, r: value); |
2133 | g_string_append_c (p->str, ';'); |
2134 | g_string_append_c (p->str, '\n'); |
2135 | } |
2136 | |
2137 | static void |
2138 | append_rounded_rect_param (Printer *p, |
2139 | const char *param_name, |
2140 | const GskRoundedRect *value) |
2141 | { |
2142 | _indent (self: p); |
2143 | g_string_append_printf (string: p->str, format: "%s: " , param_name); |
2144 | append_rounded_rect (str: p->str, r: value); |
2145 | g_string_append_c (p->str, ';'); |
2146 | g_string_append_c (p->str, '\n'); |
2147 | } |
2148 | |
2149 | static void |
2150 | append_point_param (Printer *p, |
2151 | const char *param_name, |
2152 | const graphene_point_t *value) |
2153 | { |
2154 | _indent (self: p); |
2155 | g_string_append_printf (string: p->str, format: "%s: " , param_name); |
2156 | append_point (str: p->str, p: value); |
2157 | g_string_append_c (p->str, ';'); |
2158 | g_string_append_c (p->str, '\n'); |
2159 | } |
2160 | |
2161 | static void |
2162 | append_string_param (Printer *p, |
2163 | const char *param_name, |
2164 | const char *value) |
2165 | { |
2166 | _indent (self: p); |
2167 | g_string_append_printf (string: p->str, format: "%s: " , param_name); |
2168 | gtk_css_print_string (str: p->str, string: value, TRUE); |
2169 | g_string_append_c (p->str, ';'); |
2170 | g_string_append_c (p->str, '\n'); |
2171 | } |
2172 | |
2173 | static void |
2174 | append_vec4_param (Printer *p, |
2175 | const char *param_name, |
2176 | const graphene_vec4_t *value) |
2177 | { |
2178 | _indent (self: p); |
2179 | g_string_append_printf (string: p->str, format: "%s: " , param_name); |
2180 | append_vec4 (str: p->str, v: value); |
2181 | g_string_append_c (p->str, ';'); |
2182 | g_string_append_c (p->str, '\n'); |
2183 | } |
2184 | |
2185 | static void |
2186 | append_matrix_param (Printer *p, |
2187 | const char *param_name, |
2188 | const graphene_matrix_t *value) |
2189 | { |
2190 | GskTransform *transform = NULL; |
2191 | |
2192 | _indent (self: p); |
2193 | g_string_append_printf (string: p->str, format: "%s: " , param_name); |
2194 | |
2195 | transform = gsk_transform_matrix (next: transform, matrix: value); |
2196 | gsk_transform_print (self: transform,string: p->str); |
2197 | g_string_append_c (p->str, ';'); |
2198 | g_string_append_c (p->str, '\n'); |
2199 | |
2200 | gsk_transform_unref (self: transform); |
2201 | } |
2202 | |
2203 | static void |
2204 | append_transform_param (Printer *p, |
2205 | const char *param_name, |
2206 | GskTransform *transform) |
2207 | { |
2208 | _indent (self: p); |
2209 | g_string_append_printf (string: p->str, format: "%s: " , param_name); |
2210 | gsk_transform_print (self: transform, string: p->str); |
2211 | g_string_append_c (p->str, ';'); |
2212 | g_string_append_c (p->str, '\n'); |
2213 | } |
2214 | |
2215 | static void render_node_print (Printer *p, |
2216 | GskRenderNode *node); |
2217 | |
2218 | static void |
2219 | append_node_param (Printer *p, |
2220 | const char *param_name, |
2221 | GskRenderNode *node) |
2222 | { |
2223 | _indent (self: p); |
2224 | g_string_append_printf (string: p->str, format: "%s: " , param_name); |
2225 | render_node_print (p, node); |
2226 | } |
2227 | |
2228 | static void |
2229 | append_stops_param (Printer *p, |
2230 | const char *param_name, |
2231 | const GskColorStop *stops, |
2232 | gsize n_stops) |
2233 | { |
2234 | gsize i; |
2235 | |
2236 | _indent (self: p); |
2237 | g_string_append (string: p->str, val: param_name); |
2238 | g_string_append (string: p->str, val: ": " ); |
2239 | |
2240 | for (i = 0; i < n_stops; i ++) |
2241 | { |
2242 | if (i > 0) |
2243 | g_string_append (string: p->str, val: ", " ); |
2244 | |
2245 | string_append_double (string: p->str, d: stops[i].offset); |
2246 | g_string_append_c (p->str, ' '); |
2247 | append_rgba (str: p->str, rgba: &stops[i].color); |
2248 | } |
2249 | g_string_append (string: p->str, val: ";\n" ); |
2250 | } |
2251 | |
2252 | static cairo_status_t |
2253 | cairo_write_array (void *closure, |
2254 | const unsigned char *data, |
2255 | unsigned int length) |
2256 | { |
2257 | g_byte_array_append (array: closure, data, len: length); |
2258 | |
2259 | return CAIRO_STATUS_SUCCESS; |
2260 | } |
2261 | |
2262 | static void |
2263 | cairo_destroy_array (gpointer array) |
2264 | { |
2265 | g_byte_array_free (array, TRUE); |
2266 | } |
2267 | |
2268 | static void |
2269 | append_escaping_newlines (GString *str, |
2270 | const char *string) |
2271 | { |
2272 | gsize len; |
2273 | |
2274 | do { |
2275 | len = strcspn (s: string, reject: "\n" ); |
2276 | g_string_append_len (string: str, val: string, len); |
2277 | string += len; |
2278 | g_string_append (string: str, val: "\\\n" ); |
2279 | string++; |
2280 | } while (*string); |
2281 | } |
2282 | |
2283 | /* like g_base64 encode, but breaks lines |
2284 | * in CSS-compatible way |
2285 | */ |
2286 | static char * |
2287 | base64_encode_with_linebreaks (const guchar *data, |
2288 | gsize len) |
2289 | { |
2290 | gsize max; |
2291 | char *out; |
2292 | int state = 0, outlen; |
2293 | int save = 0; |
2294 | |
2295 | g_return_val_if_fail (data != NULL || len == 0, NULL); |
2296 | |
2297 | /* We can use a smaller limit here, since we know the saved state is 0, |
2298 | +1 is needed for trailing \0, also check for unlikely integer overflow */ |
2299 | g_return_val_if_fail (len < ((G_MAXSIZE - 1) / 4 - 1) * 3, NULL); |
2300 | |
2301 | max = (len / 3 + 1) * 4 + 1; |
2302 | max += 2 * (max / 76); |
2303 | |
2304 | out = g_malloc (n_bytes: max); |
2305 | |
2306 | outlen = g_base64_encode_step (in: data, len, TRUE, out, state: &state, save: &save); |
2307 | outlen += g_base64_encode_close (TRUE, out: out + outlen, state: &state, save: &save); |
2308 | out[outlen] = '\0'; |
2309 | |
2310 | return out; |
2311 | } |
2312 | |
2313 | void |
2314 | gsk_text_node_serialize_glyphs (GskRenderNode *node, |
2315 | GString *p) |
2316 | { |
2317 | const guint n_glyphs = gsk_text_node_get_num_glyphs (node); |
2318 | const PangoGlyphInfo *glyphs = gsk_text_node_get_glyphs (node, NULL); |
2319 | PangoFont *font = gsk_text_node_get_font (node); |
2320 | GString *str; |
2321 | guint i, j; |
2322 | PangoGlyphString *ascii; |
2323 | |
2324 | ascii = create_ascii_glyphs (font); |
2325 | str = g_string_new (init: "" ); |
2326 | |
2327 | for (i = 0; i < n_glyphs; i++) |
2328 | { |
2329 | if (ascii) |
2330 | { |
2331 | for (j = 0; j < ascii->num_glyphs; j++) |
2332 | { |
2333 | if (glyphs[i].glyph == ascii->glyphs[j].glyph && |
2334 | glyphs[i].geometry.width == ascii->glyphs[j].geometry.width && |
2335 | glyphs[i].geometry.x_offset == 0 && |
2336 | glyphs[i].geometry.y_offset == 0 && |
2337 | glyphs[i].attr.is_cluster_start && |
2338 | !glyphs[i].attr.is_color) |
2339 | { |
2340 | switch (j + MIN_ASCII_GLYPH) |
2341 | { |
2342 | case '\\': |
2343 | g_string_append (string: str, val: "\\\\" ); |
2344 | break; |
2345 | case '"': |
2346 | g_string_append (string: str, val: "\\\"" ); |
2347 | break; |
2348 | default: |
2349 | g_string_append_c (str, j + MIN_ASCII_GLYPH); |
2350 | break; |
2351 | } |
2352 | break; |
2353 | } |
2354 | } |
2355 | if (j != ascii->num_glyphs) |
2356 | continue; |
2357 | } |
2358 | |
2359 | if (str->len) |
2360 | { |
2361 | g_string_append_printf (string: p, format: "\"%s\", " , str->str); |
2362 | g_string_set_size (string: str, len: 0); |
2363 | } |
2364 | |
2365 | g_string_append_printf (string: p, format: "%u " , glyphs[i].glyph); |
2366 | string_append_double (string: p, d: (double) glyphs[i].geometry.width / PANGO_SCALE); |
2367 | if (!glyphs[i].attr.is_cluster_start || |
2368 | glyphs[i].attr.is_color || |
2369 | glyphs[i].geometry.x_offset != 0 || |
2370 | glyphs[i].geometry.y_offset != 0) |
2371 | { |
2372 | g_string_append (string: p, val: " " ); |
2373 | string_append_double (string: p, d: (double) glyphs[i].geometry.x_offset / PANGO_SCALE); |
2374 | g_string_append (string: p, val: " " ); |
2375 | string_append_double (string: p, d: (double) glyphs[i].geometry.y_offset / PANGO_SCALE); |
2376 | if (!glyphs[i].attr.is_cluster_start) |
2377 | g_string_append (string: p, val: " same-cluster" ); |
2378 | if (!glyphs[i].attr.is_color) |
2379 | g_string_append (string: p, val: " color" ); |
2380 | } |
2381 | |
2382 | if (i + 1 < n_glyphs) |
2383 | g_string_append (string: p, val: ", " ); |
2384 | } |
2385 | |
2386 | if (str->len) |
2387 | g_string_append_printf (string: p, format: "\"%s\"" , str->str); |
2388 | |
2389 | g_string_free (string: str, TRUE); |
2390 | if (ascii) |
2391 | pango_glyph_string_free (string: ascii); |
2392 | } |
2393 | |
2394 | static void |
2395 | render_node_print (Printer *p, |
2396 | GskRenderNode *node) |
2397 | { |
2398 | char *b64; |
2399 | |
2400 | switch (gsk_render_node_get_node_type (node)) |
2401 | { |
2402 | case GSK_CONTAINER_NODE: |
2403 | { |
2404 | guint i; |
2405 | |
2406 | start_node (self: p, node_name: "container" ); |
2407 | for (i = 0; i < gsk_container_node_get_n_children (node); i ++) |
2408 | { |
2409 | GskRenderNode *child = gsk_container_node_get_child (node, idx: i); |
2410 | |
2411 | /* Only in container nodes do we want nodes to be indented. */ |
2412 | _indent (self: p); |
2413 | render_node_print (p, node: child); |
2414 | } |
2415 | end_node (self: p); |
2416 | } |
2417 | break; |
2418 | |
2419 | case GSK_COLOR_NODE: |
2420 | { |
2421 | start_node (self: p, node_name: "color" ); |
2422 | append_rect_param (p, param_name: "bounds" , value: &node->bounds); |
2423 | append_rgba_param (p, param_name: "color" , value: gsk_color_node_get_color (node)); |
2424 | end_node (self: p); |
2425 | } |
2426 | break; |
2427 | |
2428 | case GSK_CROSS_FADE_NODE: |
2429 | { |
2430 | start_node (self: p, node_name: "cross-fade" ); |
2431 | |
2432 | append_float_param (p, param_name: "progress" , value: gsk_cross_fade_node_get_progress (node), default_value: 0.5f); |
2433 | append_node_param (p, param_name: "start" , node: gsk_cross_fade_node_get_start_child (node)); |
2434 | append_node_param (p, param_name: "end" , node: gsk_cross_fade_node_get_end_child (node)); |
2435 | |
2436 | end_node (self: p); |
2437 | } |
2438 | break; |
2439 | |
2440 | case GSK_REPEATING_LINEAR_GRADIENT_NODE: |
2441 | case GSK_LINEAR_GRADIENT_NODE: |
2442 | { |
2443 | if (gsk_render_node_get_node_type (node) == GSK_REPEATING_LINEAR_GRADIENT_NODE) |
2444 | start_node (self: p, node_name: "repeating-linear-gradient" ); |
2445 | else |
2446 | start_node (self: p, node_name: "linear-gradient" ); |
2447 | |
2448 | append_rect_param (p, param_name: "bounds" , value: &node->bounds); |
2449 | append_point_param (p, param_name: "start" , value: gsk_linear_gradient_node_get_start (node)); |
2450 | append_point_param (p, param_name: "end" , value: gsk_linear_gradient_node_get_end (node)); |
2451 | append_stops_param (p, param_name: "stops" , stops: gsk_linear_gradient_node_get_color_stops (node, NULL), |
2452 | n_stops: gsk_linear_gradient_node_get_n_color_stops (node)); |
2453 | |
2454 | end_node (self: p); |
2455 | } |
2456 | break; |
2457 | |
2458 | case GSK_REPEATING_RADIAL_GRADIENT_NODE: |
2459 | case GSK_RADIAL_GRADIENT_NODE: |
2460 | { |
2461 | if (gsk_render_node_get_node_type (node) == GSK_REPEATING_RADIAL_GRADIENT_NODE) |
2462 | start_node (self: p, node_name: "repeating-radial-gradient" ); |
2463 | else |
2464 | start_node (self: p, node_name: "radial-gradient" ); |
2465 | |
2466 | append_rect_param (p, param_name: "bounds" , value: &node->bounds); |
2467 | append_point_param (p, param_name: "center" , value: gsk_radial_gradient_node_get_center (node)); |
2468 | append_float_param (p, param_name: "hradius" , value: gsk_radial_gradient_node_get_hradius (node), default_value: 0.0f); |
2469 | append_float_param (p, param_name: "vradius" , value: gsk_radial_gradient_node_get_vradius (node), default_value: 0.0f); |
2470 | append_float_param (p, param_name: "start" , value: gsk_radial_gradient_node_get_start (node), default_value: 0.0f); |
2471 | append_float_param (p, param_name: "end" , value: gsk_radial_gradient_node_get_end (node), default_value: 1.0f); |
2472 | |
2473 | append_stops_param (p, param_name: "stops" , stops: gsk_radial_gradient_node_get_color_stops (node, NULL), |
2474 | n_stops: gsk_radial_gradient_node_get_n_color_stops (node)); |
2475 | |
2476 | end_node (self: p); |
2477 | } |
2478 | break; |
2479 | |
2480 | case GSK_CONIC_GRADIENT_NODE: |
2481 | { |
2482 | start_node (self: p, node_name: "conic-gradient" ); |
2483 | |
2484 | append_rect_param (p, param_name: "bounds" , value: &node->bounds); |
2485 | append_point_param (p, param_name: "center" , value: gsk_conic_gradient_node_get_center (node)); |
2486 | append_float_param (p, param_name: "rotation" , value: gsk_conic_gradient_node_get_rotation (node), default_value: 0.0f); |
2487 | |
2488 | append_stops_param (p, param_name: "stops" , stops: gsk_conic_gradient_node_get_color_stops (node, NULL), |
2489 | n_stops: gsk_conic_gradient_node_get_n_color_stops (node)); |
2490 | |
2491 | end_node (self: p); |
2492 | } |
2493 | break; |
2494 | |
2495 | case GSK_OPACITY_NODE: |
2496 | { |
2497 | start_node (self: p, node_name: "opacity" ); |
2498 | |
2499 | append_float_param (p, param_name: "opacity" , value: gsk_opacity_node_get_opacity (node), default_value: 0.5f); |
2500 | append_node_param (p, param_name: "child" , node: gsk_opacity_node_get_child (node)); |
2501 | |
2502 | end_node (self: p); |
2503 | } |
2504 | break; |
2505 | |
2506 | case GSK_OUTSET_SHADOW_NODE: |
2507 | { |
2508 | const GdkRGBA *color = gsk_outset_shadow_node_get_color (node); |
2509 | |
2510 | start_node (self: p, node_name: "outset-shadow" ); |
2511 | |
2512 | append_float_param (p, param_name: "blur" , value: gsk_outset_shadow_node_get_blur_radius (node), default_value: 0.0f); |
2513 | if (!gdk_rgba_equal (p1: color, p2: &GDK_RGBA("000" ))) |
2514 | append_rgba_param (p, param_name: "color" , value: color); |
2515 | append_float_param (p, param_name: "dx" , value: gsk_outset_shadow_node_get_dx (node), default_value: 1.0f); |
2516 | append_float_param (p, param_name: "dy" , value: gsk_outset_shadow_node_get_dy (node), default_value: 1.0f); |
2517 | append_rounded_rect_param (p, param_name: "outline" , value: gsk_outset_shadow_node_get_outline (node)); |
2518 | append_float_param (p, param_name: "spread" , value: gsk_outset_shadow_node_get_spread (node), default_value: 0.0f); |
2519 | |
2520 | end_node (self: p); |
2521 | } |
2522 | break; |
2523 | |
2524 | case GSK_CLIP_NODE: |
2525 | { |
2526 | start_node (self: p, node_name: "clip" ); |
2527 | |
2528 | append_rect_param (p, param_name: "clip" , value: gsk_clip_node_get_clip (node)); |
2529 | append_node_param (p, param_name: "child" , node: gsk_clip_node_get_child (node)); |
2530 | |
2531 | end_node (self: p); |
2532 | } |
2533 | break; |
2534 | |
2535 | case GSK_ROUNDED_CLIP_NODE: |
2536 | { |
2537 | start_node (self: p, node_name: "rounded-clip" ); |
2538 | |
2539 | append_rounded_rect_param (p, param_name: "clip" , value: gsk_rounded_clip_node_get_clip (node)); |
2540 | append_node_param (p, param_name: "child" , node: gsk_rounded_clip_node_get_child (node)); |
2541 | |
2542 | |
2543 | end_node (self: p); |
2544 | } |
2545 | break; |
2546 | |
2547 | case GSK_TRANSFORM_NODE: |
2548 | { |
2549 | GskTransform *transform = gsk_transform_node_get_transform (node); |
2550 | start_node (self: p, node_name: "transform" ); |
2551 | |
2552 | if (gsk_transform_get_category (transform) != GSK_TRANSFORM_CATEGORY_IDENTITY) |
2553 | append_transform_param (p, param_name: "transform" , transform); |
2554 | append_node_param (p, param_name: "child" , node: gsk_transform_node_get_child (node)); |
2555 | |
2556 | end_node (self: p); |
2557 | } |
2558 | break; |
2559 | |
2560 | case GSK_COLOR_MATRIX_NODE: |
2561 | { |
2562 | start_node (self: p, node_name: "color-matrix" ); |
2563 | |
2564 | if (!graphene_matrix_is_identity (m: gsk_color_matrix_node_get_color_matrix (node))) |
2565 | append_matrix_param (p, param_name: "matrix" , value: gsk_color_matrix_node_get_color_matrix (node)); |
2566 | if (!graphene_vec4_equal (v1: gsk_color_matrix_node_get_color_offset (node), v2: graphene_vec4_zero ())) |
2567 | append_vec4_param (p, param_name: "offset" , value: gsk_color_matrix_node_get_color_offset (node)); |
2568 | append_node_param (p, param_name: "child" , node: gsk_color_matrix_node_get_child (node)); |
2569 | |
2570 | end_node (self: p); |
2571 | } |
2572 | break; |
2573 | |
2574 | case GSK_BORDER_NODE: |
2575 | { |
2576 | const GdkRGBA *colors = gsk_border_node_get_colors (node); |
2577 | const float *widths = gsk_border_node_get_widths (node); |
2578 | guint i, n; |
2579 | start_node (self: p, node_name: "border" ); |
2580 | |
2581 | if (!gdk_rgba_equal (p1: &colors[3], p2: &colors[1])) |
2582 | n = 4; |
2583 | else if (!gdk_rgba_equal (p1: &colors[2], p2: &colors[0])) |
2584 | n = 3; |
2585 | else if (!gdk_rgba_equal (p1: &colors[1], p2: &colors[0])) |
2586 | n = 2; |
2587 | else if (!gdk_rgba_equal (p1: &colors[0], p2: &GDK_RGBA("000000" ))) |
2588 | n = 1; |
2589 | else |
2590 | n = 0; |
2591 | |
2592 | if (n > 0) |
2593 | { |
2594 | _indent (self: p); |
2595 | g_string_append (string: p->str, val: "colors: " ); |
2596 | for (i = 0; i < n; i++) |
2597 | { |
2598 | if (i > 0) |
2599 | g_string_append_c (p->str, ' '); |
2600 | append_rgba (str: p->str, rgba: &colors[i]); |
2601 | } |
2602 | g_string_append (string: p->str, val: ";\n" ); |
2603 | } |
2604 | |
2605 | append_rounded_rect_param (p, param_name: "outline" , value: gsk_border_node_get_outline (node)); |
2606 | |
2607 | if (widths[3] != widths[1]) |
2608 | n = 4; |
2609 | else if (widths[2] != widths[0]) |
2610 | n = 3; |
2611 | else if (widths[1] != widths[0]) |
2612 | n = 2; |
2613 | else if (widths[0] != 1.0) |
2614 | n = 1; |
2615 | else |
2616 | n = 0; |
2617 | |
2618 | if (n > 0) |
2619 | { |
2620 | _indent (self: p); |
2621 | g_string_append (string: p->str, val: "widths: " ); |
2622 | for (i = 0; i < n; i++) |
2623 | { |
2624 | if (i > 0) |
2625 | g_string_append_c (p->str, ' '); |
2626 | string_append_double (string: p->str, d: widths[i]); |
2627 | } |
2628 | g_string_append (string: p->str, val: ";\n" ); |
2629 | } |
2630 | |
2631 | end_node (self: p); |
2632 | } |
2633 | break; |
2634 | |
2635 | case GSK_SHADOW_NODE: |
2636 | { |
2637 | const guint n_shadows = gsk_shadow_node_get_n_shadows (node); |
2638 | int i; |
2639 | |
2640 | start_node (self: p, node_name: "shadow" ); |
2641 | |
2642 | _indent (self: p); |
2643 | g_string_append (string: p->str, val: "shadows: " ); |
2644 | for (i = 0; i < n_shadows; i ++) |
2645 | { |
2646 | const GskShadow *s = gsk_shadow_node_get_shadow (node, i); |
2647 | char *color; |
2648 | |
2649 | if (i > 0) |
2650 | g_string_append (string: p->str, val: ", " ); |
2651 | |
2652 | color = gdk_rgba_to_string (rgba: &s->color); |
2653 | g_string_append (string: p->str, val: color); |
2654 | g_string_append_c (p->str, ' '); |
2655 | string_append_double (string: p->str, d: s->dx); |
2656 | g_string_append_c (p->str, ' '); |
2657 | string_append_double (string: p->str, d: s->dy); |
2658 | if (s->radius > 0) |
2659 | { |
2660 | g_string_append_c (p->str, ' '); |
2661 | string_append_double (string: p->str, d: s->radius); |
2662 | } |
2663 | |
2664 | g_free (mem: color); |
2665 | } |
2666 | |
2667 | g_string_append_c (p->str, ';'); |
2668 | g_string_append_c (p->str, '\n'); |
2669 | append_node_param (p, param_name: "child" , node: gsk_shadow_node_get_child (node)); |
2670 | |
2671 | end_node (self: p); |
2672 | } |
2673 | break; |
2674 | |
2675 | case GSK_INSET_SHADOW_NODE: |
2676 | { |
2677 | const GdkRGBA *color = gsk_inset_shadow_node_get_color (node); |
2678 | start_node (self: p, node_name: "inset-shadow" ); |
2679 | |
2680 | append_float_param (p, param_name: "blur" , value: gsk_inset_shadow_node_get_blur_radius (node), default_value: 0.0f); |
2681 | if (!gdk_rgba_equal (p1: color, p2: &GDK_RGBA("000" ))) |
2682 | append_rgba_param (p, param_name: "color" , value: color); |
2683 | append_float_param (p, param_name: "dx" , value: gsk_inset_shadow_node_get_dx (node), default_value: 1.0f); |
2684 | append_float_param (p, param_name: "dy" , value: gsk_inset_shadow_node_get_dy (node), default_value: 1.0f); |
2685 | append_rounded_rect_param (p, param_name: "outline" , value: gsk_inset_shadow_node_get_outline (node)); |
2686 | append_float_param (p, param_name: "spread" , value: gsk_inset_shadow_node_get_spread (node), default_value: 0.0f); |
2687 | |
2688 | end_node (self: p); |
2689 | } |
2690 | break; |
2691 | |
2692 | case GSK_TEXTURE_NODE: |
2693 | { |
2694 | GdkTexture *texture = gsk_texture_node_get_texture (node); |
2695 | GBytes *bytes; |
2696 | |
2697 | start_node (self: p, node_name: "texture" ); |
2698 | append_rect_param (p, param_name: "bounds" , value: &node->bounds); |
2699 | |
2700 | bytes = gdk_texture_save_to_png_bytes (texture); |
2701 | |
2702 | _indent (self: p); |
2703 | g_string_append (string: p->str, val: "texture: url(\"data:image/png;base64," ); |
2704 | b64 = base64_encode_with_linebreaks (data: g_bytes_get_data (bytes, NULL), |
2705 | len: g_bytes_get_size (bytes)); |
2706 | append_escaping_newlines (str: p->str, string: b64); |
2707 | g_free (mem: b64); |
2708 | g_string_append (string: p->str, val: "\");\n" ); |
2709 | end_node (self: p); |
2710 | |
2711 | g_bytes_unref (bytes); |
2712 | } |
2713 | break; |
2714 | |
2715 | case GSK_TEXT_NODE: |
2716 | { |
2717 | const graphene_point_t *offset = gsk_text_node_get_offset (node); |
2718 | const GdkRGBA *color = gsk_text_node_get_color (node); |
2719 | PangoFont *font = gsk_text_node_get_font (node); |
2720 | PangoFontDescription *desc; |
2721 | char *font_name; |
2722 | |
2723 | start_node (self: p, node_name: "text" ); |
2724 | |
2725 | if (!gdk_rgba_equal (p1: color, p2: &GDK_RGBA("000000" ))) |
2726 | append_rgba_param (p, param_name: "color" , value: color); |
2727 | |
2728 | _indent (self: p); |
2729 | desc = pango_font_describe (font); |
2730 | font_name = pango_font_description_to_string (desc); |
2731 | g_string_append_printf (string: p->str, format: "font: \"%s\";\n" , font_name); |
2732 | g_free (mem: font_name); |
2733 | pango_font_description_free (desc); |
2734 | |
2735 | _indent (self: p); |
2736 | g_string_append (string: p->str, val: "glyphs: " ); |
2737 | |
2738 | gsk_text_node_serialize_glyphs (node, p: p->str); |
2739 | |
2740 | g_string_append_c (p->str, ';'); |
2741 | g_string_append_c (p->str, '\n'); |
2742 | |
2743 | if (!graphene_point_equal (a: offset, b: graphene_point_zero ())) |
2744 | append_point_param (p, param_name: "offset" , value: offset); |
2745 | |
2746 | end_node (self: p); |
2747 | } |
2748 | break; |
2749 | |
2750 | case GSK_DEBUG_NODE: |
2751 | { |
2752 | const char *message = gsk_debug_node_get_message (node); |
2753 | |
2754 | start_node (self: p, node_name: "debug" ); |
2755 | |
2756 | /* TODO: We potentially need to escape certain characters in the message */ |
2757 | if (message) |
2758 | { |
2759 | _indent (self: p); |
2760 | g_string_append_printf (string: p->str, format: "message: \"%s\";\n" , message); |
2761 | } |
2762 | append_node_param (p, param_name: "child" , node: gsk_debug_node_get_child (node)); |
2763 | |
2764 | end_node (self: p); |
2765 | } |
2766 | break; |
2767 | |
2768 | case GSK_BLUR_NODE: |
2769 | { |
2770 | start_node (self: p, node_name: "blur" ); |
2771 | |
2772 | append_float_param (p, param_name: "blur" , value: gsk_blur_node_get_radius (node), default_value: 1.0f); |
2773 | append_node_param (p, param_name: "child" , node: gsk_blur_node_get_child (node)); |
2774 | |
2775 | end_node (self: p); |
2776 | } |
2777 | break; |
2778 | |
2779 | case GSK_GL_SHADER_NODE: |
2780 | { |
2781 | GskGLShader *shader = gsk_gl_shader_node_get_shader (node); |
2782 | GBytes *args = gsk_gl_shader_node_get_args (node); |
2783 | |
2784 | start_node (self: p, node_name: "glshader" ); |
2785 | |
2786 | append_rect_param (p, param_name: "bounds" , value: &node->bounds); |
2787 | |
2788 | GBytes *bytes = gsk_gl_shader_get_source (shader); |
2789 | /* Ensure we are zero-terminated */ |
2790 | char *sourcecode = g_strndup (str: g_bytes_get_data (bytes, NULL), n: g_bytes_get_size (bytes)); |
2791 | append_string_param (p, param_name: "sourcecode" , value: sourcecode); |
2792 | g_free (mem: sourcecode); |
2793 | |
2794 | if (gsk_gl_shader_get_n_uniforms (shader) > 0) |
2795 | { |
2796 | GString *data = g_string_new (init: "" ); |
2797 | |
2798 | for (guint i = 0; i < gsk_gl_shader_get_n_uniforms (shader); i++) |
2799 | { |
2800 | if (i > 0) |
2801 | g_string_append (string: data, val: ", " ); |
2802 | |
2803 | switch (gsk_gl_shader_get_uniform_type (shader, idx: i)) |
2804 | { |
2805 | case GSK_GL_UNIFORM_TYPE_NONE: |
2806 | default: |
2807 | g_assert_not_reached (); |
2808 | break; |
2809 | |
2810 | case GSK_GL_UNIFORM_TYPE_FLOAT: |
2811 | { |
2812 | float value = gsk_gl_shader_get_arg_float (shader, args, idx: i); |
2813 | string_append_double (string: data, d: value); |
2814 | } |
2815 | break; |
2816 | |
2817 | case GSK_GL_UNIFORM_TYPE_INT: |
2818 | { |
2819 | gint32 value = gsk_gl_shader_get_arg_int (shader, args, idx: i); |
2820 | g_string_append_printf (string: data, format: "%d" , value); |
2821 | } |
2822 | break; |
2823 | |
2824 | case GSK_GL_UNIFORM_TYPE_UINT: |
2825 | { |
2826 | guint32 value = gsk_gl_shader_get_arg_uint (shader, args, idx: i); |
2827 | g_string_append_printf (string: data, format: "%u" , value); |
2828 | } |
2829 | break; |
2830 | |
2831 | case GSK_GL_UNIFORM_TYPE_BOOL: |
2832 | { |
2833 | gboolean value = gsk_gl_shader_get_arg_bool (shader, args, idx: i); |
2834 | g_string_append_printf (string: data, format: "%d" , value); |
2835 | } |
2836 | break; |
2837 | |
2838 | case GSK_GL_UNIFORM_TYPE_VEC2: |
2839 | { |
2840 | graphene_vec2_t value; |
2841 | gsk_gl_shader_get_arg_vec2 (shader, args, idx: i, |
2842 | out_value: &value); |
2843 | string_append_double (string: data, d: graphene_vec2_get_x (v: &value)); |
2844 | g_string_append (string: data, val: " " ); |
2845 | string_append_double (string: data, d: graphene_vec2_get_y (v: &value)); |
2846 | } |
2847 | break; |
2848 | |
2849 | case GSK_GL_UNIFORM_TYPE_VEC3: |
2850 | { |
2851 | graphene_vec3_t value; |
2852 | gsk_gl_shader_get_arg_vec3 (shader, args, idx: i, |
2853 | out_value: &value); |
2854 | string_append_double (string: data, d: graphene_vec3_get_x (v: &value)); |
2855 | g_string_append (string: data, val: " " ); |
2856 | string_append_double (string: data, d: graphene_vec3_get_y (v: &value)); |
2857 | g_string_append (string: data, val: " " ); |
2858 | string_append_double (string: data, d: graphene_vec3_get_z (v: &value)); |
2859 | } |
2860 | break; |
2861 | |
2862 | case GSK_GL_UNIFORM_TYPE_VEC4: |
2863 | { |
2864 | graphene_vec4_t value; |
2865 | gsk_gl_shader_get_arg_vec4 (shader, args, idx: i, |
2866 | out_value: &value); |
2867 | string_append_double (string: data, d: graphene_vec4_get_x (v: &value)); |
2868 | g_string_append (string: data, val: " " ); |
2869 | string_append_double (string: data, d: graphene_vec4_get_y (v: &value)); |
2870 | g_string_append (string: data, val: " " ); |
2871 | string_append_double (string: data, d: graphene_vec4_get_z (v: &value)); |
2872 | g_string_append (string: data, val: " " ); |
2873 | string_append_double (string: data, d: graphene_vec4_get_w (v: &value)); |
2874 | } |
2875 | break; |
2876 | } |
2877 | } |
2878 | _indent (self: p); |
2879 | g_string_append_printf (string: p->str, format: "args: %s;\n" , data->str); |
2880 | g_string_free (string: data, TRUE); |
2881 | } |
2882 | |
2883 | for (guint i = 0; i < gsk_gl_shader_node_get_n_children (node); i ++) |
2884 | { |
2885 | GskRenderNode *child = gsk_gl_shader_node_get_child (node, idx: i); |
2886 | char *name; |
2887 | |
2888 | name = g_strdup_printf (format: "child%d" , i + 1); |
2889 | append_node_param (p, param_name: name, node: child); |
2890 | g_free (mem: name); |
2891 | } |
2892 | |
2893 | end_node (self: p); |
2894 | } |
2895 | break; |
2896 | |
2897 | case GSK_REPEAT_NODE: |
2898 | { |
2899 | GskRenderNode *child = gsk_repeat_node_get_child (node); |
2900 | const graphene_rect_t *child_bounds = gsk_repeat_node_get_child_bounds (node); |
2901 | |
2902 | start_node (self: p, node_name: "repeat" ); |
2903 | |
2904 | if (!graphene_rect_equal (a: &node->bounds, b: &child->bounds)) |
2905 | append_rect_param (p, param_name: "bounds" , value: &node->bounds); |
2906 | if (!graphene_rect_equal (a: child_bounds, b: &child->bounds)) |
2907 | append_rect_param (p, param_name: "child-bounds" , value: child_bounds); |
2908 | append_node_param (p, param_name: "child" , node: gsk_repeat_node_get_child (node)); |
2909 | |
2910 | end_node (self: p); |
2911 | } |
2912 | break; |
2913 | |
2914 | case GSK_BLEND_NODE: |
2915 | { |
2916 | GskBlendMode mode = gsk_blend_node_get_blend_mode (node); |
2917 | guint i; |
2918 | |
2919 | start_node (self: p, node_name: "blend" ); |
2920 | |
2921 | if (mode != GSK_BLEND_MODE_DEFAULT) |
2922 | { |
2923 | _indent (self: p); |
2924 | for (i = 0; i < G_N_ELEMENTS (blend_modes); i++) |
2925 | { |
2926 | if (blend_modes[i].mode == mode) |
2927 | { |
2928 | g_string_append_printf (string: p->str, format: "mode: %s;\n" , blend_modes[i].name); |
2929 | break; |
2930 | } |
2931 | } |
2932 | } |
2933 | append_node_param (p, param_name: "bottom" , node: gsk_blend_node_get_bottom_child (node)); |
2934 | append_node_param (p, param_name: "top" , node: gsk_blend_node_get_top_child (node)); |
2935 | |
2936 | end_node (self: p); |
2937 | } |
2938 | break; |
2939 | |
2940 | case GSK_NOT_A_RENDER_NODE: |
2941 | g_assert_not_reached (); |
2942 | break; |
2943 | |
2944 | case GSK_CAIRO_NODE: |
2945 | { |
2946 | cairo_surface_t *surface = gsk_cairo_node_get_surface (node); |
2947 | GByteArray *array; |
2948 | |
2949 | start_node (self: p, node_name: "cairo" ); |
2950 | append_rect_param (p, param_name: "bounds" , value: &node->bounds); |
2951 | |
2952 | if (surface != NULL) |
2953 | { |
2954 | array = g_byte_array_new (); |
2955 | cairo_surface_write_to_png_stream (surface, write_func: cairo_write_array, closure: array); |
2956 | |
2957 | _indent (self: p); |
2958 | g_string_append (string: p->str, val: "pixels: url(\"data:image/png;base64," ); |
2959 | b64 = base64_encode_with_linebreaks (data: array->data, len: array->len); |
2960 | append_escaping_newlines (str: p->str, string: b64); |
2961 | g_free (mem: b64); |
2962 | g_string_append (string: p->str, val: "\");\n" ); |
2963 | |
2964 | g_byte_array_free (array, TRUE); |
2965 | |
2966 | #ifdef CAIRO_HAS_SCRIPT_SURFACE |
2967 | if (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_RECORDING) |
2968 | { |
2969 | static const cairo_user_data_key_t cairo_is_stupid_key; |
2970 | cairo_device_t *script; |
2971 | |
2972 | array = g_byte_array_new (); |
2973 | script = cairo_script_create_for_stream (write_func: cairo_write_array, closure: array); |
2974 | |
2975 | if (cairo_script_from_recording_surface (script, recording_surface: surface) == CAIRO_STATUS_SUCCESS) |
2976 | { |
2977 | _indent (self: p); |
2978 | g_string_append (string: p->str, val: "script: url(\"data:;base64," ); |
2979 | b64 = base64_encode_with_linebreaks (data: array->data, len: array->len); |
2980 | append_escaping_newlines (str: p->str, string: b64); |
2981 | g_free (mem: b64); |
2982 | g_string_append (string: p->str, val: "\");\n" ); |
2983 | } |
2984 | |
2985 | /* because Cairo is stupid and writes to the device after we finished it, |
2986 | * we can't just |
2987 | g_byte_array_free (array, TRUE); |
2988 | * but have to |
2989 | */ |
2990 | g_byte_array_set_size (array, length: 0); |
2991 | cairo_device_set_user_data (device: script, key: &cairo_is_stupid_key, user_data: array, destroy: cairo_destroy_array); |
2992 | cairo_device_destroy (device: script); |
2993 | } |
2994 | #endif |
2995 | } |
2996 | |
2997 | end_node (self: p); |
2998 | } |
2999 | break; |
3000 | |
3001 | default: |
3002 | g_error ("Unhandled node: %s" , g_type_name_from_instance ((GTypeInstance *) node)); |
3003 | break; |
3004 | } |
3005 | } |
3006 | |
3007 | /** |
3008 | * gsk_render_node_serialize: |
3009 | * @node: a `GskRenderNode` |
3010 | * |
3011 | * Serializes the @node for later deserialization via |
3012 | * gsk_render_node_deserialize(). No guarantees are made about the format |
3013 | * used other than that the same version of GTK will be able to deserialize |
3014 | * the result of a call to gsk_render_node_serialize() and |
3015 | * gsk_render_node_deserialize() will correctly reject files it cannot open |
3016 | * that were created with previous versions of GTK. |
3017 | * |
3018 | * The intended use of this functions is testing, benchmarking and debugging. |
3019 | * The format is not meant as a permanent storage format. |
3020 | * |
3021 | * Returns: a `GBytes` representing the node. |
3022 | **/ |
3023 | GBytes * |
3024 | gsk_render_node_serialize (GskRenderNode *node) |
3025 | { |
3026 | Printer p; |
3027 | |
3028 | printer_init (self: &p); |
3029 | |
3030 | if (gsk_render_node_get_node_type (node) == GSK_CONTAINER_NODE) |
3031 | { |
3032 | guint i; |
3033 | |
3034 | for (i = 0; i < gsk_container_node_get_n_children (node); i ++) |
3035 | { |
3036 | GskRenderNode *child = gsk_container_node_get_child (node, idx: i); |
3037 | |
3038 | render_node_print (p: &p, node: child); |
3039 | } |
3040 | } |
3041 | else |
3042 | { |
3043 | render_node_print (p: &p, node); |
3044 | } |
3045 | |
3046 | return g_string_free_to_bytes (string: p.str); |
3047 | } |
3048 | |