[pango/pango2: 10/84] Introduce user fonts




commit 2f0ef523185ca12ebcb66eb8dad9a5cadb5384fb
Author: Matthias Clasen <mclasen redhat com>
Date:   Thu Jan 27 00:46:15 2022 -0500

    Introduce user fonts
    
    Add a way to create callback-based faces
    and fonts. The cairo implementation of this
    uses cairos user fonts.
    
    New APIs: PangoUserFace and PangoUserFont

 meson.build                          |   4 +
 pango/meson.build                    |   4 +
 pango/pango-generic-family-private.h |   3 +-
 pango/pango-generic-family.c         |   4 +-
 pango/pango-hbface-private.h         |  24 +-
 pango/pango-hbface.c                 |   3 +-
 pango/pango-hbface.h                 |   2 +
 pango/pango-hbfamily-private.h       |   6 +-
 pango/pango-hbfamily.c               | 100 +++++---
 pango/pango-hbfont-private.h         |  23 +-
 pango/pango-hbfont.c                 |  22 +-
 pango/pango-hbfont.h                 |   1 +
 pango/pango-hbfontmap.c              | 111 +++++---
 pango/pango-hbfontmap.h              |   4 +-
 pango/pango-userface-private.h       |  54 ++++
 pango/pango-userface.c               | 483 +++++++++++++++++++++++++++++++++++
 pango/pango-userface.h               |  81 ++++++
 pango/pango-userfont-private.h       |  39 +++
 pango/pango-userfont.c               | 482 ++++++++++++++++++++++++++++++++++
 pango/pango-userfont.h               |  46 ++++
 pango/pango.h                        |   2 +
 pango/pangocairo-font.c              | 115 +++++++--
 pango/pangofc-hbfontmap.c            |   2 +-
 pango/shape.c                        |  85 +++---
 tests/testhbfont.c                   |  26 +-
 25 files changed, 1573 insertions(+), 153 deletions(-)
---
diff --git a/meson.build b/meson.build
index 07cdfd610..026617176 100644
--- a/meson.build
+++ b/meson.build
@@ -400,6 +400,10 @@ if not cairo_dep.found()
   cairo_found_type = cairo_dep.type_name()
 endif
 
+if cc.has_function('cairo_user_font_face_set_render_color_glyph_func', args: '-lcairo', dependencies: 
cairo_dep)
+  pango_conf.set('HAVE_CAIRO_USER_FONT_FACE_SET_RENDER_COLOR_GLYPH_FUNC', 1)
+endif
+
 pango_font_backends = []
 pango_cairo_backends = []
 
diff --git a/pango/meson.build b/pango/meson.build
index 50a4bd5a9..fefc32412 100644
--- a/pango/meson.build
+++ b/pango/meson.build
@@ -46,6 +46,8 @@ pango_sources = [
   'pango-generic-family.c',
   'pango-hbfont.c',
   'pango-hbfontmap.c',
+  'pango-userface.c',
+  'pango-userfont.c',
 ]
 
 pango_headers = [
@@ -86,6 +88,8 @@ pango_headers = [
   'pango-hbface.h',
   'pango-hbfont.h',
   'pango-hbfontmap.h',
+  'pango-userface.h',
+  'pango-userfont.h',
 ]
 
 pango_gir_includes = [
diff --git a/pango/pango-generic-family-private.h b/pango/pango-generic-family-private.h
index efc2bfb23..5584e935c 100644
--- a/pango/pango-generic-family-private.h
+++ b/pango/pango-generic-family-private.h
@@ -20,7 +20,6 @@
 #pragma once
 
 #include "pango-font.h"
-#include "pango-hbface.h"
 #include "pango-generic-family.h"
 #include "pango-hbfamily-private.h"
 
@@ -38,7 +37,7 @@ struct _PangoGenericFamily
 void                    pango_generic_family_set_font_map       (PangoGenericFamily      *self,
                                                                  PangoFontMap            *map);
 
-PangoHbFace *           pango_generic_family_find_face          (PangoGenericFamily     *self,
+PangoFontFace *         pango_generic_family_find_face          (PangoGenericFamily     *self,
                                                                  PangoFontDescription   *description,
                                                                  PangoLanguage          *language,
                                                                  gunichar                wc);
diff --git a/pango/pango-generic-family.c b/pango/pango-generic-family.c
index d8c3dc7f4..586310def 100644
--- a/pango/pango-generic-family.c
+++ b/pango/pango-generic-family.c
@@ -199,13 +199,13 @@ pango_generic_family_set_font_map (PangoGenericFamily *self,
  *
  * Returns: (transfer none) (nullable): the face
  */
-PangoHbFace *
+PangoFontFace *
 pango_generic_family_find_face (PangoGenericFamily   *self,
                                 PangoFontDescription *description,
                                 PangoLanguage        *language,
                                 gunichar              wc)
 {
-  PangoHbFace *face = NULL;
+  PangoFontFace *face = NULL;
 
   for (int i = 0; i < self->families->len; i++)
     {
diff --git a/pango/pango-hbface-private.h b/pango/pango-hbface-private.h
index 7eb8bc1a3..71a95ac49 100644
--- a/pango/pango-hbface-private.h
+++ b/pango/pango-hbface-private.h
@@ -26,21 +26,35 @@
 #include <hb.h>
 
 
+typedef struct _CommonFace CommonFace;
+struct _CommonFace {
+  PangoFontFace parent_instance;
+
+  PangoFontDescription *description;
+  char *name;
+  PangoFontFamily *family;
+  char *psname;
+  char *faceid;
+};
+
 struct _PangoHbFace
 {
   PangoFontFace parent_instance;
 
+  PangoFontDescription *description;
+  char *name;
+  PangoFontFamily *family;
+  char *psname;
+  char *faceid;
+
+  /* up to here shared with PangoUserFace */
+
   unsigned int index;
   int instance_id;
   char *file;
   hb_face_t *face;
-  char *psname;
-  char *faceid;
   hb_variation_t *variations;
   unsigned int n_variations;
-  char *name;
-  PangoFontFamily *family;
-  PangoFontDescription *description;
   PangoMatrix *matrix;
   double x_scale, y_scale;
   PangoLanguageSet *languages;
diff --git a/pango/pango-hbface.c b/pango/pango-hbface.c
index bbbda2448..8ccd7223c 100644
--- a/pango/pango-hbface.c
+++ b/pango/pango-hbface.c
@@ -283,7 +283,8 @@ pango_hb_face_finalize (GObject *object)
 {
   PangoHbFace *self = PANGO_HB_FACE (object);
 
-  hb_face_destroy (self->face);
+  if (self->face)
+    hb_face_destroy (self->face);
   pango_font_description_free (self->description);
   g_free (self->name);
   g_free (self->file);
diff --git a/pango/pango-hbface.h b/pango/pango-hbface.h
index d2dd9973b..7e485fa4d 100644
--- a/pango/pango-hbface.h
+++ b/pango/pango-hbface.h
@@ -26,6 +26,8 @@
 
 G_BEGIN_DECLS
 
+typedef struct _PangoHbFont PangoHbFont;
+
 #define PANGO_TYPE_HB_FACE      (pango_hb_face_get_type ())
 
 PANGO_AVAILABLE_IN_ALL
diff --git a/pango/pango-hbfamily-private.h b/pango/pango-hbfamily-private.h
index 30c2f2e86..bf5545b82 100644
--- a/pango/pango-hbfamily-private.h
+++ b/pango/pango-hbfamily-private.h
@@ -42,12 +42,12 @@ void                    pango_hb_family_set_font_map    (PangoHbFamily
                                                          PangoFontMap           *map);
 
 void                    pango_hb_family_add_face        (PangoHbFamily          *self,
-                                                         PangoHbFace            *face);
+                                                         PangoFontFace          *face);
 
 void                    pango_hb_family_remove_face     (PangoHbFamily          *self,
-                                                         PangoHbFace            *face);
+                                                         PangoFontFace          *face);
 
-PangoHbFace *           pango_hb_family_find_face       (PangoHbFamily          *self,
+PangoFontFace *         pango_hb_family_find_face       (PangoHbFamily          *self,
                                                          PangoFontDescription   *description,
                                                          PangoLanguage          *language,
                                                          gunichar                wc);
diff --git a/pango/pango-hbfamily.c b/pango/pango-hbfamily.c
index a0f745130..05edcefbf 100644
--- a/pango/pango-hbfamily.c
+++ b/pango/pango-hbfamily.c
@@ -27,6 +27,7 @@
 #include "pango-hbface-private.h"
 #include "pango-font-face-private.h"
 #include "pango-font-description-private.h"
+#include "pango-userface-private.h"
 
 /* {{{ GListModel implementation */
 
@@ -46,7 +47,7 @@ pango_hb_family_get_n_items (GListModel *list)
 
 static gpointer
 pango_hb_family_get_item (GListModel *list,
-                              guint       position)
+                          guint       position)
 {
   PangoHbFamily *self = PANGO_HB_FAMILY (list);
 
@@ -67,28 +68,59 @@ pango_hb_family_list_model_init (GListModelInterface *iface)
 /* }}} */
 /* {{{ Utilities */
 
+static void
+face_set_family (PangoFontFace   *face,
+                 PangoFontFamily *family)
+{
+  if (PANGO_IS_HB_FACE (face))
+    pango_hb_face_set_family (PANGO_HB_FACE (face), family);
+  else
+    pango_user_face_set_family (PANGO_USER_FACE (face), family);
+}
+
+static const char *
+face_get_faceid (PangoFontFace *face)
+{
+  if (PANGO_IS_HB_FACE (face))
+    return pango_hb_face_get_faceid (PANGO_HB_FACE (face));
+  else
+    return pango_user_face_get_faceid (PANGO_USER_FACE (face));
+}
+
+static gboolean
+face_has_char (PangoFontFace *face,
+               gunichar       wc)
+{
+  if (PANGO_IS_HB_FACE (face))
+    return pango_hb_face_has_char (PANGO_HB_FACE (face), wc);
+  else
+    return pango_user_face_has_char (PANGO_USER_FACE (face), wc);
+}
+
 static int
-sort_face_func (PangoHbFace *face1,
-                PangoHbFace *face2)
+sort_face_func (PangoFontFace *face1,
+                PangoFontFace *face2)
 {
+  CommonFace *cf1 = (CommonFace *)face1;
+  CommonFace *cf2 = (CommonFace *)face2;
   int a, b;
 
-  a = pango_font_description_get_style (face1->description);
-  b = pango_font_description_get_style (face2->description);
+  a = pango_font_description_get_style (cf1->description);
+  b = pango_font_description_get_style (cf2->description);
   if (a != b)
     return a - b;
 
-  a = pango_font_description_get_weight (face1->description);
-  b = pango_font_description_get_weight (face2->description);
+  a = pango_font_description_get_weight (cf1->description);
+  b = pango_font_description_get_weight (cf2->description);
   if (a != b)
     return a - b;
 
-  a = pango_font_description_get_stretch (face1->description);
-  b = pango_font_description_get_stretch (face2->description);
+  a = pango_font_description_get_stretch (cf1->description);
+  b = pango_font_description_get_stretch (cf2->description);
   if (a != b)
     return a - b;
 
-  return strcmp (face1->name, face2->name);
+  return strcmp (cf1->name, cf2->name);
 }
 
 /* return 2 if face is a named instance,
@@ -96,11 +128,11 @@ sort_face_func (PangoHbFace *face1,
  * 0 otherwise
  */
 static int
-hb_face_get_variableness (PangoHbFace *face)
+face_get_variableness (PangoFontFace *face)
 {
   if (pango_font_face_is_variable (PANGO_FONT_FACE (face)))
     {
-      if (face->instance_id != -1)
+      if (PANGO_HB_FACE (face)->instance_id != -1)
         return 2;
       else
         return 1;
@@ -236,7 +268,7 @@ pango_hb_family_new (const char *name)
  * @map: (nullable): a `PangoFontMap`
  *
  * Sets the map of @self.
- */
+ G*/
 void
 pango_hb_family_set_font_map (PangoHbFamily *self,
                               PangoFontMap  *map)
@@ -264,21 +296,24 @@ pango_hb_family_set_font_map (PangoHbFamily *self,
  */
 void
 pango_hb_family_add_face (PangoHbFamily *self,
-                          PangoHbFace   *face)
+                          PangoFontFace *face)
 {
   int position;
 
+  g_return_if_fail (PANGO_IS_HB_FACE (face) || PANGO_IS_USER_FACE (face));
+
   position = 0;
   while (position < self->faces->len)
     {
-      PangoHbFace *f = g_ptr_array_index (self->faces, position);
+      PangoFontFace *f = g_ptr_array_index (self->faces, position);
       if (sort_face_func (face, f) < 0)
         break;
       position++;
     }
 
   g_ptr_array_insert (self->faces, position, face);
-  pango_hb_face_set_family (face, PANGO_FONT_FAMILY (self));
+
+  face_set_family (face, PANGO_FONT_FAMILY (self));
 
   g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
 }
@@ -286,20 +321,23 @@ pango_hb_family_add_face (PangoHbFamily *self,
 /*< private >
  * pango_hb_family_remove_face:
  * @self: a `PangoHbFamily`
- * @face: a `PangoHbFace`
+ * @face: a `PangoFontFace`
  *
  * Remove a `PangoFontFace` from a `PangoHbFamily`.
  */
 void
 pango_hb_family_remove_face (PangoHbFamily *self,
-                             PangoHbFace   *face)
+                             PangoFontFace *face)
 {
   unsigned int position;
 
+  g_return_if_fail (PANGO_IS_HB_FACE (face) || PANGO_IS_USER_FACE (face));
+
   if (!g_ptr_array_find (self->faces, face, &position))
     return;
 
-  pango_hb_face_set_family (face, NULL);
+  face_set_family (face, NULL);
+
   g_ptr_array_remove_index (self->faces, position);
 
   g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0);
@@ -321,47 +359,49 @@ pango_hb_family_remove_face (PangoHbFamily *self,
  *
  * Returns: (transfer none) (nullable): the face
  */
-PangoHbFace *
+PangoFontFace *
 pango_hb_family_find_face (PangoHbFamily        *family,
                            PangoFontDescription *description,
                            PangoLanguage        *language,
                            gunichar              wc)
 {
-  PangoHbFace *face = NULL;
+  PangoFontFace *face = NULL;
   int best_distance = G_MAXINT;
   int best_variableness = 0;
 
   /* First look for an exact match if the description has a faceid */
   if (pango_font_description_get_set_fields (description) & PANGO_FONT_MASK_FACEID)
     {
+      const char *faceid = pango_font_description_get_faceid (description);
+
       for (int i = 0; i < family->faces->len; i++)
         {
-          PangoHbFace *face2 = g_ptr_array_index (family->faces, i);
+          PangoFontFace *face2 = g_ptr_array_index (family->faces, i);
+          const char *faceid2 = face_get_faceid (face2);
 
-          if (g_strcmp0 (pango_font_description_get_faceid (description),
-                         pango_hb_face_get_faceid (face2)) == 0)
+          if (g_strcmp0 (faceid, faceid2) == 0)
             return face2;
         }
     }
 
   for (int i = 0; i < family->faces->len; i++)
     {
-      PangoHbFace *face2 = g_ptr_array_index (family->faces, i);
+      PangoFontFace *face2 = g_ptr_array_index (family->faces, i);
       int distance;
       int variableness;
 
-      if (language && !pango_font_face_supports_language (PANGO_FONT_FACE (face2), language))
+      if (language && !pango_font_face_supports_language (face2, language))
         continue;
 
-      if (wc && !pango_hb_face_has_char (face2, wc))
+      if (wc && !face_has_char (face2, wc))
         continue;
 
-      if (!pango_font_description_is_similar (description, face2->description))
+      if (!pango_font_description_is_similar (description, ((CommonFace *)face2)->description))
         continue;
 
-      distance = pango_font_description_compute_distance (description, face2->description);
+      distance = pango_font_description_compute_distance (description, ((CommonFace *)face2)->description);
 
-      variableness = hb_face_get_variableness (face2);
+      variableness = face_get_variableness (PANGO_FONT_FACE (face2));
       if (distance < best_distance ||
           (distance == best_distance && variableness > best_variableness))
         {
diff --git a/pango/pango-hbfont-private.h b/pango/pango-hbfont-private.h
index eb945dc2b..dbaa55cb7 100644
--- a/pango/pango-hbfont-private.h
+++ b/pango/pango-hbfont-private.h
@@ -40,19 +40,36 @@ struct _HexBoxInfo
   double box_height;
 };
 
+typedef struct _CommonFont CommonFont;
+struct _CommonFont
+{
+  PangoFont parent_instance;
+
+  int size;
+  float dpi;
+  PangoGravity gravity;
+  PangoMatrix matrix;
+};
+
 struct _PangoHbFont
 {
   PangoFont parent_instance;
 
-  PangoHbFace *face;
   int size; /* point size, scaled by PANGO_SCALE */
   float dpi;
+  PangoGravity gravity;
+  PangoMatrix matrix;
+
+  /* up to here shared with PangoUserFont */
+
+  PangoHbFace *face;
   hb_feature_t *features;
   unsigned int n_features;
   hb_variation_t *variations;
   unsigned int n_variations;
-  PangoGravity gravity;
-  PangoMatrix matrix;
 
   HexBoxInfo *hex_box_info;
+  PangoLanguage *approximate_char_lang;
+  int approximate_char_width;
+  int approximate_digit_width;
 };
diff --git a/pango/pango-hbfont.c b/pango/pango-hbfont.c
index 76423697e..1a957318d 100644
--- a/pango/pango-hbfont.c
+++ b/pango/pango-hbfont.c
@@ -48,7 +48,7 @@
  * matrix.
  */
 
- /* {{{ Utilities */
+/* {{{ Utilities */
 
 static int
 get_average_char_width (PangoFont  *font,
@@ -764,6 +764,7 @@ static PangoFontMetrics *
 pango_hb_font_get_metrics (PangoFont     *font,
                            PangoLanguage *language)
 {
+  PangoHbFont *self = PANGO_HB_FONT (font);
   hb_font_t *hb_font = pango_font_get_hb_font (font);
   PangoFontMetrics *metrics;
   hb_font_extents_t extents;
@@ -797,8 +798,17 @@ pango_hb_font_get_metrics (PangoFont     *font,
   else
     metrics->strikethrough_position = metrics->ascent / 2;
 
-  metrics->approximate_char_width = get_average_char_width (font, pango_language_get_sample_string 
(language));
-  get_max_char_size (font, "0123456789", &metrics->approximate_digit_width, NULL);
+  if (self->approximate_char_width == 0 || self->approximate_char_lang != language)
+    {
+      self->approximate_char_width = get_average_char_width (font, pango_language_get_sample_string 
(language));
+      self->approximate_char_lang = language;
+    }
+
+  if (self->approximate_digit_width == 0)
+    get_max_char_size (font, "0123456789", &self->approximate_digit_width, NULL);
+
+  metrics->approximate_char_width = self->approximate_char_width;
+  metrics->approximate_digit_width = self->approximate_digit_width;
 
   return metrics;
 }
@@ -807,7 +817,6 @@ static hb_font_t *
 pango_hb_font_create_hb_font (PangoFont *font)
 {
   PangoHbFont *self = PANGO_HB_FONT (font);
-  hb_face_t *hb_face;
   hb_font_t *hb_font;
   double x_scale, y_scale;
   unsigned int n_axes;
@@ -815,8 +824,7 @@ pango_hb_font_create_hb_font (PangoFont *font)
   float *coords;
   int size;
 
-  hb_face = pango_hb_face_get_hb_face (self->face);
-  hb_font = hb_font_create (hb_face);
+  hb_font = hb_font_create (pango_hb_face_get_hb_face (self->face));
 
   size = self->size * self->dpi / 72.f;
   x_scale = self->face->x_scale;
@@ -844,7 +852,7 @@ pango_hb_font_create_hb_font (PangoFont *font)
       axes = g_alloca (sizeof (hb_ot_var_axis_info_t) * n_axes);
       coords = g_alloca (sizeof (float) * n_axes);
 
-      hb_ot_var_get_axis_infos (hb_face, 0, &n_axes, axes);
+      hb_ot_var_get_axis_infos (self->face->face, 0, &n_axes, axes);
 
       if (self->face->instance_id >= 0)
         hb_ot_var_named_instance_get_design_coords (self->face->face, self->face->instance_id, &n_axes, 
coords);
diff --git a/pango/pango-hbfont.h b/pango/pango-hbfont.h
index 8ff0a6500..017a77acc 100644
--- a/pango/pango-hbfont.h
+++ b/pango/pango-hbfont.h
@@ -47,4 +47,5 @@ PangoHbFont *           pango_hb_font_new_for_description       (PangoHbFace
                                                                  const PangoFontDescription     *description,
                                                                  float                           dpi,
                                                                  const PangoMatrix              *matrix);
+
 G_END_DECLS
diff --git a/pango/pango-hbfontmap.c b/pango/pango-hbfontmap.c
index b2c086105..d5b83c82b 100644
--- a/pango/pango-hbfontmap.c
+++ b/pango/pango-hbfontmap.c
@@ -29,6 +29,8 @@
 #include "pango-hbface-private.h"
 #include "pango-hbfont-private.h"
 #include "pango-fontset-private.h"
+#include "pango-userface-private.h"
+#include "pango-userfont-private.h"
 #include "pango-fontset.h"
 #include "pango-trace-private.h"
 #include "pango-context.h"
@@ -153,6 +155,18 @@ pango_fontset_cached_finalize (GObject *object)
   G_OBJECT_CLASS (pango_fontset_cached_parent_class)->finalize (object);
 }
 
+static PangoFont *
+font_new_for_description (PangoFontFace              *face,
+                          const PangoFontDescription *description,
+                          float                       dpi,
+                          const PangoMatrix          *matrix)
+{
+  if (PANGO_IS_HB_FACE (face))
+    return PANGO_FONT (pango_hb_font_new_for_description (PANGO_HB_FACE (face), description, dpi, matrix));
+  else
+    return PANGO_FONT (pango_user_font_new_for_description (PANGO_USER_FACE (face), description, dpi, 
matrix));
+}
+
 static PangoFont *
 pango_fontset_cached_get_font (PangoFontset *fontset,
                                guint         wc)
@@ -169,7 +183,7 @@ pango_fontset_cached_get_font (PangoFontset *fontset,
     {
       gpointer item = g_ptr_array_index (self->items, i);
 
-      if (PANGO_IS_HB_FONT (item))
+      if (PANGO_IS_FONT (item))
         {
           PangoFont *font = PANGO_FONT (item);
           if (pango_font_has_char (font, wc))
@@ -181,7 +195,7 @@ pango_fontset_cached_get_font (PangoFontset *fontset,
       else if (PANGO_IS_GENERIC_FAMILY (item))
         {
           PangoGenericFamily *family = PANGO_GENERIC_FAMILY (item);
-          PangoHbFace *face;
+          PangoFontFace *face;
 
           /* Here is where we implement delayed picking for generic families.
            * If a face does not cover the character and its family is generic,
@@ -193,10 +207,10 @@ pango_fontset_cached_get_font (PangoFontset *fontset,
                                                  wc);
           if (face)
             {
-              retval = PANGO_FONT (pango_hb_font_new_for_description (face,
-                                                                      self->description,
-                                                                      self->dpi,
-                                                                      self->matrix));
+              retval = font_new_for_description (face,
+                                                 self->description,
+                                                 self->dpi,
+                                                 self->matrix);
               break;
             }
         }
@@ -215,12 +229,12 @@ pango_fontset_cached_get_first_font (PangoFontsetCached *self)
 
   item = g_ptr_array_index (self->items, 0);
 
-  if (PANGO_IS_HB_FONT (item))
+  if (PANGO_IS_FONT (item))
     return g_object_ref (PANGO_FONT (item));
   else if (PANGO_IS_GENERIC_FAMILY (item))
     {
-      PangoHbFace *face = pango_generic_family_find_face (PANGO_GENERIC_FAMILY (item), self->description, 
self->language, 0);
-      return PANGO_FONT (pango_hb_font_new_for_description (face, self->description, self->dpi, 
self->matrix));
+      PangoFontFace *face = pango_generic_family_find_face (PANGO_GENERIC_FAMILY (item), self->description, 
self->language, 0);
+      return font_new_for_description (face, self->description, self->dpi, self->matrix);
     }
 
   return NULL;
@@ -266,12 +280,14 @@ pango_fontset_cached_foreach (PangoFontset            *fontset,
       gpointer item = g_ptr_array_index (self->items, i);
       PangoFont *font = NULL;
 
-      if (PANGO_IS_HB_FONT (item))
-        font = g_object_ref (PANGO_FONT (item));
+      if (PANGO_IS_FONT (item))
+        {
+          font = g_object_ref (PANGO_FONT (item));
+        }
       else if (PANGO_IS_GENERIC_FAMILY (item))
         {
-          PangoHbFace *face = pango_generic_family_find_face (PANGO_GENERIC_FAMILY (item), 
self->description, self->language, 0);
-          font = PANGO_FONT (pango_hb_font_new_for_description (face, self->description, self->dpi, 
self->matrix));
+          PangoFontFace *face = pango_generic_family_find_face (PANGO_GENERIC_FAMILY (item), 
self->description, self->language, 0);
+          font = font_new_for_description (face, self->description, self->dpi, self->matrix);
         }
 
       if ((*func) (fontset, font, data))
@@ -316,13 +332,20 @@ pango_fontset_cached_new (const PangoFontDescription *description,
 
 static void
 pango_fontset_cached_add_face (PangoFontsetCached *self,
-                               PangoHbFace        *face)
+                               PangoFontFace      *face)
 {
-  g_ptr_array_add (self->items,
-                   pango_hb_font_new_for_description (face,
-                                                      self->description,
-                                                      self->dpi,
-                                                      self->matrix));
+  if (PANGO_IS_HB_FACE (face))
+    g_ptr_array_add (self->items,
+                     pango_hb_font_new_for_description (PANGO_HB_FACE (face),
+                                                        self->description,
+                                                        self->dpi,
+                                                        self->matrix));
+  else
+    g_ptr_array_add (self->items,
+                     pango_user_font_new_for_description (PANGO_USER_FACE (face),
+                                                          self->description,
+                                                          self->dpi,
+                                                          self->matrix));
 }
 
 static void
@@ -447,18 +470,19 @@ add_style_variation (PangoHbFamily *family,
 {
   PangoMatrix italic_matrix = { 1, 0.2, 0, 1, 0, 0 };
   PangoFontDescription *desc;
+  PangoHbFace *variation;
 
   desc = pango_font_description_new ();
   pango_font_description_set_family (desc, pango_font_family_get_name (PANGO_FONT_FAMILY (family)));
   pango_font_description_set_style (desc, style);
   pango_font_description_set_weight (desc, weight);
 
-  pango_hb_family_add_face (family,
-      pango_hb_face_new_synthetic (face,
-                                   style == PANGO_STYLE_ITALIC ? &italic_matrix : NULL,
-                                    weight == PANGO_WEIGHT_BOLD,
-                                    NULL,
-                                    desc));
+  variation = pango_hb_face_new_synthetic (face,
+                                           style == PANGO_STYLE_ITALIC ? &italic_matrix : NULL,
+                                           weight == PANGO_WEIGHT_BOLD,
+                                           NULL,
+                                           desc);
+  pango_hb_family_add_face (family, PANGO_FONT_FACE (variation));
 
   pango_font_description_free (desc);
 }
@@ -486,6 +510,9 @@ synthesize_bold_and_italic_faces (PangoHbFontMap *map)
           PangoStyle style;
           int dist;
 
+          if (!PANGO_IS_HB_FACE (face))
+            continue;
+
           weight = pango_font_description_get_weight (face->description);
           style = pango_font_description_get_style (face->description);
 
@@ -533,7 +560,7 @@ synthesize_bold_and_italic_faces (PangoHbFontMap *map)
     }
 }
 
- /* }}} */
+/* }}} */
 /* {{{ PangoFontMap implementation */
 
 G_DEFINE_TYPE_WITH_CODE (PangoHbFontMap, pango_hb_font_map, PANGO_TYPE_FONT_MAP,
@@ -614,7 +641,7 @@ pango_hb_font_map_load_fontset (PangoFontMap               *map,
   char **families;
   PangoFontDescription *copy;
   PangoFontFamily *family;
-  PangoHbFace *face;
+  PangoFontFace *face;
   gboolean has_generic = FALSE;
   gint64 before G_GNUC_UNUSED;
 
@@ -788,7 +815,7 @@ pango_hb_font_map_repopulate (PangoHbFontMap *self,
 
   for (int i = 0; i < self->added_faces->len; i++)
     {
-      PangoHbFace *face = PANGO_HB_FACE (g_ptr_array_index (self->added_faces, i));
+      PangoFontFace *face = g_ptr_array_index (self->added_faces, i);
       pango_hb_font_map_add_face (self, face);
     }
 
@@ -825,7 +852,7 @@ pango_hb_font_map_new (void)
 /**
  * pango_hb_font_map_add_face:
  * @self: a `PangoHbFontMap`
- * @face: (transfer full): a `PangoHbFace`
+ * @face: (transfer full): a `PangoFontFace`
  *
  * Adds @face to the `PangoHbFontMap`.
  *
@@ -834,20 +861,28 @@ pango_hb_font_map_new (void)
  */
 void
 pango_hb_font_map_add_face (PangoHbFontMap *self,
-                            PangoHbFace    *face)
+                            PangoFontFace  *face)
 {
   PangoFontMap *map = PANGO_FONT_MAP (self);
   const char *family_name;
   PangoHbFamily *family;
+  const PangoFontDescription *description;
+
+  g_return_if_fail (PANGO_IS_HB_FACE (face) || PANGO_IS_USER_FACE (face));
 
-  if (pango_font_description_get_set_fields (face->description) &
+  if (PANGO_IS_HB_FACE (face))
+    description = PANGO_HB_FACE (face)->description;
+  else
+    description = PANGO_USER_FACE (face)->description;
+
+  if (pango_font_description_get_set_fields (description) &
       (PANGO_FONT_MASK_VARIANT | PANGO_FONT_MASK_GRAVITY))
-    g_warning ("Font description for PangoHbFace includes things that it shouldn't");
+    g_warning ("Font description for PangoFontFace includes things that it shouldn't");
 
   if (!self->in_populate)
     g_ptr_array_add (self->added_faces, g_object_ref (face));
 
-  family_name = pango_font_description_get_family (face->description);
+  family_name = pango_font_description_get_family (description);
   family = PANGO_HB_FAMILY (pango_font_map_get_family (map, family_name));
   if (!family)
     {
@@ -865,7 +900,7 @@ pango_hb_font_map_add_face (PangoHbFontMap *self,
 /**
  * pango_hb_font_map_remove_face:
  * @self: a `PangoHbFontMap`
- * @face: a `PangoHbFace` that belongs to @map
+ * @face: a `PangoFontFace` that belongs to @map
  *
  * Removes @face from the `PangoHbFontMap`.
  *
@@ -873,15 +908,17 @@ pango_hb_font_map_add_face (PangoHbFontMap *self,
  */
 void
 pango_hb_font_map_remove_face (PangoHbFontMap *self,
-                               PangoHbFace    *face)
+                               PangoFontFace  *face)
 {
   PangoHbFamily *family;
   unsigned int position;
 
+  g_return_if_fail (PANGO_IS_HB_FACE (face) || PANGO_IS_USER_FACE (face));
+
   if (!g_ptr_array_find (self->added_faces, face, &position))
     return;
 
-  family = PANGO_HB_FAMILY (pango_font_face_get_family (PANGO_FONT_FACE (face)));
+  family = PANGO_HB_FAMILY (pango_font_face_get_family (face));
 
   pango_hb_family_remove_face (family, face);
 
@@ -912,7 +949,7 @@ pango_hb_font_map_add_file (PangoHbFontMap *self,
   PangoHbFace *face;
 
   face = pango_hb_face_new_from_file (file, 0, -1, NULL, NULL);
-  pango_hb_font_map_add_face (self, face);
+  pango_hb_font_map_add_face (self, PANGO_FONT_FACE (face));
 }
 
 /**
diff --git a/pango/pango-hbfontmap.h b/pango/pango-hbfontmap.h
index b7a933d5f..ae456ad98 100644
--- a/pango/pango-hbfontmap.h
+++ b/pango/pango-hbfontmap.h
@@ -39,11 +39,11 @@ void                    pango_hb_font_map_add_file              (PangoHbFontMap
 
 PANGO_AVAILABLE_IN_ALL
 void                    pango_hb_font_map_add_face              (PangoHbFontMap *self,
-                                                                 PangoHbFace    *face);
+                                                                 PangoFontFace  *face);
 
 PANGO_AVAILABLE_IN_ALL
 void                    pango_hb_font_map_remove_face           (PangoHbFontMap *self,
-                                                                 PangoHbFace    *face);
+                                                                 PangoFontFace  *face);
 
 PANGO_AVAILABLE_IN_ALL
 void                    pango_hb_font_map_add_family            (PangoHbFontMap  *self,
diff --git a/pango/pango-userface-private.h b/pango/pango-userface-private.h
new file mode 100644
index 000000000..087874956
--- /dev/null
+++ b/pango/pango-userface-private.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-userface.h"
+#include "pango-fontmap.h"
+#include "pango-font-face-private.h"
+
+
+struct _PangoUserFace
+{
+  PangoFontFace parent_instance;
+
+  PangoFontDescription *description;
+  char *name;
+  PangoFontFamily *family;
+  char *psname;
+  char *faceid;
+
+  /* up to here shared with PangoHbFace */
+
+  PangoUserFaceGetFontInfoFunc font_info_func;
+  PangoUserFaceUnicodeToGlyphFunc glyph_func;
+  PangoUserFaceGetGlyphInfoFunc glyph_info_func;
+  PangoUserFaceTextToGlyphFunc shape_func;
+  PangoUserFaceRenderGlyphFunc render_func;
+  gpointer user_data;
+  GDestroyNotify destroy;
+};
+
+void                    pango_user_face_set_family        (PangoUserFace        *self,
+                                                           PangoFontFamily      *family);
+
+gboolean                pango_user_face_has_char          (PangoUserFace        *self,
+                                                           gunichar              wc);
+
+const char *            pango_user_face_get_faceid        (PangoUserFace        *self);
diff --git a/pango/pango-userface.c b/pango/pango-userface.c
new file mode 100644
index 000000000..3858e2d5d
--- /dev/null
+++ b/pango/pango-userface.c
@@ -0,0 +1,483 @@
+/* Pango
+ *
+ * Copyright (C) 2022 Matthias Clasen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "pango-font-face-private.h"
+#include "pango-userface-private.h"
+#include "pango-userfont-private.h"
+#include "pango-utils.h"
+#include "pango-impl-utils.h"
+
+#include <string.h>
+#include <hb-ot.h>
+
+/**
+ * PangoUserFace:
+ *
+ * `PangoUserFace` is a `PangoFontFace` implementation that uses callbacks.
+ */
+
+ /* {{{ Utilities */
+
+static void
+ensure_psname (PangoUserFace *self)
+{
+  char *p;
+
+  if (self->psname)
+    return;
+
+  self->psname = g_strconcat (pango_font_description_get_family (self->description), "_", self->name, NULL);
+
+  /* PostScript name should not contain problematic chars, but just in case,
+   * make sure we don't have any ' ', '=' or ',' that would give us parsing
+   * problems.
+   */
+  p = self->psname;
+  while ((p = strpbrk (p, " =,")) != NULL)
+    *p = '?';
+}
+
+static const char *
+style_from_font_description (const PangoFontDescription *desc)
+{
+  PangoStyle style = pango_font_description_get_style (desc);
+  PangoWeight weight = pango_font_description_get_weight (desc);
+
+  switch (style)
+    {
+    case PANGO_STYLE_ITALIC:
+      if (weight == PANGO_WEIGHT_BOLD)
+        return "Bold Italic";
+      else
+        return "Italic";
+      break;
+    case PANGO_STYLE_OBLIQUE:
+      if (weight == PANGO_WEIGHT_BOLD)
+        return "Bold Oblique";
+      else
+        return "Oblique";
+      break;
+    case PANGO_STYLE_NORMAL:
+      if (weight == PANGO_WEIGHT_BOLD)
+        return "Bold";
+      else
+        return "Regular";
+      break;
+    default: ;
+    }
+
+  return NULL;
+}
+
+static gboolean
+default_shape_func (PangoUserFace       *face,
+                    int                  size,
+                    const char          *text,
+                    int                  length,
+                    const PangoAnalysis *analysis,
+                    PangoGlyphString    *glyphs,
+                    PangoShapeFlags      flags,
+                    gpointer             user_data)
+{
+  int n_chars;
+  const char *p;
+  int cluster = 0;
+  int i;
+  int last_cluster;
+  gboolean is_color;
+  hb_glyph_extents_t ext;
+  hb_position_t dummy;
+
+  n_chars = g_utf8_strlen (text, length);
+
+  pango_glyph_string_set_size (glyphs, n_chars);
+
+  last_cluster = -1;
+
+  p = text;
+  for (i = 0; i < n_chars; i++)
+    {
+      gunichar wc;
+      PangoGlyph glyph;
+      PangoRectangle logical_rect;
+
+      wc = g_utf8_get_char (p);
+
+      if (g_unichar_type (wc) != G_UNICODE_NON_SPACING_MARK)
+        cluster = p - text;
+
+      if (pango_is_zero_width (wc))
+        glyph = PANGO_GLYPH_EMPTY;
+      else if (!face->glyph_func (face, wc, &glyph, face->user_data))
+        glyph = PANGO_GET_UNKNOWN_GLYPH (wc);
+
+      face->glyph_info_func (face, size, glyph, &ext, &dummy, &dummy, &is_color, face->user_data);
+      pango_font_get_glyph_extents (analysis->font, glyph, NULL, &logical_rect);
+
+      glyphs->glyphs[i].glyph = glyph;
+
+      glyphs->glyphs[i].attr.is_cluster_start = cluster != last_cluster;
+      glyphs->glyphs[i].attr.is_color = is_color;
+
+      glyphs->glyphs[i].geometry.x_offset = 0;
+      glyphs->glyphs[i].geometry.y_offset = 0;
+      glyphs->glyphs[i].geometry.width = logical_rect.width;
+
+      glyphs->log_clusters[i] = cluster;
+      last_cluster = cluster;
+
+      p = g_utf8_next_char (p);
+    }
+
+  if (analysis->level & 1)
+    pango_glyph_string_reverse_range (glyphs, 0, glyphs->num_glyphs);
+
+  return TRUE;
+}
+
+static gboolean
+default_render_func (PangoUserFace *face,
+                     int            size,
+                     hb_codepoint_t  glyph,
+                     gpointer        user_data,
+                     const char     *backend_id,
+                     gpointer        backend_data)
+{
+  /* Draw nothing... not very exciting */
+  return TRUE;
+}
+
+/* }}} */
+/* {{{ PangoFontFace implementation */
+
+struct _PangoUserFaceClass
+{
+  PangoFontFaceClass parent_class;
+};
+
+G_DEFINE_TYPE (PangoUserFace, pango_user_face, PANGO_TYPE_FONT_FACE)
+
+static void
+pango_user_face_init (PangoUserFace *self)
+{
+}
+
+static void
+pango_user_face_finalize (GObject *object)
+{
+  PangoUserFace *self = PANGO_USER_FACE (object);
+
+  pango_font_description_free (self->description);
+  g_free (self->name);
+  g_free (self->faceid);
+  if (self->destroy)
+    self->destroy (self->user_data);
+
+  G_OBJECT_CLASS (pango_user_face_parent_class)->finalize (object);
+}
+
+static const char *
+pango_user_face_get_face_name (PangoFontFace *face)
+{
+  PangoUserFace *self = PANGO_USER_FACE (face);
+
+  return self->name;
+}
+
+static PangoFontDescription *
+pango_user_face_describe (PangoFontFace *face)
+{
+  PangoUserFace *self = PANGO_USER_FACE (face);
+
+  if ((pango_font_description_get_set_fields (self->description) & PANGO_FONT_MASK_FACEID) == 0)
+    pango_font_description_set_faceid (self->description, pango_user_face_get_faceid (self));
+
+  return pango_font_description_copy (self->description);
+}
+
+static PangoFontFamily *
+pango_user_face_get_family (PangoFontFace *face)
+{
+  PangoUserFace *self = PANGO_USER_FACE (face);
+
+  return self->family;
+}
+
+static gboolean
+pango_user_face_is_synthesized (PangoFontFace *face)
+{
+  return TRUE;
+}
+
+static gboolean
+pango_user_face_is_monospace (PangoFontFace *face)
+{
+  return FALSE;
+}
+
+static gboolean
+pango_user_face_is_variable (PangoFontFace *face)
+{
+  return FALSE;
+}
+
+static void
+pango_user_face_class_init (PangoUserFaceClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+  PangoFontFaceClass *face_class = PANGO_FONT_FACE_CLASS (class);
+
+  object_class->finalize = pango_user_face_finalize;
+
+  face_class->get_face_name = pango_user_face_get_face_name;
+  face_class->describe = pango_user_face_describe;
+  face_class->is_synthesized = pango_user_face_is_synthesized;
+  face_class->get_family = pango_user_face_get_family;
+  face_class->is_monospace = pango_user_face_is_monospace;
+  face_class->is_variable = pango_user_face_is_variable;
+}
+
+/* }}} */
+/* {{{ Private API */
+
+/*< private >
+ * pango_user_face_set_family:
+ * @self: a `PangoUserFace`
+ * @family: a `PangoFontFamily`
+ *
+ * Sets the font family of a `PangoUserFace`.
+ *
+ * This should only be called by fontmap implementations.
+ */
+void
+pango_user_face_set_family (PangoUserFace   *self,
+                            PangoFontFamily *family)
+{
+  self->family = family;
+}
+
+/*< private >
+ * pango_user_face_has_char:
+ * @self: a `PangoUserFace`
+ * @wc: a Unicode character
+ *
+ * Returns whether the face provides a glyph for this character.
+ *
+ * Returns: `TRUE` if @font can render @wc
+ */
+gboolean
+pango_user_face_has_char (PangoUserFace *self,
+                          gunichar     wc)
+{
+  hb_codepoint_t glyph;
+
+  return self->glyph_func (self, wc, &glyph, self->user_data);
+}
+
+/*< private >
+ * pango_user_face_get_faceid:
+ * @self: a `PangoUserFace`
+ *
+ * Returns the faceid of the face.
+ *
+ * Returns: (transfer none): the faceid
+ */
+const char *
+pango_user_face_get_faceid (PangoUserFace *self)
+{
+  if (!self->faceid)
+    {
+      ensure_psname (self);
+      self->faceid = g_strconcat ("user:", self->psname, NULL);
+    }
+
+  return self->faceid;
+}
+
+/* }}} */
+ /* {{{ Public API */
+
+/**
+ * PangoUserFaceGetFontInfoFunc:
+ * @face: the `PangoUserFace`
+ * @size: the size of the font that is being created
+ * @extents: (out caller-allocates): return location for font extents
+ * user_data: user data that was pased to [ctor Pango UserFace new]
+ *
+ * The type of the function that is called to obtain font extents for user fonts.
+ *
+ *  Returns: `TRUE` on success
+ */
+
+/**
+ * PangoUserFaceUnicodeToGlyphFunc:
+ * @face: the `PangoUserFace`
+ * @unicode: the Unicode character
+ * @glyph: (out caller-allocates): return location for the glyph that
+ * user_data: user data that was pased to [ctor Pango UserFace new]
+ *
+ * The type of the function that is called to determine if a user
+ * font can render a character, and what glyph it will use.
+ *
+ *  Returns: `TRUE` on success
+ */
+
+/**
+ * PangoUserFaceGetGlyphInfoFunc:
+ * @face: the `PangoUserFace`
+ * @size: the size of the font that is queried
+ * @glyph: the glyph that is being queried
+ * @extents: (out caller-allocates): return location for the glyphs ink rectangle
+ * @h_advance: (out caller-allocates): return location for the h advance
+ * @v_advance: (out caller-allocates): return location for the v advance
+ * @is_color_glyph: (out caller-allocates): return location for information about
+ *   whether @glyph has color
+ * user_data: user data that was pased to [ctor Pango UserFace new]
+ *
+ * The type of the function that is called to query information about
+ * a glyph in a user font.
+ *
+ * Returns: `TRUE` on success
+ */
+
+/**
+ * PangoUserFaceTextToGlyphFunc:
+ * @face: the `PangoUserFace`
+ * @size: the size of the font that is used
+ * @text: the text to shape
+ * @length: the length of @text
+ * @analysis: `PangoAnalysis` for @text
+ * @glyphs: (out caller-allocates): the `PangoGlyphString` to populate
+ * @flags: `PangoShapeFlags` to use
+ * user_data: user data that was pased to [ctor Pango UserFace new]
+ *
+ * The type of the function that is used to shape a segment of text
+ * with a user font.
+ *
+ * This callback is optional when creating a user font. If it isn't
+ * provided, Pango will rely on the `PangoUserFaceUnicodeToGlyphFunc`
+ * and the `PangoUserFaceGetGlyphInfo` callback to translate Unicode
+ * characters to glyphs 1-1, and position the glyphs according to their
+ * advance widths.
+ *
+ * If this callback is provided, it replaces all of Pango's own shaping.
+ * The function can implement ligatures, reordering, and other features
+ * that turn the text-to-glyph mapping into an m-n relationship. The
+ * function is responsible for filling not just the glyphs and their
+ * positions, but also cluster information and glyph attributes in
+ * [struct@Pango.VisAttr].
+ *
+ * Returns: `TRUE` on success
+ */
+
+/**
+ * PangoUserFaceRenderGlyphFunc:
+ * @face: the `PangoUserFace`
+ * @size: the size of the font that is used
+ * @glyph: the glyph that is being queried
+ * @user_data: user data that was pased to [ctor Pango UserFace new]
+ * @backend_id: a string identifying the [class@Pango.Renderer] in use
+ * @backend_data: backend-specific data
+ *
+ * The type of the function that is called to render a glyph with a
+ * user font.
+ *
+ * This callback is optional when creating a user font. IF it isn't
+ * provided, the font will not produce any visible output.
+ *
+ * The @backend_id identifies the [class@Pango.Renderer] in use.
+ * Implementations should return `FALSE` for unsupported backends.
+ *
+ * The cairo backend uses the string "cairo" as @backend_id, and
+ * provides a `cairo_t` as @backend_data. The context is set up
+ * to render in `font space`, i.e. The transformation is set up
+ * to map the unit square to @size x @size. If supported, Pango
+ * uses `cairo_user_font_face_set_render_color_glyph_func` to
+ * allow glyphs to be rendered with colors. For more information,
+ * see the cairo documentation about user fonts.
+ *
+ * Returns: `TRUE` on success
+ */
+
+/**
+ * pango_user_face_new:
+ * @font_info_func: (scope notified): a `PangoUserFaceGetFontInfoFunc`
+ * @glyph_func: (scope notified): a `PangoUserFaceUnicodeToGlyphFunc`
+ * @glyph_info_func: (scope notified): a `PangoUserFaceGetGlyphInfoFunc`
+ * @shape_func: (scope notified) (nullable): a `PangoUserFaceTextToGlyphFunc`
+ * @render_func: (scope notified) (nullable): a `PangoUserFaceRenderGlyphFunc`
+ * @user_data: user data that will be assed to the callbacks
+ * @destroy: destroy notify for @user_data
+ * @name: name for the face
+ * @description: (nullable): `PangoFontDescription` for the font
+ *
+ * Creates a new user font face.
+ *
+ * A user font face does not rely on font data from a font file,
+ * but instead uses callbacks to determine glyph extents, positions
+ * and rendering.
+ *
+ * If @shape_func is `NULL`, Pango will rely on @glyph_func and
+ * @glyph_info_func to find and position a glyph for each character.
+ *
+ * If @render_func is `NULL`, the font will not produce any visible
+ * glyphs.
+ *
+ * Returns: (transfer full): a newly created `PangoUserFace`
+ *
+ * Since: 1.52
+ */
+PangoUserFace *
+pango_user_face_new (PangoUserFaceGetFontInfoFunc     font_info_func,
+                     PangoUserFaceUnicodeToGlyphFunc  glyph_func,
+                     PangoUserFaceGetGlyphInfoFunc    glyph_info_func,
+                     PangoUserFaceTextToGlyphFunc     shape_func,
+                     PangoUserFaceRenderGlyphFunc     render_func,
+                     gpointer                         user_data,
+                     GDestroyNotify                   destroy,
+                     const char                      *name,
+                     const PangoFontDescription      *description)
+{
+  PangoUserFace *self;
+
+  self = g_object_new (PANGO_TYPE_USER_FACE, NULL);
+
+  self->font_info_func = font_info_func;
+  self->glyph_func = glyph_func;
+  self->glyph_info_func = glyph_info_func;
+  self->shape_func = shape_func ? shape_func : default_shape_func;
+  self->render_func = render_func ? render_func : default_render_func;
+  self->user_data = user_data;
+  self->destroy = destroy;
+
+  if (!name)
+    name = style_from_font_description (description);
+
+  self->name = g_strdup (name);
+  self->description = pango_font_description_copy (description);
+
+  return self;
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango/pango-userface.h b/pango/pango-userface.h
new file mode 100644
index 000000000..6d6def4b7
--- /dev/null
+++ b/pango/pango-userface.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango/pango-types.h>
+#include <pango/pango-glyph.h>
+#include <pango/pango-font-face.h>
+
+G_BEGIN_DECLS
+
+typedef struct _PangoUserFont PangoUserFont;
+
+#define PANGO_TYPE_USER_FACE      (pango_user_face_get_type ())
+
+PANGO_AVAILABLE_IN_ALL
+PANGO_DECLARE_INTERNAL_TYPE (PangoUserFace, pango_user_face, PANGO, USER_FACE, PangoFontFace)
+
+typedef gboolean      (* PangoUserFaceGetFontInfoFunc)     (PangoUserFace     *face,
+                                                            int                size,
+                                                            hb_font_extents_t *extents,
+                                                            gpointer           user_data);
+
+typedef gboolean      (* PangoUserFaceUnicodeToGlyphFunc)  (PangoUserFace  *face,
+                                                            hb_codepoint_t  unicode,
+                                                            hb_codepoint_t *glyph,
+                                                            gpointer        user_data);
+
+typedef gboolean      (* PangoUserFaceGetGlyphInfoFunc)    (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_glyph,
+                                                            gpointer            user_data);
+
+typedef gboolean      (* PangoUserFaceTextToGlyphFunc)     (PangoUserFace       *face,
+                                                            int                  size,
+                                                            const char          *text,
+                                                            int                  length,
+                                                            const PangoAnalysis *analysis,
+                                                            PangoGlyphString    *glyphs,
+                                                            PangoShapeFlags      flags,
+                                                            gpointer             user_data);
+
+typedef gboolean      (* PangoUserFaceRenderGlyphFunc)     (PangoUserFace  *face,
+                                                            int             size,
+                                                            hb_codepoint_t  glyph,
+                                                            gpointer        user_data,
+                                                            const char     *backend_id,
+                                                            gpointer        backend_data);
+
+PANGO_AVAILABLE_IN_ALL
+PangoUserFace *   pango_user_face_new          (PangoUserFaceGetFontInfoFunc    font_info_func,
+                                                PangoUserFaceUnicodeToGlyphFunc glyph_func,
+                                                PangoUserFaceGetGlyphInfoFunc   glyph_info_func,
+                                                PangoUserFaceTextToGlyphFunc    shape_func,
+                                                PangoUserFaceRenderGlyphFunc    render_func,
+                                                gpointer                        user_data,
+                                                GDestroyNotify                  destroy,
+                                                const char                     *name,
+                                                const PangoFontDescription     *description);
+
+G_END_DECLS
diff --git a/pango/pango-userfont-private.h b/pango/pango-userfont-private.h
new file mode 100644
index 000000000..a3669a09f
--- /dev/null
+++ b/pango/pango-userfont-private.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pango-userfont.h"
+#include "pango-userface.h"
+#include "pango-font-private.h"
+
+
+struct _PangoUserFont
+{
+  PangoFont parent_instance;
+
+  int size; /* point size, scaled by PANGO_SCALE */
+  float dpi;
+  PangoGravity gravity;
+  PangoMatrix matrix;
+
+  /* up to here shared with PangoHbFont */
+
+  PangoUserFace *face;
+};
diff --git a/pango/pango-userfont.c b/pango/pango-userfont.c
new file mode 100644
index 000000000..e3027f9a1
--- /dev/null
+++ b/pango/pango-userfont.c
@@ -0,0 +1,482 @@
+/* Pango
+ *
+ * Copyright (C) 2021 Matthias Clasen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "pango-userfont-private.h"
+
+#include "pango-font-private.h"
+#include "pango-font-metrics-private.h"
+#include "pango-coverage-private.h"
+#include "pango-userface-private.h"
+#include "pango-hbfamily-private.h"
+#include "pango-impl-utils.h"
+#include "pango-language-set-private.h"
+
+#include <hb-ot.h>
+
+/**
+ * PangoUserFont:
+ *
+ * `PangoUserFont` is a `PangoFont` implementation that uses callbacks.
+ */
+
+/* {{{ PangoFont implementation */
+
+struct _PangoUserFontClass
+{
+  PangoFontClass parent_class;
+};
+
+G_DEFINE_TYPE (PangoUserFont, pango_user_font, PANGO_TYPE_FONT)
+
+static void
+pango_user_font_init (PangoUserFont *self)
+{
+  self->gravity = PANGO_GRAVITY_AUTO;
+  self->matrix = (PangoMatrix) PANGO_MATRIX_INIT;
+}
+
+static void
+pango_user_font_finalize (GObject *object)
+{
+  PangoUserFont *self = PANGO_USER_FONT (object);
+
+  g_object_unref (self->face);
+
+  G_OBJECT_CLASS (pango_user_font_parent_class)->finalize (object);
+}
+
+static PangoFontDescription *
+pango_user_font_describe (PangoFont *font)
+{
+  PangoUserFont *self = PANGO_USER_FONT (font);
+  PangoFontDescription *desc;
+
+  desc = pango_font_face_describe (PANGO_FONT_FACE (self->face));
+  pango_font_description_set_gravity (desc, self->gravity);
+  pango_font_description_set_size (desc, self->size);
+
+  return desc;
+}
+
+static PangoFontDescription *
+pango_user_font_describe_absolute (PangoFont *font)
+{
+  PangoUserFont *self = PANGO_USER_FONT (font);
+  PangoFontDescription *desc;
+
+  desc = pango_user_font_describe (font);
+  pango_font_description_set_absolute_size (desc, self->size * self->dpi / 72.);
+
+  return desc;
+}
+
+static PangoCoverage *
+pango_user_font_get_coverage (PangoFont     *font,
+                              PangoLanguage *language G_GNUC_UNUSED)
+{
+  /* FIXME */
+  return pango_coverage_new ();
+}
+
+static void
+pango_user_font_get_glyph_extents (PangoFont      *font,
+                                   PangoGlyph      glyph,
+                                   PangoRectangle *ink_rect,
+                                   PangoRectangle *logical_rect)
+{
+  PangoUserFont *self = PANGO_USER_FONT (font);
+  hb_font_t *hb_font = pango_font_get_hb_font (font);
+  hb_glyph_extents_t extents;
+  hb_direction_t direction;
+  hb_font_extents_t font_extents;
+
+  direction = PANGO_GRAVITY_IS_VERTICAL (self->gravity)
+              ? HB_DIRECTION_TTB
+              : HB_DIRECTION_LTR;
+
+  hb_font_get_extents_for_direction (hb_font, direction, &font_extents);
+
+  if (glyph == PANGO_GLYPH_EMPTY || (glyph & PANGO_GLYPH_UNKNOWN_FLAG))
+    {
+      if (ink_rect)
+        ink_rect->x = ink_rect->y = ink_rect->width = ink_rect->height = 0;
+
+      if (logical_rect)
+        {
+          logical_rect->x = logical_rect->width = 0;
+          logical_rect->y = - font_extents.ascender;
+          logical_rect->height = font_extents.ascender - font_extents.descender;
+        }
+
+      return;
+    }
+
+  hb_font_get_glyph_extents (hb_font, glyph, &extents);
+
+  if (ink_rect)
+    {
+      PangoRectangle r;
+
+      r.x = extents.x_bearing;
+      r.y = - extents.y_bearing;
+      r.width = extents.width;
+      r.height = - extents.height;
+
+      switch (self->gravity)
+        {
+        case PANGO_GRAVITY_AUTO:
+        case PANGO_GRAVITY_SOUTH:
+          ink_rect->x = r.x;
+          ink_rect->y = r.y;
+          ink_rect->width = r.width;
+          ink_rect->height = r.height;
+          break;
+        case PANGO_GRAVITY_NORTH:
+          ink_rect->x = - r.x;
+          ink_rect->y = - r.y;
+          ink_rect->width = - r.width;
+          ink_rect->height = - r.height;
+          break;
+        case PANGO_GRAVITY_EAST:
+          ink_rect->x = r.y;
+          ink_rect->y = - r.x - r.width;
+          ink_rect->width = r.height;
+          ink_rect->height = r.width;
+          break;
+        case PANGO_GRAVITY_WEST:
+          ink_rect->x = - r.y - r.height;
+          ink_rect->y = r.x;
+          ink_rect->width = r.height;
+          ink_rect->height = r.width;
+          break;
+        default:
+          g_assert_not_reached ();
+        }
+
+      if (PANGO_GRAVITY_IS_IMPROPER (self->gravity))
+        {
+          PangoMatrix matrix = (PangoMatrix) PANGO_MATRIX_INIT;
+          pango_matrix_scale (&matrix, -1, -1);
+          pango_matrix_transform_rectangle (&matrix, ink_rect);
+        }
+    }
+
+  if (logical_rect)
+    {
+      hb_position_t h_advance;
+      hb_font_extents_t extents;
+
+      h_advance = hb_font_get_glyph_h_advance (hb_font, glyph);
+      hb_font_get_h_extents (hb_font, &extents);
+
+      logical_rect->x = 0;
+      logical_rect->y = - extents.ascender;
+      logical_rect->width = h_advance;
+      logical_rect->height = extents.ascender - extents.descender;
+
+      switch (self->gravity)
+        {
+        case PANGO_GRAVITY_AUTO:
+        case PANGO_GRAVITY_SOUTH:
+          logical_rect->y = - extents.ascender;
+          logical_rect->width = h_advance;
+          break;
+        case PANGO_GRAVITY_NORTH:
+          logical_rect->y = extents.descender;
+          logical_rect->width = h_advance;
+          break;
+        case PANGO_GRAVITY_EAST:
+          logical_rect->y = - logical_rect->height / 2;
+          logical_rect->width = logical_rect->height;
+          break;
+        case PANGO_GRAVITY_WEST:
+          logical_rect->y = - logical_rect->height / 2;
+          logical_rect->width = - logical_rect->height;
+          break;
+        default:
+          g_assert_not_reached ();
+        }
+
+      if (PANGO_GRAVITY_IS_IMPROPER (self->gravity))
+        {
+          logical_rect->height = - logical_rect->height;
+          logical_rect->y = - logical_rect->y;
+        }
+    }
+}
+
+static PangoFontMetrics *
+pango_user_font_get_metrics (PangoFont     *font,
+                             PangoLanguage *language)
+{
+  hb_font_t *hb_font = pango_font_get_hb_font (font);
+  PangoFontMetrics *metrics;
+  hb_font_extents_t extents;
+
+  metrics = pango_font_metrics_new ();
+
+  hb_font_get_extents_for_direction (hb_font, HB_DIRECTION_LTR, &extents);
+
+  metrics->descent = - extents.descender;
+  metrics->ascent = extents.ascender;
+  metrics->height = extents.ascender - extents.descender + extents.line_gap;
+
+  metrics->underline_thickness = PANGO_SCALE;
+  metrics->underline_position = - PANGO_SCALE;
+  metrics->strikethrough_thickness = PANGO_SCALE;
+  metrics->strikethrough_position = metrics->ascent / 2;
+  metrics->approximate_char_width = 0; /* FIXME */
+  metrics->approximate_digit_width = 0;
+
+  return metrics;
+}
+
+static PangoFontMap *
+pango_user_font_get_font_map (PangoFont *font)
+{
+  PangoUserFont *self = PANGO_USER_FONT (font);
+  PangoHbFamily *family = PANGO_HB_FAMILY (self->face->family);
+
+  return family->map;
+}
+
+static hb_bool_t
+nominal_glyph_func (hb_font_t *font, void *font_data,
+                    hb_codepoint_t unicode,
+                    hb_codepoint_t *glyph,
+                    void *user_data)
+{
+  PangoUserFont *self = font_data;
+
+  return self->face->glyph_func (self->face, unicode, glyph, self->face->user_data);
+}
+
+static hb_position_t
+glyph_h_advance_func (hb_font_t *font, void *font_data,
+                      hb_codepoint_t glyph,
+                      void *user_data)
+{
+  PangoUserFont *self = font_data;
+  int size = self->size * self->dpi / 72.;
+  hb_position_t h_advance, v_advance;
+  hb_glyph_extents_t glyph_extents;
+  gboolean is_color;
+
+  self->face->glyph_info_func (self->face, size, glyph,
+                               &glyph_extents,
+                               &h_advance, &v_advance,
+                               &is_color,
+                               self->face->user_data);
+
+  return h_advance;
+}
+
+static hb_bool_t
+glyph_extents_func (hb_font_t *font, void *font_data,
+                    hb_codepoint_t glyph,
+                    hb_glyph_extents_t *extents,
+                    void *user_data)
+{
+  PangoUserFont *self = font_data;
+  int size = self->size * self->dpi / 72.;
+  hb_position_t h_advance, v_advance;
+  gboolean is_color;
+
+  return self->face->glyph_info_func (self->face, size, glyph,
+                                      extents,
+                                      &h_advance, &v_advance,
+                                      &is_color,
+                                      self->face->user_data);
+}
+
+static hb_bool_t
+font_extents_func (hb_font_t *font, void *font_data,
+                   hb_font_extents_t *extents,
+                   void *user_data)
+{
+  PangoUserFont *self = font_data;
+  int size = self->size * self->dpi / 72.;
+
+  return self->face->font_info_func (self->face, size, extents, self->face->user_data);
+}
+
+static hb_font_t *
+pango_user_font_create_hb_font (PangoFont *font)
+{
+  PangoUserFont *self = PANGO_USER_FONT (font);
+  hb_font_t *hb_font;
+  double x_scale, y_scale;
+  int size;
+  hb_blob_t *blob;
+  hb_face_t *face;
+  hb_font_funcs_t *funcs;
+
+  blob = hb_blob_create ("", 0, HB_MEMORY_MODE_READONLY, NULL, NULL);
+  face = hb_face_create (blob, 0);
+  hb_font = hb_font_create (face);
+
+  funcs = hb_font_funcs_create ();
+
+  hb_font_funcs_set_nominal_glyph_func (funcs, nominal_glyph_func, NULL, NULL);
+  hb_font_funcs_set_glyph_h_advance_func (funcs, glyph_h_advance_func, NULL, NULL);
+  hb_font_funcs_set_glyph_extents_func (funcs, glyph_extents_func, NULL, NULL);
+  hb_font_funcs_set_font_h_extents_func (funcs, font_extents_func, NULL, NULL);
+
+  hb_font_set_funcs (hb_font, funcs, self, NULL);
+
+  hb_font_funcs_destroy (funcs);
+  hb_face_destroy (face);
+  hb_blob_destroy (blob);
+
+  size = self->size * self->dpi / 72.f;
+  x_scale = y_scale = 1;
+
+  if (PANGO_GRAVITY_IS_IMPROPER (self->gravity))
+    {
+      x_scale = - x_scale;
+      y_scale = - y_scale;
+    }
+
+  hb_font_set_scale (hb_font, size * x_scale, size * y_scale);
+  hb_font_set_ptem (hb_font, self->size / PANGO_SCALE);
+
+  return hb_font;
+}
+
+static gboolean
+pango_user_font_has_char (PangoFont *font,
+                          gunichar   wc)
+{
+  hb_font_t *user_font = pango_font_get_hb_font (font);
+  hb_codepoint_t glyph;
+
+  return hb_font_get_nominal_glyph (user_font, wc, &glyph);
+}
+
+static PangoFontFace *
+pango_user_font_get_face (PangoFont *font)
+{
+  PangoUserFont *self = PANGO_USER_FONT (font);
+
+  return PANGO_FONT_FACE (self->face);
+}
+
+static void
+pango_user_font_class_init (PangoUserFontClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+  PangoFontClass *font_class = PANGO_FONT_CLASS (class);
+
+  object_class->finalize = pango_user_font_finalize;
+
+  font_class->describe = pango_user_font_describe;
+  font_class->describe_absolute = pango_user_font_describe_absolute;
+  font_class->get_coverage = pango_user_font_get_coverage;
+  font_class->get_glyph_extents = pango_user_font_get_glyph_extents;
+  font_class->get_metrics = pango_user_font_get_metrics;
+  font_class->get_font_map = pango_user_font_get_font_map;
+  font_class->create_hb_font = pango_user_font_create_hb_font;
+  font_class->has_char = pango_user_font_has_char;
+  font_class->get_face = pango_user_font_get_face;
+}
+
+/* }}} */
+ /* {{{ Public API */
+
+/**
+ * pango_user_font_new:
+ * @face: the `PangoUserFace` to use
+ * @size: the desired size in points, scaled by `PANGO_SCALE`
+ * @gravity: the gravity to use when rendering
+ * @dpi: the dpi used when rendering
+ * @matrix: (nullable): transformation matrix to use when rendering
+ *
+ * Creates a new `PangoUserFont`.
+ *
+ * Returns: a newly created `PangoUserFont`
+ *
+ * Since: 1.52
+ */
+PangoUserFont *
+pango_user_font_new (PangoUserFace     *face,
+                     int                size,
+                     PangoGravity       gravity,
+                     float              dpi,
+                     const PangoMatrix *matrix)
+{
+  PangoUserFont *self;
+
+  self = g_object_new (PANGO_TYPE_USER_FONT, NULL);
+
+  self->face = g_object_ref (face);
+
+  self->size = size;
+  self->dpi = dpi;
+  if (gravity != PANGO_GRAVITY_AUTO)
+    self->gravity = gravity;
+  if (matrix)
+    self->matrix = *matrix;
+
+  return self;
+}
+
+/**
+ * pango_user_font_new_for_description:
+ * @face: the `PangoUserFace` to use
+ * @description: a `PangoFontDescription`
+ * @dpi: the dpi used when rendering
+ * @matrix: (nullable): transformation matrix to use when rendering
+ *
+ * Creates a new `PangoUserFont` with size and gravity taken
+ * from a font description.
+ *
+ * Returns: a newly created `PangoHbFont`
+ *
+ * Since: 1.52
+ */
+
+PangoUserFont *
+pango_user_font_new_for_description (PangoUserFace              *face,
+                                     const PangoFontDescription *description,
+                                     float                       dpi,
+                                     const PangoMatrix          *matrix)
+{
+  int size;
+  PangoGravity gravity;
+
+  if (pango_font_description_get_size_is_absolute (description))
+    size = pango_font_description_get_size (description) * 72. / dpi;
+  else
+    size = pango_font_description_get_size (description);
+
+  if ((pango_font_description_get_set_fields (description) & PANGO_FONT_MASK_GRAVITY) != 0 &&
+      pango_font_description_get_gravity (description) != PANGO_GRAVITY_SOUTH)
+    gravity = pango_font_description_get_gravity (description);
+  else
+    gravity = PANGO_GRAVITY_AUTO;
+
+  return pango_user_font_new (face, size, gravity, dpi, matrix);
+}
+
+/* }}} */
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/pango/pango-userfont.h b/pango/pango-userfont.h
new file mode 100644
index 000000000..e60cf508d
--- /dev/null
+++ b/pango/pango-userfont.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2022 Red Hat, Inc.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <pango/pango-types.h>
+#include <pango/pango-userface.h>
+#include <hb.h>
+
+G_BEGIN_DECLS
+
+#define PANGO_TYPE_USER_FONT      (pango_user_font_get_type ())
+
+PANGO_AVAILABLE_IN_ALL
+PANGO_DECLARE_INTERNAL_TYPE (PangoUserFont, pango_user_font, PANGO, USER_FONT, PangoFont)
+
+PANGO_AVAILABLE_IN_ALL
+PangoUserFont * pango_user_font_new                 (PangoUserFace              *face,
+                                                     int                         size,
+                                                     PangoGravity                gravity,
+                                                     float                       dpi,
+                                                     const PangoMatrix          *matrix);
+
+PANGO_AVAILABLE_IN_ALL
+PangoUserFont * pango_user_font_new_for_description (PangoUserFace              *face,
+                                                     const PangoFontDescription *description,
+                                                     float                       dpi,
+                                                     const PangoMatrix          *matrix);
+
+G_END_DECLS
diff --git a/pango/pango.h b/pango/pango.h
index 569636a1f..07169f42c 100644
--- a/pango/pango.h
+++ b/pango/pango.h
@@ -59,6 +59,8 @@
 #include <pango/pango-script.h>
 #include <pango/pango-tabs.h>
 #include <pango/pango-types.h>
+#include <pango/pango-userface.h>
+#include <pango/pango-userfont.h>
 #include <pango/pango-utils.h>
 #include <pango/pango-version-macros.h>
 
diff --git a/pango/pangocairo-font.c b/pango/pangocairo-font.c
index 134e57fed..021c9d626 100644
--- a/pango/pangocairo-font.c
+++ b/pango/pangocairo-font.c
@@ -31,6 +31,8 @@
 #include "pango-impl-utils.h"
 #include "pango-hbfont-private.h"
 #include "pango-hbface-private.h"
+#include "pango-userfont-private.h"
+#include "pango-userface-private.h"
 #include "pangocairo-fc.h"
 
 #pragma GCC diagnostic push
@@ -74,6 +76,76 @@ _pango_cairo_font_private_scaled_font_data_destroy (PangoCairoFontPrivateScaledF
 
 static FT_Library ft_library;
 
+static cairo_user_data_key_t cairo_user_data;
+
+static cairo_status_t
+render_func (cairo_scaled_font_t  *scaled_font,
+             unsigned long         glyph,
+             cairo_t              *cr,
+             cairo_text_extents_t *extents)
+{
+  cairo_font_face_t *font_face;
+  PangoUserFont *font;
+  hb_glyph_extents_t glyph_extents;
+  hb_position_t h_advance;
+  hb_position_t v_advance;
+  gboolean is_color;
+
+  font_face = cairo_scaled_font_get_font_face (scaled_font);
+  font = cairo_font_face_get_user_data (font_face, &cairo_user_data);
+
+  extents->x_bearing = 0;
+  extents->y_bearing = 0;
+  extents->width = 0;
+  extents->height = 0;
+  extents->x_advance = 0;
+  extents->y_advance = 0;
+
+  if (!font->face->glyph_info_func (font->face, 1024,
+                                    (hb_codepoint_t)glyph,
+                                    &glyph_extents,
+                                    &h_advance, &v_advance,
+                                    &is_color,
+                                    font->face->user_data))
+    {
+      return CAIRO_STATUS_USER_FONT_ERROR;
+    }
+
+  extents->x_bearing = glyph_extents.x_bearing / (double) 1024;
+  extents->y_bearing = glyph_extents.y_bearing / (double) 1024;
+  extents->width = glyph_extents.width / (double) 1024;
+  extents->height = glyph_extents.height / (double) 1024;
+  extents->x_advance = h_advance / (double) 1024;
+  extents->y_advance = v_advance / (double) 1024;
+
+  if (!font->face->render_func (font->face, font->size,
+                                (hb_codepoint_t)glyph,
+                                font->face->user_data,
+                                "cairo",
+                                cr))
+    {
+      return CAIRO_STATUS_USER_FONT_ERROR;
+    }
+
+  return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_font_face_t *
+create_font_face_for_user_font (PangoUserFont *font)
+{
+  cairo_font_face_t *cairo_face;
+
+  cairo_face = cairo_user_font_face_create ();
+  cairo_font_face_set_user_data (cairo_face, &cairo_user_data, font, NULL);
+#ifdef HAVE_CAIRO_USER_FONT_FACE_SET_RENDER_COLOR_GLYPH_FUNC
+  cairo_user_font_face_set_render_color_glyph_func (cairo_face, render_func);
+#else
+  cairo_user_font_face_set_render_glyph_func (cairo_face, render_func);
+#endif
+
+  return cairo_face;
+}
+
 static cairo_font_face_t *
 create_font_face_for_hb_font (PangoHbFont *font)
 {
@@ -145,8 +217,10 @@ _pango_cairo_font_private_get_scaled_font (PangoCairoFontPrivate *cf_priv)
 
   if (PANGO_IS_CAIRO_FONT (cf_priv->cfont))
     font_face = (* PANGO_CAIRO_FONT_GET_IFACE (cf_priv->cfont)->create_font_face) (cf_priv->cfont);
-  else
+  else if (PANGO_IS_HB_FONT (cf_priv->cfont))
     font_face = create_font_face_for_hb_font (PANGO_HB_FONT (cf_priv->cfont));
+  else if (PANGO_IS_USER_FONT (cf_priv->cfont))
+    font_face = create_font_face_for_user_font (PANGO_USER_FONT (cf_priv->cfont));
 
   if (G_UNLIKELY (font_face == NULL))
     goto done;
@@ -694,26 +768,35 @@ _pango_font_get_cairo_font_private (PangoFont *font)
   cf_priv = g_object_get_data (G_OBJECT (font), "pango-hb-font-cairo_private");
   if (!cf_priv)
     {
-      PangoHbFont *hbfont = PANGO_HB_FONT (font);
+      CommonFont *cf = (CommonFont *)font;
       cairo_font_options_t *font_options;
       cairo_matrix_t font_matrix;
+      double x_scale, y_scale;
       int size;
 
-      if (hbfont->face->matrix)
-        cairo_matrix_init (&font_matrix,
-                           hbfont->face->matrix->xx,
-                           - hbfont->face->matrix->yx,
-                           - hbfont->face->matrix->xy,
-                           hbfont->face->matrix->yy,
-                           0., 0.);
-      else
-        cairo_matrix_init (&font_matrix, 1., 0., 0., 1., 0., 0.);
+      cairo_matrix_init (&font_matrix, 1., 0., 0., 1., 0., 0.);
+      x_scale = y_scale = 1;
+
+      if (PANGO_IS_HB_FONT (font))
+        {
+          PangoHbFont *hbfont = PANGO_HB_FONT (font);
+          if (hbfont->face->matrix)
+            cairo_matrix_init (&font_matrix,
+                               hbfont->face->matrix->xx,
+                               - hbfont->face->matrix->yx,
+                               - hbfont->face->matrix->xy,
+                               hbfont->face->matrix->yy,
+                               0., 0.);
+
+          x_scale = hbfont->face->x_scale;
+          y_scale = hbfont->face->y_scale;
+        }
 
-      size = hbfont->size * hbfont->dpi / 72.;
+      size = cf->size * cf->dpi / 72.;
 
       cairo_matrix_scale (&font_matrix,
-                          hbfont->face->x_scale * size / (double)PANGO_SCALE,
-                          hbfont->face->y_scale * size / (double)PANGO_SCALE);
+                          x_scale * size / (double)PANGO_SCALE,
+                          y_scale * size / (double)PANGO_SCALE);
 
       font_options = cairo_font_options_create ();
       cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_NONE);
@@ -722,9 +805,9 @@ _pango_font_get_cairo_font_private (PangoFont *font)
       cf_priv = g_new0 (PangoCairoFontPrivate, 1);
       _pango_cairo_font_private_initialize (cf_priv,
                                             (PangoCairoFont *)font,
-                                            hbfont->gravity,
+                                            cf->gravity,
                                             font_options,
-                                            &hbfont->matrix,
+                                            &cf->matrix,
                                             &font_matrix);
 
       cairo_font_options_destroy (font_options);
diff --git a/pango/pangofc-hbfontmap.c b/pango/pangofc-hbfontmap.c
index ce197e930..804f2ebe5 100644
--- a/pango/pangofc-hbfontmap.c
+++ b/pango/pangofc-hbfontmap.c
@@ -352,7 +352,7 @@ pango_fc_hb_font_map_populate (PangoHbFontMap *map)
       if (!face)
         continue;
 
-      pango_hb_font_map_add_face (PANGO_HB_FONT_MAP (self), face);
+      pango_hb_font_map_add_face (PANGO_HB_FONT_MAP (self), PANGO_FONT_FACE (face));
     }
 
   g_hash_table_unref (lang_hash);
diff --git a/pango/shape.c b/pango/shape.c
index e408aa9b3..81447d6e3 100644
--- a/pango/shape.c
+++ b/pango/shape.c
@@ -30,6 +30,8 @@
 
 #include "pango-item-private.h"
 #include "pango-font-private.h"
+#include "pango-userfont-private.h"
+#include "pango-userface-private.h"
 
 #include <hb-ot.h>
 
@@ -529,6 +531,25 @@ pango_hb_shape (const char          *item_text,
   hb_font_destroy (hb_font);
 }
 
+/* }}} */
+/* {{{ User shaping */
+
+static void
+pango_user_shape (const char          *text,
+                  unsigned int         length,
+                  const PangoAnalysis *analysis,
+                  PangoGlyphString    *glyphs,
+                  PangoShapeFlags      flags)
+{
+  PangoUserFont *font = PANGO_USER_FONT (analysis->font);
+
+  font->face->shape_func (font->face, font->size,
+                          text, length,
+                          analysis,
+                          glyphs, flags,
+                          font->face->user_data);
+}
+
 /* }}} */
 /* {{{ Fallback shaping */
 
@@ -616,47 +637,47 @@ pango_shape_internal (const char          *item_text,
   g_return_if_fail (paragraph_text <= item_text);
   g_return_if_fail (paragraph_text + paragraph_length >= item_text + item_length);
 
-  if (analysis->font)
+  if (PANGO_IS_USER_FONT (analysis->font))
+    pango_user_shape (item_text, item_length, analysis, glyphs, flags);
+  else if (analysis->font)
+    pango_hb_shape (item_text, item_length,
+                    paragraph_text, paragraph_length,
+                    analysis,
+                    log_attrs, num_chars,
+                    glyphs, flags);
+  else
+    glyphs->num_glyphs = 0;
+
+  if (analysis->font && glyphs->num_glyphs == 0)
     {
-      pango_hb_shape (item_text, item_length,
-                      paragraph_text, paragraph_length,
-                      analysis,
-                      log_attrs, num_chars,
-                      glyphs, flags);
+      /* If a font has been correctly chosen, but no glyphs are output,
+       * there's probably something wrong with the font.
+       *
+       * Trying to be informative, we print out the font description,
+       * and the text, but to not flood the terminal with
+       * zillions of the message, we set a flag to only err once per
+       * font.
+       */
+      GQuark warned_quark = g_quark_from_static_string ("pango-shape-fail-warned");
 
-      if (G_UNLIKELY (glyphs->num_glyphs == 0))
+      if (!g_object_get_qdata (G_OBJECT (analysis->font), warned_quark))
         {
-          /* If a font has been correctly chosen, but no glyphs are output,
-           * there's probably something wrong with the font.
-           *
-           * Trying to be informative, we print out the font description,
-           * and the text, but to not flood the terminal with
-           * zillions of the message, we set a flag to only err once per
-           * font.
-           */
-          GQuark warned_quark = g_quark_from_static_string ("pango-shape-fail-warned");
-
-          if (!g_object_get_qdata (G_OBJECT (analysis->font), warned_quark))
-            {
-              PangoFontDescription *desc;
-              char *font_name;
+          PangoFontDescription *desc;
+          char *font_name;
 
-              desc = pango_font_describe (analysis->font);
-              font_name = pango_font_description_to_string (desc);
-              pango_font_description_free (desc);
+          desc = pango_font_describe (analysis->font);
+          font_name = pango_font_description_to_string (desc);
+          pango_font_description_free (desc);
 
-              g_warning ("shaping failure, expect ugly output. font='%s', text='%.*s'",
-                         font_name, item_length, item_text);
+          g_warning ("shaping failure, expect ugly output. font='%s', text='%.*s'",
+                     font_name, item_length, item_text);
 
-              g_free (font_name);
+          g_free (font_name);
 
-              g_object_set_qdata (G_OBJECT (analysis->font), warned_quark,
-                                  GINT_TO_POINTER (1));
-            }
+          g_object_set_qdata (G_OBJECT (analysis->font), warned_quark,
+                              GINT_TO_POINTER (1));
         }
     }
-  else
-    glyphs->num_glyphs = 0;
 
   if (G_UNLIKELY (!glyphs->num_glyphs))
     {
diff --git a/tests/testhbfont.c b/tests/testhbfont.c
index 2e689e660..6b6860a95 100644
--- a/tests/testhbfont.c
+++ b/tests/testhbfont.c
@@ -51,7 +51,7 @@ test_hbfont_monospace (void)
   g_assert_false (pango_font_family_is_variable (family));
   g_assert_false (pango_font_family_is_monospace (family));
 
-  pango_hb_font_map_add_face (map, pango_hb_face_new_from_file (path, 0, -2, NULL, NULL));
+  pango_hb_font_map_add_face (map, PANGO_FONT_FACE (pango_hb_face_new_from_file (path, 0, -2, NULL, NULL)));
 
   g_assert_true (pango_font_family_is_variable (family));
 
@@ -394,7 +394,7 @@ test_hbfont_load (void)
   pango_font_description_free (desc);
   g_free (path);
 
-  pango_hb_font_map_add_face (map, face_fat);
+  pango_hb_font_map_add_face (map, PANGO_FONT_FACE (face_fat));
 
   path = g_test_build_filename (G_TEST_DIST, "fonts", "DejaVuSans.ttf", NULL);
   desc = pango_font_description_new ();
@@ -402,7 +402,7 @@ test_hbfont_load (void)
   face_wild = pango_hb_face_new_from_file (path, 0, -1, "Wild", desc);
   pango_font_description_free (desc);
 
-  pango_hb_font_map_add_face (map, face_wild);
+  pango_hb_font_map_add_face (map, PANGO_FONT_FACE (face_wild));
 
   desc = pango_font_face_describe (PANGO_FONT_FACE (face_wild));
   pango_font_description_set_size (desc, 12 * PANGO_SCALE);
@@ -460,7 +460,7 @@ test_hbfont_load_variation (void)
   pango_font_description_free (desc);
   g_free (path);
 
-  pango_hb_font_map_add_face (map, face_fat);
+  pango_hb_font_map_add_face (map, PANGO_FONT_FACE (face_fat));
 
   desc = pango_font_description_new ();
   pango_font_description_set_family (desc, "Cat");
@@ -469,7 +469,7 @@ test_hbfont_load_variation (void)
   face_wild = pango_hb_face_new_instance (face_fat, &v, 1, "Wild", desc);
   pango_font_description_free (desc);
 
-  pango_hb_font_map_add_face (map, face_wild);
+  pango_hb_font_map_add_face (map, PANGO_FONT_FACE (face_wild));
 
   desc = pango_font_face_describe (PANGO_FONT_FACE (face_wild));
 
@@ -630,12 +630,13 @@ compare_family_name (gconstpointer a,
 static gboolean
 is_generic_family (PangoFontFamily *fam)
 {
-  const char *name = pango_font_family_get_name (fam);
-
-  if (strcmp (G_OBJECT_TYPE_NAME (fam), "PangoGenericFamily") == 0)
-    return TRUE;
+  return g_str_equal (G_OBJECT_TYPE_NAME (fam), "PangoGenericFamily");
+}
 
-  return g_strv_contains ((const char *[]){ "Sans", "Serif", "Monospace", "System-ui", NULL}, name);
+static gboolean
+is_generic_family_name (const char *family)
+{
+  return g_strv_contains ((const char *[]){ "Sans", "Serif", "Monospace", "System-ui", NULL}, family);
 }
 
 static const char * const *
@@ -646,9 +647,10 @@ collect_nongeneric_families (PangoFontMap *map)
   for (int i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (map)); i++)
     {
       PangoFontFamily *fam = g_list_model_get_item (G_LIST_MODEL (map), i);
+      const char *name = pango_font_family_get_name (fam);
 
-      if (!is_generic_family (fam))
-        g_ptr_array_add (array, (gpointer)pango_font_family_get_name (fam));
+      if (!(is_generic_family (fam) || is_generic_family_name (name)))
+        g_ptr_array_add (array, (gpointer)name);
 
       g_object_unref (fam);
     }


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