/*
 * 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 (values[SVG_PROPERTY_X1] && values[SVG_PROPERTY_Y1] &&
          values[SVG_PROPERTY_X2] && 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 (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, 0, 1),
                                        svg_numbers_get (v, 1, 1));

              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 + 1, 1));
                }
              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 (values[SVG_PROPERTY_CX] && values[SVG_PROPERTY_CY] && 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 (values[SVG_PROPERTY_CX] && values[SVG_PROPERTY_CY] &&
          values[SVG_PROPERTY_RX] && 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 (values[SVG_PROPERTY_X] && values[SVG_PROPERTY_Y] &&
          values[SVG_PROPERTY_WIDTH] && values[SVG_PROPERTY_HEIGHT] &&
          values[SVG_PROPERTY_RX] && 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 (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, 0, 0, 0, 0);
          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, 0, 0, 0, 0);
      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;
}

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, TRUE, 400);
        if (!gsk_path_get_stroke_bounds (element->path, stroke, &b))
          graphene_rect_init (&b, 0, 0, 0, 0);
        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, 0, 0, 0, 0);
      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, TRUE, 400);
      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, TRUE, 400);
      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, TRUE, 400);
      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)));
  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;
}
