/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

/* SVG loader plug-in
 * (C) Copyright 2003  Dom Lachowicz <cinamod@hotmail.com>
 *
 * Largely rewritten in September 2003 by Sven Neumann <sven@gimp.org>
 */

#include "config.h"

#include <stdlib.h>
#include <string.h>

#include <glib/gstdio.h>
#include <librsvg/rsvg.h>

#include "libgimp/gimp.h"
#include "libgimp/gimpui.h"

#include "libgimp/stdplugins-intl.h"


#define LOAD_PROC               "file-svg-load"
#define EXPORT_PROC             "file-svg-export"
#define PLUG_IN_BINARY          "file-svg"
#define PLUG_IN_ROLE            "gimp-file-svg"
#define SVG_VERSION             "2.5.0"
#define SVG_DEFAULT_RESOLUTION  90.0
#define SVG_DEFAULT_SIZE        500
#define SVG_PREVIEW_SIZE        128

#define EPSILON 1e-6


typedef struct
{
  gdouble    resolution;
  gint       width;
  gint       height;
} SvgLoadVals;

typedef enum
{
  EXPORT_FORMAT_NONE,
  EXPORT_FORMAT_PNG,
  EXPORT_FORMAT_JPEG
} EXPORT_FORMAT;

typedef enum
{
  LAYER_ID_VECTOR,
  LAYER_ID_GROUP,
  LAYER_ID_TEXT,
  LAYER_ID_LINK,
  LAYER_ID_RASTER,
  LAYER_ID_MASK,
} LAYER_ID;

typedef struct _Svg      Svg;
typedef struct _SvgClass SvgClass;

struct _Svg
{
  GimpPlugIn      parent_instance;
};

struct _SvgClass
{
  GimpPlugInClass parent_class;
};


#define SVG_TYPE  (svg_get_type ())
#define SVG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SVG_TYPE, Svg))

GType                   svg_get_type         (void) G_GNUC_CONST;

static GList          * svg_query_procedures (GimpPlugIn            *plug_in);
static GimpProcedure  * svg_create_procedure (GimpPlugIn            *plug_in,
                                              const gchar           *name);

static gboolean        svg_extract           (GimpProcedure         *procedure,
                                              GimpRunMode            run_mode,
                                              GFile                 *file,
                                              GimpMetadata          *metadata,
                                              GimpProcedureConfig   *config,
                                              GimpVectorLoadData    *extracted_dimensions,
                                              gpointer              *data_for_run,
                                              GDestroyNotify        *data_for_run_destroy,
                                              gpointer               extract_data,
                                              GError               **error);
static GimpValueArray * svg_load             (GimpProcedure         *procedure,
                                              GimpRunMode            run_mode,
                                              GFile                 *file,
                                              gint                   width,
                                              gint                   height,
                                              GimpVectorLoadData     extracted_data,
                                              GimpMetadata          *metadata,
                                              GimpMetadataLoadFlags *flags,
                                              GimpProcedureConfig   *config,
                                              gpointer               data_from_extract,
                                              gpointer               run_data);
static GimpValueArray * svg_export           (GimpProcedure         *procedure,
                                              GimpRunMode            run_mode,
                                              GimpImage             *image,
                                              GFile                 *file,
                                              GimpExportOptions     *options,
                                              GimpMetadata          *metadata,
                                              GimpProcedureConfig   *config,
                                              gpointer               run_data);

static GimpImage         * load_image        (GFile                 *file,
                                              RsvgHandle            *handle,
                                              gint                   width,
                                              gint                   height,
                                              gdouble                resolution,
                                              GError               **error);
static gboolean            export_image      (GFile                 *file,
                                              GimpProcedureConfig   *config,
                                              GimpImage             *image,
                                              GError               **error);

static GdkPixbuf         * load_rsvg_pixbuf  (RsvgHandle            *handle,
                                              gint                   width,
                                              gint                   height,
                                              gdouble                resolution,
                                              GError               **error);
static GimpPDBStatusType   load_dialog       (GFile                 *file,
                                              RsvgHandle            *handle,
                                              GimpProcedure         *procedure,
                                              GimpProcedureConfig   *config,
                                              GimpVectorLoadData     extracted_data,
                                              GError               **error);
static gboolean            export_dialog     (GimpImage             *image,
                                              GimpProcedure         *procedure,
                                              GObject               *config);
static gboolean            has_invalid_links (GimpLayer            **layers,
                                              GimpGroupLayer        *group);

static gboolean       svg_extract_dimensions (RsvgHandle            *handle,
                                              GimpVectorLoadData    *extracted_dimensions);

/* Taken from /app/path/gimppath-export.c */
static GString     * svg_export_file         (GimpImage             *image,
                                              GimpProcedureConfig   *config,
                                              GError               **error);
static void          svg_export_image_size   (GimpImage             *image,
                                              GString               *str);
static void          svg_export_defs         (GimpItem             **layers,
                                              GimpGroupLayer        *group,
                                              GString               *str,
                                              GimpProcedureConfig   *config,
                                              GList                **exported_res,
                                              GError               **error);
static void          svg_export_def          (GimpVectorLayer       *layer,
                                              GString               *str,
                                              GimpProcedureConfig   *config,
                                              GList                **exported_res,
                                              GError               **error);
static gchar       * svg_get_def_string      (GimpVectorLayer       *layer,
                                              const gchar           *type,
                                              GimpProcedureConfig   *config,
                                              GList                **exported_res,
                                              GError               **error);
static void          svg_export_layers       (GimpItem             **layers,
                                              GimpGroupLayer        *group,
                                              gint                  *layer_ids,
                                              GString               *str,
                                              GimpProcedureConfig   *config,
                                              gchar                 *spacing,
                                              GError               **error);
static void          svg_export_group_header (GimpGroupLayer        *group,
                                              gint                   group_id,
                                              GString               *str,
                                              gchar                 *spacing);
static void          svg_export_path         (GimpVectorLayer       *layer,
                                              gint                   vector_id,
                                              GString               *str,
                                              gchar                 *spacing);
static gchar       * svg_export_path_data    (GimpPath              *paths);
static void          svg_export_text         (GimpTextLayer         *layer,
                                              gint                   text_id,
                                              GString               *str,
                                              gchar                 *spacing);
static void          svg_export_text_lines   (GimpTextLayer         *layer,
                                              GString               *str,
                                              gchar                 *text);
static void          svg_export_link_layer   (GimpLinkLayer         *layer,
                                              gint                   link_id,
                                              GString               *str,
                                              gchar                 *spacing);
static void          svg_export_raster       (GimpDrawable          *layer,
                                              gint                  *layer_ids,
                                              GString               *str,
                                              gint                   format_id,
                                              gchar                 *spacing,
                                              GError               **error);
static void          svg_export_layer_mask   (GimpLayerMask         *mask,
                                              gint                  *layer_ids,
                                              GString               *str,
                                              gchar                 *spacing,
                                              GError               **error);

static gchar       * svg_get_color_string    (GimpVectorLayer       *layer,
                                              const gchar           *type);
static gchar       * svg_get_hex_color       (GeglColor             *color);

static gboolean     svg_drawable_has_transparency (GimpDrawable     *drawable);

#if LIBRSVG_CHECK_VERSION(2, 46, 0)
static GimpUnit      * svg_rsvg_to_gimp_unit (RsvgUnit               unit);
static void              svg_destroy_surface (guchar                *pixels,
                                              cairo_surface_t       *surface);
#endif



G_DEFINE_TYPE (Svg, svg, GIMP_TYPE_PLUG_IN)

GIMP_MAIN (SVG_TYPE)
DEFINE_STD_SET_I18N


static void
svg_class_init (SvgClass *klass)
{
  GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);

  plug_in_class->query_procedures = svg_query_procedures;
  plug_in_class->create_procedure = svg_create_procedure;
  plug_in_class->set_i18n         = STD_SET_I18N;
}

static void
svg_init (Svg *svg)
{
}

static GList *
svg_query_procedures (GimpPlugIn *plug_in)
{
  GList *list = NULL;

  list = g_list_append (list, g_strdup (LOAD_PROC));
  list = g_list_append (list, g_strdup (EXPORT_PROC));

  return list;
}

static GimpProcedure *
svg_create_procedure (GimpPlugIn  *plug_in,
                      const gchar *name)
{
  GimpProcedure *procedure = NULL;

  if (! strcmp (name, LOAD_PROC))
    {
      procedure = gimp_vector_load_procedure_new (plug_in, name,
                                                  GIMP_PDB_PROC_TYPE_PLUGIN,
                                                  svg_extract, NULL, NULL,
                                                  svg_load, NULL, NULL);

      gimp_procedure_set_menu_label (procedure, _("SVG image"));

      gimp_procedure_set_documentation (procedure,
                                        _("Loads files in SVG file format"),
                                        /* TODO: let's localize after we
                                         * add support for vector and/or
                                         * text and link layers:
                                         * _("Loads SVG (Scalable Vector Graphic) files")
                                         */
                                        "Renders SVG files to raster graphics "
                                        "using librsvg.",
                                        name);
      gimp_procedure_set_attribution (procedure,
                                      "Dom Lachowicz, Sven Neumann",
                                      "Dom Lachowicz <cinamod@hotmail.com>",
                                      SVG_VERSION);

      gimp_file_procedure_set_format_name (GIMP_FILE_PROCEDURE (procedure),
                                           _("SVG"));
      gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
                                          "image/svg+xml");
      gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
                                          "svg");
      gimp_file_procedure_set_magics (GIMP_FILE_PROCEDURE (procedure),
                                      "0,string,<?xml,0,string,<svg");

      gimp_procedure_add_choice_argument (procedure, "paths",
                                          _("_Paths"),
                                          _("Whether and how to import paths so that they can be used with the path tool"),
                                          gimp_choice_new_with_values ("no-import",     0, _("Don't import paths"),        NULL,
                                                                       "import",        1, _("Import paths individually"), NULL,
                                                                       "import-merged", 2, _("Merge imported paths"),      NULL,
                                                                       NULL),
                                          "no-import", G_PARAM_READWRITE);
    }
  else if (! strcmp (name, EXPORT_PROC))
    {
      procedure = gimp_export_procedure_new (plug_in, name,
                                             GIMP_PDB_PROC_TYPE_PLUGIN,
                                             FALSE, svg_export, NULL, NULL);

      gimp_procedure_set_image_types (procedure, "*");

      gimp_procedure_set_menu_label (procedure, _("Scalable Vector Graphic"));

      gimp_procedure_set_documentation (procedure,
                                        _("Exports files in SVG file format"),
                                        _("Exports files in SVG file format "
                                          "(Scalable Vector Graphic)"),
                                        name);
      gimp_procedure_set_attribution (procedure,
                                      "Alex S.",
                                      "Alex S.",
                                      "2025");

      gimp_file_procedure_set_format_name (GIMP_FILE_PROCEDURE (procedure),
                                           "SVG");
      gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
                                          "image/svg+xml");
      gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
                                          "svg");

      gimp_export_procedure_set_capabilities (GIMP_EXPORT_PROCEDURE (procedure),
                                              GIMP_EXPORT_CAN_HANDLE_RGB     |
                                              GIMP_EXPORT_CAN_HANDLE_GRAY    |
                                              GIMP_EXPORT_CAN_HANDLE_INDEXED |
                                              GIMP_EXPORT_CAN_HANDLE_ALPHA   |
                                              GIMP_EXPORT_CAN_HANDLE_LAYERS  |
                                              GIMP_EXPORT_CAN_HANDLE_LAYER_MASKS,
                                              NULL, NULL, NULL);

      gimp_procedure_add_string_argument (procedure, "title",
                                          _("_Title"),
                                          _("Optional <title> for SVG"),
                                          NULL,
                                          G_PARAM_READWRITE);

      gimp_procedure_add_choice_argument (procedure, "raster-export-format",
                                          _("Em_bed raster layers as"),
                                          NULL,
                                          gimp_choice_new_with_values ("png",  EXPORT_FORMAT_PNG,  _("PNG (always)"),                            NULL,
                                                                       "jpeg", EXPORT_FORMAT_JPEG, _("JPEG (for opaque images, PNG otherwise)"), NULL,
                                                                       "none", EXPORT_FORMAT_NONE, _("Do not export raster layers"),             NULL,
                                                                       NULL),
                                          "png",
                                          G_PARAM_READWRITE);
    }

  return procedure;
}

static gboolean
svg_extract (GimpProcedure        *procedure,
             GimpRunMode           run_mode,
             GFile                *file,
             GimpMetadata         *metadata,
             GimpProcedureConfig  *config,
             GimpVectorLoadData   *extracted_dimensions,
             gpointer             *data_for_run,
             GDestroyNotify       *data_for_run_destroy,
             gpointer              extract_data,
             GError              **error)
{
  RsvgHandle *handle;

  g_return_val_if_fail (extracted_dimensions != NULL, FALSE);
  g_return_val_if_fail (data_for_run_destroy != NULL && *data_for_run_destroy == NULL, FALSE);
  g_return_val_if_fail (data_for_run != NULL && *data_for_run == NULL, FALSE);
  g_return_val_if_fail (error != NULL && *error == NULL, FALSE);

  handle = rsvg_handle_new_from_gfile_sync (file, RSVG_HANDLE_FLAGS_NONE, NULL, error);

  if (! handle)
    {
      if (run_mode == GIMP_RUN_INTERACTIVE)
        {
          GtkWidget *dialog;
          GtkWidget *main_hbox;
          GtkWidget *label;
          gchar     *text;
          gboolean   retry;

          gimp_ui_init (PLUG_IN_BINARY);

          /* We need to ask explicitly before using the "unlimited" size
           * option (XML_PARSE_HUGE in libxml) because it is considered
           * unsafe, possibly consumming too much memory with malicious XML
           * files.
           */
          dialog = gimp_dialog_new (_("Disable safety size limits?"),
                                    PLUG_IN_ROLE,
                                    NULL, 0,
                                    gimp_standard_help_func, LOAD_PROC,

                                    _("_No"),  GTK_RESPONSE_NO,
                                    _("_Yes"), GTK_RESPONSE_YES,

                                    NULL);

          gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_NO);
          gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
          gimp_window_set_transient (GTK_WINDOW (dialog));

          main_hbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
          gtk_container_set_border_width (GTK_CONTAINER (main_hbox), 12);
          gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
                              main_hbox, TRUE, TRUE, 0);
          gtk_widget_show (main_hbox);

          /* Unfortunately the error returned by librsvg is unclear. While
           * libxml explicitly returns a "parser error : internal error:
           * Huge input lookup", librsvg does not seem to relay this error.
           * It sends a further parsing error, false positive provoked by
           * the huge input error.
           * If we were able to single out the huge data error, we could
           * just directly return from the plug-in as a failure run in other
           * cases. Instead of this, we need to ask each and every time, in
           * case it might be the huge data error.
           */
          label = gtk_label_new (_("A parsing error occurred.\n"
                                   "Disabling safety limits may help. "
                                   "Malicious SVG files may use this to consume too much memory."));
          gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER);
          gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
          gtk_label_set_line_wrap_mode (GTK_LABEL (label), PANGO_WRAP_WORD);
          gtk_label_set_max_width_chars (GTK_LABEL (label), 80);
          gtk_box_pack_start (GTK_BOX (main_hbox), label, FALSE, FALSE, 0);
          gtk_widget_show (label);

          label = gtk_label_new (NULL);
          text = g_strdup_printf ("<b>%s</b>",
                                  _("For security reasons, this should only be used for trusted input!"));
          gtk_label_set_markup (GTK_LABEL (label), text);
          g_free (text);
          gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER);
          gtk_box_pack_start (GTK_BOX (main_hbox), label, FALSE, FALSE, 0);
          gtk_widget_show (label);

          label = gtk_label_new (_("Retry without limits preventing to parse huge data?"));
          gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER);
          gtk_box_pack_start (GTK_BOX (main_hbox), label, FALSE, FALSE, 0);
          gtk_widget_show (label);

          gtk_widget_show (dialog);

          retry = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_YES);
          gtk_widget_destroy (dialog);

          if (retry)
            {
              g_clear_error (error);
              handle = rsvg_handle_new_from_gfile_sync (file, RSVG_HANDLE_FLAG_UNLIMITED, NULL, error);
            }
        }

      if (! handle)
        return FALSE;
    }

  if (! svg_extract_dimensions (handle, extracted_dimensions))
    {
      g_object_unref (handle);
      return FALSE;
    }

  *data_for_run = handle;
  *data_for_run_destroy = g_object_unref;

  return TRUE;
}

static gboolean
svg_extract_dimensions (RsvgHandle         *handle,
                        GimpVectorLoadData *extracted_dimensions)
{
#if LIBRSVG_CHECK_VERSION(2, 46, 0)
  gboolean           out_has_width;
  RsvgLength         out_width;
  gboolean           out_has_height;
  RsvgLength         out_height;
  gboolean           out_has_viewbox;
  RsvgRectangle      out_viewbox;
#endif
#if ! LIBRSVG_CHECK_VERSION(2, 52, 0)
  RsvgDimensionData  dim;
#endif

  /* SVG has no pixel density information */
  extracted_dimensions->exact_density = TRUE;
  extracted_dimensions->correct_ratio = TRUE;

#if LIBRSVG_CHECK_VERSION(2, 46, 0)
  rsvg_handle_get_intrinsic_dimensions (handle,
                                        &out_has_width,   &out_width,
                                        &out_has_height,  &out_height,
                                        &out_has_viewbox, &out_viewbox);

  if (out_has_width && out_has_height)
    {
      /* Starting in librsvg 2.54, rsvg_handle_get_intrinsic_dimensions ()
       * always returns TRUE for width/height. If width/height default to 100%,
       * we should check the viewport dimensions */
      gdouble viewbox_width  = 0;
      gdouble viewbox_height = 0;

      if (out_has_viewbox)
        {
          viewbox_width  = out_viewbox.width - out_viewbox.x;
          viewbox_height = out_viewbox.height - out_viewbox.y;
        }

      /* "width" and "height" present */
      if (svg_rsvg_to_gimp_unit (out_width.unit)  != NULL &&
          svg_rsvg_to_gimp_unit (out_height.unit) != NULL)
        {
          /* Best case scenario: we know all these units. */
          extracted_dimensions->width        = out_width.length;
          extracted_dimensions->width_unit   = svg_rsvg_to_gimp_unit (out_width.unit);
          extracted_dimensions->height       = out_height.length;
          extracted_dimensions->height_unit  = svg_rsvg_to_gimp_unit (out_height.unit);

          if ((out_width.length == 1.0 && out_width.unit == RSVG_UNIT_PERCENT) &&
              (out_height.length == 1.0 && out_height.unit == RSVG_UNIT_PERCENT))
            {
              extracted_dimensions->width        = viewbox_width;
              extracted_dimensions->height       = viewbox_height;
              extracted_dimensions->exact_width  = FALSE;
              extracted_dimensions->exact_height = FALSE;
            }
          else
            {
              extracted_dimensions->exact_width  = TRUE;
              extracted_dimensions->exact_height = TRUE;
            }
        }
      else
        {
          /* Ideally we let GIMP handle the transformation, but for cases where
           * we don't know how, we let rsvg make a computation to pixel for us.
           */
#if LIBRSVG_CHECK_VERSION(2, 52, 0)
          if (rsvg_handle_get_intrinsic_size_in_pixels (handle,
                                                        &extracted_dimensions->width,
                                                        &extracted_dimensions->height))
            {
              extracted_dimensions->width_unit   = gimp_unit_pixel ();
              extracted_dimensions->height_unit  = gimp_unit_pixel ();
              extracted_dimensions->exact_width  = FALSE;
              extracted_dimensions->exact_height = FALSE;
            }
          else
            {
              /* Honestly at this point, setting 300 PPI is random. */
              rsvg_handle_set_dpi (handle, 300.0);
              if (rsvg_handle_get_intrinsic_size_in_pixels (handle,
                                                            &extracted_dimensions->width,
                                                            &extracted_dimensions->height))
                {
                  extracted_dimensions->width_unit  = gimp_unit_percent ();
                  extracted_dimensions->height_unit = gimp_unit_percent ();
                }
              else if (out_width.unit == out_height.unit)
                {
                  /* Odd case which should likely never happen as we have width,
                   * height and a (fake) pixel density. Just in case though.
                   */
                  extracted_dimensions->width       = out_width.length;
                  extracted_dimensions->height      = out_height.length;
                  extracted_dimensions->width_unit  = gimp_unit_percent ();
                  extracted_dimensions->height_unit = gimp_unit_percent ();
                }
              else
                {
                  /* Should even less happen! */
                  return FALSE;
                }
            }
#else
          rsvg_handle_get_dimensions (handle, &dim);
          extracted_dimensions->width         = (gdouble) dim.width;
          extracted_dimensions->height        = (gdouble) dim.height;
          extracted_dimensions->exact_width   = FALSE;
          extracted_dimensions->exact_height  = FALSE;
          extracted_dimensions->correct_ratio = TRUE;
#endif
        }
    }
  else if (out_has_viewbox)
    {
      /* Only a viewbox, so dimensions have a ratio, but no units. */
      extracted_dimensions->width         = out_width.length;
      extracted_dimensions->width_unit    = gimp_unit_percent ();
      extracted_dimensions->exact_width   = FALSE;
      extracted_dimensions->height        = out_height.length;
      extracted_dimensions->height_unit   = gimp_unit_percent ();
      extracted_dimensions->exact_height  = FALSE;
      extracted_dimensions->correct_ratio = TRUE;
    }
  else
    {
      /* Neither width nor viewbox. Nothing can be determined. */
      return FALSE;
    }

#else
  rsvg_handle_get_dimensions (handle, &dim);
  extracted_dimensions->width         = (gdouble) dim.width;
  extracted_dimensions->height        = (gdouble) dim.height;
  extracted_dimensions->exact_width   = FALSE;
  extracted_dimensions->exact_height  = FALSE;
  extracted_dimensions->correct_ratio = TRUE;
#endif

  return TRUE;
}

static GimpValueArray *
svg_load (GimpProcedure         *procedure,
          GimpRunMode            run_mode,
          GFile                 *file,
          gint                   width,
          gint                   height,
          GimpVectorLoadData     extracted_data,
          GimpMetadata          *metadata,
          GimpMetadataLoadFlags *flags,
          GimpProcedureConfig   *config,
          gpointer               data_from_extract,
          gpointer               run_data)
{
  GimpValueArray    *return_vals;
  GimpImage         *image;
  GError            *error      = NULL;
  GimpPDBStatusType  status;
  gchar             *import_paths;
  gdouble            resolution;

  gegl_init (NULL, NULL);

  if (run_mode == GIMP_RUN_INTERACTIVE)
    {
      status = load_dialog (file, RSVG_HANDLE (data_from_extract),
                            procedure, config, extracted_data, &error);
      if (status != GIMP_PDB_SUCCESS)
        return gimp_procedure_new_return_values (procedure, status, error);
    }

  g_object_get (config,
                "width",         &width,
                "height",        &height,
                "pixel-density", &resolution,
                NULL);
  image = load_image (file, RSVG_HANDLE (data_from_extract),
                      width, height, resolution, &error);

  if (! image)
    return gimp_procedure_new_return_values (procedure,
                                             GIMP_PDB_EXECUTION_ERROR,
                                             error);

  g_object_get (config, "paths", &import_paths, NULL);
  if (g_strcmp0 (import_paths, "no-import") != 0)
    {
      GimpPath **paths = NULL;

      gimp_image_import_paths_from_file (image, file,
                                         g_strcmp0 (import_paths, "import-merged") == 0,
                                         TRUE, &paths);
      g_free (paths);
    }
  g_free (import_paths);

  return_vals = gimp_procedure_new_return_values (procedure,
                                                  GIMP_PDB_SUCCESS,
                                                  NULL);

  GIMP_VALUES_SET_IMAGE (return_vals, 1, image);

  return return_vals;
}

static GimpValueArray *
svg_export (GimpProcedure       *procedure,
            GimpRunMode          run_mode,
            GimpImage           *image,
            GFile               *file,
            GimpExportOptions   *options,
            GimpMetadata        *metadata,
            GimpProcedureConfig *config,
            gpointer             run_data)
{
  GimpPDBStatusType  status = GIMP_PDB_SUCCESS;
  GimpExportReturn   export = GIMP_EXPORT_IGNORE;
  GError            *error  = NULL;

  gegl_init (NULL, NULL);

  if (run_mode == GIMP_RUN_INTERACTIVE)
    {
      gimp_ui_init (PLUG_IN_BINARY);

      if (! export_dialog (image, procedure, G_OBJECT (config)))
        status = GIMP_PDB_CANCEL;
    }

  if (status == GIMP_PDB_SUCCESS)
    {
      export = gimp_export_options_get_image (options, &image);

      if (! export_image (file, config, image, &error))
        status = GIMP_PDB_EXECUTION_ERROR;

      if (export == GIMP_EXPORT_EXPORT)
        gimp_image_delete (image);
    }

  return gimp_procedure_new_return_values (procedure, status, error);
}

static GimpImage *
load_image (GFile            *file,
            RsvgHandle       *handle,
            gint              width,
            gint              height,
            gdouble           resolution,
            GError          **load_error)
{
  GimpImage *image;
  GimpLayer *layer;
  GdkPixbuf *pixbuf;
  GError    *error = NULL;

  pixbuf = load_rsvg_pixbuf (handle, width, height, resolution, &error);

  if (! pixbuf)
    {
      /*  Do not rely on librsvg setting GError on failure!  */
      g_set_error (load_error,
                   error ? error->domain : 0, error ? error->code : 0,
                   _("Could not open '%s' for reading: %s"),
                   gimp_file_get_utf8_name (file),
                   error ? error->message : _("Unknown reason"));
      g_clear_error (&error);

      return NULL;
    }

  gimp_progress_init (_("Rendering SVG"));

  width  = gdk_pixbuf_get_width (pixbuf);
  height = gdk_pixbuf_get_height (pixbuf);

  image = gimp_image_new (width, height, GIMP_RGB);
  gimp_image_undo_disable (image);

  gimp_image_set_resolution (image, resolution, resolution);

  layer = gimp_layer_new_from_pixbuf (image, _("Rendered SVG"), pixbuf,
                                      100,
                                      gimp_image_get_default_new_layer_mode (image),
                                      0.0, 1.0);
  gimp_image_insert_layer (image, layer, NULL, 0);

  gimp_image_undo_enable (image);

  return image;
}

static gboolean
export_image (GFile                *file,
              GimpProcedureConfig  *config,
              GimpImage            *image,
              GError              **error)
{
  GOutputStream *output;
  GString       *string;

  gimp_progress_init_printf (_("Exporting '%s'"),
                             gimp_file_get_utf8_name (file));

  output = G_OUTPUT_STREAM (g_file_replace (file,
                                            NULL, FALSE, G_FILE_CREATE_NONE,
                                            NULL, error));
  if (! output)
    {
      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                   _("Writing to file '%s' failed: %s"),
                   gimp_file_get_utf8_name (file), g_strerror (errno));
      return FALSE;
    }

  string = svg_export_file (image, config, error);

  if (! g_output_stream_write_all (output, string->str, string->len,
                                   NULL, NULL, error))
    {
      GCancellable *cancellable = g_cancellable_new ();

      if (error)
        g_clear_error (error);

      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                   _("Writing to file '%s' failed: %s"),
                   gimp_file_get_utf8_name (file), g_strerror (errno));
      g_string_free (string, TRUE);

      /* Cancel the overwrite initiated by g_file_replace(). */
      g_cancellable_cancel (cancellable);
      g_output_stream_close (output, cancellable, NULL);
      g_object_unref (cancellable);
      g_object_unref (output);

      return FALSE;
    }

  g_string_free (string, TRUE);
  g_object_unref (output);

  return TRUE;
}

#if ! LIBRSVG_CHECK_VERSION(2, 46, 0)
/* XXX Why we keep old deprecated implementation next to newer librsvg API is
 * because librsvg uses Rust since version 2.41.0. There are 2 raised problems:
 * 1. Rust is still not available or well tested on every architecture/OS out
 *    there yet so unless we decide to have SVG support as optional again, it
 *    would make GIMP non-available on these.
 * 2. There are some technical-ideological position against Rust for bundling
 *    and linking dependencies statically.
 * While the second point may or may not be a problem we want to take into
 * account (I guess that it mostly depends on the amount of additional
 * maintenance work it would imply), the first is definitely enough of a reason
 * to keep an old version requirement.
 * See also report #6821.
 */
static void
load_set_size_callback (gint     *width,
                        gint     *height,
                        gpointer  data)
{
  SvgLoadVals *vals = data;

  if (*width < 1 || *height < 1)
    {
      *width  = SVG_DEFAULT_SIZE;
      *height = SVG_DEFAULT_SIZE;
    }

  if (!vals->width || !vals->height)
    return;

  /*  either both arguments negative or none  */
  if ((vals->width * vals->height) < 0)
    return;

  if (vals->width > 0)
    {
      *width  = vals->width;
      *height = vals->height;
    }
  else
    {
      gdouble w      = *width;
      gdouble h      = *height;
      gdouble aspect = (gdouble) vals->width / (gdouble) vals->height;

      if (aspect > (w / h))
        {
          *height = abs (vals->height);
          *width  = (gdouble) abs (vals->width) * (w / h) + 0.5;
        }
      else
        {
          *width  = abs (vals->width);
          *height = (gdouble) abs (vals->height) / (w / h) + 0.5;
        }

      vals->width  = *width;
      vals->height = *height;
    }
}
#endif /* ! LIBRSVG_CHECK_VERSION(2, 46, 0) */

/*  This function renders a pixbuf from an SVG file according to vals.  */
static GdkPixbuf *
load_rsvg_pixbuf (RsvgHandle  *handle,
                  gint         width,
                  gint         height,
                  gdouble      resolution,
                  GError     **error)
{
  GdkPixbuf  *pixbuf  = NULL;

#if LIBRSVG_CHECK_VERSION(2, 46, 0)
  cairo_surface_t   *surface  = NULL;
  cairo_t           *cr       = NULL;
  RsvgRectangle      viewport = { 0, };
  guchar            *src;
  gint               y;
  /* Aspect-ratio breaking is only implemented with newer librsvg API. */
  GimpVectorLoadData dimensions;
  gdouble            x_scale       = 1.0;
  gdouble            y_scale       = 1.0;
  gdouble            aspect_width  = width;
  gdouble            aspect_height = height;
#else
  SvgLoadVals      vals;
#endif

  g_return_val_if_fail (handle, NULL);

  rsvg_handle_set_dpi (handle, resolution);
#if LIBRSVG_CHECK_VERSION(2, 46, 0)
  if (svg_extract_dimensions (handle, &dimensions))
    {
      if (fabs ((gdouble) dimensions.width / dimensions.height - (gdouble) width / height) < EPSILON)
        {
          rsvg_handle_set_dpi (handle, resolution);
        }
      else if ((gdouble) dimensions.width / dimensions.height > (gdouble) width / height)
        {
          aspect_height = (gdouble) width * dimensions.height / dimensions.width;
          /* XXX The X/Y resolution doesn't change a thing regarding
           * rendering unfortunately so we still have to scale when the
           * aspect ratio is not kept. I still set these, just in case.
           */
          rsvg_handle_set_dpi_x_y (handle, resolution, aspect_height / height * resolution);
          y_scale = height / aspect_height;
        }
      else
        {
          aspect_width = (gdouble) height * dimensions.width / dimensions.height;
          rsvg_handle_set_dpi_x_y (handle, aspect_width / width * resolution, resolution);
          x_scale = width / aspect_width;
        }
    }

  surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
  cr      = cairo_create (surface);

  viewport.width  = (double) aspect_width;
  viewport.height = (double) aspect_height;

  if (x_scale != 1.0 || y_scale != 1.0)
    cairo_scale (cr, x_scale, y_scale);

  rsvg_handle_render_document (handle, cr, &viewport, NULL);
  pixbuf = gdk_pixbuf_new_from_data (cairo_image_surface_get_data (surface),
                                     GDK_COLORSPACE_RGB, TRUE, 8, width, height,
                                     cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width),
                                     (GdkPixbufDestroyNotify) svg_destroy_surface, surface);
  cairo_destroy (cr);

  /* un-premultiply the data */
  src = gdk_pixbuf_get_pixels (pixbuf);

  for (y = 0; y < height; y++)
    {
      guchar *s = src;
      gint    w = width;

      while (w--)
        {
          GIMP_CAIRO_ARGB32_GET_PIXEL (s, s[0], s[1], s[2], s[3]);
          s += 4;
        }

      src += gdk_pixbuf_get_rowstride (pixbuf);
    }
#else
  #pragma GCC diagnostic push
  #pragma GCC diagnostic ignored "-Wdeprecated-declarations"

  vals.resolution = resolution;
  vals.width      = width;
  vals.height     = height;
  rsvg_handle_set_size_callback (handle, load_set_size_callback, (gpointer) &vals, NULL);
  pixbuf = rsvg_handle_get_pixbuf (handle);

  #pragma GCC diagnostic pop
#endif

  return pixbuf;
}


/*  User interface  */

static GimpSizeEntry *size       = NULL;
static GtkAdjustment *xadj       = NULL;
static GtkAdjustment *yadj       = NULL;
static GtkWidget     *constrain  = NULL;
static gdouble        ratio_x    = 1.0;
static gdouble        ratio_y    = 1.0;
static gint           svg_width  = 0;
static gint           svg_height = 0;

static void  load_dialog_set_ratio (gdouble x,
                                    gdouble y);


static void
load_dialog_size_callback (GtkWidget *widget,
                           gpointer   data)
{
  if (gimp_chain_button_get_active (GIMP_CHAIN_BUTTON (constrain)))
    {
      gdouble x = gimp_size_entry_get_refval (size, 0) / (gdouble) svg_width;
      gdouble y = gimp_size_entry_get_refval (size, 1) / (gdouble) svg_height;

      if (x != ratio_x)
        {
          load_dialog_set_ratio (x, x);
        }
      else if (y != ratio_y)
        {
          load_dialog_set_ratio (y, y);
        }
    }
}

static void
load_dialog_ratio_callback (GtkAdjustment *adj,
                            gpointer       data)
{
  gdouble x = gtk_adjustment_get_value (xadj);
  gdouble y = gtk_adjustment_get_value (yadj);

  if (gimp_chain_button_get_active (GIMP_CHAIN_BUTTON (constrain)))
    {
      if (x != ratio_x)
        y = x;
      else
        x = y;
    }

  load_dialog_set_ratio (x, y);
}

static void
load_dialog_set_ratio (gdouble x,
                       gdouble y)
{
  ratio_x = x;
  ratio_y = y;

  g_signal_handlers_block_by_func (size, load_dialog_size_callback, NULL);

  gimp_size_entry_set_refval (size, 0, svg_width  * x);
  gimp_size_entry_set_refval (size, 1, svg_height * y);

  g_signal_handlers_unblock_by_func (size, load_dialog_size_callback, NULL);

  g_signal_handlers_block_by_func (xadj, load_dialog_ratio_callback, NULL);
  g_signal_handlers_block_by_func (yadj, load_dialog_ratio_callback, NULL);

  gtk_adjustment_set_value (xadj, x);
  gtk_adjustment_set_value (yadj, y);

  g_signal_handlers_unblock_by_func (xadj, load_dialog_ratio_callback, NULL);
  g_signal_handlers_unblock_by_func (yadj, load_dialog_ratio_callback, NULL);
}

static GimpPDBStatusType
load_dialog (GFile                *file,
             RsvgHandle           *handle,
             GimpProcedure        *procedure,
             GimpProcedureConfig  *config,
             GimpVectorLoadData    extracted_data,
             GError              **load_error)
{
  GtkWidget *dialog;
  gboolean   run;

  gimp_ui_init (PLUG_IN_BINARY);

  dialog = gimp_vector_load_procedure_dialog_new (GIMP_VECTOR_LOAD_PROCEDURE (procedure),
                                                  GIMP_PROCEDURE_CONFIG (config),
                                                  &extracted_data, file);
  gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog), NULL);
  run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (dialog));

  gtk_widget_destroy (dialog);

  return run ? GIMP_PDB_SUCCESS : GIMP_PDB_CANCEL;
}

static gboolean
export_dialog (GimpImage     *image,
               GimpProcedure *procedure,
               GObject       *config)
{
  GtkWidget *dialog;
  GtkWidget *box;
  GtkWidget *hint = NULL;
  gboolean   run;
  gboolean   has_nonstandard_links = FALSE;

  dialog = gimp_export_procedure_dialog_new (GIMP_EXPORT_PROCEDURE (procedure),
                                             GIMP_PROCEDURE_CONFIG (config),
                                             image);

  box = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dialog),
                                        "svg-box", "title",
                                        "raster-export-format", NULL);

  has_nonstandard_links = has_invalid_links (gimp_image_get_layers (image),
                                             NULL);
  if (has_nonstandard_links)
    {
      hint =
        g_object_new (GIMP_TYPE_HINT_BOX,
                      "icon-name", GIMP_ICON_DIALOG_WARNING,
                      "hint",      _("The SVG format only requires viewers to "
                                     "display image links for SVG, PNG, and "
                                     "JPEG images.\n"
                                     "Your image links external image files "
                                     "in other formats which may not show up "
                                     "in all viewers."),
                      NULL);
      gtk_widget_set_visible (hint, TRUE);
      gtk_widget_set_margin_bottom (hint, 12);

      gtk_box_pack_start (GTK_BOX (box), hint, FALSE, FALSE, 0);
      gtk_box_reorder_child (GTK_BOX (box), hint, 0);
    }

  gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog), "svg-box", NULL);

  run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (dialog));

  gtk_widget_destroy (dialog);

  return run;
}

static gboolean
has_invalid_links (GimpLayer      **layers,
                   GimpGroupLayer  *group)
{
  GimpItem **temp_layers = (GimpItem **) layers;
  gint32     n_layers;

  if (layers == NULL)
    temp_layers = gimp_item_get_children (GIMP_ITEM (group));

  n_layers = gimp_core_object_array_get_length ((GObject **) temp_layers);

  for (gint i = 0; i < n_layers; i++)
    {
      if (gimp_item_is_link_layer (temp_layers[i]))
        {
          gchar *mimetype =
            gimp_link_layer_get_mime_type (GIMP_LINK_LAYER (temp_layers[i]));

          /* The SVG specification only requires viewers to support
             SVG, PNG, and JPEG links. If the linked layer is not one of
             those types, we'll notify the user */
          if (g_strcmp0 (mimetype, "image/svg+xml") != 0 &&
              g_strcmp0 (mimetype, "image/png") != 0 &&
              g_strcmp0 (mimetype, "image/jpeg") != 0)
            return TRUE;

        }
      else if (gimp_item_is_group (GIMP_ITEM (temp_layers[i])))
        {
          if (has_invalid_links (NULL, GIMP_GROUP_LAYER (temp_layers[i])))
            return TRUE;
        }
    }

  return FALSE;
}

#if LIBRSVG_CHECK_VERSION(2, 46, 0)
static GimpUnit *
svg_rsvg_to_gimp_unit (RsvgUnit unit)
{
  switch (unit)
    {
    case RSVG_UNIT_PERCENT:
      return gimp_unit_percent ();
    case RSVG_UNIT_PX:
      return gimp_unit_pixel ();
    case RSVG_UNIT_IN:
      return gimp_unit_inch ();
    case RSVG_UNIT_MM:
      return gimp_unit_mm ();
    case RSVG_UNIT_PT:
      return gimp_unit_point ();
    case RSVG_UNIT_PC:
      return gimp_unit_pica ();
    case RSVG_UNIT_CM:
    case RSVG_UNIT_EM:
    case RSVG_UNIT_EX:
      /*case RSVG_UNIT_CH:*/
    default:
      return NULL;
    }
}

static void
svg_destroy_surface (guchar          *pixels,
                     cairo_surface_t *surface)
{
  cairo_surface_destroy (surface);
}
#endif

/* Taken from app/path/gimppath-export.c */
static GString *
svg_export_file (GimpImage            *image,
                 GimpProcedureConfig  *config,
                 GError              **error)
{
  GimpLayer **layers;
  gchar      *title        = NULL;
  GString    *str          = g_string_new (NULL);
  GList      *exported_res = NULL;
  gint        layer_ids[6] = { 0 };

  g_object_get (config, "title", &title, NULL);

  g_string_append_printf (str,
                          "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
                          "<svg xmlns=\"http://www.w3.org/2000/svg\"\n"
                          "     xmlns:svg=\"http://www.w3.org/2000/svg\"\n"
                          "     version=\"1.1\"\n");

  g_string_append (str, "     ");
  svg_export_image_size (image, str);
  g_string_append_c (str, '\n');

  g_string_append_printf (str,
                          "     viewBox=\"0 0 %d %d\">\n",
                          gimp_image_get_width  (image),
                          gimp_image_get_height (image));

  /* Optional SVG title */
  if (title && strlen (title))
    g_string_append_printf (str,
                            "  <title>%s</title>\n",
                            title);

  layers = gimp_image_get_layers (image);
  g_string_append (str, "  <defs>\n");
  svg_export_defs ((GimpItem **) layers, NULL, str, config, &exported_res, error);
  g_list_free (exported_res);
  g_string_append (str, "  </defs>\n");
  svg_export_layers ((GimpItem **) layers, NULL, layer_ids, str, config, "", error);
  g_free (layers);

  g_string_append (str, "</svg>\n");

  g_free (title);

  return str;
}

static void
svg_export_defs (GimpItem             **layers,
                 GimpGroupLayer        *group,
                 GString               *str,
                 GimpProcedureConfig   *config,
                 GList                **exported_res,
                 GError               **error)
{
  GimpItem **items;
  gint32     n_layers;

  if (group)
    items = gimp_item_get_children (GIMP_ITEM (group));
  else
    items = layers;

  n_layers = gimp_core_object_array_get_length ((GObject **) items);

  for (gint i = n_layers - 1; i >= 0; i--)
    {
      if (gimp_item_get_visible (items[i]))
        {
          if (gimp_item_is_group (items[i]))
            svg_export_defs (NULL, GIMP_GROUP_LAYER (items[i]), str, config, exported_res, error);
          else if (GIMP_IS_VECTOR_LAYER (items[i]) &&
                   ! gimp_rasterizable_is_rasterized (GIMP_RASTERIZABLE (items[i])))
            svg_export_def (GIMP_VECTOR_LAYER (items[i]), str, config, exported_res, error);
        }
      gimp_progress_update ((gdouble) (n_layers - i) / n_layers);

      if (*error)
        break;
    }
  if (group)
    g_free (items);

  gimp_progress_update (1.0);
}

static void
svg_export_def (GimpVectorLayer      *layer,
                GString              *str,
                GimpProcedureConfig  *config,
                GList               **exported_res,
                GError              **error)
{
  gchar *fill_string;
  gchar *stroke_string;

  fill_string   = svg_get_def_string (layer, "fill", config, exported_res, error);
  stroke_string = svg_get_def_string (layer, "stroke", config, exported_res, error);

  g_string_append_printf (str,
                          "%s%s",
                          fill_string ? fill_string : "",
                          stroke_string ? stroke_string : "");

  g_free (fill_string);
  g_free (stroke_string);
}

static gchar *
svg_get_def_string (GimpVectorLayer      *layer,
                    const gchar          *type,
                    GimpProcedureConfig  *config,
                    GList               **exported_res,
                    GError              **error)
{
  GimpPattern *pattern = NULL;
  gboolean     enabled;

  if (g_strcmp0 ("fill", type) == 0)
    {
      enabled = gimp_vector_layer_get_enable_fill (layer);
      if (enabled)
        pattern = gimp_vector_layer_get_fill_pattern (layer);
    }
  else
    {
      enabled = gimp_vector_layer_get_enable_stroke (layer);
      if (enabled)
        pattern = gimp_vector_layer_get_stroke_pattern (layer);
    }

  if (enabled && pattern && ! g_list_find (*exported_res, pattern))
    {
      gchar          *def_string = NULL;
      GFile          *temp_file  = NULL;
      FILE           *temp_fp;
      gsize           temp_size;
      GimpProcedure  *procedure;
      GimpValueArray *return_vals;
      GimpImage      *temp_image;
      GimpLayer      *temp_layer;
      const gchar    *mimetype;
      gint            width;
      gint            height;
      gint            format_id;

      GeglBuffer     *src_buffer;
      GeglBuffer     *dst_buffer;
      gint            res_id;

      res_id     = gimp_resource_get_id (GIMP_RESOURCE (pattern));
      /* XXX Should we try to output in high-bit depth when possible? */
      src_buffer = gimp_pattern_get_buffer (pattern, 0, 0, babl_format ("R'G'B' u8"));
      width      = gegl_buffer_get_width (src_buffer);
      height     = gegl_buffer_get_height (src_buffer);
      temp_image = gimp_image_new (width, height, GIMP_RGB);
      temp_layer = gimp_layer_new (temp_image, NULL, width, height, GIMP_RGBA_IMAGE,
                                   100.0, GIMP_LAYER_MODE_NORMAL);
      gimp_image_insert_layer (temp_image, temp_layer, NULL, 0);
      dst_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (temp_layer));
      gegl_buffer_copy (src_buffer, NULL, GEGL_ABYSS_NONE, dst_buffer, NULL);

      g_object_unref (src_buffer);
      g_object_unref (dst_buffer);

      format_id = gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config),
                                                       "raster-export-format");
      if (format_id == EXPORT_FORMAT_PNG)
        {
          temp_file = gimp_temp_file ("png");
          mimetype  = "image/png";

          procedure   = gimp_pdb_lookup_procedure (gimp_get_pdb (), "file-png-export");
          return_vals = gimp_procedure_run (procedure,
                                            "run-mode",              GIMP_RUN_NONINTERACTIVE,
                                            "image",                 temp_image,
                                            "file",                  temp_file,
                                            "interlaced",            FALSE,
                                            "compression",           9,
                                            "bkgd",                  FALSE,
                                            "offs",                  FALSE,
                                            "phys",                  FALSE,
                                            "time",                  FALSE,
                                            "save-transparent",      FALSE,
                                            "optimize-palette",      FALSE,
                                            "include-color-profile", FALSE,
                                            NULL);
        }
      else
        {
          temp_file = gimp_temp_file ("jpeg");
          mimetype  = "image/jpeg";

          procedure   = gimp_pdb_lookup_procedure (gimp_get_pdb (), "file-jpeg-export");
          return_vals = gimp_procedure_run (procedure,
                                            "run-mode",              GIMP_RUN_NONINTERACTIVE,
                                            "image",                 temp_image,
                                            "file",                  temp_file,
                                            "quality",               0.9f,
                                            "cmyk",                  FALSE,
                                            "include-color-profile", FALSE,
                                            NULL);
        }
      gimp_image_delete (temp_image);

      if (GIMP_VALUES_GET_ENUM (return_vals, 0) != GIMP_PDB_SUCCESS)
        {
          if (! error)
            {
              if (G_VALUE_HOLDS_STRING (gimp_value_array_index (return_vals, 1)))
                g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                             "SVG: Unable to export pattern image data: %s",
                             GIMP_VALUES_GET_STRING (return_vals, 1));
              else
                g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                                     "SVG: Unable to export pattern image data.");
            }

          g_object_unref (temp_file);

          return NULL;
        }

      temp_fp = g_fopen (g_file_peek_path (temp_file), "rb");
      fseek (temp_fp, 0L, SEEK_END);
      temp_size = ftell (temp_fp);
      fseek (temp_fp, 0L, SEEK_SET);

      if (temp_size > 0)
        {
          guchar *buf;
          gchar  *encoded;

          buf = g_malloc0 (temp_size);
          fread (buf, 1, temp_size, temp_fp);

          encoded = g_base64_encode ((const guchar *) buf, temp_size + 1);

          def_string = g_strdup_printf ("    <pattern id=\"Pattern%d\"\n"
                                        "             patternUnits=\"userSpaceOnUse\"\n"
                                        "             width=\"%d\"\n"
                                        "             height=\"%d\">\n"
                                        "      <image href=\"data:%s;base64,%s\" />\n"
                                        "    </pattern>\n",
                                        res_id, width, height, mimetype, encoded);

          g_free (encoded);
          g_free (buf);
        }

      fclose (temp_fp);
      g_file_delete (temp_file, NULL, NULL);
      g_object_unref (temp_file);

      *exported_res = g_list_prepend (*exported_res, pattern);

      return def_string;
    }

  return NULL;
}

static void
svg_export_layers (GimpItem             **layers,
                   GimpGroupLayer        *group,
                   gint                  *layer_ids,
                   GString               *str,
                   GimpProcedureConfig   *config,
                   gchar                 *spacing,
                   GError               **error)
{
  GimpItem **items;
  gint32     n_layers;
  gchar     *extra_spacing;
  gint       format_id;

  if (group)
    items = gimp_item_get_children (GIMP_ITEM (group));
  else
    items = layers;

  format_id = gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config),
                                                   "raster-export-format");

  extra_spacing = g_strdup_printf ("%s  ", spacing);

  n_layers = gimp_core_object_array_get_length ((GObject **) items);

  for (gint i = n_layers - 1; i >= 0; i--)
    {
      if (gimp_item_get_visible (items[i]))
        {
          if (gimp_item_is_group (items[i]))
            {
              svg_export_group_header (GIMP_GROUP_LAYER (items[i]),
                                       layer_ids[LAYER_ID_GROUP]++, str,
                                       extra_spacing);
              svg_export_layers (NULL, GIMP_GROUP_LAYER (items[i]), layer_ids,
                                 str, config, extra_spacing, error);
              g_string_append_printf (str, "%s</g>\n", extra_spacing);
            }
          else if (GIMP_IS_RASTERIZABLE (items[i]) &&
                   ! gimp_rasterizable_is_rasterized (GIMP_RASTERIZABLE (items[i])))
            {
              if (GIMP_IS_VECTOR_LAYER (items[i]))
                {
                  GimpVectorLayer *layer = GIMP_VECTOR_LAYER (items[i]);

                  svg_export_path (layer, layer_ids[LAYER_ID_VECTOR]++, str,
                                   extra_spacing);
                }
              else if (GIMP_IS_TEXT_LAYER (items[i]))
                {
                  GimpTextLayer *layer = GIMP_TEXT_LAYER (items[i]);

                  svg_export_text (layer, layer_ids[LAYER_ID_TEXT]++, str,
                                   extra_spacing);
                }
              else if (GIMP_IS_LINK_LAYER (items[i]))
                {
                  GimpLinkLayer *layer = GIMP_LINK_LAYER (items[i]);

                  svg_export_link_layer (layer, layer_ids[LAYER_ID_LINK]++, str,
                                         extra_spacing);
                }
            }
          else if (format_id != EXPORT_FORMAT_NONE)
            {
              svg_export_raster (GIMP_DRAWABLE (items[i]), layer_ids, str,
                                 format_id, extra_spacing, error);
            }
        }
      gimp_progress_update ((gdouble) (n_layers - i) / n_layers);
    }
  g_free (extra_spacing);
  if (group)
    g_free (items);

  gimp_progress_update (1.0);
}

static void
svg_export_group_header (GimpGroupLayer *group,
                         gint            group_id,
                         GString        *str,
                         gchar          *spacing)
{
  gchar   *name;
  gdouble  opacity;

  name    = g_strdup_printf ("group%d", group_id);
  opacity = gimp_layer_get_opacity (GIMP_LAYER (group)) / 100.0f;

  g_string_append_printf (str, "%s<g id=\"%s\" opacity=\"%f\">\n",
                          spacing, name, opacity);
  g_free (name);
}

static void
svg_export_image_size (GimpImage *image,
                       GString   *str)
{
  GimpUnit    *unit;
  const gchar *abbrev;
  gchar        wbuf[G_ASCII_DTOSTR_BUF_SIZE];
  gchar        hbuf[G_ASCII_DTOSTR_BUF_SIZE];
  gdouble      xres;
  gdouble      yres;
  gdouble      w, h;

  gimp_image_get_resolution (image, &xres, &yres);

  w = (gdouble) gimp_image_get_width  (image) / xres;
  h = (gdouble) gimp_image_get_height (image) / yres;

  /*  FIXME: should probably use the display unit here  */
  unit = gimp_image_get_unit (image);
  switch (gimp_unit_get_id (unit))
    {
    case GIMP_UNIT_INCH:  abbrev = "in";  break;
    case GIMP_UNIT_MM:    abbrev = "mm";  break;
    case GIMP_UNIT_POINT: abbrev = "pt";  break;
    case GIMP_UNIT_PICA:  abbrev = "pc";  break;
    default:              abbrev = "cm";
      unit = gimp_unit_mm ();
      w /= 10.0;
      h /= 10.0;
      break;
    }

  g_ascii_formatd (wbuf, sizeof (wbuf), "%g", w * gimp_unit_get_factor (unit));
  g_ascii_formatd (hbuf, sizeof (hbuf), "%g", h * gimp_unit_get_factor (unit));

  g_string_append_printf (str,
                          "width=\"%s%s\" height=\"%s%s\"",
                          wbuf, abbrev, hbuf, abbrev);
}

static void
svg_export_path (GimpVectorLayer *layer,
                 gint             vector_id,
                 GString         *str,
                 gchar           *spacing)
{
  GimpPath    *path;
  gchar       *name;
  gchar       *data;
  gdouble      opacity;
  gchar       *fill_string;
  gchar       *stroke_string;
  gdouble      stroke_width;
  const gchar *stroke_capstyle;
  const gchar *stroke_joinstyle;
  gdouble      stroke_miter_limit;
  gdouble      stroke_dash_offset;
  gsize        num_dashes;
  gdouble     *stroke_dash_pattern;

  path = gimp_vector_layer_get_path (layer);
  if (! path)
    return;

  name = g_strdup_printf ("vector%d", vector_id);
  data = svg_export_path_data (path);

  opacity = gimp_layer_get_opacity (GIMP_LAYER (layer)) / 100.0f;

  /* Vector Layer Properties */
  fill_string         = svg_get_color_string (layer, "fill");
  stroke_string       = svg_get_color_string (layer, "stroke");
  stroke_width        = gimp_vector_layer_get_stroke_width (layer);
  stroke_miter_limit  = gimp_vector_layer_get_stroke_miter_limit (layer);
  stroke_dash_offset  = gimp_vector_layer_get_stroke_dash_offset (layer);

  gimp_vector_layer_get_stroke_dash_pattern (layer, &num_dashes,
                                             &stroke_dash_pattern);

  switch (gimp_vector_layer_get_stroke_cap_style (layer))
    {
    case GIMP_CAP_ROUND:
      stroke_capstyle = "round";
      break;

    case GIMP_CAP_SQUARE:
      stroke_capstyle = "square";
      break;

    default:
      stroke_capstyle = "butt";
      break;
    }

  switch (gimp_vector_layer_get_stroke_join_style (layer))
    {
    case GIMP_JOIN_ROUND:
      stroke_joinstyle = "round";
      break;

    case GIMP_JOIN_BEVEL:
      stroke_joinstyle = "bevel";
      break;

    default:
      stroke_joinstyle = "miter";
      break;
    }

  g_string_append_printf (str,
                          "%s<path id=\"%s\"\n"
                          "%s      %s\n"
                          "%s      %s\n"
                          "%s      opacity=\"%f\"\n"
                          "%s      stroke-width=\"%f\"\n"
                          "%s      stroke-linecap=\"%s\"\n"
                          "%s      stroke-linejoin=\"%s\"\n"
                          "%s      stroke-miterlimit=\"%f\"\n",
                          spacing, name,
                          spacing, fill_string,
                          spacing, stroke_string,
                          spacing, opacity,
                          spacing, stroke_width,
                          spacing, stroke_capstyle,
                          spacing, stroke_joinstyle,
                          spacing, stroke_miter_limit);

  if (num_dashes > 0)
    {
      for (gint i = 0; i < num_dashes; i++)
        stroke_dash_pattern[i] *= stroke_width;

      g_string_append_printf (str,
                              "%s      stroke-dashoffset=\"%f\"\n"
                              "%s      stroke-dasharray=\"%f",
                              spacing, stroke_dash_offset,
                              spacing, stroke_dash_pattern[0]);

      for (gint i = 0; i < num_dashes; i++)
        g_string_append_printf (str, " %f", stroke_dash_pattern[i]);

      g_string_append_printf (str, "\"\n");
    }

   g_string_append_printf (str,
                           "%s      d=\"%s\" />\n",
                           spacing, data);

  g_free (fill_string);
  g_free (stroke_string);
  g_free (name);
  g_free (data);
}


#define NEWLINE "\n           "

static gchar *
svg_export_path_data (GimpPath *path)
{
  GString  *str;
  gsize     num_strokes;
  gint     *strokes;
  gint      s;
  gchar     x_string[G_ASCII_DTOSTR_BUF_SIZE];
  gchar     y_string[G_ASCII_DTOSTR_BUF_SIZE];
  gboolean  closed = FALSE;

  str = g_string_new (NULL);

  strokes = gimp_path_get_strokes (path, &num_strokes);
  for (s = 0; s < num_strokes; s++)
    {
      GimpPathStrokeType  type;
      gdouble            *control_points;
      gsize               num_points;
      gsize               num_indexes;

      type = gimp_path_stroke_get_points (path, strokes[s], &num_points,
                                          &control_points, &closed);

      if (type == GIMP_PATH_STROKE_TYPE_BEZIER)
        {
          num_indexes = num_points / 2;
          if (num_indexes >= 3)
            {
              g_ascii_formatd (x_string, G_ASCII_DTOSTR_BUF_SIZE,
                               "%.2f", control_points[2]);
              g_ascii_formatd (y_string, G_ASCII_DTOSTR_BUF_SIZE,
                               "%.2f", control_points[3]);
              if (s > 0)
                g_string_append_printf (str, NEWLINE);
              g_string_append_printf (str, "M %s,%s", x_string, y_string);
            }

          if (num_indexes > 3)
            g_string_append_printf (str, NEWLINE "C");

          for (gint i = 2; i < (num_indexes + (closed ? 2 : - 1)); i++)
            {
              gint index = (i % num_indexes) * 2;

              if (i > 2 && i % 3 == 2)
                g_string_append_printf (str, NEWLINE " ");

              g_ascii_formatd (x_string, G_ASCII_DTOSTR_BUF_SIZE,
                               "%.2f", control_points[index]);
              g_ascii_formatd (y_string, G_ASCII_DTOSTR_BUF_SIZE,
                               "%.2f", control_points[index + 1]);
              g_string_append_printf (str, " %s,%s", x_string, y_string);
            }

          if (closed && num_points > 3)
            g_string_append_printf (str, " Z");
        }
    }

  return g_strchomp (g_string_free (str, FALSE));
}

static void
svg_export_text (GimpTextLayer *layer,
                 gint           text_id,
                 GString       *str,
                 gchar         *spacing)
{
  gchar                *name;
  gint                  x     = 0;
  gint                  y     = 0;
  gdouble               x_res = 0;
  gdouble               y_res = 0;
  gdouble               opacity;
  gchar                *hex_color;
  gdouble               font_size;
  GimpUnit             *unit;
  GimpFont             *gimp_font;
  PangoFontDescription *font_description;
  PangoFontDescription *font_description2;
  PangoFontMap         *fontmap;
  PangoFont            *font;
  PangoContext         *context;

  name = g_strdup_printf ("text%d", text_id);

  gimp_drawable_get_offsets (GIMP_DRAWABLE (layer), &x, &y);
  gimp_image_get_resolution (gimp_item_get_image (GIMP_ITEM (layer)),
                             &x_res, &y_res);

  opacity   = gimp_layer_get_opacity (GIMP_LAYER (layer)) / 100.0f;
  hex_color = svg_get_hex_color (gimp_text_layer_get_color (layer));

  g_string_append_printf (str,
                          "%s<text id=\"%s\"\n"
                          "%s      x=\"%d\"\n"
                          "%s      y=\"%d\"\n"
                          "%s      opacity=\"%f\"\n"
                          "%s      fill=\"%s\"\n",
                          spacing, name,
                          spacing, x,
                          spacing, y,
                          spacing, opacity,
                          spacing, hex_color);
  g_free (hex_color);
  g_free (name);

  /* Font style */
  font_size = gimp_text_layer_get_font_size (layer, &unit);
  font_size = gimp_units_to_pixels (font_size, unit, y_res);

  fontmap = pango_cairo_font_map_new_for_font_type (CAIRO_FONT_TYPE_FT);
  context = pango_font_map_create_context (fontmap);

  gimp_font         = gimp_text_layer_get_font (layer);
  font_description  = gimp_font_get_pango_font_description (gimp_font);
  font              = pango_font_map_load_font (fontmap, context,
                                                font_description);
  font_description2 = pango_font_describe (font);

  g_string_append_printf (str,
                          "%s      style=\"font-size:%fpx; "
                          "font-family:%s; font-weight:%d; "
                          "letter-spacing:%fpx;\">",
                          spacing, font_size,
                          pango_font_description_get_family (font_description2),
                          pango_font_description_get_weight (font_description2),
                          gimp_text_layer_get_letter_spacing (layer));

  g_object_unref (font);
  pango_font_description_free (font_description);
  pango_font_description_free (font_description2);
  g_object_unref (context);
  g_object_unref (fontmap);

  /* Text */
  if (gimp_text_layer_get_markup (layer))
    {
      GString *markup;

      markup = g_string_new (gimp_text_layer_get_markup (layer));

      g_string_replace (markup, "<markup>", "", 1);
      g_string_replace (markup, "</markup>", "", 1);

      g_string_replace (markup, "<b>",
                        "<tspan style=\"font-weight: bold\">", 0);
      g_string_replace (markup, "</b>", "</tspan>", 0);

      g_string_replace (markup, "<i>",
                        "<tspan style=\"font-style: italic\">", 0);
      g_string_replace (markup, "</i>", "</tspan>", 0);

      g_string_replace (markup, "<u>",
                        "<tspan style=\"text-decoration: underline\">", 0);
      g_string_replace (markup, "</u>", "</tspan>", 0);

      g_string_replace (markup, "<s>",
                        "<tspan style=\"text-decoration: linethrough\">", 0);
      g_string_replace (markup, "</s>", "</tspan>", 0);

      g_string_replace (markup, "<span foreground=\"", "<tspan style=\"fill:", 0);
      g_string_replace (markup, "<span font=\"", "<tspan style=\"font-family:", 0);

      g_string_replace (markup, "<span ", "<tspan ", 0);
      g_string_replace (markup, "</span>", "</tspan>", 0);

      svg_export_text_lines (layer, str, markup->str);
      g_string_free (markup, TRUE);
    }
  else
    {
      svg_export_text_lines (layer, str, gimp_text_layer_get_text (layer));
    }

  g_string_append_printf (str, "</text>\n");
}

static void
svg_export_text_lines (GimpTextLayer *layer,
                       GString       *str,
                       gchar         *text)
{
  gint                  x = 0;
  gint                  y = 0;
  gdouble               x_res = 0;
  gdouble               y_res = 0;
  gdouble               font_size;
  GimpUnit             *unit;
  gdouble               line_spacing;
  gchar               **lines;
  guint                 count;

  gimp_drawable_get_offsets (GIMP_DRAWABLE (layer), &x, &y);
  gimp_image_get_resolution (gimp_item_get_image (GIMP_ITEM (layer)),
                             &x_res, &y_res);

  line_spacing = gimp_text_layer_get_line_spacing (layer);
  font_size    = gimp_text_layer_get_font_size (layer, &unit);
  font_size    = gimp_units_to_pixels (font_size, unit, y_res);

  lines = g_strsplit (text, "\n", -1);
  count = g_strv_length (lines);

  for (gint i = 0; i < count; i++)
    {
      g_string_append_printf (str, "<tspan x=\"%d\" y=\"%d\">"
                              "%s</tspan>",
                              x, y, lines[i]);
      y += font_size + line_spacing;
    }
  g_strfreev (lines);
}

static void
svg_export_link_layer (GimpLinkLayer *layer,
                       gint           link_id,
                       GString       *str,
                       gchar         *spacing)
{
  GFile   *file;
  gchar   *name;
  gchar   *path;
  gint     width;
  gint     height;
  gdouble  opacity;
  gint     x;
  gint     y;

  width   = gimp_drawable_get_width (GIMP_DRAWABLE (layer));
  height  = gimp_drawable_get_height (GIMP_DRAWABLE (layer));
  opacity = gimp_layer_get_opacity (GIMP_LAYER (layer)) / 100.0f;
  gimp_drawable_get_offsets (GIMP_DRAWABLE (layer), &x, &y);

  name = g_strdup_printf ("link%d", link_id);

  file = gimp_link_layer_get_file (layer);
  path = g_file_get_path (file);

  g_string_append_printf (str,
                          "%s<image id=\"%s\"\n"
                          "%s       x=\"%d\"\n"
                          "%s       y=\"%d\"\n"
                          "%s       width=\"%d\"\n"
                          "%s       height=\"%d\"\n"
                          "%s       opacity=\"%f\"\n"
                          "%s       href=\"%s\" />\n",
                          spacing, name,
                          spacing, x,
                          spacing, y,
                          spacing, width,
                          spacing, height,
                          spacing, opacity,
                          spacing, path);
  g_free (name);
  g_free (path);
}

static void
svg_export_raster (GimpDrawable  *layer,
                   gint          *layer_ids,
                   GString       *str,
                   gint           format_id,
                   gchar         *spacing,
                   GError       **error)
{
  GimpProcedure  *procedure;
  GimpValueArray *return_vals = NULL;
  GimpImage      *image;
  GimpImage      *temp_image;
  GimpLayer      *temp_layer;
  GFile          *temp_file = NULL;
  FILE           *temp_fp;
  gsize           temp_size;
  gint            raster_id;
  gint            width;
  gint            height;
  gdouble         opacity;
  gboolean        include_color_profile;
  gboolean        has_layer_mask;
  const gchar    *mimetype;

  has_layer_mask = FALSE;
  if (GIMP_IS_LAYER (layer) && gimp_layer_get_mask (GIMP_LAYER (layer)))
    {
      svg_export_layer_mask (gimp_layer_get_mask (GIMP_LAYER (layer)),
                             layer_ids, str, spacing, error);
      has_layer_mask = TRUE;
    }

  if (format_id == EXPORT_FORMAT_JPEG && svg_drawable_has_transparency (layer))
    format_id = EXPORT_FORMAT_PNG;

  if (format_id == EXPORT_FORMAT_PNG)
    {
      temp_file = gimp_temp_file ("png");
      mimetype  = "image/png";
    }
  else
    {
      temp_file = gimp_temp_file ("jpeg");
      mimetype  = "image/jpeg";
    }
  raster_id = layer_ids[LAYER_ID_RASTER]++;

  width  = gimp_drawable_get_width (layer);
  height = gimp_drawable_get_height (layer);
  if (GIMP_IS_LAYER (layer))
    opacity = gimp_layer_get_opacity (GIMP_LAYER (layer)) / 100.0f;
  else
    opacity = gimp_channel_get_opacity (GIMP_CHANNEL (layer)) / 100.0f;

  image = gimp_item_get_image (GIMP_ITEM (layer));
  temp_image = gimp_image_new (width, height,
                               gimp_image_get_base_type (image));
  if (gimp_image_get_base_type (image) == GIMP_INDEXED)
    gimp_image_set_palette (temp_image,
                            gimp_image_get_palette (image));

  temp_layer = gimp_layer_new_from_drawable (layer, temp_image);
  gimp_image_insert_layer (temp_image, temp_layer, NULL, 0);
  gimp_layer_set_offsets (temp_layer, 0, 0);
  /* Set layer to full opacity and use opacity attribute instead */
  gimp_layer_set_opacity (temp_layer, 100.0f);

  include_color_profile = FALSE;
  if (gimp_image_get_color_profile (image))
    {
      GimpColorProfile *profile;

      profile =  gimp_image_get_color_profile (image);
      gimp_image_set_color_profile (temp_image, profile);
      include_color_profile = TRUE;
    }

  if (format_id == EXPORT_FORMAT_PNG)
    {
      procedure   = gimp_pdb_lookup_procedure (gimp_get_pdb (), "file-png-export");
      return_vals = gimp_procedure_run (procedure,
                                        "run-mode",              GIMP_RUN_NONINTERACTIVE,
                                        "image",                 temp_image,
                                        "file",                  temp_file,
                                        "interlaced",            FALSE,
                                        "compression",           9,
                                        "bkgd",                  FALSE,
                                        "offs",                  FALSE,
                                        "phys",                  FALSE,
                                        "time",                  FALSE,
                                        "save-transparent",      FALSE,
                                        "optimize-palette",      FALSE,
                                        "include-color-profile", include_color_profile,
                                        NULL);
    }
  else
    {
      procedure   = gimp_pdb_lookup_procedure (gimp_get_pdb (), "file-jpeg-export");
      return_vals = gimp_procedure_run (procedure,
                                        "run-mode",              GIMP_RUN_NONINTERACTIVE,
                                        "image",                 temp_image,
                                        "file",                  temp_file,
                                        "quality",               0.9f,
                                        "cmyk",                  FALSE,
                                        "include-color-profile", include_color_profile,
                                        NULL);
    }
  gimp_image_delete (temp_image);

  if (GIMP_VALUES_GET_ENUM (return_vals, 0) != GIMP_PDB_SUCCESS)
    {
      if (! error)
        g_set_error (error, G_FILE_ERROR, 0,
                     "SVG: Unable to export raster layer.");

      return;
    }

  temp_fp = g_fopen (g_file_peek_path (temp_file), "rb");
  fseek (temp_fp, 0L, SEEK_END);
  temp_size = ftell (temp_fp);
  fseek (temp_fp, 0L, SEEK_SET);

  if (temp_size > 0)
    {
      guchar *buf;
      gchar  *encoded;
      gchar  *name;
      gint    x;
      gint    y;

      buf = g_malloc0 (temp_size);
      fread (buf, 1, temp_size, temp_fp);

      encoded = g_base64_encode ((const guchar *) buf, temp_size + 1);

      name = g_strdup_printf ("raster%d", raster_id);
      gimp_drawable_get_offsets (GIMP_DRAWABLE (layer), &x, &y);

      g_string_append_printf (str,
                              "%s<image id=\"%s\"\n"
                              "%s       x=\"%d\"\n"
                              "%s       y=\"%d\"\n"
                              "%s       width=\"%d\"\n"
                              "%s       height=\"%d\"\n"
                              "%s       opacity=\"%f\"\n",
                              spacing, name,
                              spacing, x,
                              spacing, y,
                              spacing, width,
                              spacing, height,
                              spacing, opacity);

      if (has_layer_mask)
        g_string_append_printf (str,
                                "%s       mask=\"url(#mask%d)\"\n",
                                spacing, layer_ids[LAYER_ID_MASK]);

      g_string_append_printf (str,
                              "%s       href=\"data:%s;base64,%s\" />\n",
                              spacing, mimetype, encoded);
      g_free (encoded);
      g_free (name);
      g_free (buf);
    }
  fclose (temp_fp);

  g_file_delete (temp_file, NULL, NULL);
  g_object_unref (temp_file);
}

static void
svg_export_layer_mask (GimpLayerMask  *mask,
                       gint           *layer_ids,
                       GString        *str,
                       gchar          *spacing,
                       GError        **error)
{
  gchar *name;
  gchar *extra_spacing;
  gint   mask_id;
  gint   x = 0;
  gint   y = 0;

  mask_id = layer_ids[LAYER_ID_MASK]++;
  name    = g_strdup_printf ("mask%d", mask_id);
  gimp_drawable_get_offsets (GIMP_DRAWABLE (mask), &x, &y);

  g_string_append_printf (str,
                          "%s<mask id=\"%s\"\n"
                          "%s      x=\"%d\"\n"
                          "%s      y=\"%d\"\n"
                          "%s      mask-type=\"luminance\">\n",
                          spacing, name,
                          spacing, x,
                          spacing, y,
                          spacing);
  g_free (name);

  extra_spacing = g_strdup_printf ("%s  ", spacing);

  svg_export_raster (GIMP_DRAWABLE (mask), layer_ids, str, EXPORT_FORMAT_PNG,
                     extra_spacing, error);

  g_free (extra_spacing);
  g_string_append_printf (str, "</mask>\n");
}

static gchar *
svg_get_color_string (GimpVectorLayer *layer,
                      const gchar     *type)
{
  GeglColor   *color   = NULL;
  GimpPattern *pattern = NULL;
  gboolean     enabled = TRUE;

  if (g_strcmp0 ("fill", type) == 0)
    {
      color   = gimp_vector_layer_get_fill_color (layer);
      if (color == NULL)
        pattern = gimp_vector_layer_get_fill_pattern (layer);
      enabled = gimp_vector_layer_get_enable_fill (layer);
    }
  else
    {
      color   = gimp_vector_layer_get_stroke_color (layer);
      if (color == NULL)
        pattern = gimp_vector_layer_get_stroke_pattern (layer);
      enabled = gimp_vector_layer_get_enable_stroke (layer);
    }

  if (enabled)
    {
      if (color)
        {
          gchar *hex_color;
          gchar *color_string;

          hex_color = svg_get_hex_color (color);

          color_string = g_strdup_printf ("%s=\"%s\"",
                                          type, hex_color);
          g_free (hex_color);
          g_clear_object (&color);

          return color_string;
        }
      else if (pattern)
        {
          gchar *color_string;
          gint   res_id = gimp_resource_get_id (GIMP_RESOURCE (pattern));

          color_string = g_strdup_printf ("%s=\"url(#Pattern%d)\"",
                                          type, res_id);

          return color_string;
        }
    }

  g_clear_object (&color);

  return g_strdup_printf ("%s=\"none\"", type);
}

static gchar *
svg_get_hex_color (GeglColor *color)
{
  guchar  rgb[3] = { 0, 0, 0 };
  gchar  *hex_color = NULL;

  gegl_color_get_pixel (color, babl_format ("R'G'B' u8"), rgb);
  hex_color = g_strdup_printf ("#%02X%02X%02X", rgb[0], rgb[1], rgb[2]);

  return hex_color;
}

static gboolean
svg_drawable_has_transparency (GimpDrawable *drawable)
{
  if (gimp_drawable_has_alpha (drawable))
    {
      GeglBuffer         *buffer;
      GeglBufferIterator *iter;

      buffer = gimp_drawable_get_buffer (drawable);
      iter   = gegl_buffer_iterator_new (buffer, NULL, 0, babl_format ("A float"),
                                         GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);

      while (gegl_buffer_iterator_next (iter))
        {
          gfloat *d = iter->items[0].data;
          gint    i;

          for (i = 0; i < iter->length; i++)
            {
              if (*d != 1.0)
                {
                  g_object_unref (buffer);
                  return TRUE;
                }

              d++;
            }
        }

      g_object_unref (buffer);
    }

  return FALSE;
}
