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
44typedef struct _Declaration Declaration;
45
46struct _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
54static gboolean
55parse_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
71static gboolean
72parse_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
138static void
139clear_texture (gpointer inout_texture)
140{
141 g_clear_object ((GdkTexture **) inout_texture);
142}
143
144static cairo_surface_t *
145csi_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
154static const cairo_user_data_key_t csi_hooks_key;
155
156static cairo_t *
157csi_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
170static void
171csi_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
184static gboolean
185parse_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
263static void
264clear_surface (gpointer inout_surface)
265{
266 g_clear_pointer ((cairo_surface_t **) inout_surface, cairo_surface_destroy);
267}
268
269static gboolean
270parse_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
341static gboolean
342parse_color (GtkCssParser *parser,
343 gpointer out_color)
344{
345 return gdk_rgba_parser_parse (parser, rgba: out_color);
346}
347
348static gboolean
349parse_double (GtkCssParser *parser,
350 gpointer out_double)
351{
352 return gtk_css_parser_consume_number (self: parser, number: out_double);
353}
354
355static gboolean
356parse_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
370static gboolean
371parse_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
387static void
388clear_transform (gpointer inout_transform)
389{
390 g_clear_pointer ((GskTransform **) inout_transform, gsk_transform_unref);
391}
392
393static gboolean
394parse_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
413static void
414clear_string (gpointer inout_string)
415{
416 g_clear_pointer ((char **) inout_string, g_free);
417}
418
419static gboolean
420parse_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
468error:
469 g_array_free (array: stops, TRUE);
470 return FALSE;
471}
472
473static void
474clear_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
485static gboolean
486parse_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
516static gboolean
517parse_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
543static gboolean
544parse_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
580static void
581clear_shadows (gpointer inout_shadows)
582{
583 g_array_set_size (array: *(GArray **) inout_shadows, length: 0);
584}
585
586static 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
609static gboolean
610parse_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
627static PangoFont *
628font_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
650static PangoGlyphString *
651create_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: &not_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
710static gboolean
711parse_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
735static void
736clear_font (gpointer inout_font)
737{
738 g_clear_object ((PangoFont **) inout_font);
739}
740
741static gboolean
742parse_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
816static void
817clear_glyphs (gpointer inout_glyphs)
818{
819 g_clear_pointer ((PangoGlyphString **) inout_glyphs, pango_glyph_string_free);
820}
821
822static gboolean
823parse_node (GtkCssParser *parser, gpointer out_node);
824
825static void
826clear_node (gpointer inout_node)
827{
828 g_clear_pointer ((GskRenderNode **) inout_node, gsk_render_node_unref);
829}
830
831static GskRenderNode *
832parse_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
863static guint
864parse_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
928static GdkTexture *
929create_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
952static GskRenderNode *
953create_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
958static GskRenderNode *
959parse_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
973static GskRenderNode *
974parse_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
1010static GskRenderNode *
1011parse_linear_gradient_node (GtkCssParser *parser)
1012{
1013 return parse_linear_gradient_node_internal (parser, FALSE);
1014}
1015
1016static GskRenderNode *
1017parse_repeating_linear_gradient_node (GtkCssParser *parser)
1018{
1019 return parse_linear_gradient_node_internal (parser, TRUE);
1020}
1021
1022static GskRenderNode *
1023parse_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, &center },
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: &center, 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: &center, 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
1067static GskRenderNode *
1068parse_radial_gradient_node (GtkCssParser *parser)
1069{
1070 return parse_radial_gradient_node_internal (parser, FALSE);
1071}
1072
1073static GskRenderNode *
1074parse_repeating_radial_gradient_node (GtkCssParser *parser)
1075{
1076 return parse_radial_gradient_node_internal (parser, TRUE);
1077}
1078
1079static GskRenderNode *
1080parse_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, &center },
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: &center, 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
1113static GskRenderNode *
1114parse_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
1133typedef union {
1134 gint32 i;
1135 double v[4];
1136} UniformValue;
1137
1138typedef struct {
1139 GskGLShader *shader;
1140 GskShaderArgsBuilder *args;
1141} ShaderInfo;
1142
1143static void
1144clear_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
1151static gboolean
1152parse_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
1172static gboolean
1173parse_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
1269static gboolean
1270parse_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
1288static GskRenderNode *
1289parse_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
1336static GskRenderNode *
1337parse_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
1353static GskRenderNode *
1354parse_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
1375static GskRenderNode *
1376parse_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
1418static GskRenderNode *
1419parse_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
1438static GskRenderNode *
1439parse_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
1465static GskRenderNode *
1466parse_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
1487static GskRenderNode *
1488parse_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
1520static GskRenderNode *
1521parse_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
1547static GskRenderNode *
1548parse_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
1574static GskRenderNode *
1575parse_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
1604static gboolean
1605unpack_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
1637static GskRenderNode *
1638parse_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
1699static GskRenderNode *
1700parse_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
1721static GskRenderNode *
1722parse_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
1746static GskRenderNode *
1747parse_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
1768static GskRenderNode *
1769parse_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
1797static GskRenderNode *
1798parse_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
1819static gboolean
1820parse_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
1890static void
1891gsk_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
1909GskRenderNode *
1910gsk_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
1940typedef struct
1941{
1942 int indentation_level;
1943 GString *str;
1944} Printer;
1945
1946static void
1947printer_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 */
1954static 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
1962static void
1963start_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
1970static void
1971end_node (Printer *self)
1972{
1973 self->indentation_level --;
1974 _indent (self);
1975 g_string_append (string: self->str, val: "}\n");
1976}
1977
1978static void
1979string_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
1989static void
1990append_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
2002static void
2003append_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
2064static void
2065append_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
2075static void
2076append_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
2084static void
2085append_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
2097static void
2098append_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
2113static void
2114append_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
2125static void
2126append_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
2137static void
2138append_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
2149static void
2150append_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
2161static void
2162append_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
2173static void
2174append_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
2185static void
2186append_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
2203static void
2204append_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
2215static void render_node_print (Printer *p,
2216 GskRenderNode *node);
2217
2218static void
2219append_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
2228static void
2229append_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
2252static cairo_status_t
2253cairo_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
2262static void
2263cairo_destroy_array (gpointer array)
2264{
2265 g_byte_array_free (array, TRUE);
2266}
2267
2268static void
2269append_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 */
2286static char *
2287base64_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
2313void
2314gsk_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
2394static void
2395render_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 **/
3023GBytes *
3024gsk_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

source code of gtk/gsk/gskrendernodeparser.c