Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  gtksvgelement.c

  Sprache: C
 

/*
 * 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 "gtksvgelementinternal.h"

#include "gtksvgutilsprivate.h"
#include "gtksvgpathutilsprivate.h"
#include "gtksvgvalueprivate.h"
#include "gtksvgcolorstopprivate.h"
#include "gtksvgfilterprivate.h"
#include "gtksvghrefprivate.h"
#include "gtksvgtransformprivate.h"
#include "gtksvgenumprivate.h"
#include "gtksvgnumberprivate.h"
#include "gtksvgnumbersprivate.h"
#include "gtksvgpathprivate.h"
#include "gtksvgstringlistprivate.h"
#include "gtksvglanguageprivate.h"
#include "gtksvgkeywordprivate.h"
#include "gtksvganimationprivate.h"
#include "gtksvgpaintprivate.h"
#include "gtksvgfilterfunctionsprivate.h"
#include "gtksvgclipprivate.h"
#include "gtksvgmaskprivate.h"
#include "gtksvgfilterprivate.h"
#include "gtkmain.h"
#include "gsk/gskpathprivate.h"
#include "gsk/gskrectprivate.h"

#define CLONE_LIMIT 15000

static void
text_node_clear (TextNode *self)
{
  switch (self->type)
    {
    case TEXT_NODE_SHAPE:
      // shape node not owned
      break;
    case TEXT_NODE_CHARACTERS:
      g_assert (self->characters.layout == NULL);
      g_free (self->characters.text);
      break;
    default:
      g_assert_not_reached ();
    }
}

void
property_value_clear (PropertyValue *v)
{
  g_clear_pointer (&v->value, svg_value_unref);
}

static void
style_elt_free (gpointer p)
{
  StyleElt *e = (StyleElt *) p;

  g_bytes_unref (e->content);
  g_free (e->media);
  g_free (e);
}

static void
lang_string_clear (LangString *l)
{
  g_clear_pointer (&l->string, g_free);
  l->lang = NULL;
  l->type = NULL;
}

void
svg_element_free (SvgElement *element)
{
  g_clear_pointer (&element->id, g_free);
  g_clear_pointer (&element->style, g_free);
  g_clear_pointer (&element->classes, g_strfreev);
  g_clear_pointer (&element->lang_strings, g_array_unref);
  g_clear_pointer (&element->title, g_free);
  g_clear_pointer (&element->description, g_free);
  g_clear_object (&element->css_node);

  g_clear_pointer (&element->specified, g_array_unref);
  g_clear_pointer (&element->inline_styles, g_array_unref);

  for (SvgProperty attr = FIRST_SVG_PROPERTY; attr <= LAST_SVG_PROPERTY; attr++)
    {
      g_clear_pointer (&element->base[attr], svg_value_unref);
      g_clear_pointer (&element->current[attr], svg_value_unref);
    }

  g_clear_pointer (&element->styles, g_ptr_array_unref);
  g_clear_pointer (&element->shapes, g_ptr_array_unref);
  g_clear_pointer (&element->animations, g_ptr_array_unref);
  g_clear_pointer (&element->color_stops, g_ptr_array_unref);
  g_clear_pointer (&element->filters, g_ptr_array_unref);
  g_clear_pointer (&element->deps, g_ptr_array_unref);

  g_clear_pointer (&element->path, gsk_path_unref);
  g_clear_pointer (&element->measure, gsk_path_measure_unref);

  g_clear_pointer (&element->text, g_array_unref);

  if (element->type == SVG_ELEMENT_POLYLINE || element->type == SVG_ELEMENT_POLYGON)
    g_clear_pointer (&element->path_for.polyline.points, svg_value_unref);

  g_clear_pointer (&element->gpa.fill, svg_value_unref);
  g_clear_pointer (&element->gpa.stroke, svg_value_unref);
  g_clear_pointer (&element->gpa.width, svg_value_unref);
  g_free (element->gpa.attach.ref);

  _gtk_bitmask_free (element->attrs);

  g_free (element);
}

SvgElement *
svg_element_new (SvgElement     *parent,
                 SvgElementType  type)
{
  SvgElement *element = g_new0 (SvgElement, 1);

  element->parent = parent;
  element->type = type;

  element->lang_strings = array_new_with_clear_func (sizeof (LangString), (GDestroyNotify) lang_string_clear);

  element->attrs = _gtk_bitmask_new ();
  element->important = _gtk_bitmask_new ();

  element->specified = array_new_with_clear_func (sizeof (PropertyValue), (GDestroyNotify) property_value_clear);

  element->inline_styles = array_new_with_clear_func (sizeof (PropertyValue), (GDestroyNotify) property_value_clear);

  for (SvgProperty attr = FIRST_SVG_PROPERTY; attr <= LAST_SVG_PROPERTY; attr++)
    {
      element->base[attr] = svg_property_ref_initial_value (attr, type, parent != NULL);
      element->current[attr] = svg_value_ref (element->base[attr]);
    }

  if (svg_element_type_is_container (type) || element->type == SVG_ELEMENT_USE)
    element->shapes = g_ptr_array_new_with_free_func ((GDestroyNotify) svg_element_free);

  if (svg_element_type_is_gradient (type))
    element->color_stops = g_ptr_array_new_with_free_func ((GDestroyNotify) svg_color_stop_free);

  if (svg_element_type_is_filter (type))
    element->filters = g_ptr_array_new_with_free_func ((GDestroyNotify) svg_filter_free);

  if (svg_element_type_is_text (type))
    element->text = array_new_with_clear_func (sizeof (TextNode), (GDestroyNotify) text_node_clear);

  if (svg_element_type_is_path (type))
    {
      element->gpa.states = ALL_STATES;
    }

  element->styles = g_ptr_array_new_with_free_func (style_elt_free);

  element->css_node = gtk_css_node_new ();
  gtk_css_node_set_name (element->css_node, g_quark_from_static_string (svg_element_type_get_name (type)));
  if (parent)
    gtk_css_node_set_parent (element->css_node, parent->css_node);

  element->focusable = svg_element_get_initial_focusable (element);

  if (type == SVG_ELEMENT_LINK)
    gtk_css_node_set_state (element->css_node, GTK_STATE_FLAG_LINK);

  return element;
}

/* {{{ Conditional exclusion */

gboolean
svg_element_conditionally_excluded (SvgElement *element,
                                    GtkSvg     *svg)
{
  SvgValue *required_extensions = element->current[SVG_PROPERTY_REQUIRED_EXTENSIONS];
  SvgValue *system_language = element->current[SVG_PROPERTY_SYSTEM_LANGUAGE];
  PangoLanguage *lang;

  if (svg_value_equal (required_extensions, svg_string_list_new (NULL)) &&
      svg_value_equal (system_language, svg_language_new_list (0, NULL)))
    return FALSE;

  for (unsigned int i = 0; i < svg_string_list_get_length (required_extensions); i++)
    {
      if ((svg->features & GTK_SVG_EXTENSIONS) == 0)
        return TRUE;

      if (strcmp (svg_string_list_get (required_extensions, i), "http://www.gtk.org/grappa") != 0)
        return TRUE;
    }

  lang = gtk_get_default_language ();

  for (unsigned int i = 0; i < svg_language_get_length (system_language); i++)
    {
      const char *l1;
      const char *l2;

      if (lang == svg_language_get (system_language, i))
        return FALSE;

      l1 = pango_language_to_string (lang);
      l2 = pango_language_to_string (svg_language_get (system_language, i));

      for (unsigned int j = 0; l1[j]; j++)
        {
          if (l1[j] == '-')
            return FALSE;

          if (l1[j] != l2[j])
            break;
        }
    }

  return TRUE;
}

/* }}} */

static gboolean
auto_or_unset (SvgValue *value)
{
  return svg_value_is_auto (value) || svg_value_is_unset (value);
}

static void
resolve_rx (SvgElement            *element,
            const graphene_rect_t *viewport,
            gboolean               current,
            double                *rx,
            double                *ry)
{
  SvgValue **values;

  if (current)
    values = element->current;
  else
    values = element->base;

  if (element->type == SVG_ELEMENT_ELLIPSE)
    {
      if (auto_or_unset (values[SVG_PROPERTY_RX]))
        {
          if (auto_or_unset (values[SVG_PROPERTY_RY]))
            *ry = 0;
          else
            *ry = svg_number_get (values[SVG_PROPERTY_RY], viewport->size.height);
          *rx = *ry;
         }
       else
         {
           *rx = svg_number_get (values[SVG_PROPERTY_RX], viewport->size.width);
           if (svg_value_is_auto (values[SVG_PROPERTY_RY]))
             *ry = *rx;
           else
             *ry = svg_number_get (values[SVG_PROPERTY_RY], viewport->size.height);
         }
    }
  else if (element->type == SVG_ELEMENT_RECT)
    {
      if (auto_or_unset (values[SVG_PROPERTY_RX]) &&
          auto_or_unset (values[SVG_PROPERTY_RY]))
        {
          *rx = *ry = 0;
        }
      else if (auto_or_unset (values[SVG_PROPERTY_RY]))
        {
          *rx = svg_number_get (values[SVG_PROPERTY_RX], viewport->size.width);
          *ry = *rx;
        }
      else if (auto_or_unset (values[SVG_PROPERTY_RX]))
        {
          *ry = svg_number_get (values[SVG_PROPERTY_RY], viewport->size.height);
          *rx = *ry;
        }
      else
        {
          *rx = svg_number_get (values[SVG_PROPERTY_RX], viewport->size.width);
          *ry = svg_number_get (values[SVG_PROPERTY_RY], viewport->size.height);
        }

      double w = svg_number_get (values[SVG_PROPERTY_WIDTH], viewport->size.width);
      double h = svg_number_get (values[SVG_PROPERTY_HEIGHT], viewport->size.height);
      if (*rx > w / 2)
        *rx = w / 2;
      if (*ry > h / 2)
        *ry = h / 2;
    }
  else
    g_assert_not_reached ();
}

GskPath *
svg_element_get_path (SvgElement            *element,
                      const graphene_rect_t *viewport,
                      gboolean               current)
{
  GskPathBuilder *builder;
  SvgValue **values;

  if (current)
    values = element->current;
  else
    values = element->base;

  switch (element->type)
    {
    case SVG_ELEMENT_LINE:
      builder = gsk_path_builder_new ();
      if (svg_value_is_set (values[SVG_PROPERTY_X1]) &&
          svg_value_is_set (values[SVG_PROPERTY_Y1]) &&
          svg_value_is_set (values[SVG_PROPERTY_X2]) &&
          svg_value_is_set (values[SVG_PROPERTY_Y2]))
        {
          double x1 = svg_number_get (values[SVG_PROPERTY_X1], viewport->size.width);
          double y1 = svg_number_get (values[SVG_PROPERTY_Y1], viewport->size.height);
          double x2 = svg_number_get (values[SVG_PROPERTY_X2], viewport->size.width);
          double y2 = svg_number_get (values[SVG_PROPERTY_Y2], viewport->size.height);
          gsk_path_builder_move_to (builder, x1, y1);
          gsk_path_builder_line_to (builder, x2, y2);
        }
      return gsk_path_builder_free_to_path (builder);

    case SVG_ELEMENT_POLYLINE:
    case SVG_ELEMENT_POLYGON:
      builder = gsk_path_builder_new ();
      if (svg_value_is_set (values[SVG_PROPERTY_POINTS]))
        {
          SvgValue *v = values[SVG_PROPERTY_POINTS];
          if (svg_numbers_get_length (v) > 0)
            {
              g_assert (svg_numbers_get_unit (v, 0) != SVG_UNIT_PERCENTAGE);
              g_assert (svg_numbers_get_unit (v, 1) != SVG_UNIT_PERCENTAGE);

              gsk_path_builder_move_to (builder,
                                        svg_numbers_get (v, 01),
                                        svg_numbers_get (v, 11));

              for (unsigned int i = 2; i + 1 < svg_numbers_get_length (v); i += 2)
                {
                  g_assert (svg_numbers_get_unit (v, i) != SVG_UNIT_PERCENTAGE);
                  g_assert (svg_numbers_get_unit (v, i + 1) != SVG_UNIT_PERCENTAGE);

                  gsk_path_builder_line_to (builder,
                                            svg_numbers_get (v, i, 1),
                                            svg_numbers_get (v, i + 11));
                }
              if (element->type == SVG_ELEMENT_POLYGON)
                gsk_path_builder_close (builder);
            }
        }
      return gsk_path_builder_free_to_path (builder);

    case SVG_ELEMENT_CIRCLE:
      builder = gsk_path_builder_new ();
      if (svg_value_is_set (values[SVG_PROPERTY_CX]) &&
          svg_value_is_set (values[SVG_PROPERTY_CY]) &&
          svg_value_is_set (values[SVG_PROPERTY_R]))
        {
          double cx = svg_number_get (values[SVG_PROPERTY_CX], viewport->size.width);
          double cy = svg_number_get (values[SVG_PROPERTY_CY], viewport->size.height);
          double r = svg_number_get (values[SVG_PROPERTY_R], normalized_diagonal (viewport));
          gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (cx, cy), r);
        }
      return gsk_path_builder_free_to_path (builder);

    case SVG_ELEMENT_ELLIPSE:
      builder = gsk_path_builder_new ();
      if (svg_value_is_set (values[SVG_PROPERTY_CX]) &&
          svg_value_is_set (values[SVG_PROPERTY_CY]) &&
          svg_value_is_set (values[SVG_PROPERTY_RX]) &&
          svg_value_is_set (values[SVG_PROPERTY_RY]))
        {
          double cx = svg_number_get (values[SVG_PROPERTY_CX], viewport->size.width);
          double cy = svg_number_get (values[SVG_PROPERTY_CY], viewport->size.height);
          double rx, ry;
          resolve_rx (element, viewport, current, &rx, &ry);
          path_builder_add_ellipse (builder, cx, cy, rx, ry);
        }
      return gsk_path_builder_free_to_path (builder);

    case SVG_ELEMENT_RECT:
      builder = gsk_path_builder_new ();
      if (svg_value_is_set (values[SVG_PROPERTY_X]) &&
          svg_value_is_set (values[SVG_PROPERTY_Y]) &&
          svg_value_is_set (values[SVG_PROPERTY_WIDTH]) &&
          svg_value_is_set (values[SVG_PROPERTY_HEIGHT]) &&
          svg_value_is_set (values[SVG_PROPERTY_RX]) &&
          svg_value_is_set (values[SVG_PROPERTY_RY]))
        {
          double x = svg_number_get (values[SVG_PROPERTY_X], viewport->size.width);
          double y = svg_number_get (values[SVG_PROPERTY_Y], viewport->size.height);
          double width = svg_number_get (values[SVG_PROPERTY_WIDTH], viewport->size.width);
          double height = svg_number_get (values[SVG_PROPERTY_HEIGHT],viewport->size.height);
          double rx, ry;
          resolve_rx (element, viewport, current, &rx, &ry);
          if (rx == 0 || ry == 0)
            gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (x, y, width, height));
          else
            gsk_path_builder_add_rounded_rect (builder,
                                               &(GskRoundedRect) {
                                                 .bounds = GRAPHENE_RECT_INIT (x, y, width, height),
                                                 .corner = {
                                                   GRAPHENE_SIZE_INIT (rx, ry),
                                                   GRAPHENE_SIZE_INIT (rx, ry),
                                                   GRAPHENE_SIZE_INIT (rx, ry),
                                                   GRAPHENE_SIZE_INIT (rx, ry),
                                                 }
                                              });
        }
      return gsk_path_builder_free_to_path (builder);

    case SVG_ELEMENT_PATH:
      if (svg_value_is_set (values[SVG_PROPERTY_PATH]) &&
          svg_path_get_gsk (values[SVG_PROPERTY_PATH]))
        {
          return gsk_path_ref (svg_path_get_gsk (values[SVG_PROPERTY_PATH]));
        }
      else
        {
          builder = gsk_path_builder_new ();
          return gsk_path_builder_free_to_path (builder);
        }
      break;

    case SVG_ELEMENT_USE:
      {
        SvgElement *use_element = svg_href_get_shape (element->current[SVG_PROPERTY_HREF]);
        if (use_element)
          {
            return svg_element_get_path (use_element, viewport, current);
          }
        else
          {
            builder = gsk_path_builder_new ();
            return gsk_path_builder_free_to_path (builder);
          }
      }
    case SVG_ELEMENT_GROUP:
    case SVG_ELEMENT_CLIP_PATH:
    case SVG_ELEMENT_MASK:
    case SVG_ELEMENT_DEFS:
    case SVG_ELEMENT_LINEAR_GRADIENT:
    case SVG_ELEMENT_RADIAL_GRADIENT:
    case SVG_ELEMENT_PATTERN:
    case SVG_ELEMENT_MARKER:
    case SVG_ELEMENT_TEXT:
    case SVG_ELEMENT_TSPAN:
    case SVG_ELEMENT_SVG:
    case SVG_ELEMENT_IMAGE:
    case SVG_ELEMENT_FILTER:
    case SVG_ELEMENT_SYMBOL:
    case SVG_ELEMENT_SWITCH:
    case SVG_ELEMENT_LINK:
    case SVG_ELEMENT_VIEW:
      g_error ("Attempt to get the path of a %s", svg_element_type_get_name (element->type));
      break;
    default:
      g_assert_not_reached ();
    }
}

GskPath *
svg_element_get_current_path (SvgElement            *element,
                              const graphene_rect_t *viewport)
{
  if (element->path)
    {
      double rx, ry;

      switch (element->type)
        {
        case SVG_ELEMENT_LINE:
          if (element->path_for.line.x1 != svg_number_get (element->current[SVG_PROPERTY_X1], viewport->size.width) ||
              element->path_for.line.y1 != svg_number_get (element->current[SVG_PROPERTY_Y1], viewport->size.height) ||
              element->path_for.line.x2 != svg_number_get (element->current[SVG_PROPERTY_X2], viewport->size.width) ||
              element->path_for.line.y2 != svg_number_get (element->current[SVG_PROPERTY_Y2], viewport->size.height))
            {
              g_clear_pointer (&element->path, gsk_path_unref);
              g_clear_pointer (&element->measure, gsk_path_measure_unref);
            }
          break;

        case SVG_ELEMENT_POLYLINE:
        case SVG_ELEMENT_POLYGON:
          if (!svg_value_equal (element->path_for.polyline.points, element->current[SVG_PROPERTY_POINTS]))
            {
              g_clear_pointer (&element->path, gsk_path_unref);
              g_clear_pointer (&element->measure, gsk_path_measure_unref);
            }
          break;

        case SVG_ELEMENT_CIRCLE:
          if (element->path_for.circle.cx != svg_number_get (element->current[SVG_PROPERTY_CX], viewport->size.width) ||
              element->path_for.circle.cy != svg_number_get (element->current[SVG_PROPERTY_CY], viewport->size.height) ||
              element->path_for.circle.r != svg_number_get (element->current[SVG_PROPERTY_R], normalized_diagonal (viewport)))
            {
              g_clear_pointer (&element->path, gsk_path_unref);
              g_clear_pointer (&element->measure, gsk_path_measure_unref);
            }
          break;

        case SVG_ELEMENT_ELLIPSE:
          resolve_rx (element, viewport, TRUE, &rx, &ry);
          if (element->path_for.ellipse.cx != svg_number_get (element->current[SVG_PROPERTY_CX], viewport->size.width) ||
              element->path_for.ellipse.cy != svg_number_get (element->current[SVG_PROPERTY_CY], viewport->size.height) ||
              element->path_for.ellipse.rx != rx ||
              element->path_for.ellipse.ry != ry)
            {
              g_clear_pointer (&element->path, gsk_path_unref);
              g_clear_pointer (&element->measure, gsk_path_measure_unref);
            }
          break;

        case SVG_ELEMENT_RECT:
          resolve_rx (element, viewport, TRUE, &rx, &ry);
          if (element->path_for.rect.x != svg_number_get (element->current[SVG_PROPERTY_X], viewport->size.width) ||
              element->path_for.rect.y != svg_number_get (element->current[SVG_PROPERTY_Y], viewport->size.height) ||
              element->path_for.rect.w != svg_number_get (element->current[SVG_PROPERTY_WIDTH], viewport->size.width) ||
              element->path_for.rect.h != svg_number_get (element->current[SVG_PROPERTY_HEIGHT], viewport->size.height) ||
              element->path_for.rect.rx != rx ||
              element->path_for.rect.ry != ry)
            {
              g_clear_pointer (&element->path, gsk_path_unref);
              g_clear_pointer (&element->measure, gsk_path_measure_unref);
            }
          break;

        case SVG_ELEMENT_PATH:
          if (element->path != svg_path_get_gsk (element->current[SVG_PROPERTY_PATH]))
            {
              g_clear_pointer (&element->path, gsk_path_unref);
              g_clear_pointer (&element->measure, gsk_path_measure_unref);
            }
          break;

        case SVG_ELEMENT_USE:
          g_clear_pointer (&element->path, gsk_path_unref);
          break;

        case SVG_ELEMENT_GROUP:
        case SVG_ELEMENT_CLIP_PATH:
        case SVG_ELEMENT_MASK:
        case SVG_ELEMENT_DEFS:
        case SVG_ELEMENT_LINEAR_GRADIENT:
        case SVG_ELEMENT_RADIAL_GRADIENT:
        case SVG_ELEMENT_PATTERN:
        case SVG_ELEMENT_MARKER:
        case SVG_ELEMENT_TEXT:
        case SVG_ELEMENT_TSPAN:
        case SVG_ELEMENT_SVG:
        case SVG_ELEMENT_IMAGE:
        case SVG_ELEMENT_FILTER:
        case SVG_ELEMENT_SYMBOL:
        case SVG_ELEMENT_SWITCH:
        case SVG_ELEMENT_LINK:
        case SVG_ELEMENT_VIEW:
          g_error ("Attempt to get the path of a %s", svg_element_type_get_name (element->type));
          break;
        default:
          g_assert_not_reached ();
        }
    }

  if (!element->path)
    {
      double rx, ry;
      element->path = svg_element_get_path (element, viewport, TRUE);
      element->valid_bounds = FALSE;

      switch (element->type)
        {
        case SVG_ELEMENT_LINE:
          element->path_for.line.x1 = svg_number_get (element->current[SVG_PROPERTY_X1], viewport->size.width);
          element->path_for.line.y1 = svg_number_get (element->current[SVG_PROPERTY_Y1], viewport->size.height);
          element->path_for.line.x2 = svg_number_get (element->current[SVG_PROPERTY_X2], viewport->size.width);
          element->path_for.line.y2 = svg_number_get (element->current[SVG_PROPERTY_Y2], viewport->size.height);
          break;

        case SVG_ELEMENT_POLYLINE:
        case SVG_ELEMENT_POLYGON:
          g_clear_pointer (&element->path_for.polyline.points, svg_value_unref);
          element->path_for.polyline.points = svg_value_ref (element->current[SVG_PROPERTY_POINTS]);
          break;

        case SVG_ELEMENT_CIRCLE:
          element->path_for.circle.cx = svg_number_get (element->current[SVG_PROPERTY_CX], viewport->size.width);
          element->path_for.circle.cy = svg_number_get (element->current[SVG_PROPERTY_CY], viewport->size.height);
          element->path_for.circle.r = svg_number_get (element->current[SVG_PROPERTY_R], normalized_diagonal (viewport));
          break;

        case SVG_ELEMENT_ELLIPSE:
          resolve_rx (element, viewport, TRUE, &rx, &ry);
          element->path_for.ellipse.cx = svg_number_get (element->current[SVG_PROPERTY_CX], viewport->size.width);
          element->path_for.ellipse.cy = svg_number_get (element->current[SVG_PROPERTY_CY], viewport->size.height);
          element->path_for.ellipse.rx = rx;
          element->path_for.ellipse.ry = ry;
          break;

        case SVG_ELEMENT_RECT:
          resolve_rx (element, viewport, TRUE, &rx, &ry);
          element->path_for.rect.x = svg_number_get (element->current[SVG_PROPERTY_X], viewport->size.width);
          element->path_for.rect.y = svg_number_get (element->current[SVG_PROPERTY_Y], viewport->size.height);
          element->path_for.rect.w = svg_number_get (element->current[SVG_PROPERTY_WIDTH], viewport->size.width);
          element->path_for.rect.h = svg_number_get (element->current[SVG_PROPERTY_HEIGHT], viewport->size.height);
          element->path_for.rect.rx = rx;
          element->path_for.rect.ry = ry;
          break;

        case SVG_ELEMENT_PATH:
        case SVG_ELEMENT_USE:
          break;

        case SVG_ELEMENT_GROUP:
        case SVG_ELEMENT_CLIP_PATH:
        case SVG_ELEMENT_MASK:
        case SVG_ELEMENT_DEFS:
        case SVG_ELEMENT_LINEAR_GRADIENT:
        case SVG_ELEMENT_RADIAL_GRADIENT:
        case SVG_ELEMENT_PATTERN:
        case SVG_ELEMENT_MARKER:
        case SVG_ELEMENT_TEXT:
        case SVG_ELEMENT_TSPAN:
        case SVG_ELEMENT_SVG:
        case SVG_ELEMENT_IMAGE:
        case SVG_ELEMENT_FILTER:
        case SVG_ELEMENT_SYMBOL:
        case SVG_ELEMENT_SWITCH:
        case SVG_ELEMENT_LINK:
        case SVG_ELEMENT_VIEW:
        default:
          g_assert_not_reached ();
        }
    }

  return gsk_path_ref (element->path);
}

GskPathMeasure *
svg_element_get_current_measure (SvgElement            *element,
                                 const graphene_rect_t *viewport)
{
  if (!element->measure)
    {
      GskPath *path = svg_element_get_current_path (element, viewport);
      element->measure = gsk_path_measure_new (path);
      gsk_path_unref (path);
    }

  return gsk_path_measure_ref (element->measure);
}

gboolean
svg_element_get_current_bounds (SvgElement            *element,
                                const graphene_rect_t *viewport,
                                GtkSvg                *svg,
                                graphene_rect_t       *bounds)
{
  graphene_rect_t b;
  gboolean has_any;
  gboolean ret = FALSE;

  graphene_rect_init_from_rect (&b, graphene_rect_zero ());

  switch (element->type)
    {
    case SVG_ELEMENT_LINE:
    case SVG_ELEMENT_POLYLINE:
    case SVG_ELEMENT_POLYGON:
    case SVG_ELEMENT_RECT:
    case SVG_ELEMENT_CIRCLE:
    case SVG_ELEMENT_ELLIPSE:
    case SVG_ELEMENT_PATH:
      if (!element->valid_bounds)
        {
          GskPath *path = svg_element_get_current_path (element, viewport);
          if (!gsk_path_get_tight_bounds (element->path, &element->bounds))
            graphene_rect_init (&element->bounds, 0000);
          element->valid_bounds = TRUE;
          gsk_path_unref (path);
        }
      graphene_rect_init_from_rect (&b, &element->bounds);
      ret = TRUE;
      break;
    case SVG_ELEMENT_USE:
      {
        SvgElement *use_element = svg_href_get_shape (element->current[SVG_PROPERTY_HREF]);
        if (use_element)
          ret = svg_element_get_current_bounds (use_element, viewport, svg, &b);
        ret = TRUE;
      }
      break;
    case SVG_ELEMENT_GROUP:
    case SVG_ELEMENT_CLIP_PATH:
    case SVG_ELEMENT_MASK:
    case SVG_ELEMENT_PATTERN:
    case SVG_ELEMENT_MARKER:
    case SVG_ELEMENT_SVG:
    case SVG_ELEMENT_SYMBOL:
    case SVG_ELEMENT_SWITCH:
    case SVG_ELEMENT_LINK:
      has_any = FALSE;
      for (unsigned int i = 0; i < element->shapes->len; i++)
        {
          SvgElement *sh = g_ptr_array_index (element->shapes, i);
          graphene_rect_t b2;

          if (svg_enum_get (sh->current[SVG_PROPERTY_DISPLAY]) == DISPLAY_NONE)
            continue;

          if (svg_element_conditionally_excluded (sh, svg))
            continue;

          if (svg_element_get_current_bounds (sh, viewport, svg, &b2))
            {
              if (_gtk_bitmask_get (sh->attrs, SVG_PROPERTY_TRANSFORM))
                {
                   GskTransform *transform = svg_transform_get_gsk (sh->current[SVG_PROPERTY_TRANSFORM]);
                   gsk_transform_transform_bounds (transform, &b2, &b2);
                   gsk_transform_unref (transform);
                }

              if (!has_any)
                graphene_rect_init_from_rect (&b, &b2);
              else
                graphene_rect_union (&b, &b2, &b);
              has_any = TRUE;
            }

          if (element->type == SVG_ELEMENT_SWITCH)
            break;
        }
      if (!has_any)
        graphene_rect_init (&b, 0000);
      ret = TRUE;
      break;
    case SVG_ELEMENT_TEXT:
    case SVG_ELEMENT_TSPAN:
      {
        SvgElement *elt = element;
        while (svg_element_get_type (elt) != SVG_ELEMENT_TEXT)
          elt = svg_element_get_parent (elt);
        if (!elt->valid_bounds)
          g_critical ("No valid bounds for text");
        graphene_rect_init_from_rect (&b, &elt->bounds);
        ret = TRUE;
      }
      break;
    case SVG_ELEMENT_IMAGE:
      {
        double x, y, width, height;

        x = viewport->origin.x + svg_number_get (element->current[SVG_PROPERTY_X], viewport->size.width);
        y = viewport->origin.y + svg_number_get (element->current[SVG_PROPERTY_Y], viewport->size.height);
        width = svg_number_get (element->current[SVG_PROPERTY_WIDTH], viewport->size.width);
        height = svg_number_get (element->current[SVG_PROPERTY_HEIGHT], viewport->size.height);

        graphene_rect_init (&b, x, y, width, height);
        ret = TRUE;
      }
      break;
    case SVG_ELEMENT_DEFS:
    case SVG_ELEMENT_LINEAR_GRADIENT:
    case SVG_ELEMENT_RADIAL_GRADIENT:
    case SVG_ELEMENT_FILTER:
    case SVG_ELEMENT_VIEW:
      break;
    default:
      g_assert_not_reached ();
    }

  graphene_rect_init_from_rect (bounds, &b);

  return ret;
}

static double
width_apply_weight (double width,
                    double minwidth,
                    double maxwidth,
                    double weight)
{
  if (weight < 1)
    {
      g_assert_not_reached ();
    }
  else if (weight < 400)
    {
      double f = (400 - weight) / (400 - 1);
      return lerp (f, width, minwidth);
    }
  else if (weight == 400)
    {
      return width;
    }
  else if (weight <= 1000)
    {
      double f = (weight - 400) / (1000 - 400);
      return lerp (f, width, maxwidth);
    }
  else
    {
      g_assert_not_reached ();
    }
}

GskStroke *
svg_element_create_basic_stroke (SvgElement            *element,
                                 const graphene_rect_t *viewport,
                                 gboolean               allow_gpa,
                                 double                 weight)
{
  GskStroke *stroke;
  double width;

  width = svg_number_get (element->current[SVG_PROPERTY_STROKE_WIDTH], normalized_diagonal (viewport));

  if (allow_gpa)
    {
      double min, max;

      min = svg_number_get (element->current[SVG_PROPERTY_STROKE_MINWIDTH], width);
      max = svg_number_get (element->current[SVG_PROPERTY_STROKE_MAXWIDTH], width);

      width = width_apply_weight (width, min, max, weight);
    }

  stroke = gsk_stroke_new (width);

  gsk_stroke_set_line_cap (stroke, svg_enum_get (element->current[SVG_PROPERTY_STROKE_LINECAP]));
  gsk_stroke_set_line_join (stroke, svg_enum_get (element->current[SVG_PROPERTY_STROKE_LINEJOIN]));
  gsk_stroke_set_miter_limit (stroke, svg_number_get (element->current[SVG_PROPERTY_STROKE_MITERLIMIT], 1));

  return stroke;
}

gboolean
svg_element_get_current_stroke_bounds (SvgElement            *element,
                                       const graphene_rect_t *viewport,
                                       GtkSvg                *svg,
                                       graphene_rect_t       *bounds)
{
  graphene_rect_t b;
  gboolean has_any;
  gboolean ret = FALSE;

  graphene_rect_init_from_rect (&b, graphene_rect_zero ());

  switch (element->type)
    {
    case SVG_ELEMENT_LINE:
    case SVG_ELEMENT_POLYLINE:
    case SVG_ELEMENT_POLYGON:
    case SVG_ELEMENT_RECT:
    case SVG_ELEMENT_CIRCLE:
    case SVG_ELEMENT_ELLIPSE:
    case SVG_ELEMENT_PATH:
      {
        GskPath *path = svg_element_get_current_path (element, viewport);
        GskStroke *stroke = svg_element_create_basic_stroke (element, viewport, TRUE400);
        if (!gsk_path_get_stroke_bounds (element->path, stroke, &b))
          graphene_rect_init (&b, 0000);
        gsk_path_unref (path);
        gsk_stroke_free (stroke);
        ret = TRUE;
      }
      break;
    case SVG_ELEMENT_USE:
      {
        SvgElement *use_element = svg_href_get_shape (element->current[SVG_PROPERTY_HREF]);
        if (use_element)
          ret = svg_element_get_current_stroke_bounds (use_element, viewport, svg, &b);
        ret = TRUE;
      }
      break;
    case SVG_ELEMENT_GROUP:
    case SVG_ELEMENT_CLIP_PATH:
    case SVG_ELEMENT_MASK:
    case SVG_ELEMENT_PATTERN:
    case SVG_ELEMENT_MARKER:
    case SVG_ELEMENT_SVG:
    case SVG_ELEMENT_SYMBOL:
    case SVG_ELEMENT_SWITCH:
    case SVG_ELEMENT_LINK:
      has_any = FALSE;
      for (unsigned int i = 0; i < element->shapes->len; i++)
        {
          SvgElement *sh = g_ptr_array_index (element->shapes, i);
          graphene_rect_t b2;

          if (svg_enum_get (sh->current[SVG_PROPERTY_DISPLAY]) == DISPLAY_NONE)
            continue;

          if (svg_element_conditionally_excluded (sh, svg))
            continue;

          if (svg_element_get_current_stroke_bounds (sh, viewport, svg, &b2))
            {
              if (!has_any)
                graphene_rect_init_from_rect (&b, &b2);
              else
                graphene_rect_union (&b, &b2, &b);
              has_any = TRUE;
            }

          if (element->type == SVG_ELEMENT_SWITCH)
            break;
        }
      if (!has_any)
        graphene_rect_init (&b, 0000);
      ret = TRUE;
      break;
    case SVG_ELEMENT_TEXT:
    case SVG_ELEMENT_TSPAN:
      if (!element->valid_bounds)
        g_critical ("No valid bounds for text");
      graphene_rect_init_from_rect (&b, &element->bounds);
      break;
    case SVG_ELEMENT_IMAGE:
      {
        double x, y, width, height;

        x = viewport->origin.x + svg_number_get (element->current[SVG_PROPERTY_X], viewport->size.width);
        y = viewport->origin.y + svg_number_get (element->current[SVG_PROPERTY_Y], viewport->size.height);
        width = svg_number_get (element->current[SVG_PROPERTY_WIDTH], viewport->size.width);
        height = svg_number_get (element->current[SVG_PROPERTY_HEIGHT], viewport->size.height);

        graphene_rect_init (&b, x, y, width, height);
        ret = TRUE;
      }
      break;
    case SVG_ELEMENT_DEFS:
    case SVG_ELEMENT_LINEAR_GRADIENT:
    case SVG_ELEMENT_RADIAL_GRADIENT:
    case SVG_ELEMENT_FILTER:
    case SVG_ELEMENT_VIEW:
      break;
    default:
      g_assert_not_reached ();
    }

  graphene_rect_init_from_rect (bounds, &b);

  return ret;
}

void
svg_element_add_color_stop (SvgElement   *element,
                            SvgColorStop *stop)
{
  g_assert (gtk_css_node_get_parent (svg_color_stop_get_css_node (stop)) == element->css_node);
  g_assert (svg_element_type_is_gradient (element->type));
  g_ptr_array_add (element->color_stops, stop);
}

void
svg_element_add_filter (SvgElement *element,
                        SvgFilter  *filter)
{
  g_assert (gtk_css_node_get_parent (svg_filter_get_css_node (filter)) == element->css_node);
  g_assert (svg_element_type_is_filter (element->type));
  g_ptr_array_add (element->filters, filter);
}

void
svg_element_add_child (SvgElement *element,
                       SvgElement *child)
{
  g_assert (gtk_css_node_get_parent (svg_element_get_css_node (child)) == element->css_node);
  g_assert (svg_element_type_is_container (element->type));
  g_ptr_array_add (element->shapes, child);

  if (child->type == SVG_ELEMENT_TSPAN)
    {
      SvgElement *text_parent = NULL;

      if (svg_element_type_is_text (element->type))
        text_parent = element;
      else if (element->parent &&
               element->type == SVG_ELEMENT_LINK &&
               svg_element_type_is_text (element->parent->type))
        text_parent = element->parent;

      if (text_parent)
        {
          TextNode node = {
            .type = TEXT_NODE_SHAPE,
            .shape = { .shape = child }
          };
          g_array_append_val (text_parent->text, node);
        }
    }
}

unsigned int
svg_element_get_n_children (SvgElement *element)
{
  return element->shapes->len;
}

SvgElement *
svg_element_get_child (SvgElement   *element,
                       unsigned int  idx)
{
  return g_ptr_array_index (element->shapes, idx);
}

void
svg_element_add_animation (SvgElement   *shape,
                           SvgAnimation *animation)
{
  if (!shape->animations)
    shape->animations = g_ptr_array_new_with_free_func ((GDestroyNotify) svg_animation_free);
  g_ptr_array_add (shape->animations, animation);
}

/* What we call base value here is roughly the 'cascaded' value of CSS:
 * the result of applying styles. We carry an 'important' booleant to
 * indicate whether the applies styles was !important.
 * Values that are important have higher priority than animations.
 */

void
svg_element_set_base_value (SvgElement  *element,
                            SvgProperty  attr,
                            SvgValue    *value,
                            gboolean     important)
{
  g_clear_pointer (&element->base[attr], svg_value_unref);
  if (value)
    element->base[attr] = svg_value_ref (value);
  else
    element->base[attr] = svg_property_ref_initial_value (attr,
                                                          svg_element_get_type (element),
                                                          svg_element_get_parent (element) != NULL);
  element->important = _gtk_bitmask_set (element->important, attr, important);
}

void
svg_element_take_base_value (SvgElement  *element,
                             SvgProperty  attr,
                             SvgValue    *value)
{
  svg_element_set_base_value (element, attr, value, FALSE);
  if (value)
    svg_value_unref (value);
}

SvgValue *
svg_element_get_base_value (SvgElement  *element,
                            SvgProperty  attr)
{
  return element->base[attr];
}

void
svg_element_set_specified_value (SvgElement  *element,
                                 SvgProperty  attr,
                                 SvgValue    *value)
{
  unsigned int i;
  PropertyValue *v;

  if (_gtk_bitmask_get (element->attrs, attr))
    {
      for (i = 0; i < element->specified->len; i++)
        {
          v = &g_array_index (element->specified, PropertyValue, i);

          if (v->attr == attr)
            {
              g_clear_pointer (&v->value, svg_value_unref);
              break;
            }
        }
    }
  else
    i = element->specified->len;

  if (i == element->specified->len)
    {
      PropertyValue pv = { attr, NULL };
      g_array_append_val (element->specified, pv);
    }

  v = &g_array_index (element->specified, PropertyValue, i);
  g_assert (v->attr == attr);
  g_assert (v->value == NULL);
  if (value)
    v->value = svg_value_ref (value);

  element->attrs = _gtk_bitmask_set (element->attrs, attr, value != NULL);
}

void
svg_element_take_specified_value (SvgElement  *element,
                                  SvgProperty  attr,
                                  SvgValue    *value)
{
  svg_element_set_specified_value (element, attr, value);
  if (value)
    svg_value_unref (value);
}

SvgValue *
svg_element_get_specified_value (SvgElement  *element,
                                 SvgProperty  attr)
{
  for (unsigned int i = 0; i < element->specified->len; i++)
    {
      PropertyValue *v = &g_array_index (element->specified, PropertyValue, i);

      if (v->attr == attr)
        return v->value;
    }

  return NULL;
}

gboolean
svg_element_is_specified (SvgElement  *element,
                          SvgProperty  attr)
{
  return _gtk_bitmask_get (element->attrs, attr);
}

gboolean
svg_element_is_important (SvgElement  *element,
                          SvgProperty  attr)
{
  return _gtk_bitmask_get (element->important, attr);
}

void
svg_element_set_current_value (SvgElement  *element,
                               SvgProperty  attr,
                               SvgValue    *value)
{
  if (value)
    svg_value_ref (value);
  g_clear_pointer (&element->current[attr], svg_value_unref);
  element->current[attr] = value;
}

SvgValue *
svg_element_get_current_value (SvgElement  *element,
                               SvgProperty  attr)
{
  return element->current[attr];
}

SvgElementType
svg_element_get_type (SvgElement *element)
{
  return element->type;
}

void
svg_element_set_id (SvgElement *element,
                    const char *id)
{
  g_set_str (&element->id, id);
  gtk_css_node_set_id (element->css_node, g_quark_from_string (id));
}

const char *
svg_element_get_id (SvgElement *element)
{
  return element->id;
}

void
svg_element_set_inline_style (SvgElement           *element,
                              const char           *style,
                              const GtkSvgLocation *location)
{
  g_set_str (&element->style, style);
  element->style_loc = *location;
}

const char *
svg_element_get_inline_style (SvgElement     *element,
                              GtkSvgLocation *location)
{
  *location = element->style_loc;
  return element->style;
}

unsigned int
svg_element_get_n_styles (SvgElement *element)
{
  return element->styles->len;
}

const char *
svg_element_get_style (SvgElement   *element,
                       unsigned int  pos)
{
  StyleElt *elt;

  g_return_val_if_fail (pos < element->styles->len, NULL);

  elt = g_ptr_array_index (element->styles, pos);

  return g_bytes_get_data (elt->content, NULL);
}

const char *
svg_element_get_style_media (SvgElement   *element,
                             unsigned int  pos)
{
  StyleElt *elt;

  g_return_val_if_fail (pos < element->styles->len, NULL);

  elt = g_ptr_array_index (element->styles, pos);

  return elt->media;
}

void
svg_element_set_style (SvgElement   *element,
                       unsigned int  pos,
                       const char   *style)
{
  StyleElt *elt;
  unsigned int i;

  g_return_if_fail (pos <= element->styles->len);

  if (pos < element->styles->len)
    {
      elt = g_ptr_array_index (element->styles, pos);
      g_bytes_unref (elt->content);
    }
  else
    {
      elt = g_new0 (StyleElt, 1);
      g_ptr_array_add (element->styles, elt);
    }

  elt->content = g_bytes_new_take (g_strdup (style), strlen (style));

  for (i = element->styles->len; i > 0; i--)
    {
      elt = g_ptr_array_index (element->styles, i - 1);
      if (g_bytes_get_size (elt->content) > 0)
        break;
    }

  g_ptr_array_set_size (element->styles, i);
}

void
svg_element_clear_style (SvgElement *element)
{
  g_ptr_array_set_size (element->styles, 0);
}

void
svg_element_parse_classes (SvgElement *element,
                           const char *classes)
{
  svg_element_take_classes (element, parse_strv (classes));
}

void
svg_element_take_classes (SvgElement *element,
                          GStrv       classes)
{
  g_strfreev (element->classes);
  element->classes = classes;
  gtk_css_node_set_classes (element->css_node, (const char **) element->classes);
}

GStrv
svg_element_get_classes (SvgElement *element)
{
  return element->classes;
}

void
svg_element_add_title (SvgElement    *element,
                       PangoLanguage *lang,
                       const char    *title)
{
  LangString l;

  l.type = "title";
  l.lang = lang;
  l.string = g_strdup (title);

  g_array_append_val (element->lang_strings, l);
}

const char *
svg_element_get_title (SvgElement *element)
{
  return element->title;
}

void
svg_element_add_description (SvgElement    *element,
                             PangoLanguage *lang,
                             const char    *description)
{
  LangString l;

  l.type = "desc";
  l.lang = lang;
  l.string = g_strdup (description);

  g_array_append_val (element->lang_strings, l);
}

const char *
svg_element_get_description (SvgElement *element)
{
  return element->description;
}

static const char *
pick_best_lang_string (GArray        *ls,
                       const char    *type,
                       PangoLanguage *lang)
{
  const char *string = NULL;

  for (unsigned int i = 0; i < ls->len; i++)
    {
      LangString *l = &g_array_index (ls, LangString, i);

      if (strcmp (l->type, type) != 0)
        continue;

      if (string == NULL)
        string = l->string;

      if (l->lang == lang)
        {
          string = l->string;
          break;
        }
    }

  return string;
}

void
svg_element_set_language (SvgElement    *element,
                          PangoLanguage *lang)
{
  g_set_str (&element->title, pick_best_lang_string (element->lang_strings, "title", lang));
  g_set_str (&element->description, pick_best_lang_string (element->lang_strings, "desc", lang));
  g_clear_pointer (&element->lang_strings, g_array_unref);
}

GtkCssNode *
svg_element_get_css_node (SvgElement *element)
{
  return element->css_node;
}

void
svg_element_set_origin (SvgElement     *element,
                        GtkSvgLocation *location)
{
  element->line = location->lines;
}

void
svg_element_get_origin (SvgElement     *element,
                        GtkSvgLocation *location)
{
  location->lines = element->line;
}

void
svg_element_delete (SvgElement *element)
{
  if (element->text)
    {
      for (unsigned int i = 0; i < element->text->len; i++)
        {
          TextNode *node = &g_array_index (element->text, TextNode, i);
          if (node->type == TEXT_NODE_SHAPE && node->shape.shape == element)
            {
              g_array_remove_index (element->text, i);
              break;
            }
        }
    }

  g_ptr_array_remove (element->parent->shapes, element);
}

SvgElement *
svg_element_get_parent (SvgElement *element)
{
  return element->parent;
}

/* Given two elements, find ancestors
 * such that ancestor0 != ancestor1 and
 * ancestor0->parent == ancestor1->parent
 */

gboolean
svg_element_common_ancestor (SvgElement  *element0,
                             SvgElement  *element1,
                             SvgElement **ancestor0,
                             SvgElement **ancestor1)
{
  SvgElement *parent0, *parent1;
  unsigned int depth0, depth1;

  depth0 = 0;
  parent0 = element0;
  while (parent0->parent)
    {
      parent0 = parent0->parent;
      depth0++;
    }

  depth1 = 0;
  parent1 = element1;
  while (parent1->parent)
    {
      parent1 = parent1->parent;
      depth1++;
    }

  if (parent0 != parent1)
    return FALSE;

  while (depth0 > depth1)
    {
      element0 = element0->parent;
      depth0--;
    }

  while (depth1 > depth0)
    {
      element1 = element1->parent;
      depth1--;
    }

  while (element0->parent != element1->parent)
    {
      element0 = element0->parent;
      element1 = element1->parent;
    }

  g_assert (element0->parent == element1->parent);

  *ancestor0 = element0;
  *ancestor1 = element1;

  return TRUE;
}

uint64_t
svg_element_get_states (SvgElement *element)
{
  return element->gpa.states;
}

void
svg_element_set_states (SvgElement *element,
                        uint64_t    states)
{
  element->gpa.states = states;
}

gboolean
svg_element_equal (SvgElement *element1,
                   SvgElement *element2)
{
  if (element1->type != element2->type ||
      g_strcmp0 (svg_element_get_id (element1), svg_element_get_id (element2)) != 0)
    return FALSE;

  if (g_strcmp0 (svg_element_get_id (element1), svg_element_get_id (element2)) != 0)
    return FALSE;

  if (g_strcmp0 (element1->style, element2->style) != 0)
    return FALSE;

  if (element1->classes && element2->classes)
    {
      if (!g_strv_equal ((const char * const *) element1->classes,
                         (const char * const *) element2->classes))
        return FALSE;
    }
  else
    {
      if (element1->classes != element2->classes)
        return FALSE;
    }

  for (unsigned int i = 0; i < N_SVG_PROPERTIES; i++)
    {
      if (!svg_value_equal (element1->base[i], element2->base[i]))
        return FALSE;
    }

  if (svg_element_type_is_container (element1->type))
    {
      if (element1->shapes->len != element2->shapes->len)
        return FALSE;

      for (unsigned int i = 0; i < element1->shapes->len; i++)
        {
          SvgElement *s1 = g_ptr_array_index (element1->shapes, i);
          SvgElement *s2 = g_ptr_array_index (element2->shapes, i);

          if (!svg_element_equal (s1, s2))
            return FALSE;
        }
    }

  if (svg_element_type_is_gradient (element1->type))
    {
      if (element1->color_stops->len != element2->color_stops->len)
        return FALSE;

      for (unsigned int i = 0; i < element1->color_stops->len; i++)
        {
          SvgColorStop *s1 = g_ptr_array_index (element1->color_stops, i);
          SvgColorStop *s2 = g_ptr_array_index (element2->color_stops, i);

          if (!svg_color_stop_equal (s1, s2))
            return FALSE;
        }
    }

  if (svg_element_type_is_filter (element1->type))
    {
      if (element1->filters->len != element2->filters->len)
        return FALSE;

      for (unsigned int i = 0; i < element1->filters->len; i++)
        {
          SvgFilter *f1 = g_ptr_array_index (element1->filters, i);
          SvgFilter *f2 = g_ptr_array_index (element2->filters, i);

          if (!svg_filter_equal (f1, f2))
            return FALSE;
        }
    }

  if (element1->animations || element2->animations)
    {
      if (!element1->animations || !element2->animations)
        return FALSE;

      if (element1->animations->len != element2->animations->len)
        return FALSE;

      for (unsigned int i = 0; i < element1->animations->len; i++)
        {
          SvgAnimation *a1 = g_ptr_array_index (element1->animations, i);
          SvgAnimation *a2 = g_ptr_array_index (element2->animations, i);

          if (!svg_animation_equal (a1, a2))
            return FALSE;
        }
    }

  return TRUE;
}

void
svg_element_foreach (SvgElement       *element,
                     SvgShapeCallback  callback,
                     gpointer          user_data)
{
  callback (element, user_data);

  if (element->shapes)
    {
      for (unsigned int i = 0; i < element->shapes->len; i++)
        {
          SvgElement *sh = g_ptr_array_index (element->shapes, i);
          svg_element_foreach (sh, callback, user_data);
        }
    }
}

void
svg_element_set_gpa_width (SvgElement *element,
                           SvgValue   *value)
{
  g_clear_pointer (&element->gpa.width, svg_value_unref);
  if (value)
    element->gpa.width = svg_value_ref (value);
}

SvgValue *
svg_element_get_gpa_width (SvgElement *element)
{
  return element->gpa.width;
}

void
svg_element_set_gpa_fill (SvgElement *element,
                          SvgValue   *value)
{
  g_clear_pointer (&element->gpa.fill, svg_value_unref);
  if (value)
    element->gpa.fill = svg_value_ref (value);
}

SvgValue *
svg_element_get_gpa_fill (SvgElement *element)
{
  return element->gpa.fill;
}

void
svg_element_set_gpa_stroke (SvgElement *element,
                            SvgValue   *value)
{
  g_clear_pointer (&element->gpa.stroke, svg_value_unref);
  if (value)
    element->gpa.stroke = svg_value_ref (value);
}

SvgValue *
svg_element_get_gpa_stroke (SvgElement *element)
{
  return element->gpa.stroke;
}

void
svg_element_set_gpa_transition (SvgElement    *element,
                                GpaTransition  transition,
                                GpaEasing      easing,
                                int64_t        duration,
                                int64_t        delay)
{
  element->gpa.transition = transition;
  element->gpa.transition_easing = easing;
  element->gpa.transition_duration = duration;
  element->gpa.transition_delay = delay;
}

void
svg_element_get_gpa_transition (SvgElement    *element,
                                GpaTransition *transition,
                                GpaEasing     *easing,
                                int64_t       *duration,
                                int64_t       *delay)
{
  if (transition)
    *transition = element->gpa.transition;
  if (easing)
    *easing = element->gpa.transition_easing;
  if (duration)
    *duration = element->gpa.transition_duration;
  if (delay)
    *delay = element->gpa.transition_delay;
}

void
svg_element_set_gpa_animation (SvgElement   *element,
                               GpaAnimation  animation,
                               GpaEasing     easing,
                               int64_t       duration,
                               double        repeat,
                               double        segment)
{
  element->gpa.animation = animation;
  element->gpa.animation_easing = easing;
  element->gpa.animation_duration = duration;
  element->gpa.animation_repeat = repeat;
  element->gpa.animation_segment = segment;
}

void
svg_element_get_gpa_animation (SvgElement   *element,
                               GpaAnimation *animation,
                               GpaEasing    *easing,
                               int64_t      *duration,
                               double       *repeat,
                               double       *segment)
{
  if (animation)
    *animation = element->gpa.animation;
  if (easing)
    *easing = element->gpa.animation_easing;
  if (duration)
    *duration = element->gpa.animation_duration;
  if (repeat)
    *repeat = element->gpa.animation_repeat;
  if (segment)
    *segment = element->gpa.animation_segment;
}

void
svg_element_set_gpa_origin (SvgElement *element,
                            double      origin)
{
  element->gpa.origin = origin;
}

double
svg_element_get_gpa_origin (SvgElement *element)
{
  return element->gpa.origin;
}

void
svg_element_set_gpa_attachment (SvgElement *element,
                                const char *id,
                                double      position,
                                SvgElement *shape)
{
  g_set_str (&element->gpa.attach.ref, id);
  element->gpa.attach.pos = position;
  element->gpa.attach.shape = shape;
}

void
svg_element_get_gpa_attachment (SvgElement  *element,
                                const char **id,
                                double      *position,
                                SvgElement **shape)
{
  if (id)
    *id = element->gpa.attach.ref;
  if (position)
    *position = element->gpa.attach.pos;
  if (shape)
    *shape = element->gpa.attach.shape;
}

SvgElement *
svg_element_duplicate (SvgElement *element,
                       SvgElement *parent)
{
  SvgElement *copy = g_new0 (SvgElement, 1);

  copy->type = element->type;
  copy->parent = parent;
  copy->attrs = _gtk_bitmask_copy (element->attrs);
  copy->important = _gtk_bitmask_copy (element->important);
  copy->id = NULL;
  copy->style = g_strdup (element->style);
  copy->classes = g_strdupv (element->classes);
  copy->title = g_strdup (element->title);
  copy->description = g_strdup (element->description);
  copy->line = element->line;
  copy->style_loc = element->style_loc;
  copy->focusable = element->focusable;

  copy->css_node = gtk_css_node_new ();
  gtk_css_node_set_parent (copy->css_node, parent->css_node);
  gtk_css_node_set_name (copy->css_node, g_quark_from_static_string (svg_element_type_get_name (copy->type)));
  gtk_css_node_set_classes (copy->css_node, (const char **) copy->classes);
  if (copy->type == SVG_ELEMENT_LINK)
    gtk_css_node_set_state (copy->css_node, GTK_STATE_FLAG_LINK);

  copy->specified = g_array_ref (element->specified);
  copy->styles = g_ptr_array_ref (element->styles);

  copy->shapes = g_ptr_array_new ();
  copy->animations = g_ptr_array_new ();

  copy->gpa.states = element->gpa.states;
  copy->gpa.transition = element->gpa.transition;
  copy->gpa.transition_easing = element->gpa.transition_easing;
  copy->gpa.transition_duration = element->gpa.transition_duration;
  copy->gpa.transition_delay = element->gpa.transition_delay;
  copy->gpa.animation = element->gpa.animation;
  copy->gpa.animation_easing = element->gpa.animation_easing;
  copy->gpa.animation_duration = element->gpa.animation_duration;
  copy->gpa.animation_repeat = element->gpa.animation_repeat;
  copy->gpa.animation_segment = element->gpa.animation_segment;
  copy->gpa.origin = element->gpa.origin;
  copy->gpa.attach.ref = NULL;
  copy->gpa.attach.shape = NULL;
  copy->gpa.attach.pos = 0;

  return copy;
}

void
svg_element_set_type (SvgElement     *element,
                      SvgElementType  type)
{
  SvgElementType old_type;

  if (element->type == type)
    return;

  old_type = element->type;
  element->type = type;

  if (svg_element_type_is_filter (old_type) &&
      !svg_element_type_is_filter (type))
    g_clear_pointer (&element->filters, g_ptr_array_unref);
  else if (!svg_element_type_is_filter (old_type) &&
           svg_element_type_is_filter (type))
    element->filters = g_ptr_array_new_with_free_func ((GDestroyNotify) svg_filter_free);

  if (svg_element_type_is_gradient (old_type) &&
      !svg_element_type_is_gradient (type))
    g_clear_pointer (&element->color_stops, g_ptr_array_unref);
  else if (!svg_element_type_is_gradient (old_type) &&
           svg_element_type_is_gradient (type))
    element->color_stops = g_ptr_array_new_with_free_func ((GDestroyNotify) svg_color_stop_free);

  if (svg_element_type_is_container (old_type) &&
      !svg_element_type_is_container (type))
    g_clear_pointer (&element->shapes, g_ptr_array_unref);
  if (!svg_element_type_is_container (old_type) &&
      svg_element_type_is_container (type))
    element->shapes = g_ptr_array_new_with_free_func ((GDestroyNotify) svg_element_free);

  if (svg_element_type_is_text (old_type) &&
      !svg_element_type_is_text (type))
    g_clear_pointer (&element->text, g_array_unref);
  if (!svg_element_type_is_text (old_type) &&
      svg_element_type_is_text (type))
    element->text = array_new_with_clear_func (sizeof (TextNode), (GDestroyNotify) text_node_clear);

  for (unsigned int attr = FIRST_SHAPE_PROPERTY; attr <= LAST_SHAPE_PROPERTY; attr++)
    {
      if (!svg_property_applies_to (attr, type))
        svg_element_set_base_value (element, attr, NULL, FALSE);
    }
}

void
svg_element_move_child_down (SvgElement *element,
                             SvgElement *child)
{
  for (unsigned int i = 0; i < element->shapes->len; i++)
    {
      if (g_ptr_array_index (element->shapes, i) == child)
        {
          g_ptr_array_steal_index (element->shapes, i);
          g_ptr_array_insert (element->shapes, i + 1, child);
          return;
        }
    }
}

SvgAnimation *
svg_element_find_animation (SvgElement *element,
                            const char *id)
{
  if (element->animations)
    {
      for (unsigned int i = 0; i < element->animations->len; i++)
        {
          SvgAnimation *a = g_ptr_array_index (element->animations, i);
          if (g_strcmp0 (a->id, id) == 0)
            return a;
        }
    }

  if (svg_element_type_is_container (element->type))
    {
      for (unsigned int i = 0; i < element->shapes->len; i++)
        {
          SvgElement *child = g_ptr_array_index (element->shapes, i);
          SvgAnimation *a = svg_element_find_animation (child, id);
          if (a)
            return a;
        }
    }

  return NULL;
}

void
svg_element_set_autofocus (SvgElement *element,
                           gboolean    autofocus)
{
  element->autofocus = autofocus;
}

gboolean
svg_element_get_autofocus (SvgElement *element)
{
  return element->autofocus;
}

void
svg_element_set_focusable (SvgElement *element,
                           gboolean    focusable)
{
  element->focusable = focusable;
}

gboolean
svg_element_get_focusable (SvgElement *element)
{
  return element->focusable;
}

gboolean
svg_element_get_initial_focusable (SvgElement *element)
{
  if (element->type == SVG_ELEMENT_SVG && element->parent == NULL)
    return TRUE;
  else if (element->type == SVG_ELEMENT_LINK)
    return TRUE;
  else
    return FALSE;
}

static void
svg_element_change_css_state (SvgElement *element,
                              GtkStateFlags add,
                              GtkStateFlags remove)
{
  GtkStateFlags state = gtk_css_node_get_state (element->css_node);
  gtk_css_node_set_state (element->css_node, (state | add) & ~remove);
}

void
svg_element_set_focus (SvgElement *element,
                       gboolean    focus)
{
  if (focus)
    svg_element_change_css_state (element, GTK_STATE_FLAG_FOCUSED, 0);
  else
    svg_element_change_css_state (element, 0, GTK_STATE_FLAG_FOCUSED);
}

gboolean
svg_element_get_focus (SvgElement *element)
{
  return (gtk_css_node_get_state (element->css_node) & GTK_STATE_FLAG_FOCUSED) != 0;
}

void
svg_element_set_active (SvgElement *element,
                        gboolean    active)
{
  if (active)
    svg_element_change_css_state (element, GTK_STATE_FLAG_ACTIVE, 0);
  else
    svg_element_change_css_state (element, 0, GTK_STATE_FLAG_ACTIVE);
}

gboolean
svg_element_get_active (SvgElement *element)
{
  return (gtk_css_node_get_state (element->css_node) & GTK_STATE_FLAG_ACTIVE) != 0;
}

void
svg_element_set_hover (SvgElement *element,
                       gboolean    hover)
{
  if (hover)
    svg_element_change_css_state (element, GTK_STATE_FLAG_PRELIGHT, 0);
  else
    svg_element_change_css_state (element, 0, GTK_STATE_FLAG_PRELIGHT);

  for (element = element->parent; element; element = element->parent)
    {
      if (hover)
        svg_element_change_css_state (element, GTK_STATE_FLAG_PRELIGHT, 0);
      else
        svg_element_change_css_state (element, 0, GTK_STATE_FLAG_PRELIGHT);
    }
}

gboolean
svg_element_get_hover (SvgElement *element)
{
  return (gtk_css_node_get_state (element->css_node) & GTK_STATE_FLAG_PRELIGHT) != 0;
}

void
svg_element_set_visited (SvgElement *element,
                         gboolean    visited)
{
  if (visited)
    svg_element_change_css_state (element, GTK_STATE_FLAG_VISITED, 0);
  else
    svg_element_change_css_state (element, 0, GTK_STATE_FLAG_VISITED);
}

gboolean
svg_element_get_visited (SvgElement *element)
{
  return (gtk_css_node_get_state (element->css_node) & GTK_STATE_FLAG_VISITED) != 0;
}

/* Depth first traversal in pre-order */

static SvgElement *
next_after (SvgElement *element,
            SvgElement *child)
{
  if (element->shapes)
    {
      for (unsigned int i = 0; i < element->shapes->len; i++)
        {
          if (g_ptr_array_index (element->shapes, i) == child)
            {
              if (i + 1 < element->shapes->len)
                return g_ptr_array_index (element->shapes, i + 1);
            }
        }
    }

  if (element->parent)
    return next_after (element->parent, element);
  else
    return NULL;
}

SvgElement *
svg_element_first (SvgElement *element)
{
  return element;
}

SvgElement *
svg_element_last (SvgElement *element)
{
  if (element->shapes && element->shapes->len > 0)
    return svg_element_last (g_ptr_array_index (element->shapes, element->shapes->len - 1));
  else
    return element;
}

SvgElement *
svg_element_next (SvgElement *element)
{
  if (element->shapes && element->shapes->len > 0)
    return g_ptr_array_index (element->shapes, 0);
  else if (element->parent)
    return next_after (element->parent, element);
  else
    return NULL;
}

SvgElement *
svg_element_previous (SvgElement *element)
{
  if (element->parent)
    {
      unsigned int idx;

      if (g_ptr_array_find (element->parent->shapes, element, &idx) && idx > 0)
        return svg_element_last (g_ptr_array_index (element->parent->shapes, idx - 1));
      else
        return element->parent;
    }
  else
    return NULL;
}

SvgElement *
svg_element_find_by_id (SvgElement *element,
                        const char *id)
{
  if (g_strcmp0 (element->id, id) == 0)
    return element;

  if (element->shapes)
    {
      for (unsigned int i = 0; i < element->shapes->len; i++)
        {
          SvgElement *child = g_ptr_array_index (element->shapes, i);
          SvgElement *res;

          res = svg_element_find_by_id (child, id);
          if (res)
            return res;
        }
    }

  return NULL;
}

static gboolean
propagate_event_down (SvgElement *element,
                      GdkEvent   *event,
                      GtkSvg     *svg)
{
  if (element->parent)
    {
      if (propagate_event_down (element->parent, event, svg))
        return GDK_EVENT_STOP;
    }

  return GDK_EVENT_PROPAGATE;
}

static gboolean
propagate_event_up (SvgElement *element,
                    GdkEvent   *event,
                    GtkSvg     *svg)
{
  if (element->type == SVG_ELEMENT_LINK)
    {
      if (gdk_event_get_event_type (event) == GDK_KEY_PRESS &&
          gdk_key_event_get_keyval (event) == GDK_KEY_Return)
        {
          gtk_svg_activate_element (svg, element);
          return GDK_EVENT_STOP;
        }

      if (gdk_event_get_event_type (event) == GDK_BUTTON_PRESS &&
          gdk_button_event_get_button (event) == GDK_BUTTON_PRIMARY)
        {
          gtk_svg_set_active (svg, element);
          return GDK_EVENT_STOP;
        }
      else if (gdk_event_get_event_type (event) == GDK_BUTTON_RELEASE &&
               gdk_button_event_get_button (event) == GDK_BUTTON_PRIMARY &&
               gtk_svg_get_active (svg) == element)
        {
          gtk_svg_activate_element (svg, element);
          gtk_svg_set_active (svg, NULL);
          return GDK_EVENT_STOP;
        }
    }

  if (element->parent)
    {
      if (propagate_event_up (element->parent, event, svg))
        return GDK_EVENT_STOP;
    }

  return GDK_EVENT_PROPAGATE;
}

gboolean
svg_element_propagate_event (SvgElement *target,
                             GdkEvent   *event,
                             GtkSvg     *svg)
{
  if (propagate_event_down (target, event, svg))
    return GDK_EVENT_STOP;

  return propagate_event_up (target, event, svg);
}

gboolean
svg_element_or_ancestor_has_type (SvgElement     *element,
                                  SvgElementType  type)
{
  for (SvgElement *p = element; p; p = p->parent)
    {
      if (p->type == type)
        return TRUE;
    }
  return FALSE;
}

/* bounding box check */
gboolean
svg_element_can_contain (SvgElement             *element,
                         const graphene_rect_t  *viewport,
                         GtkSvg                 *svg,
                         const graphene_point_t *point)
{
  graphene_rect_t bounds;

  if (svg_element_get_current_bounds (element, viewport, svg, &bounds))
    return gsk_rect_contains_point (&bounds, point);

  return TRUE;
}

/* accurate check according to pointer-events */
gboolean
svg_element_contains (SvgElement             *element,
                      const graphene_rect_t  *viewport,
                      GtkSvg                 *svg,
                      const graphene_point_t *point)
{
  GskPath *path;
  GskFillRule fill_rule;
  gboolean ret;
  PointerEvents pe;
  Visibility vis;
  graphene_rect_t bounds;
  PaintKind fill_kind;
  PaintKind stroke_kind;
  GskStroke *stroke;

  g_assert (svg_element_type_is_path (element->type));

  pe = svg_enum_get (svg_element_get_current_value (element, SVG_PROPERTY_POINTER_EVENTS));
  vis = svg_enum_get (svg_element_get_current_value (element, SVG_PROPERTY_VISIBILITY));
  fill_kind = svg_paint_get_kind (svg_element_get_current_value (element, SVG_PROPERTY_FILL));
  stroke_kind = svg_paint_get_kind (svg_element_get_current_value (element, SVG_PROPERTY_STROKE));
  fill_rule = svg_enum_get (svg_element_get_current_value (element, SVG_PROPERTY_FILL_RULE));

  switch ((unsigned int) pe)
    {
    case POINTER_EVENTS_NONE:
      return FALSE;
      break;

    case POINTER_EVENTS_BOUNDING_BOX:
      if (svg_element_get_current_bounds (element, viewport, svg, &bounds))
        return gsk_rect_contains_point (&bounds, point);
      return FALSE;

    case POINTER_EVENTS_AUTO:
    case POINTER_EVENTS_VISIBLE_PAINTED:
      if (vis == VISIBILITY_HIDDEN)
        return FALSE;
      G_GNUC_FALLTHROUGH;

    case POINTER_EVENTS_PAINTED:
      path = svg_element_get_current_path (element, viewport);
      stroke = svg_element_create_basic_stroke (element, viewport, TRUE400);
      if (fill_kind != PAINT_NONE && gsk_path_in_fill (path, point, fill_rule))
        ret = TRUE;
      else if (stroke_kind != PAINT_NONE && path_in_stroke (path, point, stroke))
        ret = TRUE;
      else
        ret = FALSE;
      gsk_stroke_free (stroke);
      gsk_path_unref (path);
      return ret;

    case POINTER_EVENTS_VISIBLE_FILL:
      if (vis == VISIBILITY_HIDDEN || fill_kind == PAINT_NONE)
        return FALSE;
      G_GNUC_FALLTHROUGH;

    case POINTER_EVENTS_FILL:
      path = svg_element_get_current_path (element, viewport);
      ret = gsk_path_in_fill (path, point, fill_rule);
      gsk_path_unref (path);
      return ret;

    case POINTER_EVENTS_VISIBLE_STROKE:
      if (vis == VISIBILITY_HIDDEN || stroke_kind == PAINT_NONE)
        return FALSE;
      G_GNUC_FALLTHROUGH;

    case POINTER_EVENTS_STROKE:
      /* FIXME: not quite right */
      path = svg_element_get_current_path (element, viewport);
      stroke = svg_element_create_basic_stroke (element, viewport, TRUE400);
      ret = path_in_stroke (path, point, stroke);
      gsk_stroke_free (stroke);
      gsk_path_unref (path);
      return ret;

    case POINTER_EVENTS_VISIBLE:
      if (vis == VISIBILITY_HIDDEN)
        return FALSE;
      G_GNUC_FALLTHROUGH;

    case POINTER_EVENTS_ALL:
      path = svg_element_get_current_path (element, viewport);
      stroke = svg_element_create_basic_stroke (element, viewport, TRUE400);
      if (gsk_path_in_fill (path, point, fill_rule))
        ret = TRUE;
      else if (path_in_stroke (path, point, stroke))
        ret = TRUE;
      else
        ret = FALSE;
      gsk_stroke_free (stroke);
      gsk_path_unref (path);
      return ret;

    default:
      g_assert_not_reached ();
    }

  return FALSE;
}

static gboolean
svg_element_has_ancestor_or_corresponding (SvgElement *element,
                                           SvgElement *ancestor)
{
  for (SvgElement *parent = element->parent; parent; parent = parent->parent)
    {
      if (parent == ancestor || parent->corresponding == ancestor)
        return TRUE;
    }
  return FALSE;
}

typedef struct
{
  unsigned int count;
  GHashTable *map;
} ShadowData;

static void svg_element_build_shadow_tree (SvgElement *element,
                                           GtkSvg     *svg,
                                           ShadowData *data);

static SvgElement *
svg_element_clone (SvgElement *element,
                   SvgElement *parent,
                   GtkSvg     *svg,
                   ShadowData *data)
{
  SvgElement *clone = g_new0 (SvgElement, 1);

  if (data->count++ > CLONE_LIMIT)
    {
      gtk_svg_rendering_error (svg, "Excessive instance count, aborting");
      return NULL;
    }

  if (element->type == SVG_ELEMENT_USE)
    svg_element_build_shadow_tree (element, svg, data);

  clone->type = element->type;
  clone->parent = parent;
  clone->attrs = _gtk_bitmask_copy (element->attrs);
  clone->important = _gtk_bitmask_copy (element->important);
  clone->id = NULL; /* Lets not confuse find-by-id */
  clone->style = g_strdup (element->style);
  clone->classes = g_strdupv (element->classes);
  clone->title = g_strdup (element->title);
  clone->description = g_strdup (element->description);
  clone->line = element->line;
  clone->style_loc = element->style_loc;
  clone->focusable = element->focusable;

  clone->first = NULL;
  clone->next = NULL;

  clone->css_node = gtk_css_node_new ();
  gtk_css_node_set_parent (clone->css_node, parent->css_node);
  gtk_css_node_set_name (clone->css_node, g_quark_from_static_string (svg_element_type_get_name (clone->type)));
  if (element->id)
    gtk_css_node_set_id (clone->css_node, g_quark_from_string (element->id));
  gtk_css_node_set_classes (clone->css_node, (const char **) clone->classes);
  if (clone->type == SVG_ELEMENT_LINK)
    gtk_css_node_set_state (clone->css_node, GTK_STATE_FLAG_LINK);

  clone->specified = array_new_with_clear_func (sizeof (PropertyValue), (GDestroyNotify) property_value_clear);
  for (unsigned int i = 0; i < element->specified->len; i++)
    {
      PropertyValue *p = &g_array_index (element->specified, PropertyValue, i);
      PropertyValue pv;

      pv.attr = p->attr;
      pv.value = svg_value_ref (p->value);
      pv.important = p->important;
      g_array_append_val (clone->specified, pv);
    }

  for (unsigned int attr = 0; attr < N_SVG_PROPERTIES; attr++)
    {
      clone->base[attr] = svg_value_ref (element->base[attr]);
      clone->current[attr] = svg_property_ref_initial_value (attr, clone->type, clone->parent != NULL);
    }

  if (element->shapes)
    {
      SvgElement *p, *q;
      unsigned int idx;

      clone->shapes = g_ptr_array_new_with_free_func ((GDestroyNotify) svg_element_free);
      for (unsigned int i = 0; i < element->shapes->len; i++)
        {
          SvgElement *child = g_ptr_array_index (element->shapes, i);
          SvgElement *child_clone = svg_element_clone (child, clone, svg, data);
          if (!child_clone)
            {
              svg_element_free (clone);
              return NULL;
            }
          g_ptr_array_add (clone->shapes, child_clone);
        }

      /* Copy dependency order */
      q = NULL;
      for (p = element->first; p; p = p->next)
        {
          g_ptr_array_find (element->shapes, p, &idx);

          if (p == element->first)
            {
              clone->first = g_ptr_array_index (clone->shapes, idx);
              q = clone->first;
            }
          else if (q)
            {
              q->next = g_ptr_array_index (clone->shapes, idx);
              q = q->next;
            }
        }
    }

  if (element->animations)
    {
      for (unsigned int i = 0; i < element->animations->len; i++)
        {
          SvgAnimation *a = g_ptr_array_index (element->animations, i);
          SvgAnimation *a_clone = svg_animation_clone (a, clone, svg->timeline);
          svg_element_add_animation (clone, a_clone);
          g_hash_table_insert (data->map, a, a_clone);
        }
    }

  if (element->color_stops)
    {
      clone->color_stops = g_ptr_array_new_with_free_func ((GDestroyNotify) svg_color_stop_free);
      for (unsigned int i = 0; i < element->color_stops->len; i++)
        {
          SvgColorStop *stop = g_ptr_array_index (element->color_stops, i);
          g_ptr_array_add (clone->color_stops, svg_color_stop_clone (stop, clone));
        }
    }

  if (element->filters)
    {
      clone->filters = g_ptr_array_new_with_free_func ((GDestroyNotify) svg_filter_free);
      for (unsigned int i = 0; i < element->filters->len; i++)
        {
          SvgFilter *f = g_ptr_array_index (element->filters, i);
          g_ptr_array_add (clone->filters, svg_filter_clone (f, clone));
        }
    }

  if (element->text)
    {
      clone->text = array_new_with_clear_func (sizeof (TextNode), (GDestroyNotify) text_node_clear);
      for (unsigned int i = 0; i < element->text->len; i++)
        {
          TextNode *t = &g_array_index (element->text, TextNode, i);
          TextNode tn;

          switch (t->type)
            {
            case TEXT_NODE_CHARACTERS:
              tn.type = TEXT_NODE_CHARACTERS;
              tn.characters.text = g_strdup (t->characters.text);
              if (t->characters.layout)
                tn.characters.layout = g_object_ref (t->characters.layout);
              tn.characters.x = t->characters.x;
              tn.characters.y = t->characters.y;
              tn.characters.r = t->characters.r;
              break;
            case TEXT_NODE_SHAPE:
              tn.type = TEXT_NODE_SHAPE;
              tn.shape.shape = t->shape.shape;
              tn.shape.has_bounds = t->shape.has_bounds;
              tn.shape.bounds = t->shape.bounds;
              break;
            default:
              g_assert_not_reached ();
            }

          g_array_append_val (clone->text, tn);
        }
    }

  clone->styles = g_ptr_array_ref (element->styles);
  clone->inline_styles = g_array_ref (element->inline_styles);

  if (element->gpa.fill)
    clone->gpa.fill = svg_value_ref (element->gpa.fill);
  if (element->gpa.stroke)
    clone->gpa.stroke = svg_value_ref (element->gpa.stroke);
  if (element->gpa.width)
    clone->gpa.width = svg_value_ref (element->gpa.width);

  clone->gpa.states = element->gpa.states;
  clone->gpa.transition = element->gpa.transition;
  clone->gpa.transition_easing = element->gpa.transition_easing;
  clone->gpa.transition_duration = element->gpa.transition_duration;
  clone->gpa.transition_delay = element->gpa.transition_delay;
  clone->gpa.animation = element->gpa.animation;
  clone->gpa.animation_easing = element->gpa.animation_easing;
  clone->gpa.animation_duration = element->gpa.animation_duration;
  clone->gpa.animation_repeat = element->gpa.animation_repeat;
  clone->gpa.animation_segment = element->gpa.animation_segment;
  clone->gpa.origin = element->gpa.origin;
  clone->gpa.attach.ref = NULL;
  clone->gpa.attach.shape = NULL;
  clone->gpa.attach.pos = 0;

  clone->corresponding = element;

  g_hash_table_insert (data->map, element, clone);

  return clone;
}

static void
override_specified (SvgElement  *element,
                    SvgProperty  attr,
                    SvgValue    *old_value,
                    SvgValue    *new_value)
{
  /* Styles have already been applied when we do this,
   * so override base values too, when necessary.
   */

  if (element->base[attr] == old_value)
    svg_element_set_base_value (element, attr, new_value, FALSE);
  svg_element_take_specified_value (element, attr, new_value);
}

static void
svg_element_resolve_shadow_references (SvgElement *element,
                                       GHashTable *map)
{
  SvgValue *value, *new_value;
  SvgElement *clone;
  SvgAnimation *anim;
  SvgProperty href_props[] = {
    SVG_PROPERTY_HREF,
    SVG_PROPERTY_FE_IMAGE_HREF,
    SVG_PROPERTY_MARKER_START,
    SVG_PROPERTY_MARKER_MID,
    SVG_PROPERTY_MARKER_END
  };
  SvgProperty paint_props[] = {
    SVG_PROPERTY_FILL,
    SVG_PROPERTY_STROKE,
  };

  for (unsigned int i = 0; i < G_N_ELEMENTS (href_props); i++)
    {
      value = svg_element_get_specified_value (element, href_props[i]);
      if (!value)
        continue;

      clone = g_hash_table_lookup (map, svg_href_get_shape (value));
      anim = g_hash_table_lookup (map, svg_href_get_animation (value));
      if (clone || anim)
        {
          if (svg_href_get_kind (value) == HREF_URL)
            new_value = svg_href_new_url_take (g_strdup (svg_href_get_ref (value)));
          else
            new_value = svg_href_new_plain (svg_href_get_ref (value));
          if (clone)
            svg_href_set_shape (new_value, clone);
          else
            svg_href_set_animation (new_value, anim);
          override_specified (element, href_props[i], value, new_value);
        }
    }

  for (unsigned int i = 0; i < G_N_ELEMENTS (paint_props); i++)
    {
      PaintKind kind;
      value = svg_element_get_specified_value (element, paint_props[i]);

      if (!value)
        continue;

      kind = svg_paint_get_kind (value);
      if (!paint_is_server (kind))
        continue;

      clone = g_hash_table_lookup (map, svg_paint_get_server_shape (value));
      if (clone)
        {
          const char *ref = svg_paint_get_server_ref (value);
          const GdkColor *fallback = svg_paint_get_server_fallback (value);

          if (kind == PAINT_SERVER_WITH_FALLBACK)
            new_value = svg_paint_new_server_with_fallback (ref, fallback);
          else if (kind == PAINT_SERVER_WITH_CURRENT_COLOR)
            new_value = svg_paint_new_server_with_current_color (ref);
          else
            new_value = svg_paint_new_server (ref);
          svg_paint_set_server_shape (new_value, clone);
          override_specified (element, paint_props[i], value, new_value);
        }
    }

  value = svg_element_get_specified_value (element, SVG_PROPERTY_CLIP_PATH);
  if (value && svg_clip_get_kind (value) == CLIP_URL)
    {
      clone = g_hash_table_lookup (map, svg_clip_get_shape (value));
      if (clone)
        {
          new_value = svg_clip_new_url_take (g_strdup (svg_clip_get_id (value)));
          svg_clip_set_shape (new_value, clone);
          override_specified (element, SVG_PROPERTY_CLIP_PATH, value, new_value);
        }
    }

  value = svg_element_get_specified_value (element, SVG_PROPERTY_MASK);
  if (value && svg_mask_get_kind (value) == MASK_URL)
    {
      clone = g_hash_table_lookup (map, svg_mask_get_shape (value));
      if (clone)
        {
          new_value = svg_mask_new_url_take (g_strdup (svg_mask_get_id (value)));
          svg_mask_set_shape (new_value, clone);
          override_specified (element, SVG_PROPERTY_MASK, value, new_value);
        }
    }

  value = svg_element_get_specified_value (element, SVG_PROPERTY_FILTER);
  if (value && !svg_filter_functions_is_none (value))
    {
      unsigned int length = svg_filter_functions_get_length (value);
      gboolean needs_copy = FALSE;

      for (unsigned int i = 0; i < length; i++)
        {
          if (svg_filter_functions_get_kind (value, i) == FILTER_REF)
            {
              needs_copy = TRUE;
              break;
            }
        }

      if (needs_copy)
        {
          new_value = svg_filter_functions_copy (value);
          for (unsigned int i = 0; i < length; i++)
            {
              if (svg_filter_functions_get_kind (value, i) == FILTER_REF)
                {
                  clone = g_hash_table_lookup (map, svg_filter_functions_get_shape (value, i));
                  if (clone)
                    svg_filter_functions_set_shape (new_value, i, clone);
                }
            }
          override_specified (element, SVG_PROPERTY_FILTER, value, new_value);
        }
    }

  if (element->filters)
    {
      for (unsigned int i = 0; i < element->filters->len; i++)
        {
          SvgFilter *f = g_ptr_array_index (element->filters, i);

          value = svg_filter_get_specified_value (f, SVG_PROPERTY_FE_IMAGE_HREF);
          if (!value)
            continue;

          clone = g_hash_table_lookup (map, svg_href_get_shape (value));
          if (!clone)
            continue;

          if (svg_href_get_kind (value) == HREF_URL)
            new_value = svg_href_new_url_take (g_strdup (svg_href_get_ref (value)));
          else
            new_value = svg_href_new_plain (svg_href_get_ref (value));
          svg_href_set_shape (new_value, clone);
          if (svg_filter_get_base_value (f, SVG_PROPERTY_FE_IMAGE_HREF) == value)
            svg_filter_set_base_value (f, SVG_PROPERTY_FE_IMAGE_HREF, new_value, FALSE);
          svg_filter_take_specified_value (f, SVG_PROPERTY_FE_IMAGE_HREF, new_value);
        }
    }

  if (element->animations)
    {
      for (unsigned int i = 0; i < element->animations->len; i++)
        {
          SvgAnimation *a = g_ptr_array_index (element->animations, i);
          svg_animation_resolve_shadow_references (a, map);
        }
    }

  if (element->shapes)
    {
      for (unsigned int i = 0; i < element->shapes->len; i++)
        {
          SvgElement *child = g_ptr_array_index (element->shapes, i);
          svg_element_resolve_shadow_references (child, map);
        }
    }
}

static void
svg_element_build_shadow_tree (SvgElement *element,
                               GtkSvg     *svg,
                               ShadowData *data)
{
  SvgValue *href;
  SvgElement *target;

  g_assert (element->type == SVG_ELEMENT_USE);

  href = svg_element_get_current_value (element, SVG_PROPERTY_HREF);
  if (!href || !svg_href_get_shape (href))
    return;

  target = svg_href_get_shape (href);

  if (element == target || element->corresponding == target ||
      svg_element_has_ancestor_or_corresponding (element, target))
    {
      if (!svg->has_use_cycle)
        gtk_svg_rendering_error (svg, "Circular <use>, aborting");
      svg->has_use_cycle = TRUE;
      return;
    }

  if (element->shapes->len > 0)
    {
      SvgElement *current = g_ptr_array_index (element->shapes, 0);
      if (current->corresponding == target)
        return;
      g_ptr_array_remove_index (element->shapes, 0);
      element->first = NULL;
    }

  if (target)
    {
      SvgElement *clone = svg_element_clone (target, element, svg, data);
      if (clone)
        {
          g_ptr_array_add (element->shapes, clone);
          element->first = clone;
        }
    }
}

void
svg_element_ensure_shadow_tree (SvgElement *element,
                                GtkSvg     *svg)
{
  ShadowData data;

  data.count = 0;
  data.map = g_hash_table_new (g_direct_hash, g_direct_equal);

  svg_element_build_shadow_tree (element, svg, &data);

  if (element->shapes && element->shapes->len > 0)
    {
      SvgElement *clone = g_ptr_array_index (element->shapes, 0);
      svg_element_resolve_shadow_references (clone, data.map);
    }

  g_hash_table_destroy (data.map);
}

SvgElement *
svg_element_get_corresponding (SvgElement *element)
{
  return element->corresponding;
}

Messung V0.5 in Prozent
C=100 H=93 G=96

¤ Dauer der Verarbeitung: 0.34 Sekunden  (vorverarbeitet am  2026-07-03) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik