/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * 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 "libpika/pika.h" #include "libpika/pikaui.h" #include "libpika/stdplugins-intl.h" #define LOAD_PROC "file-svg-load" #define LOAD_THUMB_PROC "file-svg-load-thumb" #define PLUG_IN_BINARY "file-svg" #define PLUG_IN_ROLE "pika-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; gboolean import; gboolean merge; } SvgLoadVals; typedef struct _Svg Svg; typedef struct _SvgClass SvgClass; struct _Svg { PikaPlugIn parent_instance; }; struct _SvgClass { PikaPlugInClass 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 (PikaPlugIn *plug_in); static PikaProcedure * svg_create_procedure (PikaPlugIn *plug_in, const gchar *name); static PikaValueArray * svg_load (PikaProcedure *procedure, PikaRunMode run_mode, GFile *file, const PikaValueArray *args, gpointer run_data); static PikaValueArray * svg_load_thumb (PikaProcedure *procedure, GFile *file, gint size, const PikaValueArray *args, gpointer run_data); static PikaImage * load_image (GFile *file, RsvgHandleFlags rsvg_flags, GError **error); static GdkPixbuf * load_rsvg_pixbuf (GFile *file, SvgLoadVals *vals, RsvgHandleFlags rsvg_flags, gboolean *allow_retry, GError **error); static gboolean load_rsvg_size (GFile *file, SvgLoadVals *vals, RsvgHandleFlags rsvg_flags, GError **error); static PikaPDBStatusType load_dialog (GFile *file, RsvgHandleFlags *rsvg_flags, GError **error); G_DEFINE_TYPE (Svg, svg, PIKA_TYPE_PLUG_IN) PIKA_MAIN (SVG_TYPE) DEFINE_STD_SET_I18N static SvgLoadVals load_vals = { SVG_DEFAULT_RESOLUTION, 0, 0, FALSE, FALSE }; static void svg_class_init (SvgClass *klass) { PikaPlugInClass *plug_in_class = PIKA_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 (PikaPlugIn *plug_in) { GList *list = NULL; list = g_list_append (list, g_strdup (LOAD_THUMB_PROC)); list = g_list_append (list, g_strdup (LOAD_PROC)); return list; } static PikaProcedure * svg_create_procedure (PikaPlugIn *plug_in, const gchar *name) { PikaProcedure *procedure = NULL; if (! strcmp (name, LOAD_PROC)) { procedure = pika_load_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, svg_load, NULL, NULL); pika_procedure_set_menu_label (procedure, _("SVG image")); pika_procedure_set_documentation (procedure, "Loads files in the SVG file format", "Renders SVG files to raster graphics " "using librsvg.", name); pika_procedure_set_attribution (procedure, "Dom Lachowicz, Sven Neumann", "Dom Lachowicz ", SVG_VERSION); pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure), "image/svg+xml"); pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure), "svg"); pika_file_procedure_set_magics (PIKA_FILE_PROCEDURE (procedure), "0,string,", SVG_VERSION); } return procedure; } static PikaValueArray * svg_load (PikaProcedure *procedure, PikaRunMode run_mode, GFile *file, const PikaValueArray *args, gpointer run_data) { PikaValueArray *return_vals; PikaImage *image; GError *error = NULL; RsvgHandleFlags rsvg_flags = RSVG_HANDLE_FLAGS_NONE; PikaPDBStatusType status; gegl_init (NULL, NULL); switch (run_mode) { case PIKA_RUN_NONINTERACTIVE: load_vals.resolution = PIKA_VALUES_GET_DOUBLE (args, 0); load_vals.width = PIKA_VALUES_GET_INT (args, 1); load_vals.height = PIKA_VALUES_GET_INT (args, 2); load_vals.import = PIKA_VALUES_GET_INT (args, 3) != FALSE; load_vals.merge = PIKA_VALUES_GET_INT (args, 3) == 2; break; case PIKA_RUN_INTERACTIVE: pika_get_data (LOAD_PROC, &load_vals); status = load_dialog (file, &rsvg_flags, &error); if (status != PIKA_PDB_SUCCESS) return pika_procedure_new_return_values (procedure, status, error); break; case PIKA_RUN_WITH_LAST_VALS: pika_get_data (LOAD_PROC, &load_vals); break; } image = load_image (file, rsvg_flags, &error); if (! image) return pika_procedure_new_return_values (procedure, PIKA_PDB_EXECUTION_ERROR, error); if (load_vals.import) { PikaVectors **vectors; gint num_vectors; pika_vectors_import_from_file (image, file, load_vals.merge, TRUE, &num_vectors, &vectors); if (num_vectors > 0) g_free (vectors); } if (run_mode != PIKA_RUN_NONINTERACTIVE) pika_set_data (LOAD_PROC, &load_vals, sizeof (load_vals)); return_vals = pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL); PIKA_VALUES_SET_IMAGE (return_vals, 1, image); return return_vals; } static PikaValueArray * svg_load_thumb (PikaProcedure *procedure, GFile *file, gint size, const PikaValueArray *args, gpointer run_data) { PikaValueArray *return_vals; gint width = 0; gint height = 0; PikaImage *image; GError *error = NULL; gegl_init (NULL, NULL); if (load_rsvg_size (file, &load_vals, RSVG_HANDLE_FLAGS_NONE, NULL)) { width = load_vals.width; height = load_vals.height; } load_vals.resolution = SVG_DEFAULT_RESOLUTION; load_vals.width = - size; load_vals.height = - size; image = load_image (file, RSVG_HANDLE_FLAGS_NONE, &error); if (! image) return pika_procedure_new_return_values (procedure, PIKA_PDB_EXECUTION_ERROR, error); return_vals = pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL); PIKA_VALUES_SET_IMAGE (return_vals, 1, image); PIKA_VALUES_SET_INT (return_vals, 2, width); PIKA_VALUES_SET_INT (return_vals, 3, height); pika_value_array_truncate (return_vals, 4); return return_vals; } static PikaImage * load_image (GFile *file, RsvgHandleFlags rsvg_flags, GError **load_error) { PikaImage *image; PikaLayer *layer; GdkPixbuf *pixbuf; gint width; gint height; GError *error = NULL; pixbuf = load_rsvg_pixbuf (file, &load_vals, rsvg_flags, NULL, &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"), pika_file_get_utf8_name (file), error ? error->message : _("Unknown reason")); g_clear_error (&error); return NULL; } pika_progress_init (_("Rendering SVG")); width = gdk_pixbuf_get_width (pixbuf); height = gdk_pixbuf_get_height (pixbuf); image = pika_image_new (width, height, PIKA_RGB); pika_image_undo_disable (image); pika_image_set_resolution (image, load_vals.resolution, load_vals.resolution); layer = pika_layer_new_from_pixbuf (image, _("Rendered SVG"), pixbuf, 100, pika_image_get_default_new_layer_mode (image), 0.0, 1.0); pika_image_insert_layer (image, layer, NULL, 0); pika_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 PIKA 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 (GFile *file, SvgLoadVals *vals, RsvgHandleFlags rsvg_flags, gboolean *allow_retry, GError **error) { GdkPixbuf *pixbuf = NULL; RsvgHandle *handle; #if LIBRSVG_CHECK_VERSION(2, 46, 0) cairo_surface_t *surf = NULL; cairo_t *cr = NULL; RsvgRectangle viewport = { 0, }; guchar *src; gint y; #endif handle = rsvg_handle_new_from_gfile_sync (file, rsvg_flags, NULL, error); if (! handle) { /* The "huge data" error will be seen from the handle creation * already. No need to retry when the error occurs later. */ if (allow_retry) *allow_retry = TRUE; return NULL; } rsvg_handle_set_dpi (handle, vals->resolution); #if LIBRSVG_CHECK_VERSION(2, 46, 0) pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, vals->width, vals->height); surf = cairo_image_surface_create_for_data (gdk_pixbuf_get_pixels (pixbuf), CAIRO_FORMAT_ARGB32, gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf), gdk_pixbuf_get_rowstride (pixbuf)); cr = cairo_create (surf); viewport.width = vals->width; viewport.height = vals->height; rsvg_handle_render_document (handle, cr, &viewport, NULL); cairo_destroy (cr); cairo_surface_destroy (surf); /* un-premultiply the data */ src = gdk_pixbuf_get_pixels (pixbuf); for (y = 0; y < vals->height; y++) { guchar *s = src; gint w = vals->width; while (w--) { PIKA_CAIRO_ARGB32_GET_PIXEL (s, s[0], s[1], s[2], s[3]); s += 4; } src += gdk_pixbuf_get_rowstride (pixbuf); } #else rsvg_handle_set_size_callback (handle, load_set_size_callback, vals, NULL); pixbuf = rsvg_handle_get_pixbuf (handle); #endif g_object_unref (handle); return pixbuf; } static GtkWidget *size_label = NULL; /* This function retrieves the pixel size from an SVG file. */ static gboolean load_rsvg_size (GFile *file, SvgLoadVals *vals, RsvgHandleFlags rsvg_flags, GError **error) { RsvgHandle *handle; gboolean has_size; gdouble width, height; #if ! LIBRSVG_CHECK_VERSION(2, 52, 0) RsvgDimensionData dim; #endif handle = rsvg_handle_new_from_gfile_sync (file, rsvg_flags, NULL, error); if (! handle) return FALSE; rsvg_handle_set_dpi (handle, vals->resolution); #if LIBRSVG_CHECK_VERSION(2, 52, 0) rsvg_handle_get_intrinsic_size_in_pixels (handle, &width, &height); #else rsvg_handle_get_dimensions (handle, &dim); width = (gdouble) dim.width; height = (gdouble) dim.height; #endif if (width > 0.0 && height > 0.0) { vals->width = ceil (width); vals->height = ceil (height); has_size = TRUE; } else { vals->width = SVG_DEFAULT_SIZE; vals->height = SVG_DEFAULT_SIZE; has_size = FALSE; } if (size_label) { if (has_size) { gchar *text = g_strdup_printf (_("%d × %d"), vals->width, vals->height); gtk_label_set_text (GTK_LABEL (size_label), text); g_free (text); } else { gtk_label_set_text (GTK_LABEL (size_label), _("SVG file does not\nspecify a size!")); } } g_object_unref (handle); if (vals->width < 1) vals->width = 1; if (vals->height < 1) vals->height = 1; return TRUE; } /* User interface */ static PikaSizeEntry *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 (pika_chain_button_get_active (PIKA_CHAIN_BUTTON (constrain))) { gdouble x = pika_size_entry_get_refval (size, 0) / (gdouble) svg_width; gdouble y = pika_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 (pika_chain_button_get_active (PIKA_CHAIN_BUTTON (constrain))) { if (x != ratio_x) y = x; else x = y; } load_dialog_set_ratio (x, y); } static void load_dialog_resolution_callback (PikaSizeEntry *res, GFile *file) { SvgLoadVals vals = { 0.0, 0, 0 }; RsvgHandleFlags rsvg_flags; rsvg_flags = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (file), "rsvg-flags")); load_vals.resolution = vals.resolution = pika_size_entry_get_refval (res, 0); if (! load_rsvg_size (file, &vals, rsvg_flags, NULL)) return; g_signal_handlers_block_by_func (size, load_dialog_size_callback, NULL); pika_size_entry_set_resolution (size, 0, load_vals.resolution, FALSE); pika_size_entry_set_resolution (size, 1, load_vals.resolution, FALSE); g_signal_handlers_unblock_by_func (size, load_dialog_size_callback, NULL); if (pika_size_entry_get_unit (size) != PIKA_UNIT_PIXEL) { ratio_x = pika_size_entry_get_refval (size, 0) / vals.width; ratio_y = pika_size_entry_get_refval (size, 1) / vals.height; } svg_width = vals.width; svg_height = vals.height; load_dialog_set_ratio (ratio_x, ratio_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); pika_size_entry_set_refval (size, 0, svg_width * x); pika_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 PikaPDBStatusType load_dialog (GFile *file, RsvgHandleFlags *rsvg_flags, GError **load_error) { GtkWidget *dialog; GtkWidget *frame; GtkWidget *hbox; GtkWidget *vbox; GtkWidget *image; GdkPixbuf *preview; GtkWidget *grid; GtkWidget *grid2; GtkWidget *res; GtkWidget *label; GtkWidget *spinbutton; GtkWidget *toggle; GtkWidget *toggle2; GtkAdjustment *adj; gboolean run; GError *error = NULL; gchar *text; gboolean allow_retry = FALSE; SvgLoadVals vals = { SVG_DEFAULT_RESOLUTION, SVG_PREVIEW_SIZE, SVG_PREVIEW_SIZE, }; preview = load_rsvg_pixbuf (file, &vals, *rsvg_flags, &allow_retry, &error); pika_ui_init (PLUG_IN_BINARY); if (! preview && *rsvg_flags == RSVG_HANDLE_FLAGS_NONE && allow_retry) { /* 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 = pika_dialog_new (_("Disable safety size limits?"), PLUG_IN_ROLE, NULL, 0, pika_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); pika_window_set_transient (GTK_WINDOW (dialog)); hbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), hbox, TRUE, TRUE, 0); gtk_widget_show (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 (hbox), label, FALSE, FALSE, 0); gtk_widget_show (label); label = gtk_label_new (NULL); text = g_strdup_printf ("%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 (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 (hbox), label, FALSE, FALSE, 0); gtk_widget_show (label); gtk_widget_show (dialog); run = (pika_dialog_run (PIKA_DIALOG (dialog)) == GTK_RESPONSE_YES); gtk_widget_destroy (dialog); if (run) { *rsvg_flags = RSVG_HANDLE_FLAG_UNLIMITED; g_clear_error (&error); preview = load_rsvg_pixbuf (file, &vals, *rsvg_flags, NULL, &error); } } if (! preview) { /* 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"), pika_file_get_utf8_name (file), error ? error->message : _("Unknown reason")); g_clear_error (&error); return PIKA_PDB_EXECUTION_ERROR; } /* Scalable Vector Graphics is SVG, should perhaps not be translated */ dialog = pika_dialog_new (_("Render Scalable Vector Graphics"), PLUG_IN_ROLE, NULL, 0, pika_standard_help_func, LOAD_PROC, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_OK"), GTK_RESPONSE_OK, NULL); pika_dialog_set_alternative_button_order (GTK_DIALOG (dialog), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1); gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); pika_window_set_transient (GTK_WINDOW (dialog)); hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), hbox, TRUE, TRUE, 0); gtk_widget_show (hbox); /* The SVG preview */ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); gtk_widget_show (vbox); frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); image = gtk_image_new_from_pixbuf (preview); gtk_container_add (GTK_CONTAINER (frame), image); gtk_widget_show (image); size_label = gtk_label_new (NULL); gtk_label_set_justify (GTK_LABEL (size_label), GTK_JUSTIFY_CENTER); gtk_box_pack_start (GTK_BOX (vbox), size_label, TRUE, TRUE, 4); gtk_widget_show (size_label); /* query the initial size after the size label is created */ vals.resolution = load_vals.resolution; load_rsvg_size (file, &vals, *rsvg_flags, NULL); svg_width = vals.width; svg_height = vals.height; grid = gtk_grid_new (); gtk_grid_set_row_spacing (GTK_GRID (grid), 6); gtk_grid_set_column_spacing (GTK_GRID (grid), 6); gtk_box_pack_start (GTK_BOX (hbox), grid, TRUE, TRUE, 0); gtk_widget_show (grid); /* Width and Height */ label = gtk_label_new (_("Width:")); gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1); // GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); gtk_widget_show (label); label = gtk_label_new (_("Height:")); gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_grid_attach (GTK_GRID (grid), label, 0, 1, 1, 1); // GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); gtk_widget_show (label); hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_grid_attach (GTK_GRID (grid), hbox, 1, 0, 1, 1); // GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); gtk_widget_show (hbox); adj = gtk_adjustment_new (1, 1, 1, 1, 10, 0); spinbutton = pika_spin_button_new (adj, 1.0, 2); gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 10); gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0); gtk_widget_show (spinbutton); hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_grid_attach (GTK_GRID (grid), hbox, 1, 1, 1, 1); // GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); gtk_widget_show (hbox); size = PIKA_SIZE_ENTRY (pika_size_entry_new (1, PIKA_UNIT_PIXEL, "%a", TRUE, FALSE, FALSE, 10, PIKA_SIZE_ENTRY_UPDATE_SIZE)); pika_size_entry_add_field (size, GTK_SPIN_BUTTON (spinbutton), NULL); gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (size), FALSE, FALSE, 0); gtk_widget_show (GTK_WIDGET (size)); pika_size_entry_set_refval_boundaries (size, 0, PIKA_MIN_IMAGE_SIZE, PIKA_MAX_IMAGE_SIZE); pika_size_entry_set_refval_boundaries (size, 1, PIKA_MIN_IMAGE_SIZE, PIKA_MAX_IMAGE_SIZE); pika_size_entry_set_refval (size, 0, svg_width); pika_size_entry_set_refval (size, 1, svg_height); pika_size_entry_set_resolution (size, 0, load_vals.resolution, FALSE); pika_size_entry_set_resolution (size, 1, load_vals.resolution, FALSE); g_signal_connect (size, "value-changed", G_CALLBACK (load_dialog_size_callback), NULL); /* Scale ratio */ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_grid_attach (GTK_GRID (grid), hbox, 1, 2, 1, 2); // GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); gtk_widget_show (hbox); grid2 = gtk_grid_new (); gtk_box_pack_start (GTK_BOX (hbox), grid2, FALSE, FALSE, 0); xadj = gtk_adjustment_new (ratio_x, (gdouble) PIKA_MIN_IMAGE_SIZE / (gdouble) svg_width, (gdouble) PIKA_MAX_IMAGE_SIZE / (gdouble) svg_width, 0.01, 0.1, 0); spinbutton = pika_spin_button_new (xadj, 0.01, 4); gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 10); gtk_grid_attach (GTK_GRID (grid2), spinbutton, 0, 0, 1, 1); gtk_widget_show (spinbutton); g_signal_connect (xadj, "value-changed", G_CALLBACK (load_dialog_ratio_callback), NULL); label = gtk_label_new_with_mnemonic (_("_X ratio:")); gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinbutton); gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_grid_attach (GTK_GRID (grid), label, 0, 2, 1, 1); // GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); gtk_widget_show (label); yadj = gtk_adjustment_new (ratio_y, (gdouble) PIKA_MIN_IMAGE_SIZE / (gdouble) svg_height, (gdouble) PIKA_MAX_IMAGE_SIZE / (gdouble) svg_height, 0.01, 0.1, 0); spinbutton = pika_spin_button_new (yadj, 0.01, 4); gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 10); gtk_grid_attach (GTK_GRID (grid2), spinbutton, 0, 1, 1, 1); gtk_widget_show (spinbutton); g_signal_connect (yadj, "value-changed", G_CALLBACK (load_dialog_ratio_callback), NULL); label = gtk_label_new_with_mnemonic (_("_Y ratio:")); gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinbutton); gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_grid_attach (GTK_GRID (grid), label, 0, 3, 1, 1); // GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); gtk_widget_show (label); /* the constrain ratio chainbutton */ constrain = pika_chain_button_new (PIKA_CHAIN_RIGHT); pika_chain_button_set_active (PIKA_CHAIN_BUTTON (constrain), TRUE); gtk_grid_attach (GTK_GRID (grid2), constrain, 1, 0, 1, 2); gtk_widget_show (constrain); pika_help_set_help_data (pika_chain_button_get_button (PIKA_CHAIN_BUTTON (constrain)), _("Constrain aspect ratio"), NULL); gtk_widget_show (grid2); /* Resolution */ label = gtk_label_new (_("Resolution:")); gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_grid_attach (GTK_GRID (grid), label, 0, 4, 1, 1); // GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); gtk_widget_show (label); res = pika_size_entry_new (1, PIKA_UNIT_INCH, _("pixels/%a"), FALSE, FALSE, FALSE, 10, PIKA_SIZE_ENTRY_UPDATE_RESOLUTION); gtk_grid_attach (GTK_GRID (grid), res, 1, 4, 1, 1); // GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); gtk_widget_show (res); /* don't let the resolution become too small, librsvg tends to crash with very small resolutions */ pika_size_entry_set_refval_boundaries (PIKA_SIZE_ENTRY (res), 0, 5.0, PIKA_MAX_RESOLUTION); pika_size_entry_set_refval (PIKA_SIZE_ENTRY (res), 0, load_vals.resolution); g_object_set_data (G_OBJECT (file), "rsvg-flags", GINT_TO_POINTER (rsvg_flags)); g_signal_connect (res, "value-changed", G_CALLBACK (load_dialog_resolution_callback), file); /* Path Import */ toggle = gtk_check_button_new_with_mnemonic (_("Import _paths")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), load_vals.import); gtk_grid_attach (GTK_GRID (grid), toggle, 0, 5, 2, 1); // GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); gtk_widget_show (toggle); pika_help_set_help_data (toggle, _("Import path elements of the SVG so they " "can be used with the PIKA path tool"), NULL); g_signal_connect (toggle, "toggled", G_CALLBACK (pika_toggle_button_update), &load_vals.import); toggle2 = gtk_check_button_new_with_mnemonic (_("Merge imported paths")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle2), load_vals.merge); gtk_grid_attach (GTK_GRID (grid), toggle2, 0, 6, 2, 1); // GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); gtk_widget_show (toggle2); g_signal_connect (toggle2, "toggled", G_CALLBACK (pika_toggle_button_update), &load_vals.merge); g_object_bind_property (toggle, "active", toggle2, "sensitive", G_BINDING_SYNC_CREATE); gtk_widget_show (dialog); run = (pika_dialog_run (PIKA_DIALOG (dialog)) == GTK_RESPONSE_OK); if (run) { load_vals.width = ROUND (pika_size_entry_get_refval (size, 0)); load_vals.height = ROUND (pika_size_entry_get_refval (size, 1)); } gtk_widget_destroy (dialog); return run ? PIKA_PDB_SUCCESS : PIKA_PDB_CANCEL; }