From 9256b5b5520548723d7e82c63a0392805ee6a1c5 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 2 Jun 2024 22:13:05 -0400 Subject: [PATCH] rendernode tool: Add an extract command This lets one extract the data urls from a node file. --- docs/reference/gtk/gtk4-rendernode-tool.rst | 13 + gsk/gskrendernodeparser.c | 2 +- tools/gtk-rendernode-tool-extract.c | 326 ++++++++++++++++++++ tools/gtk-rendernode-tool.c | 3 + tools/gtk-rendernode-tool.h | 1 + tools/meson.build | 1 + 6 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 tools/gtk-rendernode-tool-extract.c diff --git a/docs/reference/gtk/gtk4-rendernode-tool.rst b/docs/reference/gtk/gtk4-rendernode-tool.rst index 3ebfd0bcf6..6a16424a2a 100644 --- a/docs/reference/gtk/gtk4-rendernode-tool.rst +++ b/docs/reference/gtk/gtk4-rendernode-tool.rst @@ -18,6 +18,7 @@ SYNOPSIS | | **gtk4-rendernode-tool** benchmark [OPTIONS...] | **gtk4-rendernode-tool** compare [OPTIONS...] +| **gtk4-rendernode-tool** extract [OPTIONS...] | **gtk4-rendernode-tool** info [OPTIONS...] | **gtk4-rendernode-tool** render [OPTIONS...] [] | **gtk4-rendernode-tool** show [OPTIONS...] @@ -99,3 +100,15 @@ exit code is 1. If the images are identical, it is 0. ``--quiet`` Don't write results to stdout. + + +Extract +^^^^^^^ + +The ``extract`` command saves all the data urls found in a node file to a given +directory. The file names for the extracted files are derived from the mimetype +of the url. + +``--dir=DIRECTORY`` + + Save extracted files in ``DIRECTORY`` (defaults to the current directory). diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c index cd877cd3cb..f37f194a21 100644 --- a/gsk/gskrendernodeparser.c +++ b/gsk/gskrendernodeparser.c @@ -2988,7 +2988,7 @@ printer_init_collect_font_info (Printer *printer, info = g_new0 (FontInfo, 1); info->face = hb_face_reference (hb_font_get_face (pango_font_get_hb_font (font))); - if (!g_object_get_data (G_OBJECT (font), "from-url")) + if (!g_object_get_data (G_OBJECT (pango_font_get_font_map (font)), "font-files")) { info->input = hb_subset_input_create_or_fail (); hb_subset_input_set_flags (info->input, HB_SUBSET_FLAGS_RETAIN_GIDS); diff --git a/tools/gtk-rendernode-tool-extract.c b/tools/gtk-rendernode-tool-extract.c new file mode 100644 index 0000000000..c85ea9b88a --- /dev/null +++ b/tools/gtk-rendernode-tool-extract.c @@ -0,0 +1,326 @@ +/* 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 . + * + * Author: Matthias Clasen + */ + +#include "config.h" + +#include +#include +#include + +#include +#include +#include +#include +#include "gtk-rendernode-tool.h" + +static gboolean verbose; +static char *directory = NULL; + +static guint texture_count; +static guint font_count; + +static GHashTable *fonts; + +static void +extract_texture (GskRenderNode *node) +{ + GdkTexture *texture; + char *filename; + char *path; + + if (gsk_render_node_get_node_type (node) == GSK_TEXTURE_NODE) + texture = gsk_texture_node_get_texture (node); + else + texture = gsk_texture_scale_node_get_texture (node); + + do { + filename = g_strdup_printf ("gtk-texture-%u.ttf", texture_count); + path = g_build_path ("/", directory, filename, NULL); + + if (!g_file_test (path, G_FILE_TEST_EXISTS)) + break; + + g_free (path); + g_free (filename); + texture_count++; + } while (TRUE); + + if (verbose) + g_print ("Writing %dx%d texture to %s\n", + gdk_texture_get_width (texture), + gdk_texture_get_height (texture), + filename); + + if (!gdk_texture_save_to_png (texture, path)) + { + g_printerr (_("Failed to write %s\n"), filename); + } + + g_free (path); + g_free (filename); + + texture_count++; +} + +static void +extract_font (GskRenderNode *node) +{ + PangoFont *font; + hb_font_t *hb_font; + hb_face_t *hb_face; + hb_blob_t *hb_blob; + const char *data; + unsigned int length; + char *filename; + char *path; + char *sum; + + font = gsk_text_node_get_font (node); + hb_font = pango_font_get_hb_font (font); + hb_face = hb_font_get_face (hb_font); + hb_blob = hb_face_reference_blob (hb_face); + + if (hb_blob == hb_blob_get_empty ()) + { + hb_blob_destroy (hb_blob); + g_warning ("Failed to extract font data\n"); + return; + } + + data = hb_blob_get_data (hb_blob, &length); + + sum = g_compute_checksum_for_data (G_CHECKSUM_SHA256, (const guchar *)data, length); + + if (!fonts) + fonts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + if (g_hash_table_contains (fonts, sum)) + { + g_free (sum); + hb_blob_destroy (hb_blob); + return; + } + + g_hash_table_add (fonts, sum); + + do { + filename = g_strdup_printf ("gtk-font-%u.ttf", font_count); + path = g_build_path ("/", directory, filename, NULL); + + if (!g_file_test (path, G_FILE_TEST_EXISTS)) + break; + + g_free (path); + g_free (filename); + font_count++; + } while (TRUE); + + if (verbose) + { + PangoFontDescription *desc; + + desc = pango_font_describe (font); + g_print ("Writing font %s to %s\n", + pango_font_description_get_family (desc), + filename); + pango_font_description_free (desc); + } + + if (!g_file_set_contents (path, data, length, NULL)) + { + g_printerr (_("Failed to write %s\n"), filename); + } + + hb_blob_destroy (hb_blob); + + g_free (path); + g_free (filename); + + font_count++; +} + +#define N_NODE_TYPES (GSK_SUBSURFACE_NODE + 1) +static void +extract_from_node (GskRenderNode *node) +{ + g_assert (gsk_render_node_get_node_type (node) < N_NODE_TYPES); + + switch (gsk_render_node_get_node_type (node)) + { + case GSK_CONTAINER_NODE: + for (unsigned int i = 0; i < gsk_container_node_get_n_children (node); i++) + extract_from_node (gsk_container_node_get_child (node, i)); + break; + + case GSK_CAIRO_NODE: + case GSK_COLOR_NODE: + case GSK_LINEAR_GRADIENT_NODE: + case GSK_REPEATING_LINEAR_GRADIENT_NODE: + case GSK_RADIAL_GRADIENT_NODE: + case GSK_REPEATING_RADIAL_GRADIENT_NODE: + case GSK_CONIC_GRADIENT_NODE: + case GSK_BORDER_NODE: + case GSK_INSET_SHADOW_NODE: + case GSK_OUTSET_SHADOW_NODE: + break; + + case GSK_TEXTURE_NODE: + case GSK_TEXTURE_SCALE_NODE: + extract_texture (node); + break; + + case GSK_TRANSFORM_NODE: + extract_from_node (gsk_transform_node_get_child (node)); + break; + + case GSK_OPACITY_NODE: + extract_from_node (gsk_opacity_node_get_child (node)); + break; + + case GSK_COLOR_MATRIX_NODE: + extract_from_node (gsk_color_matrix_node_get_child (node)); + break; + + case GSK_REPEAT_NODE: + extract_from_node (gsk_repeat_node_get_child (node)); + break; + + case GSK_CLIP_NODE: + extract_from_node (gsk_clip_node_get_child (node)); + break; + + case GSK_ROUNDED_CLIP_NODE: + extract_from_node (gsk_rounded_clip_node_get_child (node)); + break; + + case GSK_SHADOW_NODE: + extract_from_node (gsk_shadow_node_get_child (node)); + break; + + case GSK_BLEND_NODE: + extract_from_node (gsk_blend_node_get_bottom_child (node)); + extract_from_node (gsk_blend_node_get_top_child (node)); + break; + + case GSK_CROSS_FADE_NODE: + extract_from_node (gsk_cross_fade_node_get_start_child (node)); + extract_from_node (gsk_cross_fade_node_get_end_child (node)); + break; + + case GSK_TEXT_NODE: + extract_font (node); + break; + + case GSK_BLUR_NODE: + extract_from_node (gsk_blur_node_get_child (node)); + break; + + case GSK_DEBUG_NODE: + extract_from_node (gsk_debug_node_get_child (node)); + break; + + case GSK_GL_SHADER_NODE: + for (unsigned int i = 0; i < gsk_gl_shader_node_get_n_children (node); i++) + extract_from_node (gsk_gl_shader_node_get_child (node, i)); + break; + + case GSK_MASK_NODE: + extract_from_node (gsk_mask_node_get_source (node)); + extract_from_node (gsk_mask_node_get_mask (node)); + break; + + case GSK_FILL_NODE: + extract_from_node (gsk_fill_node_get_child (node)); + break; + + case GSK_STROKE_NODE: + extract_from_node (gsk_stroke_node_get_child (node)); + break; + + case GSK_SUBSURFACE_NODE: + extract_from_node (gsk_subsurface_node_get_child (node)); + break; + + case GSK_NOT_A_RENDER_NODE: + default: + g_assert_not_reached (); + } +} + +static void +file_extract (const char *filename) +{ + GskRenderNode *node; + + node = load_node_file (filename); + + extract_from_node (node); + + gsk_render_node_unref (node); +} + +void +do_extract (int *argc, + const char ***argv) +{ + GOptionContext *context; + char **filenames = NULL; + const GOptionEntry entries[] = { + { "dir", 0, 0, G_OPTION_ARG_FILENAME, &directory, N_("Directory to use"), N_("DIRECTORY") }, + { "verbose", 0, 0, G_OPTION_ARG_NONE, &verbose, N_("Be verbose"), NULL }, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, N_("FILE") }, + { NULL, } + }; + GError *error = NULL; + + g_set_prgname ("gtk4-rendernode-tool extract"); + 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, _("Extract data urls from the render node.")); + + 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) > 1) + { + g_printerr (_("Can only accept a single .node file\n")); + exit (1); + } + + if (directory == NULL) + directory = g_strdup ("."); + + file_extract (filenames[0]); + + g_strfreev (filenames); + g_free (directory); +} diff --git a/tools/gtk-rendernode-tool.c b/tools/gtk-rendernode-tool.c index 1b7407fada..7a2f0bf81f 100644 --- a/tools/gtk-rendernode-tool.c +++ b/tools/gtk-rendernode-tool.c @@ -40,6 +40,7 @@ usage (void) "Commands:\n" " benchmark Benchmark rendering of a node\n" " compare Compare nodes or images\n" + " extract Extract data urls\n" " info Provide information about the node\n" " show Show the node\n" " render Take a screenshot of the node\n" @@ -119,6 +120,8 @@ main (int argc, const char *argv[]) do_benchmark (&argc, &argv); else if (strcmp (argv[0], "compare") == 0) do_compare (&argc, &argv); + else if (strcmp (argv[0], "extract") == 0) + do_extract (&argc, &argv); else usage (); diff --git a/tools/gtk-rendernode-tool.h b/tools/gtk-rendernode-tool.h index 12f653b837..015082c883 100644 --- a/tools/gtk-rendernode-tool.h +++ b/tools/gtk-rendernode-tool.h @@ -6,6 +6,7 @@ void do_compare (int *argc, const char ***argv); void do_info (int *argc, const char ***argv); void do_show (int *argc, const char ***argv); void do_render (int *argc, const char ***argv); +void do_extract (int *argc, const char ***argv); GskRenderNode *load_node_file (const char *filename); GskRenderer *create_renderer (const char *name, GError **error); diff --git a/tools/meson.build b/tools/meson.build index f259cc5070..c9380f93f7 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -43,6 +43,7 @@ gtk_tools = [ ['gtk4-rendernode-tool', ['gtk-rendernode-tool.c', 'gtk-rendernode-tool-benchmark.c', 'gtk-rendernode-tool-compare.c', + 'gtk-rendernode-tool-extract.c', 'gtk-rendernode-tool-info.c', 'gtk-rendernode-tool-render.c', 'gtk-rendernode-tool-show.c',