From a6ffd6b3b22aa76f75cac470f5725d9c131044ac Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 5 May 2024 10:19:25 -0400 Subject: [PATCH] nodeparser: Subset fonts When serializing nodes, collect the glyphs that are used from each font, subset the font to that set of glyphs, and embed it into the node file. We are careful to preserve the glyph IDs, so our text nodes transparently work with the subsettted fonts. --- gsk/gskrendernodeparser.c | 155 ++++++++++++++++++++++++++------------ gtk/meson.build | 1 + meson.build | 1 + 3 files changed, 108 insertions(+), 49 deletions(-) diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c index f3b403f154..07e415f37b 100644 --- a/gsk/gskrendernodeparser.c +++ b/gsk/gskrendernodeparser.c @@ -54,6 +54,8 @@ #include #endif +#include + #include typedef struct _Context Context; @@ -946,23 +948,22 @@ create_ascii_glyphs (PangoFont *font) PangoGlyphString *result, *glyph_string; guint i; - coverage = pango_font_get_coverage (font, language); - for (i = MIN_ASCII_GLYPH; i < MAX_ASCII_GLYPH; i++) - { - if (!pango_coverage_get (coverage, i)) - break; - } - g_object_unref (coverage); - if (i < MAX_ASCII_GLYPH) - return NULL; - result = pango_glyph_string_new (); + + coverage = pango_font_get_coverage (font, language); + pango_glyph_string_set_size (result, N_ASCII_GLYPHS); glyph_string = pango_glyph_string_new (); for (i = MIN_ASCII_GLYPH; i < MAX_ASCII_GLYPH; i++) { const char text[2] = { i, 0 }; + if (!pango_coverage_get (coverage, i)) + { + result->glyphs[i - MIN_ASCII_GLYPH].glyph = PANGO_GLYPH_INVALID_INPUT; + continue; + } + pango_shape_with_flags (text, 1, text, 1, ¬_a_hack, @@ -970,14 +971,13 @@ create_ascii_glyphs (PangoFont *font) PANGO_SHAPE_NONE); if (glyph_string->num_glyphs != 1) - { - pango_glyph_string_free (glyph_string); - pango_glyph_string_free (result); - return NULL; - } - result->glyphs[i - MIN_ASCII_GLYPH] = glyph_string->glyphs[0]; + result->glyphs[i - MIN_ASCII_GLYPH].glyph = PANGO_GLYPH_INVALID_INPUT; + else + result->glyphs[i - MIN_ASCII_GLYPH] = glyph_string->glyphs[0]; } + g_object_unref (coverage); + pango_glyph_string_free (glyph_string); return result; @@ -1183,6 +1183,9 @@ parse_font (GtkCssParser *parser, "The given url does not define a font named \"%s\"", font_name); } + + /* Mark the font as created from a url, so we don't try to subset it again */ + g_object_set_data (G_OBJECT (font), "from-url", GINT_TO_POINTER (1)); } } } @@ -2255,6 +2258,12 @@ unpack_glyphs (PangoFont *font, return FALSE; } + if (ascii->glyphs[idx].glyph == PANGO_GLYPH_INVALID_INPUT) + { + g_clear_pointer (&ascii, pango_glyph_string_free); + return FALSE; + } + gi->glyph = ascii->glyphs[idx].glyph; gi->geometry.width = ascii->glyphs[idx].geometry.width; } @@ -2946,7 +2955,7 @@ typedef struct gsize named_node_counter; GHashTable *named_textures; gsize named_texture_counter; - GHashTable *serialized_fonts; + GHashTable *fonts; } Printer; static void @@ -2961,6 +2970,59 @@ printer_init_check_texture (Printer *printer, g_hash_table_insert (printer->named_textures, texture, g_strdup ("")); } +typedef struct { + hb_face_t *face; + hb_subset_input_t *input; + gboolean serialized; +} FontInfo; + +static void +font_info_free (gpointer data) +{ + FontInfo *info = (FontInfo *) data; + + hb_face_destroy (info->face); + if (info->input) + hb_subset_input_destroy (info->input); + g_free (info); +} + +static void +printer_init_collect_font_info (Printer *printer, + GskRenderNode *node) +{ + PangoFont *font; + FontInfo *info; + + font = gsk_text_node_get_font (node); + + info = (FontInfo *) g_hash_table_lookup (printer->fonts, font); + if (!info) + { + 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")) + { + info->input = hb_subset_input_create_or_fail (); + hb_subset_input_set_flags (info->input, HB_SUBSET_FLAGS_RETAIN_GIDS); + } + + g_hash_table_insert (printer->fonts, g_object_ref (font), info); + } + + if (info->input) + { + const PangoGlyphInfo *glyphs; + guint n_glyphs; + + glyphs = gsk_text_node_get_glyphs (node, &n_glyphs); + + for (guint i = 0; i < n_glyphs; i++) + hb_set_add (hb_subset_input_glyph_set (info->input), glyphs[i].glyph); + } +} + static void printer_init_duplicates_for_node (Printer *printer, GskRenderNode *node) @@ -2976,6 +3038,9 @@ printer_init_duplicates_for_node (Printer *printer, { case GSK_CAIRO_NODE: case GSK_TEXT_NODE: + printer_init_collect_font_info (printer, node); + break; + case GSK_COLOR_NODE: case GSK_LINEAR_GRADIENT_NODE: case GSK_REPEATING_LINEAR_GRADIENT_NODE: @@ -3099,7 +3164,7 @@ printer_init (Printer *self, self->named_node_counter = 0; self->named_textures = g_hash_table_new_full (NULL, NULL, NULL, g_free); self->named_texture_counter = 0; - self->serialized_fonts = g_hash_table_new (g_str_hash, g_str_equal); + self->fonts = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, font_info_free); printer_init_duplicates_for_node (self, node); } @@ -3111,7 +3176,7 @@ printer_clear (Printer *self) g_string_free (self->str, TRUE); g_hash_table_unref (self->named_nodes); g_hash_table_unref (self->named_textures); - g_hash_table_unref (self->serialized_fonts); + g_hash_table_unref (self->fonts); } #define IDENT_LEVEL 2 /* Spaces per level */ @@ -3576,9 +3641,14 @@ gsk_text_node_serialize_font (GskRenderNode *node, Printer *p) { PangoFont *font = gsk_text_node_get_font (node); - PangoFontMap *fontmap = pango_font_get_font_map (font); PangoFontDescription *desc; char *s; + FontInfo *info; + hb_face_t *face; + hb_blob_t *blob; + const char *data; + guint length; + char *b64; desc = pango_font_describe_with_absolute_size (font); s = pango_font_description_to_string (desc); @@ -3586,42 +3656,29 @@ gsk_text_node_serialize_font (GskRenderNode *node, g_free (s); pango_font_description_free (desc); - /* Check if this is a custom font that we created from a url */ - if (!g_object_get_data (G_OBJECT (fontmap), "font-files")) + info = g_hash_table_lookup (p->fonts, font); + if (info->serialized) return; -#ifdef HAVE_PANGOFT - { - FcPattern *pat; - FcResult res; - const char *file; - char *data; - gsize len; - char *b64; + if (info->input) + face = hb_subset_or_fail (info->face, info->input); + else + face = hb_face_reference (info->face); - pat = pango_fc_font_get_pattern (PANGO_FC_FONT (font)); - res = FcPatternGetString (pat, FC_FILE, 0, (FcChar8 **)&file); - if (res != FcResultMatch) - return; + blob = hb_face_reference_blob (face); + data = hb_blob_get_data (blob, &length); - if (g_hash_table_contains (p->serialized_fonts, file)) - return; + b64 = base64_encode_with_linebreaks ((const guchar *) data, length); - if (!g_file_get_contents (file, &data, &len, NULL)) - return; + g_string_append (p->str, " url(\"data:font/ttf;base64,\\\n"); + append_escaping_newlines (p->str, b64); + g_string_append (p->str, "\")"); - g_hash_table_add (p->serialized_fonts, (gpointer) file); + g_free (b64); + hb_blob_destroy (blob); + hb_face_destroy (face); - b64 = base64_encode_with_linebreaks ((const guchar *) data, len); - - g_string_append (p->str, " url(\"data:font/ttf;base64,\\\n"); - append_escaping_newlines (p->str, b64); - g_string_append (p->str, "\")"); - - g_free (b64); - g_free (data); - } -#endif + info->serialized = TRUE; } static void diff --git a/gtk/meson.build b/gtk/meson.build index 2a83114158..4187d50f6e 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -1002,6 +1002,7 @@ gtk_deps = [ platform_gio_dep, pangocairo_dep, harfbuzz_dep, + hb_subset_dep, fribidi_dep, cairogobj_dep, fontconfig_dep, diff --git a/meson.build b/meson.build index c3194846ee..b18c8ca882 100644 --- a/meson.build +++ b/meson.build @@ -399,6 +399,7 @@ fribidi_dep = dependency('fribidi', version: fribidi_req, default_options: ['docs=false']) harfbuzz_dep = dependency('harfbuzz', version: harfbuzz_req, default_options: ['coretext=enabled']) +hb_subset_dep = dependency('harfbuzz-subset', version: harfbuzz_req) # Require PangoFT2 if on X11 or wayland pangoft_dep = dependency('pangoft2', version: pango_req,