[pango/pango2-color-palette] Support color palettes in fonts




commit c8fe442bb53f6e7fd6aed12a99c75da6a32733f8
Author: Matthias Clasen <mclasen redhat com>
Date:   Fri Jul 1 09:57:35 2022 -0400

    Support color palettes in fonts
    
    Add a pango_context_set_palette to select whether
    we prefer the default palette, the palette for light
    background, or the palette for dark background. Also
    add a palette attribute that can be used to override
    this.
    
    Make the cairo renderer pick up the palette, and
    apply it when installing the font for a run.
    
    Predefined palette names are "default", "light",
    "dark", "palette0", "palette1", ...
    
    Additionally, PangoHbFace can associate custom
    names with palette indices.
    
    To try this, use the new --palette option of
    pango-view.
    
    Fonts to try this with are Amiri Quran Colored
    or the Bungee Color family.

 docs/pango_markup.md                    |   3 +
 pango2/pango-attr-iterator.c            |   3 +-
 pango2/pango-attr-list.c                |  11 ++
 pango2/pango-attributes.c               |  23 +++-
 pango2/pango-attributes.h               |   4 +
 pango2/pango-color-palette-private.h    |  25 +++++
 pango2/pango-context-private.h          |   2 +
 pango2/pango-context.c                  |  81 +++++++++++++-
 pango2/pango-context.h                  |   8 ++
 pango2/pango-font-description-private.h |   2 +
 pango2/pango-font-description.c         | 139 +++++++++++++++---------
 pango2/pango-font-private.h             |   6 --
 pango2/pango-fontmap.c                  |   3 +-
 pango2/pango-fontset-cached.c           |  64 +++++-------
 pango2/pango-hbface-private.h           |   9 ++
 pango2/pango-hbface.c                   | 180 ++++++++++++++++++++++++++++++++
 pango2/pango-hbface.h                   |   7 ++
 pango2/pango-hbfont.c                   |   2 +-
 pango2/pango-markup.c                   |  10 ++
 pango2/pango-types.h                    |  23 ++++
 pango2/pangocairo-font.c                |  84 +++++++++++----
 pango2/pangocairo-private.h             |  25 +++--
 pango2/pangocairo-render.c              |  77 ++++++++++++--
 pango2/pangocairo-render.h              |   5 +
 pango2/serializer.c                     |   6 ++
 tests/test-font.c                       |  35 ++++++-
 utils/viewer-pangocairo.c               |   9 +-
 27 files changed, 702 insertions(+), 144 deletions(-)
---
diff --git a/docs/pango_markup.md b/docs/pango_markup.md
index 587c4352e..a33c04d6b 100644
--- a/docs/pango_markup.md
+++ b/docs/pango_markup.md
@@ -108,6 +108,9 @@ font_features
 : A comma-separated list of OpenType font feature settings, in the same syntax as
   accepted by CSS. E.g: `font_features='dlig=1, -kern, afrc on'`.
 
+palette
+: The name of a palette to use.
+
 foreground
 fgcolor
 color
diff --git a/pango2/pango-attr-iterator.c b/pango2/pango-attr-iterator.c
index 59bb1255d..72a23c24c 100644
--- a/pango2/pango-attr-iterator.c
+++ b/pango2/pango-attr-iterator.c
@@ -306,8 +306,7 @@ pango2_attr_iterator_get (Pango2AttrIterator *iterator,
  *   order to free this value, you must call
  *   [method@Pango2.Attribute.destroy] on each member.
  *
- * Get the font and other attributes at the current
- * iterator position.
+ * Get the font and other attributes at the current iterator position.
  */
 void
 pango2_attr_iterator_get_font (Pango2AttrIterator     *iterator,
diff --git a/pango2/pango-attr-list.c b/pango2/pango-attr-list.c
index fb0d24715..eae66d87f 100644
--- a/pango2/pango-attr-list.c
+++ b/pango2/pango-attr-list.c
@@ -1253,6 +1253,17 @@ pango2_attr_list_from_string (const char *text)
           INT_ATTR(line_spacing, int);
           break;
 
+        case PANGO2_ATTR_PALETTE:
+          p++;
+          endp = strchr (p, '"');
+          if (!endp) goto fail;
+          str = g_strndup (p, endp - p);
+          attr = pango2_attr_palette_new (str);
+          g_free (str);
+          endp++;
+          if (!is_valid_end_char (*endp)) goto fail;
+          break;
+
         case PANGO2_ATTR_SHAPE:
         default:
           g_assert_not_reached ();
diff --git a/pango2/pango-attributes.c b/pango2/pango-attributes.c
index 1318451fb..2c72c9c04 100644
--- a/pango2/pango-attributes.c
+++ b/pango2/pango-attributes.c
@@ -564,7 +564,7 @@ pango2_attr_gravity_hint_new (Pango2GravityHint hint)
 /**
  * pango2_attr_font_features_new:
  * @features: a string with OpenType font features, with the syntax of the [CSS
- * font-feature-settings property](https://www.w3.org/TR/css-fonts-4/#font-rend-desc)
+ *   font-feature-settings property](https://www.w3.org/TR/css-fonts-4/#font-rend-desc)
  *
  * Create a new font features tag attribute.
  *
@@ -583,6 +583,27 @@ pango2_attr_font_features_new (const char *features)
   return pango2_attr_string_new (PANGO2_ATTR_FONT_FEATURES, features);
 }
 
+/**
+ * pango2_attr_palette_new:
+ * @palette : name of palette to use
+ *
+ * Create a new palette attribute.
+ *
+ * You can use this attribute to select font color palettes
+ * by name, like "light", "dark" or "palette3".
+ *
+ * Return value: (transfer full): the newly allocated
+ *   `Pango2Attribute`, which should be freed with
+ *   [method@Pango2.Attribute.destroy]
+ */
+Pango2Attribute *
+pango2_attr_palette_new (const char *palette)
+{
+  g_return_val_if_fail (palette != NULL, NULL);
+
+  return pango2_attr_string_new (PANGO2_ATTR_PALETTE, palette);
+}
+
 /**
  * pango2_attr_allow_breaks_new:
  * @allow_breaks: %TRUE if we line breaks are allowed
diff --git a/pango2/pango-attributes.h b/pango2/pango-attributes.h
index d7c7661c6..990d90e15 100644
--- a/pango2/pango-attributes.h
+++ b/pango2/pango-attributes.h
@@ -57,6 +57,7 @@ G_BEGIN_DECLS
  * @PANGO2_ATTR_GRAVITY: base text gravity
  * @PANGO2_ATTR_GRAVITY_HINT: gravity hint
  * @PANGO2_ATTR_FONT_FEATURES: OpenType font features
+ * @PANGO2_ATTR_PALETTE: Color palette name
  * @PANGO2_ATTR_ALLOW_BREAKS: whether line breaks are allowed
  * @PANGO2_ATTR_SHOW: how to render invisible characters
  * @PANGO2_ATTR_INSERT_HYPHENS: whether to insert hyphens at intra-word line breaks
@@ -105,6 +106,7 @@ typedef enum
   PANGO2_ATTR_GRAVITY              = PANGO2_ATTR_TYPE (INT, ITEMIZATION, OVERRIDES),
   PANGO2_ATTR_GRAVITY_HINT         = PANGO2_ATTR_TYPE (INT, ITEMIZATION, OVERRIDES),
   PANGO2_ATTR_FONT_FEATURES        = PANGO2_ATTR_TYPE (STRING, SHAPING, ACCUMULATES),
+  PANGO2_ATTR_PALETTE              = PANGO2_ATTR_TYPE (STRING, ITEMIZATION, OVERRIDES),
   PANGO2_ATTR_ALLOW_BREAKS         = PANGO2_ATTR_TYPE (BOOLEAN, BREAKING, OVERRIDES),
   PANGO2_ATTR_SHOW                 = PANGO2_ATTR_TYPE (INT, SHAPING, OVERRIDES),
   PANGO2_ATTR_INSERT_HYPHENS       = PANGO2_ATTR_TYPE (BOOLEAN, SHAPING, OVERRIDES),
@@ -245,6 +247,8 @@ Pango2Attribute *        pango2_attr_gravity_hint_new             (Pango2Gravity
 PANGO2_AVAILABLE_IN_ALL
 Pango2Attribute *        pango2_attr_font_features_new            (const char                  *features);
 PANGO2_AVAILABLE_IN_ALL
+Pango2Attribute *        pango2_attr_palette_new                  (const char                  *palette);
+PANGO2_AVAILABLE_IN_ALL
 Pango2Attribute *        pango2_attr_allow_breaks_new             (gboolean                     
allow_breaks);
 PANGO2_AVAILABLE_IN_ALL
 Pango2Attribute *        pango2_attr_word_new                     (void);
diff --git a/pango2/pango-color-palette-private.h b/pango2/pango-color-palette-private.h
new file mode 100644
index 000000000..283c221d0
--- /dev/null
+++ b/pango2/pango-color-palette-private.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 Red Hat Software
+ *
+ * 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 "pango-color-palette.h"
+
+struct _Pango2ColorPalette {
+  GQuark name;
+  unsigned int index;
+};
diff --git a/pango2/pango-context-private.h b/pango2/pango-context-private.h
index 07b4b506b..4ec65da6e 100644
--- a/pango2/pango-context-private.h
+++ b/pango2/pango-context-private.h
@@ -48,6 +48,8 @@ struct _Pango2Context
 
   gboolean round_glyph_positions;
 
+  GQuark palette;
+
 #ifdef HAVE_CAIRO
   gboolean set_options_explicit;
 
diff --git a/pango2/pango-context.c b/pango2/pango-context.c
index 371c8405f..a6b6ee243 100644
--- a/pango2/pango-context.c
+++ b/pango2/pango-context.c
@@ -69,6 +69,7 @@ enum {
   PROP_GRAVITY_HINT,
   PROP_MATRIX,
   PROP_ROUND_GLYPH_POSITIONS,
+  PROP_PALETTE,
   N_PROPERTIES
 };
 
@@ -88,6 +89,7 @@ pango2_context_init (Pango2Context *context)
   context->language = pango2_language_get_default ();
   context->font_map = NULL;
   context->round_glyph_positions = TRUE;
+  context->palette = g_quark_from_static_string (PANGO2_COLOR_PALETTE_DEFAULT);
 
   context->font_desc = pango2_font_description_new ();
   pango2_font_description_set_family_static (context->font_desc, "serif");
@@ -140,6 +142,10 @@ pango2_context_set_property (GObject      *object,
       pango2_context_set_round_glyph_positions (context, g_value_get_boolean (value));
       break;
 
+    case PROP_PALETTE:
+      pango2_context_set_palette (context, g_value_get_string (value));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     }
@@ -187,6 +193,10 @@ pango2_context_get_property (GObject    *object,
       g_value_set_boolean (value, pango2_context_get_round_glyph_positions (context));
       break;
 
+    case PROP_PALETTE:
+      g_value_set_string (value, pango2_context_get_palette (context));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     }
@@ -197,6 +207,10 @@ pango2_context_class_init (Pango2ContextClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
+  g_intern_static_string (PANGO2_COLOR_PALETTE_DEFAULT);
+  g_intern_static_string (PANGO2_COLOR_PALETTE_LIGHT);
+  g_intern_static_string (PANGO2_COLOR_PALETTE_DARK);
+
   object_class->finalize = pango2_context_finalize;
   object_class->set_property = pango2_context_set_property;
   object_class->get_property = pango2_context_get_property;
@@ -303,15 +317,22 @@ pango2_context_class_init (Pango2ContextClass *klass)
     g_param_spec_boolean ("round-glyph-positions", NULL, NULL, TRUE,
                           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
+  /**
+   * Pango2Context:palette: (attributes org.gtk.Property.get=pango2_context_get_palette 
org.gtk.Property.set=pango2_context_set_palette)
+   *
+   * The name of the color palette to use for color fonts.
+   */
+  properties[PROP_PALETTE] =
+    g_param_spec_string ("palette", NULL, NULL, "default",
+                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
   g_object_class_install_properties (object_class, N_PROPERTIES, properties);
 }
 
 static void
 pango2_context_finalize (GObject *object)
 {
-  Pango2Context *context;
-
-  context = PANGO2_CONTEXT (object);
+  Pango2Context *context = PANGO2_CONTEXT (object);
 
   if (context->font_map)
     g_object_unref (context->font_map);
@@ -1044,3 +1065,57 @@ pango2_context_get_round_glyph_positions (Pango2Context *context)
 {
   return context->round_glyph_positions;
 }
+
+/**
+ * pango2_context_set_palette:
+ * @context: a `Pango2Context`
+ * @palette: the name of the palette to use
+ *
+ * Sets the default palette to use for color fonts.
+ *
+ * This can either be one of the predefined names
+ * "default", "light" or "dark", a name referring
+ * to a palette by index ("palette0", "palette1", …),
+ * or a custom name.
+ *
+ * Some color fonts include metadata that indicates
+ * the default palette, as well as palettes that work
+ * well on light or dark backgrounds. The predefined
+ * names select those.
+ *
+ * To associate custom names with palettes in fonts,
+ * use [method@Pango2.HbFace.set_palette_name].
+ */
+void
+pango2_context_set_palette (Pango2Context *context,
+                            const char    *palette)
+{
+  GQuark quark;
+
+  g_return_if_fail (PANGO2_IS_CONTEXT (context));
+
+  quark = g_quark_from_string (palette);
+  if (context->palette == quark)
+    return;
+
+  context->palette = quark;
+  g_object_notify_by_pspec (G_OBJECT (context), properties[PROP_PALETTE]);
+}
+
+/**
+ * pango2_context_get_palette:
+ * @context: a `Pango2Context`
+ *
+ * Returns the default palette to use for color fonts.
+ *
+ * See [method@Pango2.Context.set_palette].
+ *
+ * Return value: (nullable): the palette name
+ */
+const char *
+pango2_context_get_palette (Pango2Context *context)
+{
+  g_return_val_if_fail (PANGO2_IS_CONTEXT (context), PANGO2_COLOR_PALETTE_DEFAULT);
+
+  return g_quark_to_string (context->palette);
+}
diff --git a/pango2/pango-context.h b/pango2/pango-context.h
index 902e840dc..219014982 100644
--- a/pango2/pango-context.h
+++ b/pango2/pango-context.h
@@ -99,4 +99,12 @@ void                     pango2_context_set_round_glyph_positions (Pango2Context
 PANGO2_AVAILABLE_IN_ALL
 gboolean                 pango2_context_get_round_glyph_positions (Pango2Context                 *context);
 
+PANGO2_AVAILABLE_IN_ALL
+void                     pango2_context_set_palette               (Pango2Context                 *context,
+                                                                   const char                    *palette);
+PANGO2_AVAILABLE_IN_ALL
+const char *             pango2_context_get_palette               (Pango2Context                 *context);
+
+
+
 G_END_DECLS
diff --git a/pango2/pango-font-description-private.h b/pango2/pango-font-description-private.h
index 11b09d00d..231a28007 100644
--- a/pango2/pango-font-description-private.h
+++ b/pango2/pango-font-description-private.h
@@ -28,6 +28,8 @@ gboolean pango2_font_description_is_similar       (const Pango2FontDescription *
 int      pango2_font_description_compute_distance (const Pango2FontDescription *a,
                                                    const Pango2FontDescription *b);
 
+GQuark   pango2_font_description_get_palette_quark (const Pango2FontDescription *desc);
+
 gboolean pango2_parse_style              (const char    *str,
                                           Pango2Style   *style,
                                           gboolean       warn);
diff --git a/pango2/pango-font-description.c b/pango2/pango-font-description.c
index 20421c893..c08613e35 100644
--- a/pango2/pango-font-description.c
+++ b/pango2/pango-font-description.c
@@ -39,6 +39,8 @@ struct _Pango2FontDescription
   char *variations;
   char *faceid;
 
+  GQuark palette;
+
   guint16 mask;
   guint static_family : 1;
   guint static_variations : 1;
@@ -61,6 +63,7 @@ static const Pango2FontDescription pfd_defaults = {
   0,                    /* size */
   NULL,                 /* variations */
   NULL,                 /* faceid */
+  0,                    /* palette */
 
   0,                    /* mask */
   0,                    /* static_family */
@@ -872,7 +875,8 @@ pango2_font_description_equal (const Pango2FontDescription *desc1,
          (desc1->family_name == desc2->family_name ||
           (desc1->family_name && desc2->family_name && g_ascii_strcasecmp (desc1->family_name, 
desc2->family_name) == 0)) &&
          (g_strcmp0 (desc1->variations, desc2->variations) == 0) &&
-         (g_strcmp0 (desc1->faceid, desc2->faceid) == 0);
+         (g_strcmp0 (desc1->faceid, desc2->faceid) == 0) &&
+         desc1->palette == desc2->palette;
 }
 
 #define TOLOWER(c) \
@@ -924,6 +928,7 @@ pango2_font_description_hash (const Pango2FontDescription *desc)
   hash ^= desc->weight << 16;
   hash ^= desc->stretch << 26;
   hash ^= desc->gravity << 28;
+  hash ^= desc->palette;
 
   return hash;
 }
@@ -1184,6 +1189,46 @@ parse_size (const char *word,
   return FALSE;
 }
 
+static gboolean
+parse_palette (const char  *word,
+               size_t       wordlen,
+               GQuark      *palette)
+{
+  const char *p, *q;
+  char *s;
+
+  if (!g_str_has_prefix (word, "@palette="))
+    return FALSE;
+
+  p = word + strlen ("@palette=");
+  q = word + wordlen;
+  s = g_strndup (p, q - p);
+
+  *palette = g_quark_from_string (s);
+
+  g_free (s);
+
+  return TRUE;
+}
+
+static gboolean
+parse_faceid (const char  *word,
+              size_t       wordlen,
+              char       **faceid)
+{
+  const char *p, *q;
+
+  if (!g_str_has_prefix (word, "@faceid="))
+    return FALSE;
+
+  p = word + strlen ("@faceid=");
+  q = word + wordlen;
+
+  *faceid = g_strndup (p, q - p);
+
+  return TRUE;
+}
+
 static gboolean
 parse_variations (const char  *word,
                   size_t       wordlen,
@@ -1201,40 +1246,6 @@ parse_variations (const char  *word,
   return TRUE;
 }
 
-static void
-faceid_from_variations (Pango2FontDescription *desc)
-{
-  const char *p, *q;
-
-  p = desc->variations;
-
-  if (g_str_has_prefix (p, "faceid="))
-    {
-      p += strlen ("faceid=");
-      q = strchr (p, ',');
-      if (q)
-        {
-          desc->faceid = g_strndup (p, q - p);
-          p = q + 1;
-        }
-      else
-        {
-          desc->faceid = g_strdup (p);
-          p = NULL;
-        }
-      desc->mask |= PANGO2_FONT_MASK_FACEID;
-    }
-
-  if (p != desc->variations)
-    {
-      char *variations = g_strdup (p);
-      g_free (desc->variations);
-      desc->variations = variations;
-      if (variations == NULL || *variations == '\0')
-        desc->mask &= ~PANGO2_FONT_MASK_VARIATIONS;
-    }
-}
-
 /**
  * pango2_font_description_from_string:
  * @str: string representation of a font description.
@@ -1243,15 +1254,24 @@ faceid_from_variations (Pango2FontDescription *desc)
  *
  * The string must have the form
  *
- *     "\[FAMILY-LIST] \[STYLE-OPTIONS] \[SIZE] \[VARIATIONS]",
+ *     "\[FAMILY-LIST] \[STYLE-OPTIONS] \[SIZE] \[VARIATIONS] \[FACEID]",
  *
  * where FAMILY-LIST is a comma-separated list of families optionally
  * terminated by a comma, STYLE_OPTIONS is a whitespace-separated list
  * of words where each word describes one of style, variant, weight,
  * stretch, or gravity, and SIZE is a decimal number (size in points)
  * or optionally followed by the unit modifier "px" for absolute size.
+ *
  * VARIATIONS is a comma-separated list of font variation
- * specifications of the form "\@axis=value" (the = sign is optional).
+ * specifications of the form "\@axis=value" (the = sign is optional),
+ * where "axis" is a 3-character name of an OpenType variation axis
+ * like "wght", "wdth" or "opsz".
+ *
+ * FACEID must have the form "\@faceid=string" with the literal string
+ * "faceid".
+ *
+ * The VARIATION, FACEID parts can appear in any order,
+ * as long as they are at the end.
  *
  * The following words are understood as styles:
  * "Normal", "Roman", "Oblique", "Italic".
@@ -1304,18 +1324,30 @@ pango2_font_description_from_string (const char *str)
 
   len = strlen (str);
   last = str + len;
-  p = getword (str, last, &wordlen, "");
-  /* Look for variations at the end of the string */
-  if (wordlen != 0)
+
+  do
     {
-      if (parse_variations (p, wordlen, &desc->variations))
+      p = getword (str, last, &wordlen, "");
+
+      if (wordlen == 0 || p[0] != '@')
+        break;
+
+      /* Look for faceid and variations at the end of the string */
+      if (parse_faceid (p, wordlen, &desc->faceid))
+        {
+          desc->mask |= PANGO2_FONT_MASK_FACEID;
+          last = p;
+        }
+      else if (parse_variations (p, wordlen, &desc->variations))
         {
           desc->mask |= PANGO2_FONT_MASK_VARIATIONS;
           last = p;
-
-          faceid_from_variations (desc);
         }
+      else
+        break;
     }
+  while ((desc->mask & (PANGO2_FONT_MASK_FACEID | PANGO2_FONT_MASK_VARIATIONS)) !=
+                       (PANGO2_FONT_MASK_FACEID | PANGO2_FONT_MASK_VARIATIONS));
 
   p = getword (str, last, &wordlen, ",");
   /* Look for a size */
@@ -1428,7 +1460,6 @@ char *
 pango2_font_description_to_string (const Pango2FontDescription *desc)
 {
   GString *result;
-  gboolean in_variations = FALSE;
 
   g_return_val_if_fail (desc != NULL, NULL);
 
@@ -1488,7 +1519,6 @@ pango2_font_description_to_string (const Pango2FontDescription *desc)
 
   if (desc->mask & PANGO2_FONT_MASK_FACEID)
     {
-      in_variations = TRUE;
       g_string_append (result, " @");
       g_string_append_printf (result, "faceid=%s", desc->faceid);
     }
@@ -1496,10 +1526,7 @@ pango2_font_description_to_string (const Pango2FontDescription *desc)
   if ((desc->variations && desc->mask & PANGO2_FONT_MASK_VARIATIONS) &&
       desc->variations[0] != '\0')
     {
-      if (!in_variations)
-        g_string_append (result, " @");
-      else
-        g_string_append (result, ",");
+      g_string_append (result, " @");
       g_string_append (result, desc->variations);
     }
 
@@ -1741,3 +1768,17 @@ pango2_font_description_get_faceid (const Pango2FontDescription *desc)
 
   return desc->faceid;
 }
+
+/*< private >
+ * pango2_font_description_get_palette_quark:
+ * @desc: a `Pango2FontDescription
+ *
+ * Gets the palette field as a `GQuark`.
+ *
+ * Return value: the palette field as a quark
+ */
+GQuark
+pango2_font_description_get_palette_quark (const Pango2FontDescription *desc)
+{
+  return desc->palette;
+}
diff --git a/pango2/pango-font-private.h b/pango2/pango-font-private.h
index ca8d3a775..7789e6be0 100644
--- a/pango2/pango-font-private.h
+++ b/pango2/pango-font-private.h
@@ -114,12 +114,6 @@ void     pango2_font_get_scale_factors (Pango2Font  *font,
 void     pango2_font_get_transform     (Pango2Font  *font,
                                        Pango2Matrix *matrix);
 
-gboolean pango2_font_description_is_similar       (const Pango2FontDescription *a,
-                                                   const Pango2FontDescription *b);
-
-int      pango2_font_description_compute_distance (const Pango2FontDescription *a,
-                                                   const Pango2FontDescription *b);
-
 /* We use these values in a few places as a fallback size for an
  * unknown glyph, if we have no better information.
  */
diff --git a/pango2/pango-fontmap.c b/pango2/pango-fontmap.c
index c3359130f..2619b80a3 100644
--- a/pango2/pango-fontmap.c
+++ b/pango2/pango-fontmap.c
@@ -34,7 +34,7 @@
 #include "pango-fontset.h"
 #include "pango-font-face-private.h"
 #include "pango-trace-private.h"
-#include "pango-context.h"
+#include "pango-context-private.h"
 
 #ifdef HAVE_CORE_TEXT
 #include "pangocoretext-fontmap.h"
@@ -553,6 +553,7 @@ pango2_font_map_default_load_fontset (Pango2FontMap               *self,
   lookup.language = language;
   lookup.description = (Pango2FontDescription *)description;
   lookup.ctm = ctm;
+
 #ifdef HAVE_CAIRO
   lookup.font_options = (cairo_font_options_t *)pango2_cairo_context_get_merged_font_options (context);
 #endif
diff --git a/pango2/pango-fontset-cached.c b/pango2/pango-fontset-cached.c
index 9feaca795..37b80687c 100644
--- a/pango2/pango-fontset-cached.c
+++ b/pango2/pango-fontset-cached.c
@@ -26,7 +26,9 @@
 #include "pango-fontset-cached-private.h"
 #include "pango-font-private.h"
 #include "pango-font-face-private.h"
+#include "pango-font-description-private.h"
 #include "pango-generic-family-private.h"
+#include "pango-context.h"
 
 #ifdef HAVE_CAIRO
 #include "pangocairo-font.h"
@@ -81,6 +83,23 @@ find_font_for_face (Pango2FontsetCached *self,
   return NULL;
 }
 
+static Pango2Font *
+create_font_for_face (Pango2FontsetCached *self,
+                      Pango2FontFace      *face)
+{
+  Pango2Font *font;
+
+  font = pango2_font_face_create_font (face,
+                                       self->description,
+                                       self->dpi,
+                                       self->ctm);
+#ifdef HAVE_CAIRO
+  pango2_cairo_font_set_font_options (font, self->font_options);
+#endif
+
+  return font;
+}
+
 static Pango2Font *
 pango2_fontset_cached_get_font (Pango2Fontset *fontset,
                                 guint          wc)
@@ -129,19 +148,9 @@ pango2_fontset_cached_get_font (Pango2Fontset *fontset,
 
               font = find_font_for_face (self, face);
               if (font)
-                {
-                  retval = g_object_ref (font);
-                }
+                retval = g_object_ref (font);
               else
-                {
-                  retval = pango2_font_face_create_font (face,
-                                                         self->description,
-                                                         self->dpi,
-                                                         self->ctm);
-#ifdef HAVE_CAIRO
-                  pango2_cairo_font_set_font_options (retval, self->font_options);
-#endif
-                }
+                retval = create_font_for_face (self, face);
               break;
             }
         }
@@ -175,15 +184,7 @@ pango2_fontset_cached_get_first_font (Pango2FontsetCached *self)
       if (font)
         g_object_ref (font);
       else
-        {
-          font = pango2_font_face_create_font (face,
-                                               self->description,
-                                               self->dpi,
-                                               self->ctm);
-#ifdef HAVE_CAIRO
-          pango2_cairo_font_set_font_options (font, self->font_options);
-#endif
-        }
+        font = create_font_for_face (self, face);
 
       return font;
     }
@@ -249,15 +250,7 @@ pango2_fontset_cached_foreach (Pango2Fontset            *fontset,
           if (font)
             g_object_ref (font);
           else
-            {
-              font = pango2_font_face_create_font (face,
-                                                   self->description,
-                                                   self->dpi,
-                                                   self->ctm);
-#ifdef HAVE_CAIRO
-              pango2_cairo_font_set_font_options (font, self->font_options);
-#endif
-            }
+            font = create_font_for_face (self, face);
         }
 
       if ((*func) (fontset, font, data))
@@ -304,16 +297,7 @@ void
 pango2_fontset_cached_add_face (Pango2FontsetCached *self,
                                 Pango2FontFace      *face)
 {
-  Pango2Font *font;
-
-  font = pango2_font_face_create_font (face,
-                                       self->description,
-                                       self->dpi,
-                                       self->ctm);
-#ifdef HAVE_CAIRO
-  pango2_cairo_font_set_font_options (font, self->font_options);
-#endif
-  g_ptr_array_add (self->items, font);
+  g_ptr_array_add (self->items, create_font_for_face (self, face));
 }
 
 void
diff --git a/pango2/pango-hbface-private.h b/pango2/pango-hbface-private.h
index e29203887..8fbf6bf3c 100644
--- a/pango2/pango-hbface-private.h
+++ b/pango2/pango-hbface-private.h
@@ -25,6 +25,11 @@
 #include "pango-language-set-private.h"
 #include <hb.h>
 
+typedef struct {
+  GQuark name;
+  unsigned int index;
+} PaletteMapEntry;
+
 struct _Pango2HbFace
 {
   Pango2FontFace parent_instance;
@@ -41,6 +46,7 @@ struct _Pango2HbFace
   Pango2LanguageSet *languages;
   gboolean embolden;
   gboolean synthetic;
+  GArray *palettes;
 };
 
 Pango2LanguageSet *     pango2_hb_face_get_language_set  (Pango2HbFace          *self);
@@ -50,3 +56,6 @@ void                    pango2_hb_face_set_language_set  (Pango2HbFace
 
 void                    pango2_hb_face_set_matrix        (Pango2HbFace          *self,
                                                           const Pango2Matrix    *matrix);
+
+unsigned int            pango2_hb_face_get_palette_index (Pango2HbFace          *self,
+                                                          GQuark                 palette);
diff --git a/pango2/pango-hbface.c b/pango2/pango-hbface.c
index dbe68e176..61be8b388 100644
--- a/pango2/pango-hbface.c
+++ b/pango2/pango-hbface.c
@@ -48,6 +48,10 @@
 
  /* {{{ Utilities */
 
+static GQuark quark_default_palette;
+static GQuark quark_light_palette;
+static GQuark quark_dark_palette;
+
 static void
 get_name_from_hb_face (hb_face_t       *face,
                        hb_ot_name_id_t  name_id,
@@ -314,6 +318,8 @@ pango2_hb_face_finalize (GObject *object)
 {
   Pango2HbFace *self = PANGO2_HB_FACE (object);
 
+  if (self->palettes)
+    g_array_unref (self->palettes);
   g_free (self->faceid);
   if (self->face)
     hb_face_destroy (self->face);
@@ -483,6 +489,10 @@ pango2_hb_face_class_init (Pango2HbFaceClass *class)
   GObjectClass *object_class = G_OBJECT_CLASS (class);
   Pango2FontFaceClass *face_class = PANGO2_FONT_FACE_CLASS (class);
 
+  quark_default_palette = g_quark_from_static_string (PANGO2_COLOR_PALETTE_DEFAULT);
+  quark_light_palette = g_quark_from_static_string (PANGO2_COLOR_PALETTE_LIGHT);
+  quark_dark_palette = g_quark_from_static_string (PANGO2_COLOR_PALETTE_DARK);
+
   object_class->finalize = pango2_hb_face_finalize;
   object_class->get_property = pango2_hb_face_get_property;
 
@@ -627,6 +637,73 @@ pango2_hb_face_set_matrix (Pango2HbFace       *self,
   pango2_matrix_scale (self->transform, 1./self->x_scale, 1./self->y_scale);
 }
 
+static unsigned int
+find_palette_index_by_flag (hb_face_t                   *hbface,
+                            hb_ot_color_palette_flags_t  flag)
+{
+  unsigned int n_palettes;
+
+  n_palettes = hb_ot_color_palette_get_count (hbface);
+  for (unsigned int i = 0; i < n_palettes; i++)
+    {
+      if (hb_ot_color_palette_get_flags (hbface, i) & flag)
+        return i;
+    }
+
+  return 0;
+}
+
+unsigned int
+pango2_hb_face_get_palette_index (Pango2HbFace *self,
+                                  GQuark        palette)
+{
+  const char *name;
+
+  if (palette == 0)
+    return 0;
+
+  if (self->palettes)
+    {
+      for (unsigned int i = 0; i < self->palettes->len; i++)
+        {
+          PaletteMapEntry *entry = &g_array_index (self->palettes, PaletteMapEntry, i);
+
+          if (entry->name == palette)
+            return entry->index;
+        }
+    }
+
+  if (palette == quark_default_palette)
+    return 0;
+
+  ensure_hb_face (self);
+
+  if (!hb_ot_color_has_palettes (self->face))
+    return 0;
+
+  /* look for a name of the form "paletteN" */
+  name = g_quark_to_string (palette);
+  if (g_str_has_prefix (name, "palette") && g_ascii_isdigit (name[strlen ("palette")]))
+    {
+      const char *p;
+      char *end;
+      unsigned int index;
+
+      p = name + strlen ("palette");
+      index = (unsigned int) g_ascii_strtoull (p, &end, 10);
+      if (*end == '\0')
+        return index;
+    }
+
+  /* look for "light", "dark" */
+  if (palette == quark_light_palette)
+    return find_palette_index_by_flag (self->face, HB_OT_COLOR_PALETTE_FLAG_USABLE_WITH_LIGHT_BACKGROUND);
+  else if (palette == quark_dark_palette)
+    return find_palette_index_by_flag (self->face, HB_OT_COLOR_PALETTE_FLAG_USABLE_WITH_DARK_BACKGROUND);
+
+  return 0;
+}
+
 /* }}} */
   /* {{{ Public API */
 
@@ -877,6 +954,104 @@ pango2_hb_face_get_transform (Pango2HbFace *self)
   return self->transform;
 }
 
+static gboolean
+name_is_valid (const char *name)
+{
+  if (!name)
+    return FALSE;
+
+  /* First character must be a letter. */
+  if ((name[0] < 'A' || name[0] > 'Z') &&
+      (name[0] < 'a' || name[0] > 'z'))
+    return FALSE;
+
+  for (const char *p = name; *p != 0; p++)
+    {
+      const char c = *p;
+
+      if (c != '-' && c != '_' &&
+          (c < '0' || c > '9') &&
+          (c < 'A' || c > 'Z') &&
+          (c < 'a' || c > 'z'))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+/**
+ * pango2_hb_face_set_palette_name:
+ * @self: a `Pango2HbFace`
+ * @name: the palette name to set
+ * @index: index of the palette
+ *
+ * Sets up a palette name for one of the palettes of the `PangoHbFace`.
+ *
+ * After this call, using the name with a palette attribute
+ * or with [method@Pango2.Context.set_palette] will use the
+ * given palette index for @self.
+ */
+void
+pango2_hb_face_set_palette_name (Pango2HbFace *self,
+                                 const char   *name,
+                                 unsigned int  index)
+{
+  PaletteMapEntry add;
+
+  g_return_if_fail (PANGO2_IS_HB_FACE (self));
+  g_return_if_fail (name_is_valid (name));
+
+  add.name = g_quark_from_string (name);
+  add.index = index;
+
+  if (self->palettes == NULL)
+    self->palettes = g_array_new (FALSE, FALSE, sizeof (PaletteMapEntry));
+
+  for (int i = 0; i < self->palettes->len; i++)
+    {
+      PaletteMapEntry *entry = &g_array_index (self->palettes, PaletteMapEntry, i);
+      if (entry->index == add.index)
+        {
+          entry->name = add.name;
+          return;
+        }
+    }
+
+  g_array_append_val (self->palettes, add);
+}
+
+/**
+ * pango2_hb_face_get_palette_name:
+ * @self: a `PangoHbFace`
+ * @index: index of the palette
+ *
+ * Gets the name for the palette with the given index in @self.
+ *
+ * Note that this function only returns names that have been
+ * set up by a prior call of [method@Pang2.HbFace.set_palette_name].
+ * It does not return the predefined "paletteN" names.
+ *
+ * Returns: (nullable): the palette name
+ */
+const char *
+pango2_hb_face_get_palette_name (Pango2HbFace *self,
+                                 unsigned int  index)
+{
+  g_return_val_if_fail (PANGO2_IS_HB_FACE (self), NULL);
+
+  if (self->palettes == NULL)
+    return NULL;
+
+  for (int i = 0; i < self->palettes->len; i++)
+    {
+      PaletteMapEntry *entry = &g_array_index (self->palettes, PaletteMapEntry, i);
+      if (entry->index == index)
+        return g_quark_to_string (entry->name);
+    }
+
+  return NULL;
+}
+
 /* }}} */
 /* {{{ Pango2HbFaceBuilder */
 
@@ -1025,6 +1200,7 @@ pango2_hb_face_builder_new (Pango2HbFace *face)
   builder->n_variations = face->n_variations;
   builder->name = g_strdup (font_face->name);
   builder->description = pango2_font_description_copy_static (font_face->description);
+  pango2_font_description_unset_fields (builder->description, PANGO2_FONT_MASK_FACEID);
 
   return builder;
 }
@@ -1043,6 +1219,7 @@ pango2_hb_face_builder_get_face (Pango2HbFaceBuilder *builder)
   Pango2HbFace *self;
 
   self = g_object_new (PANGO2_TYPE_HB_FACE, NULL);
+
   if (builder->face)
     {
       self->file = g_strdup (builder->face->file);
@@ -1051,6 +1228,9 @@ pango2_hb_face_builder_get_face (Pango2HbFaceBuilder *builder)
       if (builder->face->face)
         self->face = hb_face_reference (builder->face->face);
       pango2_hb_face_set_language_set (self, builder->face->languages);
+
+      if (builder->face->palettes)
+        self->palettes = g_array_copy (builder->face->palettes);
     }
   else if (builder->hb_face)
     {
diff --git a/pango2/pango-hbface.h b/pango2/pango-hbface.h
index b110ff99e..ef7c6b731 100644
--- a/pango2/pango-hbface.h
+++ b/pango2/pango-hbface.h
@@ -66,6 +66,13 @@ gboolean                pango2_hb_face_get_embolden      (Pango2HbFace
 PANGO2_AVAILABLE_IN_ALL
 const Pango2Matrix *    pango2_hb_face_get_transform     (Pango2HbFace                *self);
 
+PANGO2_AVAILABLE_IN_ALL
+void                    pango2_hb_face_set_palette_name (Pango2HbFace                 *self,
+                                                         const char                   *name,
+                                                         unsigned int                  index);
+PANGO2_AVAILABLE_IN_ALL
+const char *            pango2_hb_face_get_palette_name  (Pango2HbFace                *self,
+                                                          unsigned int                 index);
 
 typedef struct _Pango2HbFaceBuilder Pango2HbFaceBuilder;
 
diff --git a/pango2/pango-hbfont.c b/pango2/pango-hbfont.c
index 91f4a305f..1bac38dec 100644
--- a/pango2/pango-hbfont.c
+++ b/pango2/pango-hbfont.c
@@ -215,7 +215,7 @@ count_variations (const char *string)
 
   n = 1;
   p = string;
-  while ((p = strchr (p, ',')) != NULL)
+  while ((p = strchr (p + 1, ',')) != NULL)
     n++;
 
   return n;
diff --git a/pango2/pango-markup.c b/pango2/pango-markup.c
index 72d2d2e84..91c9aded0 100644
--- a/pango2/pango-markup.c
+++ b/pango2/pango-markup.c
@@ -1341,6 +1341,7 @@ span_parse_func (MarkupData            *md G_GNUC_UNUSED,
   const char *gravity = NULL;
   const char *gravity_hint = NULL;
   const char *font_features = NULL;
+  const char *palette = NULL;
   const char *allow_breaks = NULL;
   const char *insert_hyphens = NULL;
   const char *show = NULL;
@@ -1406,6 +1407,10 @@ span_parse_func (MarkupData            *md G_GNUC_UNUSED,
 
         CHECK_ATTRIBUTE (font_features);
         break;
+      case 'p':
+        CHECK_ATTRIBUTE (palette);
+        break;
+
       case 's':
         CHECK_ATTRIBUTE (show);
         CHECK_ATTRIBUTE (size);
@@ -1841,6 +1846,11 @@ span_parse_func (MarkupData            *md G_GNUC_UNUSED,
       add_attribute (tag, pango2_attr_font_features_new (font_features));
     }
 
+  if (G_UNLIKELY (palette))
+    {
+      add_attribute (tag, pango2_attr_palette_new (palette));
+    }
+
   if (G_UNLIKELY (allow_breaks))
     {
       gboolean b = FALSE;
diff --git a/pango2/pango-types.h b/pango2/pango-types.h
index 56cf3dbfd..fb045f144 100644
--- a/pango2/pango-types.h
+++ b/pango2/pango-types.h
@@ -319,6 +319,29 @@ typedef enum
  */
 #define PANGO2_LEADING_TRIM_BOTH (PANGO2_LEADING_TRIM_START|PANGO2_LEADING_TRIM_END)
 
+/**
+ * PANGO2_COLOR_PALETTE_DEFAULT:
+ *
+ * The name for the default color palette.
+ */
+#define PANGO2_COLOR_PALETTE_DEFAULT "default"
+
+/**
+ * PANGO2_COLOR_PALETTE_LIGHT:
+ *
+ * The name for a color palette suitable for use on
+ * a light background.
+ */
+#define PANGO2_COLOR_PALETTE_LIGHT  "light"
+
+/**
+ * PANGO2_COLOR_PALETTE_DARK:
+ *
+ * The name for a color palette suitable for use on
+ * a dark background.
+ */
+#define PANGO2_COLOR_PALETTE_DARK   "dark"
+
 /*
  * PANGO2_DECLARE_INTERNAL_TYPE:
  * @ModuleObjName: The name of the new type, in camel case (like GtkWidget)
diff --git a/pango2/pangocairo-font.c b/pango2/pangocairo-font.c
index b9103d977..72c06bdb1 100644
--- a/pango2/pangocairo-font.c
+++ b/pango2/pangocairo-font.c
@@ -24,6 +24,8 @@
 #include <math.h>
 #include <string.h>
 
+#include <hb-ot.h>
+
 #include "pangocairo.h"
 #include "pangocairo-private.h"
 #include "pango-font-private.h"
@@ -36,7 +38,8 @@
 #include "pango-font-private.h"
 
 static Pango2CairoFontPrivate * _pango2_font_get_cairo_font_private (Pango2Font *font);
-static cairo_scaled_font_t * _pango2_font_get_scaled_font (Pango2Font *font);
+static cairo_scaled_font_t * _pango2_font_get_scaled_font (Pango2Font *font,
+                                                           GQuark      palette);
 static void _pango2_cairo_font_private_initialize (Pango2CairoFontPrivate     *cf_priv,
                                                    Pango2Font                 *font,
                                                    Pango2Gravity               gravity,
@@ -92,32 +95,74 @@ create_cairo_font_face (Pango2Font *font)
   return NULL;
 }
 
+#ifdef CAIRO_COLOR_PALETTE_DEFAULT
+
+static int
+find_palette_index_for_font (Pango2Font *font,
+                             GQuark      palette)
+{
+  Pango2FontFace *face = pango2_font_get_face (font);
+
+  if (PANGO2_IS_HB_FACE (face))
+    return pango2_hb_face_get_palette_index (PANGO2_HB_FACE (face), palette);
+
+  return CAIRO_COLOR_PALETTE_DEFAULT;
+}
+
+#endif
+
 static cairo_scaled_font_t *
-_pango2_cairo_font_private_get_scaled_font (Pango2CairoFontPrivate *cf_priv)
+_pango2_cairo_font_private_get_scaled_font (Pango2CairoFontPrivate *cf_priv,
+                                            GQuark                  palette)
 {
   cairo_font_face_t *font_face;
+  cairo_font_options_t *options;
+  cairo_matrix_t _font_matrix, *font_matrix;
+  cairo_matrix_t _ctm, *ctm;
 
-  if (G_LIKELY (cf_priv->scaled_font))
+  if (G_LIKELY (cf_priv->palette == palette && cf_priv->scaled_font))
     return cf_priv->scaled_font;
 
+  if (!cf_priv->data && !cf_priv->scaled_font)
+    return NULL;
+
   /* need to create it */
 
-  if (G_UNLIKELY (cf_priv->data == NULL))
+  if (cf_priv->scaled_font)
     {
-      /* we have tried to create and failed before */
-      return NULL;
+      font_face = cairo_scaled_font_get_font_face (cf_priv->scaled_font);
+      cairo_font_face_reference (font_face);
+
+      options = cairo_font_options_create ();
+      cairo_scaled_font_get_font_options (cf_priv->scaled_font, options);
+      cairo_scaled_font_get_font_matrix (cf_priv->scaled_font, &_font_matrix);
+      font_matrix = &_font_matrix;
+      cairo_scaled_font_get_ctm (cf_priv->scaled_font, &_ctm);
+      ctm = &_ctm;
+
+      cairo_scaled_font_destroy (cf_priv->scaled_font);
+      cf_priv->scaled_font = NULL;
+    }
+  else
+    {
+      font_face = create_cairo_font_face (cf_priv->cfont);
+      options = cairo_font_options_copy (cf_priv->data->options);
+      font_matrix = &cf_priv->data->font_matrix;
+      ctm = &cf_priv->data->ctm;
     }
-
-  font_face = create_cairo_font_face (cf_priv->cfont);
 
   if (G_UNLIKELY (font_face == NULL))
     goto done;
 
-  cf_priv->scaled_font = cairo_scaled_font_create (font_face,
-                                                   &cf_priv->data->font_matrix,
-                                                   &cf_priv->data->ctm,
-                                                   cf_priv->data->options);
+#ifdef CAIRO_COLOR_PALETTE_DEFAULT
+  cairo_font_options_set_color_palette (options,
+                                        find_palette_index_for_font (cf_priv->cfont, palette));
+#endif
 
+  cf_priv->palette = palette;
+  cf_priv->scaled_font = cairo_scaled_font_create (font_face, font_matrix, ctm, options);
+
+  cairo_font_options_destroy (options);
   cairo_font_face_destroy (font_face);
 
 done:
@@ -168,8 +213,9 @@ done:
   return cf_priv->scaled_font;
 }
 
-cairo_scaled_font_t *
-_pango2_font_get_scaled_font (Pango2Font *font)
+static cairo_scaled_font_t *
+_pango2_font_get_scaled_font (Pango2Font *font,
+                              GQuark      palette)
 {
   Pango2CairoFontPrivate *cf_priv;
 
@@ -178,12 +224,13 @@ _pango2_font_get_scaled_font (Pango2Font *font)
   if (G_UNLIKELY (!cf_priv))
     return NULL;
 
-  return _pango2_cairo_font_private_get_scaled_font (cf_priv);
+  return _pango2_cairo_font_private_get_scaled_font (cf_priv, palette);
 }
 
 /**
  * _pango2_cairo_font_install:
  * @font: a `Pango2CairoFont`
+ * @palette: a palette
  * @cr: a #cairo_t
  *
  * Makes @font the current font for rendering in the specified
@@ -193,11 +240,12 @@ _pango2_font_get_scaled_font (Pango2Font *font)
  */
 gboolean
 _pango2_cairo_font_install (Pango2Font *font,
+                            GQuark      palette,
                             cairo_t    *cr)
 {
   cairo_scaled_font_t *scaled_font;
 
-  scaled_font = _pango2_font_get_scaled_font (font);
+  scaled_font = _pango2_font_get_scaled_font (font, palette);
 
   if (G_UNLIKELY (scaled_font == NULL || cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS))
     return FALSE;
@@ -239,7 +287,7 @@ _pango2_cairo_font_private_get_hex_box_info (Pango2CairoFontPrivate *cf_priv)
   if (cf_priv->hbi)
     return cf_priv->hbi;
 
-  scaled_font = _pango2_cairo_font_private_get_scaled_font (cf_priv);
+  scaled_font = _pango2_cairo_font_private_get_scaled_font (cf_priv, 0);
   if (G_UNLIKELY (scaled_font == NULL || cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS))
     return NULL;
 
@@ -352,7 +400,7 @@ _pango2_cairo_font_private_get_hex_box_info (Pango2CairoFontPrivate *cf_priv)
   pango2_font_description_free (desc);
   cairo_font_options_destroy (font_options);
 
-  scaled_mini_font = _pango2_font_get_scaled_font (mini_font);
+  scaled_mini_font = _pango2_font_get_scaled_font (mini_font, 0);
   if (G_UNLIKELY (scaled_mini_font == NULL || cairo_scaled_font_status (scaled_mini_font) != 
CAIRO_STATUS_SUCCESS))
     return NULL;
 
diff --git a/pango2/pangocairo-private.h b/pango2/pangocairo-private.h
index be9fa8cf9..0ff05b98d 100644
--- a/pango2/pangocairo-private.h
+++ b/pango2/pangocairo-private.h
@@ -41,6 +41,7 @@ struct _Pango2CairoFontPrivate
 
   Pango2CairoFontPrivateScaledFontData *data;
 
+  GQuark palette;
   cairo_scaled_font_t *scaled_font;
   Pango2CairoFontHexBoxInfo *hbi;
 
@@ -50,9 +51,11 @@ struct _Pango2CairoFontPrivate
   Pango2Rectangle font_extents;
 };
 
-gboolean _pango2_cairo_font_install (Pango2Font *font,
-                                     cairo_t    *cr);
-Pango2CairoFontHexBoxInfo *_pango2_cairo_font_get_hex_box_info (Pango2Font *font);
+gboolean                _pango2_cairo_font_install                      (Pango2Font     *font,
+                                                                         GQuark          palette,
+                                                                         cairo_t        *cr);
+Pango2CairoFontHexBoxInfo *
+                        _pango2_cairo_font_get_hex_box_info             (Pango2Font     *font);
 
 #define PANGO2_TYPE_CAIRO_RENDERER            (pango2_cairo_renderer_get_type())
 #define PANGO2_CAIRO_RENDERER(object)         (G_TYPE_CHECK_INSTANCE_CAST ((object), 
PANGO2_TYPE_CAIRO_RENDERER, Pango2CairoRenderer))
@@ -61,27 +64,23 @@ Pango2CairoFontHexBoxInfo *_pango2_cairo_font_get_hex_box_info (Pango2Font *font
 typedef struct _Pango2CairoRenderer Pango2CairoRenderer;
 
 _PANGO2_EXTERN
-GType pango2_cairo_renderer_get_type    (void) G_GNUC_CONST;
+GType                   pango2_cairo_renderer_get_type                  (void) G_GNUC_CONST;
 
 const cairo_font_options_t *
-         pango2_cairo_context_get_merged_font_options (Pango2Context *context);
+                        pango2_cairo_context_get_merged_font_options    (Pango2Context  *context);
 
-cairo_font_face_t *
-create_cairo_user_font_face (Pango2Font *font);
+cairo_font_face_t *     create_cairo_user_font_face                     (Pango2Font     *font);
 
 #ifdef CAIRO_HAS_FT_FONT
-cairo_font_face_t *
-create_cairo_ft_font_face (Pango2Font *font);
+cairo_font_face_t *     create_cairo_ft_font_face                       (Pango2Font     *font);
 #endif
 
 #ifdef HAVE_CORE_TEXT
-cairo_font_face_t *
-create_cairo_core_text_font_face (Pango2Font *font);
+cairo_font_face_t *     create_cairo_core_text_font_face                (Pango2Font     *font);
 #endif
 
 #ifdef HAVE_DIRECT_WRITE
-cairo_font_face_t *
-create_cairo_dwrite_font_face (Pango2Font *font);
+cairo_font_face_t *     create_cairo_dwrite_font_face                   (Pango2Font     *font);
 #endif
 
 G_END_DECLS
diff --git a/pango2/pangocairo-render.c b/pango2/pangocairo-render.c
index e4cfefa1b..fd0460734 100644
--- a/pango2/pangocairo-render.c
+++ b/pango2/pangocairo-render.c
@@ -31,6 +31,9 @@
 #include "pango-run-private.h"
 #include "pango-impl-utils.h"
 #include "pango-hbfont-private.h"
+#include "pango-attr-private.h"
+#include "pango-context-private.h"
+
 
 typedef struct _Pango2CairoRendererClass Pango2CairoRendererClass;
 
@@ -46,6 +49,7 @@ struct _Pango2CairoRenderer
   gboolean do_path;
   gboolean has_show_text_glyphs;
   double x_offset, y_offset;
+  GQuark palette;
 
   /* house-keeping options */
   gboolean is_cached_renderer;
@@ -253,7 +257,7 @@ _pango2_cairo_renderer_draw_unknown_glyph (Pango2CairoRenderer *crenderer,
     hbi = PANGO2_HB_FONT (font)->hex_box_info;
   else
     hbi = _pango2_cairo_font_get_hex_box_info (font);
-  if (!hbi || !_pango2_cairo_font_install ((Pango2Font *)(hbi->font), crenderer->cr))
+  if (!hbi || !_pango2_cairo_font_install ((Pango2Font *)(hbi->font), 0, crenderer->cr))
     {
       _pango2_cairo_renderer_draw_box_glyph (crenderer, gi, cx, cy, invalid_input);
       goto done;
@@ -413,11 +417,11 @@ pango2_cairo_renderer_show_text_glyphs (Pango2Renderer       *renderer,
                                         int                   num_clusters,
                                         gboolean              backward,
                                         Pango2Font           *font,
+                                        GQuark                palette,
                                         int                   x,
                                         int                   y)
 {
   Pango2CairoRenderer *crenderer = (Pango2CairoRenderer *) (renderer);
-
   int i, count;
   int x_position = 0;
   cairo_glyph_t *cairo_glyphs;
@@ -429,7 +433,7 @@ pango2_cairo_renderer_show_text_glyphs (Pango2Renderer       *renderer,
   if (!crenderer->do_path)
     set_color (crenderer, PANGO2_RENDER_PART_FOREGROUND);
 
-  if (!_pango2_cairo_font_install (font, crenderer->cr))
+  if (!_pango2_cairo_font_install (font, palette, crenderer->cr))
     {
       for (i = 0; i < glyphs->num_glyphs; i++)
         {
@@ -511,7 +515,8 @@ pango2_cairo_renderer_draw_glyphs (Pango2Renderer     *renderer,
                                    int                 x,
                                    int                 y)
 {
-  pango2_cairo_renderer_show_text_glyphs (renderer, NULL, 0, glyphs, NULL, 0, FALSE, font, x, y);
+  Pango2CairoRenderer *crenderer = (Pango2CairoRenderer *) (renderer);
+  pango2_cairo_renderer_show_text_glyphs (renderer, NULL, 0, glyphs, NULL, 0, FALSE, font, 
crenderer->palette, x, y);
 }
 
 static void
@@ -534,7 +539,7 @@ pango2_cairo_renderer_draw_run (Pango2Renderer *renderer,
 
   if (!crenderer->has_show_text_glyphs || crenderer->do_path)
     {
-      pango2_cairo_renderer_show_text_glyphs (renderer, NULL, 0, glyphs, NULL, 0, FALSE, font, x, y);
+      pango2_cairo_renderer_show_text_glyphs (renderer, NULL, 0, glyphs, NULL, 0, FALSE, font, 
crenderer->palette, x, y);
       return;
     }
 
@@ -581,6 +586,7 @@ pango2_cairo_renderer_draw_run (Pango2Renderer *renderer,
                                           cairo_clusters, num_clusters,
                                           backward,
                                           font,
+                                          crenderer->palette,
                                           x, y);
 
   if (cairo_clusters != stack_clusters)
@@ -816,6 +822,35 @@ pango2_cairo_renderer_draw_styled_line (Pango2Renderer   *renderer,
     }
 }
 
+static GQuark
+find_palette (Pango2Context *context,
+              Pango2Item    *item)
+{
+  GSList *l;
+
+  for (l = item->analysis.extra_attrs; l; l = l->next)
+    {
+      Pango2Attribute *attr = l->data;
+
+      if (attr->type == PANGO2_ATTR_PALETTE)
+        return g_quark_from_string (attr->str_value);
+    }
+
+  return context->palette;
+}
+
+static void
+pango2_cairo_renderer_prepare_run (Pango2Renderer *renderer,
+                                   Pango2Run      *run)
+{
+  Pango2CairoRenderer *crenderer = (Pango2CairoRenderer *) (renderer);
+
+  PANGO2_RENDERER_CLASS (pango2_cairo_renderer_parent_class)->prepare_run (renderer, run);
+
+  crenderer->palette = find_palette (pango2_renderer_get_context (renderer),
+                                     pango2_run_get_item (run));
+}
+
 static void
 pango2_cairo_renderer_init (Pango2CairoRenderer *renderer G_GNUC_UNUSED)
 {
@@ -831,6 +866,7 @@ pango2_cairo_renderer_class_init (Pango2CairoRendererClass *klass)
   renderer_class->draw_rectangle = pango2_cairo_renderer_draw_rectangle;
   renderer_class->draw_styled_line = pango2_cairo_renderer_draw_styled_line;
   renderer_class->draw_trapezoid = pango2_cairo_renderer_draw_trapezoid;
+  renderer_class->prepare_run = pango2_cairo_renderer_prepare_run;
 }
 
 static Pango2CairoRenderer *cached_renderer = NULL; /* MT-safe */
@@ -903,6 +939,7 @@ restore_current_point (Pango2CairoRenderer *renderer)
 static void
 _pango2_cairo_do_glyph_string (cairo_t           *cr,
                                Pango2Font        *font,
+                               GQuark             palette,
                                Pango2GlyphString *glyphs,
                                gboolean           do_path)
 {
@@ -911,6 +948,7 @@ _pango2_cairo_do_glyph_string (cairo_t           *cr,
 
   crenderer->cr = cr;
   crenderer->do_path = do_path;
+  crenderer->palette = palette;
   save_current_point (crenderer);
 
   if (!do_path)
@@ -1057,7 +1095,32 @@ pango2_cairo_show_glyph_string (cairo_t           *cr,
   g_return_if_fail (cr != NULL);
   g_return_if_fail (glyphs != NULL);
 
-  _pango2_cairo_do_glyph_string (cr, font, glyphs, FALSE);
+  _pango2_cairo_do_glyph_string (cr, font, 0, glyphs, FALSE);
+}
+
+/**
+ * pango2_cairo_show_color_glyph_string:
+ * @cr: a Cairo context
+ * @font: a `Pango2Font` from a `Pango2CairoFontMap`
+ * @palette: a palette name, as quark
+ * @glyphs: a `Pango2GlyphString`
+ *
+ * Draws the glyphs in @glyphs in the specified cairo context,
+ * and with the given palette name.
+ *
+ * This is a variation of [func@Pango2.cairo_show_glyph_string]
+ * for use with fonts that have color palettes.
+ */
+void
+pango2_cairo_show_color_glyph_string (cairo_t           *cr,
+                                      Pango2Font        *font,
+                                      GQuark             palette,
+                                      Pango2GlyphString *glyphs)
+{
+  g_return_if_fail (cr != NULL);
+  g_return_if_fail (glyphs != NULL);
+
+  _pango2_cairo_do_glyph_string (cr, font, palette, glyphs, FALSE);
 }
 
 /**
@@ -1169,7 +1232,7 @@ pango2_cairo_glyph_string_path (cairo_t           *cr,
   g_return_if_fail (cr != NULL);
   g_return_if_fail (glyphs != NULL);
 
-  _pango2_cairo_do_glyph_string (cr, font, glyphs, TRUE);
+  _pango2_cairo_do_glyph_string (cr, font, 0, glyphs, TRUE);
 }
 
 /**
diff --git a/pango2/pangocairo-render.h b/pango2/pangocairo-render.h
index fb40fba90..e36a41001 100644
--- a/pango2/pangocairo-render.h
+++ b/pango2/pangocairo-render.h
@@ -28,6 +28,11 @@ PANGO2_AVAILABLE_IN_ALL
 void    pango2_cairo_show_glyph_string           (cairo_t           *cr,
                                                   Pango2Font        *font,
                                                   Pango2GlyphString *glyphs);
+PANGO2_AVAILABLE_IN_ALL
+void    pango2_cairo_show_color_glyph_string     (cairo_t           *cr,
+                                                  Pango2Font        *font,
+                                                  GQuark             palette,
+                                                  Pango2GlyphString *glyphs);
 
 PANGO2_AVAILABLE_IN_ALL
 void    pango2_cairo_show_run                    (cairo_t           *cr,
diff --git a/pango2/serializer.c b/pango2/serializer.c
index 7f719a06b..03ffb4cd8 100644
--- a/pango2/serializer.c
+++ b/pango2/serializer.c
@@ -1053,6 +1053,12 @@ attr_for_type (GtkJsonParser *parser,
       g_free (str);
       break;
 
+    case PANGO2_ATTR_PALETTE:
+      str = gtk_json_parser_get_string (parser);
+      attr = pango2_attr_palette_new (str);
+      g_free (str);
+      break;
+
     case PANGO2_ATTR_ALLOW_BREAKS:
       attr = pango2_attr_allow_breaks_new (gtk_json_parser_get_boolean (parser));
       break;
diff --git a/tests/test-font.c b/tests/test-font.c
index dd3ef1b54..56a120465 100644
--- a/tests/test-font.c
+++ b/tests/test-font.c
@@ -499,7 +499,39 @@ test_set_gravity (void)
 static void
 test_faceid (void)
 {
-  const char *test = "Cantarell Bold Italic 32 @faceid=Cantarell-Regular:0:-1:0,wght=600";
+  const char *test = "Cantarell Bold Italic 32 @faceid=Cantarell-Regular:0:-1:0 @wght=600";
+  Pango2FontDescription *desc;
+  char *s;
+
+  desc = pango2_font_description_from_string (test);
+  g_assert_cmpint (pango2_font_description_get_set_fields (desc), ==, PANGO2_FONT_MASK_FAMILY|
+                                                                     PANGO2_FONT_MASK_STYLE|
+                                                                     PANGO2_FONT_MASK_WEIGHT|
+                                                                     PANGO2_FONT_MASK_VARIANT|
+                                                                     PANGO2_FONT_MASK_STRETCH|
+                                                                     PANGO2_FONT_MASK_SIZE|
+                                                                     PANGO2_FONT_MASK_FACEID|
+                                                                     PANGO2_FONT_MASK_VARIATIONS);
+  g_assert_cmpstr (pango2_font_description_get_family (desc), ==, "Cantarell");
+  g_assert_cmpint (pango2_font_description_get_size (desc), ==, 32 * PANGO2_SCALE);
+  g_assert_cmpint (pango2_font_description_get_style (desc), ==, PANGO2_STYLE_ITALIC);
+  g_assert_cmpint (pango2_font_description_get_variant (desc), ==, PANGO2_VARIANT_NORMAL);
+  g_assert_cmpint (pango2_font_description_get_weight (desc), ==, PANGO2_WEIGHT_BOLD);
+  g_assert_cmpint (pango2_font_description_get_stretch (desc), ==, PANGO2_STRETCH_NORMAL);
+  g_assert_cmpstr (pango2_font_description_get_faceid (desc), ==, "Cantarell-Regular:0:-1:0");
+  g_assert_cmpstr (pango2_font_description_get_variations (desc), ==, "wght=600");
+
+  s = pango2_font_description_to_string (desc);
+  g_assert_cmpstr (s, ==, test);
+  g_free (s);
+
+  pango2_font_description_free (desc);
+}
+
+static void
+test_all (void)
+{
+  const char *test = "Cantarell Bold Italic 32 @faceid=Cantarell-Regular:0:-1:0 @wght=600";
   Pango2FontDescription *desc;
   char *s;
 
@@ -657,6 +689,7 @@ main (int argc, char *argv[])
   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/fontdescription/all", test_all);
   g_test_add_func ("/pango/font/metrics", test_metrics);
   g_test_add_func ("/pango/font/extents", test_extents);
   g_test_add_func ("/pango/font/glyph-extents", test_glyph_extents);
diff --git a/utils/viewer-pangocairo.c b/utils/viewer-pangocairo.c
index d51b20552..b6e906a8a 100644
--- a/utils/viewer-pangocairo.c
+++ b/utils/viewer-pangocairo.c
@@ -34,6 +34,7 @@
 static int opt_annotate = 0;
 static gboolean opt_userfont = 0;
 static char **opt_font_file = NULL;
+static char *opt_palette = NULL;
 
 typedef struct
 {
@@ -44,6 +45,7 @@ typedef struct
   Pango2FontMap *fontmap;
   cairo_font_options_t *font_options;
   gboolean subpixel_positions;
+  const char *palette;
 } CairoViewer;
 
 static gpointer
@@ -66,8 +68,8 @@ pangocairo_view_create (const Pango2Viewer *klass G_GNUC_UNUSED)
           Pango2FontFace *face;
 
           face = PANGO2_FONT_FACE (pango2_hb_face_new_from_file (opt_font_file[i],
-                                                               0, -1,
-                                                               NULL, NULL));
+                                                                 0, -1,
+                                                                 NULL, NULL));
 
           pango2_font_map_add_face (instance->fontmap, face);
 
@@ -119,6 +121,7 @@ pangocairo_view_create (const Pango2Viewer *klass G_GNUC_UNUSED)
     cairo_font_options_set_antialias (instance->font_options, (cairo_antialias_t)opt_antialias);
 
   instance->subpixel_positions = opt_subpixel_positions;
+  instance->palette = opt_palette;
 
   return instance;
 }
@@ -146,6 +149,7 @@ pangocairo_view_get_context (gpointer instance)
   context = pango2_context_new_with_font_map (c->fontmap);
   pango2_cairo_context_set_font_options (context, c->font_options);
   pango2_context_set_round_glyph_positions (context, !c->subpixel_positions);
+  pango2_context_set_palette (context, c->palette);
 
   return context;
 }
@@ -973,6 +977,7 @@ pangocairo_view_get_option_group (const Pango2Viewer *klass G_GNUC_UNUSED)
     {"annotate", 0, 0, G_OPTION_ARG_CALLBACK, parse_annotate_arg, annotate_arg_help, "FLAGS"},
     { "font-file", 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &opt_font_file, "Create a fontmap with this font", 
"FILE" },
     { "userfont", 0, 0, G_OPTION_ARG_NONE, &opt_userfont, "Add userfont" },
+    { "palette", 0, 0, G_OPTION_ARG_STRING, &opt_palette, "Preferred palette", "PALETTE" },
     {NULL}
   };
   GOptionGroup *group;


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