mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-27 06:00:22 +00:00
746cfe48b0
When we don't have an embedded font file via a url, then we want to parse fonts "as normal", i.e. allow fallback for aliases like "Monospace 10". This was broken when the url support was added. Make it work again. Update affected tests. In particular, the output of the text-fail test goes back to be the same it was before the url changes.
4415 lines
123 KiB
C
4415 lines
123 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 "gskpath.h"
|
|
#include "gskpathbuilder.h"
|
|
#include "gskroundedrectprivate.h"
|
|
#include "gskrendernodeprivate.h"
|
|
#include "gskstroke.h"
|
|
#include "gsktransformprivate.h"
|
|
#include "gskenumtypes.h"
|
|
|
|
#include "gdk/gdkrgbaprivate.h"
|
|
#include "gdk/gdktextureprivate.h"
|
|
#include "gdk/gdkmemoryformatprivate.h"
|
|
#include <gtk/css/gtkcss.h>
|
|
#include "gtk/css/gtkcssdataurlprivate.h"
|
|
#include "gtk/css/gtkcssparserprivate.h"
|
|
#include "gtk/css/gtkcssserializerprivate.h"
|
|
|
|
#ifdef CAIRO_HAS_SCRIPT_SURFACE
|
|
#include <cairo-script.h>
|
|
#endif
|
|
#ifdef HAVE_CAIRO_SCRIPT_INTERPRETER
|
|
#include <cairo-script-interpreter.h>
|
|
#endif
|
|
|
|
#include <pango/pangocairo.h>
|
|
#ifdef HAVE_PANGOFT
|
|
#include <pango/pangofc-fontmap.h>
|
|
#endif
|
|
|
|
#include <glib/gstdio.h>
|
|
|
|
typedef struct _Context Context;
|
|
|
|
struct _Context
|
|
{
|
|
GHashTable *named_nodes;
|
|
GHashTable *named_textures;
|
|
PangoFontMap *fontmap;
|
|
};
|
|
|
|
typedef struct _Declaration Declaration;
|
|
|
|
struct _Declaration
|
|
{
|
|
const char *name;
|
|
gboolean (* parse_func) (GtkCssParser *parser, Context *context, gpointer result);
|
|
void (* clear_func) (gpointer data);
|
|
gpointer result;
|
|
};
|
|
|
|
static void
|
|
context_init (Context *context)
|
|
{
|
|
memset (context, 0, sizeof (Context));
|
|
}
|
|
|
|
static void
|
|
context_finish (Context *context)
|
|
{
|
|
g_clear_pointer (&context->named_nodes, g_hash_table_unref);
|
|
g_clear_pointer (&context->named_textures, g_hash_table_unref);
|
|
g_clear_object (&context->fontmap);
|
|
}
|
|
|
|
static gboolean
|
|
parse_rect (GtkCssParser *parser,
|
|
Context *context,
|
|
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_vec4 (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer out_vec4)
|
|
{
|
|
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_vec4_init (out_vec4, numbers[0], numbers[1], numbers[2], numbers[3]);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_texture (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer out_data)
|
|
{
|
|
GdkTexture *texture;
|
|
GError *error = NULL;
|
|
const GtkCssToken *token;
|
|
GtkCssLocation start_location;
|
|
char *url, *scheme, *texture_name;
|
|
|
|
token = gtk_css_parser_get_token (parser);
|
|
if (gtk_css_token_is (token, GTK_CSS_TOKEN_STRING))
|
|
{
|
|
texture_name = gtk_css_parser_consume_string (parser);
|
|
|
|
if (context->named_textures)
|
|
texture = g_hash_table_lookup (context->named_textures, texture_name);
|
|
else
|
|
texture = NULL;
|
|
|
|
if (texture)
|
|
{
|
|
*(GdkTexture **) out_data = g_object_ref (texture);
|
|
g_free (texture_name);
|
|
return TRUE;
|
|
}
|
|
else if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
|
|
{
|
|
gtk_css_parser_error_value (parser, "No texture named \"%s\"", texture_name);
|
|
g_free (texture_name);
|
|
return FALSE;
|
|
}
|
|
|
|
if (context->named_textures && g_hash_table_lookup (context->named_textures, texture_name))
|
|
{
|
|
gtk_css_parser_error_value (parser, "A texture named \"%s\" already exists.", texture_name);
|
|
g_clear_pointer (&texture_name, g_free);
|
|
}
|
|
}
|
|
else
|
|
texture_name = NULL;
|
|
|
|
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)
|
|
{
|
|
GBytes *bytes;
|
|
|
|
bytes = gtk_css_data_url_parse (url, NULL, &error);
|
|
if (bytes)
|
|
{
|
|
texture = gdk_texture_new_from_bytes (bytes, &error);
|
|
g_bytes_unref (bytes);
|
|
}
|
|
else
|
|
{
|
|
texture = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GFile *file;
|
|
|
|
file = gtk_css_parser_resolve_url (parser, url);
|
|
|
|
if (file)
|
|
{
|
|
texture = gdk_texture_new_from_file (file, &error);
|
|
g_object_unref (file);
|
|
}
|
|
else
|
|
{
|
|
g_set_error (&error,
|
|
GTK_CSS_PARSER_ERROR,
|
|
GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE,
|
|
"Failed to resolve URL");
|
|
texture = NULL;
|
|
}
|
|
}
|
|
|
|
g_free (scheme);
|
|
g_free (url);
|
|
|
|
if (texture == NULL)
|
|
{
|
|
if (error)
|
|
{
|
|
gtk_css_parser_emit_error (parser,
|
|
&start_location,
|
|
gtk_css_parser_get_end_location (parser),
|
|
error);
|
|
g_clear_error (&error);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
if (texture_name)
|
|
{
|
|
if (context->named_textures == NULL)
|
|
context->named_textures = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, g_object_unref);
|
|
g_hash_table_insert (context->named_textures, texture_name, g_object_ref (texture));
|
|
}
|
|
|
|
*(GdkTexture **) out_data = texture;
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
clear_texture (gpointer inout_texture)
|
|
{
|
|
g_clear_object ((GdkTexture **) inout_texture);
|
|
}
|
|
|
|
static cairo_surface_t *
|
|
csi_hooks_surface_create (void *closure,
|
|
cairo_content_t content,
|
|
double width,
|
|
double height,
|
|
long uid)
|
|
{
|
|
return cairo_surface_create_similar (closure, content, width, height);
|
|
}
|
|
|
|
static const cairo_user_data_key_t csi_hooks_key;
|
|
|
|
static cairo_t *
|
|
csi_hooks_context_create (void *closure,
|
|
cairo_surface_t *surface)
|
|
{
|
|
cairo_t *cr = cairo_create (surface);
|
|
|
|
cairo_set_user_data (cr,
|
|
&csi_hooks_key,
|
|
cairo_surface_reference (surface),
|
|
(cairo_destroy_func_t) cairo_surface_destroy);
|
|
|
|
return cr;
|
|
}
|
|
|
|
static void
|
|
csi_hooks_context_destroy (void *closure,
|
|
void *ptr)
|
|
{
|
|
cairo_surface_t *surface;
|
|
cairo_t *cr;
|
|
|
|
surface = cairo_get_user_data (ptr, &csi_hooks_key);
|
|
cr = cairo_create (closure);
|
|
cairo_set_source_surface (cr, surface, 0, 0);
|
|
cairo_paint (cr);
|
|
cairo_destroy (cr);
|
|
}
|
|
|
|
static gboolean
|
|
parse_script (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer out_data)
|
|
{
|
|
#ifdef HAVE_CAIRO_SCRIPT_INTERPRETER
|
|
GError *error = NULL;
|
|
GBytes *bytes;
|
|
GtkCssLocation start_location;
|
|
char *url, *scheme;
|
|
cairo_script_interpreter_t *csi;
|
|
cairo_script_interpreter_hooks_t hooks = {
|
|
.surface_create = csi_hooks_surface_create,
|
|
.context_create = csi_hooks_context_create,
|
|
.context_destroy = csi_hooks_context_destroy,
|
|
};
|
|
|
|
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)
|
|
{
|
|
bytes = gtk_css_data_url_parse (url, NULL, &error);
|
|
}
|
|
else
|
|
{
|
|
GFile *file;
|
|
|
|
file = gtk_css_parser_resolve_url (parser, url);
|
|
if (file)
|
|
{
|
|
bytes = g_file_load_bytes (file, NULL, NULL, &error);
|
|
g_object_unref (file);
|
|
}
|
|
else
|
|
{
|
|
g_set_error (&error,
|
|
GTK_CSS_PARSER_ERROR,
|
|
GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE,
|
|
"Failed to resolve URL");
|
|
bytes = NULL;
|
|
}
|
|
}
|
|
|
|
g_free (scheme);
|
|
g_free (url);
|
|
|
|
if (bytes == NULL)
|
|
{
|
|
gtk_css_parser_emit_error (parser,
|
|
&start_location,
|
|
gtk_css_parser_get_end_location (parser),
|
|
error);
|
|
g_clear_error (&error);
|
|
return FALSE;
|
|
}
|
|
|
|
hooks.closure = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
|
|
csi = cairo_script_interpreter_create ();
|
|
cairo_script_interpreter_install_hooks (csi, &hooks);
|
|
cairo_script_interpreter_feed_string (csi, g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes));
|
|
g_bytes_unref (bytes);
|
|
if (cairo_surface_status (hooks.closure) != CAIRO_STATUS_SUCCESS)
|
|
{
|
|
gtk_css_parser_error_value (parser, "Invalid Cairo script: %s", cairo_status_to_string (cairo_surface_status (hooks.closure)));
|
|
cairo_script_interpreter_destroy (csi);
|
|
return FALSE;
|
|
}
|
|
if (cairo_script_interpreter_destroy (csi) != CAIRO_STATUS_SUCCESS)
|
|
{
|
|
gtk_css_parser_error_value (parser, "Invalid Cairo script");
|
|
cairo_surface_destroy (hooks.closure);
|
|
return FALSE;
|
|
}
|
|
|
|
*(cairo_surface_t **) out_data = hooks.closure;
|
|
return TRUE;
|
|
#else
|
|
gtk_css_parser_warn (parser,
|
|
GTK_CSS_PARSER_WARNING_UNIMPLEMENTED,
|
|
gtk_css_parser_get_block_location (parser),
|
|
gtk_css_parser_get_start_location (parser),
|
|
"GTK was compiled with script interpreter support. Using fallback pixel data for Cairo node.");
|
|
*(cairo_surface_t **) out_data = NULL;
|
|
return TRUE;
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
clear_surface (gpointer inout_surface)
|
|
{
|
|
g_clear_pointer ((cairo_surface_t **) inout_surface, cairo_surface_destroy);
|
|
}
|
|
|
|
static gboolean
|
|
parse_rounded_rect (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer out_rect)
|
|
{
|
|
graphene_rect_t r;
|
|
graphene_size_t corners[4];
|
|
double d;
|
|
guint i;
|
|
|
|
if (!parse_rect (parser, context, &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,
|
|
Context *context,
|
|
gpointer out_color)
|
|
{
|
|
return gdk_rgba_parser_parse (parser, out_color);
|
|
}
|
|
|
|
static gboolean
|
|
parse_double (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer out_double)
|
|
{
|
|
return gtk_css_parser_consume_number (parser, out_double);
|
|
}
|
|
|
|
static gboolean
|
|
parse_positive_double (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer out_double)
|
|
{
|
|
if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNED_NUMBER)
|
|
|| gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNED_INTEGER))
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Expected a positive number");
|
|
return FALSE;
|
|
}
|
|
|
|
return gtk_css_parser_consume_number (parser, out_double);
|
|
}
|
|
|
|
static gboolean
|
|
parse_point (GtkCssParser *parser,
|
|
Context *context,
|
|
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,
|
|
Context *context,
|
|
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,
|
|
Context *context,
|
|
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))
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Expected a string");
|
|
return FALSE;
|
|
}
|
|
|
|
s = g_strdup (gtk_css_token_get_string (token));
|
|
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,
|
|
Context *context,
|
|
gpointer out_stops)
|
|
{
|
|
GArray *stops;
|
|
GskColorStop stop;
|
|
|
|
stops = g_array_new (FALSE, FALSE, sizeof (GskColorStop));
|
|
|
|
for (;;)
|
|
{
|
|
double dval;
|
|
|
|
if (!gtk_css_parser_consume_number (parser, &dval))
|
|
goto error;
|
|
|
|
stop.offset = dval;
|
|
|
|
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,
|
|
Context *context,
|
|
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,
|
|
Context *context,
|
|
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,
|
|
Context *context,
|
|
gpointer out_shadows)
|
|
{
|
|
GArray *shadows = out_shadows;
|
|
|
|
do
|
|
{
|
|
GskShadow shadow = { GDK_RGBA("000000"), 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 y offset");
|
|
|
|
if (gtk_css_parser_has_number (parser))
|
|
{
|
|
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);
|
|
}
|
|
while (gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
clear_shadows (gpointer inout_shadows)
|
|
{
|
|
g_array_set_size (inout_shadows, 0);
|
|
}
|
|
|
|
static const struct
|
|
{
|
|
GskScalingFilter filter;
|
|
const char *name;
|
|
} scaling_filters[] = {
|
|
{ GSK_SCALING_FILTER_LINEAR, "linear" },
|
|
{ GSK_SCALING_FILTER_NEAREST, "nearest" },
|
|
{ GSK_SCALING_FILTER_TRILINEAR, "trilinear" },
|
|
};
|
|
|
|
static gboolean
|
|
parse_scaling_filter (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer out_filter)
|
|
{
|
|
for (unsigned int i = 0; i < G_N_ELEMENTS (scaling_filters); i++)
|
|
{
|
|
if (gtk_css_parser_try_ident (parser, scaling_filters[i].name))
|
|
{
|
|
*(GskScalingFilter *) out_filter = scaling_filters[i].filter;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
gtk_css_parser_error_syntax (parser, "Not a valid scaling filter.");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
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 const char *
|
|
get_blend_mode_name (GskBlendMode mode)
|
|
{
|
|
for (unsigned int i = 0; i < G_N_ELEMENTS (blend_modes); i++)
|
|
{
|
|
if (blend_modes[i].mode == mode)
|
|
return blend_modes[i].name;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
parse_blend_mode (GtkCssParser *parser,
|
|
Context *context,
|
|
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;
|
|
}
|
|
}
|
|
|
|
gtk_css_parser_error_syntax (parser, "Not a valid blend mode.");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static const struct
|
|
{
|
|
GskMaskMode mode;
|
|
const char *name;
|
|
} mask_modes[] = {
|
|
{ GSK_MASK_MODE_ALPHA, "alpha" },
|
|
{ GSK_MASK_MODE_INVERTED_ALPHA, "inverted-alpha" },
|
|
{ GSK_MASK_MODE_LUMINANCE, "luminance" },
|
|
{ GSK_MASK_MODE_INVERTED_LUMINANCE, "inverted-luminance" },
|
|
};
|
|
|
|
static const char *
|
|
get_mask_mode_name (GskMaskMode mode)
|
|
{
|
|
for (unsigned int i = 0; i < G_N_ELEMENTS (mask_modes); i++)
|
|
{
|
|
if (mask_modes[i].mode == mode)
|
|
return mask_modes[i].name;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
parse_mask_mode (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer out_mode)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (mask_modes); i++)
|
|
{
|
|
if (gtk_css_parser_try_ident (parser, mask_modes[i].name))
|
|
{
|
|
*(GskMaskMode *) out_mode = mask_modes[i].mode;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
gtk_css_parser_error_syntax (parser, "Not a valid mask mode.");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static PangoFont *
|
|
font_from_string (PangoFontMap *fontmap,
|
|
const char *string,
|
|
gboolean allow_fallback)
|
|
{
|
|
PangoFontDescription *desc;
|
|
PangoContext *ctx;
|
|
PangoFont *font;
|
|
|
|
desc = pango_font_description_from_string (string);
|
|
ctx = pango_font_map_create_context (fontmap);
|
|
font = pango_font_map_load_font (fontmap, ctx, desc);
|
|
g_object_unref (ctx);
|
|
|
|
if (font && !allow_fallback)
|
|
{
|
|
PangoFontDescription *desc2;
|
|
const char *family, *family2;
|
|
|
|
desc2 = pango_font_describe (font);
|
|
|
|
family = pango_font_description_get_family (desc);
|
|
family2 = pango_font_description_get_family (desc2);
|
|
|
|
if (g_strcmp0 (family, family2) != 0)
|
|
g_clear_object (&font);
|
|
|
|
pango_font_description_free (desc2);
|
|
}
|
|
|
|
pango_font_description_free (desc);
|
|
|
|
return font;
|
|
}
|
|
|
|
#define MIN_ASCII_GLYPH 32
|
|
#define MAX_ASCII_GLYPH 127 /* exclusive */
|
|
#define N_ASCII_GLYPHS (MAX_ASCII_GLYPH - MIN_ASCII_GLYPH)
|
|
|
|
static PangoGlyphString *
|
|
create_ascii_glyphs (PangoFont *font)
|
|
{
|
|
PangoLanguage *language = pango_language_from_string ("en_US"); /* just pick one */
|
|
PangoCoverage *coverage;
|
|
PangoAnalysis not_a_hack = {
|
|
.shape_engine = NULL, /* unused */
|
|
.lang_engine = NULL, /* unused by pango_shape() */
|
|
.font = font,
|
|
.level = 0,
|
|
.gravity = PANGO_GRAVITY_SOUTH,
|
|
.flags = 0,
|
|
.script = PANGO_SCRIPT_COMMON,
|
|
.language = language,
|
|
.extra_attrs = NULL
|
|
};
|
|
PangoGlyphString *result, *glyph_string;
|
|
guint i;
|
|
|
|
coverage = pango_font_get_coverage (font, language);
|
|
for (i = MIN_ASCII_GLYPH; i < MAX_ASCII_GLYPH; i++)
|
|
{
|
|
if (!pango_coverage_get (coverage, i))
|
|
break;
|
|
}
|
|
g_object_unref (coverage);
|
|
if (i < MAX_ASCII_GLYPH)
|
|
return NULL;
|
|
|
|
result = pango_glyph_string_new ();
|
|
pango_glyph_string_set_size (result, N_ASCII_GLYPHS);
|
|
glyph_string = pango_glyph_string_new ();
|
|
for (i = MIN_ASCII_GLYPH; i < MAX_ASCII_GLYPH; i++)
|
|
{
|
|
const char text[2] = { i, 0 };
|
|
PangoShapeFlags flags = 0;
|
|
|
|
if (cairo_version () < CAIRO_VERSION_ENCODE (1, 17, 4))
|
|
flags = PANGO_SHAPE_ROUND_POSITIONS;
|
|
|
|
pango_shape_with_flags (text, 1,
|
|
text, 1,
|
|
¬_a_hack,
|
|
glyph_string,
|
|
flags);
|
|
|
|
if (glyph_string->num_glyphs != 1)
|
|
{
|
|
pango_glyph_string_free (glyph_string);
|
|
pango_glyph_string_free (result);
|
|
return NULL;
|
|
}
|
|
result->glyphs[i - MIN_ASCII_GLYPH] = glyph_string->glyphs[0];
|
|
}
|
|
|
|
pango_glyph_string_free (glyph_string);
|
|
|
|
return result;
|
|
}
|
|
|
|
#ifdef HAVE_PANGOFT
|
|
|
|
static void
|
|
delete_file (gpointer data)
|
|
{
|
|
char *path = data;
|
|
|
|
g_remove (path);
|
|
g_free (path);
|
|
}
|
|
|
|
static void
|
|
ensure_fontmap (Context *context)
|
|
{
|
|
FcConfig *config;
|
|
GPtrArray *files;
|
|
|
|
if (context->fontmap)
|
|
return;
|
|
|
|
context->fontmap = pango_cairo_font_map_new ();
|
|
|
|
config = FcInitLoadConfig ();
|
|
pango_fc_font_map_set_config (PANGO_FC_FONT_MAP (context->fontmap), config);
|
|
FcConfigDestroy (config);
|
|
|
|
files = g_ptr_array_new_with_free_func (delete_file);
|
|
|
|
g_object_set_data_full (G_OBJECT (context->fontmap), "font-files", files, (GDestroyNotify) g_ptr_array_unref);
|
|
}
|
|
|
|
static gboolean
|
|
add_font_from_file (Context *context,
|
|
const char *path,
|
|
GError **error)
|
|
{
|
|
FcConfig *config;
|
|
GPtrArray *files;
|
|
|
|
ensure_fontmap (context);
|
|
|
|
if (!PANGO_IS_FC_FONT_MAP (context->fontmap))
|
|
{
|
|
g_set_error (error,
|
|
GTK_CSS_PARSER_ERROR,
|
|
GTK_CSS_PARSER_ERROR_FAILED,
|
|
"Custom fonts are not implemented for %s", G_OBJECT_TYPE_NAME (context->fontmap));
|
|
return FALSE;
|
|
}
|
|
|
|
config = pango_fc_font_map_get_config (PANGO_FC_FONT_MAP (context->fontmap));
|
|
|
|
if (!FcConfigAppFontAddFile (config, (FcChar8 *) path))
|
|
{
|
|
g_set_error (error,
|
|
GTK_CSS_PARSER_ERROR,
|
|
GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE,
|
|
"Failed to load font");
|
|
return FALSE;
|
|
}
|
|
|
|
files = (GPtrArray *) g_object_get_data (G_OBJECT (context->fontmap), "font-files");
|
|
g_ptr_array_add (files, g_strdup (path));
|
|
|
|
pango_fc_font_map_config_changed (PANGO_FC_FONT_MAP (context->fontmap));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
add_font_from_bytes (Context *context,
|
|
GBytes *bytes,
|
|
GError **error)
|
|
{
|
|
GFile *file;
|
|
GIOStream *iostream;
|
|
GOutputStream *ostream;
|
|
gboolean result;
|
|
|
|
file = g_file_new_tmp ("gtk4-font-XXXXXX.ttf", (GFileIOStream **) &iostream, error);
|
|
if (!file)
|
|
return FALSE;
|
|
|
|
ostream = g_io_stream_get_output_stream (iostream);
|
|
if (g_output_stream_write_bytes (ostream, bytes, NULL, error) == -1)
|
|
{
|
|
g_object_unref (file);
|
|
g_object_unref (iostream);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
g_io_stream_close (iostream, NULL, NULL);
|
|
g_object_unref (iostream);
|
|
|
|
result = add_font_from_file (context, g_file_peek_path (file), error);
|
|
|
|
g_object_unref (file);
|
|
|
|
return result;
|
|
}
|
|
|
|
#else /* !HAVE_PANGOFT */
|
|
|
|
static gboolean
|
|
add_font_from_bytes (Context *context,
|
|
GBytes *bytes,
|
|
GError **error)
|
|
{
|
|
g_set_error (error,
|
|
GTK_CSS_PARSER_ERROR,
|
|
GTK_CSS_PARSER_ERROR_FAILED,
|
|
"Not implemented");
|
|
return FALSE;
|
|
}
|
|
|
|
#endif
|
|
|
|
static gboolean
|
|
parse_font (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer out_font)
|
|
{
|
|
PangoFont *font = NULL;
|
|
char *font_name;
|
|
|
|
font_name = gtk_css_parser_consume_string (parser);
|
|
if (font_name == NULL)
|
|
return FALSE;
|
|
|
|
if (context->fontmap)
|
|
font = font_from_string (context->fontmap, font_name, FALSE);
|
|
|
|
if (gtk_css_parser_has_url (parser))
|
|
{
|
|
char *url;
|
|
|
|
if (font != NULL)
|
|
{
|
|
gtk_css_parser_error_value (parser, "A font with this name already exists.");
|
|
/* consume the url to avoid more errors */
|
|
url = gtk_css_parser_consume_url (parser);
|
|
g_free (url);
|
|
}
|
|
else
|
|
{
|
|
char *scheme;
|
|
GBytes *bytes;
|
|
GError *error = NULL;
|
|
GtkCssLocation start_location;
|
|
gboolean success = FALSE;
|
|
|
|
start_location = *gtk_css_parser_get_start_location (parser);
|
|
url = gtk_css_parser_consume_url (parser);
|
|
|
|
if (url != NULL)
|
|
{
|
|
scheme = g_uri_parse_scheme (url);
|
|
if (scheme && g_ascii_strcasecmp (scheme, "data") == 0)
|
|
{
|
|
bytes = gtk_css_data_url_parse (url, NULL, &error);
|
|
}
|
|
else
|
|
{
|
|
GFile *file;
|
|
|
|
file = g_file_new_for_uri (url);
|
|
bytes = g_file_load_bytes (file, NULL, NULL, &error);
|
|
g_object_unref (file);
|
|
}
|
|
|
|
g_free (scheme);
|
|
g_free (url);
|
|
if (bytes != NULL)
|
|
{
|
|
success = add_font_from_bytes (context, bytes, &error);
|
|
g_bytes_unref (bytes);
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
gtk_css_parser_emit_error (parser,
|
|
&start_location,
|
|
gtk_css_parser_get_end_location (parser),
|
|
error);
|
|
}
|
|
}
|
|
|
|
if (success)
|
|
{
|
|
font = font_from_string (context->fontmap, font_name, FALSE);
|
|
if (!font)
|
|
{
|
|
gtk_css_parser_error (parser,
|
|
GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE,
|
|
&start_location,
|
|
gtk_css_parser_get_end_location (parser),
|
|
"The given url does not define a font named \"%s\"",
|
|
font_name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!font)
|
|
font = font_from_string (pango_cairo_font_map_get_default (), font_name, TRUE);
|
|
|
|
if (!font)
|
|
gtk_css_parser_error_value (parser, "The font \"%s\" does not exist", font_name);
|
|
}
|
|
|
|
g_free (font_name);
|
|
|
|
if (font)
|
|
{
|
|
*((PangoFont**)out_font) = font;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clear_font (gpointer inout_font)
|
|
{
|
|
g_clear_object ((PangoFont **) inout_font);
|
|
}
|
|
|
|
static gboolean
|
|
parse_glyphs (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer out_glyphs)
|
|
{
|
|
PangoGlyphString *glyph_string;
|
|
|
|
glyph_string = pango_glyph_string_new ();
|
|
|
|
do
|
|
{
|
|
PangoGlyphInfo gi = { 0, { 0, 0, 0}, { 1 } };
|
|
double d, d2;
|
|
int i;
|
|
|
|
if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_STRING))
|
|
{
|
|
char *s = gtk_css_parser_consume_string (parser);
|
|
|
|
for (i = 0; s[i] != 0; i++)
|
|
{
|
|
if (s[i] < MIN_ASCII_GLYPH || s[i] >= MAX_ASCII_GLYPH)
|
|
{
|
|
gtk_css_parser_error_value (parser, "Unsupported character %d in string", i);
|
|
}
|
|
gi.glyph = PANGO_GLYPH_INVALID_INPUT - MAX_ASCII_GLYPH + s[i];
|
|
pango_glyph_string_set_size (glyph_string, glyph_string->num_glyphs + 1);
|
|
glyph_string->glyphs[glyph_string->num_glyphs - 1] = gi;
|
|
}
|
|
|
|
g_free (s);
|
|
}
|
|
else
|
|
{
|
|
if (!gtk_css_parser_consume_integer (parser, &i) ||
|
|
!gtk_css_parser_consume_number (parser, &d))
|
|
{
|
|
pango_glyph_string_free (glyph_string);
|
|
return FALSE;
|
|
}
|
|
gi.glyph = i;
|
|
gi.geometry.width = (int) (d * PANGO_SCALE);
|
|
|
|
if (gtk_css_parser_has_number (parser))
|
|
{
|
|
if (!gtk_css_parser_consume_number (parser, &d) ||
|
|
!gtk_css_parser_consume_number (parser, &d2))
|
|
{
|
|
pango_glyph_string_free (glyph_string);
|
|
return FALSE;
|
|
}
|
|
gi.geometry.x_offset = (int) (d * PANGO_SCALE);
|
|
gi.geometry.y_offset = (int) (d2 * PANGO_SCALE);
|
|
|
|
if (gtk_css_parser_try_ident (parser, "same-cluster"))
|
|
gi.attr.is_cluster_start = 0;
|
|
else
|
|
gi.attr.is_cluster_start = 1;
|
|
|
|
if (gtk_css_parser_try_ident (parser, "color"))
|
|
gi.attr.is_color = 1;
|
|
else
|
|
gi.attr.is_color = 0;
|
|
}
|
|
|
|
pango_glyph_string_set_size (glyph_string, glyph_string->num_glyphs + 1);
|
|
glyph_string->glyphs[glyph_string->num_glyphs - 1] = gi;
|
|
}
|
|
}
|
|
while (gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA));
|
|
|
|
*((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, Context *context, 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,
|
|
Context *context)
|
|
{
|
|
GPtrArray *nodes;
|
|
const GtkCssToken *token;
|
|
GskRenderNode *node;
|
|
|
|
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 want 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, context, &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,
|
|
Context *context,
|
|
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 (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 (!gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COLON))
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Expected ':' after variable declaration");
|
|
}
|
|
else
|
|
{
|
|
if (!declarations[i].parse_func (parser, context, 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_token_get_string (gtk_css_parser_get_token (parser)));
|
|
else
|
|
gtk_css_parser_error_syntax (parser, "Expected a variable name");
|
|
}
|
|
|
|
gtk_css_parser_end_block (parser);
|
|
}
|
|
|
|
return parsed;
|
|
}
|
|
|
|
static GdkTexture *
|
|
create_default_texture (void)
|
|
{
|
|
static const guint32 pixels[100] = {
|
|
0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0, 0, 0, 0, 0,
|
|
0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0, 0, 0, 0, 0,
|
|
0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0, 0, 0, 0, 0,
|
|
0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0, 0, 0, 0, 0,
|
|
0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC,
|
|
0, 0, 0, 0, 0, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC,
|
|
0, 0, 0, 0, 0, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC,
|
|
0, 0, 0, 0, 0, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC,
|
|
0, 0, 0, 0, 0, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC, 0xFFFF00CC };
|
|
GBytes *bytes;
|
|
GdkTexture *texture;
|
|
|
|
bytes = g_bytes_new_static ((guchar *) pixels, 400);
|
|
texture = gdk_memory_texture_new (10, 10, GDK_MEMORY_DEFAULT, bytes, 40);
|
|
g_bytes_unref (bytes);
|
|
|
|
return texture;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
create_default_render_node_with_bounds (const graphene_rect_t *rect)
|
|
{
|
|
return gsk_color_node_new (&GDK_RGBA("FF00CC"), rect);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
create_default_render_node (void)
|
|
{
|
|
return create_default_render_node_with_bounds (&GRAPHENE_RECT_INIT (0, 0, 50, 50));
|
|
}
|
|
|
|
static GskPath *
|
|
create_default_path (void)
|
|
{
|
|
GskPathBuilder *builder;
|
|
guint i;
|
|
|
|
builder = gsk_path_builder_new ();
|
|
|
|
gsk_path_builder_move_to (builder, 25, 0);
|
|
for (i = 1; i < 5; i++)
|
|
{
|
|
gsk_path_builder_line_to (builder,
|
|
sin (i * G_PI * 0.8) * 25 + 25,
|
|
-cos (i * G_PI * 0.8) * 25 + 25);
|
|
}
|
|
gsk_path_builder_close (builder);
|
|
|
|
return gsk_path_builder_free_to_path (builder);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_color_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50);
|
|
GdkRGBA color = GDK_RGBA("FF00CC");
|
|
const Declaration declarations[] = {
|
|
{ "bounds", parse_rect, NULL, &bounds },
|
|
{ "color", parse_color, NULL, &color },
|
|
};
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
|
|
return gsk_color_node_new (&color, &bounds);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_linear_gradient_node_internal (GtkCssParser *parser,
|
|
Context *context,
|
|
gboolean repeating)
|
|
{
|
|
graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50);
|
|
graphene_point_t start = GRAPHENE_POINT_INIT (0, 0);
|
|
graphene_point_t end = GRAPHENE_POINT_INIT (0, 50);
|
|
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, context, declarations, G_N_ELEMENTS (declarations));
|
|
if (stops == NULL)
|
|
{
|
|
GskColorStop from = { 0.0, GDK_RGBA("AAFF00") };
|
|
GskColorStop to = { 1.0, GDK_RGBA("FF00CC") };
|
|
|
|
stops = g_array_new (FALSE, FALSE, sizeof (GskColorStop));
|
|
g_array_append_val (stops, from);
|
|
g_array_append_val (stops, to);
|
|
}
|
|
|
|
if (repeating)
|
|
result = gsk_repeating_linear_gradient_node_new (&bounds, &start, &end, (GskColorStop *) stops->data, stops->len);
|
|
else
|
|
result = gsk_linear_gradient_node_new (&bounds, &start, &end, (GskColorStop *) stops->data, stops->len);
|
|
|
|
g_array_free (stops, TRUE);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_linear_gradient_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
return parse_linear_gradient_node_internal (parser, context, FALSE);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_repeating_linear_gradient_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
return parse_linear_gradient_node_internal (parser, context, TRUE);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_radial_gradient_node_internal (GtkCssParser *parser,
|
|
Context *context,
|
|
gboolean repeating)
|
|
{
|
|
graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50);
|
|
graphene_point_t center = GRAPHENE_POINT_INIT (25, 25);
|
|
double hradius = 25.0;
|
|
double vradius = 25.0;
|
|
double start = 0;
|
|
double end = 1.0;
|
|
GArray *stops = NULL;
|
|
const Declaration declarations[] = {
|
|
{ "bounds", parse_rect, NULL, &bounds },
|
|
{ "center", parse_point, NULL, ¢er },
|
|
{ "hradius", parse_positive_double, NULL, &hradius },
|
|
{ "vradius", parse_positive_double, NULL, &vradius },
|
|
{ "start", parse_positive_double, NULL, &start },
|
|
{ "end", parse_positive_double, NULL, &end },
|
|
{ "stops", parse_stops, clear_stops, &stops },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
if (stops == NULL)
|
|
{
|
|
GskColorStop from = { 0.0, GDK_RGBA("AAFF00") };
|
|
GskColorStop to = { 1.0, GDK_RGBA("FF00CC") };
|
|
|
|
stops = g_array_new (FALSE, FALSE, sizeof (GskColorStop));
|
|
g_array_append_val (stops, from);
|
|
g_array_append_val (stops, to);
|
|
}
|
|
|
|
if (repeating)
|
|
result = gsk_repeating_radial_gradient_node_new (&bounds, ¢er, hradius, vradius, start, end,
|
|
(GskColorStop *) stops->data, stops->len);
|
|
else
|
|
result = gsk_radial_gradient_node_new (&bounds, ¢er, hradius, vradius, start, end,
|
|
(GskColorStop *) stops->data, stops->len);
|
|
|
|
g_array_free (stops, TRUE);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_radial_gradient_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
return parse_radial_gradient_node_internal (parser, context, FALSE);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_repeating_radial_gradient_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
return parse_radial_gradient_node_internal (parser, context, TRUE);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_conic_gradient_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50);
|
|
graphene_point_t center = GRAPHENE_POINT_INIT (25, 25);
|
|
double rotation = 0.0;
|
|
GArray *stops = NULL;
|
|
const Declaration declarations[] = {
|
|
{ "bounds", parse_rect, NULL, &bounds },
|
|
{ "center", parse_point, NULL, ¢er },
|
|
{ "rotation", parse_double, NULL, &rotation },
|
|
{ "stops", parse_stops, clear_stops, &stops },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
if (stops == NULL)
|
|
{
|
|
GskColorStop from = { 0.0, GDK_RGBA("AAFF00") };
|
|
GskColorStop to = { 1.0, GDK_RGBA("FF00CC") };
|
|
|
|
stops = g_array_new (FALSE, FALSE, sizeof (GskColorStop));
|
|
g_array_append_val (stops, from);
|
|
g_array_append_val (stops, to);
|
|
}
|
|
|
|
result = gsk_conic_gradient_node_new (&bounds, ¢er, rotation,
|
|
(GskColorStop *) stops->data, stops->len);
|
|
|
|
g_array_free (stops, TRUE);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_inset_shadow_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
GskRoundedRect outline = GSK_ROUNDED_RECT_INIT (0, 0, 50, 50);
|
|
GdkRGBA color = GDK_RGBA("000000");
|
|
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_positive_double, NULL, &blur }
|
|
};
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
|
|
return gsk_inset_shadow_node_new (&outline, &color, dx, dy, spread, blur);
|
|
}
|
|
|
|
typedef union {
|
|
gint32 i;
|
|
double v[4];
|
|
} UniformValue;
|
|
|
|
typedef struct {
|
|
GskGLShader *shader;
|
|
GskShaderArgsBuilder *args;
|
|
} ShaderInfo;
|
|
|
|
static void
|
|
clear_shader_info (gpointer data)
|
|
{
|
|
ShaderInfo *info = data;
|
|
g_clear_object (&info->shader);
|
|
g_clear_pointer (&info->args, gsk_shader_args_builder_unref);
|
|
}
|
|
|
|
static gboolean
|
|
parse_shader (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer out_shader_info)
|
|
{
|
|
ShaderInfo *shader_info = out_shader_info;
|
|
char *sourcecode = NULL;
|
|
GBytes *bytes;
|
|
GskGLShader *shader;
|
|
|
|
if (!parse_string (parser, context, &sourcecode))
|
|
{
|
|
gtk_css_parser_error_value (parser, "Not a string");
|
|
return FALSE;
|
|
}
|
|
|
|
bytes = g_bytes_new_take (sourcecode, strlen (sourcecode));
|
|
shader = gsk_gl_shader_new_from_bytes (bytes);
|
|
g_bytes_unref (bytes);
|
|
|
|
shader_info->shader = shader;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_uniform_value (GtkCssParser *parser,
|
|
int idx,
|
|
ShaderInfo *shader_info)
|
|
{
|
|
switch (gsk_gl_shader_get_uniform_type (shader_info->shader, idx))
|
|
{
|
|
case GSK_GL_UNIFORM_TYPE_FLOAT:
|
|
{
|
|
double f;
|
|
if (!gtk_css_parser_consume_number (parser, &f))
|
|
return FALSE;
|
|
gsk_shader_args_builder_set_float (shader_info->args, idx, f);
|
|
}
|
|
break;
|
|
|
|
case GSK_GL_UNIFORM_TYPE_INT:
|
|
{
|
|
int i;
|
|
if (!gtk_css_parser_consume_integer (parser, &i))
|
|
return FALSE;
|
|
gsk_shader_args_builder_set_int (shader_info->args, idx, i);
|
|
}
|
|
break;
|
|
|
|
case GSK_GL_UNIFORM_TYPE_UINT:
|
|
{
|
|
int i;
|
|
if (!gtk_css_parser_consume_integer (parser, &i) || i < 0)
|
|
return FALSE;
|
|
gsk_shader_args_builder_set_uint (shader_info->args, idx, i);
|
|
}
|
|
break;
|
|
|
|
case GSK_GL_UNIFORM_TYPE_BOOL:
|
|
{
|
|
int i;
|
|
if (!gtk_css_parser_consume_integer (parser, &i) || (i != 0 && i != 1))
|
|
return FALSE;
|
|
gsk_shader_args_builder_set_bool (shader_info->args, idx, i);
|
|
}
|
|
break;
|
|
|
|
case GSK_GL_UNIFORM_TYPE_VEC2:
|
|
{
|
|
double f0, f1;
|
|
graphene_vec2_t v;
|
|
if (!gtk_css_parser_consume_number (parser, &f0) ||
|
|
!gtk_css_parser_consume_number (parser, &f1))
|
|
return FALSE;
|
|
graphene_vec2_init (&v, f0, f1);
|
|
gsk_shader_args_builder_set_vec2 (shader_info->args, idx, &v);
|
|
}
|
|
break;
|
|
|
|
case GSK_GL_UNIFORM_TYPE_VEC3:
|
|
{
|
|
double f0, f1, f2;
|
|
graphene_vec3_t v;
|
|
if (!gtk_css_parser_consume_number (parser, &f0) ||
|
|
!gtk_css_parser_consume_number (parser, &f1) ||
|
|
!gtk_css_parser_consume_number (parser, &f2))
|
|
return FALSE;
|
|
graphene_vec3_init (&v, f0, f1, f2);
|
|
gsk_shader_args_builder_set_vec3 (shader_info->args, idx, &v);
|
|
}
|
|
break;
|
|
|
|
case GSK_GL_UNIFORM_TYPE_VEC4:
|
|
{
|
|
double f0, f1, f2, f3;
|
|
graphene_vec4_t v;
|
|
if (!gtk_css_parser_consume_number (parser, &f0) ||
|
|
!gtk_css_parser_consume_number (parser, &f1) ||
|
|
!gtk_css_parser_consume_number (parser, &f2) ||
|
|
!gtk_css_parser_consume_number (parser, &f3))
|
|
return FALSE;
|
|
graphene_vec4_init (&v, f0, f1, f2, f3);
|
|
gsk_shader_args_builder_set_vec4 (shader_info->args, idx, &v);
|
|
}
|
|
break;
|
|
|
|
case GSK_GL_UNIFORM_TYPE_NONE:
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
if (idx < gsk_gl_shader_get_n_uniforms (shader_info->shader))
|
|
{
|
|
if (!gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_shader_args (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer data)
|
|
{
|
|
ShaderInfo *shader_info = data;
|
|
int n_uniforms;
|
|
int i;
|
|
|
|
shader_info->args = gsk_shader_args_builder_new (shader_info->shader, NULL);
|
|
n_uniforms = gsk_gl_shader_get_n_uniforms (shader_info->shader);
|
|
|
|
for (i = 0; i < n_uniforms; i++)
|
|
{
|
|
if (!parse_uniform_value (parser, i, shader_info))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static const char default_glsl[] =
|
|
"void\n"
|
|
"mainImage(out vec4 fragColor,\n"
|
|
" in vec2 fragCoord,\n"
|
|
" in vec2 resolution,\n"
|
|
" in vec2 uv)\n"
|
|
"{\n"
|
|
" fragColor = vec4(1.0, 105.0/255.0, 180.0/255.0, 1.0);\n"
|
|
"}";
|
|
|
|
static GskGLShader *
|
|
get_default_glshader (void)
|
|
{
|
|
GBytes *bytes;
|
|
GskGLShader *shader;
|
|
|
|
bytes = g_bytes_new (default_glsl, strlen (default_glsl) + 1);
|
|
shader = gsk_gl_shader_new_from_bytes (bytes);
|
|
g_bytes_unref (bytes);
|
|
|
|
return shader;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_glshader_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50);
|
|
GskRenderNode *child[4] = { NULL, };
|
|
ShaderInfo shader_info = {
|
|
NULL,
|
|
NULL,
|
|
};
|
|
const Declaration declarations[] = {
|
|
{ "bounds", parse_rect, NULL, &bounds },
|
|
{ "sourcecode", parse_shader, NULL, &shader_info },
|
|
{ "args", parse_shader_args, clear_shader_info, &shader_info },
|
|
{ "child1", parse_node, clear_node, &child[0] },
|
|
{ "child2", parse_node, clear_node, &child[1] },
|
|
{ "child3", parse_node, clear_node, &child[2] },
|
|
{ "child4", parse_node, clear_node, &child[3] },
|
|
};
|
|
GskGLShader *shader;
|
|
GskShaderArgsBuilder *builder;
|
|
GskRenderNode *node;
|
|
GBytes *args = NULL;
|
|
int len, i;
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
|
|
for (len = 0; len < 4; len++)
|
|
{
|
|
if (child[len] == NULL)
|
|
break;
|
|
}
|
|
|
|
if (shader_info.shader)
|
|
shader = shader_info.shader;
|
|
else
|
|
shader = get_default_glshader ();
|
|
|
|
if (shader_info.args)
|
|
builder = shader_info.args;
|
|
else
|
|
builder = gsk_shader_args_builder_new (shader, NULL);
|
|
|
|
args = gsk_shader_args_builder_free_to_args (builder);
|
|
|
|
node = gsk_gl_shader_node_new (shader, &bounds, args, child, len);
|
|
|
|
g_bytes_unref (args);
|
|
g_object_unref (shader);
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
if (child[i])
|
|
gsk_render_node_unref (child[i]);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_mask_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
GskRenderNode *source = NULL;
|
|
GskRenderNode *mask = NULL;
|
|
GskMaskMode mode = GSK_MASK_MODE_ALPHA;
|
|
const Declaration declarations[] = {
|
|
{ "mode", parse_mask_mode, NULL, &mode },
|
|
{ "source", parse_node, clear_node, &source },
|
|
{ "mask", parse_node, clear_node, &mask },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS(declarations));
|
|
if (source == NULL)
|
|
source = create_default_render_node ();
|
|
if (mask == NULL)
|
|
mask = gsk_color_node_new (&GDK_RGBA("AAFF00"), &GRAPHENE_RECT_INIT (0, 0, 50, 50));
|
|
|
|
result = gsk_mask_node_new (source, mask, mode);
|
|
|
|
gsk_render_node_unref (source);
|
|
gsk_render_node_unref (mask);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_border_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
GskRoundedRect outline = GSK_ROUNDED_RECT_INIT (0, 0, 50, 50);
|
|
float widths[4] = { 1, 1, 1, 1 };
|
|
GdkRGBA colors[4] = { GDK_RGBA("000"), GDK_RGBA("000"), GDK_RGBA("000"), GDK_RGBA("000") };
|
|
const Declaration declarations[] = {
|
|
{ "outline", parse_rounded_rect, NULL, &outline },
|
|
{ "widths", parse_float4, NULL, &widths },
|
|
{ "colors", parse_colors4, NULL, &colors }
|
|
};
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
|
|
return gsk_border_node_new (&outline, widths, colors);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_texture_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50);
|
|
GdkTexture *texture = NULL;
|
|
const Declaration declarations[] = {
|
|
{ "bounds", parse_rect, NULL, &bounds },
|
|
{ "texture", parse_texture, clear_texture, &texture }
|
|
};
|
|
GskRenderNode *node;
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
|
|
if (texture == NULL)
|
|
texture = create_default_texture ();
|
|
|
|
node = gsk_texture_node_new (texture, &bounds);
|
|
g_object_unref (texture);
|
|
|
|
return node;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_texture_scale_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50);
|
|
GdkTexture *texture = NULL;
|
|
GskScalingFilter filter = GSK_SCALING_FILTER_LINEAR;
|
|
const Declaration declarations[] = {
|
|
{ "bounds", parse_rect, NULL, &bounds },
|
|
{ "texture", parse_texture, clear_texture, &texture },
|
|
{ "filter", parse_scaling_filter, NULL, &filter }
|
|
};
|
|
GskRenderNode *node;
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
|
|
if (texture == NULL)
|
|
texture = create_default_texture ();
|
|
|
|
node = gsk_texture_scale_node_new (texture, &bounds, filter);
|
|
g_object_unref (texture);
|
|
|
|
return node;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_cairo_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
graphene_rect_t bounds = GRAPHENE_RECT_INIT (0, 0, 50, 50);
|
|
GdkTexture *pixels = NULL;
|
|
cairo_surface_t *surface = NULL;
|
|
const Declaration declarations[] = {
|
|
{ "bounds", parse_rect, NULL, &bounds },
|
|
{ "pixels", parse_texture, clear_texture, &pixels },
|
|
{ "script", parse_script, clear_surface, &surface }
|
|
};
|
|
GskRenderNode *node;
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
|
|
node = gsk_cairo_node_new (&bounds);
|
|
|
|
if (surface != NULL)
|
|
{
|
|
cairo_t *cr = gsk_cairo_node_get_draw_context (node);
|
|
cairo_set_source_surface (cr, surface, 0, 0);
|
|
cairo_paint (cr);
|
|
cairo_destroy (cr);
|
|
}
|
|
else if (pixels != NULL)
|
|
{
|
|
cairo_t *cr = gsk_cairo_node_get_draw_context (node);
|
|
surface = gdk_texture_download_surface (pixels);
|
|
cairo_set_source_surface (cr, surface, 0, 0);
|
|
cairo_paint (cr);
|
|
cairo_destroy (cr);
|
|
}
|
|
else
|
|
{
|
|
/* do nothing */
|
|
}
|
|
|
|
g_clear_object (&pixels);
|
|
g_clear_pointer (&surface, cairo_surface_destroy);
|
|
|
|
return node;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_outset_shadow_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
GskRoundedRect outline = GSK_ROUNDED_RECT_INIT (0, 0, 50, 50);
|
|
GdkRGBA color = GDK_RGBA("000000");
|
|
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_positive_double, NULL, &blur }
|
|
};
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
|
|
return gsk_outset_shadow_node_new (&outline, &color, dx, dy, spread, blur);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_transform_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
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, context, declarations, G_N_ELEMENTS (declarations));
|
|
if (child == NULL)
|
|
child = create_default_render_node ();
|
|
|
|
/* 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,
|
|
Context *context)
|
|
{
|
|
GskRenderNode *child = NULL;
|
|
double opacity = 0.5;
|
|
const Declaration declarations[] = {
|
|
{ "opacity", parse_double, NULL, &opacity },
|
|
{ "child", parse_node, clear_node, &child },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
if (child == NULL)
|
|
child = create_default_render_node ();
|
|
|
|
result = gsk_opacity_node_new (child, opacity);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_color_matrix_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
GskRenderNode *child = NULL;
|
|
graphene_matrix_t matrix;
|
|
GskTransform *transform = NULL;
|
|
graphene_vec4_t offset;
|
|
const Declaration declarations[] = {
|
|
{ "matrix", parse_transform, clear_transform, &transform },
|
|
{ "offset", parse_vec4, NULL, &offset },
|
|
{ "child", parse_node, clear_node, &child }
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
graphene_vec4_init (&offset, 0, 0, 0, 0);
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
if (child == NULL)
|
|
child = create_default_render_node ();
|
|
|
|
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,
|
|
Context *context)
|
|
{
|
|
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, context, declarations, G_N_ELEMENTS (declarations));
|
|
if (start == NULL)
|
|
start = gsk_color_node_new (&GDK_RGBA("AAFF00"), &GRAPHENE_RECT_INIT (0, 0, 50, 50));
|
|
if (end == NULL)
|
|
end = create_default_render_node ();
|
|
|
|
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,
|
|
Context *context)
|
|
{
|
|
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, context, declarations, G_N_ELEMENTS (declarations));
|
|
if (bottom == NULL)
|
|
bottom = gsk_color_node_new (&GDK_RGBA("AAFF00"), &GRAPHENE_RECT_INIT (0, 0, 50, 50));
|
|
if (top == NULL)
|
|
top = create_default_render_node ();
|
|
|
|
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,
|
|
Context *context)
|
|
{
|
|
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, context, declarations, G_N_ELEMENTS (declarations));
|
|
if (child == NULL)
|
|
child = create_default_render_node ();
|
|
|
|
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 gboolean
|
|
unpack_glyphs (PangoFont *font,
|
|
PangoGlyphString *glyphs)
|
|
{
|
|
PangoGlyphString *ascii = NULL;
|
|
guint i;
|
|
|
|
for (i = 0; i < glyphs->num_glyphs; i++)
|
|
{
|
|
PangoGlyph glyph = glyphs->glyphs[i].glyph;
|
|
|
|
if (glyph < PANGO_GLYPH_INVALID_INPUT - MAX_ASCII_GLYPH ||
|
|
glyph >= PANGO_GLYPH_INVALID_INPUT)
|
|
continue;
|
|
|
|
glyph = glyph - (PANGO_GLYPH_INVALID_INPUT - MAX_ASCII_GLYPH) - MIN_ASCII_GLYPH;
|
|
|
|
if (ascii == NULL)
|
|
{
|
|
ascii = create_ascii_glyphs (font);
|
|
if (ascii == NULL)
|
|
return FALSE;
|
|
}
|
|
|
|
glyphs->glyphs[i].glyph = ascii->glyphs[glyph].glyph;
|
|
glyphs->glyphs[i].geometry.width = ascii->glyphs[glyph].geometry.width;
|
|
}
|
|
|
|
g_clear_pointer (&ascii, pango_glyph_string_free);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_text_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
PangoFont *font = NULL;
|
|
graphene_point_t offset = GRAPHENE_POINT_INIT (0, 0);
|
|
GdkRGBA color = GDK_RGBA("000000");
|
|
PangoGlyphString *glyphs = NULL;
|
|
const Declaration declarations[] = {
|
|
{ "font", parse_font, clear_font, &font },
|
|
{ "offset", parse_point, NULL, &offset },
|
|
{ "color", parse_color, NULL, &color },
|
|
{ "glyphs", parse_glyphs, clear_glyphs, &glyphs }
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
|
|
if (font == NULL)
|
|
{
|
|
font = font_from_string (pango_cairo_font_map_get_default (), "Cantarell 11", TRUE);
|
|
g_assert (font);
|
|
}
|
|
|
|
if (!glyphs)
|
|
{
|
|
const char *text = "Hello";
|
|
PangoGlyphInfo gi = { 0, { 0, 0, 0}, { 1 } };
|
|
guint i;
|
|
|
|
glyphs = pango_glyph_string_new ();
|
|
pango_glyph_string_set_size (glyphs, strlen (text));
|
|
for (i = 0; i < strlen (text); i++)
|
|
{
|
|
gi.glyph = PANGO_GLYPH_INVALID_INPUT - MAX_ASCII_GLYPH + text[i];
|
|
glyphs->glyphs[i] = gi;
|
|
}
|
|
}
|
|
|
|
if (!unpack_glyphs (font, glyphs))
|
|
{
|
|
gtk_css_parser_error_value (parser, "Given font cannot decode the glyph text");
|
|
result = NULL;
|
|
}
|
|
else
|
|
{
|
|
result = gsk_text_node_new (font, glyphs, &color, &offset);
|
|
if (result == NULL)
|
|
{
|
|
gtk_css_parser_error_value (parser, "Glyphs result in empty text");
|
|
}
|
|
}
|
|
|
|
g_object_unref (font);
|
|
pango_glyph_string_free (glyphs);
|
|
|
|
/* return anything, whatever, just not NULL */
|
|
if (result == NULL)
|
|
result = create_default_render_node ();
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_blur_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
GskRenderNode *child = NULL;
|
|
double blur_radius = 1.0;
|
|
const Declaration declarations[] = {
|
|
{ "blur", parse_positive_double, NULL, &blur_radius },
|
|
{ "child", parse_node, clear_node, &child },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
if (child == NULL)
|
|
child = create_default_render_node ();
|
|
|
|
result = gsk_blur_node_new (child, blur_radius);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_clip_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
GskRoundedRect clip = GSK_ROUNDED_RECT_INIT (0, 0, 50, 50);
|
|
GskRenderNode *child = NULL;
|
|
const Declaration declarations[] = {
|
|
{ "clip", parse_rounded_rect, NULL, &clip },
|
|
{ "child", parse_node, clear_node, &child },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
if (child == NULL)
|
|
child = create_default_render_node ();
|
|
|
|
if (gsk_rounded_rect_is_rectilinear (&clip))
|
|
result = gsk_clip_node_new (child, &clip.bounds);
|
|
else
|
|
result = gsk_rounded_clip_node_new (child, &clip);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_rounded_clip_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
GskRoundedRect clip = GSK_ROUNDED_RECT_INIT (0, 0, 50, 50);
|
|
GskRenderNode *child = NULL;
|
|
const Declaration declarations[] = {
|
|
{ "clip", parse_rounded_rect, NULL, &clip },
|
|
{ "child", parse_node, clear_node, &child },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
if (child == NULL)
|
|
child = create_default_render_node ();
|
|
|
|
result = gsk_rounded_clip_node_new (child, &clip);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
parse_path (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer out_path)
|
|
{
|
|
GskPath *path;
|
|
char *str = NULL;
|
|
|
|
if (!parse_string (parser, context, &str))
|
|
return FALSE;
|
|
|
|
path = gsk_path_parse (str);
|
|
g_free (str);
|
|
|
|
if (path == NULL)
|
|
{
|
|
gtk_css_parser_error_value (parser, "Invalid path");
|
|
return FALSE;
|
|
}
|
|
|
|
*((GskPath **) out_path) = path;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
clear_path (gpointer inout_path)
|
|
{
|
|
g_clear_pointer ((GskPath **) inout_path, gsk_path_unref);
|
|
}
|
|
|
|
static gboolean
|
|
parse_dash (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer out_dash)
|
|
{
|
|
GArray *dash;
|
|
double d;
|
|
|
|
/* because CSS does this, too */
|
|
if (gtk_css_parser_try_ident (parser, "none"))
|
|
{
|
|
*((GArray **) out_dash) = NULL;
|
|
return TRUE;
|
|
}
|
|
|
|
dash = g_array_new (FALSE, FALSE, sizeof (float));
|
|
while (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_NUMBER) ||
|
|
gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_INTEGER))
|
|
{
|
|
if (!gtk_css_parser_consume_number (parser, &d))
|
|
{
|
|
g_array_free (dash, TRUE);
|
|
return FALSE;
|
|
}
|
|
|
|
g_array_append_vals (dash, (float[1]) { d }, 1);
|
|
}
|
|
|
|
if (dash->len == 0)
|
|
{
|
|
gtk_css_parser_error_syntax (parser, "Empty dash array");
|
|
g_array_free (dash, TRUE);
|
|
return FALSE;
|
|
}
|
|
|
|
*((GArray **) out_dash) = dash;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
clear_dash (gpointer inout_array)
|
|
{
|
|
g_clear_pointer ((GArray **) inout_array, g_array_unref);
|
|
}
|
|
|
|
static gboolean
|
|
parse_enum (GtkCssParser *parser,
|
|
GType type,
|
|
gpointer out_value)
|
|
{
|
|
GEnumClass *class;
|
|
GEnumValue *v;
|
|
char *enum_name;
|
|
|
|
enum_name = gtk_css_parser_consume_ident (parser);
|
|
if (enum_name == NULL)
|
|
return FALSE;
|
|
|
|
class = g_type_class_ref (type);
|
|
|
|
v = g_enum_get_value_by_nick (class, enum_name);
|
|
if (v == NULL)
|
|
{
|
|
gtk_css_parser_error_value (parser, "Unknown value \"%s\" for enum \"%s\"",
|
|
enum_name, g_type_name (type));
|
|
g_free (enum_name);
|
|
g_type_class_unref (class);
|
|
return FALSE;
|
|
}
|
|
|
|
*(int*)out_value = v->value;
|
|
|
|
g_free (enum_name);
|
|
g_type_class_unref (class);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_fill_rule (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer out_rule)
|
|
{
|
|
return parse_enum (parser, GSK_TYPE_FILL_RULE, out_rule);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_fill_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
GskRenderNode *child = NULL;
|
|
GskPath *path = NULL;
|
|
int rule = GSK_FILL_RULE_WINDING;
|
|
const Declaration declarations[] = {
|
|
{ "child", parse_node, clear_node, &child },
|
|
{ "path", parse_path, clear_path, &path },
|
|
{ "fill-rule", parse_fill_rule, NULL, &rule },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
if (path == NULL)
|
|
path = create_default_path ();
|
|
if (child == NULL)
|
|
{
|
|
graphene_rect_t bounds;
|
|
gsk_path_get_bounds (path, &bounds);
|
|
child = create_default_render_node_with_bounds (&bounds);
|
|
}
|
|
|
|
result = gsk_fill_node_new (child, path, rule);
|
|
|
|
gsk_path_unref (path);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
parse_line_cap (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer out)
|
|
{
|
|
return parse_enum (parser, GSK_TYPE_LINE_CAP, out);
|
|
}
|
|
|
|
static gboolean
|
|
parse_line_join (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer out)
|
|
{
|
|
return parse_enum (parser, GSK_TYPE_LINE_JOIN, out);
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_stroke_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
GskRenderNode *child = NULL;
|
|
GskPath *path = NULL;
|
|
double line_width = 1.0;
|
|
int line_cap = GSK_LINE_CAP_BUTT;
|
|
int line_join = GSK_LINE_JOIN_MITER;
|
|
double miter_limit = 4.0;
|
|
GArray *dash = NULL;
|
|
double dash_offset = 0.0;
|
|
GskStroke *stroke;
|
|
|
|
const Declaration declarations[] = {
|
|
{ "child", parse_node, clear_node, &child },
|
|
{ "path", parse_path, clear_path, &path },
|
|
{ "line-width", parse_positive_double, NULL, &line_width },
|
|
{ "line-cap", parse_line_cap, NULL, &line_cap },
|
|
{ "line-join", parse_line_join, NULL, &line_join },
|
|
{ "miter-limit", parse_positive_double, NULL, &miter_limit },
|
|
{ "dash", parse_dash, clear_dash, &dash },
|
|
{ "dash-offset", parse_double, NULL, &dash_offset}
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
if (path == NULL)
|
|
path = create_default_path ();
|
|
|
|
stroke = gsk_stroke_new (line_width);
|
|
gsk_stroke_set_line_cap (stroke, line_cap);
|
|
gsk_stroke_set_line_join (stroke, line_join);
|
|
gsk_stroke_set_miter_limit (stroke, miter_limit);
|
|
if (dash)
|
|
{
|
|
gsk_stroke_set_dash (stroke, (float *) dash->data, dash->len);
|
|
g_array_free (dash, TRUE);
|
|
}
|
|
gsk_stroke_set_dash_offset (stroke, dash_offset);
|
|
|
|
if (child == NULL)
|
|
{
|
|
graphene_rect_t bounds;
|
|
gsk_path_get_stroke_bounds (path, stroke, &bounds);
|
|
child = create_default_render_node_with_bounds (&bounds);
|
|
}
|
|
|
|
result = gsk_stroke_node_new (child, path, stroke);
|
|
|
|
gsk_path_unref (path);
|
|
gsk_stroke_free (stroke);
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_shadow_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
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, context, declarations, G_N_ELEMENTS (declarations));
|
|
if (child == NULL)
|
|
child = create_default_render_node ();
|
|
|
|
if (shadows->len == 0)
|
|
{
|
|
GskShadow default_shadow = { GDK_RGBA("000000"), 1, 1, 0 };
|
|
g_array_append_val (shadows, default_shadow);
|
|
}
|
|
|
|
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,
|
|
Context *context)
|
|
{
|
|
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, context, declarations, G_N_ELEMENTS (declarations));
|
|
if (child == NULL)
|
|
child = create_default_render_node ();
|
|
|
|
result = gsk_debug_node_new (child, message);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static GskRenderNode *
|
|
parse_subsurface_node (GtkCssParser *parser,
|
|
Context *context)
|
|
{
|
|
GskRenderNode *child = NULL;
|
|
const Declaration declarations[] = {
|
|
{ "child", parse_node, clear_node, &child },
|
|
};
|
|
GskRenderNode *result;
|
|
|
|
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
|
if (child == NULL)
|
|
child = create_default_render_node ();
|
|
|
|
result = gsk_subsurface_node_new (child, NULL);
|
|
|
|
gsk_render_node_unref (child);
|
|
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
parse_node (GtkCssParser *parser,
|
|
Context *context,
|
|
gpointer out_node)
|
|
{
|
|
static struct {
|
|
const char *name;
|
|
GskRenderNode * (* func) (GtkCssParser *, Context *);
|
|
} node_parsers[] = {
|
|
{ "blend", parse_blend_node },
|
|
{ "blur", parse_blur_node },
|
|
{ "border", parse_border_node },
|
|
{ "cairo", parse_cairo_node },
|
|
{ "clip", parse_clip_node },
|
|
{ "color", parse_color_node },
|
|
{ "color-matrix", parse_color_matrix_node },
|
|
{ "container", parse_container_node },
|
|
{ "cross-fade", parse_cross_fade_node },
|
|
{ "debug", parse_debug_node },
|
|
{ "inset-shadow", parse_inset_shadow_node },
|
|
{ "linear-gradient", parse_linear_gradient_node },
|
|
{ "radial-gradient", parse_radial_gradient_node },
|
|
{ "conic-gradient", parse_conic_gradient_node },
|
|
{ "opacity", parse_opacity_node },
|
|
{ "outset-shadow", parse_outset_shadow_node },
|
|
{ "repeat", parse_repeat_node },
|
|
{ "repeating-linear-gradient", parse_repeating_linear_gradient_node },
|
|
{ "repeating-radial-gradient", parse_repeating_radial_gradient_node },
|
|
{ "rounded-clip", parse_rounded_clip_node },
|
|
{ "fill", parse_fill_node },
|
|
{ "stroke", parse_stroke_node },
|
|
{ "shadow", parse_shadow_node },
|
|
{ "text", parse_text_node },
|
|
{ "texture", parse_texture_node },
|
|
{ "texture-scale", parse_texture_scale_node },
|
|
{ "transform", parse_transform_node },
|
|
{ "glshader", parse_glshader_node },
|
|
{ "mask", parse_mask_node },
|
|
{ "subsurface", parse_subsurface_node },
|
|
};
|
|
GskRenderNode **node_p = out_node;
|
|
const GtkCssToken *token;
|
|
guint i;
|
|
|
|
token = gtk_css_parser_get_token (parser);
|
|
if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_STRING))
|
|
if (gtk_css_token_is (token, GTK_CSS_TOKEN_STRING))
|
|
{
|
|
GskRenderNode *node;
|
|
char *node_name;
|
|
|
|
node_name = gtk_css_parser_consume_string (parser);
|
|
|
|
if (context->named_nodes)
|
|
node = g_hash_table_lookup (context->named_nodes, node_name);
|
|
else
|
|
node = NULL;
|
|
|
|
if (node)
|
|
{
|
|
*node_p = gsk_render_node_ref (node);
|
|
g_free (node_name);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
gtk_css_parser_error_value (parser, "No node named \"%s\"", node_name);
|
|
g_free (node_name);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (node_parsers); i++)
|
|
{
|
|
if (gtk_css_parser_try_ident (parser, node_parsers[i].name))
|
|
{
|
|
GskRenderNode *node;
|
|
GtkCssLocation node_name_start_location, node_name_end_location;
|
|
char *node_name;
|
|
|
|
if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_STRING))
|
|
{
|
|
node_name_start_location = *gtk_css_parser_get_start_location (parser);
|
|
node_name_end_location = *gtk_css_parser_get_end_location (parser);
|
|
node_name = gtk_css_parser_consume_string (parser);
|
|
}
|
|
else
|
|
node_name = NULL;
|
|
|
|
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, context);
|
|
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);
|
|
|
|
if (node_name)
|
|
{
|
|
if (context->named_nodes == NULL)
|
|
context->named_nodes = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, (GDestroyNotify) gsk_render_node_unref);
|
|
if (g_hash_table_lookup (context->named_nodes, node_name))
|
|
{
|
|
gtk_css_parser_error (parser,
|
|
GTK_CSS_PARSER_ERROR_FAILED,
|
|
&node_name_start_location,
|
|
&node_name_end_location,
|
|
"A node named \"%s\" already exists.", node_name);
|
|
}
|
|
else
|
|
{
|
|
g_hash_table_insert (context->named_nodes, g_strdup (node_name), gsk_render_node_ref (node));
|
|
}
|
|
}
|
|
|
|
*node_p = node;
|
|
}
|
|
|
|
g_free (node_name);
|
|
|
|
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_token_get_string (gtk_css_parser_get_token (parser)));
|
|
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)
|
|
error_func_pair->error_func ((const GskParseLocation *)start,
|
|
(const GskParseLocation *)end,
|
|
error,
|
|
error_func_pair->user_data);
|
|
}
|
|
|
|
GskRenderNode *
|
|
gsk_render_node_deserialize_from_bytes (GBytes *bytes,
|
|
GskParseErrorFunc error_func,
|
|
gpointer user_data)
|
|
{
|
|
GskRenderNode *root = NULL;
|
|
GtkCssParser *parser;
|
|
Context context;
|
|
struct {
|
|
GskParseErrorFunc error_func;
|
|
gpointer user_data;
|
|
} error_func_pair = { error_func, user_data };
|
|
|
|
parser = gtk_css_parser_new_for_bytes (bytes, NULL, gsk_render_node_parser_error,
|
|
&error_func_pair, NULL);
|
|
context_init (&context);
|
|
|
|
root = parse_container_node (parser, &context);
|
|
|
|
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;
|
|
}
|
|
|
|
context_finish (&context);
|
|
gtk_css_parser_unref (parser);
|
|
|
|
return root;
|
|
}
|
|
|
|
|
|
|
|
typedef struct
|
|
{
|
|
int indentation_level;
|
|
GString *str;
|
|
GHashTable *named_nodes;
|
|
gsize named_node_counter;
|
|
GHashTable *named_textures;
|
|
gsize named_texture_counter;
|
|
GHashTable *serialized_fonts;
|
|
} Printer;
|
|
|
|
static void
|
|
printer_init_check_texture (Printer *printer,
|
|
GdkTexture *texture)
|
|
{
|
|
gpointer name;
|
|
|
|
if (!g_hash_table_lookup_extended (printer->named_textures, texture, NULL, &name))
|
|
g_hash_table_insert (printer->named_textures, texture, NULL);
|
|
else if (name == NULL)
|
|
g_hash_table_insert (printer->named_textures, texture, g_strdup (""));
|
|
}
|
|
|
|
static void
|
|
printer_init_duplicates_for_node (Printer *printer,
|
|
GskRenderNode *node)
|
|
{
|
|
gpointer name;
|
|
|
|
if (!g_hash_table_lookup_extended (printer->named_nodes, node, NULL, &name))
|
|
g_hash_table_insert (printer->named_nodes, node, NULL);
|
|
else if (name == NULL)
|
|
g_hash_table_insert (printer->named_nodes, node, g_strdup (""));
|
|
|
|
switch (gsk_render_node_get_node_type (node))
|
|
{
|
|
case GSK_CAIRO_NODE:
|
|
case GSK_TEXT_NODE:
|
|
case GSK_COLOR_NODE:
|
|
case GSK_LINEAR_GRADIENT_NODE:
|
|
case GSK_REPEATING_LINEAR_GRADIENT_NODE:
|
|
case GSK_RADIAL_GRADIENT_NODE:
|
|
case GSK_REPEATING_RADIAL_GRADIENT_NODE:
|
|
case GSK_CONIC_GRADIENT_NODE:
|
|
case GSK_BORDER_NODE:
|
|
case GSK_INSET_SHADOW_NODE:
|
|
case GSK_OUTSET_SHADOW_NODE:
|
|
/* no children */
|
|
break;
|
|
|
|
case GSK_TEXTURE_NODE:
|
|
printer_init_check_texture (printer, gsk_texture_node_get_texture (node));
|
|
break;
|
|
|
|
case GSK_TEXTURE_SCALE_NODE:
|
|
printer_init_check_texture (printer, gsk_texture_scale_node_get_texture (node));
|
|
break;
|
|
|
|
case GSK_TRANSFORM_NODE:
|
|
printer_init_duplicates_for_node (printer, gsk_transform_node_get_child (node));
|
|
break;
|
|
|
|
case GSK_OPACITY_NODE:
|
|
printer_init_duplicates_for_node (printer, gsk_opacity_node_get_child (node));
|
|
break;
|
|
|
|
case GSK_COLOR_MATRIX_NODE:
|
|
printer_init_duplicates_for_node (printer, gsk_color_matrix_node_get_child (node));
|
|
break;
|
|
|
|
case GSK_BLUR_NODE:
|
|
printer_init_duplicates_for_node (printer, gsk_blur_node_get_child (node));
|
|
break;
|
|
|
|
case GSK_REPEAT_NODE:
|
|
printer_init_duplicates_for_node (printer, gsk_repeat_node_get_child (node));
|
|
break;
|
|
|
|
case GSK_CLIP_NODE:
|
|
printer_init_duplicates_for_node (printer, gsk_clip_node_get_child (node));
|
|
break;
|
|
|
|
case GSK_ROUNDED_CLIP_NODE:
|
|
printer_init_duplicates_for_node (printer, gsk_rounded_clip_node_get_child (node));
|
|
break;
|
|
|
|
case GSK_SHADOW_NODE:
|
|
printer_init_duplicates_for_node (printer, gsk_shadow_node_get_child (node));
|
|
break;
|
|
|
|
case GSK_DEBUG_NODE:
|
|
printer_init_duplicates_for_node (printer, gsk_debug_node_get_child (node));
|
|
break;
|
|
|
|
case GSK_FILL_NODE:
|
|
printer_init_duplicates_for_node (printer, gsk_fill_node_get_child (node));
|
|
break;
|
|
|
|
case GSK_STROKE_NODE:
|
|
printer_init_duplicates_for_node (printer, gsk_stroke_node_get_child (node));
|
|
break;
|
|
|
|
case GSK_BLEND_NODE:
|
|
printer_init_duplicates_for_node (printer, gsk_blend_node_get_bottom_child (node));
|
|
printer_init_duplicates_for_node (printer, gsk_blend_node_get_top_child (node));
|
|
break;
|
|
|
|
case GSK_MASK_NODE:
|
|
printer_init_duplicates_for_node (printer, gsk_mask_node_get_source (node));
|
|
printer_init_duplicates_for_node (printer, gsk_mask_node_get_mask (node));
|
|
break;
|
|
|
|
case GSK_CROSS_FADE_NODE:
|
|
printer_init_duplicates_for_node (printer, gsk_cross_fade_node_get_start_child (node));
|
|
printer_init_duplicates_for_node (printer, gsk_cross_fade_node_get_end_child (node));
|
|
break;
|
|
|
|
case GSK_GL_SHADER_NODE:
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < gsk_gl_shader_node_get_n_children (node); i++)
|
|
{
|
|
printer_init_duplicates_for_node (printer, gsk_gl_shader_node_get_child (node, i));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GSK_CONTAINER_NODE:
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < gsk_container_node_get_n_children (node); i++)
|
|
{
|
|
printer_init_duplicates_for_node (printer, gsk_container_node_get_child (node, i));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GSK_SUBSURFACE_NODE:
|
|
printer_init_duplicates_for_node (printer, gsk_subsurface_node_get_child (node));
|
|
break;
|
|
|
|
default:
|
|
case GSK_NOT_A_RENDER_NODE:
|
|
g_assert_not_reached ();
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
static void
|
|
printer_init (Printer *self,
|
|
GskRenderNode *node)
|
|
{
|
|
self->indentation_level = 0;
|
|
self->str = g_string_new (NULL);
|
|
self->named_nodes = g_hash_table_new_full (NULL, NULL, NULL, g_free);
|
|
self->named_node_counter = 0;
|
|
self->named_textures = g_hash_table_new_full (NULL, NULL, NULL, g_free);
|
|
self->named_texture_counter = 0;
|
|
self->serialized_fonts = g_hash_table_new (g_str_hash, g_str_equal);
|
|
|
|
printer_init_duplicates_for_node (self, node);
|
|
}
|
|
|
|
static void
|
|
printer_clear (Printer *self)
|
|
{
|
|
if (self->str)
|
|
g_string_free (self->str, TRUE);
|
|
g_hash_table_unref (self->named_nodes);
|
|
g_hash_table_unref (self->named_textures);
|
|
g_hash_table_unref (self->serialized_fonts);
|
|
}
|
|
|
|
#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_type,
|
|
const char *node_name)
|
|
{
|
|
g_string_append_printf (self->str, "%s ", node_type);
|
|
if (node_name)
|
|
{
|
|
gtk_css_print_string (self->str, node_name, FALSE);
|
|
g_string_append_c (self->str, ' ');
|
|
}
|
|
g_string_append_printf (self->str, "{\n");
|
|
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,
|
|
float default_value)
|
|
{
|
|
/* Don't approximate-compare here, better be too verbose */
|
|
if (value == default_value)
|
|
return;
|
|
|
|
_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_string_param (Printer *p,
|
|
const char *param_name,
|
|
const char *value)
|
|
{
|
|
_indent (p);
|
|
g_string_append_printf (p->str, "%s: ", param_name);
|
|
gtk_css_print_string (p->str, value, TRUE);
|
|
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 void
|
|
append_stops_param (Printer *p,
|
|
const char *param_name,
|
|
const GskColorStop *stops,
|
|
gsize n_stops)
|
|
{
|
|
gsize i;
|
|
|
|
_indent (p);
|
|
g_string_append (p->str, param_name);
|
|
g_string_append (p->str, ": ");
|
|
|
|
for (i = 0; i < n_stops; i ++)
|
|
{
|
|
if (i > 0)
|
|
g_string_append (p->str, ", ");
|
|
|
|
string_append_double (p->str, stops[i].offset);
|
|
g_string_append_c (p->str, ' ');
|
|
append_rgba (p->str, &stops[i].color);
|
|
}
|
|
g_string_append (p->str, ";\n");
|
|
}
|
|
|
|
static cairo_status_t
|
|
cairo_write_array (void *closure,
|
|
const unsigned char *data,
|
|
unsigned int length)
|
|
{
|
|
g_byte_array_append (closure, data, length);
|
|
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
cairo_destroy_array (gpointer array)
|
|
{
|
|
g_byte_array_free (array, TRUE);
|
|
}
|
|
|
|
static void
|
|
append_escaping_newlines (GString *str,
|
|
const char *string)
|
|
{
|
|
gsize len;
|
|
|
|
do {
|
|
len = strcspn (string, "\n");
|
|
g_string_append_len (str, string, len);
|
|
string += len;
|
|
if (*string)
|
|
{
|
|
g_string_append (str, "\\\n");
|
|
string++;
|
|
}
|
|
} while (*string);
|
|
}
|
|
|
|
/* like g_base64 encode, but breaks lines
|
|
* in CSS-compatible way
|
|
*/
|
|
static char *
|
|
base64_encode_with_linebreaks (const guchar *data,
|
|
gsize len)
|
|
{
|
|
gsize max;
|
|
char *out;
|
|
int state = 0, outlen;
|
|
int save = 0;
|
|
|
|
g_return_val_if_fail (data != NULL || len == 0, NULL);
|
|
|
|
/* We can use a smaller limit here, since we know the saved state is 0,
|
|
+1 is needed for trailing \0, also check for unlikely integer overflow */
|
|
g_return_val_if_fail (len < ((G_MAXSIZE - 1) / 4 - 1) * 3, NULL);
|
|
|
|
/* The glib docs say:
|
|
*
|
|
* The output buffer must be large enough to fit all the data that will
|
|
* be written to it. Due to the way base64 encodes you will need
|
|
* at least: (@len / 3 + 1) * 4 + 4 bytes (+ 4 may be needed in case of
|
|
* non-zero state). If you enable line-breaking you will need at least:
|
|
* ((@len / 3 + 1) * 4 + 4) / 76 + 1 bytes of extra space.
|
|
*/
|
|
max = (len / 3 + 1) * 4;
|
|
max += ((len / 3 + 1) * 4 + 4) / 76 + 1;
|
|
/* and the null byte */
|
|
max += 1;
|
|
|
|
out = g_malloc (max);
|
|
|
|
outlen = g_base64_encode_step (data, len, TRUE, out, &state, &save);
|
|
outlen += g_base64_encode_close (TRUE, out + outlen, &state, &save);
|
|
out[outlen] = '\0';
|
|
|
|
return out;
|
|
}
|
|
|
|
static void
|
|
append_texture_param (Printer *p,
|
|
const char *param_name,
|
|
GdkTexture *texture)
|
|
{
|
|
GBytes *bytes;
|
|
char *b64;
|
|
const char *texture_name;
|
|
|
|
_indent (p);
|
|
|
|
g_string_append_printf (p->str, "%s: ", param_name);
|
|
|
|
texture_name = g_hash_table_lookup (p->named_textures, texture);
|
|
if (texture_name == NULL)
|
|
{
|
|
/* nothing to do here, texture is unique */
|
|
}
|
|
else if (texture_name[0])
|
|
{
|
|
/* texture has been named already */
|
|
gtk_css_print_string (p->str, texture_name, TRUE);
|
|
g_string_append (p->str, ";\n");
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
/* texture needs a name */
|
|
char *new_name = g_strdup_printf ("texture%zu", ++p->named_texture_counter);
|
|
gtk_css_print_string (p->str, new_name, TRUE);
|
|
g_string_append_c (p->str, ' ');
|
|
g_hash_table_insert (p->named_textures, texture, new_name);
|
|
}
|
|
|
|
switch (gdk_memory_format_get_depth (gdk_texture_get_format (texture)))
|
|
{
|
|
case GDK_MEMORY_U8:
|
|
case GDK_MEMORY_U16:
|
|
bytes = gdk_texture_save_to_png_bytes (texture);
|
|
g_string_append (p->str, "url(\"data:image/png;base64,");
|
|
break;
|
|
|
|
case GDK_MEMORY_FLOAT16:
|
|
case GDK_MEMORY_FLOAT32:
|
|
bytes = gdk_texture_save_to_tiff_bytes (texture);
|
|
g_string_append (p->str, "url(\"data:image/tiff;base64,");
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
b64 = base64_encode_with_linebreaks (g_bytes_get_data (bytes, NULL),
|
|
g_bytes_get_size (bytes));
|
|
append_escaping_newlines (p->str, b64);
|
|
g_free (b64);
|
|
g_string_append (p->str, "\");\n");
|
|
|
|
g_bytes_unref (bytes);
|
|
}
|
|
|
|
static void
|
|
gsk_text_node_serialize_font (GskRenderNode *node,
|
|
Printer *p)
|
|
{
|
|
PangoFont *font = gsk_text_node_get_font (node);
|
|
PangoFontMap *fontmap = pango_font_get_font_map (font);
|
|
PangoFontDescription *desc;
|
|
char *s;
|
|
|
|
desc = pango_font_describe (font);
|
|
s = pango_font_description_to_string (desc);
|
|
g_string_append_printf (p->str, "\"%s\"", s);
|
|
g_free (s);
|
|
pango_font_description_free (desc);
|
|
|
|
/* Check if this is a custom font that we created from a url */
|
|
if (!g_object_get_data (G_OBJECT (fontmap), "font-files"))
|
|
return;
|
|
|
|
#ifdef HAVE_PANGOFT
|
|
{
|
|
FcPattern *pat;
|
|
FcResult res;
|
|
const char *file;
|
|
char *data;
|
|
gsize len;
|
|
char *b64;
|
|
|
|
pat = pango_fc_font_get_pattern (PANGO_FC_FONT (font));
|
|
res = FcPatternGetString (pat, FC_FILE, 0, (FcChar8 **)&file);
|
|
if (res != FcResultMatch)
|
|
return;
|
|
|
|
if (g_hash_table_contains (p->serialized_fonts, file))
|
|
return;
|
|
|
|
if (!g_file_get_contents (file, &data, &len, NULL))
|
|
return;
|
|
|
|
g_hash_table_add (p->serialized_fonts, (gpointer) file);
|
|
|
|
b64 = base64_encode_with_linebreaks ((const guchar *) data, len);
|
|
|
|
g_string_append (p->str, " url(\"data:font/ttf;base64,");
|
|
append_escaping_newlines (p->str, b64);
|
|
g_string_append (p->str, "\")");
|
|
|
|
g_free (b64);
|
|
g_free (data);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
gsk_text_node_serialize_glyphs (GskRenderNode *node,
|
|
GString *p)
|
|
{
|
|
const guint n_glyphs = gsk_text_node_get_num_glyphs (node);
|
|
const PangoGlyphInfo *glyphs = gsk_text_node_get_glyphs (node, NULL);
|
|
PangoFont *font = gsk_text_node_get_font (node);
|
|
GString *str;
|
|
guint i, j;
|
|
PangoGlyphString *ascii;
|
|
|
|
ascii = create_ascii_glyphs (font);
|
|
str = g_string_new ("");
|
|
|
|
for (i = 0; i < n_glyphs; i++)
|
|
{
|
|
if (ascii)
|
|
{
|
|
for (j = 0; j < ascii->num_glyphs; j++)
|
|
{
|
|
if (glyphs[i].glyph == ascii->glyphs[j].glyph &&
|
|
glyphs[i].geometry.width == ascii->glyphs[j].geometry.width &&
|
|
glyphs[i].geometry.x_offset == 0 &&
|
|
glyphs[i].geometry.y_offset == 0 &&
|
|
glyphs[i].attr.is_cluster_start &&
|
|
!glyphs[i].attr.is_color)
|
|
{
|
|
switch (j + MIN_ASCII_GLYPH)
|
|
{
|
|
case '\\':
|
|
g_string_append (str, "\\\\");
|
|
break;
|
|
case '"':
|
|
g_string_append (str, "\\\"");
|
|
break;
|
|
default:
|
|
g_string_append_c (str, j + MIN_ASCII_GLYPH);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (j != ascii->num_glyphs)
|
|
continue;
|
|
}
|
|
|
|
if (str->len)
|
|
{
|
|
g_string_append_printf (p, "\"%s\", ", str->str);
|
|
g_string_set_size (str, 0);
|
|
}
|
|
|
|
g_string_append_printf (p, "%u ", glyphs[i].glyph);
|
|
string_append_double (p, (double) glyphs[i].geometry.width / PANGO_SCALE);
|
|
if (!glyphs[i].attr.is_cluster_start ||
|
|
glyphs[i].attr.is_color ||
|
|
glyphs[i].geometry.x_offset != 0 ||
|
|
glyphs[i].geometry.y_offset != 0)
|
|
{
|
|
g_string_append (p, " ");
|
|
string_append_double (p, (double) glyphs[i].geometry.x_offset / PANGO_SCALE);
|
|
g_string_append (p, " ");
|
|
string_append_double (p, (double) glyphs[i].geometry.y_offset / PANGO_SCALE);
|
|
if (!glyphs[i].attr.is_cluster_start)
|
|
g_string_append (p, " same-cluster");
|
|
if (glyphs[i].attr.is_color)
|
|
g_string_append (p, " color");
|
|
}
|
|
|
|
if (i + 1 < n_glyphs)
|
|
g_string_append (p, ", ");
|
|
}
|
|
|
|
if (str->len)
|
|
g_string_append_printf (p, "\"%s\"", str->str);
|
|
|
|
g_string_free (str, TRUE);
|
|
if (ascii)
|
|
pango_glyph_string_free (ascii);
|
|
}
|
|
|
|
static const char *
|
|
enum_to_nick (GType type,
|
|
int value)
|
|
{
|
|
GEnumClass *class;
|
|
GEnumValue *v;
|
|
|
|
class = g_type_class_ref (type);
|
|
v = g_enum_get_value (class, value);
|
|
g_type_class_unref (class);
|
|
|
|
return v->value_nick;
|
|
}
|
|
|
|
static void
|
|
append_enum_param (Printer *p,
|
|
const char *param_name,
|
|
GType type,
|
|
int value)
|
|
{
|
|
_indent (p);
|
|
g_string_append_printf (p->str, "%s: ", param_name);
|
|
g_string_append (p->str, enum_to_nick (type, value));
|
|
g_string_append_c (p->str, ';');
|
|
g_string_append_c (p->str, '\n');
|
|
}
|
|
|
|
static void
|
|
append_path_param (Printer *p,
|
|
const char *param_name,
|
|
GskPath *path)
|
|
{
|
|
char *str, *s;
|
|
|
|
_indent (p);
|
|
g_string_append (p->str, "path: \"\\\n");
|
|
str = gsk_path_to_string (path);
|
|
/* Put each command on a new line */
|
|
for (s = str; *s; s++)
|
|
{
|
|
if (*s == ' ' &&
|
|
(s[1] == 'M' || s[1] == 'C' || s[1] == 'Z' || s[1] == 'L'))
|
|
*s = '\n';
|
|
}
|
|
append_escaping_newlines (p->str, str);
|
|
g_string_append (p->str, "\";\n");
|
|
g_free (str);
|
|
}
|
|
|
|
static void
|
|
append_dash_param (Printer *p,
|
|
const char *param_name,
|
|
const float *dash,
|
|
gsize n_dash)
|
|
{
|
|
_indent (p);
|
|
g_string_append (p->str, "dash: ");
|
|
|
|
if (n_dash == 0)
|
|
{
|
|
g_string_append (p->str, "none");
|
|
}
|
|
else
|
|
{
|
|
gsize i;
|
|
|
|
string_append_double (p->str, dash[0]);
|
|
for (i = 1; i < n_dash; i++)
|
|
{
|
|
g_string_append_c (p->str, ' ');
|
|
string_append_double (p->str, dash[i]);
|
|
}
|
|
}
|
|
|
|
g_string_append (p->str, ";\n");
|
|
}
|
|
|
|
static void
|
|
render_node_print (Printer *p,
|
|
GskRenderNode *node)
|
|
{
|
|
char *b64;
|
|
const char *node_name;
|
|
|
|
node_name = g_hash_table_lookup (p->named_nodes, node);
|
|
if (node_name == NULL)
|
|
{
|
|
/* nothing to do here, node is unique */
|
|
}
|
|
else if (node_name[0])
|
|
{
|
|
/* node has been named already */
|
|
gtk_css_print_string (p->str, node_name, TRUE);
|
|
g_string_append (p->str, ";\n");
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
/* node needs a name */
|
|
char *new_name = g_strdup_printf ("node%zu", ++p->named_node_counter);
|
|
g_hash_table_insert (p->named_nodes, node, new_name);
|
|
node_name = new_name;
|
|
}
|
|
|
|
switch (gsk_render_node_get_node_type (node))
|
|
{
|
|
case GSK_CONTAINER_NODE:
|
|
{
|
|
guint i;
|
|
|
|
start_node (p, "container", node_name);
|
|
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", node_name);
|
|
append_rect_param (p, "bounds", &node->bounds);
|
|
append_rgba_param (p, "color", gsk_color_node_get_color (node));
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_CROSS_FADE_NODE:
|
|
{
|
|
start_node (p, "cross-fade", node_name);
|
|
|
|
append_float_param (p, "progress", gsk_cross_fade_node_get_progress (node), 0.5f);
|
|
append_node_param (p, "start", gsk_cross_fade_node_get_start_child (node));
|
|
append_node_param (p, "end", gsk_cross_fade_node_get_end_child (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_REPEATING_LINEAR_GRADIENT_NODE:
|
|
case GSK_LINEAR_GRADIENT_NODE:
|
|
{
|
|
if (gsk_render_node_get_node_type (node) == GSK_REPEATING_LINEAR_GRADIENT_NODE)
|
|
start_node (p, "repeating-linear-gradient", node_name);
|
|
else
|
|
start_node (p, "linear-gradient", node_name);
|
|
|
|
append_rect_param (p, "bounds", &node->bounds);
|
|
append_point_param (p, "start", gsk_linear_gradient_node_get_start (node));
|
|
append_point_param (p, "end", gsk_linear_gradient_node_get_end (node));
|
|
append_stops_param (p, "stops", gsk_linear_gradient_node_get_color_stops (node, NULL),
|
|
gsk_linear_gradient_node_get_n_color_stops (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_REPEATING_RADIAL_GRADIENT_NODE:
|
|
case GSK_RADIAL_GRADIENT_NODE:
|
|
{
|
|
if (gsk_render_node_get_node_type (node) == GSK_REPEATING_RADIAL_GRADIENT_NODE)
|
|
start_node (p, "repeating-radial-gradient", node_name);
|
|
else
|
|
start_node (p, "radial-gradient", node_name);
|
|
|
|
append_rect_param (p, "bounds", &node->bounds);
|
|
append_point_param (p, "center", gsk_radial_gradient_node_get_center (node));
|
|
append_float_param (p, "hradius", gsk_radial_gradient_node_get_hradius (node), 0.0f);
|
|
append_float_param (p, "vradius", gsk_radial_gradient_node_get_vradius (node), 0.0f);
|
|
append_float_param (p, "start", gsk_radial_gradient_node_get_start (node), 0.0f);
|
|
append_float_param (p, "end", gsk_radial_gradient_node_get_end (node), 1.0f);
|
|
|
|
append_stops_param (p, "stops", gsk_radial_gradient_node_get_color_stops (node, NULL),
|
|
gsk_radial_gradient_node_get_n_color_stops (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_CONIC_GRADIENT_NODE:
|
|
{
|
|
start_node (p, "conic-gradient", node_name);
|
|
|
|
append_rect_param (p, "bounds", &node->bounds);
|
|
append_point_param (p, "center", gsk_conic_gradient_node_get_center (node));
|
|
append_float_param (p, "rotation", gsk_conic_gradient_node_get_rotation (node), 0.0f);
|
|
|
|
append_stops_param (p, "stops", gsk_conic_gradient_node_get_color_stops (node, NULL),
|
|
gsk_conic_gradient_node_get_n_color_stops (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_OPACITY_NODE:
|
|
{
|
|
start_node (p, "opacity", node_name);
|
|
|
|
append_float_param (p, "opacity", gsk_opacity_node_get_opacity (node), 0.5f);
|
|
append_node_param (p, "child", gsk_opacity_node_get_child (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_OUTSET_SHADOW_NODE:
|
|
{
|
|
const GdkRGBA *color = gsk_outset_shadow_node_get_color (node);
|
|
|
|
start_node (p, "outset-shadow", node_name);
|
|
|
|
append_float_param (p, "blur", gsk_outset_shadow_node_get_blur_radius (node), 0.0f);
|
|
if (!gdk_rgba_equal (color, &GDK_RGBA("000")))
|
|
append_rgba_param (p, "color", color);
|
|
append_float_param (p, "dx", gsk_outset_shadow_node_get_dx (node), 1.0f);
|
|
append_float_param (p, "dy", gsk_outset_shadow_node_get_dy (node), 1.0f);
|
|
append_rounded_rect_param (p, "outline", gsk_outset_shadow_node_get_outline (node));
|
|
append_float_param (p, "spread", gsk_outset_shadow_node_get_spread (node), 0.0f);
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_CLIP_NODE:
|
|
{
|
|
start_node (p, "clip", node_name);
|
|
|
|
append_rect_param (p, "clip", gsk_clip_node_get_clip (node));
|
|
append_node_param (p, "child", gsk_clip_node_get_child (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_ROUNDED_CLIP_NODE:
|
|
{
|
|
start_node (p, "rounded-clip", node_name);
|
|
|
|
append_rounded_rect_param (p, "clip", gsk_rounded_clip_node_get_clip (node));
|
|
append_node_param (p, "child", gsk_rounded_clip_node_get_child (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_FILL_NODE:
|
|
{
|
|
start_node (p, "fill", node_name);
|
|
|
|
append_node_param (p, "child", gsk_fill_node_get_child (node));
|
|
append_path_param (p, "path", gsk_fill_node_get_path (node));
|
|
append_enum_param (p, "fill-rule", GSK_TYPE_FILL_RULE, gsk_fill_node_get_fill_rule (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_STROKE_NODE:
|
|
{
|
|
const GskStroke *stroke;
|
|
const float *dash;
|
|
gsize n_dash;
|
|
|
|
start_node (p, "stroke", node_name);
|
|
|
|
append_node_param (p, "child", gsk_stroke_node_get_child (node));
|
|
append_path_param (p, "path", gsk_stroke_node_get_path (node));
|
|
|
|
stroke = gsk_stroke_node_get_stroke (node);
|
|
append_float_param (p, "line-width", gsk_stroke_get_line_width (stroke), 0.0f);
|
|
append_enum_param (p, "line-cap", GSK_TYPE_LINE_CAP, gsk_stroke_get_line_cap (stroke));
|
|
append_enum_param (p, "line-join", GSK_TYPE_LINE_JOIN, gsk_stroke_get_line_join (stroke));
|
|
append_float_param (p, "miter-limit", gsk_stroke_get_miter_limit (stroke), 4.0f);
|
|
dash = gsk_stroke_get_dash (stroke, &n_dash);
|
|
if (dash)
|
|
append_dash_param (p, "dash", dash, n_dash);
|
|
append_float_param (p, "dash-offset", gsk_stroke_get_dash_offset (stroke), 0.0f);
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_TRANSFORM_NODE:
|
|
{
|
|
GskTransform *transform = gsk_transform_node_get_transform (node);
|
|
start_node (p, "transform", node_name);
|
|
|
|
if (gsk_transform_get_category (transform) != GSK_TRANSFORM_CATEGORY_IDENTITY)
|
|
append_transform_param (p, "transform", transform);
|
|
append_node_param (p, "child", gsk_transform_node_get_child (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_COLOR_MATRIX_NODE:
|
|
{
|
|
start_node (p, "color-matrix", node_name);
|
|
|
|
if (!graphene_matrix_is_identity (gsk_color_matrix_node_get_color_matrix (node)))
|
|
append_matrix_param (p, "matrix", gsk_color_matrix_node_get_color_matrix (node));
|
|
if (!graphene_vec4_equal (gsk_color_matrix_node_get_color_offset (node), graphene_vec4_zero ()))
|
|
append_vec4_param (p, "offset", gsk_color_matrix_node_get_color_offset (node));
|
|
append_node_param (p, "child", gsk_color_matrix_node_get_child (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_BORDER_NODE:
|
|
{
|
|
const GdkRGBA *colors = gsk_border_node_get_colors (node);
|
|
const float *widths = gsk_border_node_get_widths (node);
|
|
guint i, n;
|
|
start_node (p, "border", node_name);
|
|
|
|
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 if (!gdk_rgba_equal (&colors[0], &GDK_RGBA("000000")))
|
|
n = 1;
|
|
else
|
|
n = 0;
|
|
|
|
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_get_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 if (widths[0] != 1.0)
|
|
n = 1;
|
|
else
|
|
n = 0;
|
|
|
|
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", node_name);
|
|
|
|
_indent (p);
|
|
g_string_append (p->str, "shadows: ");
|
|
for (i = 0; i < n_shadows; i ++)
|
|
{
|
|
const GskShadow *s = gsk_shadow_node_get_shadow (node, i);
|
|
char *color;
|
|
|
|
if (i > 0)
|
|
g_string_append (p->str, ", ");
|
|
|
|
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);
|
|
if (s->radius > 0)
|
|
{
|
|
g_string_append_c (p->str, ' ');
|
|
string_append_double (p->str, s->radius);
|
|
}
|
|
|
|
g_free (color);
|
|
}
|
|
|
|
g_string_append_c (p->str, ';');
|
|
g_string_append_c (p->str, '\n');
|
|
append_node_param (p, "child", gsk_shadow_node_get_child (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_INSET_SHADOW_NODE:
|
|
{
|
|
const GdkRGBA *color = gsk_inset_shadow_node_get_color (node);
|
|
start_node (p, "inset-shadow", node_name);
|
|
|
|
append_float_param (p, "blur", gsk_inset_shadow_node_get_blur_radius (node), 0.0f);
|
|
if (!gdk_rgba_equal (color, &GDK_RGBA("000")))
|
|
append_rgba_param (p, "color", color);
|
|
append_float_param (p, "dx", gsk_inset_shadow_node_get_dx (node), 1.0f);
|
|
append_float_param (p, "dy", gsk_inset_shadow_node_get_dy (node), 1.0f);
|
|
append_rounded_rect_param (p, "outline", gsk_inset_shadow_node_get_outline (node));
|
|
append_float_param (p, "spread", gsk_inset_shadow_node_get_spread (node), 0.0f);
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_TEXTURE_NODE:
|
|
{
|
|
start_node (p, "texture", node_name);
|
|
|
|
append_rect_param (p, "bounds", &node->bounds);
|
|
append_texture_param (p, "texture", gsk_texture_node_get_texture (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_TEXTURE_SCALE_NODE:
|
|
{
|
|
GskScalingFilter filter = gsk_texture_scale_node_get_filter (node);
|
|
|
|
start_node (p, "texture-scale", node_name);
|
|
append_rect_param (p, "bounds", &node->bounds);
|
|
|
|
if (filter != GSK_SCALING_FILTER_LINEAR)
|
|
{
|
|
_indent (p);
|
|
for (unsigned int i = 0; i < G_N_ELEMENTS (scaling_filters); i++)
|
|
{
|
|
if (scaling_filters[i].filter == filter)
|
|
{
|
|
g_string_append_printf (p->str, "filter: %s;\n", scaling_filters[i].name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
append_texture_param (p, "texture", gsk_texture_scale_node_get_texture (node));
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_TEXT_NODE:
|
|
{
|
|
const graphene_point_t *offset = gsk_text_node_get_offset (node);
|
|
const GdkRGBA *color = gsk_text_node_get_color (node);
|
|
|
|
start_node (p, "text", node_name);
|
|
|
|
if (!gdk_rgba_equal (color, &GDK_RGBA ("000000")))
|
|
append_rgba_param (p, "color", color);
|
|
|
|
_indent (p);
|
|
g_string_append (p->str, "font: ");
|
|
gsk_text_node_serialize_font (node, p);
|
|
g_string_append (p->str, ";\n");
|
|
|
|
_indent (p);
|
|
g_string_append (p->str, "glyphs: ");
|
|
gsk_text_node_serialize_glyphs (node, p->str);
|
|
g_string_append (p->str, ";\n");
|
|
|
|
if (!graphene_point_equal (offset, graphene_point_zero ()))
|
|
append_point_param (p, "offset", offset);
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_DEBUG_NODE:
|
|
{
|
|
const char *message = gsk_debug_node_get_message (node);
|
|
|
|
start_node (p, "debug", node_name);
|
|
|
|
/* TODO: We potentially need to escape certain characters in the message */
|
|
if (message)
|
|
{
|
|
_indent (p);
|
|
g_string_append_printf (p->str, "message: \"%s\";\n", message);
|
|
}
|
|
append_node_param (p, "child", gsk_debug_node_get_child (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_BLUR_NODE:
|
|
{
|
|
start_node (p, "blur", node_name);
|
|
|
|
append_float_param (p, "blur", gsk_blur_node_get_radius (node), 1.0f);
|
|
append_node_param (p, "child", gsk_blur_node_get_child (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_GL_SHADER_NODE:
|
|
{
|
|
GskGLShader *shader = gsk_gl_shader_node_get_shader (node);
|
|
GBytes *args = gsk_gl_shader_node_get_args (node);
|
|
|
|
start_node (p, "glshader", node_name);
|
|
|
|
append_rect_param (p, "bounds", &node->bounds);
|
|
|
|
GBytes *bytes = gsk_gl_shader_get_source (shader);
|
|
/* Ensure we are zero-terminated */
|
|
char *sourcecode = g_strndup (g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes));
|
|
append_string_param (p, "sourcecode", sourcecode);
|
|
g_free (sourcecode);
|
|
|
|
if (gsk_gl_shader_get_n_uniforms (shader) > 0)
|
|
{
|
|
GString *data = g_string_new ("");
|
|
|
|
for (guint i = 0; i < gsk_gl_shader_get_n_uniforms (shader); i++)
|
|
{
|
|
if (i > 0)
|
|
g_string_append (data, ", ");
|
|
|
|
switch (gsk_gl_shader_get_uniform_type (shader, i))
|
|
{
|
|
case GSK_GL_UNIFORM_TYPE_NONE:
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
|
|
case GSK_GL_UNIFORM_TYPE_FLOAT:
|
|
{
|
|
float value = gsk_gl_shader_get_arg_float (shader, args, i);
|
|
string_append_double (data, value);
|
|
}
|
|
break;
|
|
|
|
case GSK_GL_UNIFORM_TYPE_INT:
|
|
{
|
|
gint32 value = gsk_gl_shader_get_arg_int (shader, args, i);
|
|
g_string_append_printf (data, "%d", value);
|
|
}
|
|
break;
|
|
|
|
case GSK_GL_UNIFORM_TYPE_UINT:
|
|
{
|
|
guint32 value = gsk_gl_shader_get_arg_uint (shader, args, i);
|
|
g_string_append_printf (data, "%u", value);
|
|
}
|
|
break;
|
|
|
|
case GSK_GL_UNIFORM_TYPE_BOOL:
|
|
{
|
|
gboolean value = gsk_gl_shader_get_arg_bool (shader, args, i);
|
|
g_string_append_printf (data, "%d", value);
|
|
}
|
|
break;
|
|
|
|
case GSK_GL_UNIFORM_TYPE_VEC2:
|
|
{
|
|
graphene_vec2_t value;
|
|
gsk_gl_shader_get_arg_vec2 (shader, args, i,
|
|
&value);
|
|
string_append_double (data, graphene_vec2_get_x (&value));
|
|
g_string_append (data, " ");
|
|
string_append_double (data, graphene_vec2_get_y (&value));
|
|
}
|
|
break;
|
|
|
|
case GSK_GL_UNIFORM_TYPE_VEC3:
|
|
{
|
|
graphene_vec3_t value;
|
|
gsk_gl_shader_get_arg_vec3 (shader, args, i,
|
|
&value);
|
|
string_append_double (data, graphene_vec3_get_x (&value));
|
|
g_string_append (data, " ");
|
|
string_append_double (data, graphene_vec3_get_y (&value));
|
|
g_string_append (data, " ");
|
|
string_append_double (data, graphene_vec3_get_z (&value));
|
|
}
|
|
break;
|
|
|
|
case GSK_GL_UNIFORM_TYPE_VEC4:
|
|
{
|
|
graphene_vec4_t value;
|
|
gsk_gl_shader_get_arg_vec4 (shader, args, i,
|
|
&value);
|
|
string_append_double (data, graphene_vec4_get_x (&value));
|
|
g_string_append (data, " ");
|
|
string_append_double (data, graphene_vec4_get_y (&value));
|
|
g_string_append (data, " ");
|
|
string_append_double (data, graphene_vec4_get_z (&value));
|
|
g_string_append (data, " ");
|
|
string_append_double (data, graphene_vec4_get_w (&value));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
_indent (p);
|
|
g_string_append_printf (p->str, "args: %s;\n", data->str);
|
|
g_string_free (data, TRUE);
|
|
}
|
|
|
|
for (guint i = 0; i < gsk_gl_shader_node_get_n_children (node); i ++)
|
|
{
|
|
GskRenderNode *child = gsk_gl_shader_node_get_child (node, i);
|
|
char *name;
|
|
|
|
name = g_strdup_printf ("child%d", i + 1);
|
|
append_node_param (p, name, child);
|
|
g_free (name);
|
|
}
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_REPEAT_NODE:
|
|
{
|
|
GskRenderNode *child = gsk_repeat_node_get_child (node);
|
|
const graphene_rect_t *child_bounds = gsk_repeat_node_get_child_bounds (node);
|
|
|
|
start_node (p, "repeat", node_name);
|
|
|
|
if (!graphene_rect_equal (&node->bounds, &child->bounds))
|
|
append_rect_param (p, "bounds", &node->bounds);
|
|
if (!graphene_rect_equal (child_bounds, &child->bounds))
|
|
append_rect_param (p, "child-bounds", child_bounds);
|
|
append_node_param (p, "child", gsk_repeat_node_get_child (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_BLEND_NODE:
|
|
{
|
|
GskBlendMode mode = gsk_blend_node_get_blend_mode (node);
|
|
|
|
start_node (p, "blend", node_name);
|
|
|
|
if (mode != GSK_BLEND_MODE_DEFAULT)
|
|
{
|
|
_indent (p);
|
|
g_string_append_printf (p->str, "mode: %s;\n", get_blend_mode_name (mode));
|
|
}
|
|
append_node_param (p, "bottom", gsk_blend_node_get_bottom_child (node));
|
|
append_node_param (p, "top", gsk_blend_node_get_top_child (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_MASK_NODE:
|
|
{
|
|
GskMaskMode mode = gsk_mask_node_get_mask_mode (node);
|
|
|
|
start_node (p, "mask", node_name);
|
|
|
|
if (mode != GSK_MASK_MODE_ALPHA)
|
|
{
|
|
_indent (p);
|
|
g_string_append_printf (p->str, "mode: %s;\n", get_mask_mode_name (mode));
|
|
}
|
|
append_node_param (p, "source", gsk_mask_node_get_source (node));
|
|
append_node_param (p, "mask", gsk_mask_node_get_mask (node));
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_NOT_A_RENDER_NODE:
|
|
g_assert_not_reached ();
|
|
break;
|
|
|
|
case GSK_CAIRO_NODE:
|
|
{
|
|
cairo_surface_t *surface = gsk_cairo_node_get_surface (node);
|
|
GByteArray *array;
|
|
|
|
start_node (p, "cairo", node_name);
|
|
append_rect_param (p, "bounds", &node->bounds);
|
|
|
|
if (surface != NULL)
|
|
{
|
|
array = g_byte_array_new ();
|
|
cairo_surface_write_to_png_stream (surface, cairo_write_array, array);
|
|
|
|
_indent (p);
|
|
g_string_append (p->str, "pixels: url(\"data:image/png;base64,");
|
|
b64 = base64_encode_with_linebreaks (array->data, array->len);
|
|
append_escaping_newlines (p->str, b64);
|
|
g_free (b64);
|
|
g_string_append (p->str, "\");\n");
|
|
|
|
g_byte_array_free (array, TRUE);
|
|
|
|
#ifdef CAIRO_HAS_SCRIPT_SURFACE
|
|
if (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_RECORDING)
|
|
{
|
|
static const cairo_user_data_key_t cairo_is_stupid_key;
|
|
cairo_device_t *script;
|
|
|
|
array = g_byte_array_new ();
|
|
script = cairo_script_create_for_stream (cairo_write_array, array);
|
|
|
|
if (cairo_script_from_recording_surface (script, surface) == CAIRO_STATUS_SUCCESS)
|
|
{
|
|
_indent (p);
|
|
g_string_append (p->str, "script: url(\"data:;base64,");
|
|
b64 = base64_encode_with_linebreaks (array->data, array->len);
|
|
append_escaping_newlines (p->str, b64);
|
|
g_free (b64);
|
|
g_string_append (p->str, "\");\n");
|
|
}
|
|
|
|
/* because Cairo is stupid and writes to the device after we finished it,
|
|
* we can't just
|
|
g_byte_array_free (array, TRUE);
|
|
* but have to
|
|
*/
|
|
g_byte_array_set_size (array, 0);
|
|
cairo_device_set_user_data (script, &cairo_is_stupid_key, array, cairo_destroy_array);
|
|
cairo_device_destroy (script);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
end_node (p);
|
|
}
|
|
break;
|
|
|
|
case GSK_SUBSURFACE_NODE:
|
|
{
|
|
start_node (p, "subsurface", node_name);
|
|
|
|
append_node_param (p, "child", gsk_subsurface_node_get_child (node));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
g_error ("Unhandled node: %s", g_type_name_from_instance ((GTypeInstance *) node));
|
|
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;
|
|
GBytes *res;
|
|
|
|
printer_init (&p, node);
|
|
|
|
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);
|
|
}
|
|
|
|
res = g_string_free_to_bytes (g_steal_pointer (&p.str));
|
|
|
|
printer_clear (&p);
|
|
|
|
return res;
|
|
}
|