mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-27 06:00:22 +00:00
3f16f7e0d4
Make .svg use the Cairo renderer to save to an SVG file. It's useful when comparing rendering behavior and times with web browsers (as long as one is aware that browsers build a full DOM tree out of those SVGs).
239 lines
6.3 KiB
C
239 lines
6.3 KiB
C
/* Copyright 2023 Red Hat, Inc.
|
|
*
|
|
* GTK is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* GTK 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with GTK; see the file COPYING. If not,
|
|
* see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Author: Matthias Clasen
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include <glib/gi18n-lib.h>
|
|
#include <glib/gprintf.h>
|
|
#include <glib/gstdio.h>
|
|
#include <gtk/gtk.h>
|
|
#include "gtk-rendernode-tool.h"
|
|
#ifdef CAIRO_HAS_SVG_SURFACE
|
|
#include <cairo-svg.h>
|
|
#endif
|
|
|
|
static char *
|
|
get_save_filename (const char *filename)
|
|
{
|
|
int length = strlen (filename);
|
|
const char *extension = ".png";
|
|
char *result;
|
|
|
|
if (strcmp (filename + (length - strlen (".node")), ".node") == 0)
|
|
{
|
|
char *basename = g_strndup (filename, length - strlen (".node"));
|
|
result = g_strconcat (basename, extension, NULL);
|
|
g_free (basename);
|
|
}
|
|
else
|
|
result = g_strconcat (filename, extension, NULL);
|
|
|
|
return result;
|
|
}
|
|
|
|
#ifdef CAIRO_HAS_SVG_SURFACE
|
|
static cairo_status_t
|
|
cairo_serializer_write (gpointer user_data,
|
|
const unsigned char *data,
|
|
unsigned int length)
|
|
{
|
|
g_byte_array_append (user_data, data, length);
|
|
|
|
return CAIRO_STATUS_SUCCESS;
|
|
}
|
|
|
|
static GBytes *
|
|
create_svg (GskRenderNode *node,
|
|
GError **error)
|
|
{
|
|
cairo_surface_t *surface;
|
|
cairo_t *cr;
|
|
graphene_rect_t bounds;
|
|
GByteArray *array;
|
|
|
|
gsk_render_node_get_bounds (node, &bounds);
|
|
array = g_byte_array_new ();
|
|
|
|
surface = cairo_svg_surface_create_for_stream (cairo_serializer_write,
|
|
array,
|
|
bounds.size.width,
|
|
bounds.size.height);
|
|
cairo_svg_surface_set_document_unit (surface, CAIRO_SVG_UNIT_PX);
|
|
cairo_surface_set_device_offset (surface, -bounds.origin.x, -bounds.origin.y);
|
|
|
|
cr = cairo_create (surface);
|
|
gsk_render_node_draw (node, cr);
|
|
cairo_destroy (cr);
|
|
|
|
cairo_surface_finish (surface);
|
|
if (cairo_surface_status (surface) == CAIRO_STATUS_SUCCESS)
|
|
{
|
|
cairo_surface_destroy (surface);
|
|
return g_byte_array_free_to_bytes (array);
|
|
}
|
|
else
|
|
{
|
|
g_set_error (error,
|
|
G_IO_ERROR, G_IO_ERROR_FAILED,
|
|
"%s", cairo_status_to_string (cairo_surface_status (surface)));
|
|
cairo_surface_destroy (surface);
|
|
g_byte_array_unref (array);
|
|
return NULL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
render_file (const char *filename,
|
|
const char *renderer_name,
|
|
const char *save_file)
|
|
{
|
|
GskRenderNode *node;
|
|
GBytes *bytes;
|
|
char *save_to;
|
|
GError *error = NULL;
|
|
|
|
save_to = (char *) save_file;
|
|
|
|
if (save_to == NULL)
|
|
{
|
|
save_to = get_save_filename (filename);
|
|
if (g_file_test (save_to, G_FILE_TEST_EXISTS))
|
|
{
|
|
g_printerr (_("File %s exists.\n"
|
|
"If you want to overwrite, specify the filename.\n"), save_to);
|
|
exit (1);
|
|
}
|
|
}
|
|
|
|
node = load_node_file (filename);
|
|
|
|
#ifdef CAIRO_HAS_SVG_SURFACE
|
|
if (g_str_has_suffix (save_to, ".svg"))
|
|
{
|
|
bytes = create_svg (node, &error);
|
|
if (bytes == NULL)
|
|
{
|
|
g_printerr (_("Failed to generate SVG: %s\n"), error->message);
|
|
exit (1);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
GdkTexture *texture;
|
|
GskRenderer *renderer;
|
|
GdkSurface *window;
|
|
|
|
if (renderer_name)
|
|
g_object_set_data_full (G_OBJECT (gdk_display_get_default ()), "gsk-renderer",
|
|
g_strdup (renderer_name), g_free);
|
|
|
|
window = gdk_surface_new_toplevel (gdk_display_get_default ());
|
|
renderer = gsk_renderer_new_for_surface (window);
|
|
|
|
texture = gsk_renderer_render_texture (renderer, node, NULL);
|
|
|
|
if (g_str_has_suffix (save_to, ".tif") ||
|
|
g_str_has_suffix (save_to, ".tiff"))
|
|
bytes = gdk_texture_save_to_tiff_bytes (texture);
|
|
else
|
|
bytes = gdk_texture_save_to_png_bytes (texture);
|
|
|
|
g_object_unref (texture);
|
|
}
|
|
|
|
if (g_file_set_contents (save_to,
|
|
g_bytes_get_data (bytes, NULL),
|
|
g_bytes_get_size (bytes),
|
|
&error))
|
|
{
|
|
if (save_file == NULL)
|
|
g_print (_("Output written to %s.\n"), save_to);
|
|
}
|
|
else
|
|
{
|
|
g_printerr (_("Failed to save %s: %s\n"), save_to, error->message);
|
|
exit (1);
|
|
}
|
|
|
|
g_bytes_unref (bytes);
|
|
|
|
if (save_to != save_file)
|
|
g_free (save_to);
|
|
|
|
gsk_render_node_unref (node);
|
|
}
|
|
|
|
void
|
|
do_render (int *argc,
|
|
const char ***argv)
|
|
{
|
|
GOptionContext *context;
|
|
char **filenames = NULL;
|
|
char *renderer = NULL;
|
|
const GOptionEntry entries[] = {
|
|
{ "renderer", 0, 0, G_OPTION_ARG_STRING, &renderer, N_("Renderer to use"), N_("RENDERER") },
|
|
{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, N_("FILE…") },
|
|
{ NULL, }
|
|
};
|
|
GError *error = NULL;
|
|
|
|
if (gdk_display_get_default () == NULL)
|
|
{
|
|
g_printerr (_("Could not initialize windowing system\n"));
|
|
exit (1);
|
|
}
|
|
|
|
g_set_prgname ("gtk4-rendernode-tool render");
|
|
context = g_option_context_new (NULL);
|
|
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
|
|
g_option_context_add_main_entries (context, entries, NULL);
|
|
g_option_context_set_summary (context, _("Render a .node file to an image."));
|
|
|
|
if (!g_option_context_parse (context, argc, (char ***)argv, &error))
|
|
{
|
|
g_printerr ("%s\n", error->message);
|
|
g_error_free (error);
|
|
exit (1);
|
|
}
|
|
|
|
g_option_context_free (context);
|
|
|
|
if (filenames == NULL)
|
|
{
|
|
g_printerr (_("No .node file specified\n"));
|
|
exit (1);
|
|
}
|
|
|
|
if (g_strv_length (filenames) > 2)
|
|
{
|
|
g_printerr (_("Can only render a single .node file to a single output file\n"));
|
|
exit (1);
|
|
}
|
|
|
|
render_file (filenames[0], renderer, filenames[1]);
|
|
|
|
g_strfreev (filenames);
|
|
}
|