/* 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"
#include "gtk-tool-utils.h"
#ifdef CAIRO_HAS_SVG_SURFACE
#include <cairo-svg.h>
#endif
#ifdef CAIRO_HAS_PDF_SURFACE
#include <cairo-pdf.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;
}
#if defined (CAIRO_HAS_SVG_SURFACE) || defined (CAIRO_HAS_PDF_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;
}
#endif
#ifdef CAIRO_HAS_SVG_SURFACE
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
#ifdef CAIRO_HAS_PDF_SURFACE
static GBytes *
create_pdf (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_pdf_surface_create_for_stream (cairo_serializer_write,
array,
bounds.size.width,
bounds.size.height);
cairo_surface_set_device_offset (surface, -bounds.origin.x, -bounds.origin.y);
cr = cairo_create (surface);
gsk_render_node_draw (node, cr);
cairo_show_page (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 graphene_rect_t *bounds,
gboolean snap,
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
#ifdef CAIRO_HAS_PDF_SURFACE
if (g_str_has_suffix (save_to, ".pdf" ))
{
bytes = create_pdf (node, &error);
if (bytes == NULL)
{
g_printerr (_("Failed to generate SVG: %s\n" ), error->message);
exit (1 );
}
}
else
#endif
{
GdkTexture *texture;
GskRenderer *renderer;
graphene_rect_t node_bounds;
renderer = create_renderer (renderer_name, &error);
if (renderer == NULL)
{
g_printerr (_("Failed to create renderer: %s\n" ), error->message);
exit (1 );
}
if (bounds == NULL)
{
gsk_render_node_get_bounds (node, &node_bounds);
if (snap)
{
graphene_rect_t snapped;
snapped.origin.x = floorf (node_bounds.origin.x);
snapped.origin.y = floorf (node_bounds.origin.y);
snapped.size.width = ceilf (node_bounds.origin.x + node_bounds.size.width) - snapped.origin.x;
snapped.size.height = ceilf (node_bounds.origin.y + node_bounds.size.height) - snapped.origin.y;
node_bounds = snapped;
}
bounds = &node_bounds;
}
texture = gsk_renderer_render_texture (renderer, node, bounds);
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);
}
static gboolean
my_strtof (const char *str,
float *result)
{
char *end;
if (str[0 ] == 0 )
return FALSE ;
*result = g_ascii_strtod (str, &end);
return *end == 0 ;
}
static gboolean
rect_init_from_arg (graphene_rect_t *rect,
const char *arg)
{
char **split = g_strsplit (arg, "," , 4 );
gboolean result;
result = g_strv_length (split) == 4 &&
my_strtof(split[0 ], &rect->origin.x) &&
my_strtof(split[1 ], &rect->origin.y) &&
my_strtof(split[2 ], &rect->size.width) &&
my_strtof(split[3 ], &rect->size.height);
g_strfreev (split);
return result;
}
void
do_render (int *argc,
const char ***argv)
{
GOptionContext *context;
char **filenames = NULL;
char *renderer = NULL;
char *bounds_str = NULL;
graphene_rect_t bounds;
gboolean snap = FALSE ;
const GOptionEntry entries[] = {
{ "renderer" , 0 , 0 , G_OPTION_ARG_STRING, &renderer, N_("Renderer to use" ), N_("RENDERER" ) },
{ "dont-move" , 0 , 0 , G_OPTION_ARG_NONE, &snap, N_("Keep node position unchanged" ), NULL },
{ "bounds" , 0 , 0 , G_OPTION_ARG_STRING, &bounds_str, N_("Area to render" ), N_("X,Y,W,H" ) },
{ 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 );
}
if (bounds_str)
{
if (!rect_init_from_arg (&bounds, bounds_str))
{
g_printerr (_("Invalid rectangle given for bounds\n" ));
exit (1 );
}
}
render_file (filenames[0 ],
renderer,
bounds_str ? &bounds : NULL,
snap,
filenames[1 ]);
g_strfreev (filenames);
}
Messung V0.5 in Prozent C=100 H=92 G=95
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-07-02)
¤
*© Formatika GbR, Deutschland