From 15d15bc68f4ae75d3a1be28cf662ffa517b1745f Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 28 Jan 2024 21:36:47 -0500 Subject: [PATCH] Accept font options in node files The goal is to fix all the context that influences the rendering of text nodes in the node file. This includes the relevant parts of cairo_font_options_t (via pango_cairo_context_set_font_options) as well as the dpi (via pango_cairo_font_map_set_resolution). The default values we use match the default of the gsettings backing our GtkSettings: hint-metrics: off; hint-style: slight; antialias: on; dpi: 96; This will help with better font testing. Parser test included. --- demos/node-editor/node-format.md | 16 +- gsk/gskrendernodeparser.c | 305 ++++++++++++++---- testsuite/gsk/meson.build | 5 + .../nodeparser/text-font-options-error.errors | 5 + .../nodeparser/text-font-options-error.node | 18 ++ .../text-font-options-error.ref.node | 13 + .../gsk/nodeparser/text-font-options.node | 34 ++ .../gsk/nodeparser/text-font-options.ref.node | 30 ++ 8 files changed, 359 insertions(+), 67 deletions(-) create mode 100644 testsuite/gsk/nodeparser/text-font-options-error.errors create mode 100644 testsuite/gsk/nodeparser/text-font-options-error.node create mode 100644 testsuite/gsk/nodeparser/text-font-options-error.ref.node create mode 100644 testsuite/gsk/nodeparser/text-font-options.node create mode 100644 testsuite/gsk/nodeparser/text-font-options.ref.node diff --git a/demos/node-editor/node-format.md b/demos/node-editor/node-format.md index a824182788..27f19485a2 100644 --- a/demos/node-editor/node-format.md +++ b/demos/node-editor/node-format.md @@ -322,12 +322,16 @@ 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 | +| hint-metrics | `` | off | non-default | +| hint-style | `` | slight | non-default | +| antialias | `` | on | non-default | +| dpi | `` | 96 | non-default | Creates a node like `gsk_text_node_new()` with the given properties. diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c index 03183c61fb..6acc85cd8d 100644 --- a/gsk/gskrendernodeparser.c +++ b/gsk/gskrendernodeparser.c @@ -46,7 +46,9 @@ #include #endif +#include #include + #ifdef HAVE_PANGOFT #include #endif @@ -86,6 +88,39 @@ context_finish (Context *context) g_clear_object (&context->fontmap); } +static gboolean +parse_enum (GtkCssParser *parser, + GType type, + gpointer out_value) +{ + GEnumClass *class; + GEnumValue *v; + char *enum_name; + + enum_name = gtk_css_parser_consume_ident (parser); + if (enum_name == NULL) + return FALSE; + + class = g_type_class_ref (type); + + v = g_enum_get_value_by_nick (class, enum_name); + if (v == NULL) + { + gtk_css_parser_error_value (parser, "Unknown value \"%s\" for enum \"%s\"", + enum_name, g_type_name (type)); + g_free (enum_name); + g_type_class_unref (class); + return FALSE; + } + + *(int*)out_value = v->value; + + g_free (enum_name); + g_type_class_unref (class); + + return TRUE; +} + static gboolean parse_rect (GtkCssParser *parser, Context *context, @@ -887,6 +922,82 @@ font_from_string (PangoFontMap *fontmap, return font; } +static double +font_get_dpi (PangoFont *font) +{ +#ifdef HAVE_PANGOFT + if (PANGO_IS_FC_FONT (font)) + { + FcPattern *pattern; + double dpi; + + pattern = pango_fc_font_get_pattern (PANGO_FC_FONT (font)); + if (FcPatternGetDouble (pattern, FC_DPI, 0, &dpi) == FcResultMatch) + return dpi; + } +#endif + + /* FIXME, needs pango api */ + + return 96.0; +} + +static PangoFont * +recreate_font_with_options (PangoFont *font, + cairo_hint_metrics_t hint_metrics, + cairo_hint_style_t hint_style, + cairo_antialias_t antialias, + double dpi) +{ + PangoFontMap *fontmap; + PangoFontDescription *desc; + PangoContext *ctx; + cairo_scaled_font_t *sf; + cairo_font_options_t *options; + PangoFont *new_font; + double old_dpi; + + options = cairo_font_options_create (); + + sf = pango_cairo_font_get_scaled_font (PANGO_CAIRO_FONT (font)); + cairo_scaled_font_get_font_options (sf, options); + + if (cairo_font_options_get_hint_metrics (options) == hint_metrics && + cairo_font_options_get_hint_style (options) == hint_style && + cairo_font_options_get_antialias (options) == antialias && + font_get_dpi (font) == dpi) + { + cairo_font_options_destroy (options); + + return font; + } + + cairo_font_options_set_hint_metrics (options, hint_metrics); + cairo_font_options_set_hint_style (options, hint_style); + cairo_font_options_set_antialias (options, antialias); + + desc = pango_font_describe (font); + fontmap = pango_font_get_font_map (font); + + ctx = pango_font_map_create_context (fontmap); + pango_cairo_context_set_font_options (ctx, options); + + old_dpi = pango_cairo_font_map_get_resolution (PANGO_CAIRO_FONT_MAP (fontmap)); + pango_cairo_font_map_set_resolution (PANGO_CAIRO_FONT_MAP (fontmap), dpi); + + new_font = pango_font_map_load_font (fontmap, ctx, desc); + + pango_cairo_font_map_set_resolution (PANGO_CAIRO_FONT_MAP (fontmap), old_dpi); + + g_object_unref (ctx); + pango_font_description_free (desc); + cairo_font_options_destroy (options); + + g_object_unref (font); + + return new_font; +} + #define MIN_ASCII_GLYPH 32 #define MAX_ASCII_GLYPH 127 /* exclusive */ #define N_ASCII_GLYPHS (MAX_ASCII_GLYPH - MIN_ASCII_GLYPH) @@ -2217,6 +2328,61 @@ unpack_glyphs (PangoFont *font, return TRUE; } +static gboolean +parse_hint_metrics (GtkCssParser *parser, + Context *context, + gpointer out) +{ + if (!parse_enum (parser, CAIRO_GOBJECT_TYPE_HINT_METRICS, out)) + return FALSE; + + if (*(cairo_hint_metrics_t *) out == CAIRO_HINT_METRICS_DEFAULT) + { + gtk_css_parser_error_value (parser, "Unsupported value for enum \"%s\"", + g_type_name (CAIRO_GOBJECT_TYPE_HINT_METRICS)); + return FALSE; + } + + return TRUE; +} + +static gboolean +parse_hint_style (GtkCssParser *parser, + Context *context, + gpointer out) +{ + if (!parse_enum (parser, CAIRO_GOBJECT_TYPE_HINT_STYLE, out)) + return FALSE; + + if (*(cairo_hint_style_t *) out == CAIRO_HINT_STYLE_DEFAULT) + { + gtk_css_parser_error_value (parser, "Unsupported value for enum \"%s\"", + g_type_name (CAIRO_GOBJECT_TYPE_HINT_STYLE)); + return FALSE; + } + + return TRUE; +} + +static gboolean +parse_antialias (GtkCssParser *parser, + Context *context, + gpointer out) +{ + if (!parse_enum (parser, CAIRO_GOBJECT_TYPE_ANTIALIAS, out)) + return FALSE; + + if (*(cairo_antialias_t *) out != CAIRO_ANTIALIAS_NONE && + *(cairo_antialias_t *) out != CAIRO_ANTIALIAS_GRAY) + { + gtk_css_parser_error_value (parser, "Unsupported value for enum \"%s\"", + g_type_name (CAIRO_GOBJECT_TYPE_ANTIALIAS)); + return FALSE; + } + + return TRUE; +} + static GskRenderNode * parse_text_node (GtkCssParser *parser, Context *context) @@ -2225,11 +2391,19 @@ parse_text_node (GtkCssParser *parser, graphene_point_t offset = GRAPHENE_POINT_INIT (0, 0); GdkRGBA color = GDK_RGBA("000000"); PangoGlyphString *glyphs = NULL; + cairo_hint_metrics_t hint_metrics = CAIRO_HINT_METRICS_OFF; + cairo_hint_style_t hint_style = CAIRO_HINT_STYLE_SLIGHT; + cairo_antialias_t antialias = CAIRO_ANTIALIAS_GRAY; + double dpi = 96.0; const Declaration declarations[] = { { "font", parse_font, clear_font, &font }, { "offset", parse_point, NULL, &offset }, { "color", parse_color, NULL, &color }, - { "glyphs", parse_glyphs, clear_glyphs, &glyphs } + { "glyphs", parse_glyphs, clear_glyphs, &glyphs }, + { "hint-metrics", parse_hint_metrics, NULL, &hint_metrics }, + { "hint-style", parse_hint_style, NULL, &hint_style }, + { "antialias", parse_antialias, NULL, &antialias }, + { "dpi", parse_positive_double, NULL, &dpi }, }; GskRenderNode *result; @@ -2241,6 +2415,8 @@ parse_text_node (GtkCssParser *parser, g_assert (font); } + font = recreate_font_with_options (font, hint_metrics, hint_style, antialias, dpi); + if (!glyphs) { const char *text = "Hello"; @@ -2429,39 +2605,6 @@ clear_dash (gpointer inout_array) g_clear_pointer ((GArray **) inout_array, g_array_unref); } -static gboolean -parse_enum (GtkCssParser *parser, - GType type, - gpointer out_value) -{ - GEnumClass *class; - GEnumValue *v; - char *enum_name; - - enum_name = gtk_css_parser_consume_ident (parser); - if (enum_name == NULL) - return FALSE; - - class = g_type_class_ref (type); - - v = g_enum_get_value_by_nick (class, enum_name); - if (v == NULL) - { - gtk_css_parser_error_value (parser, "Unknown value \"%s\" for enum \"%s\"", - enum_name, g_type_name (type)); - g_free (enum_name); - g_type_class_unref (class); - return FALSE; - } - - *(int*)out_value = v->value; - - g_free (enum_name); - g_type_class_unref (class); - - return TRUE; -} - static gboolean parse_fill_rule (GtkCssParser *parser, Context *context, @@ -3248,6 +3391,33 @@ append_string_param (Printer *p, g_string_append_c (p->str, '\n'); } +static const char * +enum_to_nick (GType type, + int value) +{ + GEnumClass *class; + GEnumValue *v; + + class = g_type_class_ref (type); + v = g_enum_get_value (class, value); + g_type_class_unref (class); + + return v->value_nick; +} + +static void +append_enum_param (Printer *p, + const char *param_name, + GType type, + int value) +{ + _indent (p); + g_string_append_printf (p->str, "%s: ", param_name); + g_string_append (p->str, enum_to_nick (type, value)); + g_string_append_c (p->str, ';'); + g_string_append_c (p->str, '\n'); +} + static void append_vec4_param (Printer *p, const char *param_name, @@ -3515,6 +3685,43 @@ gsk_text_node_serialize_font (GskRenderNode *node, #endif } +static void +gsk_text_node_serialize_font_options (GskRenderNode *node, + Printer *p) +{ + PangoFont *font = gsk_text_node_get_font (node); + cairo_scaled_font_t *sf = pango_cairo_font_get_scaled_font (PANGO_CAIRO_FONT (font)); + cairo_font_options_t *options; + cairo_hint_metrics_t hint_metrics; + cairo_hint_style_t hint_style; + cairo_antialias_t antialias; + + options = cairo_font_options_create (); + cairo_scaled_font_get_font_options (sf, options); + hint_metrics = cairo_font_options_get_hint_metrics (options); + hint_style = cairo_font_options_get_hint_style (options); + antialias = cairo_font_options_get_antialias (options); + cairo_font_options_destroy (options); + + if (hint_metrics != CAIRO_HINT_METRICS_OFF && hint_metrics != CAIRO_HINT_METRICS_DEFAULT) + append_enum_param (p, "hint-metrics", CAIRO_GOBJECT_TYPE_HINT_METRICS, hint_metrics); + + if (hint_style != CAIRO_HINT_STYLE_SLIGHT && hint_style != CAIRO_HINT_STYLE_DEFAULT) + append_enum_param (p, "hint-style", CAIRO_GOBJECT_TYPE_HINT_STYLE, hint_style); + + if (antialias != CAIRO_ANTIALIAS_GRAY && antialias != CAIRO_ANTIALIAS_DEFAULT) + append_enum_param (p, "antialias", CAIRO_GOBJECT_TYPE_ANTIALIAS, antialias); +} + +static void +gsk_text_node_serialize_font_dpi (GskRenderNode *node, + Printer *p) +{ + PangoFont *font = gsk_text_node_get_font (node); + + append_float_param (p, "dpi", font_get_dpi (font), 96.f); +} + void gsk_text_node_serialize_glyphs (GskRenderNode *node, GString *p) @@ -3596,33 +3803,6 @@ gsk_text_node_serialize_glyphs (GskRenderNode *node, pango_glyph_string_free (ascii); } -static const char * -enum_to_nick (GType type, - int value) -{ - GEnumClass *class; - GEnumValue *v; - - class = g_type_class_ref (type); - v = g_enum_get_value (class, value); - g_type_class_unref (class); - - return v->value_nick; -} - -static void -append_enum_param (Printer *p, - const char *param_name, - GType type, - int value) -{ - _indent (p); - g_string_append_printf (p->str, "%s: ", param_name); - g_string_append (p->str, enum_to_nick (type, value)); - g_string_append_c (p->str, ';'); - g_string_append_c (p->str, '\n'); -} - static void append_path_param (Printer *p, const char *param_name, @@ -4087,6 +4267,9 @@ render_node_print (Printer *p, if (!graphene_point_equal (offset, graphene_point_zero ())) append_point_param (p, "offset", offset); + gsk_text_node_serialize_font_options (node, p); + gsk_text_node_serialize_font_dpi (node, p); + end_node (p); } break; diff --git a/testsuite/gsk/meson.build b/testsuite/gsk/meson.build index 33fe66d2c0..d25f767082 100644 --- a/testsuite/gsk/meson.build +++ b/testsuite/gsk/meson.build @@ -400,6 +400,11 @@ node_parser_tests = [ 'text-font-errors.node', 'text-font-errors.ref.node', 'text-font-errors.errors', + 'text-font-options.node', + 'text-font-options.ref.node', + 'text-font-options-error.node', + 'text-font-options-error.ref.node', + 'text-font-options-error.ref.errors', 'text-no-color.node', 'texture-fail.node', 'texture-fail.ref.node', diff --git a/testsuite/gsk/nodeparser/text-font-options-error.errors b/testsuite/gsk/nodeparser/text-font-options-error.errors new file mode 100644 index 0000000000..f6f0e52619 --- /dev/null +++ b/testsuite/gsk/nodeparser/text-font-options-error.errors @@ -0,0 +1,5 @@ +:7:17-24: error: GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE +:8:15-22: error: GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE +:9:14-21: error: GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE +:10:8-10: error: GTK_CSS_PARSER_ERROR_SYNTAX +:17:14-22: error: GTK_CSS_PARSER_ERROR_UNKNOWN_VALUE diff --git a/testsuite/gsk/nodeparser/text-font-options-error.node b/testsuite/gsk/nodeparser/text-font-options-error.node new file mode 100644 index 0000000000..e25fdd2776 --- /dev/null +++ b/testsuite/gsk/nodeparser/text-font-options-error.node @@ -0,0 +1,18 @@ + +text { + color: rgb(50,50,50); + font: "Cantarell 11"; + glyphs: "N", 430 5, 406 8, 417 7, 772 4, 783 5, 783 5, 793 6 0 0 same-cluster; + offset: 0 32.0186; + hint-metrics: default; + hint-style: default; + antialias: default; + dpi: -1; +} +text { + color: rgb(50,50,50); + font: "Cantarell 11"; + glyphs: "N", 430 5, 406 8, 417 7, 772 4, 783 5, 783 5, 793 6 0 0 same-cluster; + offset: 0 32.0186; + antialias: subpixel; +} diff --git a/testsuite/gsk/nodeparser/text-font-options-error.ref.node b/testsuite/gsk/nodeparser/text-font-options-error.ref.node new file mode 100644 index 0000000000..a63ec13769 --- /dev/null +++ b/testsuite/gsk/nodeparser/text-font-options-error.ref.node @@ -0,0 +1,13 @@ +text { + color: rgb(50,50,50); + font: "Cantarell 11"; + glyphs: "N", 430 5, 406 8, 417 7, 772 4, 783 5, 783 5, 793 6 0 0 same-cluster; + offset: 0 32.0186; +} +text { + color: rgb(50,50,50); + font: "Cantarell 11"; + glyphs: "N", 430 5, 406 8, 417 7, 772 4, 783 5, 783 5, 793 6 0 0 same-cluster; + offset: 0 32.0186; + antialias: subpixel; +} diff --git a/testsuite/gsk/nodeparser/text-font-options.node b/testsuite/gsk/nodeparser/text-font-options.node new file mode 100644 index 0000000000..9b5f94f672 --- /dev/null +++ b/testsuite/gsk/nodeparser/text-font-options.node @@ -0,0 +1,34 @@ +text { + color: rgb(50,50,50); + font: "Cantarell 11"; + glyphs: "N", 430 5, 406 8, 417 7, 772 4, 783 5, 783 5, 793 6 0 0 same-cluster; + offset: 0 32.0186; + hint-metrics: off; + hint-style: slight; + antialias: gray; + dpi: 96; +} +text { + color: rgb(50,50,50); + font: "Cantarell 11"; + glyphs: "N", 430 5, 406 8, 417 7, 772 4, 783 5, 783 5, 793 6 0 0 same-cluster; + offset: 0 32.0186; + hint-metrics: on; + hint-style: full; + antialias: none; + dpi: 111; +} +text { + color: rgb(50,50,50); + font: "Cantarell 11"; + glyphs: "N", 430 5, 406 8, 417 7, 772 4, 783 5, 783 5, 793 6 0 0 same-cluster; + offset: 0 32.0186; + hint-style: medium; +} +text { + color: rgb(50,50,50); + font: "Cantarell 11"; + glyphs: "N", 430 5, 406 8, 417 7, 772 4, 783 5, 783 5, 793 6 0 0 same-cluster; + offset: 0 32.0186; + hint-style: none; +} diff --git a/testsuite/gsk/nodeparser/text-font-options.ref.node b/testsuite/gsk/nodeparser/text-font-options.ref.node new file mode 100644 index 0000000000..4052ced4f1 --- /dev/null +++ b/testsuite/gsk/nodeparser/text-font-options.ref.node @@ -0,0 +1,30 @@ +text { + color: rgb(50,50,50); + font: "Cantarell 11"; + glyphs: "N", 430 5, 406 8, 417 7, 772 4, 783 5, 783 5, 793 6 0 0 same-cluster; + offset: 0 32.0186; +} +text { + color: rgb(50,50,50); + font: "Cantarell 10.9990234375"; + glyphs: "N", 430 5, 406 8, 417 7, 772 4, 783 5, 783 5, 793 6 0 0 same-cluster; + offset: 0 32.0186; + hint-metrics: on; + hint-style: full; + antialias: none; + dpi: 111; +} +text { + color: rgb(50,50,50); + font: "Cantarell 11"; + glyphs: "N", 430 5, 406 8, 417 7, 772 4, 783 5, 783 5, 793 6 0 0 same-cluster; + offset: 0 32.0186; + hint-style: medium; +} +text { + color: rgb(50,50,50); + font: "Cantarell 11"; + glyphs: "N", 430 5, 406 8, 417 7, 772 4, 783 5, 783 5, 793 6 0 0 same-cluster; + offset: 0 32.0186; + hint-style: none; +}