diff --git a/plug-ins/common/file-svg.c b/plug-ins/common/file-svg.c index 8b2a0c76905f106f2acb88743f9a77d8e452146e..d8c0f96e3feb54d884551065339fec0c2fe50742 100644 --- a/plug-ins/common/file-svg.c +++ b/plug-ins/common/file-svg.c @@ -26,6 +26,7 @@ #include #include +#include #include #include "libgimp/gimp.h" @@ -35,6 +36,7 @@ #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" @@ -52,6 +54,22 @@ typedef struct 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; @@ -97,6 +115,14 @@ static GimpValueArray * svg_load (GimpProcedure *procedure, 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, @@ -104,6 +130,11 @@ static GimpImage * load_image (GFile *file, 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, @@ -115,9 +146,64 @@ static GimpPDBStatusType load_dialog (GFile *file, 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_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); + #if LIBRSVG_CHECK_VERSION(2, 46, 0) static GimpUnit * svg_rsvg_to_gimp_unit (RsvgUnit unit); static void svg_destroy_surface (guchar *pixels, @@ -153,6 +239,7 @@ 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; } @@ -200,6 +287,58 @@ svg_create_procedure (GimpPlugIn *plug_in, 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, + _("Export vector layers in SVG file format"), + _("Export vector layers 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 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"), NULL, + "jpeg", EXPORT_FORMAT_JPEG, _("JPEG"), NULL, + "none", EXPORT_FORMAT_NONE, _("Do not export raster layers"), NULL, + NULL), + "png", + G_PARAM_READWRITE); + } return procedure; } @@ -533,6 +672,44 @@ svg_load (GimpProcedure *procedure, 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, @@ -582,6 +759,59 @@ load_image (GFile *file, 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: @@ -843,6 +1073,93 @@ load_dialog (GFile *file, 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) @@ -877,3 +1194,807 @@ svg_destroy_surface (guchar *pixels, 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; + GString *str = g_string_new (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\n", + title); + + layers = gimp_image_get_layers (image); + svg_export_layers ((GimpItem **) layers, NULL, layer_ids, str, config, "", + error); + g_free (layers); + + g_string_append (str, "\n"); + + return str; +} + +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_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_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\n", extra_spacing); + } + else if (gimp_item_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_item_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\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 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", + 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, "", "", 1); + g_string_replace (markup, "", "", 1); + + g_string_replace (markup, "", + "", 0); + g_string_replace (markup, "", "", 0); + + g_string_replace (markup, "", + "", 0); + g_string_replace (markup, "", "", 0); + + g_string_replace (markup, "", + "", 0); + g_string_replace (markup, "", "", 0); + + g_string_replace (markup, "", + "", 0); + g_string_replace (markup, "", "", 0); + + g_string_replace (markup, "", "", 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, "\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, "" + "%s", + 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\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_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\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\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, "\n"); +} + +static gchar * +svg_get_color_string (GimpVectorLayer *layer, + const gchar *type) +{ + GeglColor *color = NULL; + gboolean enabled = TRUE; + + if (g_strcmp0 ("fill", type) == 0) + { + color = gimp_vector_layer_get_fill_color (layer); + enabled = gimp_vector_layer_get_enable_fill (layer); + } + else + { + color = gimp_vector_layer_get_stroke_color (layer); + enabled = gimp_vector_layer_get_enable_stroke (layer); + } + + if (enabled && 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); + + return color_string; + } + + 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; +}