/* 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 . */ #include "config.h" #include #include #include "libpikabase/pikabase.h" #include "vectors-types.h" #include "core/pikaimage.h" #include "core/pikaitem.h" #include "pikaanchor.h" #include "pikastroke.h" #include "pikabezierstroke.h" #include "pikavectors.h" #include "pikavectors-export.h" #include "pika-intl.h" static GString * pika_vectors_export (PikaImage *image, GList *vectors); static void pika_vectors_export_image_size (PikaImage *image, GString *str); static void pika_vectors_export_path (PikaVectors *vectors, GString *str); static gchar * pika_vectors_export_path_data (PikaVectors *vectors); /** * pika_vectors_export_file: * @image: the #PikaImage from which to export * @path_list: a #GList of #PikaVectors objects or %NULL to export all paths in @image * @file: the file to write * @error: return location for errors * * Exports one or more vectors aka path to an SVG file aka XML doc. * * When @path_list is %NULL aka empty list, exports all paths in image. * * When @path_list is empty and image has no paths, * this still writes a non-empty file containing an XML doc. * * Will overwrite any existing file. * * Returns: %TRUE on success, * %FALSE when there was an error writing the file **/ gboolean pika_vectors_export_file (PikaImage *image, GList *path_list, GFile *file, GError **error) { GOutputStream *output; GString *string; GError *my_error = NULL; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); g_return_val_if_fail (G_IS_FILE (file), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); output = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error)); if (! output) return FALSE; string = pika_vectors_export (image, path_list); if (! g_output_stream_write_all (output, string->str, string->len, NULL, NULL, &my_error)) { GCancellable *cancellable = g_cancellable_new (); g_set_error (error, my_error->domain, my_error->code, _("Writing SVG file '%s' failed: %s"), pika_file_get_utf8_name (file), my_error->message); g_clear_error (&my_error); 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; } /** * pika_vectors_export_string: * @image: the #PikaImage from which to export * @path_list: a #GList of #PikaVectors objects, or %NULL to export all paths in @image * * Exports one or more vectors aka path to a SVG string. * * When @path_list is %NULL aka empty list, exports all paths in image. * * When @path_list is empty and image has no paths, * this still returns a string for an empty XML doc. * * Returns: a NULL-terminated string that holds a complete XML document **/ gchar * pika_vectors_export_string (PikaImage *image, GList *path_list) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return g_string_free (pika_vectors_export (image, path_list), FALSE); } static GString * pika_vectors_export (PikaImage *image, GList *vectors) { GString *str = g_string_new (NULL); GList *list; g_string_append_printf (str, "\n" "\n" "\n" "\n", pika_image_get_width (image), pika_image_get_height (image)); if (! vectors) vectors = pika_image_get_vectors_iter (image); for (list = vectors; list; list = list->next) pika_vectors_export_path (PIKA_VECTORS (list->data), str); g_string_append (str, "\n"); return str; } static void pika_vectors_export_image_size (PikaImage *image, GString *str) { PikaUnit 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; pika_image_get_resolution (image, &xres, &yres); w = (gdouble) pika_image_get_width (image) / xres; h = (gdouble) pika_image_get_height (image) / yres; /* FIXME: should probably use the display unit here */ unit = pika_image_get_unit (image); switch (unit) { case PIKA_UNIT_INCH: abbrev = "in"; break; case PIKA_UNIT_MM: abbrev = "mm"; break; case PIKA_UNIT_POINT: abbrev = "pt"; break; case PIKA_UNIT_PICA: abbrev = "pc"; break; default: abbrev = "cm"; unit = PIKA_UNIT_MM; w /= 10.0; h /= 10.0; break; } g_ascii_formatd (wbuf, sizeof (wbuf), "%g", w * pika_unit_get_factor (unit)); g_ascii_formatd (hbuf, sizeof (hbuf), "%g", h * pika_unit_get_factor (unit)); g_string_append_printf (str, "width=\"%s%s\" height=\"%s%s\"", wbuf, abbrev, hbuf, abbrev); } static void pika_vectors_export_path (PikaVectors *vectors, GString *str) { const gchar *name = pika_object_get_name (vectors); gchar *data = pika_vectors_export_path_data (vectors); gchar *esc_name; esc_name = g_markup_escape_text (name, strlen (name)); g_string_append_printf (str, " \n", esc_name, data); g_free (esc_name); g_free (data); } #define NEWLINE "\n " static gchar * pika_vectors_export_path_data (PikaVectors *vectors) { GString *str; GList *strokes; gchar x_string[G_ASCII_DTOSTR_BUF_SIZE]; gchar y_string[G_ASCII_DTOSTR_BUF_SIZE]; gboolean closed = FALSE; str = g_string_new (NULL); for (strokes = vectors->strokes->head; strokes; strokes = strokes->next) { PikaStroke *stroke = strokes->data; GArray *control_points; PikaAnchor *anchor; gint i; if (closed) g_string_append_printf (str, NEWLINE); control_points = pika_stroke_control_points_get (stroke, &closed); if (PIKA_IS_BEZIER_STROKE (stroke)) { if (control_points->len >= 3) { anchor = &g_array_index (control_points, PikaAnchor, 1); g_ascii_formatd (x_string, G_ASCII_DTOSTR_BUF_SIZE, "%.2f", anchor->position.x); g_ascii_formatd (y_string, G_ASCII_DTOSTR_BUF_SIZE, "%.2f", anchor->position.y); g_string_append_printf (str, "M %s,%s", x_string, y_string); } if (control_points->len > 3) { g_string_append_printf (str, NEWLINE "C"); } for (i = 2; i < (control_points->len + (closed ? 2 : - 1)); i++) { if (i > 2 && i % 3 == 2) g_string_append_printf (str, NEWLINE " "); anchor = &g_array_index (control_points, PikaAnchor, i % control_points->len); g_ascii_formatd (x_string, G_ASCII_DTOSTR_BUF_SIZE, "%.2f", anchor->position.x); g_ascii_formatd (y_string, G_ASCII_DTOSTR_BUF_SIZE, "%.2f", anchor->position.y); g_string_append_printf (str, " %s,%s", x_string, y_string); } if (closed && control_points->len > 3) g_string_append_printf (str, " Z"); } else { g_printerr ("Unknown stroke type\n"); if (control_points->len >= 1) { anchor = &g_array_index (control_points, PikaAnchor, 0); g_ascii_formatd (x_string, G_ASCII_DTOSTR_BUF_SIZE, ".2f", anchor->position.x); g_ascii_formatd (y_string, G_ASCII_DTOSTR_BUF_SIZE, ".2f", anchor->position.y); g_string_append_printf (str, "M %s,%s", x_string, y_string); } if (control_points->len > 1) { g_string_append_printf (str, NEWLINE "L"); } for (i = 1; i < control_points->len; i++) { if (i > 1 && i % 3 == 1) g_string_append_printf (str, NEWLINE " "); anchor = &g_array_index (control_points, PikaAnchor, i); g_ascii_formatd (x_string, G_ASCII_DTOSTR_BUF_SIZE, "%.2f", anchor->position.x); g_ascii_formatd (y_string, G_ASCII_DTOSTR_BUF_SIZE, "%.2f", anchor->position.y); g_string_append_printf (str, " %s,%s", x_string, y_string); } if (closed && control_points->len > 1) g_string_append_printf (str, " Z"); } g_array_free (control_points, TRUE); } return g_strchomp (g_string_free (str, FALSE)); }