/* 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 . */ /* SVG loader plug-in * (C) Copyright 2003 Dom Lachowicz * * Largely rewritten in September 2003 by Sven Neumann */ #include "config.h" #include #include #include #include "libgimp/gimp.h" #include "libgimp/gimpui.h" #include "libgimp/stdplugins-intl.h" #define LOAD_PROC "file-svg-load" #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 typedef struct { gdouble resolution; gint width; gint height; } SvgLoadVals; 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 GimpUnit * svg_rsvg_to_gimp_unit (RsvgUnit unit); 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 GimpImage * load_image (GFile *file, RsvgHandle *handle, gint width, gint height, gdouble resolution, 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 void svg_destroy_surface (guchar *pixels, cairo_surface_t *surface); 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)); 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 the SVG file format", "Renders SVG files to raster graphics " "using librsvg.", name); gimp_procedure_set_attribution (procedure, "Dom Lachowicz, Sven Neumann", "Dom Lachowicz ", 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,%s", _("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; } /* 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 *data_for_run = handle; *data_for_run_destroy = g_object_unref; 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 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; } #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; #else SvgLoadVals vals; #endif g_return_val_if_fail (handle, NULL); rsvg_handle_set_dpi (handle, resolution); #if LIBRSVG_CHECK_VERSION(2, 46, 0) surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); cr = cairo_create (surface); viewport.width = width; viewport.height = height; 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, 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 void svg_destroy_surface (guchar *pixels, cairo_surface_t *surface) { cairo_surface_destroy (surface); }