app/path: Add <title> to SVG import/export

In 84ff512c, we made the SVG id attribute
conform to SVG standards by using a
generic ID. However, we also relied on
that field to rename the path when copying
and pasting in GIMP. As a result, the path
name was lost.
This patch adds a <title> element inside the
<path> element, per David Gowers.
This allows us to retain the original
path name when copying and pasting
paths within GIMP itself, as this also adds
a check to see if there's a title element and
uses that as the path name.
This commit is contained in:
Alx Sa 2026-01-06 12:52:30 +00:00
parent 4e2463b688
commit 0da5e56a7b
2 changed files with 65 additions and 9 deletions

View file

@ -215,21 +215,27 @@ gimp_path_export_path (GimpPath *path,
GString *str,
gint path_id)
{
gchar *name;
gchar *data = gimp_path_export_path_data (path);
const gchar *name = gimp_object_get_name (path);
gchar *data = gimp_path_export_path_data (path);
gchar *id;
gchar *esc_name;
/* SVG specification states the id attribute must not contain whitespace.
* Rather than filter user names, we'll just define a generic,
* auto-incrementing id. */
name = g_strdup_printf ("path%d", path_id);
id = g_strdup_printf ("path%d", path_id);
esc_name = g_markup_escape_text (name, strlen (name));
g_string_append_printf (str,
" <path id=\"%s\"\n"
" fill=\"none\" stroke=\"black\" stroke-width=\"1\"\n"
" d=\"%s\" />\n",
name, data);
" d=\"%s\">\n"
" <title>%s</title>\n"
" </path>\n",
id, data, esc_name);
g_free (name);
g_free (id);
g_free (esc_name);
g_free (data);
}

View file

@ -134,6 +134,11 @@ static void svg_parser_start_element (GMarkupParseContext *context,
const gchar **attribute_values,
gpointer user_data,
GError **error);
static void svg_parser_text (GMarkupParseContext *context,
const gchar *text,
gsize text_len,
gpointer user_data,
GError **error);
static void svg_parser_end_element (GMarkupParseContext *context,
const gchar *element_name,
gpointer user_data,
@ -143,7 +148,7 @@ static const GMarkupParser markup_parser =
{
svg_parser_start_element,
svg_parser_end_element,
NULL, /* characters */
svg_parser_text,
NULL, /* passthrough */
NULL /* error */
};
@ -190,7 +195,8 @@ static const SvgHandler svg_handlers[] =
{ "ellipse", svg_handler_ellipse_start, NULL },
{ "line", svg_handler_line_start, NULL },
{ "polyline", svg_handler_poly_start, NULL },
{ "polygon", svg_handler_poly_start, NULL }
{ "polygon", svg_handler_poly_start, NULL },
{ "title", NULL, NULL }
};
@ -480,6 +486,50 @@ svg_parser_start_element (GMarkupParseContext *context,
handler->start (handler, attribute_names, attribute_values, parser);
}
static void
svg_parser_text (GMarkupParseContext *context,
const gchar *text,
gsize text_len,
gpointer user_data,
GError **error)
{
SvgParser *parser = user_data;
SvgHandler *handler;
handler = g_queue_pop_head (parser->stack);
if (handler &&
handler->name &&
strcmp (handler->name, "title") == 0)
{
gchar *title = g_markup_escape_text (text, text_len);
SvgHandler *prior_handler;
GList *paths;
prior_handler = g_queue_pop_head (parser->stack);
/* Replace path ID with title name if imported */
if (prior_handler && prior_handler->paths)
{
for (paths = prior_handler->paths; paths; paths = paths->next)
{
SvgPath *svg_path = paths->data;
if (svg_path->id)
g_free (svg_path->id);
svg_path->id = g_strdup (title);
}
g_list_free (paths);
}
g_queue_push_head (parser->stack, prior_handler);
g_free (title);
}
g_queue_push_head (parser->stack, handler);
}
static void
svg_parser_end_element (GMarkupParseContext *context,
const gchar *element_name,
@ -1761,7 +1811,7 @@ parse_number (ParsePathContext *ctx,
if (c >= '0' && c <= '9')
{
fraction *= 0.1;
value += fraction * (c - '0');
value += fraction * (c - '0');
}
else if (c == 'e' || c == 'E')
{