/*
* Copyright © 2025 Red Hat , Inc
*
* SPDX - License - Identifier : LGPL - 2 . 1 - or - later
*
* 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 : Matthias Clasen < mclasen @ redhat . com >
*/
#include "config.h"
#include "gtksvgprivate.h"
#include "gtksvgparserprivate.h"
#include "gtkenums.h"
#include "gtktypebuiltins.h"
#include "gtk/css/gtkcssparserprivate.h"
#include "gtk/css/gtkcssdataurlprivate.h"
#include "gtk/gtkcssselectorprivate.h"
#include "gtk/gtkcssmediaqueryprivate.h"
#include <glib/gstdio.h>
#include "gtksvgenumtypes.h"
#include "gtksvgutilsprivate.h"
#include "gtksvgstringutilsprivate.h"
#include "gtksvgvalueprivate.h"
#include "gtksvgkeywordprivate.h"
#include "gtksvgnumberprivate.h"
#include "gtksvgnumbersprivate.h"
#include "gtksvgstringprivate.h"
#include "gtksvgstringlistprivate.h"
#include "gtksvgenumprivate.h"
#include "gtksvgfilterrefprivate.h"
#include "gtksvgtransformprivate.h"
#include "gtksvgcolorprivate.h"
#include "gtksvgpaintprivate.h"
#include "gtksvgfilterfunctionsprivate.h"
#include "gtksvgdasharrayprivate.h"
#include "gtksvgpathprivate.h"
#include "gtksvgpathdataprivate.h"
#include "gtksvgclipprivate.h"
#include "gtksvgmaskprivate.h"
#include "gtksvgviewboxprivate.h"
#include "gtksvgcontentfitprivate.h"
#include "gtksvgorientprivate.h"
#include "gtksvglanguageprivate.h"
#include "gtksvghrefprivate.h"
#include "gtksvgtextdecorationprivate.h"
#include "gtksvglocationprivate.h"
#include "gtksvgerrorprivate.h"
#include "gtksvgelementtypeprivate.h"
#include "gtksvgpropertyprivate.h"
#include "gtksvgfiltertypeprivate.h"
#include "gtksvgfilterprivate.h"
#include "gtksvgcolorstopprivate.h"
#include "gtksvgelementinternal.h"
#include "gtksvganimationprivate.h"
#include "gtksvgtimespecprivate.h"
#include "gtksvggpaprivate.h"
#include "gtksvgmediaqueryprivate.h"
#include "gtkmain.h"
#include <stdint.h>
#ifdef HAVE_PANGOFT
#include <pango/pangofc-fontmap.h>
#endif
/* The parser creates the shape tree. We maintain a current shape,
* and a current animation . Some things are done in a post - processing
* step : finding the shape that an animation belongs to , resolving
* other kinds of shape references , determining the proper order
* for computing updated values .
*
* Note that we treat images , text , markers , gradients , filters as
* shapes , but not color stops and filter primitives . SvgAnimations
* are their own thing too .
*
* So each shape can have multiple
* - child shapes
* - animations
* - color stops
* - filter primitives
*/
typedef struct
{
GtkSvg *svg;
SvgElement *current_shape;
GSList *shape_stack;
GHashTable *shapes;
GHashTable *animations;
SvgAnimation *current_animation;
GPtrArray *pending_animations;
GHashTable *pending_refs;
struct {
const GSList *to;
GtkSvgLocation start;
GtkSvgError code;
char *reason;
gboolean skip_over_target;
} skip;
struct {
GtkSvgLocation start;
gboolean collect;
GString *text;
} text;
uint64_t num_loaded_elements;
gboolean load_user_style;
PangoLanguage *lang;
char *media;
} ParserData;
/* {{{ Errors */
G_GNUC_PRINTF (6 , 7 )
static void
gtk_svg_skipped_element (GtkSvg *self,
const char *parent_element,
const GtkSvgLocation *start,
const GtkSvgLocation *end,
GtkSvgError code,
const char *format,
...)
{
GError *error;
va_list args;
va_start (args, format);
error = g_error_new_valist (GTK_SVG_ERROR, code, format, args);
va_end (args);
gtk_svg_error_set_input (error, "svg" );
gtk_svg_error_set_element (error, parent_element);
gtk_svg_error_set_location (error, start, end);
gtk_svg_emit_error (self, error);
g_clear_error (&error);
}
static void
gtk_svg_markup_error (GtkSvg *self,
GMarkupParseContext *context,
const GError *markup_error)
{
GError *error;
GtkSvgLocation start, end;
error = g_error_new_literal (GTK_SVG_ERROR,
GTK_SVG_ERROR_INVALID_SYNTAX,
markup_error->message);
gtk_svg_error_set_input (error, "svg" );
gtk_svg_error_set_element (error, g_markup_parse_context_get_element (context));
gtk_svg_location_init_tag_range (&start, &end, context);
gtk_svg_error_set_location (error, &start, &end);
gtk_svg_emit_error (self, error);
g_clear_error (&error);
}
static void
gtk_svg_missing_attribute (GtkSvg *self,
GMarkupParseContext *context,
const char *attr_name)
{
GError *error;
GtkSvgLocation start, end;
error = g_error_new (GTK_SVG_ERROR,
GTK_SVG_ERROR_MISSING_ATTRIBUTE,
"Missing attribute: %s" , attr_name);
gtk_svg_error_set_input (error, "svg" );
gtk_svg_error_set_element (error, g_markup_parse_context_get_element (context));
gtk_svg_error_set_attribute (error, attr_name);
gtk_svg_location_init_tag_range (&start, &end, context);
gtk_svg_error_set_location (error, &start, &end);
gtk_svg_emit_error (self, error);
g_error_free (error);
}
static void
gtk_svg_check_unhandled_attributes (GtkSvg *self,
GMarkupParseContext *context,
const char **attr_names,
uint64_t handled)
{
unsigned int n = g_strv_length ((char **) attr_names);
for (unsigned int i = 0 ; i < n; i++)
{
if ((handled & BIT (i)) == 0 )
gtk_svg_invalid_attribute (self, context, attr_names, attr_names[i],
"Unhandled attribute: %s" , attr_names[i]);
}
}
/* }}} */
/* {{{ Helpers */
static gboolean
has_ancestor (GMarkupParseContext *context,
const char *elt)
{
const GSList *list;
for (list = g_markup_parse_context_get_element_stack (context);
list != NULL;
list = list->next)
{
const char *name = list->data;
if (strcmp (name, elt) == 0 )
return TRUE ;
}
return FALSE ;
}
static gboolean
check_ancestors (GMarkupParseContext *context,
...)
{
const GSList *list;
va_list args;
const char *name;
list = g_markup_parse_context_get_element_stack (context);
va_start (args, context);
while ((name = va_arg (args, const char *)) != NULL)
{
list = list->next;
if (list == NULL)
return FALSE ;
if (strcmp (name, (const char *) list->data) != 0 )
return FALSE ;
}
va_end (args);
return TRUE ;
}
static void
markup_filter_attributes (const char *element_name,
const char **attr_names,
const char **attr_values,
uint64_t *handled,
const char *name,
...)
{
va_list ap;
va_start (ap, name);
while (name)
{
const char **ptr;
ptr = va_arg (ap, const char **);
if (ptr)
*ptr = NULL;
for (unsigned int i = 0 ; attr_names[i]; i++)
{
if (strcmp (attr_names[i], name) == 0 )
{
if (ptr)
*ptr = attr_values[i];
*handled |= G_GUINT64_CONSTANT(1 ) << i;
break ;
}
}
name = va_arg (ap, const char *);
}
va_end (ap);
}
static inline gboolean
g_strv_has (GStrv strv,
const char *s)
{
return g_strv_contains ((const char * const *) strv, s);
}
/* }}} */
/* {{{ SvgAnimation attributes */
typedef struct
{
GtkSvg *svg;
GArray *array;
GError *error;
SvgElement *default_event_target;
} Specs;
static gboolean
time_spec_parse_one (GtkCssParser *parser,
gpointer user_data)
{
Specs *specs = user_data;
TimeSpec *spec;
g_array_set_size (specs->array, specs->array->len + 1 );
spec = &g_array_index (specs->array, TimeSpec, specs->array->len - 1 );
return time_spec_parse (parser, specs->svg, specs->default_event_target, spec, &specs->error);
}
static gboolean
parse_base_animation_attrs (SvgAnimation *a,
const char *element_name,
const char **attr_names,
const char **attr_values,
uint64_t *handled,
ParserData *data,
GMarkupParseContext *context)
{
const char *id_attr = NULL;
const char *href_attr = NULL;
const char *xlink_href_attr = NULL;
const char *begin_attr = NULL;
const char *end_attr = NULL;
const char *dur_attr = NULL;
const char *repeat_count_attr = NULL;
const char *repeat_dur_attr = NULL;
const char *fill_attr = NULL;
const char *restart_attr = NULL;
const char *attr_name_attr = NULL;
const char *ignored = NULL;
SvgProperty attr;
SvgElement *current_shape = NULL;
SvgElementType current_type;
SvgFilterType filter_type = 0 ;
const char *href_attr_name = "href" ;
markup_filter_attributes (element_name,
attr_names, attr_values,
handled,
"id" , &id_attr,
"href" , &href_attr,
"xlink:href" , &xlink_href_attr,
"begin" , &begin_attr,
"end" , &end_attr,
"dur" , &dur_attr,
"repeatCount" , &repeat_count_attr,
"repeatDur" , &repeat_dur_attr,
"fill" , &fill_attr,
"restart" , &restart_attr,
"attributeName" , &attr_name_attr,
"gpa:status" , &ignored,
NULL);
a->id = g_strdup (id_attr);
if (xlink_href_attr && !href_attr)
{
href_attr = xlink_href_attr;
href_attr_name = "xlink:href" ;
}
if (href_attr)
{
if (href_attr[0 ] != '#' )
gtk_svg_invalid_attribute (data->svg, context, attr_names, href_attr_name,
"Missing '#' in 'href'" );
else
a->href = g_strdup (href_attr + 1 );
}
if (a->href)
current_shape = g_hash_table_lookup (data->shapes, a->href);
if (!current_shape)
current_shape = data->current_shape;
if (current_shape)
current_type = svg_element_get_type (current_shape);
else
current_type = 0 ;
if (begin_attr)
{
GtkCssParser *parser = parser_new_for_string (begin_attr);
Specs specs = { 0 , };
specs.svg = data->svg;
specs.default_event_target = current_shape;
specs.array = g_array_new (FALSE , TRUE , sizeof (TimeSpec));
g_array_set_clear_func (specs.array, (GDestroyNotify) time_spec_clear);
if (parser_parse_list (parser, time_spec_parse_one, &specs))
{
for (unsigned int i = 0 ; i < specs.array->len; i++)
{
TimeSpec *spec = &g_array_index (specs.array, TimeSpec, i);
TimeSpec *begin;
a->has_begin = 1 ;
begin = svg_animation_add_begin (a, timeline_get_time_spec (data->svg->timeline, spec));
time_spec_add_animation (begin, a);
}
}
else
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "begin" , NULL);
}
gtk_css_parser_unref (parser);
g_array_unref (specs.array);
if (specs.error)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "begin" , "%s" , specs.error->message);
g_error_free (specs.error);
}
}
else
{
TimeSpec *begin;
begin = svg_animation_add_begin (a, timeline_get_start_of_time (data->svg->timeline));
time_spec_add_animation (begin, a);
}
if (end_attr)
{
GtkCssParser *parser = parser_new_for_string (end_attr);
Specs specs = { 0 , };
specs.svg = data->svg;
specs.default_event_target = current_shape;
specs.array = g_array_new (FALSE , TRUE , sizeof (TimeSpec));
g_array_set_clear_func (specs.array, (GDestroyNotify) time_spec_clear);
if (parser_parse_list (parser, time_spec_parse_one, &specs))
{
for (unsigned int i = 0 ; i < specs.array->len; i++)
{
TimeSpec *spec = &g_array_index (specs.array, TimeSpec, i);
TimeSpec *end;
a->has_end = 1 ;
end = svg_animation_add_end (a, timeline_get_time_spec (data->svg->timeline, spec));
time_spec_add_animation (end, a);
}
}
else
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "end" , NULL);
}
gtk_css_parser_unref (parser);
g_array_unref (specs.array);
if (specs.error)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "end" , "%s" , specs.error->message);
g_error_free (specs.error);
}
}
else
{
TimeSpec *end;
end = svg_animation_add_end (a, timeline_get_end_of_time (data->svg->timeline));
time_spec_add_animation (end, a);
}
a->simple_duration = INDEFINITE;
if (dur_attr)
{
a->has_simple_duration = 1 ;
if (!parse_duration (dur_attr, TRUE , &a->simple_duration))
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "dur" , NULL);
a->has_simple_duration = 0 ;
}
}
a->repeat_count = REPEAT_FOREVER;
if (repeat_count_attr)
{
a->has_repeat_count = 1 ;
if (!parse_number_or_named (repeat_count_attr, 0 , DBL_MAX, "indefinite" , REPEAT_FOREVER, &a->repeat_count) || a->repeat_count == 0 )
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "repeatCount" , NULL);
a->has_repeat_count = 0 ;
}
}
a->repeat_duration = INDEFINITE;
if (repeat_dur_attr)
{
a->has_repeat_duration = 1 ;
if (!parse_duration (repeat_dur_attr, TRUE , &a->repeat_duration))
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "repeatDur" , NULL);
a->has_repeat_duration = 0 ;
}
}
if (!a->has_repeat_duration && !a->has_repeat_count)
{
a->repeat_count = 1 ;
a->repeat_duration = a->simple_duration;
}
else if (!a->has_repeat_count && !a->has_simple_duration)
{
a->repeat_count = 1 ;
a->simple_duration = a->repeat_duration;
}
else if (a->has_repeat_count && a->has_simple_duration && !a->has_repeat_duration)
{
if (a->repeat_count == REPEAT_FOREVER)
a->repeat_duration = INDEFINITE;
else
a->repeat_duration = a->simple_duration * a->repeat_count;
}
else if (a->has_repeat_duration && a->has_simple_duration && !a->has_repeat_count)
{
if (a->repeat_duration == INDEFINITE)
a->repeat_count = REPEAT_FOREVER;
else
a->repeat_count = a->repeat_duration / a->simple_duration;
}
else if (a->has_repeat_duration && a->has_repeat_count && !a->has_simple_duration)
{
if (a->repeat_duration == INDEFINITE || a->repeat_count == REPEAT_FOREVER)
a->simple_duration = INDEFINITE;
else
a->simple_duration = a->repeat_duration / a->repeat_count;
}
a->fill = ANIMATION_FILL_REMOVE;
if (fill_attr)
{
unsigned int value;
if (!parse_enum (fill_attr,
(const char *[]) { "freeze" , "remove" }, 2 ,
&value))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "fill" , NULL);
else
a->fill = (AnimationFill) value;
}
a->restart = ANIMATION_RESTART_ALWAYS;
if (restart_attr)
{
unsigned int value;
if (!parse_enum (restart_attr,
(const char *[]) { "always" , "whenNotActive" , "never" }, 3 ,
&value))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "restart" , NULL);
else
a->restart = (AnimationRestart) value;
}
if (attr_name_attr && strcmp (attr_name_attr, "xlink:href" ) == 0 )
attr_name_attr = "href" ;
if (current_shape != NULL &&
current_type == SVG_ELEMENT_FILTER &&
current_shape->filters->len > 0 )
{
SvgFilter *f;
f = g_ptr_array_index (current_shape->filters,
current_shape->filters->len - 1 );
filter_type = svg_filter_get_type (f);
}
attr = a->attr;
if (a->type == ANIMATION_TYPE_MOTION)
{
if (attr_name_attr)
gtk_svg_invalid_attribute (data->svg, context, attr_names, "attributeName" ,
"Not allowed on <animateMotion>" );
}
else if (a->type == ANIMATION_TYPE_TRANSFORM)
{
if (current_shape != NULL)
{
const char *expected;
/* FIXME: if href is set, current_shape might be the wrong shape */
expected = svg_property_get_presentation (SVG_PROPERTY_TRANSFORM, current_type);
if (expected == NULL)
gtk_svg_invalid_attribute (data->svg, context, attr_names, "attributeName" ,
"No transform attribute" );
else if (attr_name_attr && strcmp (attr_name_attr, expected) != 0 )
gtk_svg_invalid_attribute (data->svg, context, attr_names, "attributeName" ,
"Value must be '%s'" , expected);
}
a->attr = SVG_PROPERTY_TRANSFORM;
}
else if (!attr_name_attr)
{
gtk_svg_missing_attribute (data->svg, context, "attributeName" );
return FALSE ;
}
/* FIXME: if href is set, current_shape might be the wrong shape */
else if (current_shape != NULL &&
((current_type == SVG_ELEMENT_FILTER &&
svg_property_lookup_for_filter (attr_name_attr, current_type, filter_type, &attr)) ||
(current_type != SVG_ELEMENT_FILTER &&
svg_property_lookup (attr_name_attr, current_type, &attr))))
{
a->attr = attr;
/* FIXME: if href is set, current_shape might be the wrong shape */
if (has_ancestor (context, "stop" ))
a->idx = current_shape->color_stops->len;
else if (has_ancestor (context, "filter" ))
a->idx = current_shape->filters->len;
}
else
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "attributeName" ,
"Can't animate '%s'" , attr_name_attr);
return FALSE ;
}
return TRUE ;
}
static GArray *
create_default_times (CalcMode calc_mode,
unsigned int n_values)
{
GArray *times;
double n;
if (calc_mode == CALC_MODE_DISCRETE)
n = n_values;
else
n = n_values - 1 ;
times = g_array_new (FALSE , FALSE , sizeof (double ));
for (unsigned int i = 0 ; i < n_values; i++)
{
double d = i / (double ) n;
g_array_append_val (times, d);
}
return times;
}
static gboolean
parse_spline (GtkCssParser *parser,
gpointer user_data)
{
GArray *array = user_data;
for (unsigned int i = 0 ; i < 4 ; i++)
{
double v;
gtk_css_parser_skip_whitespace (parser);
if (!gtk_css_parser_consume_number (parser, &v))
return FALSE ;
if (v < 0 || v > 1 )
return FALSE ;
g_array_append_val (array, v);
skip_whitespace_and_optional_comma (parser);
}
return TRUE ;
}
static gboolean
parse_value_animation_attrs (SvgAnimation *a,
const char *element_name,
const char **attr_names,
const char **attr_values,
uint64_t *handled,
ParserData *data,
GMarkupParseContext *context)
{
const char *type_attr = NULL;
const char *calc_mode_attr = NULL;
const char *values_attr = NULL;
const char *from_attr = NULL;
const char *to_attr = NULL;
const char *by_attr = NULL;
const char *key_times_attr = NULL;
const char *splines_attr = NULL;
const char *additive_attr = NULL;
const char *accumulate_attr = NULL;
const char *color_interpolation_attr = NULL;
TransformType transform_type = TRANSFORM_NONE;
GArray *times = NULL;
GArray *points = NULL;
GArray *params = NULL;
GPtrArray *values = NULL;
markup_filter_attributes (element_name,
attr_names, attr_values,
handled,
"type" , &type_attr,
"calcMode" , &calc_mode_attr,
"values" , &values_attr,
"from" , &from_attr,
"to" , &to_attr,
"by" , &by_attr,
"keyTimes" , &key_times_attr,
"keySplines" , &splines_attr,
"additive" , &additive_attr,
"accumulate" , &accumulate_attr,
"color-interpolation" , &color_interpolation_attr,
NULL);
if (a->type == ANIMATION_TYPE_MOTION)
transform_type = TRANSFORM_TRANSLATE;
if (a->type == ANIMATION_TYPE_TRANSFORM)
{
if (type_attr)
{
unsigned int value;
if (!parse_enum (type_attr,
(const char *[]) { "translate" , "scale" , "rotate" ,
"skewX" , "skewY" }, 5 ,
&value))
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "type" , NULL);
return FALSE ;
}
else
transform_type = (TransformType) (value + 1 );
}
else
{
gtk_svg_missing_attribute (data->svg, context, "type" );
return FALSE ;
}
}
else if (type_attr)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "type" , NULL);
}
if (calc_mode_attr)
{
unsigned int value;
if (!parse_enum (calc_mode_attr,
(const char *[]) { "discrete" , "linear" , "paced" , "spline" }, 4 ,
&value))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "calcMode" , NULL);
else
a->calc_mode = (CalcMode) value;
}
if (additive_attr)
{
unsigned int value;
if (!parse_enum (additive_attr,
(const char *[]) { "replace" , "sum" , }, 2 ,
&value))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "additive" , NULL);
else
a->additive = (AnimationAdditive) value;
}
if (accumulate_attr)
{
unsigned int value;
if (!parse_enum (accumulate_attr,
(const char *[]) { "none" , "sum" , }, 2 ,
&value))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "accumulate" , NULL);
else
a->accumulate = (AnimationAccumulate) value;
}
if (color_interpolation_attr)
{
SvgValue *v;
GError *error = NULL;
v = svg_property_parse (SVG_PROPERTY_COLOR_INTERPOLATION, color_interpolation_attr, &error);
if (!v)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "color-interpolation" , "%s" , error->message);
g_error_free (error);
}
else
{
a->color_interpolation = svg_enum_get (v);
svg_value_unref (v);
}
}
if (values_attr)
{
values = svg_property_parse_values (a->attr, transform_type, values_attr);
if (!values || values->len < 2 )
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "values" , NULL);
g_clear_pointer (&values, g_ptr_array_unref);
return FALSE ;
}
}
else if (from_attr && to_attr)
{
GPtrArray *tovals;
values = svg_property_parse_values (a->attr, transform_type, from_attr);
if (!values || values->len != 1 )
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "from" , NULL);
g_clear_pointer (&values, g_ptr_array_unref);
return FALSE ;
}
tovals = svg_property_parse_values (a->attr, transform_type, to_attr);
if (!tovals || tovals->len != 1 )
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "to" , NULL);
g_clear_pointer (&values, g_ptr_array_unref);
g_clear_pointer (&tovals, g_ptr_array_unref);
return FALSE ;
}
g_ptr_array_extend_and_steal (values, tovals);
}
else if (from_attr && by_attr)
{
GPtrArray *byvals;
SvgValue *from;
SvgValue *by;
SvgValue *to;
SvgComputeContext ctx = { 0 , };
values = svg_property_parse_values (a->attr, transform_type, from_attr);
if (!values || values->len != 1 )
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "from" , NULL);
g_clear_pointer (&values, g_ptr_array_unref);
return FALSE ;
}
byvals = svg_property_parse_values (a->attr, transform_type, by_attr);
if (!byvals || byvals->len != 1 )
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "by" , NULL);
g_clear_pointer (&values, g_ptr_array_unref);
g_clear_pointer (&byvals, g_ptr_array_unref);
return FALSE ;
}
from = g_ptr_array_index (values, 0 );
by = g_ptr_array_index (byvals, 0 );
ctx.interpolation = GDK_COLOR_STATE_SRGB; /* Nothing else is used */
to = svg_value_accumulate (by, from, &ctx, 1 );
g_ptr_array_add (values, to);
g_ptr_array_unref (byvals);
}
else if (to_attr)
{
values = svg_property_parse_values (a->attr, transform_type, to_attr);
if (!values || values->len != 1 )
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "to" , NULL);
g_clear_pointer (&values, g_ptr_array_unref);
return FALSE ;
}
/* We use a special keyword for this */
g_ptr_array_insert (values, 0 , svg_current_new ());
}
else if (by_attr)
{
SvgValue *from;
SvgValue *by;
values = svg_property_parse_values (a->attr, transform_type, by_attr);
if (!values || values->len != 1 )
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "by" , NULL);
g_clear_pointer (&values, g_ptr_array_unref);
return FALSE ;
}
by = g_ptr_array_index (values, 0 );
if (svg_value_is_number (by))
from = svg_number_new_full (svg_number_get_unit (by), 0 );
else if (svg_value_is_transform (by))
from = svg_transform_new_none ();
else if (svg_value_is_paint (by) &&
svg_paint_get_kind (by) == PAINT_COLOR)
from = svg_paint_new_transparent ();
else
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "by" ,
"Don't know how to handle this value" );
g_ptr_array_unref (values);
return FALSE ;
}
g_ptr_array_insert (values, 0 , from);
a->additive = ANIMATION_ADDITIVE_SUM;
}
if (a->type == ANIMATION_TYPE_MOTION)
{
if (values != NULL)
{
GskPathBuilder *builder = gsk_path_builder_new ();
double length;
double vals[6 ];
points = g_array_new (FALSE , FALSE , sizeof (double ));
for (unsigned int i = 0 ; i < values->len; i++)
{
SvgValue *tf = g_ptr_array_index (values, i);
if (svg_value_is_current (tf) ||
svg_transform_get_length (tf) != 1 ||
svg_transform_get_primitive (tf, 0 , vals) != TRANSFORM_TRANSLATE)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, NULL,
"Transform is not a translation" );
g_ptr_array_unref (values);
g_array_unref (points);
gsk_path_builder_unref (builder);
return FALSE ;
}
if (i == 0 )
{
length = 0 ;
gsk_path_builder_move_to (builder, vals[0 ], vals[1 ]);
}
else
{
length += graphene_point_distance (gsk_path_builder_get_current_point (builder),
&GRAPHENE_POINT_INIT (vals[0 ], vals[1 ]), NULL, NULL);
gsk_path_builder_line_to (builder, vals[0 ], vals[1 ]);
}
g_array_append_val (points, length);
}
for (unsigned int i = 1 ; i < points->len; i++)
g_array_index (points, double , i) /= length;
a->motion.path = gsk_path_builder_free_to_path (builder);
g_clear_pointer (&values, g_ptr_array_unref);
}
}
else
{
if (values == NULL)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, NULL,
"Either 'values' or 'from', 'to' or "
"'by' must be given" );
return FALSE ;
}
}
if (a->calc_mode == CALC_MODE_PACED)
{
if (values && values->len > 2 )
{
double length, distance;
const SvgValue *v0, *v1;
/* use value distances to compute evenly paced times */
times = g_array_new (FALSE , FALSE , sizeof (double ));
length = distance = 0 ;
g_array_append_val (times, distance);
for (unsigned int i = 1 ; i < values->len; i++)
{
v0 = g_ptr_array_index (values, i - 1 );
v1 = g_ptr_array_index (values, i);
distance = svg_value_distance (v0, v1);
g_array_append_val (times, distance);
length += distance;
}
for (unsigned int i = 1 ; i < times->len; i++)
{
g_array_index (times, double , i) /= length;
}
for (unsigned int i = 1 ; i < times->len; i++)
{
g_array_index (times, double , i) += g_array_index (times, double , i - 1 );
}
}
}
else if (key_times_attr)
{
times = parse_number_list (key_times_attr, 0 , 1 );
if (!times)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "keyTimes" , NULL);
g_clear_pointer (&values, g_ptr_array_unref);
g_clear_pointer (&points, g_array_unref);
return FALSE ;
}
}
if (times != NULL)
{
if (values && times->len != values->len)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, NULL,
"'values' and 'keyTimes' must have "
"the same number of items" );
g_clear_pointer (&values, g_ptr_array_unref);
g_clear_pointer (×, g_array_unref);
g_clear_pointer (&points, g_array_unref);
return FALSE ;
}
if (times->len == 0 )
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "keyTimes" ,
"No values" );
g_clear_pointer (&values, g_ptr_array_unref);
g_clear_pointer (×, g_array_unref);
g_clear_pointer (&points, g_array_unref);
return FALSE ;
}
if (g_array_index (times, double , 0 ) != 0 )
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "keyTimes" ,
"First value must be 0" );
g_clear_pointer (&values, g_ptr_array_unref);
g_clear_pointer (×, g_array_unref);
g_clear_pointer (&points, g_array_unref);
return FALSE ;
}
if (a->calc_mode != CALC_MODE_DISCRETE && g_array_index (times, double , times->len - 1 ) != 1 )
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "keyTimes" ,
"Last value must be 1" );
g_clear_pointer (&values, g_ptr_array_unref);
g_clear_pointer (×, g_array_unref);
g_clear_pointer (&points, g_array_unref);
return FALSE ;
}
for (unsigned int i = 1 ; i < times->len; i++)
{
if (g_array_index (times, double , i) < g_array_index (times, double , i - 1 ))
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "keyTimes" ,
"Values must be increasing" );
g_clear_pointer (&values, g_ptr_array_unref);
g_clear_pointer (×, g_array_unref);
g_clear_pointer (&points, g_array_unref);
return FALSE ;
}
}
}
if (a->calc_mode != CALC_MODE_PACED)
{
params = NULL;
if (splines_attr)
{
GtkCssParser *parser = parser_new_for_string (splines_attr);
unsigned int n;
params = g_array_new (FALSE , TRUE , sizeof (double ));
if (!parser_parse_list (parser, parse_spline, params))
{
gtk_css_parser_unref (parser);
gtk_svg_invalid_attribute (data->svg, context, attr_names, "keySplines" , NULL);
g_clear_pointer (&values, g_ptr_array_unref);
g_clear_pointer (×, g_array_unref);
g_clear_pointer (¶ms, g_array_unref);
g_clear_pointer (&points, g_array_unref);
return FALSE ;
}
gtk_css_parser_unref (parser);
n = params->len / 4 ;
if (times == NULL)
times = create_default_times (a->calc_mode, n + 1 );
if (n != times->len - 1 )
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "keySplines" ,
"Wrong number of values" );
g_clear_pointer (&values, g_ptr_array_unref);
g_clear_pointer (×, g_array_unref);
g_clear_pointer (¶ms, g_array_unref);
g_clear_pointer (&points, g_array_unref);
return FALSE ;
}
}
}
if (times == NULL)
{
unsigned int n;
if (values)
n = values->len;
else if (points)
n = points->len;
else
n = 2 ;
times = create_default_times (a->calc_mode, n);
}
g_assert (times != NULL);
if (times->len < 2 ||
(values && times->len != values->len) ||
(params && 4 * (times->len - 1 ) != params->len) ||
(points && times->len != points->len))
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, NULL,
"Invalid value attributes" );
g_clear_pointer (&values, g_ptr_array_unref);
g_clear_pointer (×, g_array_unref);
g_clear_pointer (¶ms, g_array_unref);
g_clear_pointer (&points, g_array_unref);
return FALSE ;
}
svg_animation_fill_from_values (a,
(double *) times->data,
times->len,
values ? (SvgValue **) values->pdata : NULL,
params ? (double *) params->data : NULL,
points ? (double *) points->data : NULL);
g_clear_pointer (&values, g_ptr_array_unref);
g_clear_pointer (×, g_array_unref);
g_clear_pointer (&points, g_array_unref);
g_clear_pointer (¶ms, g_array_unref);
return TRUE ;
}
static gboolean
parse_motion_animation_attrs (SvgAnimation *a,
const char *element_name,
const char **attr_names,
const char **attr_values,
uint64_t *handled,
ParserData *data,
GMarkupParseContext *context)
{
const char *path_attr = NULL;
const char *rotate_attr = NULL;
const char *key_points_attr = NULL;
markup_filter_attributes (element_name,
attr_names, attr_values,
handled,
"path" , &path_attr,
"rotate" , &rotate_attr,
"keyPoints" , &key_points_attr,
NULL);
if (path_attr)
{
SvgValue *value = NULL;
if (strcmp (path_attr, "none" ) == 0 )
value = svg_path_new_none ();
else
{
SvgPathData *path_data = NULL;
if (svg_path_data_parse_full (path_attr, &path_data))
value = svg_path_new_from_data (path_data);
else
gtk_svg_invalid_attribute (data->svg, context, attr_names, "path" , "Path data is invalid" );
}
if (value)
{
g_clear_pointer (&a->motion.path, gsk_path_unref);
a->motion.path = gsk_path_ref (svg_path_get_gsk (value));
svg_value_unref (value);
}
else
return FALSE ;
}
a->motion.rotate = ROTATE_FIXED;
a->motion.angle = 0 ;
if (rotate_attr)
{
GtkCssParser *parser = parser_new_for_string (rotate_attr);
SvgValue *value;
gtk_css_parser_skip_whitespace (parser);
value = svg_number_parse (parser, -DBL_MAX, DBL_MAX, 0 );
if (value)
{
a->motion.angle = svg_number_get (value, 100 );
a->motion.rotate = ROTATE_FIXED;
svg_value_unref (value);
}
else if (!parser_try_enum (parser,
(const char *[]) { "auto" , "auto-reverse" }, 2 ,
(unsigned int *) &a->motion.rotate))
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "rotate" , NULL);
}
gtk_css_parser_skip_whitespace (parser);
if (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "rotate" ,
"Junk at end of value" );
gtk_css_parser_unref (parser);
}
if (a->calc_mode != CALC_MODE_PACED)
{
if (key_points_attr)
{
GArray *points = parse_number_list (key_points_attr, 0 , 1 );
if (!points)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "keyPoints" , NULL);
return FALSE ;
}
if (points->len != a->n_frames)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "keyPoints" ,
"Wrong number of values" );
g_array_unref (points);
return FALSE ;
}
for (unsigned int i = 0 ; i < a->n_frames; i++)
a->frames[i].point = g_array_index (points, double , i);
g_array_unref (points);
}
}
if (a->calc_mode == CALC_MODE_PACED)
{
/* When paced, keyTimes, keyPoints and keySplines
* are all ignored , and we calculate times and
* points from the path to ensure an even pace
*/
g_clear_pointer (&a->frames, g_free);
a->n_frames = 0 ;
if (a->motion.path && !gsk_path_is_empty (a->motion.path))
{
svg_animation_motion_fill_from_path (a, a->motion.path);
}
else
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "calcMode" ,
"Paced animation without explicit path is not implemented" );
return FALSE ;
}
}
return TRUE ;
}
/* }}} */
/* {{{ gpa things */
static gboolean
strokewidth_parse (const char *value,
SvgValue **values)
{
GtkCssParser *parser = parser_new_for_string (value);
unsigned int i;
gboolean retval = TRUE ;
for (i = 0 ; i < 3 ; i++)
{
gtk_css_parser_skip_whitespace (parser);
values[i] = svg_number_parse (parser, -DBL_MAX, DBL_MAX, SVG_PARSE_NUMBER|SVG_PARSE_LENGTH|SVG_PARSE_PERCENTAGE);
if (!values[i])
retval = FALSE ;
}
if (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
retval = FALSE ;
gtk_css_parser_unref (parser);
return retval;
}
/* }}} */
/* {{{ Attributes */
static void
parse_shape_attrs (SvgElement *shape,
const char *element_name,
const char **attr_names,
const char **attr_values,
uint64_t *handled,
ParserData *data,
GMarkupParseContext *context)
{
for (unsigned int i = 0 ; attr_names[i]; i++)
{
SvgProperty attr;
if (*handled & BIT (i))
continue ;
if (svg_element_get_type (shape) == SVG_ELEMENT_SVG &&
(g_str_has_prefix (attr_names[i], "xmlns" ) ||
strcmp (attr_names[i], "version" ) == 0 ||
strcmp (attr_names[i], "baseProfile" ) == 0 ))
{
*handled |= BIT (i);
continue ;
}
if (strcmp (attr_names[i], "class" ) == 0 )
{
*handled |= BIT (i);
svg_element_parse_classes (shape, attr_values[i]);
}
else if (strcmp (attr_names[i], "style" ) == 0 )
{
GtkSvgLocation start, end;
gtk_svg_location_init_attr_range (&start, &end, context, i);
*handled |= BIT (i);
svg_element_set_inline_style (shape, attr_values[i], &start);
}
else if (strcmp (attr_names[i], "id" ) == 0 )
{
*handled |= BIT (i);
svg_element_set_id (shape, attr_values[i]);
}
else if (strcmp (attr_names[i], "tabindex" ) == 0 )
{
int tabindex;
char *end;
*handled |= BIT (i);
tabindex = g_ascii_strtoll (attr_values[i], &end, 10 );
if (end && *end != '\0' )
gtk_svg_invalid_attribute (data->svg, context, attr_names, attr_names[i], "Not an integer" );
else
svg_element_set_focusable (shape, tabindex >= 0 );
}
else if (strcmp (attr_names[i], "autofocus" ) == 0 )
{
*handled |= BIT (i);
if (strcmp (attr_values[i], "" ) == 0 || strcmp (attr_values[i], "autofocus" ) == 0 )
svg_element_set_autofocus (shape, TRUE );
else
gtk_svg_invalid_attribute (data->svg, context, attr_names, attr_names[i], NULL);
}
else if (svg_property_lookup (attr_names[i], svg_element_get_type (shape), &attr) &&
svg_property_has_presentation (attr))
{
if (svg_property_applies_to (attr, svg_element_get_type (shape)))
{
if (svg_element_is_specified (shape, attr) &&
svg_attr_is_deprecated (attr_names[i]))
{
/* ignore */
}
else
{
SvgValue *value;
GError *error = NULL;
value = svg_property_parse_and_validate (attr, attr_values[i], &error);
/* It is possible that a value is returned and error
* is still set , e . g . for ' d ' or ' points '
*/
if (error)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, attr_names[i], "%s" , error->message);
g_error_free (error);
}
else if (!value)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, attr_names[i], NULL);
}
if (value)
svg_element_take_specified_value (shape, attr, value);
}
}
else
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, attr_names[i], "Unknown attribute: %s" , attr_names[i]);
}
*handled |= BIT (i);
}
}
if (svg_property_applies_to (SVG_PROPERTY_FX, svg_element_get_type (shape)) &&
svg_property_applies_to (SVG_PROPERTY_FY, svg_element_get_type (shape)))
{
if (svg_element_is_specified (shape, SVG_PROPERTY_CX) &&
!svg_element_is_specified (shape, SVG_PROPERTY_FX))
svg_element_set_specified_value (shape, SVG_PROPERTY_FX, svg_element_get_specified_value (shape, SVG_PROPERTY_CX));
if (svg_element_is_specified (shape, SVG_PROPERTY_CY) &&
!svg_element_is_specified (shape, SVG_PROPERTY_FY))
svg_element_set_specified_value (shape, SVG_PROPERTY_FY, svg_element_get_specified_value (shape, SVG_PROPERTY_CY));
}
if (svg_element_get_autofocus (shape) && svg_element_get_focusable (shape))
{
if (data->svg->initial_focus == NULL)
data->svg->initial_focus = shape;
else
gtk_svg_invalid_attribute (data->svg, context, attr_names, "autofocus" , "Duplicate autofocus" );
}
}
static void
parse_svg_gpa_attrs (GtkSvg *svg,
const char *element_name,
const char **attr_names,
const char **attr_values,
uint64_t *handled,
ParserData *data,
GMarkupParseContext *context)
{
const char *state_names_attr = NULL;
const char *state_attr = NULL;
const char *version_attr = NULL;
const char *keywords_attr = NULL;
markup_filter_attributes (element_name,
attr_names, attr_values,
handled,
"gpa:state-names" , &state_names_attr,
"gpa:state" , &state_attr,
"gpa:version" , &version_attr,
"gpa:keywords" , &keywords_attr,
NULL);
if (state_names_attr)
{
GStrv strv = parse_strv (state_names_attr);
if (strv == NULL)
{
gtk_svg_invalid_attribute (svg, context, attr_names, "gpa:state-names" , NULL);
}
else
{
if (!gtk_svg_set_state_names (svg, (const char **) strv))
gtk_svg_invalid_attribute (svg, context, attr_names, "gpa:state-names" , NULL);
g_strfreev (strv);
}
}
if (state_attr)
{
double v;
unsigned int state;
if (parse_number (state_attr, 0 , 63 , &v))
svg->initial_state = (unsigned int ) v;
else if (find_named_state (svg, state_attr, &state))
svg->initial_state = state;
else
gtk_svg_invalid_attribute (svg, context, attr_names, "gpa:state" , NULL);
svg->state = svg->initial_state;
}
if (version_attr)
{
unsigned int version;
char *end;
version = (unsigned int ) g_ascii_strtoull (version_attr, &end, 10 );
if ((end && *end != '\0' ) || version != 1 )
gtk_svg_invalid_attribute (svg, context, attr_names, "gpa:version" ,
"Must be 1" );
else
svg->gpa_version = version;
}
if (keywords_attr)
g_set_str (&svg->keywords, keywords_attr);
}
static void
parse_shape_gpa_attrs (SvgElement *shape,
const char *element_name,
const char **attr_names,
const char **attr_values,
uint64_t *handled,
ParserData *data,
GMarkupParseContext *context)
{
const char *stroke_attr = NULL;
const char *fill_attr = NULL;
const char *strokewidth_attr = NULL;
const char *states_attr = NULL;
const char *transition_type_attr = NULL;
const char *transition_duration_attr = NULL;
const char *transition_delay_attr = NULL;
const char *transition_easing_attr = NULL;
const char *animation_type_attr = NULL;
const char *animation_direction_attr = NULL;
const char *animation_duration_attr = NULL;
const char *animation_repeat_attr = NULL;
const char *animation_segment_attr = NULL;
const char *animation_easing_attr = NULL;
const char *origin_attr = NULL;
const char *attach_to_attr = NULL;
const char *attach_pos_attr = NULL;
SvgValue *value;
uint64_t states;
double origin;
unsigned int transition_type;
int64_t transition_duration;
int64_t transition_delay;
unsigned int transition_easing;
unsigned int has_animation;
unsigned int animation_direction;
int64_t animation_duration;
double animation_repeat;
unsigned int animation_easing;
double animation_segment;
double attach_pos;
if (!svg_element_type_is_path (svg_element_get_type (shape)))
return ;
markup_filter_attributes (element_name,
attr_names, attr_values,
handled,
"gpa:stroke" , &stroke_attr,
"gpa:fill" , &fill_attr,
"gpa:stroke-width" , &strokewidth_attr,
"gpa:states" , &states_attr,
"gpa:origin" , &origin_attr,
"gpa:transition-type" , &transition_type_attr,
"gpa:transition-duration" , &transition_duration_attr,
"gpa:transition-delay" , &transition_delay_attr,
"gpa:transition-easing" , &transition_easing_attr,
"gpa:animation-type" , &animation_type_attr,
"gpa:animation-direction" , &animation_direction_attr,
"gpa:animation-duration" , &animation_duration_attr,
"gpa:animation-repeat" , &animation_repeat_attr,
"gpa:animation-segment" , &animation_segment_attr,
"gpa:animation-easing" , &animation_easing_attr,
"gpa:attach-to" , &attach_to_attr,
"gpa:attach-pos" , &attach_pos_attr,
NULL);
if (stroke_attr)
{
value = svg_paint_parse_gpa (stroke_attr);
if (value)
{
svg_element_set_gpa_stroke (shape, value);
svg_element_set_base_value (shape, SVG_PROPERTY_STROKE, value, FALSE );
svg_value_unref (value);
}
else
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "gpa:stroke" , NULL);
}
}
if (fill_attr)
{
value = svg_paint_parse_gpa (fill_attr);
if (value)
{
svg_element_set_gpa_fill (shape, value);
svg_element_set_base_value (shape, SVG_PROPERTY_FILL, value, FALSE );
svg_value_unref (value);
}
else
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "gpa:fill" , NULL);
}
}
if (strokewidth_attr)
{
SvgValue *values[3 ] = { NULL, };
if (strokewidth_parse (strokewidth_attr, values) &&
values[0 ] && values[1 ] && values[2 ])
{
svg_element_set_specified_value (shape, SVG_PROPERTY_STROKE_MINWIDTH, values[0 ]);
svg_element_set_specified_value (shape, SVG_PROPERTY_STROKE_WIDTH, values[1 ]);
svg_element_set_specified_value (shape, SVG_PROPERTY_STROKE_MAXWIDTH, values[2 ]);
svg_element_set_gpa_width (shape, values[1 ]);
}
else
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "gpa:stroke-width" , NULL);
}
g_clear_pointer (&values[0 ], svg_value_unref);
g_clear_pointer (&values[1 ], svg_value_unref);
g_clear_pointer (&values[2 ], svg_value_unref);
}
states = ALL_STATES;
if (states_attr)
{
if (!parse_states (data->svg, states_attr, &states))
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "gpa:states" , NULL);
states = ALL_STATES;
}
else
{
if (states != NO_STATES)
data->svg->max_state = MAX (data->svg->max_state, g_bit_nth_msf (states, -1 ));
}
}
origin = 0 ;
if (origin_attr)
{
if (!parse_number (origin_attr, 0 , 1 , &origin))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "gpa:origin" , NULL);
}
transition_type = GPA_TRANSITION_NONE;
if (transition_type_attr)
{
if (!parse_enum (transition_type_attr,
(const char *[]) { "none" , "animate" , "morph" , "fade" }, 4 ,
&transition_type))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "gpa:transition-type" , NULL);
}
transition_duration = 0 ;
if (transition_duration_attr)
{
if (!parse_duration (transition_duration_attr, FALSE , &transition_duration))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "gpa:transition-duration" , NULL);
}
transition_delay = 0 ;
if (transition_delay_attr)
{
if (!parse_duration (transition_delay_attr, FALSE , &transition_delay))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "gpa:transition-delay" , NULL);
}
transition_easing = GPA_EASING_LINEAR;
if (transition_easing_attr)
{
if (!parse_enum (transition_easing_attr,
(const char *[]) { "linear" , "ease-in-out" , "ease-in" ,
"ease-out" , "ease" }, 5 ,
&transition_easing))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "gpa:transition-easing" , NULL);
}
has_animation = 1 ;
if (animation_type_attr)
{
if (!parse_enum (animation_type_attr,
(const char *[]) { "none" , "automatic" , }, 2 ,
&has_animation))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "gpa:animation-type" , NULL);
}
animation_direction = GPA_ANIMATION_NONE;
if (has_animation && animation_direction_attr)
{
if (!parse_enum (animation_direction_attr,
(const char *[]) { "none" , "normal" , "alternate" , "reverse" ,
"reverse-alternate" , "in-out" ,
"in-out-alternate" , "in-out-reverse" ,
"segment" , "segment-alternate" }, 10 ,
&animation_direction))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "gpa:animation-direction" , NULL);
}
animation_duration = 0 ;
if (animation_duration_attr)
{
if (!parse_duration (animation_duration_attr, FALSE , &animation_duration))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "gpa:animation-duration" , NULL);
}
animation_repeat = REPEAT_FOREVER;
if (animation_repeat_attr)
{
if (!parse_number_or_named (animation_repeat_attr, 0 , DBL_MAX, "indefinite" , REPEAT_FOREVER, &animation_repeat))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "gpa:animation-repeat" , NULL);
}
animation_segment = 0 .2 ;
if (animation_segment_attr)
{
if (!parse_number (animation_segment_attr, 0 , 1 , &animation_segment))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "gpa:animation-segment" , NULL);
}
animation_easing = GPA_EASING_LINEAR;
if (animation_easing_attr)
{
if (!parse_enum (animation_easing_attr,
(const char *[]) { "linear" , "ease-in-out" , "ease-in" ,
"ease-out" , "ease" }, 5 ,
&animation_easing))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "gpa:animation-easing" , NULL);
}
attach_pos = 0 ;
if (attach_pos_attr)
{
if (!parse_number (attach_pos_attr, 0 , 1 , &attach_pos))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "gpa:attach-pos" , NULL);
}
svg_element_set_states (shape, states);
svg_element_set_gpa_transition (shape,
transition_type,
transition_easing,
transition_duration,
transition_delay);
svg_element_set_gpa_animation (shape,
animation_direction,
animation_easing,
animation_duration,
animation_repeat,
animation_segment);
svg_element_set_gpa_origin (shape, origin);
svg_element_set_gpa_attachment (shape, attach_to_attr, attach_pos, NULL);
if ((data->svg->features & GTK_SVG_ANIMATIONS) == 0 )
return ;
if (transition_type != GPA_TRANSITION_NONE ||
animation_direction != GPA_ANIMATION_NONE)
{
/* our dasharray-based animations require unit path length */
if (svg_element_is_specified (shape, SVG_PROPERTY_PATH_LENGTH))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "pathLength" ,
"Can't set '%s' and use gpa features" , "pathLength" );
if (svg_element_is_specified (shape, SVG_PROPERTY_STROKE_DASHARRAY))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "stroke-dasharray" ,
"Can't set '%s' and use gpa features" , "stroke-dasharray" );
if (svg_element_is_specified (shape, SVG_PROPERTY_STROKE_DASHOFFSET))
gtk_svg_invalid_attribute (data->svg, context, attr_names, "stroke-dashoffset" ,
"Can't set '%s' and use gpa features" , "stroke-dashoffset" );
if (svg_element_is_specified (shape, SVG_PROPERTY_FILTER) &&
transition_type == GPA_TRANSITION_MORPH)
gtk_svg_invalid_attribute (data->svg, context, attr_names, "filter" ,
"Can't set '%s' and use gpa features" , "filter" );
}
create_states (shape,
data->svg->timeline,
states,
transition_delay,
data->svg->state);
if (attach_to_attr ||
transition_type == GPA_TRANSITION_ANIMATE ||
animation_direction != GPA_ANIMATION_NONE)
create_path_length (shape, data->svg->timeline);
if (attach_to_attr)
create_attachment (shape,
data->svg->timeline,
states,
attach_to_attr,
attach_pos,
origin);
create_transitions (shape,
data->svg->timeline,
data->shapes,
states,
transition_type,
transition_duration,
transition_delay,
transition_easing,
origin);
create_animations (shape,
data->svg->timeline,
states,
data->svg->state,
animation_repeat,
animation_duration,
animation_direction,
animation_easing,
animation_segment,
origin);
}
/* }}} */
/* {{{ Color Stop attributes */
static void
parse_color_stop_attrs (SvgElement *shape,
SvgColorStop *stop,
const char *element_name,
const char **attr_names,
const char **attr_values,
uint64_t *handled,
ParserData *data,
GMarkupParseContext *context)
{
for (unsigned int i = 0 ; attr_names[i]; i++)
{
SvgProperty attr;
if (strcmp (attr_names[i], "id" ) == 0 )
{
*handled |= BIT (i);
svg_color_stop_set_id (stop, attr_values[i]);
}
else if (strcmp (attr_names[i], "style" ) == 0 )
{
*handled |= BIT (i);
svg_color_stop_set_inline_style (stop, attr_values[i]);
}
else if (strcmp (attr_names[i], "class" ) == 0 )
{
*handled |= BIT (i);
svg_color_stop_parse_classes (stop, attr_values[i]);
}
else if (svg_property_lookup_for_stop (attr_names[i], svg_element_get_type (shape), &attr))
{
SvgValue *value;
GError *error = NULL;
*handled |= BIT (i);
value = svg_property_parse_and_validate (attr, attr_values[i], &error);
if (error)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, attr_names[i], "%s" , error->message);
g_error_free (error);
}
else if (!value)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, attr_names[i], NULL);
}
if (value)
svg_color_stop_take_specified_value (stop, attr, value);
}
else
gtk_svg_invalid_attribute (data->svg, context, attr_names, attr_names[i], "Unknown attribute: %s" , attr_names[i]);
}
}
/* }}} */
/* {{{ Filter attributes */
static void
parse_filter_attrs (SvgElement *shape,
SvgFilter *f,
const char *element_name,
const char **attr_names,
const char **attr_values,
uint64_t *handled,
ParserData *data,
GMarkupParseContext *context)
{
SvgFilterType type = svg_filter_get_type (f);
for (unsigned int i = 0 ; attr_names[i]; i++)
{
SvgProperty attr;
if (strcmp (attr_names[i], "id" ) == 0 )
{
*handled |= BIT (i);
svg_filter_set_id (f, attr_values[i]);
}
else if (strcmp (attr_names[i], "style" ) == 0 )
{
*handled |= BIT (i);
svg_filter_set_inline_style (f, attr_values[i]);
}
else if (strcmp (attr_names[i], "class" ) == 0 )
{
*handled |= BIT (i);
svg_filter_parse_classes (f, attr_values[i]);
}
else if (svg_property_lookup_for_filter (attr_names[i], svg_element_get_type (shape), type, &attr))
{
if (svg_filter_is_specified (f, attr) && svg_attr_is_deprecated (attr_names[i]))
{
/* ignore */
}
else
{
SvgValue *value;
GError *error = NULL;
*handled |= BIT (i);
value = svg_property_parse_and_validate (attr, attr_values[i], &error);
if (error)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, attr_names[i], "%s" , error->message);
g_error_free (error);
}
else if (!value)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, attr_names[i], NULL);
}
if (value)
svg_filter_take_specified_value (f, attr, value);
}
}
else
gtk_svg_invalid_attribute (data->svg, context, attr_names, attr_names[i], "Unknown attribute: %s" , attr_names[i]);
}
}
/* }}} */
/* {{{ Parser */
G_GNUC_PRINTF (4 , 5 )
static void
skip_element (ParserData *data,
GMarkupParseContext *context,
GtkSvgError code,
const char *format,
...)
{
va_list args;
gtk_svg_location_init_tag_start (&data->skip.start, context);
data->skip.to = g_markup_parse_context_get_element_stack (context);
data->skip.skip_over_target = TRUE ;
data->skip.code = code;
va_start (args, format);
g_vasprintf (&data->skip.reason, format, args);
va_end (args);
}
static void
start_collect_text (ParserData *data,
GMarkupParseContext *context)
{
gtk_svg_location_init (&data->text.start, context);
data->text.collect = TRUE ;
g_string_set_size (data->text.text, 0 );
}
static void
start_element_cb (GMarkupParseContext *context,
const char *element_name,
const char **attr_names,
const char **attr_values,
gpointer user_data,
GError **gmarkup_error)
{
ParserData *data = user_data;
SvgElementType shape_type;
uint64_t handled = 0 ;
SvgFilterType filter_type;
GtkSvgLocation location;
if (data->skip.to)
return ;
gtk_svg_location_init_tag_start (&location, context);
if (data->num_loaded_elements++ > LOADING_LIMIT)
{
data->skip.start = location;
data->skip.to = g_markup_parse_context_get_element_stack (context)->next;
data->skip.code = GTK_SVG_ERROR_LIMITS_EXCEEDED;
data->skip.reason = g_strdup ("Loading limit exceeded" );
data->skip.skip_over_target = FALSE ;
return ;
}
if (svg_element_type_lookup (element_name, &shape_type))
{
SvgElement *shape = NULL;
const char *id;
if (data->current_shape &&
!svg_element_type_is_container (svg_element_get_type (data->current_shape)))
{
skip_element (data, context, GTK_SVG_ERROR_INVALID_ELEMENT, "Parent element can't contain shapes" );
return ;
}
if (shape_type != SVG_ELEMENT_USE &&
!svg_element_type_is_clip_path_content (shape_type) &&
has_ancestor (context, "clipPath" ) &&
shape_type != SVG_ELEMENT_CLIP_PATH)
{
skip_element (data, context, GTK_SVG_ERROR_INVALID_ELEMENT, "<clipPath> can only contain shapes, not %s" , element_name);
return ;
}
shape = svg_element_new (data->current_shape, shape_type);
svg_element_set_origin (shape, &location);
if (data->current_shape == NULL && svg_element_get_type (shape) == SVG_ELEMENT_SVG)
{
data->svg->content = shape;
if (data->svg->features & GTK_SVG_EXTENSIONS)
parse_svg_gpa_attrs (data->svg,
element_name, attr_names, attr_values,
&handled, data, context);
}
parse_shape_attrs (shape,
element_name, attr_names, attr_values,
&handled, data, context);
if (data->svg->gpa_version > 0 )
parse_shape_gpa_attrs (shape,
element_name, attr_names, attr_values,
&handled, data, context);
gtk_svg_check_unhandled_attributes (data->svg, context, attr_names, handled);
if (data->current_shape)
svg_element_add_child (data->current_shape, shape);
data->shape_stack = g_slist_prepend (data->shape_stack, data->current_shape);
data->current_shape = shape;
id = svg_element_get_id (shape);
if (id && !g_hash_table_contains (data->shapes, id))
g_hash_table_insert (data->shapes, (gpointer) id, shape);
return ;
}
if (strcmp (element_name, "stop" ) == 0 )
{
SvgColorStop *stop;
if (data->current_shape == NULL ||
(!check_ancestors (context, "linearGradient" , NULL) &&
!check_ancestors (context, "radialGradient" , NULL)))
{
skip_element (data, context, GTK_SVG_ERROR_INVALID_ELEMENT, "<stop> only allowed in <linearGradient> or <radialGradient>" );
return ;
}
stop = svg_color_stop_new (data->current_shape);
svg_color_stop_set_origin (stop, &location);
parse_color_stop_attrs (data->current_shape, stop,
element_name, attr_names, attr_values,
&handled, data, context);
gtk_svg_check_unhandled_attributes (data->svg, context, attr_names, handled);
svg_element_add_color_stop (data->current_shape, stop);
return ;
}
if (svg_filter_type_lookup (element_name, &filter_type))
{
SvgFilter *f;
if (filter_type == SVG_FILTER_MERGE_NODE)
{
if (!check_ancestors (context, "feMerge" , "filter" , NULL))
{
skip_element (data, context, GTK_SVG_ERROR_INVALID_ELEMENT, "<%s> only allowed in <feMerge>" , element_name);
return ;
}
}
else if (filter_type == SVG_FILTER_FUNC_R ||
filter_type == SVG_FILTER_FUNC_G ||
filter_type == SVG_FILTER_FUNC_B ||
filter_type == SVG_FILTER_FUNC_A)
{
if (!check_ancestors (context, "feComponentTransfer" , "filter" , NULL))
{
skip_element (data, context, GTK_SVG_ERROR_INVALID_ELEMENT, "<%s> only allowed in <feComponentTransfer>" , element_name);
return ;
}
}
else
{
if (!check_ancestors (context, "filter" , NULL))
{
skip_element (data, context, GTK_SVG_ERROR_INVALID_ELEMENT, "<%s> only allowed in <filter>" , element_name);
return ;
}
}
f = svg_filter_new (data->current_shape, filter_type);
svg_filter_set_origin (f, &location);
parse_filter_attrs (data->current_shape, f,
element_name, attr_names, attr_values,
&handled, data, context);
gtk_svg_check_unhandled_attributes (data->svg, context, attr_names, handled);
if (filter_type == SVG_FILTER_COLOR_MATRIX)
{
SvgValue *values = svg_filter_get_base_value (f, SVG_PROPERTY_FE_COLOR_MATRIX_VALUES);
SvgValue *initial = svg_filter_ref_initial_value (f, SVG_PROPERTY_FE_COLOR_MATRIX_VALUES);
if (svg_numbers_get_length (values) != svg_numbers_get_length (initial))
{
svg_filter_set_base_value (f, SVG_PROPERTY_FE_COLOR_MATRIX_VALUES, initial, FALSE );
if (!svg_filter_is_specified (f, SVG_PROPERTY_FE_COLOR_MATRIX_VALUES))
{
/* If this wasn't user-provided, we quietly correct the initial
* value to match the type
*/
gtk_svg_invalid_attribute (data->svg, context, attr_names, "values" , NULL);
}
}
svg_value_unref (initial);
}
svg_element_add_filter (data->current_shape, f);
return ;
}
if (strcmp (element_name, "metadata" ) == 0 )
{
return ;
}
if (strcmp (element_name, "title" ) == 0 ||
strcmp (element_name, "desc" ) == 0 )
{
for (unsigned int i = 0 ; attr_names[i]; i++)
{
if (strcmp (attr_names[i], "lang" ) == 0 )
{
data->lang = pango_language_from_string (attr_values[i]);
break ;
}
}
start_collect_text (data, context);
return ;
}
if (strcmp (element_name, "rdf:RDF" ) == 0 ||
strcmp (element_name, "cc:Work" ) == 0 ||
strcmp (element_name, "dc:subject" ) == 0 ||
strcmp (element_name, "dc:description" ) == 0 ||
strcmp (element_name, "dc:creator" ) == 0 ||
strcmp (element_name, "cc:Agent" ) == 0 ||
strcmp (element_name, "dc:title" ) == 0 ||
strcmp (element_name, "cc:license" ) == 0 ||
strcmp (element_name, "rdf:Bag" ) == 0 ||
strcmp (element_name, "rdf:li" ) == 0 )
{
if (!has_ancestor (context, "metadata" ))
skip_element (data, context, GTK_SVG_ERROR_IGNORED_ELEMENT, "Ignoring RDF elements outside <metadata>: <%s>" , element_name);
if (strcmp (element_name, "rdf:li" ) == 0 )
{
/* Verify we're in the right place */
if (check_ancestors (context, "rdf:Bag" , "dc:subject" , "cc:Work" , "rdf:RDF" , "metadata" , NULL))
start_collect_text (data, context);
else
skip_element (data, context, GTK_SVG_ERROR_IGNORED_ELEMENT, "Ignoring RDF element in wrong context: <%s>" , element_name);
}
else if (strcmp (element_name, "dc:description" ) == 0 )
{
if (check_ancestors (context, "cc:Work" , "rdf:RDF" , "metadata" , NULL))
start_collect_text (data, context);
else
skip_element (data, context, GTK_SVG_ERROR_IGNORED_ELEMENT, "Ignoring RDF element in wrong context: <%s>" , element_name);
}
else if (strcmp (element_name, "dc:title" ) == 0 )
{
if (check_ancestors (context, "cc:Agent" , "dc:creator" , "cc:Work" , "rdf:RDF" , "metadata" , NULL))
start_collect_text (data, context);
else
skip_element (data, context, GTK_SVG_ERROR_IGNORED_ELEMENT, "Ignoring RDF element in wrong context: <%s>" , element_name);
}
else if (strcmp (element_name, "cc:license" ) == 0 )
{
const char *license = NULL;
markup_filter_attributes (element_name,
attr_names, attr_values,
&handled,
"rdf:resource" , &license,
NULL);
g_set_str (&data->svg->license, license);
}
return ;
}
if (strcmp (element_name, "font-face" ) == 0 ||
strcmp (element_name, "font-face-src" ) == 0 )
{
return ;
}
if (strcmp (element_name, "font-face-uri" ) == 0 )
{
if (check_ancestors (context, "font-face-src" , "font-face" , NULL))
{
for (unsigned int i = 0 ; attr_names[i]; i++)
{
if (strcmp (attr_names[i], "href" ) == 0 )
{
if (!add_font_from_url (data->svg, context, attr_names, attr_names[i], attr_values[i]))
{
// too bad
}
}
}
}
else
skip_element (data, context, GTK_SVG_ERROR_IGNORED_ELEMENT, "Ignoring font element in the wrong context: <%s>" , element_name);
return ;
}
if (strcmp (element_name, "style" ) == 0 )
{
gboolean is_css = TRUE ;
const char *media = NULL;
for (unsigned int i = 0 ; attr_names[i]; i++)
{
if (strcmp (attr_names[i], "type" ) == 0 )
{
if (strcmp (attr_values[i], "text/css" ) != 0 )
is_css = FALSE ;
}
else if (strcmp (attr_names[i], "media" ) == 0 )
{
media = attr_values[i];
}
}
g_string_set_size (data->text.text, 0 );
if (is_css)
start_collect_text (data, context);
g_set_str (&data->media, media);
return ;
}
if (strcmp (element_name, "textPath" ) == 0 ||
strcmp (element_name, "feConvolveMatrix" ) == 0 ||
strcmp (element_name, "feDiffuseLighting" ) == 0 ||
strcmp (element_name, "feMorphology" ) == 0 ||
strcmp (element_name, "feSpecularLighting" ) == 0 )
{
skip_element (data, context, GTK_SVG_ERROR_NOT_IMPLEMENTED, "<%s> is not supported" , element_name);
return ;
}
if (g_str_has_prefix (element_name, "sodipodi:" ) ||
g_str_has_prefix (element_name, "inkscape:" ))
{
skip_element (data, context, GTK_SVG_ERROR_IGNORED_ELEMENT, "Ignoring metadata and non-standard elements: <%s>" , element_name);
return ;
}
if (strcmp (element_name, "set" ) == 0 )
{
SvgAnimation *a;
const char *to_attr = NULL;
SvgValue *value;
GError *error = NULL;
if ((data->svg->features & GTK_SVG_ANIMATIONS) == 0 )
{
skip_element (data, context, GTK_SVG_ERROR_FEATURE_DISABLED, "SvgAnimations are disabled" );
return ;
}
if (data->current_animation)
{
skip_element (data, context, GTK_SVG_ERROR_INVALID_ELEMENT, "Nested animation elements are not allowed: <set>" );
return ;
}
a = svg_animation_new (ANIMATION_TYPE_SET);
markup_filter_attributes (element_name,
attr_names, attr_values,
&handled,
"to" , &to_attr,
NULL);
if (!parse_base_animation_attrs (a,
element_name,
attr_names, attr_values,
&handled,
data,
context))
{
svg_animation_drop_and_free (a);
skip_element (data, context, GTK_SVG_ERROR_INVALID_ELEMENT, "Skipping <%s> - bad attributes" , element_name);
return ;
}
gtk_svg_check_unhandled_attributes (data->svg, context, attr_names, handled);
if (!to_attr)
{
gtk_svg_missing_attribute (data->svg, context, "to" );
svg_animation_drop_and_free (a);
skip_element (data, context, GTK_SVG_ERROR_INVALID_ELEMENT, "Dropping <set> without 'to'" );
return ;
}
a->calc_mode = CALC_MODE_DISCRETE;
a->frames = g_new0 (Frame, 2 );
a->frames[0 ].time = 0 ;
a->frames[1 ].time = 1 ;
value = svg_property_parse_and_validate (a->attr, to_attr, &error);
if (error)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "to" , "%s" , error->message);
g_error_free (error);
}
else if (!value)
{
gtk_svg_invalid_attribute (data->svg, context, attr_names, "to" , NULL);
}
if (!value)
{
svg_animation_drop_and_free (a);
skip_element (data, context, GTK_SVG_ERROR_INVALID_ELEMENT, "Dropping <set> without 'to'" );
return ;
}
a->frames[0 ].value = svg_value_ref (value);
a->frames[1 ].value = svg_value_ref (value);
a->n_frames = 2 ;
svg_value_unref (value);
if (!a->href ||
(data->current_shape != NULL &&
g_strcmp0 (a->href, svg_element_get_id (data->current_shape)) == 0 ))
{
a->shape = data->current_shape;
svg_element_add_animation (data->current_shape, a);
}
else
g_ptr_array_add (data->pending_animations, a);
if (a->id)
g_hash_table_insert (data->animations, a->id, a);
data->current_animation = a;
return ;
}
if (strcmp (element_name, "animate" ) == 0 ||
strcmp (element_name, "animateTransform" ) == 0 ||
strcmp (element_name, "animateMotion" ) == 0 )
{
SvgAnimation *a;
if ((data->svg->features & GTK_SVG_ANIMATIONS) == 0 )
{
skip_element (data, context, GTK_SVG_ERROR_IGNORED_ELEMENT, "SvgAnimations are disabled" );
return ;
}
if (data->current_animation)
{
skip_element (data, context, GTK_SVG_ERROR_INVALID_ELEMENT, "Nested animation elements are not allowed: <%s>" , element_name);
return ;
}
if (strcmp (element_name, "animate" ) == 0 )
a = svg_animation_new (ANIMATION_TYPE_ANIMATE);
else if (strcmp (element_name, "animateTransform" ) == 0 )
a = svg_animation_new (ANIMATION_TYPE_TRANSFORM);
else
a = svg_animation_new (ANIMATION_TYPE_MOTION);
a->line = location.lines;
if (!parse_base_animation_attrs (a,
element_name,
attr_names, attr_values,
&handled,
data,
context))
{
svg_animation_drop_and_free (a);
skip_element (data, context, GTK_SVG_ERROR_INVALID_ELEMENT, "Skipping <%s> - bad attributes" , element_name);
return ;
}
if (!parse_value_animation_attrs (a,
element_name,
attr_names, attr_values,
&handled,
data,
context))
{
svg_animation_drop_and_free (a);
skip_element (data, context, GTK_SVG_ERROR_INVALID_ELEMENT, "Skipping <%s> - bad attributes" , element_name);
return ;
}
if (a->type == ANIMATION_TYPE_MOTION)
{
if (!parse_motion_animation_attrs (a,
element_name,
attr_names, attr_values,
&handled,
data,
context))
{
svg_animation_drop_and_free (a);
skip_element (data, context, GTK_SVG_ERROR_INVALID_ELEMENT, "Skipping <%s>: bad attributes" , element_name);
return ;
}
}
gtk_svg_check_unhandled_attributes (data->svg, context, attr_names, handled);
if (data->current_shape != NULL &&
(a->href == NULL ||
g_strcmp0 (a->href, svg_element_get_id (data->current_shape)) == 0 ))
{
a->shape = data->current_shape;
svg_element_add_animation (data->current_shape, a);
}
else
g_ptr_array_add (data->pending_animations, a);
if (a->id)
g_hash_table_insert (data->animations, a->id, a);
data->current_animation = a;
return ;
}
if (strcmp (element_name, "mpath" ) == 0 )
{
const char *xlink_href_attr = NULL;
const char *href_attr = NULL;
const char *href_attr_name = "href" ;
if (data->current_animation == NULL ||
data->current_animation->type != ANIMATION_TYPE_MOTION ||
data->current_animation->motion.path_ref != NULL)
{
skip_element (data, context, GTK_SVG_ERROR_INVALID_ELEMENT, "<mpath> only allowed in <animateMotion>" );
return ;
}
for (unsigned int i = 0 ; attr_names[i]; i++)
{
if (strcmp (attr_names[i], "xlink:href" ) == 0 )
{
xlink_href_attr = attr_values[i];
handled |= BIT (i);
}
else if (strcmp (attr_names[i], "href" ) == 0 )
{
href_attr = attr_values[i];
handled |= BIT (i);
}
}
if (xlink_href_attr && !href_attr)
{
href_attr = xlink_href_attr;
href_attr_name = "xlink:href" ;
}
if (href_attr != NULL)
{
if (href_attr[0 ] != '#' )
gtk_svg_invalid_attribute (data->svg, context, attr_names, href_attr_name,
"Missing '#' in href" );
else
data->current_animation->motion.path_ref = g_strdup (href_attr + 1 );
}
gtk_svg_check_unhandled_attributes (data->svg, context, attr_names, handled);
if (!data->current_animation->motion.path_ref)
gtk_svg_missing_attribute (data->svg, context, "href" );
return ;
}
/* If we get here, its all over */
skip_element (data, context, GTK_SVG_ERROR_IGNORED_ELEMENT, "Unknown element: <%s>" , element_name);
}
static void
end_element_cb (GMarkupParseContext *context,
const char *element_name,
gpointer user_data,
GError **gmarkup_error)
{
ParserData *data = user_data;
SvgElementType shape_type;
data->text.collect = FALSE ;
if (data->skip.to != NULL)
{
if (data->skip.to == g_markup_parse_context_get_element_stack (context))
{
GtkSvgLocation end;
const char *parent;
if (data->skip.to->next)
parent = data->skip.to->next->data;
else
parent = NULL;
gtk_svg_location_init_tag_end (&end, context);
gtk_svg_skipped_element (data->svg,
parent,
&data->skip.start,
&end,
data->skip.code,
"%s" , data->skip.reason);
g_clear_pointer (&data->skip.reason, g_free);
data->skip.to = NULL;
if (!data->skip.skip_over_target)
goto do_target;
}
return ;
}
do_target:
if (strcmp (element_name, "style" ) == 0 )
{
if (data->text.text->len > 0 )
{
char *string;
StyleElt *elt;
string = g_strdup (data->text.text->str);
elt = g_new0 (StyleElt, 1 );
elt->content = g_bytes_new_take (string, strlen (string));
elt->location = data->text.start;
elt->media = g_steal_pointer (&data->media);
g_ptr_array_add (data->current_shape->styles, elt);
}
}
else if (strcmp (element_name, "rdf:li" ) == 0 )
{
g_set_str (&data->svg->keywords, data->text.text->str);
}
else if (strcmp (element_name, "dc:description" ) == 0 )
{
g_set_str (&data->svg->description, data->text.text->str);
}
else if (strcmp (element_name, "dc:title" ) == 0 )
{
g_set_str (&data->svg->author, data->text.text->str);
}
else if (svg_element_type_lookup (element_name, &shape_type))
{
GSList *tos = data->shape_stack;
g_assert (shape_type == svg_element_get_type (data->current_shape));
svg_element_set_language (data->current_shape, gtk_get_default_language ());
data->current_shape = tos->data;
data->shape_stack = tos->next;
g_slist_free_1 (tos);
}
else if (strcmp (element_name, "set" ) == 0 ||
strcmp (element_name, "animate" ) == 0 ||
strcmp (element_name, "animateTransform" ) == 0 ||
strcmp (element_name, "animateMotion" ) == 0 )
{
data->current_animation = NULL;
}
else if (strcmp (element_name, "title" ) == 0 )
{
svg_element_add_title (data->current_shape, data->lang, data->text.text->str);
}
else if (strcmp (element_name, "desc" ) == 0 )
{
svg_element_add_description (data->current_shape, data->lang, data->text.text->str);
}
data->lang = NULL;
}
static void
text_cb (GMarkupParseContext *context,
const char *text,
size_t len,
gpointer user_data,
GError **error)
{
ParserData *data = user_data;
SvgElement *text_parent = NULL;
if (!data->current_shape)
return ;
if (svg_element_type_is_text (svg_element_get_type (data->current_shape)))
text_parent = data->current_shape;
else
{
SvgElement *parent = svg_element_get_parent (data->current_shape);
if (parent &&
svg_element_get_type (data->current_shape) == SVG_ELEMENT_LINK &&
svg_element_type_is_text (svg_element_get_type (parent)))
text_parent = parent;
}
if (text_parent)
{
TextNode node = {
.type = TEXT_NODE_CHARACTERS,
.characters = { .text = g_strndup (text, len) }
};
g_array_append_val (text_parent->text, node);
return ;
}
if (!data->text.collect)
return ;
g_string_append_len (data->text.text, text, len);
}
static void
error_cb (GMarkupParseContext *context,
GError *error,
gpointer user_data)
{
ParserData *data = user_data;
gtk_svg_markup_error (data->svg, context, error);
}
/* }}} */
/* {{{ Href handling, dependency tracking */
/* To deal with forward references, we resolve references after parsing
* the entire SVG . The references hide in various value types ( e . g . paint
* servers ) , as well as in animations .
*
* Note : Since we don ' t have scripting , once resolved , the reference of a
* value does not change . However , animations can change swap the value
* out for another one with a different reference .
*
* Some kinds of references induce dependencies wrt . to computing current
* values ( e . g . the when an < animateMotion > element has an < mpath > child
* that refers to the path of an element , then that element needs to have
* its values upated before the animation uses its path .
*/
static void
add_dependency (SvgElement *shape0,
SvgElement *shape1)
{
if (!shape0->deps)
shape0->deps = g_ptr_array_new ();
g_ptr_array_add (shape0->deps, shape1);
}
/* Record the fact that when computing updated
* values , shape0 must be handled before shape1 .
*/
static void
add_dependency_to_common_ancestor (SvgElement *shape0,
SvgElement *shape1)
{
SvgElement *anc0, *anc1;
if (svg_element_common_ancestor (shape0, shape1, &anc0, &anc1))
add_dependency (anc0, anc1);
}
static void
resolve_clip_ref (SvgValue *value,
SvgElement *shape,
ParserData *data)
{
if (svg_value_is_keyword (value))
return ;
if (svg_clip_get_kind (value) == CLIP_URL &&
svg_clip_get_shape (value) == NULL)
{
const char *ref = svg_clip_get_id (value);
SvgElement *target = g_hash_table_lookup (data->shapes, ref);
if (!target)
gtk_svg_invalid_reference (data->svg,
"No path with ID %s (resolving clip-path)" ,
ref);
else if (target->type != SVG_ELEMENT_CLIP_PATH)
gtk_svg_invalid_reference (data->svg,
"SvgElement with ID %s not a <clipPath> (resolving clip-path)" ,
ref);
else
{
svg_clip_set_shape (value, target);
if (shape)
add_dependency_to_common_ancestor (shape, target);
}
}
}
static void
resolve_mask_ref (SvgValue *value,
SvgElement *shape,
ParserData *data)
{
if (svg_value_is_keyword (value))
return ;
if (svg_mask_get_kind (value) == MASK_URL && svg_mask_get_shape (value) == NULL)
{
const char *id = svg_mask_get_id (value);
SvgElement *target = g_hash_table_lookup (data->shapes, id);
if (!target)
gtk_svg_invalid_reference (data->svg, "No shape with ID %s (resolving mask)" , id);
else if (target->type != SVG_ELEMENT_MASK)
gtk_svg_invalid_reference (data->svg, "SvgElement with ID %s not a <mask> (resolving mask)" , id);
else
{
svg_mask_set_shape (value, target);
if (shape)
add_dependency_to_common_ancestor (shape, target);
}
}
}
static void
resolve_href_ref (SvgValue *value,
SvgElement *shape,
ParserData *data)
{
const char *ref;
if (svg_value_is_keyword (value))
return ;
if (svg_href_get_kind (value) == HREF_NONE)
return ;
/* Since hrefs can't be set in css, we can only ever get here
* from a presentation attr , so we always have a shape .
*/
g_assert (shape != NULL);
ref = svg_href_get_ref (value);
if (svg_element_get_type (shape) == SVG_ELEMENT_IMAGE || svg_element_get_type (shape) == SVG_ELEMENT_FILTER)
{
GError *error = NULL;
GdkTexture *texture;
texture = get_texture (data->svg, ref, &error);
svg_href_set_texture (value, texture);
if (texture != NULL)
return ;
if (svg_element_get_type (shape) == SVG_ELEMENT_IMAGE)
{
if (g_error_matches (error, GTK_SVG_ERROR, GTK_SVG_ERROR_FEATURE_DISABLED))
gtk_svg_emit_error (data->svg, error);
else
gtk_svg_invalid_reference (data->svg, "Failed to load %s (resolving <image>): %s" , ref, error->message);
g_error_free (error);
return ; /* Image href is always external */
}
g_error_free (error);
}
if (svg_href_get_shape (value) == NULL)
{
const char *id = NULL;
SvgElement *target = NULL;
if (ref && ref[0 ] == '#' )
id = ref + 1 ;
if (id)
target = g_hash_table_lookup (data->shapes, id);
if (!target)
{
if (id && svg_element_get_type (shape) == SVG_ELEMENT_LINK)
{
SvgAnimation *animation = svg_element_find_animation (data->svg->content, id);
if (animation)
{
svg_href_set_animation (value, animation);
return ;
}
}
gtk_svg_invalid_reference (data->svg,
"No element with ID %s (resolving href in <%s>)" ,
ref,
svg_element_type_get_name (svg_element_get_type (shape)));
}
else if (svg_element_get_type (shape) == SVG_ELEMENT_USE &&
svg_element_or_ancestor_has_type (shape, SVG_ELEMENT_CLIP_PATH) &&
!svg_element_type_is_clip_path_content (target->type))
{
gtk_svg_invalid_reference (data->svg,
"Can't include a <%s> in a <clipPath> via <use>" ,
svg_element_type_get_name (target->type));
}
else
{
svg_href_set_shape (value, target);
add_dependency_to_common_ancestor (shape, target);
}
}
}
static void
resolve_marker_ref (SvgValue *value,
SvgElement *shape,
ParserData *data)
{
if (svg_value_is_keyword (value))
return ;
if (svg_href_get_kind (value) != HREF_NONE && svg_href_get_shape (value) == NULL)
{
const char *ref = svg_href_get_id (value);
SvgElement *target = g_hash_table_lookup (data->shapes, ref);
if (!target)
{
gtk_svg_invalid_reference (data->svg, "No shape with ID %s" , ref);
}
else if (target->type != SVG_ELEMENT_MARKER)
{
gtk_svg_invalid_reference (data->svg,
"SvgElement with ID %s not a <marker>" ,
ref);
}
else
{
svg_href_set_shape (value, target);
if (shape)
add_dependency_to_common_ancestor (shape, target);
}
}
}
static void
resolve_paint_ref (SvgValue *value,
SvgElement *shape,
ParserData *data)
{
SvgValue *paint = value;
if (svg_value_is_keyword (value))
return ;
if (paint_is_server (svg_paint_get_kind (paint)) &&
svg_paint_get_server_shape (paint) == NULL)
{
const char *ref = svg_paint_get_server_ref (paint);
SvgElement *target = g_hash_table_lookup (data->shapes, ref);
if (!target)
{
GtkSymbolicColor symbolic;
if ((data->svg->features & GTK_SVG_EXTENSIONS) != 0 &&
g_str_has_prefix (ref, "gpa:" ) &&
parse_symbolic_color (ref + strlen ("gpa:" ), &symbolic))
return ; /* Handled later */
gtk_svg_invalid_reference (data->svg, "No shape with ID %s (resolving fill or stroke)" , ref);
}
else if (target->type != SVG_ELEMENT_LINEAR_GRADIENT &&
target->type != SVG_ELEMENT_RADIAL_GRADIENT &&
target->type != SVG_ELEMENT_PATTERN)
{
gtk_svg_invalid_reference (data->svg, "SvgElement with ID %s not a paint server (resolving fill or stroke)" , ref);
}
else
{
svg_paint_set_server_shape (paint, target);
if (shape)
add_dependency_to_common_ancestor (shape, target);
}
}
}
static void
resolve_attach_ref (SvgElement *shape,
ParserData *data)
{
const char *id;
double position;
SvgElement *sh;
svg_element_get_gpa_attachment (shape, &id, &position, &sh);
if (id != NULL && sh == NULL)
{
sh = g_hash_table_lookup (data->shapes, id);
svg_element_set_gpa_attachment (shape, id, position, sh);
}
}
static void
resolve_filter_ref (SvgValue *value,
SvgElement *shape,
ParserData *data)
{
if (svg_value_is_keyword (value))
return ;
for (unsigned int i = 0 ; i < svg_filter_functions_get_length (value); i++)
{
if (svg_filter_functions_get_kind (value, i) == FILTER_REF &&
svg_filter_functions_get_shape (value, i) == NULL)
{
const char *ref = svg_filter_functions_get_ref (value, i);
SvgElement *target = g_hash_table_lookup (data->shapes, ref);
if (!target)
{
gtk_svg_invalid_reference (data->svg,
"No shape with ID %s (resolving filter)" ,
ref);
}
else if (target->type != SVG_ELEMENT_FILTER)
{
gtk_svg_invalid_reference (data->svg,
"SvgElement with ID %s not a filter (resolving filter)" ,
ref);
}
else
{
svg_filter_functions_set_shape (value, i, target);
if (shape)
add_dependency_to_common_ancestor (shape, target);
}
}
}
}
static void
resolve_refs_for_animation (SvgAnimation *a,
ParserData *data)
{
unsigned int first;
for (int k = 0 ; k < 2 ; k++)
{
GPtrArray *specs = k == 0 ? a->begin : a->end;
for (unsigned int j = 0 ; j < specs->len; j++)
{
TimeSpec *spec = g_ptr_array_index (specs, j);
if (spec->type == TIME_SPEC_TYPE_SYNC && spec->sync.base == NULL)
{
g_assert (spec->sync.ref);
spec->sync.base = g_hash_table_lookup (data->animations, spec->sync.ref);
if (!spec->sync.base)
gtk_svg_invalid_reference (data->svg, "No animation with ID %s" , spec->sync.ref);
else
svg_animation_add_dep (spec->sync.base, a);
}
else if (spec->type == TIME_SPEC_TYPE_EVENT && spec->event.shape == NULL)
{
g_assert (spec->event.ref);
spec->event.shape = g_hash_table_lookup (data->shapes, spec->event.ref);
if (!spec->event.shape)
gtk_svg_invalid_reference (data->svg, "No shape with ID %s" , spec->event.ref);
}
}
}
/* The resolve functions don't know how to handle
* our special current keyword , and it gets resolved
* later anyway , so skip it .
*/
if (a->frames[0 ].value && svg_value_is_current (a->frames[0 ].value))
first = 1 ;
else
first = 0 ;
if (a->attr == SVG_PROPERTY_CLIP_PATH)
{
for (unsigned int j = first; j < a->n_frames; j++)
resolve_clip_ref (a->frames[j].value, a->shape, data);
}
else if (a->attr == SVG_PROPERTY_MASK)
{
for (unsigned int j = first; j < a->n_frames; j++)
resolve_mask_ref (a->frames[j].value, a->shape, data);
}
else if (a->attr == SVG_PROPERTY_HREF)
{
for (unsigned int j = first; j < a->n_frames; j++)
resolve_href_ref (a->frames[j].value, a->shape, data);
}
else if (a->attr == SVG_PROPERTY_MARKER_START ||
a->attr == SVG_PROPERTY_MARKER_MID ||
a->attr == SVG_PROPERTY_MARKER_END)
{
for (unsigned int j = first; j < a->n_frames; j++)
resolve_marker_ref (a->frames[j].value, a->shape, data);
}
else if (a->attr == SVG_PROPERTY_FILL ||
a->attr == SVG_PROPERTY_STROKE)
{
for (unsigned int j = first; j < a->n_frames; j++)
resolve_paint_ref (a->frames[j].value, a->shape, data);
}
else if (a->attr == SVG_PROPERTY_FILTER)
{
for (unsigned int j = first; j < a->n_frames; j++)
resolve_filter_ref (a->frames[j].value, a->shape, data);
}
else if (a->attr == SVG_PROPERTY_FE_IMAGE_HREF)
{
for (unsigned int j = first; j < a->n_frames; j++)
resolve_href_ref (a->frames[j].value, a->shape, data);
}
if (a->motion.path_ref)
{
SvgElement *shape = g_hash_table_lookup (data->shapes, a->motion.path_ref);
if (shape == NULL)
gtk_svg_invalid_reference (data->svg,
"No path with ID %s (resolving <mpath>" ,
a->motion.path_ref);
else if (!svg_element_type_is_path (svg_element_get_type (shape)))
gtk_svg_invalid_reference (data->svg,
"Element with ID %s is not a shape (resolving <mpath>" ,
a->motion.path_ref);
else
{
a->motion.path_shape = shape;
add_dependency_to_common_ancestor (a->shape, a->motion.path_shape);
if (a->id && g_str_has_prefix (a->id, "gpa:attachment:" ))
{
/* a's path is attached to a->motion.path_shape
* Make sure it moves along with transitions and animations
*/
create_attachment_connection (a, a->motion.path_shape, data->svg->timeline);
}
}
}
}
static void
resolve_animation_refs (SvgElement *shape,
ParserData *data)
{
if (shape->animations)
{
for (unsigned int i = 0 ; i < shape->animations->len; i++)
{
SvgAnimation *a = g_ptr_array_index (shape->animations, i);
resolve_refs_for_animation (a, data);
}
}
if (svg_element_type_is_container (svg_element_get_type (shape)))
{
for (unsigned int i = 0 ; i < shape->shapes->len; i++)
{
SvgElement *sh = g_ptr_array_index (shape->shapes, i);
resolve_animation_refs (sh, data);
}
}
}
static void
resolve_filter_image_refs (SvgElement *shape,
ParserData *data)
{
if (svg_element_get_type (shape) != SVG_ELEMENT_FILTER)
return ;
for (unsigned int i = 0 ; i < shape->filters->len; i++)
{
SvgFilter *f = g_ptr_array_index (shape->filters, i);
SvgFilterType type = svg_filter_get_type (f);
if (type == SVG_FILTER_IMAGE)
{
SvgValue *value = svg_filter_get_specified_value (f, SVG_PROPERTY_FE_IMAGE_HREF);
if (value)
resolve_href_ref (value, shape, data);
}
}
}
static void
resolve_ref_in_property (SvgProperty attr,
SvgValue *value,
SvgElement *shape,
ParserData *data)
{
switch ((unsigned int ) attr)
{
case SVG_PROPERTY_FILL:
case SVG_PROPERTY_STROKE:
resolve_paint_ref (value, shape, data);
break ;
case SVG_PROPERTY_MASK:
resolve_mask_ref (value, shape, data);
break ;
case SVG_PROPERTY_MARKER_START:
case SVG_PROPERTY_MARKER_MID:
case SVG_PROPERTY_MARKER_END:
resolve_marker_ref (value, shape, data);
break ;
case SVG_PROPERTY_CLIP_PATH:
resolve_clip_ref (value, shape, data);
break ;
case SVG_PROPERTY_FILTER:
resolve_filter_ref (value, shape, data);
break ;
case SVG_PROPERTY_HREF:
resolve_href_ref (value, shape, data);
break ;
default :
break ;
}
}
static void
resolve_refs_in_properties (PropertyValue *styles,
unsigned int n_styles,
SvgElement *shape,
ParserData *data)
{
for (unsigned int i = 0 ; i < n_styles; i++)
{
PropertyValue *p = &styles[i];
resolve_ref_in_property (p->attr, p->value, shape, data);
}
}
static void
resolve_refs_in_ruleset (SvgCssRuleset *r,
SvgElement *shape,
ParserData *data)
{
resolve_refs_in_properties (r->styles, r->n_styles, shape, data);
}
static void
resolve_refs_in_array (GArray *a,
SvgElement *shape,
ParserData *data)
{
resolve_refs_in_properties ((PropertyValue *)a->data, a->len, shape, data);
}
static void
resolve_refs_in_rulesets (GArray *rulesets,
SvgElement *shape,
ParserData *data)
{
for (unsigned int i = 0 ; i < rulesets->len; i++)
{
SvgCssRuleset *r = &g_array_index (rulesets, SvgCssRuleset, i);
resolve_refs_in_ruleset (r, shape, data);
}
}
static gboolean
can_add (SvgElement *shape,
GHashTable *waiting)
{
if (shape->deps)
{
for (unsigned int i = 0 ; i < shape->deps->len; i++)
{
SvgElement *dep = g_ptr_array_index (shape->deps, i);
if (g_hash_table_contains (waiting, dep))
return FALSE ;
}
}
return TRUE ;
}
static void
do_compute_update_order (SvgElement *shape,
GtkSvg *svg,
GHashTable *waiting)
{
unsigned int n_waiting;
gboolean has_cycle = FALSE ;
SvgElement *last = NULL;
if (!svg_element_type_is_container (svg_element_get_type (shape)))
return ;
g_assert (g_hash_table_size (waiting) == 0 );
for (unsigned int i = 0 ; i < shape->shapes->len; i++)
{
SvgElement *sh = g_ptr_array_index (shape->shapes, i);
do_compute_update_order (sh, svg, waiting);
}
for (unsigned int i = 0 ; i < shape->shapes->len; i++)
{
SvgElement *sh = g_ptr_array_index (shape->shapes, i);
g_hash_table_add (waiting, sh);
}
n_waiting = g_hash_table_size (waiting);
while (n_waiting > 0 )
{
GHashTableIter iter;
SvgElement *key;
g_hash_table_iter_init (&iter, waiting);
while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL))
{
if (can_add (key, waiting) || has_cycle)
{
if (last)
last->next = key;
else
shape->first = key;
last = key;
last->next = NULL;
g_hash_table_iter_remove (&iter);
}
}
if (n_waiting == g_hash_table_size (waiting))
{
gtk_svg_update_error (svg, "Cyclic dependency detected" );
has_cycle = TRUE ;
}
n_waiting = g_hash_table_size (waiting);
}
for (unsigned int i = 0 ; i < shape->shapes->len; i++)
{
SvgElement *sh = g_ptr_array_index (shape->shapes, i);
g_clear_pointer (&sh->deps, g_ptr_array_unref);
}
}
static void
compute_update_order (SvgElement *shape,
GtkSvg *svg)
{
GHashTable *waiting = g_hash_table_new (g_direct_hash, g_direct_equal);
do_compute_update_order (shape, svg, waiting);
g_hash_table_unref (waiting);
}
/* }}} */
/* {{{ CSS handling */
/* Most of this is adapted from gtkcssprovider.c */
#define GDK_ARRAY_NAME svg_css_selectors
#define GDK_ARRAY_TYPE_NAME SvgCssSelectors
#define GDK_ARRAY_ELEMENT_TYPE GtkCssSelector *
#define GDK_ARRAY_PREALLOC 64
#include "gdk/gdkarrayimpl.c"
static void
svg_css_ruleset_init_copy (SvgCssRuleset *new ,
SvgCssRuleset *ruleset,
GtkCssSelector *selector)
{
memcpy (new , ruleset, sizeof (SvgCssRuleset));
new ->selector = selector;
if (ruleset->owns_styles)
ruleset->owns_styles = FALSE ;
}
void
svg_css_ruleset_clear (SvgCssRuleset *ruleset)
{
if (ruleset->owns_styles)
{
for (unsigned int i = 0 ; i < ruleset->n_styles; i++)
{
svg_value_unref (ruleset->styles[i].value);
ruleset->styles[i].value = NULL;
}
g_free (ruleset->styles);
}
if (ruleset->selector)
_gtk_css_selector_free (ruleset->selector);
memset (ruleset, 0 , sizeof (SvgCssRuleset));
}
static void
svg_css_ruleset_add (SvgCssRuleset *ruleset,
SvgProperty attr,
SvgValue *value,
gboolean important)
{
unsigned int i;
g_return_if_fail (ruleset->owns_styles || ruleset->n_styles == 0 );
ruleset->owns_styles = TRUE ;
for (i = 0 ; i < ruleset->n_styles; i++)
{
if (ruleset->styles[i].attr == attr &&
ruleset->styles[i].important == important)
{
svg_value_unref (ruleset->styles[i].value);
ruleset->styles[i].value = NULL;
break ;
}
}
if (i == ruleset->n_styles)
{
ruleset->n_styles++;
ruleset->styles = g_realloc (ruleset->styles, ruleset->n_styles * sizeof (PropertyValue));
ruleset->styles[i].attr = attr;
}
ruleset->styles[i].value = value;
ruleset->styles[i].important = important;
}
typedef struct _SvgCssScanner SvgCssScanner;
struct _SvgCssScanner
{
ParserData *data;
GtkCssParser *parser;
SvgCssScanner *parent;
SvgCssMediaBlock *media;
};
static void
svg_css_scanner_parser_error (GtkCssParser *parser,
const GtkCssLocation *css_start,
const GtkCssLocation *css_end,
const GError *css_error,
gpointer user_data)
{
ParserData *data = user_data;
if (css_error->domain == GTK_CSS_PARSER_ERROR)
{
GError *error;
GFile *file;
GtkSvgLocation start = { 0 , };
GtkSvgLocation end = { 0 , };
error = g_error_new_literal (GTK_SVG_ERROR,
GTK_SVG_ERROR_INVALID_SYNTAX,
css_error->message);
if ((file = gtk_css_parser_get_file (parser)) != NULL)
gtk_svg_error_set_input (error, g_file_peek_path (file));
else if (data->load_user_style)
gtk_svg_error_set_input (error, "stylesheet" );
else
{
gtk_svg_error_set_input (error, "svg" );
start = data->text.start;
end = data->text.start;
}
if (data->current_shape)
{
gtk_svg_error_set_element (error, svg_element_type_get_name (svg_element_get_type (data->current_shape)));
gtk_svg_error_set_attribute (error, "style" );
}
start.bytes += css_start->bytes;
start.lines += css_start->lines;
if (css_start->lines == 0 )
start.line_chars += css_start->line_chars;
else
start.line_chars = css_start->line_chars;
end.bytes += css_end->bytes;
end.lines += css_end->lines;
if (css_end->lines == 0 )
end.line_chars += css_end->line_chars;
else
end.line_chars = css_end->line_chars;
gtk_svg_error_set_location (error, &start, &end);
gtk_svg_emit_error (data->svg, error);
g_clear_error (&error);
}
}
static SvgCssScanner *
svg_css_scanner_new (ParserData *data,
SvgCssScanner *parent,
GFile *file,
GBytes *bytes)
{
SvgCssScanner *scanner = g_new0 (SvgCssScanner, 1 );
scanner->data = data;
scanner->parent = parent;
scanner->parser = gtk_css_parser_new_for_bytes (bytes, file, svg_css_scanner_parser_error, data, NULL);
return scanner;
}
static void
svg_css_scanner_destroy (SvgCssScanner *scanner)
{
gtk_css_parser_unref (scanner->parser);
g_free (scanner);
}
static int
svg_css_compare_rule (gconstpointer a_,
gconstpointer b_)
{
const SvgCssRuleset *a = (const SvgCssRuleset *) a_;
const SvgCssRuleset *b = (const SvgCssRuleset *) b_;
/* Sort from highest to lowest specificity */
return _gtk_css_selector_compare (b->selector, a->selector);
}
static void load_internal (ParserData *data,
SvgCssScanner *scanner,
GFile *file,
GBytes *bytes,
const char *media);
static gboolean
svg_css_scanner_would_recurse (SvgCssScanner *scanner,
GFile *file)
{
while (scanner)
{
GFile *parser_file = gtk_css_parser_get_file (scanner->parser);
if (parser_file && g_file_equal (parser_file, file))
return TRUE ;
scanner = scanner->parent;
}
return FALSE ;
}
static gboolean
parse_import (SvgCssScanner *scanner)
{
GFile *file;
if (!gtk_css_parser_try_at_keyword (scanner->parser, "import" ))
return FALSE ;
if (gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_STRING))
{
char *url = gtk_css_parser_consume_string (scanner->parser);
if (url)
{
if (gtk_css_parser_get_file (scanner->parser))
file = gtk_css_parser_resolve_url (scanner->parser, url);
else
file = g_file_new_for_path (url);
if (file == NULL)
{
gtk_css_parser_error_import (scanner->parser,
"Could not resolve \" %s\" to a valid URL" ,
url);
}
g_free (url);
}
else
file = NULL;
}
else
{
char *url = gtk_css_parser_consume_url (scanner->parser);
if (url)
{
if (gtk_css_parser_get_file (scanner->parser))
file = gtk_css_parser_resolve_url (scanner->parser, url);
else
file = g_file_new_for_uri (url);
g_free (url);
}
else
file = NULL;
}
if (file == NULL)
{
/* nothing to do; */
}
else if (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
{
gtk_css_parser_error_syntax (scanner->parser, "Expected ';'" );
}
else if ((scanner->data->svg->features & GTK_SVG_EXTERNAL_RESOURCES) == 0 )
{
/* Not allowed; TODO: emit an error */
}
else if (svg_css_scanner_would_recurse (scanner, file))
{
char *path = g_file_get_path (file);
gtk_css_parser_error (scanner->parser,
GTK_CSS_PARSER_ERROR_IMPORT,
gtk_css_parser_get_block_location (scanner->parser),
gtk_css_parser_get_end_location (scanner->parser),
"Loading '%s' would recurse" ,
path);
g_free (path);
}
else
{
GError *load_error = NULL;
GBytes *bytes = g_file_load_bytes (file, NULL, NULL, &load_error);
if (bytes == NULL)
{
gtk_css_parser_error (scanner->parser,
GTK_CSS_PARSER_ERROR_IMPORT,
gtk_css_parser_get_block_location (scanner->parser),
gtk_css_parser_get_end_location (scanner->parser),
"Failed to import: %s" ,
load_error->message);
g_error_free (load_error);
}
else
{
load_internal (scanner->data, scanner, file, bytes, NULL);
g_bytes_unref (bytes);
}
}
g_clear_object (&file);
return TRUE ;
}
static void parse_statement (SvgCssScanner *scanner);
static gboolean
parse_media_block (SvgCssScanner *scanner)
{
SvgCssMediaCondition *condition = NULL;
SvgCssMediaBlock *media;
if (!gtk_css_parser_try_at_keyword (scanner->parser, "media" ))
return FALSE ;
if (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_OPEN_CURLY))
{
condition = parse_media_query (scanner->parser);
if (condition == NULL)
return FALSE ;
}
if (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_OPEN_CURLY))
{
svg_css_media_condition_free (condition);
gtk_css_parser_error_syntax (scanner->parser, "Expected '{' after @media query" );
return FALSE ;
}
media = g_new0 (SvgCssMediaBlock, 1 );
media->condition = condition;
media->next = scanner->media;
/* GtkSvg takes ownership of the media blocks. They are evaluated when
* we apply styles .
*/
scanner->data->svg->media = g_list_append (scanner->data->svg->media, media);
scanner->media = media;
gtk_css_parser_start_block (scanner->parser);
while (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_CLOSE_CURLY) &&
!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
parse_statement (scanner);
gtk_css_parser_end_block (scanner->parser);
scanner->media = scanner->media->next;
return TRUE ;
}
static void
parse_at_keyword (SvgCssScanner *scanner)
{
if (parse_media_block (scanner))
return ;
gtk_css_parser_start_semicolon_block (scanner->parser, GTK_CSS_TOKEN_OPEN_CURLY);
if (!parse_import (scanner))
gtk_css_parser_error_syntax (scanner->parser, "Unknown @ rule" );
gtk_css_parser_end_block (scanner->parser);
}
static void
clear_selectors (SvgCssSelectors *selectors)
{
for (int i = 0 ; i < svg_css_selectors_get_size (selectors); i++)
_gtk_css_selector_free (svg_css_selectors_get (selectors, i));
}
static void
parse_selector_list (SvgCssScanner *scanner,
SvgCssSelectors *selectors)
{
do
{
GtkCssSelector *selector = _gtk_css_selector_parse (scanner->parser);
if (selector == NULL)
{
clear_selectors (selectors);
svg_css_selectors_clear (selectors);
return ;
}
svg_css_selectors_append (selectors, selector);
}
while (gtk_css_parser_try_token (scanner->parser, GTK_CSS_TOKEN_COMMA));
}
static gboolean
lookup_property (const char *name,
SvgProperty *result,
gboolean *marker)
{
if (strcmp (name, "marker" ) == 0 )
{
*marker = TRUE ;
*result = SVG_PROPERTY_MARKER_START;
return TRUE ;
}
*marker = FALSE ;
return svg_property_lookup_for_css (name, result);
}
static void
parse_declaration (SvgCssScanner *scanner,
PropertyValue *p,
gboolean *marker)
{
char *name;
p->value = NULL;
p->important = FALSE ;
*marker = FALSE ;
/* advance the location over whitespace */
gtk_css_parser_get_token (scanner->parser);
gtk_css_parser_start_semicolon_block (scanner->parser, GTK_CSS_TOKEN_EOF);
if (gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
{
gtk_css_parser_warn_syntax (scanner->parser, "Empty declaration" );
gtk_css_parser_end_block (scanner->parser);
return ;
}
name = gtk_css_parser_consume_ident (scanner->parser);
if (name == NULL)
goto out;
if (lookup_property (name, &p->attr, marker))
{
if (!gtk_css_parser_try_token (scanner->parser, GTK_CSS_TOKEN_COLON))
{
gtk_css_parser_error_syntax (scanner->parser, "Expected ':'" );
goto out;
}
p->value = svg_property_parse_css (p->attr, scanner->parser);
if (p->value == NULL)
goto out;
if (gtk_css_parser_try_delim (scanner->parser, '!' ) &&
gtk_css_parser_try_ident (scanner->parser, "important" ))
{
p->important = TRUE ;
}
if (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
{
gtk_css_parser_error_syntax (scanner->parser, "Junk at end of value for %s" , name);
g_clear_pointer (&p->value, svg_value_unref);
goto out;
}
}
else
{
gtk_css_parser_error_value (scanner->parser, "No property named \" %s\"" , name);
}
out:
g_free (name);
gtk_css_parser_end_block (scanner->parser);
}
static void
parse_declarations_into_ruleset (SvgCssScanner *scanner,
SvgCssRuleset *r)
{
while (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
{
PropertyValue p = { 0 , };
gboolean marker;
parse_declaration (scanner, &p, &marker);
if (p.value)
{
svg_css_ruleset_add (r, p.attr, svg_value_ref (p.value), p.important);
if (marker)
{
svg_css_ruleset_add (r, SVG_PROPERTY_MARKER_MID, svg_value_ref (p.value), p.important);
svg_css_ruleset_add (r, SVG_PROPERTY_MARKER_END, svg_value_ref (p.value), p.important);
}
svg_value_unref (p.value);
}
}
}
static void
parse_declarations_into_array (SvgCssScanner *scanner,
GArray *styles)
{
while (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
{
PropertyValue p = { 0 , };
gboolean marker;
parse_declaration (scanner, &p, &marker);
if (p.value)
{
g_array_append_val (styles, p);
if (marker)
{
p.attr = SVG_PROPERTY_MARKER_MID;
svg_value_ref (p.value);
g_array_append_val (styles, p);
p.attr = SVG_PROPERTY_MARKER_END;
svg_value_ref (p.value);
g_array_append_val (styles, p);
}
}
}
}
static void
parse_style_into_properties (GArray *styles,
const char *style,
ParserData *data)
{
GBytes *bytes;
SvgCssScanner *scanner;
if (style == NULL)
return ;
bytes = g_bytes_new_static (style, strlen (style));
scanner = svg_css_scanner_new (data, NULL, NULL, bytes);
parse_declarations_into_array (scanner, styles);
svg_css_scanner_destroy (scanner);
g_bytes_unref (bytes);
}
static void
commit_ruleset (GArray *rulesets,
SvgCssSelectors *selectors,
SvgCssRuleset *ruleset)
{
if (ruleset->styles == NULL)
return ;
for (unsigned int i = 0 ; i < svg_css_selectors_get_size (selectors); i++)
{
SvgCssRuleset *new ;
g_array_set_size (rulesets, rulesets->len + 1 );
new = &g_array_index (rulesets, SvgCssRuleset, rulesets->len - 1 );
svg_css_ruleset_init_copy (new , ruleset, gtk_css_selector_copy (svg_css_selectors_get (selectors, i)));
}
}
static void
parse_ruleset (SvgCssScanner *scanner)
{
SvgCssSelectors selectors;
SvgCssRuleset ruleset = { 0 , };
svg_css_selectors_init (&selectors);
parse_selector_list (scanner, &selectors);
if (svg_css_selectors_get_size (&selectors) == 0 )
{
gtk_css_parser_skip_until (scanner->parser, GTK_CSS_TOKEN_OPEN_CURLY);
gtk_css_parser_skip (scanner->parser);
goto out;
}
if (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_OPEN_CURLY))
{
gtk_css_parser_error_syntax (scanner->parser, "Expected '{' after selectors" );
clear_selectors (&selectors);
gtk_css_parser_skip_until (scanner->parser, GTK_CSS_TOKEN_OPEN_CURLY);
gtk_css_parser_skip (scanner->parser);
goto out;
}
gtk_css_parser_start_block (scanner->parser);
parse_declarations_into_ruleset (scanner, &ruleset);
gtk_css_parser_end_block (scanner->parser);
ruleset.media = scanner->media;
if (scanner->data->load_user_style)
commit_ruleset (scanner->data->svg->user_styles, &selectors, &ruleset);
else
commit_ruleset (scanner->data->svg->author_styles, &selectors, &ruleset);
svg_css_ruleset_clear (&ruleset);
clear_selectors (&selectors);
out:
svg_css_selectors_clear (&selectors);
}
static void
parse_statement (SvgCssScanner *scanner)
{
if (gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_AT_KEYWORD))
parse_at_keyword (scanner);
else
parse_ruleset (scanner);
}
static void
parse_stylesheet (SvgCssScanner *scanner)
{
while (!gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_EOF))
{
if (gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_CDO) ||
gtk_css_parser_has_token (scanner->parser, GTK_CSS_TOKEN_CDC))
{
gtk_css_parser_consume_token (scanner->parser);
continue ;
}
parse_statement (scanner);
}
}
static void
load_internal (ParserData *data,
SvgCssScanner *parent,
GFile *file,
GBytes *bytes,
const char *media_attr)
{
SvgCssScanner *scanner;
SvgCssMediaCondition *condition = NULL;
scanner = svg_css_scanner_new (data, parent, file, bytes);
if (media_attr)
{
GtkCssParser *parser;
parser = parser_new_for_string (media_attr);
condition = parse_media_condition (parser);
if (!condition)
{
gtk_css_parser_error_syntax (parser, "Failed to parse media attribute" );
}
else
{
gtk_css_parser_skip_whitespace (parser);
if (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
{
gtk_css_parser_error_syntax (parser, "Junk at end of media attribute" );
g_clear_pointer (&condition, svg_css_media_condition_free);
}
}
gtk_css_parser_unref (parser);
}
if (condition)
{
SvgCssMediaBlock *media;
media = g_new0 (SvgCssMediaBlock, 1 );
media->condition = condition;
media->next = scanner->media;
scanner->data->svg->media = g_list_append (scanner->data->svg->media, media);
scanner->media = media;
}
parse_stylesheet (scanner);
svg_css_scanner_destroy (scanner);
}
static void
load_styles_for_shape (SvgElement *shape,
ParserData *data)
{
for (unsigned int i = 0 ; i < shape->styles->len; i++)
{
StyleElt *elt = g_ptr_array_index (shape->styles, i);
data->text.start = elt->location;
load_internal (data, NULL, NULL, elt->content, elt->media);
}
if (svg_element_type_is_container (svg_element_get_type (shape)))
{
for (unsigned int i = 0 ; i < shape->shapes->len; i++)
{
SvgElement *sh = g_ptr_array_index (shape->shapes, i);
load_styles_for_shape (sh, data);
}
}
}
static void
load_author_styles (ParserData *data)
{
data->current_shape = NULL;
data->load_user_style = FALSE ;
load_styles_for_shape (data->svg->content, data);
g_array_sort (data->svg->author_styles, svg_css_compare_rule);
resolve_refs_in_rulesets (data->svg->author_styles, NULL, data);
}
static void
load_user_styles (ParserData *data)
{
if (data->svg->stylesheet == NULL)
return ;
data->current_shape = NULL;
data->load_user_style = TRUE ;
load_internal (data, NULL, NULL, data->svg->stylesheet, NULL);
g_array_sort (data->svg->user_styles, svg_css_compare_rule);
resolve_refs_in_rulesets (data->svg->user_styles, NULL, data);
}
static void
load_inline_style_here (SvgElement *element,
gpointer user_data)
{
ParserData *data = user_data;
const char *style;
data->current_shape = element;
style = svg_element_get_inline_style (element, &data->text.start);
data->text.start.line_chars += strlen ("style='" );
data->text.start.bytes += strlen ("style='" );
parse_style_into_properties (element->inline_styles, style, data);
if (svg_element_type_is_gradient (element->type))
{
for (unsigned int idx = 0 ; idx < element->color_stops->len; idx++)
{
SvgColorStop *stop = g_ptr_array_index (element->color_stops, idx);
style = svg_color_stop_get_inline_style (stop);
svg_color_stop_get_origin (stop, &data->text.start);
data->text.start.line_chars += strlen ("style='" );
data->text.start.bytes += strlen ("style='" );
parse_style_into_properties (svg_color_stop_get_inline_styles (stop),
style,
data);
}
}
if (svg_element_type_is_filter (element->type))
{
for (unsigned int idx = 0 ; idx < element->filters->len; idx++)
{
SvgFilter *f = g_ptr_array_index (element->filters, idx);
style = svg_filter_get_inline_style (f);
svg_filter_get_origin (f, &data->text.start);
data->text.start.line_chars += strlen ("style='" );
data->text.start.bytes += strlen ("style='" );
parse_style_into_properties (svg_filter_get_inline_styles (f),
style,
data);
}
}
}
static void
load_inline_styles (ParserData *data)
{
svg_element_foreach (data->svg->content, load_inline_style_here, data);
}
static void
resolve_refs_here (SvgElement *element,
gpointer user_data)
{
ParserData *data = user_data;
resolve_refs_in_array (element->specified, element, data);
resolve_refs_in_array (element->inline_styles, element, data);
/* I don't think refs are possible in styles of color stops or filters */
resolve_attach_ref (element, data);
resolve_filter_image_refs (element, data);
}
static void
resolve_refs_in_shapes (ParserData *data)
{
svg_element_foreach (data->svg->content, resolve_refs_here, data);
}
#if 0
static gboolean
ruleset_has_important (SvgCssRuleset *r,
gboolean important)
{
for (unsigned int j = 0 ; j < r->n_styles; j++)
{
PropertyValue *p = &r->styles[j];
if (p->important == important)
return TRUE ;
}
return FALSE ;
}
static void
dump_rulesets (GString *s,
GArray *rulesets,
gboolean important)
{
for (unsigned int i = 0 ; i < rulesets->len; i++)
{
SvgCssRuleset *r = &g_array_index (rulesets, SvgCssRuleset, i);
if (!ruleset_has_important (r, important))
continue ;
_gtk_css_selector_print (r->selector, s);
g_string_append (s, " {\n" );
for (unsigned int j = 0 ; j < r->n_styles; j++)
{
PropertyValue *p = &r->styles[j];
if (p->important != important)
continue ;
g_string_append (s, " " );
g_string_append (s, svg_property_get_name (p->attr));
g_string_append (s, ": " );
svg_value_print (p->value, s);
g_string_append (s, ";" );
g_string_append (s, "\n" );
}
g_string_append (s, "}\n" );
}
}
static void
dump_styles (ParserData *data)
{
GString *s = g_string_new ("" );
if (data->svg->user_styles->len > 0 )
{
g_string_append (s, "/* user styles */\n");
dump_rulesets (s, data->svg->user_styles, FALSE );
}
if (data->svg->author_styles->len > 0 )
{
g_string_append (s, "/* author styles */\n");
dump_rulesets (s, data->svg->author_styles, FALSE );
g_string_append (s, "/* important author styles */\n");
dump_rulesets (s, data->svg->author_styles, TRUE );
}
if (data->svg->user_styles->len > 0 )
{
g_string_append (s, "/* important user styles */\n");
dump_rulesets (s, data->svg->user_styles, TRUE );
}
g_print ("%s" , s->str);
g_string_free (s, TRUE );
}
#endif
static void
shape_set_base_value (SvgElement *shape,
SvgProperty attr,
unsigned int idx,
SvgValue *value,
gboolean important)
{
if (idx == 0 )
{
svg_element_set_base_value (shape, attr, value, important);
}
else if (FIRST_STOP_PROPERTY <= attr && attr <= LAST_STOP_PROPERTY)
{
SvgColorStop *stop;
g_assert (svg_element_type_is_gradient (svg_element_get_type (shape)));
g_assert (idx <= shape->color_stops->len);
stop = g_ptr_array_index (shape->color_stops, idx - 1 );
svg_color_stop_set_base_value (stop, attr, value, important);
}
else if (FIRST_FILTER_PROPERTY <= attr && attr <= LAST_FILTER_PROPERTY)
{
SvgFilter *f;
g_assert (svg_element_type_is_filter (svg_element_get_type (shape)));
g_assert (idx <= shape->filters->len);
f = g_ptr_array_index (shape->filters, idx - 1 );
svg_filter_set_base_value (f, attr, value, important);
}
else
g_assert_not_reached ();
}
static void
apply_ruleset_to_shape (SvgCssRuleset *r,
gboolean important,
SvgElement *shape,
unsigned int idx,
GtkBitmask **set)
{
for (unsigned int j = 0 ; j < r->n_styles; j++)
{
PropertyValue *p = &r->styles[j];
if (_gtk_bitmask_get (*set, p->attr))
continue ;
if (important != p->important)
continue ;
if (svg_property_applies_to (p->attr, svg_element_get_type (shape)))
shape_set_base_value (shape, p->attr, idx, p->value, important);
*set = _gtk_bitmask_set (*set, p->attr, TRUE );
}
}
static gboolean
media_condition_is_true (SvgCssRuleset *r)
{
return r->media == NULL || r->media->value;
}
static void
apply_styles_here (SvgElement *shape,
unsigned int idx,
GtkSvg *svg)
{
GtkCssNode *node;
GtkBitmask *set;
GArray *inline_styles = NULL;
if (idx > 0 )
{
if (svg_element_type_is_gradient (svg_element_get_type (shape)))
{
SvgColorStop *stop = g_ptr_array_index (shape->color_stops, idx - 1 );
node = svg_color_stop_get_css_node (stop);
inline_styles = svg_color_stop_get_inline_styles (stop);
}
else
{
SvgFilter *ff = g_ptr_array_index (shape->filters, idx - 1 );
node = svg_filter_get_css_node (ff);
inline_styles = svg_filter_get_inline_styles (ff);
}
}
else
{
node = svg_element_get_css_node (shape);
inline_styles = shape->inline_styles;
}
if (node == NULL)
return ;
/* Our strategy to compute base values is the following:
* - unset everything
* - apply presentation attributes and style values , in priority order
* - what is left unset at compute time will be inherited or set to initial values
*/
/* unset */
if (idx == 0 )
{
for (unsigned int i = FIRST_SVG_PROPERTY; i <= LAST_SVG_PROPERTY; i++)
shape_set_base_value (shape, i, idx, svg_unset_new (), FALSE );
}
else if (svg_element_type_is_gradient (shape->type))
{
for (unsigned int i = FIRST_STOP_PROPERTY; i <= LAST_STOP_PROPERTY; i++)
shape_set_base_value (shape, i, idx, svg_unset_new (), FALSE );
}
else if (svg_element_type_is_filter (shape->type))
{
for (unsigned int i = FIRST_FILTER_PROPERTY; i <= LAST_FILTER_PROPERTY; i++)
shape_set_base_value (shape, i, idx, svg_unset_new (), FALSE );
}
else
g_assert_not_reached ();
/* Work from the highest priority downwards,
* and keep a bitmask of attrs we ' ve already set .
*/
set = _gtk_bitmask_new ();
/* important user styles */
for (unsigned int i = 0 ; i < svg->user_styles->len; i++)
{
SvgCssRuleset *r = &g_array_index (svg->user_styles, SvgCssRuleset, i);
if (gtk_css_selector_matches (r->selector, node) && media_condition_is_true (r))
apply_ruleset_to_shape (r, TRUE , shape, idx, &set);
}
/* important inline styles */
if (inline_styles)
{
for (unsigned int i = 0 ; i < inline_styles->len; i++)
{
PropertyValue *p = &g_array_index (inline_styles, PropertyValue, i);
if (p->important && !_gtk_bitmask_get (set, p->attr))
{
shape_set_base_value (shape, p->attr, idx, p->value, TRUE );
set = _gtk_bitmask_set (set, p->attr, TRUE );
}
}
}
/* important author styles */
for (unsigned int i = 0 ; i < svg->author_styles->len; i++)
{
SvgCssRuleset *r = &g_array_index (svg->author_styles, SvgCssRuleset, i);
if (gtk_css_selector_matches (r->selector, node) && media_condition_is_true (r))
apply_ruleset_to_shape (r, TRUE , shape, idx, &set);
}
/* inline styles */
if (inline_styles)
{
for (unsigned int i = 0 ; i < inline_styles->len; i++)
{
PropertyValue *p = &g_array_index (inline_styles, PropertyValue, i);
if (!p->important && !_gtk_bitmask_get (set, p->attr))
{
shape_set_base_value (shape, p->attr, idx, p->value, FALSE );
set = _gtk_bitmask_set (set, p->attr, TRUE );
}
}
}
/* author styles */
for (unsigned int i = 0 ; i < svg->author_styles->len; i++)
{
SvgCssRuleset *r = &g_array_index (svg->author_styles, SvgCssRuleset, i);
if (gtk_css_selector_matches (r->selector, node) && media_condition_is_true (r))
apply_ruleset_to_shape (r, FALSE , shape, idx, &set);
}
/* user styles */
for (unsigned int i = 0 ; i < svg->user_styles->len; i++)
{
SvgCssRuleset *r = &g_array_index (svg->user_styles, SvgCssRuleset, i);
if (gtk_css_selector_matches (r->selector, node) && media_condition_is_true (r))
apply_ruleset_to_shape (r, FALSE , shape, idx, &set);
}
/* presentation attributes */
if (idx == 0 )
{
for (unsigned int i = 0 ; i < shape->specified->len; i++)
{
PropertyValue *p = &g_array_index (shape->specified, PropertyValue, i);
if (!_gtk_bitmask_get (set, p->attr))
svg_element_set_base_value (shape, p->attr, p->value, FALSE );
}
}
else if (svg_element_type_is_gradient (shape->type))
{
SvgColorStop *stop = g_ptr_array_index (shape->color_stops, idx - 1 );
for (unsigned int attr = FIRST_STOP_PROPERTY; attr <= LAST_STOP_PROPERTY; attr++)
{
if (!_gtk_bitmask_get (set, attr))
{
SvgValue *value = svg_color_stop_get_specified_value (stop, attr);
if (value)
svg_color_stop_set_base_value (stop, attr, value, FALSE );
}
}
}
else if (svg_element_type_is_filter (shape->type))
{
SvgFilter *filter = g_ptr_array_index (shape->filters, idx - 1 );
SvgFilterType filter_type = svg_filter_get_type (filter);
unsigned int n_attrs = svg_filter_type_get_n_attrs (filter_type);
for (unsigned int i = 0 ; i < n_attrs; i++)
{
SvgProperty attr = svg_filter_type_get_property (filter_type, i);
if (!_gtk_bitmask_get (set, attr))
{
SvgValue *value = svg_filter_get_specified_value (filter, attr);
if (value)
svg_filter_set_base_value (filter, attr, value, FALSE );
}
}
}
else
g_assert_not_reached ();
_gtk_bitmask_free (set);
}
void
apply_styles_to_shape (SvgElement *shape,
GtkSvg *svg)
{
SvgValue *color, *paint;
apply_styles_here (shape, 0 , svg);
if (shape->shapes)
{
for (unsigned int i = 0 ; i < shape->shapes->len; i++)
{
SvgElement *sh = g_ptr_array_index (shape->shapes, i);
apply_styles_to_shape (sh, svg);
}
}
if (svg_element_type_is_gradient (svg_element_get_type (shape)))
{
for (unsigned int idx = 0 ; idx < shape->color_stops->len; idx++)
apply_styles_here (shape, idx + 1 , svg);
}
if (svg_element_type_is_filter (svg_element_get_type (shape)))
{
for (unsigned int idx = 0 ; idx < shape->filters->len; idx++)
apply_styles_here (shape, idx + 1 , svg);
}
/* Apply traditional symbolic heuristics *after*
* CSS and styles , so that these take precedence .
*/
if (svg->gpa_version == 0 &&
((svg->features & GTK_SVG_TRADITIONAL_SYMBOLIC) != 0 ))
{
GStrv classes = svg_element_get_classes (shape);
SvgValue *value;
gboolean has_stroke;
if (!classes)
value = svg_paint_new_symbolic (GTK_SYMBOLIC_COLOR_FOREGROUND);
else if (g_strv_has (classes, "transparent-fill" ))
value = svg_paint_new_none ();
else if (g_strv_has (classes, "foreground-fill" ))
value = svg_paint_new_symbolic (GTK_SYMBOLIC_COLOR_FOREGROUND);
else if (g_strv_has (classes, "success" ) ||
g_strv_has (classes, "success-fill" ))
value = svg_paint_new_symbolic (GTK_SYMBOLIC_COLOR_SUCCESS);
else if (g_strv_has (classes, "warning" ) ||
g_strv_has (classes, "warning-fill" ))
value = svg_paint_new_symbolic (GTK_SYMBOLIC_COLOR_WARNING);
else if (g_strv_has (classes, "error" ) ||
g_strv_has (classes, "error-fill" ))
value = svg_paint_new_symbolic (GTK_SYMBOLIC_COLOR_ERROR);
else
value = svg_paint_new_symbolic (GTK_SYMBOLIC_COLOR_FOREGROUND);
svg_element_set_base_value (shape, SVG_PROPERTY_FILL, value, FALSE );
svg_value_unref (value);
if (!classes)
value = svg_paint_new_none ();
else if (g_strv_has (classes, "success-stroke" ))
value = svg_paint_new_symbolic (GTK_SYMBOLIC_COLOR_SUCCESS);
else if (g_strv_has (classes, "warning-stroke" ))
value = svg_paint_new_symbolic (GTK_SYMBOLIC_COLOR_WARNING);
else if (g_strv_has (classes, "error-stroke" ))
value = svg_paint_new_symbolic (GTK_SYMBOLIC_COLOR_ERROR);
else if (g_strv_has (classes, "foreground-stroke" ))
value = svg_paint_new_symbolic (GTK_SYMBOLIC_COLOR_FOREGROUND);
else
value = svg_paint_new_none ();
has_stroke = !svg_value_equal (value, svg_paint_new_none ());
svg_element_set_base_value (shape, SVG_PROPERTY_STROKE, value, FALSE );
svg_value_unref (value);
if (has_stroke)
{
value = svg_number_new (2 );
svg_element_set_base_value (shape, SVG_PROPERTY_STROKE_WIDTH, value, FALSE );
svg_value_unref (value);
value = svg_linejoin_new (GSK_LINE_JOIN_ROUND);
svg_element_set_base_value (shape, SVG_PROPERTY_STROKE_LINEJOIN, value, FALSE );
svg_value_unref (value);
value = svg_linecap_new (GSK_LINE_CAP_ROUND);
svg_element_set_base_value (shape, SVG_PROPERTY_STROKE_LINECAP, value, FALSE );
svg_value_unref (value);
}
}
/* gpa attrs are supposed to have higher priority than style and CSS,
* so re - set them here
*/
if (svg->gpa_version > 0 )
{
if (svg_element_get_gpa_fill (shape))
svg_element_set_base_value (shape, SVG_PROPERTY_FILL, svg_element_get_gpa_fill (shape), FALSE );
if (svg_element_get_gpa_stroke (shape))
svg_element_set_base_value (shape, SVG_PROPERTY_STROKE, svg_element_get_gpa_stroke (shape), FALSE );
if (svg_element_get_gpa_width (shape))
svg_element_set_base_value (shape, SVG_PROPERTY_STROKE_WIDTH, svg_element_get_gpa_width (shape), FALSE );
}
color = svg_element_get_base_value (shape, SVG_PROPERTY_COLOR);
if (!svg_value_is_keyword (color))
{
if ((svg->features & GTK_SVG_EXTENSIONS) == 0 &&
svg_color_get_kind (color) == COLOR_SYMBOLIC)
{
SvgValue *value = svg_color_new_black ();
svg_element_set_base_value (shape, SVG_PROPERTY_COLOR, value, FALSE );
svg_value_unref (value);
color = svg_element_get_base_value (shape, SVG_PROPERTY_COLOR);
}
if (svg_color_get_kind (color) == COLOR_SYMBOLIC)
svg->used |= BIT (svg_color_get_symbolic (color) + 1 );
}
paint = svg_element_get_base_value (shape, SVG_PROPERTY_FILL);
if (svg_value_is_paint (paint))
{
GtkSymbolicColor symbolic;
if ((svg->features & GTK_SVG_EXTENSIONS) == 0 &&
svg_paint_get_kind (paint) == PAINT_SYMBOLIC)
{
SvgValue *value = svg_paint_new_black ();
svg_element_set_base_value (shape, SVG_PROPERTY_FILL, value, FALSE );
svg_value_unref (value);
paint = svg_element_get_base_value (shape, SVG_PROPERTY_FILL);
}
if (svg_paint_is_symbolic (paint, &symbolic))
svg->used |= BIT (symbolic + 1 );
}
paint = svg_element_get_base_value (shape, SVG_PROPERTY_STROKE);
if (svg_value_is_paint (paint))
{
GtkSymbolicColor symbolic;
if ((svg->features & GTK_SVG_EXTENSIONS) == 0 &&
svg_paint_get_kind (paint) == PAINT_SYMBOLIC)
{
SvgValue *value = svg_paint_new_black ();
svg_element_set_base_value (shape, SVG_PROPERTY_STROKE, value, FALSE );
svg_value_unref (value);
paint = svg_element_get_base_value (shape, SVG_PROPERTY_STROKE);
}
if (svg_paint_is_symbolic (paint, &symbolic))
svg->used |= BIT (symbolic + 1 );
if (svg_paint_get_kind (paint) != PAINT_NONE)
svg->used |= GTK_SVG_USES_STROKES;
}
}
/* }}} */
/* {{{ API */
/* Determine the intrinsic width, height, and aspect ratio from
* what we have .
*/
void
determine_size (GtkSvg *self)
{
SvgValue *vb;
double width = 0 ;
double height = 0 ;
double aspect_ratio = 0 ;
if (self->view && svg_element_is_specified (self->view, SVG_PROPERTY_VIEW_BOX))
vb = svg_element_get_specified_value (self->view, SVG_PROPERTY_VIEW_BOX);
else
vb = svg_element_get_specified_value (self->content, SVG_PROPERTY_VIEW_BOX);
if (vb)
{
graphene_rect_t rect;
svg_view_box_get (vb, &rect);
width = rect.size.width;
height = rect.size.height;
}
if (svg_element_is_specified (self->content, SVG_PROPERTY_WIDTH))
{
SvgValue *value = svg_element_get_specified_value (self->content, SVG_PROPERTY_WIDTH);
g_assert (value != NULL);
SvgUnit unit = svg_number_get_unit (value);
double v = svg_number_get (value, 100 );
if (is_absolute_length (unit))
width = absolute_length_to_px (v, unit);
else if (unit != SVG_UNIT_PERCENTAGE)
width = v;
}
if (svg_element_is_specified (self->content, SVG_PROPERTY_HEIGHT))
{
SvgValue *value = svg_element_get_specified_value (self->content, SVG_PROPERTY_HEIGHT);
g_assert (value != 0 );
SvgUnit unit = svg_number_get_unit (value);
double v = svg_number_get (value, 100 );
if (is_absolute_length (unit))
height = absolute_length_to_px (v, unit);
else if (unit != SVG_UNIT_PERCENTAGE)
height = v;
}
if (!vb &&
!svg_element_is_specified (self->content, SVG_PROPERTY_WIDTH) &&
!svg_element_is_specified (self->content, SVG_PROPERTY_HEIGHT))
{
/* arbitrary */
width = 200 ;
height = 200 ;
}
if (width > 0 && height > 0 )
{
aspect_ratio = width / height;
}
else if (vb)
{
graphene_rect_t rect;
svg_view_box_get (vb, &rect);
aspect_ratio = rect.size.width / rect.size.height;
}
else
{
aspect_ratio = 0 ;
}
if (self->width != width ||
self->height != height ||
self->aspect_ratio != aspect_ratio)
{
self->width = width;
self->height = height;
self->aspect_ratio = aspect_ratio;
gdk_paintable_invalidate_size (GDK_PAINTABLE (self));
}
}
void
gtk_svg_init_from_bytes (GtkSvg *self,
GBytes *bytes)
{
ParserData data;
GMarkupParseContext *context;
GMarkupParser parser = {
start_element_cb,
end_element_cb,
text_cb,
NULL,
error_cb,
};
g_clear_pointer (&self->content, svg_element_free);
if ((self->features & GTK_SVG_SYSTEM_RESOURCES) == 0 )
{
g_assert (self->fontmap == NULL);
ensure_fontmap (self);
}
data.svg = self;
data.current_shape = NULL;
data.shape_stack = NULL;
data.shapes = g_hash_table_new (g_str_hash, g_str_equal);
data.animations = g_hash_table_new (g_str_hash, g_str_equal);
data.current_animation = NULL;
data.pending_animations = g_ptr_array_new_with_free_func ((GDestroyNotify) svg_animation_free);
data.skip.to = NULL;
data.skip.reason = NULL;
data.text.text = g_string_new ("" );
data.text.collect = FALSE ;
data.num_loaded_elements = 0 ;
data.load_user_style = FALSE ;
data.lang = NULL;
data.media = NULL;
context = g_markup_parse_context_new (&parser,
G_MARKUP_PREFIX_ERROR_POSITION |
G_MARKUP_TREAT_CDATA_AS_TEXT,
&data, NULL);
if (!g_markup_parse_context_parse (context,
g_bytes_get_data (bytes, NULL),
g_bytes_get_size (bytes),
NULL) ||
!g_markup_parse_context_end_parse (context, NULL))
{
gtk_svg_clear_content (self);
g_slist_free (data.shape_stack);
g_clear_pointer (&data.skip.reason, g_free);
g_ptr_array_set_size (data.pending_animations, 0 );
}
else
{
g_assert (data.current_shape == NULL);
g_assert (data.shape_stack == NULL);
g_assert (data.current_animation == NULL);
g_assert (data.skip.to == NULL);
g_assert (data.skip.reason == NULL);
}
g_markup_parse_context_free (context);
if (self->content == NULL)
self->content = svg_element_new (NULL, SVG_ELEMENT_SVG);
load_user_styles (&data);
load_author_styles (&data);
load_inline_styles (&data);
gtk_svg_update_media (self);
apply_styles_to_shape (self->content, self);
resolve_refs_in_shapes (&data);
determine_size (self);
for (unsigned int i = 0 ; i < data.pending_animations->len; i++)
{
SvgAnimation *a = g_ptr_array_index (data.pending_animations, i);
SvgElement *shape;
g_assert (a->href != NULL);
g_assert (a->shape == NULL);
shape = g_hash_table_lookup (data.shapes, a->href);
if (!shape)
{
gtk_svg_invalid_reference (self,
"No shape with ID %s (resolving begin or end attribute)" ,
a->href);
svg_animation_drop_and_free (a);
}
else
{
a->shape = shape;
svg_element_add_animation (shape, a);
}
}
/* Faster than stealing the items out of the array one-by-one */
g_ptr_array_set_free_func (data.pending_animations, NULL);
g_ptr_array_set_size (data.pending_animations, 0 );
resolve_animation_refs (self->content, &data);
compute_update_order (self->content, self);
self->state_change_delay = timeline_get_state_change_delay (self->timeline);
g_hash_table_unref (data.shapes);
g_hash_table_unref (data.animations);
g_ptr_array_unref (data.pending_animations);
g_string_free (data.text.text, TRUE );
g_free (data.media);
if (self->gpa_version > 0 && (self->features & GTK_SVG_ANIMATIONS) == 0 )
apply_state (self, self->state);
}
void
gtk_svg_init_from_resource (GtkSvg *self,
const char *path)
{
GBytes *bytes;
bytes = g_resources_lookup_data (path, 0 , NULL);
if (bytes)
{
gtk_svg_init_from_bytes (self, bytes);
g_bytes_unref (bytes);
}
}
/* }}} */
/* vim:set foldmethod=marker: */
Messung V0.5 in Prozent C=99 H=91 G=94
¤ Dauer der Verarbeitung: 0.103 Sekunden
(vorverarbeitet am 2026-07-03)
¤
*© Formatika GbR, Deutschland