mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-10 10:50:10 +00:00
20d1bc2ccc
When printing, behave the same way as when parsing: Magically skip a container node if there is one - just like the parser magically creates a container node to hold all the nodes it parses.
2011 lines
54 KiB
C
2011 lines
54 KiB
C
/*
|
|
* Copyright © 2019 Benjamin Otte
|
|
* Timm Bäder
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors: Benjamin Otte <otte@gnome.org>
|
|
* Timm Bäder <mail@baedert.org>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gskrendernodeparserprivate.h"
|
|
|
|
#include "gskroundedrectprivate.h"
|
|
#include "gskrendernodeprivate.h"
|
|
#include "gsktransformprivate.h"
|
|
|
|
#include "gdk/gdkrgbaprivate.h"
|
|
#include "gdk/gdktextureprivate.h"
|
|
#include <gtk/css/gtkcss.h>
|
|
#include "gtk/css/gtkcssdataurlprivate.h"
|
|
#include "gtk/css/gtkcssparserprivate.h"
|
|
|
|
typedef struct _Declaration Declaration;
|
|
|
|
struct _Declaration
|
|
{
|
|
const char *name;
|
|
gboolean (* parse_func) (GtkCssParser *parser, gpointer result);
|
|
void (* clear_func) (gpointer data);
|
|
gpointer result;
|
|
};
|
|
|
|
static gboolean
|
|
parse_rect (GtkCssParser *parser,
|
|
gpointer out_rect)
|
|
{
|
|
double numbers[4];
|
|
|
|
if (!gtk_css_parser_consume_number (parser, &numbers[0]) ||
|
|
!gtk_css_parser_consume_number (parser, &numbers[1]) ||
|
|
!gtk_css_parser_consume_number (parser, &numbers[2]) ||
|
|
!gtk_css_parser_consume_number (parser, &numbers[3]))
|
|
return FALSE;
|
|
|
|
graphene_rect_init (out_rect, numbers[0], numbers[1], numbers[2], numbers[3]);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_texture (GtkCssParser *parser,
|
|
gpointer out_data)
|
|
{
|
|
GdkTexture *texture;
|
|
GError *error = NULL;
|
|
GtkCssLocation start_location;
|
|
char *url, *scheme;
|
|
|
|
start_location = *gtk_css_parser_get_start_location (parser);
|
|
url = gtk_css_parser_consume_url (parser);
|
|
if (url == NULL)
|
|
return FALSE;
|
|
|
|
scheme = g_uri_parse_scheme (url);
|
|
if (scheme && g_ascii_strcasecmp (scheme, "data") == 0)
|
|
{
|
|
GInputStream *stream;
|
|
GdkPixbuf *pixbuf;
|
|
GBytes *bytes;
|
|
|
|
texture = NULL;
|
|
|
|
bytes = gtk_css_data_url_parse (url, NULL, &error);
|
|
if (bytes)
|
|
{
|
|
stream = g_memory_input_stream_new_from_bytes (bytes);
|
|
pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, &error);
|
|
g_object_unref (stream);
|
|
if (pixbuf != NULL)
|
|
{
|
|
texture = gdk_texture_new_for_pixbuf (pixbuf);
|
|
g_object_unref (pixbuf);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GFile *file;
|
|
|
|
file = gtk_css_parser_resolve_url (parser, url);
|
|
texture = gdk_texture_new_from_file (file, &error);
|
|
g_object_unref (file);
|
|
}
|
|
|
|
g_free (scheme);
|
|
g_free (url);
|
|
|
|
if (texture == NULL)
|
|
{
|
|
gtk_css_parser_emit_error (parser,
|
|
&start_location,
|
|
gtk_css_parser_get_end_location (parser),
|
|
error);
|
|
g_clear_error (&error);
|
|
return FALSE;
|
|
}
|
|
|
|
*(GdkTexture **) out_data = texture;
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
clear_texture (gpointer inout_texture)
|
|
{
|
|
g_clear_object ((GdkTexture **) inout_texture);
|
|
}
|
|
|
|
static gboolean
|
|
parse_rounded_rect (GtkCssParser *parser,
|
|
gpointer out_rect)
|
|
{
|
|
graphene_rect_t r;
|
|
graphene_size_t corners[4];
|
|
double d;
|
|
guint i;
|
|
|
|
if (!parse_rect (parser, &r))
|
|
return FALSE;
|
|
|
|
if (!gtk_css_parser_try_delim (parser, '/'))
|
|
{
|
|
gsk_rounded_rect_init_from_rect (out_rect, &r, 0);
|
|
return TRUE;
|
|
}
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
if (!gtk_css_parser_has_number (parser))
|
|
break;
|
|
if (!gtk_css_parser_consume_number (parser, &d))
|
|
return FALSE;
|
|
corners[i].width = d;
|
|
}
|
|
|
|
if (i == 0)
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Expected a number");
|
|
return FALSE;
|
|
}
|
|
|
|
/* The magic (i - 1) >> 1 below makes it take the correct value
|
|
* according to spec. Feel free to check the 4 cases
|
|
*/
|
|
for (; i < 4; i++)
|
|
corners[i].width = corners[(i - 1) >> 1].width;
|
|
|
|
if (gtk_css_parser_try_delim (parser, '/'))
|
|
{
|
|
gtk_css_parser_consume_token (parser);
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
if (!gtk_css_parser_has_number (parser))
|
|
break;
|
|
if (!gtk_css_parser_consume_number (parser, &d))
|
|
return FALSE;
|
|
corners[i].height = d;
|
|
}
|
|
|
|
if (i == 0)
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Expected a number");
|
|
return FALSE;
|
|
}
|
|
|
|
for (; i < 4; i++)
|
|
corners[i].height = corners[(i - 1) >> 1].height;
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < 4; i++)
|
|
corners[i].height = corners[i].width;
|
|
}
|
|
|
|
gsk_rounded_rect_init (out_rect, &r, &corners[0], &corners[1], &corners[2], &corners[3]);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_color (GtkCssParser *parser,
|
|
gpointer out_color)
|
|
{
|
|
return gdk_rgba_parser_parse (parser, out_color);
|
|
}
|
|
|
|
static gboolean
|
|
parse_double (GtkCssParser *parser,
|
|
gpointer out_double)
|
|
{
|
|
return gtk_css_parser_consume_number (parser, out_double);
|
|
}
|
|
|
|
static gboolean
|
|
parse_point (GtkCssParser *parser,
|
|
gpointer out_point)
|
|
{
|
|
double x, y;
|
|
|
|
if (!gtk_css_parser_consume_number (parser, &x) ||
|
|
!gtk_css_parser_consume_number (parser, &y))
|
|
return FALSE;
|
|
|
|
graphene_point_init (out_point, x, y);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_transform (GtkCssParser *parser,
|
|
gpointer out_transform)
|
|
{
|
|
GskTransform *transform;
|
|
|
|
if (!gsk_transform_parser_parse (parser, &transform))
|
|
{
|
|
gsk_transform_unref (transform);
|
|
return FALSE;
|
|
}
|
|
|
|
*(GskTransform **) out_transform = transform;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
clear_transform (gpointer inout_transform)
|
|
{
|
|
g_clear_pointer ((GskTransform **) inout_transform, gsk_transform_unref);
|
|
}
|
|
|
|
static gboolean
|
|
parse_string (GtkCssParser *parser,
|
|
gpointer out_string)
|
|
{
|
|
const GtkCssToken *token;
|
|
char *s;
|
|
|
|
token = gtk_css_parser_get_token (parser);
|
|
if (!gtk_css_token_is (token, GTK_CSS_TOKEN_STRING))
|
|
return FALSE;
|
|
|
|
s = g_strdup (token->string.string);
|
|
gtk_css_parser_consume_token (parser);
|
|
|
|
g_free (*(char **) out_string);
|
|
*(char **) out_string = s;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
clear_string (gpointer inout_string)
|
|
{
|
|
g_clear_pointer ((char **) inout_string, g_free);
|
|
}
|
|
|
|
static gboolean
|
|
parse_stops (GtkCssParser *parser,
|
|
gpointer out_stops)
|
|
{
|
|
GArray *stops;
|
|
GskColorStop stop;
|
|
|
|
stops = g_array_new (FALSE, FALSE, sizeof (GskColorStop));
|
|
|
|
for (;;)
|
|
{
|
|
if (!gtk_css_parser_consume_number (parser, &stop.offset))
|
|
goto error;
|
|
|
|
if (!gdk_rgba_parser_parse (parser, &stop.color))
|
|
goto error;
|
|
|
|
if (stops->len == 0 && stop.offset < 0)
|
|
gtk_css_parser_error_value (parser, "Color stop offset must be >= 0");
|
|
else if (stops->len > 0 && stop.offset < g_array_index (stops, GskColorStop, stops->len - 1).offset)
|
|
gtk_css_parser_error_value (parser, "Color stop offset must be >= previous value");
|
|
else if (stop.offset > 1)
|
|
gtk_css_parser_error_value (parser, "Color stop offset must be <= 1");
|
|
else
|
|
g_array_append_val (stops, stop);
|
|
|
|
if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_COMMA))
|
|
gtk_css_parser_skip (parser);
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (stops->len < 2)
|
|
{
|
|
gtk_css_parser_error_value (parser, "At least 2 color stops need to be specified");
|
|
g_array_free (stops, TRUE);
|
|
return FALSE;
|
|
}
|
|
|
|
if (*(GArray **) out_stops)
|
|
g_array_free (*(GArray **) out_stops, TRUE);
|
|
*(GArray **) out_stops = stops;
|
|
|
|
return TRUE;
|
|
|
|
error:
|
|
g_array_free (stops, TRUE);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
clear_stops (gpointer inout_stops)
|
|
{
|
|
GArray **stops = (GArray **) inout_stops;
|
|
|
|
if (*stops)
|
|
{
|
|
g_array_free (*stops, TRUE);
|
|
*stops = NULL;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
parse_float4 (GtkCssParser *parser,
|
|
gpointer out_floats)
|
|
{
|
|
float *floats = (float *) out_floats;
|
|
double d[4];
|
|
int i;
|
|
|
|
for (i = 0; i < 4 && !gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF); i ++)
|
|
{
|
|
if (!gtk_css_parser_consume_number (parser, &d[i]))
|
|
return FALSE;
|
|
}
|
|
if (i == 0)
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Expected a color");
|
|
return FALSE;
|
|
}
|
|
for (; i < 4; i++)
|
|
{
|
|
d[i] = d[(i - 1) >> 1];
|
|
}
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
floats[i] = d[i];
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_colors4 (GtkCssParser *parser,
|
|
gpointer out_colors)
|
|
{
|
|
GdkRGBA colors[4];
|
|
int i;
|
|
|
|
for (i = 0; i < 4 && !gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF); i ++)
|
|
{
|
|
if (!gdk_rgba_parser_parse (parser, &colors[i]))
|
|
return FALSE;
|
|
}
|
|
if (i == 0)
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Expected a color");
|
|
return FALSE;
|
|
}
|
|
for (; i < 4; i++)
|
|
{
|
|
colors[i] = colors[(i - 1) >> 1];
|
|
}
|
|
|
|
memcpy (out_colors, colors, sizeof (GdkRGBA) * 4);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_shadows (GtkCssParser *parser,
|
|
gpointer out_shadows)
|
|
{
|
|
GArray *shadows = out_shadows;
|
|
|
|
for (;;)
|
|
{
|
|
GskShadow shadow = { {0, 0, 0, 1}, 0, 0, 0 };
|
|
double dx = 0, dy = 0, radius = 0;
|
|
|
|
if (!gdk_rgba_parser_parse (parser, &shadow.color))
|
|
gtk_css_parser_error_value (parser, "Expected shadow color");
|
|
|
|
if (!gtk_css_parser_consume_number (parser, &dx))
|
|
gtk_css_parser_error_value (parser, "Expected shadow x offset");
|
|
|
|
if (!gtk_css_parser_consume_number (parser, &dy))
|
|
gtk_css_parser_error_value (parser, "Expected shadow x offset");
|
|
|
|
if (!gtk_css_parser_consume_number (parser, &radius))
|
|
gtk_css_parser_error_value (parser, "Expected shadow blur radius");
|
|
|
|
shadow.dx = dx;
|
|
shadow.dy = dy;
|
|
shadow.radius = radius;
|
|
|
|
g_array_append_val (shadows, shadow);
|
|
|
|
if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_COMMA))
|
|
gtk_css_parser_skip (parser);
|
|
else
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
clear_shadows (gpointer inout_shadows)
|
|
{
|
|
g_array_set_size (*(GArray **) inout_shadows, 0);
|
|
}
|
|
|
|
static const struct
|
|
{
|
|
GskBlendMode mode;
|
|
const char *name;
|
|
} blend_modes[] = {
|
|
{ GSK_BLEND_MODE_DEFAULT, "normal" },
|
|
{ GSK_BLEND_MODE_MULTIPLY, "multiply" },
|
|
{ GSK_BLEND_MODE_SCREEN, "screen" },
|
|
{ GSK_BLEND_MODE_OVERLAY, "overlay" },
|
|
{ GSK_BLEND_MODE_DARKEN, "darken" },
|
|
{ GSK_BLEND_MODE_LIGHTEN, "lighten" },
|
|
{ GSK_BLEND_MODE_COLOR_DODGE, "color-dodge" },
|
|
{ GSK_BLEND_MODE_COLOR_BURN, "color-burn" },
|
|
{ GSK_BLEND_MODE_HARD_LIGHT, "hard-light" },
|
|
{ GSK_BLEND_MODE_SOFT_LIGHT, "soft-light" },
|
|
{ GSK_BLEND_MODE_DIFFERENCE, "difference" },
|
|
{ GSK_BLEND_MODE_EXCLUSION, "exclusion" },
|
|
{ GSK_BLEND_MODE_COLOR, "color" },
|
|
{ GSK_BLEND_MODE_HUE, "hue" },
|
|
{ GSK_BLEND_MODE_SATURATION, "saturation" },
|
|
{ GSK_BLEND_MODE_LUMINOSITY, "luminosity" }
|
|
};
|
|
|
|
static gboolean
|
|
parse_blend_mode (GtkCssParser *parser,
|
|
gpointer out_mode)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (blend_modes); i++)
|
|
{
|
|
if (gtk_css_parser_try_ident (parser, blend_modes[i].name))
|
|
{
|
|
*(GskBlendMode *) out_mode = blend_modes[i].mode;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_font (GtkCssParser *parser,
|
|
gpointer out_font)
|
|
{
|
|
const GtkCssToken *token;
|
|
PangoFontDescription *desc;
|
|
PangoFontMap *font_map;
|
|
PangoContext *context;
|
|
PangoFont *font;
|
|
|
|
token = gtk_css_parser_get_token (parser);
|
|
if (!gtk_css_token_is (token, GTK_CSS_TOKEN_STRING))
|
|
return FALSE;
|
|
|
|
desc = pango_font_description_from_string (token->string.string);
|
|
font_map = pango_cairo_font_map_get_default ();
|
|
context = pango_font_map_create_context (font_map);
|
|
font = pango_font_map_load_font (font_map, context, desc);
|
|
|
|
pango_font_description_free (desc);
|
|
g_object_unref (context);
|
|
|
|
*((PangoFont**)out_font) = font;
|
|
|
|
/* Skip font name token */
|
|
gtk_css_parser_consume_token (parser);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
clear_font (gpointer inout_font)
|
|
{
|
|
g_clear_object ((PangoFont **) inout_font);
|
|
}
|
|
|
|
static gboolean
|
|
parse_glyphs (GtkCssParser *parser,
|
|
gpointer out_glyphs)
|
|
{
|
|
GArray *glyphs;
|
|
PangoGlyphString *glyph_string;
|
|
int i;
|
|
|
|
glyphs = g_array_new (FALSE, TRUE, sizeof (double[5]));
|
|
|
|
for (;;)
|
|
{
|
|
double values[5];
|
|
|
|
/* We have 5 numbers per glyph */
|
|
for (i = 0; i < 5; i ++)
|
|
{
|
|
if (!gtk_css_parser_consume_number (parser, &values[i]))
|
|
return FALSE;
|
|
}
|
|
|
|
g_array_append_val (glyphs, values);
|
|
|
|
if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_COMMA))
|
|
gtk_css_parser_skip (parser);
|
|
else
|
|
break;
|
|
}
|
|
|
|
glyph_string = pango_glyph_string_new ();
|
|
pango_glyph_string_set_size (glyph_string, glyphs->len);
|
|
|
|
for (i = 0; i < glyphs->len; i ++)
|
|
{
|
|
PangoGlyphInfo g;
|
|
double *v = (double *)(glyphs->data + (i * sizeof (double) * 5));
|
|
|
|
g.glyph = (guint)v[0];
|
|
g.geometry.width = (int)v[1];
|
|
g.geometry.x_offset = (int)v[2];
|
|
g.geometry.y_offset = (int)v[3];
|
|
g.attr.is_cluster_start = (int)v[4];
|
|
|
|
glyph_string->glyphs[i] = g;
|
|
}
|
|
|
|
g_array_free (glyphs, TRUE);
|
|
|
|
*((PangoGlyphString **)out_glyphs) = glyph_string;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
clear_glyphs (gpointer inout_glyphs)
|
|
{
|
|
g_clear_pointer ((PangoGlyphString **) inout_glyphs, pango_glyph_string_free);
|
|
}
|
|
|
|
static gboolean
|
|
parse_node (GtkCssParser *parser, gpointer out_node);
|
|
|
|
static void
|
|
clear_node (gpointer inout_node)
|
|
{
|
|
g_clear_pointer ((GskRenderNode **) inout_node, gsk_render_node_unref);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_container_node (GtkCssParser *parser)
|
|
{
|
|
GskRenderNode *node;
|
|
GPtrArray *nodes;
|
|
const GtkCssToken *token;
|
|
|
|
nodes = g_ptr_array_new_with_free_func ((GDestroyNotify) gsk_render_node_unref);
|
|
|
|
for (token = gtk_css_parser_get_token (parser);
|
|
!gtk_css_token_is (token, GTK_CSS_TOKEN_EOF);
|
|
token = gtk_css_parser_get_token (parser))
|
|
{
|
|
node = NULL;
|
|
/* We don't wand a semicolon here, but the parse_node function will figure
|
|
* that out itself and return an error if we encounter one.
|
|
*/
|
|
gtk_css_parser_start_semicolon_block (parser, GTK_CSS_TOKEN_OPEN_CURLY);
|
|
|
|
if (parse_node (parser, &node))
|
|
g_ptr_array_add (nodes, node);
|
|
|
|
gtk_css_parser_end_block (parser);
|
|
}
|
|
|
|
node = gsk_container_node_new ((GskRenderNode **) nodes->pdata, nodes->len);
|
|
|
|
g_ptr_array_unref (nodes);
|
|
|
|
return node;
|
|
}
|
|
|
|
static guint
|
|
parse_declarations (GtkCssParser *parser,
|
|
const Declaration *declarations,
|
|
guint n_declarations)
|
|
{
|
|
guint parsed = 0;
|
|
guint i;
|
|
|
|
g_assert (n_declarations < 8 * sizeof (guint));
|
|
|
|
while (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
|
|
{
|
|
gtk_css_parser_start_semicolon_block (parser, GTK_CSS_TOKEN_OPEN_CURLY);
|
|
|
|
for (i = 0; i < n_declarations; i++)
|
|
{
|
|
if (gtk_css_parser_try_ident (parser, declarations[i].name))
|
|
{
|
|
if (!gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COLON))
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Expected ':' after variable declaration");
|
|
}
|
|
else
|
|
{
|
|
if (parsed & (1 << i))
|
|
{
|
|
gtk_css_parser_warn_syntax (parser, "Variable \"%s\" defined multiple times", declarations[i].name);
|
|
/* Unset, just to be sure */
|
|
parsed &= ~(1 << i);
|
|
if (declarations[i].clear_func)
|
|
declarations[i].clear_func (declarations[i].result);
|
|
}
|
|
if (!declarations[i].parse_func (parser, declarations[i].result))
|
|
{
|
|
/* nothing to do */
|
|
}
|
|
else if (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Expected ';' at end of statement");
|
|
if (declarations[i].clear_func)
|
|
declarations[i].clear_func (declarations[i].result);
|
|
}
|
|
else
|
|
{
|
|
parsed |= (1 << i);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (i == n_declarations)
|
|
{
|
|
if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_IDENT))
|
|
gtk_css_parser_error_syntax (parser, "No variable named \"%s\"",
|
|
gtk_css_parser_get_token (parser)->string.string);
|
|
else
|
|
gtk_css_parser_error_syntax (parser, "Expected a variable name");
|
|
}
|
|
|
|
gtk_css_parser_end_block (parser);
|
|
}
|
|
|
|
return parsed;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_color_node (GtkCssParser *parser)
|
|
{
|
|
graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 0, 0);
|
|
GdkRGBA color = { 0, 0, 0, 1 };
|
|
const Declaration declarations[] = {
|
|
{ "bounds", parse_rect, NULL, &bounds },
|
|
{ "color", parse_color, NULL, &color },
|
|
};
|
|
|
|
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
|
|
return gsk_color_node_new (&color, &bounds);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_linear_gradient_node (GtkCssParser *parser)
|
|
{
|
|
graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 0, 0);
|
|
graphene_point_t start = GRAPHENE_POINT_INIT (0, 0);
|
|
graphene_point_t end = GRAPHENE_POINT_INIT (0, 0);
|
|
GArray *stops = NULL;
|
|
const Declaration declarations[] = {
|
|
{ "bounds", parse_rect, NULL, &bounds },
|
|
{ "start", parse_point, NULL, &start },
|
|
{ "end", parse_point, NULL, &end },
|
|
{ "stops", parse_stops, clear_stops, &stops },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
if (stops == NULL)
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "No color stops given");
|
|
return NULL;
|
|
}
|
|
|
|
result = gsk_linear_gradient_node_new (&bounds, &start, &end, (GskColorStop *) stops->data, stops->len);
|
|
|
|
g_array_free (stops, TRUE);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_inset_shadow_node (GtkCssParser *parser)
|
|
{
|
|
GskRoundedRect outline = GSK_ROUNDED_RECT_INIT (0, 0, 0, 0);
|
|
GdkRGBA color = { 0, 0, 0, 1 };
|
|
double dx = 1, dy = 1, blur = 0, spread = 0;
|
|
const Declaration declarations[] = {
|
|
{ "outline", parse_rounded_rect, NULL, &outline },
|
|
{ "color", parse_color, NULL, &color },
|
|
{ "dx", parse_double, NULL, &dx },
|
|
{ "dy", parse_double, NULL, &dy },
|
|
{ "spread", parse_double, NULL, &spread },
|
|
{ "blur", parse_double, NULL, &blur }
|
|
};
|
|
|
|
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
|
|
return gsk_inset_shadow_node_new (&outline, &color, dx, dy, spread, blur);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_border_node (GtkCssParser *parser)
|
|
{
|
|
GskRoundedRect outline = GSK_ROUNDED_RECT_INIT (0, 0, 0, 0);
|
|
float widths[4] = { 1, 1, 1, 1 };
|
|
GdkRGBA colors[4] = { { 0, 0, 0, 1 }, {0, 0, 0, 1 }, {0, 0, 0, 1 }, { 0, 0, 0, 1 } };
|
|
const Declaration declarations[] = {
|
|
{ "outline", parse_rounded_rect, NULL, &outline },
|
|
{ "widths", parse_float4, NULL, &widths },
|
|
{ "colors", parse_colors4, NULL, &colors }
|
|
};
|
|
|
|
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
|
|
return gsk_border_node_new (&outline, widths, colors);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_texture_node (GtkCssParser *parser)
|
|
{
|
|
graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 0, 0);
|
|
GdkTexture *texture = NULL;
|
|
const Declaration declarations[] = {
|
|
{ "bounds", parse_rect, NULL, &bounds },
|
|
{ "texture", parse_texture, clear_texture, &texture }
|
|
};
|
|
GskRenderNode *node;
|
|
|
|
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
|
|
if (texture == NULL)
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Missing \"texture\" property definition");
|
|
return NULL;
|
|
}
|
|
|
|
node = gsk_texture_node_new (texture, &bounds);
|
|
g_object_unref (texture);
|
|
|
|
return node;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_outset_shadow_node (GtkCssParser *parser)
|
|
{
|
|
GskRoundedRect outline = GSK_ROUNDED_RECT_INIT (0, 0, 0, 0);
|
|
GdkRGBA color = { 0, 0, 0, 1 };
|
|
double dx = 1, dy = 1, blur = 0, spread = 0;
|
|
const Declaration declarations[] = {
|
|
{ "outline", parse_rounded_rect, NULL, &outline },
|
|
{ "color", parse_color, NULL, &color },
|
|
{ "dx", parse_double, NULL, &dx },
|
|
{ "dy", parse_double, NULL, &dy },
|
|
{ "spread", parse_double, NULL, &spread },
|
|
{ "blur", parse_double, NULL, &blur }
|
|
};
|
|
|
|
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
|
|
return gsk_outset_shadow_node_new (&outline, &color, dx, dy, spread, blur);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_transform_node (GtkCssParser *parser)
|
|
{
|
|
GskRenderNode *child = NULL;
|
|
GskTransform *transform = NULL;
|
|
const Declaration declarations[] = {
|
|
{ "transform", parse_transform, clear_transform, &transform },
|
|
{ "child", parse_node, clear_node, &child },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
if (child == NULL)
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
|
|
gsk_transform_unref (transform);
|
|
return NULL;
|
|
}
|
|
/* This is very much cheating, isn't it? */
|
|
if (transform == NULL)
|
|
transform = gsk_transform_new ();
|
|
|
|
result = gsk_transform_node_new (child, transform);
|
|
|
|
gsk_render_node_unref (child);
|
|
gsk_transform_unref (transform);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_opacity_node (GtkCssParser *parser)
|
|
{
|
|
GskRenderNode *child = NULL;
|
|
double opacity = 1.0;
|
|
const Declaration declarations[] = {
|
|
{ "opacity", parse_double, NULL, &opacity },
|
|
{ "child", parse_node, clear_node, &child },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
if (child == NULL)
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
|
|
return NULL;
|
|
}
|
|
|
|
result = gsk_opacity_node_new (child, opacity);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_color_matrix_node (GtkCssParser *parser)
|
|
{
|
|
GskRenderNode *child = NULL;
|
|
graphene_matrix_t matrix;
|
|
GskTransform *transform = NULL;
|
|
graphene_rect_t offset_rect = GRAPHENE_RECT_INIT (0, 0, 0, 0);
|
|
graphene_vec4_t offset;
|
|
const Declaration declarations[] = {
|
|
{ "matrix", parse_transform, clear_transform, &transform },
|
|
{ "offset", parse_rect, NULL, &offset_rect },
|
|
{ "child", parse_node, clear_node, &child }
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
if (child == NULL)
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
|
|
return NULL;
|
|
}
|
|
|
|
graphene_vec4_init (&offset,
|
|
offset_rect.origin.x, offset_rect.origin.y,
|
|
offset_rect.size.width, offset_rect.size.height);
|
|
|
|
gsk_transform_to_matrix (transform, &matrix);
|
|
|
|
result = gsk_color_matrix_node_new (child, &matrix, &offset);
|
|
|
|
gsk_transform_unref (transform);
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_cross_fade_node (GtkCssParser *parser)
|
|
{
|
|
GskRenderNode *start = NULL;
|
|
GskRenderNode *end = NULL;
|
|
double progress = 0.5;
|
|
const Declaration declarations[] = {
|
|
{ "progress", parse_double, NULL, &progress },
|
|
{ "start", parse_node, clear_node, &start },
|
|
{ "end", parse_node, clear_node, &end },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
if (start == NULL || end == NULL)
|
|
{
|
|
if (start == NULL)
|
|
gtk_css_parser_error_syntax (parser, "Missing \"start\" property definition");
|
|
if (end == NULL)
|
|
gtk_css_parser_error_syntax (parser, "Missing \"end\" property definition");
|
|
g_clear_pointer (&start, gsk_render_node_unref);
|
|
g_clear_pointer (&end, gsk_render_node_unref);
|
|
return NULL;
|
|
}
|
|
|
|
result = gsk_cross_fade_node_new (start, end, progress);
|
|
|
|
gsk_render_node_unref (start);
|
|
gsk_render_node_unref (end);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_blend_node (GtkCssParser *parser)
|
|
{
|
|
GskRenderNode *bottom = NULL;
|
|
GskRenderNode *top = NULL;
|
|
GskBlendMode mode = GSK_BLEND_MODE_DEFAULT;
|
|
const Declaration declarations[] = {
|
|
{ "mode", parse_blend_mode, NULL, &mode },
|
|
{ "bottom", parse_node, clear_node, &bottom },
|
|
{ "top", parse_node, clear_node, &top },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
if (bottom == NULL || top == NULL)
|
|
{
|
|
if (bottom == NULL)
|
|
gtk_css_parser_error_syntax (parser, "Missing \"bottom\" property definition");
|
|
if (top == NULL)
|
|
gtk_css_parser_error_syntax (parser, "Missing \"top\" property definition");
|
|
g_clear_pointer (&bottom, gsk_render_node_unref);
|
|
g_clear_pointer (&top, gsk_render_node_unref);
|
|
return NULL;
|
|
}
|
|
|
|
result = gsk_blend_node_new (bottom, top, mode);
|
|
|
|
gsk_render_node_unref (bottom);
|
|
gsk_render_node_unref (top);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_repeat_node (GtkCssParser *parser)
|
|
{
|
|
GskRenderNode *child = NULL;
|
|
graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 0, 0);
|
|
graphene_rect_t child_bounds = GRAPHENE_RECT_INIT (0, 0, 0, 0);
|
|
const Declaration declarations[] = {
|
|
{ "child", parse_node, clear_node, &child },
|
|
{ "bounds", parse_rect, NULL, &bounds },
|
|
{ "child-bounds", parse_rect, NULL, &child_bounds },
|
|
};
|
|
GskRenderNode *result;
|
|
guint parse_result;
|
|
|
|
parse_result = parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
if (child == NULL)
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
|
|
return NULL;
|
|
}
|
|
|
|
if (!(parse_result & (1 << 1)))
|
|
gsk_render_node_get_bounds (child, &bounds);
|
|
if (!(parse_result & (1 << 2)))
|
|
gsk_render_node_get_bounds (child, &child_bounds);
|
|
|
|
result = gsk_repeat_node_new (&bounds, child, &child_bounds);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_text_node (GtkCssParser *parser)
|
|
{
|
|
PangoFont *font = NULL;
|
|
double x = 0;
|
|
double y = 0;
|
|
GdkRGBA color = { 0, 0, 0, 1 };
|
|
PangoGlyphString *glyphs = NULL;
|
|
const Declaration declarations[] = {
|
|
{ "font", parse_font, clear_font, &font },
|
|
{ "x", parse_double, NULL, &x },
|
|
{ "y", parse_double, NULL, &y },
|
|
{ "color", parse_color, NULL, &color },
|
|
{ "glyphs", parse_glyphs, clear_glyphs, &glyphs }
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
|
|
if (!font || !glyphs)
|
|
return NULL;
|
|
|
|
result = gsk_text_node_new (font, glyphs, &color, x, y);
|
|
|
|
g_object_unref (font);
|
|
pango_glyph_string_free (glyphs);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_blur_node (GtkCssParser *parser)
|
|
{
|
|
GskRenderNode *child = NULL;
|
|
double blur_radius = 1.0;
|
|
const Declaration declarations[] = {
|
|
{ "blur", parse_double, NULL, &blur_radius },
|
|
{ "child", parse_node, clear_node, &child },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
if (child == NULL)
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
|
|
return NULL;
|
|
}
|
|
|
|
result = gsk_blur_node_new (child, blur_radius);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_clip_node (GtkCssParser *parser)
|
|
{
|
|
graphene_rect_t clip = GRAPHENE_RECT_INIT (0, 0, 0, 0);
|
|
GskRenderNode *child = NULL;
|
|
const Declaration declarations[] = {
|
|
{ "clip", parse_rect, NULL, &clip },
|
|
{ "child", parse_node, clear_node, &child },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
if (child == NULL)
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
|
|
return NULL;
|
|
}
|
|
|
|
result = gsk_clip_node_new (child, &clip);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_rounded_clip_node (GtkCssParser *parser)
|
|
{
|
|
GskRoundedRect clip = GSK_ROUNDED_RECT_INIT (0, 0, 0, 0);
|
|
GskRenderNode *child = NULL;
|
|
const Declaration declarations[] = {
|
|
{ "clip", parse_rounded_rect, NULL, &clip },
|
|
{ "child", parse_node, clear_node, &child },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
if (child == NULL)
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
|
|
return NULL;
|
|
}
|
|
|
|
result = gsk_rounded_clip_node_new (child, &clip);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_shadow_node (GtkCssParser *parser)
|
|
{
|
|
GskRenderNode *child = NULL;
|
|
GArray *shadows = g_array_new (FALSE, TRUE, sizeof (GskShadow));
|
|
const Declaration declarations[] = {
|
|
{ "child", parse_node, clear_node, &child },
|
|
{ "shadows", parse_shadows, clear_shadows, shadows }
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
if (child == NULL)
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
|
|
return NULL;
|
|
}
|
|
|
|
if (shadows->len == 0)
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Need at least one shadow");
|
|
return child;
|
|
}
|
|
|
|
result = gsk_shadow_node_new (child, (GskShadow *)shadows->data, shadows->len);
|
|
|
|
g_array_free (shadows, TRUE);
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_debug_node (GtkCssParser *parser)
|
|
{
|
|
char *message = NULL;
|
|
GskRenderNode *child = NULL;
|
|
const Declaration declarations[] = {
|
|
{ "message", parse_string, clear_string, &message},
|
|
{ "child", parse_node, clear_node, &child },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, declarations, G_N_ELEMENTS(declarations));
|
|
if (child == NULL)
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Missing \"child\" property definition");
|
|
return NULL;
|
|
}
|
|
|
|
result = gsk_debug_node_new (child, message);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
parse_node (GtkCssParser *parser,
|
|
gpointer out_node)
|
|
{
|
|
static struct {
|
|
const char *name;
|
|
GskRenderNode * (* func) (GtkCssParser *);
|
|
} node_parsers[] = {
|
|
{ "container", parse_container_node },
|
|
{ "color", parse_color_node },
|
|
{ "linear-gradient", parse_linear_gradient_node },
|
|
{ "border", parse_border_node },
|
|
{ "texture", parse_texture_node },
|
|
{ "inset-shadow", parse_inset_shadow_node },
|
|
{ "outset-shadow", parse_outset_shadow_node },
|
|
{ "transform", parse_transform_node },
|
|
{ "opacity", parse_opacity_node },
|
|
{ "color-matrix", parse_color_matrix_node },
|
|
{ "clip", parse_clip_node },
|
|
{ "rounded-clip", parse_rounded_clip_node },
|
|
{ "shadow", parse_shadow_node },
|
|
{ "cross-fade", parse_cross_fade_node },
|
|
{ "text", parse_text_node },
|
|
{ "blur", parse_blur_node },
|
|
{ "debug", parse_debug_node },
|
|
{ "blend", parse_blend_node },
|
|
{ "repeat", parse_repeat_node },
|
|
#if 0
|
|
{ "cairo", parse_cairo_node },
|
|
#endif
|
|
|
|
};
|
|
GskRenderNode **node_p = out_node;
|
|
guint i;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (node_parsers); i++)
|
|
{
|
|
if (gtk_css_parser_try_ident (parser, node_parsers[i].name))
|
|
{
|
|
GskRenderNode *node;
|
|
|
|
if (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Expected '{' after node name");
|
|
return FALSE;
|
|
}
|
|
gtk_css_parser_end_block_prelude (parser);
|
|
node = node_parsers[i].func (parser);
|
|
if (node)
|
|
{
|
|
if (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
|
|
gtk_css_parser_error_syntax (parser, "Expected '}' at end of node definition");
|
|
g_clear_pointer (node_p, gsk_render_node_unref);
|
|
*node_p = node;
|
|
}
|
|
|
|
return node != NULL;
|
|
}
|
|
}
|
|
|
|
if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_IDENT))
|
|
gtk_css_parser_error_value (parser, "\"%s\" is not a valid node name",
|
|
gtk_css_parser_get_token (parser)->string.string);
|
|
else
|
|
gtk_css_parser_error_syntax (parser, "Expected a node name");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
gsk_render_node_parser_error (GtkCssParser *parser,
|
|
const GtkCssLocation *start,
|
|
const GtkCssLocation *end,
|
|
const GError *error,
|
|
gpointer user_data)
|
|
{
|
|
struct {
|
|
GskParseErrorFunc error_func;
|
|
gpointer user_data;
|
|
} *error_func_pair = user_data;
|
|
|
|
if (error_func_pair->error_func)
|
|
{
|
|
GtkCssSection *section = gtk_css_section_new (gtk_css_parser_get_file (parser), start, end);
|
|
|
|
error_func_pair->error_func (section, error, error_func_pair->user_data);
|
|
gtk_css_section_unref (section);
|
|
}
|
|
}
|
|
|
|
GskRenderNode *
|
|
gsk_render_node_deserialize_from_bytes (GBytes *bytes,
|
|
GskParseErrorFunc error_func,
|
|
gpointer user_data)
|
|
{
|
|
GskRenderNode *root = NULL;
|
|
GtkCssParser *parser;
|
|
struct {
|
|
GskParseErrorFunc error_func;
|
|
gpointer user_data;
|
|
} error_func_pair = { error_func, user_data };
|
|
|
|
parser = gtk_css_parser_new_for_bytes (bytes, NULL, NULL, gsk_render_node_parser_error,
|
|
&error_func_pair, NULL);
|
|
root = parse_container_node (parser);
|
|
|
|
if (root && gsk_container_node_get_n_children (root) == 1)
|
|
{
|
|
GskRenderNode *child = gsk_container_node_get_child (root, 0);
|
|
|
|
gsk_render_node_ref (child);
|
|
gsk_render_node_unref (root);
|
|
root = child;
|
|
}
|
|
|
|
gtk_css_parser_unref (parser);
|
|
|
|
return root;
|
|
}
|
|
|
|
|
|
typedef struct
|
|
{
|
|
int indentation_level;
|
|
GString *str;
|
|
} Printer;
|
|
|
|
static void
|
|
printer_init (Printer *self)
|
|
{
|
|
self->indentation_level = 0;
|
|
self->str = g_string_new (NULL);
|
|
}
|
|
|
|
#define IDENT_LEVEL 2 /* Spaces per level */
|
|
static void
|
|
_indent (Printer *self)
|
|
{
|
|
if (self->indentation_level > 0)
|
|
g_string_append_printf (self->str, "%*s", self->indentation_level * IDENT_LEVEL, " ");
|
|
}
|
|
#undef IDENT_LEVEL
|
|
|
|
static void
|
|
start_node (Printer *self,
|
|
const char *node_name)
|
|
{
|
|
g_string_append_printf (self->str, "%s {\n", node_name);
|
|
self->indentation_level ++;
|
|
}
|
|
|
|
static void
|
|
end_node (Printer *self)
|
|
{
|
|
self->indentation_level --;
|
|
_indent (self);
|
|
g_string_append (self->str, "}\n");
|
|
}
|
|
|
|
static void
|
|
string_append_double (GString *string,
|
|
double d)
|
|
{
|
|
char buf[G_ASCII_DTOSTR_BUF_SIZE];
|
|
|
|
g_ascii_formatd (buf, G_ASCII_DTOSTR_BUF_SIZE, "%g", d);
|
|
g_string_append (string, buf);
|
|
}
|
|
|
|
|
|
static void
|
|
append_rect (GString *str,
|
|
const graphene_rect_t *r)
|
|
{
|
|
string_append_double (str, r->origin.x);
|
|
g_string_append_c (str, ' ');
|
|
string_append_double (str, r->origin.y);
|
|
g_string_append_c (str, ' ');
|
|
string_append_double (str, r->size.width);
|
|
g_string_append_c (str, ' ');
|
|
string_append_double (str, r->size.height);
|
|
}
|
|
|
|
static void
|
|
append_rounded_rect (GString *str,
|
|
const GskRoundedRect *r)
|
|
{
|
|
append_rect (str, &r->bounds);
|
|
|
|
if (!gsk_rounded_rect_is_rectilinear (r))
|
|
{
|
|
gboolean all_the_same = TRUE;
|
|
gboolean all_square = TRUE;
|
|
float w = r->corner[0].width;
|
|
float h = r->corner[0].height;
|
|
int i;
|
|
|
|
for (i = 1; i < 4; i ++)
|
|
{
|
|
if (r->corner[i].width != w ||
|
|
r->corner[i].height != h)
|
|
all_the_same = FALSE;
|
|
|
|
if (r->corner[i].width != r->corner[i].height)
|
|
all_square = FALSE;
|
|
|
|
}
|
|
|
|
g_string_append (str, " / ");
|
|
|
|
if (all_the_same)
|
|
{
|
|
string_append_double (str, w);
|
|
}
|
|
else if (all_square)
|
|
{
|
|
string_append_double (str, r->corner[0].width);
|
|
g_string_append_c (str, ' ');
|
|
string_append_double (str, r->corner[1].width);
|
|
g_string_append_c (str, ' ');
|
|
string_append_double (str, r->corner[2].width);
|
|
g_string_append_c (str, ' ');
|
|
string_append_double (str, r->corner[3].width);
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < 4; i ++)
|
|
{
|
|
string_append_double (str, r->corner[i].width);
|
|
g_string_append_c (str, ' ');
|
|
}
|
|
|
|
g_string_append (str, "/ ");
|
|
|
|
for (i = 0; i < 3; i ++)
|
|
{
|
|
string_append_double (str, r->corner[i].height);
|
|
g_string_append_c (str, ' ');
|
|
}
|
|
|
|
string_append_double (str, r->corner[3].height);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
append_rgba (GString *str,
|
|
const GdkRGBA *rgba)
|
|
{
|
|
char *rgba_str = gdk_rgba_to_string (rgba);
|
|
|
|
g_string_append (str, rgba_str);
|
|
|
|
g_free (rgba_str);
|
|
}
|
|
|
|
static void
|
|
append_point (GString *str,
|
|
const graphene_point_t *p)
|
|
{
|
|
string_append_double (str, p->x);
|
|
g_string_append_c (str, ' ');
|
|
string_append_double (str, p->y);
|
|
}
|
|
|
|
static void
|
|
append_vec4 (GString *str,
|
|
const graphene_vec4_t *v)
|
|
{
|
|
string_append_double (str, graphene_vec4_get_x (v));
|
|
g_string_append_c (str, ' ');
|
|
string_append_double (str, graphene_vec4_get_y (v));
|
|
g_string_append_c (str, ' ');
|
|
string_append_double (str, graphene_vec4_get_z (v));
|
|
g_string_append_c (str, ' ');
|
|
string_append_double (str, graphene_vec4_get_w (v));
|
|
}
|
|
|
|
static void
|
|
append_float_param (Printer *p,
|
|
const char *param_name,
|
|
float value)
|
|
{
|
|
_indent (p);
|
|
g_string_append_printf (p->str, "%s: ", param_name);
|
|
string_append_double (p->str, value);
|
|
g_string_append (p->str, ";\n");
|
|
}
|
|
|
|
static void
|
|
append_rgba_param (Printer *p,
|
|
const char *param_name,
|
|
const GdkRGBA *value)
|
|
{
|
|
_indent (p);
|
|
g_string_append_printf (p->str, "%s: ", param_name);
|
|
append_rgba (p->str, value);
|
|
g_string_append_c (p->str, ';');
|
|
g_string_append_c (p->str, '\n');
|
|
}
|
|
|
|
static void
|
|
append_rect_param (Printer *p,
|
|
const char *param_name,
|
|
const graphene_rect_t *value)
|
|
{
|
|
_indent (p);
|
|
g_string_append_printf (p->str, "%s: ", param_name);
|
|
append_rect (p->str, value);
|
|
g_string_append_c (p->str, ';');
|
|
g_string_append_c (p->str, '\n');
|
|
}
|
|
|
|
static void
|
|
append_rounded_rect_param (Printer *p,
|
|
const char *param_name,
|
|
const GskRoundedRect *value)
|
|
{
|
|
_indent (p);
|
|
g_string_append_printf (p->str, "%s: ", param_name);
|
|
append_rounded_rect (p->str, value);
|
|
g_string_append_c (p->str, ';');
|
|
g_string_append_c (p->str, '\n');
|
|
}
|
|
|
|
static void
|
|
append_point_param (Printer *p,
|
|
const char *param_name,
|
|
const graphene_point_t *value)
|
|
{
|
|
_indent (p);
|
|
g_string_append_printf (p->str, "%s: ", param_name);
|
|
append_point (p->str, value);
|
|
g_string_append_c (p->str, ';');
|
|
g_string_append_c (p->str, '\n');
|
|
}
|
|
|
|
static void
|
|
append_vec4_param (Printer *p,
|
|
const char *param_name,
|
|
const graphene_vec4_t *value)
|
|
{
|
|
_indent (p);
|
|
g_string_append_printf (p->str, "%s: ", param_name);
|
|
append_vec4 (p->str, value);
|
|
g_string_append_c (p->str, ';');
|
|
g_string_append_c (p->str, '\n');
|
|
}
|
|
|
|
static void
|
|
append_matrix_param (Printer *p,
|
|
const char *param_name,
|
|
const graphene_matrix_t *value)
|
|
{
|
|
GskTransform *transform = NULL;
|
|
|
|
_indent (p);
|
|
g_string_append_printf (p->str, "%s: ", param_name);
|
|
|
|
transform = gsk_transform_matrix (transform, value);
|
|
gsk_transform_print (transform,p->str);
|
|
g_string_append_c (p->str, ';');
|
|
g_string_append_c (p->str, '\n');
|
|
|
|
gsk_transform_unref (transform);
|
|
}
|
|
|
|
static void
|
|
append_transform_param (Printer *p,
|
|
const char *param_name,
|
|
GskTransform *transform)
|
|
{
|
|
_indent (p);
|
|
g_string_append_printf (p->str, "%s: ", param_name);
|
|
gsk_transform_print (transform, p->str);
|
|
g_string_append_c (p->str, ';');
|
|
g_string_append_c (p->str, '\n');
|
|
}
|
|
|
|
static void render_node_print (Printer *p,
|
|
GskRenderNode *node);
|
|
|
|
static void
|
|
append_node_param (Printer *p,
|
|
const char *param_name,
|
|
GskRenderNode *node)
|
|
{
|
|
_indent (p);
|
|
g_string_append_printf (p->str, "%s: ", param_name);
|
|
render_node_print (p, node);
|
|
}
|
|
|
|
static cairo_status_t
|
|
surface_write (void *closure,
|
|
const unsigned char *data,
|
|
unsigned int length)
|
|
{
|
|
g_byte_array_append (closure, data, length);
|
|
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
render_node_print (Printer *p,
|
|
GskRenderNode *node)
|
|
{
|
|
switch (gsk_render_node_get_node_type (node))
|
|
{
|
|
case GSK_CONTAINER_NODE:
|
|
{
|
|
guint i;
|
|
|
|
start_node (p, "container");
|
|
for (i = 0; i < gsk_container_node_get_n_children (node); i ++)
|
|
{
|
|
GskRenderNode *child = gsk_container_node_get_child (node, i);
|
|
|
|
/* Only in container nodes do we want nodes to be indented. */
|
|
_indent (p);
|
|
render_node_print (p, child);
|
|
}
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_COLOR_NODE:
|
|
{
|
|
start_node (p, "color");
|
|
append_rect_param (p, "bounds", &node->bounds);
|
|
append_rgba_param (p, "color", gsk_color_node_peek_color (node));
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_CROSS_FADE_NODE:
|
|
{
|
|
start_node (p, "cross-fade");
|
|
|
|
append_node_param (p, "end", gsk_cross_fade_node_get_end_child (node));
|
|
append_float_param (p, "progress", gsk_cross_fade_node_get_progress (node));
|
|
append_node_param (p, "start", gsk_cross_fade_node_get_start_child (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_LINEAR_GRADIENT_NODE:
|
|
{
|
|
const guint n_stops = gsk_linear_gradient_node_get_n_color_stops (node);
|
|
const GskColorStop *stop;
|
|
int i;
|
|
|
|
start_node (p, "linear-gradient");
|
|
|
|
append_rect_param (p, "bounds", &node->bounds);
|
|
append_point_param (p, "end", gsk_linear_gradient_node_peek_end (node));
|
|
append_point_param (p, "start", gsk_linear_gradient_node_peek_start (node));
|
|
|
|
_indent (p);
|
|
g_string_append (p->str, "stops:");
|
|
for (i = 0; i < n_stops - 1; i ++)
|
|
{
|
|
stop = gsk_linear_gradient_node_peek_color_stops (node) + i;
|
|
|
|
g_string_append_c (p->str, ' ');
|
|
string_append_double (p->str, stop->offset);
|
|
g_string_append_c (p->str, ' ');
|
|
append_rgba (p->str, &stop->color);
|
|
g_string_append_c (p->str, ',');
|
|
}
|
|
|
|
/* Last one, no comma */
|
|
stop = gsk_linear_gradient_node_peek_color_stops (node) + n_stops - 1;
|
|
string_append_double (p->str, stop->offset);
|
|
g_string_append_c (p->str, ' ');
|
|
append_rgba (p->str, &stop->color);
|
|
g_string_append (p->str, ";\n");
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_OPACITY_NODE:
|
|
{
|
|
start_node (p, "opacity");
|
|
|
|
append_node_param (p, "child", gsk_opacity_node_get_child (node));
|
|
append_float_param (p, "opacity", gsk_opacity_node_get_opacity (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_OUTSET_SHADOW_NODE:
|
|
{
|
|
start_node (p, "outset-shadow");
|
|
|
|
append_float_param (p, "blur", gsk_outset_shadow_node_get_blur_radius (node));
|
|
append_rgba_param (p, "color", gsk_outset_shadow_node_peek_color (node));
|
|
append_float_param (p, "dx", gsk_outset_shadow_node_get_dx (node));
|
|
append_float_param (p, "dy", gsk_outset_shadow_node_get_dy (node));
|
|
append_rounded_rect_param (p, "outline", gsk_outset_shadow_node_peek_outline (node));
|
|
append_float_param (p, "spread", gsk_outset_shadow_node_get_spread (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_CLIP_NODE:
|
|
{
|
|
start_node (p, "clip");
|
|
|
|
append_node_param (p, "child", gsk_clip_node_get_child (node));
|
|
append_rect_param (p, "clip", gsk_clip_node_peek_clip (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_ROUNDED_CLIP_NODE:
|
|
{
|
|
start_node (p, "rounded-clip");
|
|
|
|
append_node_param (p, "child", gsk_rounded_clip_node_get_child (node));
|
|
append_rounded_rect_param (p, "clip", gsk_rounded_clip_node_peek_clip (node));
|
|
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_TRANSFORM_NODE:
|
|
{
|
|
start_node (p, "transform");
|
|
|
|
append_node_param (p, "child", gsk_transform_node_get_child (node));
|
|
append_transform_param (p, "transform", gsk_transform_node_get_transform (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_COLOR_MATRIX_NODE:
|
|
{
|
|
start_node (p, "color-matrix");
|
|
|
|
append_node_param (p, "child", gsk_color_matrix_node_get_child (node));
|
|
append_matrix_param (p, "matrix", gsk_color_matrix_node_peek_color_matrix (node));
|
|
append_vec4_param (p, "offset", gsk_color_matrix_node_peek_color_offset (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_BORDER_NODE:
|
|
{
|
|
const GdkRGBA *colors = gsk_border_node_peek_colors (node);
|
|
const float *widths = gsk_border_node_peek_widths (node);
|
|
guint i, n;
|
|
start_node (p, "border");
|
|
|
|
if (!gdk_rgba_equal (&colors[3], &colors[1]))
|
|
n = 4;
|
|
else if (!gdk_rgba_equal (&colors[2], &colors[0]))
|
|
n = 3;
|
|
else if (!gdk_rgba_equal (&colors[1], &colors[0]))
|
|
n = 2;
|
|
else
|
|
n = 1;
|
|
|
|
if (n > 0)
|
|
{
|
|
_indent (p);
|
|
g_string_append (p->str, "colors: ");
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
if (i > 0)
|
|
g_string_append_c (p->str, ' ');
|
|
append_rgba (p->str, &colors[i]);
|
|
}
|
|
g_string_append (p->str, ";\n");
|
|
}
|
|
|
|
append_rounded_rect_param (p, "outline", gsk_border_node_peek_outline (node));
|
|
|
|
if (widths[3] != widths[1])
|
|
n = 4;
|
|
else if (widths[2] != widths[0])
|
|
n = 3;
|
|
else if (widths[1] != widths[0])
|
|
n = 2;
|
|
else
|
|
n = 1;
|
|
|
|
if (n > 0)
|
|
{
|
|
_indent (p);
|
|
g_string_append (p->str, "widths: ");
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
if (i > 0)
|
|
g_string_append_c (p->str, ' ');
|
|
string_append_double (p->str, widths[i]);
|
|
}
|
|
g_string_append (p->str, ";\n");
|
|
}
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_SHADOW_NODE:
|
|
{
|
|
const guint n_shadows = gsk_shadow_node_get_n_shadows (node);
|
|
int i;
|
|
|
|
start_node (p, "shadow");
|
|
|
|
_indent (p);
|
|
append_node_param (p, "child", gsk_shadow_node_get_child (node));
|
|
|
|
g_string_append (p->str, "shadows: ");
|
|
for (i = 0; i < n_shadows; i ++)
|
|
{
|
|
const GskShadow *s = gsk_shadow_node_peek_shadow (node, i);
|
|
char *color;
|
|
|
|
color = gdk_rgba_to_string (&s->color);
|
|
g_string_append (p->str, color);
|
|
g_string_append_c (p->str, ' ');
|
|
string_append_double (p->str, s->dx);
|
|
g_string_append_c (p->str, ' ');
|
|
string_append_double (p->str, s->dy);
|
|
g_string_append_c (p->str, ' ');
|
|
string_append_double (p->str, s->radius);
|
|
|
|
if (i < n_shadows - 1)
|
|
g_string_append (p->str, ", ");
|
|
|
|
g_free (color);
|
|
}
|
|
|
|
g_string_append_c (p->str, ';');
|
|
g_string_append_c (p->str, '\n');
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_INSET_SHADOW_NODE:
|
|
{
|
|
start_node (p, "inset-shadow");
|
|
|
|
append_float_param (p, "blur", gsk_inset_shadow_node_get_blur_radius (node));
|
|
append_rgba_param (p, "color", gsk_inset_shadow_node_peek_color (node));
|
|
append_float_param (p, "dx", gsk_inset_shadow_node_get_dx (node));
|
|
append_float_param (p, "dy", gsk_inset_shadow_node_get_dy (node));
|
|
append_rounded_rect_param (p, "outline", gsk_inset_shadow_node_peek_outline (node));
|
|
append_float_param (p, "spread", gsk_inset_shadow_node_get_spread (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_TEXTURE_NODE:
|
|
{
|
|
GdkTexture *texture = gsk_texture_node_get_texture (node);
|
|
cairo_surface_t *surface;
|
|
GByteArray *array;
|
|
char *b64;
|
|
|
|
start_node (p, "texture");
|
|
append_rect_param (p, "bounds", &node->bounds);
|
|
|
|
surface = gdk_texture_download_surface (texture);
|
|
array = g_byte_array_new ();
|
|
cairo_surface_write_to_png_stream (surface, surface_write, array);
|
|
b64 = g_base64_encode (array->data, array->len);
|
|
|
|
_indent (p);
|
|
g_string_append_printf (p->str, "texture: url(\"data:image/png;base64,%s\");\n", b64);
|
|
end_node (p);
|
|
|
|
g_free (b64);
|
|
g_byte_array_free (array, TRUE);
|
|
cairo_surface_destroy (surface);
|
|
}
|
|
break;
|
|
|
|
case GSK_TEXT_NODE:
|
|
{
|
|
const guint n_glyphs = gsk_text_node_get_num_glyphs (node);
|
|
const PangoGlyphInfo *glyph;
|
|
PangoFontDescription *desc;
|
|
char *font_name;
|
|
guint i;
|
|
start_node (p, "text");
|
|
|
|
append_rgba_param (p, "color", gsk_text_node_peek_color (node));
|
|
|
|
_indent (p);
|
|
desc = pango_font_describe ((PangoFont *)gsk_text_node_peek_font (node));;
|
|
font_name = pango_font_description_to_string (desc);
|
|
g_string_append_printf (p->str, "font: \"%s\";\n", font_name);
|
|
g_free (font_name);
|
|
pango_font_description_free (desc);
|
|
|
|
_indent (p);
|
|
g_string_append (p->str, "glyphs: ");
|
|
glyph = gsk_text_node_peek_glyphs (node);
|
|
g_string_append_printf (p->str, "%u %i %i %i %i",
|
|
glyph->glyph, glyph->geometry.width,
|
|
glyph->geometry.x_offset, glyph->geometry.y_offset,
|
|
glyph->attr.is_cluster_start);
|
|
for (i = 1; i < n_glyphs; i ++)
|
|
{
|
|
glyph = gsk_text_node_peek_glyphs (node) + i;
|
|
g_string_append_printf (p->str, ", %u %i %i %i %i",
|
|
glyph->glyph, glyph->geometry.width,
|
|
glyph->geometry.x_offset, glyph->geometry.y_offset,
|
|
glyph->attr.is_cluster_start);
|
|
}
|
|
g_string_append_c (p->str, ';');
|
|
g_string_append_c (p->str, '\n');
|
|
|
|
append_float_param (p, "x", gsk_text_node_get_x (node));
|
|
append_float_param (p, "y", gsk_text_node_get_y (node));
|
|
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_DEBUG_NODE:
|
|
{
|
|
start_node (p, "debug");
|
|
|
|
_indent (p);
|
|
append_node_param (p, "child", gsk_debug_node_get_child (node));
|
|
/* TODO: We potentially need to escape certain characters in the message */
|
|
g_string_append_printf (p->str, "message: \"%s\";\n", gsk_debug_node_get_message (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_BLUR_NODE:
|
|
{
|
|
start_node (p, "blur");
|
|
|
|
append_float_param (p, "blur", gsk_blur_node_get_radius (node));
|
|
append_node_param (p, "child", gsk_blur_node_get_child (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_REPEAT_NODE:
|
|
{
|
|
start_node (p, "repeat");
|
|
|
|
append_rect_param (p, "bounds", &node->bounds);
|
|
append_node_param (p, "child", gsk_repeat_node_get_child (node));
|
|
append_rect_param (p, "child-bounds", gsk_repeat_node_peek_child_bounds (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_BLEND_NODE:
|
|
{
|
|
GskBlendMode mode = gsk_blend_node_get_blend_mode (node);
|
|
guint i;
|
|
|
|
start_node (p, "blend");
|
|
|
|
append_node_param (p, "bottom", gsk_blend_node_get_bottom_child (node));
|
|
|
|
_indent (p);
|
|
for (i = 0; i < G_N_ELEMENTS (blend_modes); i++)
|
|
{
|
|
if (blend_modes[i].mode == mode)
|
|
{
|
|
g_string_append_printf (p->str, "mode: %s;\n", blend_modes[i].name);
|
|
break;
|
|
}
|
|
}
|
|
append_node_param (p, "top", gsk_blend_node_get_top_child (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_NOT_A_RENDER_NODE:
|
|
g_assert_not_reached ();
|
|
break;
|
|
|
|
case GSK_CAIRO_NODE:
|
|
case GSK_REPEATING_LINEAR_GRADIENT_NODE:
|
|
default:
|
|
g_error ("Unhandled node: %s", node->node_class->type_name);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gsk_render_node_serialize:
|
|
* @node: a #GskRenderNode
|
|
*
|
|
* Serializes the @node for later deserialization via
|
|
* gsk_render_node_deserialize(). No guarantees are made about the format
|
|
* used other than that the same version of GTK+ will be able to deserialize
|
|
* the result of a call to gsk_render_node_serialize() and
|
|
* gsk_render_node_deserialize() will correctly reject files it cannot open
|
|
* that were created with previous versions of GTK+.
|
|
*
|
|
* The intended use of this functions is testing, benchmarking and debugging.
|
|
* The format is not meant as a permanent storage format.
|
|
*
|
|
* Returns: a #GBytes representing the node.
|
|
**/
|
|
GBytes *
|
|
gsk_render_node_serialize (GskRenderNode *node)
|
|
{
|
|
Printer p;
|
|
|
|
printer_init (&p);
|
|
|
|
if (gsk_render_node_get_node_type (node) == GSK_CONTAINER_NODE)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < gsk_container_node_get_n_children (node); i ++)
|
|
{
|
|
GskRenderNode *child = gsk_container_node_get_child (node, i);
|
|
|
|
render_node_print (&p, child);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
render_node_print (&p, node);
|
|
}
|
|
|
|
return g_string_free_to_bytes (p.str);
|
|
}
|