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.
This commit is contained in:
Matthias Clasen
2024-05-05 10:19:25 -04:00
parent 1080822ffa
commit a6ffd6b3b2
3 changed files with 108 additions and 49 deletions

View File

@@ -54,6 +54,8 @@
#include <pango/pangofc-fontmap.h>
#endif
#include <hb-subset.h>
#include <glib/gstdio.h>
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,
&not_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

View File

@@ -1002,6 +1002,7 @@ gtk_deps = [
platform_gio_dep,
pangocairo_dep,
harfbuzz_dep,
hb_subset_dep,
fribidi_dep,
cairogobj_dep,
fontconfig_dep,

View File

@@ -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,