From 8158945de99dcd21ddec3950af89fe1ab1948d4e Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 14 Jan 2024 17:53:46 -0500 Subject: [PATCH] gsk: Allow custom fonts in node files This will let us store complete test fonts inside node files, as data: urls. You can also use a file: url to refer to a local file. The syntax is as follows: text { font: "FONT DESCRIPTION" url("data:font/ttf;base64,FONT DATA"); } with the url being optional. --- demos/node-editor/node-format.md | 20 +- gsk/gskrendernodeparser.c | 232 ++++++++++++++++++-- gsk/gskrendernodeparserprivate.h | 1 - testsuite/gsk/nodeparser/text-fail.errors | 4 +- testsuite/gsk/nodeparser/text-fail.ref.node | 8 +- 5 files changed, 240 insertions(+), 25 deletions(-) diff --git a/demos/node-editor/node-format.md b/demos/node-editor/node-format.md index 90ad8ae142..a824182788 100644 --- a/demos/node-editor/node-format.md +++ b/demos/node-editor/node-format.md @@ -322,15 +322,23 @@ stroke bounds of the path. ### text -| property | syntax | default | printed | -| -------- | ---------------- | ---------------------- | ----------- | -| color | `` | black | non-default | -| font | `` | "Cantarell 11" | always | -| glyphs | `` | "Hello" | always | -| offset | `` | 0 0 | non-default | +| property | syntax | default | printed | +| -------- | ------------------- | ------------------- | ----------- | +| color | `` | black | non-default | +| font | `` ``? | "Cantarell 11" | always | +| glyphs | `` | "Hello" | always | +| offset | `` | 0 0 | non-default | Creates a node like `gsk_text_node_new()` with the given properties. +If a url is specified for the font, it must point to a font file for the +font that is specified in the string. It can be either a data url containing +a base64-encoded font file, or a regular url that points to a font file. + +Glyphs can be specified as an ASCII string, or as a comma-separated list of +their glyph ID and advance width. Optionally, x and y offsets and flags can +be specified as well, like this: 40 10 0 0 color. + If the given font does not exist or the given glyphs are invalid for the given font, an error node will be returned. diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c index 76cbc4b308..2408b27c55 100644 --- a/gsk/gskrendernodeparser.c +++ b/gsk/gskrendernodeparser.c @@ -46,12 +46,20 @@ #include #endif +#include +#ifdef HAVE_PANGOFT +#include +#endif + +#include + typedef struct _Context Context; struct _Context { GHashTable *named_nodes; GHashTable *named_textures; + PangoFontMap *fontmap; }; typedef struct _Declaration Declaration; @@ -65,7 +73,7 @@ struct _Declaration }; static void -context_init (Context *context) +context_init (Context *context) { memset (context, 0, sizeof (Context)); } @@ -75,6 +83,7 @@ context_finish (Context *context) { g_clear_pointer (&context->named_nodes, g_hash_table_unref); g_clear_pointer (&context->named_textures, g_hash_table_unref); + g_clear_object (&context->fontmap); } static gboolean @@ -844,20 +853,35 @@ parse_mask_mode (GtkCssParser *parser, } static PangoFont * -font_from_string (const char *string) +font_from_string (PangoFontMap *fontmap, + const char *string) { PangoFontDescription *desc; - PangoFontMap *font_map; - PangoContext *context; + PangoContext *ctx; PangoFont *font; desc = pango_font_description_from_string (string); - font_map = pango_cairo_font_map_get_default (); - context = pango_font_map_create_context (font_map); - font = pango_font_map_load_font (font_map, context, desc); + ctx = pango_font_map_create_context (fontmap); + font = pango_font_map_load_font (fontmap, ctx, desc); + g_object_unref (ctx); + + if (font) + { + PangoFontDescription *desc2; + const char *family, *family2; + + desc2 = pango_font_describe (font); + + family = pango_font_description_get_family (desc); + family2 = pango_font_description_get_family (desc2); + + if (g_strcmp0 (family, family2) != 0) + g_clear_object (&font); + + pango_font_description_free (desc2); + } pango_font_description_free (desc); - g_object_unref (context); return font; } @@ -926,22 +950,204 @@ create_ascii_glyphs (PangoFont *font) return result; } +#ifdef HAVE_PANGOFT + +static void +delete_file (gpointer data) +{ + char *path = data; + + g_remove (path); + g_free (path); +} + +static void +ensure_fontmap (Context *context) +{ + FcConfig *config; + GPtrArray *files; + + if (context->fontmap) + return; + + context->fontmap = pango_cairo_font_map_new (); + + config = FcInitLoadConfig (); + pango_fc_font_map_set_config (PANGO_FC_FONT_MAP (context->fontmap), config); + FcConfigDestroy (config); + + files = g_ptr_array_new_with_free_func (delete_file); + + g_object_set_data_full (G_OBJECT (context->fontmap), "font-files", files, (GDestroyNotify) g_ptr_array_unref); +} + +static void +add_font_from_file (Context *context, + const char *path, + GError **error) +{ + FcConfig *config; + GPtrArray *files; + + ensure_fontmap (context); + + if (!PANGO_IS_FC_FONT_MAP (context->fontmap)) + { + g_set_error (error, + GTK_CSS_PARSER_ERROR, + GTK_CSS_PARSER_ERROR_FAILED, + "Custom fonts are not implemented for %s", G_OBJECT_TYPE_NAME (context->fontmap)); + return; + } + + config = pango_fc_font_map_get_config (PANGO_FC_FONT_MAP (context->fontmap)); + + if (!FcConfigAppFontAddFile (config, (FcChar8 *) path)) + { + g_set_error (error, + GTK_CSS_PARSER_ERROR, + GTK_CSS_PARSER_ERROR_FAILED, + "Failed to add %s to FcConfig", path); + return; + } + + files = (GPtrArray *) g_object_get_data (G_OBJECT (context->fontmap), "font-files"); + g_ptr_array_add (files, g_strdup (path)); + + pango_fc_font_map_config_changed (PANGO_FC_FONT_MAP (context->fontmap)); +} + +static void +add_font_from_bytes (Context *context, + GBytes *bytes, + GError **error) +{ + GFile *file; + GIOStream *iostream; + GOutputStream *ostream; + + file = g_file_new_tmp ("gtk4-font-XXXXXX.ttf", (GFileIOStream **) &iostream, error); + if (!file) + return; + + ostream = g_io_stream_get_output_stream (iostream); + if (g_output_stream_write_bytes (ostream, bytes, NULL, error) == -1) + { + g_object_unref (file); + g_object_unref (iostream); + + return; + } + + g_io_stream_close (iostream, NULL, NULL); + g_object_unref (iostream); + + add_font_from_file (context, g_file_peek_path (file), error); + + g_object_unref (file); +} + +#else /* !HAVE_PANGOFT */ + +static void +add_font_from_bytes (Context *context, + GBytes *bytes, + GError **error) +{ + g_set_error (error, + GTK_CSS_PARSER_ERROR, + GTK_CSS_PARSER_ERROR_FAILED, + "Not implemented"); +} + +#endif + static gboolean parse_font (GtkCssParser *parser, Context *context, gpointer out_font) { - PangoFont *font; + PangoFont *font = NULL; char *s; + GtkCssLocation start_location; + PangoFontMap *fontmap; + + fontmap = pango_cairo_font_map_get_default (); s = gtk_css_parser_consume_string (parser); if (s == NULL) return FALSE; - font = font_from_string (s); - if (font == NULL) + start_location = *gtk_css_parser_get_start_location (parser); + + if (gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_URL) || + gtk_css_parser_has_function (parser, "url")) { - gtk_css_parser_error_syntax (parser, "This font does not exist."); + char *url; + char *scheme; + GBytes *bytes = NULL; + GError *error = NULL; + + /* If we have a url, it is a bug if the font already exists in our custom fontmap */ + if (context->fontmap) + { + font = font_from_string (context->fontmap, s); + if (font) + { + g_object_unref (font); + gtk_css_parser_error_value (parser, "This font already exists."); + return FALSE; + } + } + + url = gtk_css_parser_consume_url (parser); + + scheme = g_uri_parse_scheme (url); + if (scheme && g_ascii_strcasecmp (scheme, "data") == 0) + { + bytes = gtk_css_data_url_parse (url, NULL, &error); + } + else + { + GFile *file; + + file = g_file_new_for_uri (url); + bytes = g_file_load_bytes (file, NULL, NULL, &error); + g_object_unref (file); + } + + g_free (scheme); + g_free (url); + + if (bytes) + { + add_font_from_bytes (context, bytes, &error); + g_bytes_unref (bytes); + + fontmap = context->fontmap; + } + else + { + g_assert (error != NULL); + + gtk_css_parser_emit_error (parser, + &start_location, + gtk_css_parser_get_end_location (parser), + error); + g_clear_error (&error); + + return FALSE; + } + } + + font = font_from_string (fontmap, s); + + if (!font && context->fontmap && fontmap != context->fontmap) + font = font_from_string (context->fontmap, s); + + if (!font) + { + gtk_css_parser_error_value (parser, "This font does not exist."); return FALSE; } @@ -2013,7 +2219,7 @@ parse_text_node (GtkCssParser *parser, if (font == NULL) { - font = font_from_string ("Cantarell 11"); + font = font_from_string (pango_cairo_font_map_get_default (), "Cantarell 11"); g_assert (font); } diff --git a/gsk/gskrendernodeparserprivate.h b/gsk/gskrendernodeparserprivate.h index e22ee9e970..423c78e5ca 100644 --- a/gsk/gskrendernodeparserprivate.h +++ b/gsk/gskrendernodeparserprivate.h @@ -6,4 +6,3 @@ GskRenderNode * gsk_render_node_deserialize_from_bytes (GBytes *bytes, GskParseErrorFunc error_func, gpointer user_data); - diff --git a/testsuite/gsk/nodeparser/text-fail.errors b/testsuite/gsk/nodeparser/text-fail.errors index 612c1ad4b5..66d41ec380 100644 --- a/testsuite/gsk/nodeparser/text-fail.errors +++ b/testsuite/gsk/nodeparser/text-fail.errors @@ -1,3 +1,3 @@ -:4:3-7: error: GTK_CSS_PARSER_WARNING_SYNTAX +:3:11-12: error: GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE +:4:11-12: error: GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE :6:3-9: error: GTK_CSS_PARSER_WARNING_SYNTAX -:8:1-2: error: GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE diff --git a/testsuite/gsk/nodeparser/text-fail.ref.node b/testsuite/gsk/nodeparser/text-fail.ref.node index 404fb1b892..a2a4db5aea 100644 --- a/testsuite/gsk/nodeparser/text-fail.ref.node +++ b/testsuite/gsk/nodeparser/text-fail.ref.node @@ -1,4 +1,6 @@ -color { - bounds: 0 0 50 50; - color: rgb(255,0,204); +text { + color: rgb(50,50,50); + font: "Cantarell 11"; + glyphs: "N"; + offset: 0 32.0186; }