[pango/pango2: 1/25] Allow cascading fontmaps




commit 316d643785f3602afae23ccb7ce246379c3c73ed
Author: Matthias Clasen <mclasen redhat com>
Date:   Sat Jun 18 13:09:54 2022 -0700

    Allow cascading fontmaps
    
    Make it possible to set a fallback fontmap on a fontmap,
    that will be used for adding more results to the fontsets
    we produce.
    
    Test included.

 pango/pango-fontmap-private.h |   1 +
 pango/pango-fontmap.c         | 103 ++++++++++++++++++++++++++++++--
 pango/pango-fontmap.h         |   6 ++
 tests/test-font.c             | 135 ++++++++++++++++++++++++++++++++++++++----
 4 files changed, 226 insertions(+), 19 deletions(-)
---
diff --git a/pango/pango-fontmap-private.h b/pango/pango-fontmap-private.h
index 6c0c5e043..b353e2545 100644
--- a/pango/pango-fontmap-private.h
+++ b/pango/pango-fontmap-private.h
@@ -36,6 +36,7 @@ struct _PangoFontMap
   GPtrArray *families;
   GHashTable *fontsets;
   GQueue fontset_cache;
+  PangoFontMap *fallback;
 
   float dpi;
   gboolean in_populate;
diff --git a/pango/pango-fontmap.c b/pango/pango-fontmap.c
index fbcd4c86c..f6d6ee861 100644
--- a/pango/pango-fontmap.c
+++ b/pango/pango-fontmap.c
@@ -61,6 +61,8 @@
  *
  * `PangoFontMap` is the base class for font enumeration.
  * It also handles caching and lookup of faces and fonts.
+ * To obtain fonts from a `PangoFontMap`, use [method@Pango.FontMap.load_font]
+ * or [method@Pango.FontMap.load_fontset].
  *
  * Subclasses populate the fontmap using backend-specific APIs
  * to enumerate the available fonts on the sytem, but it is
@@ -68,6 +70,11 @@
  * populate it manually using [method@Pango.FontMap.add_file]
  * and [method@Pango.FontMap.add_face].
  *
+ * Fontmaps can be combined using [method@Pango.FontMap.set_fallback].
+ * This can be useful to add custom fonts to the default fonts
+ * without making them available to every user of the default
+ * fontmap.
+ *
  * Note that to be fully functional, a fontmap needs to provide
  * generic families for monospace and sans-serif. These can
  * be added using [method@Pango.FontMap.add_family] and
@@ -366,6 +373,7 @@ synthesize_bold_and_italic_faces (PangoFontMap *map)
 
 enum {
   PROP_RESOLUTION = 1,
+  PROP_FALLBACK,
   PROP_ITEM_TYPE,
   PROP_N_ITEMS,
   N_PROPERTIES
@@ -400,6 +408,7 @@ pango_font_map_finalize (GObject *object)
 {
   PangoFontMap *self = PANGO_FONT_MAP (object);
 
+  g_clear_object (&self->fallback);
   g_ptr_array_unref (self->added_faces);
   g_ptr_array_unref (self->added_families);
   g_hash_table_unref (self->families_hash);
@@ -423,6 +432,10 @@ pango_font_map_set_property (GObject      *object,
       pango_font_map_set_resolution (map, g_value_get_float (value));
       break;
 
+    case PROP_FALLBACK:
+      pango_font_map_set_fallback (map, g_value_get_object (value));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     }
@@ -442,6 +455,10 @@ pango_font_map_get_property (GObject    *object,
       g_value_set_float (value, map->dpi);
       break;
 
+    case PROP_FALLBACK:
+      g_value_set_object (value, map->fallback);
+      break;
+
     case PROP_ITEM_TYPE:
       g_value_set_gtype (value, PANGO_TYPE_FONT_FAMILY);
       break;
@@ -497,6 +514,7 @@ pango_font_map_default_load_fontset (PangoFontMap               *self,
   PangoFontFamily *family;
   PangoFontFace *face;
   gboolean has_generic = FALSE;
+  PangoFontset *fallback;
   gint64 before G_GNUC_UNUSED;
 
   before = PANGO_TRACE_CURRENT_TIME;
@@ -525,8 +543,13 @@ pango_font_map_default_load_fontset (PangoFontMap               *self,
 
   if (self->families->len == 0)
     {
-      g_warning ("Font map contains no fonts!!!!");
-      goto done_no_cache;
+      if (self->fallback)
+        goto add_fallback;
+      else
+        {
+          g_warning ("Font map contains no fonts!!!!");
+          goto done_no_cache;
+        }
     }
 
   families = g_strsplit (family_name ? family_name : "", ",", -1);
@@ -558,20 +581,30 @@ pango_font_map_default_load_fontset (PangoFontMap               *self,
 
   g_strfreev (families);
 
+  pango_font_description_free (copy);
+
   /* Returning an empty fontset leads to bad outcomes.
    *
    * We always include a generic family in order
    * to produce fontsets with good coverage.
+   *
+   * If we have a fallback fontmap, this is where we bring
+   * it in and just add its results to ours.
    */
   if (!has_generic)
     {
       family = find_family (self, "sans-serif");
       if (PANGO_IS_GENERIC_FAMILY (family))
         pango_fontset_cached_add_family (fontset, PANGO_GENERIC_FAMILY (family));
+      else if (self->fallback)
+        {
+add_fallback:
+          fallback = pango_font_map_load_fontset (self->fallback, context, description, language);
+          pango_fontset_cached_append (fontset, PANGO_FONTSET_CACHED (fallback));
+          g_object_unref (fallback);
+        }
     }
 
-  pango_font_description_free (copy);
-
   g_hash_table_add (self->fontsets, fontset);
 
 done:
@@ -585,7 +618,7 @@ done:
     }
 
 done_no_cache:
-  pango_trace_mark (before, "pango_hb_fontmap_load_fontset", "%s", family_name);
+  pango_trace_mark (before, "pango_fontmap_load_fontset", "%s", family_name);
 
   return g_object_ref (PANGO_FONTSET (fontset));
 }
@@ -646,6 +679,16 @@ pango_font_map_class_init (PangoFontMapClass *class)
       g_param_spec_float ("resolution", NULL, NULL, 0, G_MAXFLOAT, 96.0,
                           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
+  /**
+   * PangoFontMap:fallback: (attributes org.gtk.Property.get=pango_font_map_get_fallback 
org.gtk.Property.set=pango_font_map_set_fallback)
+   *
+   * The fallback fontmap is used to look up fonts that
+   * this map does not have itself.
+   */
+  properties[PROP_FALLBACK] =
+      g_param_spec_object ("fallback", NULL, NULL, PANGO_TYPE_FONT_MAP,
+                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
   /**
    * PangoFontMap:item-type:
    *
@@ -762,6 +805,8 @@ pango_font_map_load_font (PangoFontMap               *self,
                           const PangoFontDescription *desc)
 {
   g_return_val_if_fail (PANGO_IS_FONT_MAP (self), NULL);
+  g_return_val_if_fail (PANGO_IS_CONTEXT (context), NULL);
+  g_return_val_if_fail (desc != NULL, NULL);
 
   return PANGO_FONT_MAP_GET_CLASS (self)->load_font (self, context, desc);
 }
@@ -771,7 +816,8 @@ pango_font_map_load_font (PangoFontMap               *self,
  * @self: a `PangoFontMap`
  * @context: the `PangoContext` the font will be used with
  * @desc: a `PangoFontDescription` describing the font to load
- * @language: a `PangoLanguage` the fonts will be used for
+ * @language: (nullable): a `PangoLanguage` the fonts will be used for,
+ *    or `NULL` to use the language of @context
  *
  * Load a set of fonts in the fontmap that can be used to render
  * a font matching @desc.
@@ -786,6 +832,8 @@ pango_font_map_load_fontset (PangoFontMap               *self,
                              PangoLanguage              *language)
 {
   g_return_val_if_fail (PANGO_IS_FONT_MAP (self), NULL);
+  g_return_val_if_fail (PANGO_IS_CONTEXT (context), NULL);
+  g_return_val_if_fail (desc != NULL, NULL);
 
   return PANGO_FONT_MAP_GET_CLASS (self)->load_fontset (self, context, desc, language);
 }
@@ -1039,6 +1087,49 @@ pango_font_map_remove_family (PangoFontMap    *self,
   g_ptr_array_remove_index (self->added_families, position);
 }
 
+/**
+ * pango_font_map_set_fallback:
+ * @self: a `PangoFontMap`
+ * @fallback: (nullable): the `PangoFontMap` to use as fallback
+ *
+ * Sets the fontmap to use as fallback when a font isn't found.
+ *
+ * This can be used to make a custom font available only via a
+ * special fontmap, while still having all the regular fonts
+ * from the fallback fontmap.
+ */
+void
+pango_font_map_set_fallback (PangoFontMap *self,
+                             PangoFontMap *fallback)
+{
+  g_return_if_fail (PANGO_IS_FONT_MAP (self));
+  g_return_if_fail (fallback == NULL || PANGO_IS_FONT_MAP (fallback));
+
+  if (!g_set_object (&self->fallback, fallback))
+    return;
+
+  clear_caches (self);
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FALLBACK]);
+}
+
+/**
+ * pango_font_map_get_fallback:
+ * @self: a `PangoFontMap`
+ *
+ * Returns the fallback fontmap of @self
+ *
+ * See [method@Pango.FontMap.set_fallback].
+ *
+ * Returns: (nullable) (transfer none): the fallback fontmap
+ */
+PangoFontMap *
+pango_font_map_get_fallback (PangoFontMap *self)
+{
+  g_return_val_if_fail (PANGO_IS_FONT_MAP (self), NULL);
+
+  return self->fallback;
+}
+
 /**
  * pango_font_map_set_resolution:
  * @self: a `PangoFontMap`
diff --git a/pango/pango-fontmap.h b/pango/pango-fontmap.h
index 14ff5c30f..de806a8ab 100644
--- a/pango/pango-fontmap.h
+++ b/pango/pango-fontmap.h
@@ -68,6 +68,12 @@ PANGO_AVAILABLE_IN_ALL
 void                    pango_font_map_remove_family            (PangoFontMap               *self,
                                                                  PangoFontFamily            *family);
 
+PANGO_AVAILABLE_IN_ALL
+void                    pango_font_map_set_fallback             (PangoFontMap               *self,
+                                                                 PangoFontMap               *fallback);
+PANGO_AVAILABLE_IN_ALL
+PangoFontMap *          pango_font_map_get_fallback             (PangoFontMap               *self);
+
 PANGO_AVAILABLE_IN_ALL
 float                   pango_font_map_get_resolution           (PangoFontMap               *self);
 
diff --git a/tests/test-font.c b/tests/test-font.c
index c457fac35..f6b5b6867 100644
--- a/tests/test-font.c
+++ b/tests/test-font.c
@@ -1,5 +1,5 @@
 /* Pango
- * test-font.c: Test PangoFontDescription
+ * test-font.c: Test fonts and font maps
  *
  * Copyright (C) 2014 Red Hat, Inc
  *
@@ -160,11 +160,7 @@ test_metrics (void)
   PangoFontMetrics *metrics;
   char *str;
 
-
-  if (strcmp (G_OBJECT_TYPE_NAME (pango_context_get_font_map (context)), "PangoCairoWin32FontMap") == 0)
-    desc = pango_font_description_from_string ("Verdana 11");
-  else
-    desc = pango_font_description_from_string ("Cantarell 11");
+  desc = pango_font_description_from_string ("Cantarell 11");
 
   str = pango_font_description_to_string (desc);
 
@@ -227,7 +223,7 @@ test_extents (void)
 }
 
 static void
-test_enumerate (void)
+test_fontmap_enumerate (void)
 {
   PangoFontMap *fontmap;
   PangoContext *context;
@@ -424,7 +420,7 @@ test_roundtrip_emoji (void)
 }
 
 static void
-test_font_models (void)
+test_fontmap_models (void)
 {
   PangoFontMap *map = pango_font_map_get_default ();
   int n_families = 0;
@@ -536,6 +532,118 @@ test_faceid (void)
   pango_font_description_free (desc);
 }
 
+static gboolean
+font_info_cb (PangoUserFace     *face,
+              int                size,
+              hb_font_extents_t *extents,
+              gpointer           user_data)
+{
+  extents->ascender = 0.75 * size;
+  extents->descender = - 0.25 * size;
+  extents->line_gap = 0;
+
+  return TRUE;
+}
+
+static gboolean
+glyph_cb (PangoUserFace  *face,
+          hb_codepoint_t  unicode,
+          hb_codepoint_t *glyph,
+          gpointer        user_data)
+{
+  if (unicode == ' ')
+    {
+      *glyph = 0x20;
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+glyph_info_cb (PangoUserFace      *face,
+               int                 size,
+               hb_codepoint_t      glyph,
+               hb_glyph_extents_t *extents,
+               hb_position_t      *h_advance,
+               hb_position_t      *v_advance,
+               gboolean           *is_color,
+               gpointer            user_data)
+{
+  return FALSE;
+}
+
+static gboolean
+count_fonts (PangoFontset *fontset,
+             PangoFont    *font,
+             gpointer      user_data)
+{
+  int *count = user_data;
+
+  (*count)++;
+
+  return FALSE;
+}
+
+/* test that fontmap fallback works as expected */
+static void
+test_fontmap_fallback (void)
+{
+  PangoFontMap *map;
+  PangoUserFace *custom;
+  PangoFontDescription *desc;
+  PangoFontset *fontset;
+  PangoFont *font;
+  int count;
+
+  map = pango_font_map_new ();
+
+  desc = pango_font_description_from_string ("CustomFont");
+  custom = pango_user_face_new (font_info_cb,
+                                glyph_cb,
+                                glyph_info_cb,
+                                NULL,
+                                NULL,
+                                NULL, NULL,
+                                "Regular", desc);
+  pango_font_description_free (desc);
+
+  pango_font_map_add_face (map, PANGO_FONT_FACE (custom));
+
+  desc = pango_font_description_from_string ("CustomFont 11");
+  fontset = pango_font_map_load_fontset (map, context, desc, NULL);
+  g_assert_nonnull (fontset);
+
+  font = pango_fontset_get_font (fontset, 0x20);
+  g_assert_nonnull (font);
+  g_assert_true (pango_font_get_face (font) == PANGO_FONT_FACE (custom));
+  g_object_unref (font);
+
+  count = 0;
+  pango_fontset_foreach (fontset, count_fonts, &count);
+  g_assert_true (count == 1);
+
+  pango_font_map_set_fallback (map, pango_font_map_get_default ());
+
+  desc = pango_font_description_from_string ("CustomFont 11");
+  fontset = pango_font_map_load_fontset (map, context, desc, NULL);
+  g_assert_nonnull (fontset);
+
+  font = pango_fontset_get_font (fontset, ' ');
+  g_assert_nonnull (font);
+  g_assert_true (pango_font_get_face (font) == PANGO_FONT_FACE (custom));
+  g_object_unref (font);
+
+  font = pango_fontset_get_font (fontset, 'a');
+  g_assert_nonnull (font);
+
+  count = 0;
+  pango_fontset_foreach (fontset, count_fonts, &count);
+  g_assert_true (count > 1);
+
+  g_object_unref (map);
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -547,21 +655,22 @@ main (int argc, char *argv[])
 
   context = pango_context_new ();
 
-  g_test_add_func ("/pango/font/metrics", test_metrics);
   g_test_add_func ("/pango/fontdescription/parse", test_parse);
   g_test_add_func ("/pango/fontdescription/roundtrip", test_roundtrip);
   g_test_add_func ("/pango/fontdescription/variations", test_variations);
   g_test_add_func ("/pango/fontdescription/empty-variations", test_empty_variations);
   g_test_add_func ("/pango/fontdescription/set-gravity", test_set_gravity);
   g_test_add_func ("/pango/fontdescription/faceid", test_faceid);
+  g_test_add_func ("/pango/font/metrics", test_metrics);
   g_test_add_func ("/pango/font/extents", test_extents);
-  g_test_add_func ("/pango/font/enumerate", test_enumerate);
+  g_test_add_func ("/pango/font/glyph-extents", test_glyph_extents);
+  g_test_add_func ("/pango/font/font-metrics", test_font_metrics);
   g_test_add_func ("/pango/font/roundtrip/plain", test_roundtrip_plain);
   g_test_add_func ("/pango/font/roundtrip/small-caps", test_roundtrip_small_caps);
   g_test_add_func ("/pango/font/roundtrip/emoji", test_roundtrip_emoji);
-  g_test_add_func ("/pango/font/models", test_font_models);
-  g_test_add_func ("/pango/font/glyph-extents", test_glyph_extents);
-  g_test_add_func ("/pango/font/font-metrics", test_font_metrics);
+  g_test_add_func ("/pango/fontmap/enumerate", test_fontmap_enumerate);
+  g_test_add_func ("/pango/fontmap/models", test_fontmap_models);
+  g_test_add_func ("/pango/fontmap/fallback", test_fontmap_fallback);
 
   return g_test_run ();
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]