Eine aufbereitete Darstellung der Quelle

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

Benutzer

Quelle  file-svg.c   Sprache: C

 

/* 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
  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);
#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;
}

Messung
CHZG
83919890

¤ Dauer der Verarbeitung: 0.51 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

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

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104856
#Domains=417294