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




commit e5575e5b581ee88c8a184d2072baf129f057471a
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. Wire
    this through PangoFontSetCached and PangoFont, and
    apply in when we create a scaled font for cairo.
    
    Also add a palette field to PangoFontDescription,
    and treat it similarly.
    
    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            |  10 +-
 pango2/pango-attr-list.c                |  11 ++
 pango2/pango-attributes.c               |  23 +++-
 pango2/pango-attributes.h               |   3 +
 pango2/pango-color-palette-private.h    |  25 ++++
 pango2/pango-context-private.h          |   2 +
 pango2/pango-context.c                  |  78 +++++++++++-
 pango2/pango-context.h                  |   8 ++
 pango2/pango-font-description-private.h |   2 +
 pango2/pango-font-description.c         | 204 ++++++++++++++++++++++++--------
 pango2/pango-font-description.h         |   7 ++
 pango2/pango-font-private.h             |  14 ++-
 pango2/pango-fontmap.c                  |   7 +-
 pango2/pango-fontset-cached-private.h   |   1 +
 pango2/pango-fontset-cached.c           |  69 +++++------
 pango2/pango-hbface-private.h           |  10 ++
 pango2/pango-hbface.c                   | 177 +++++++++++++++++++++++++++
 pango2/pango-hbface.h                   |   7 ++
 pango2/pango-hbfont.c                   |   2 +-
 pango2/pango-markup.c                   |  10 ++
 pango2/pango-types.h                    |  23 ++++
 pango2/pangocairo-font.c                |  30 ++++-
 pango2/serializer.c                     |   6 +
 tests/test-font.c                       |  70 ++++++++++-
 utils/viewer-pangocairo.c               |   9 +-
 26 files changed, 704 insertions(+), 107 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..efc308129 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,
@@ -397,6 +396,13 @@ pango2_attr_iterator_get_font (Pango2AttrIterator     *iterator,
               pango2_font_description_set_absolute_size (desc, attr->int_value);
             }
           break;
+        case PANGO2_ATTR_PALETTE:
+          if (!(mask & PANGO2_FONT_MASK_PALETTE))
+            {
+              mask |= PANGO2_FONT_MASK_PALETTE;
+              pango2_font_description_set_palette (desc, attr->str_value);
+            }
+          break;
         case PANGO2_ATTR_SCALE:
           if (!have_scale)
             {
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..22354a992 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 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..f6931071f 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),
@@ -244,6 +246,7 @@ PANGO2_AVAILABLE_IN_ALL
 Pango2Attribute *        pango2_attr_gravity_hint_new             (Pango2GravityHint            hint);
 PANGO2_AVAILABLE_IN_ALL
 Pango2Attribute *        pango2_attr_font_features_new            (const char                  *features);
+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
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..70c879f01 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,54 @@ 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 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.
+ */
+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 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..2f615a6e1 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 */
@@ -733,6 +736,8 @@ pango2_font_description_merge_static (Pango2FontDescription       *desc,
     pango2_font_description_set_variations_static (desc, desc_to_merge->variations);
   if (new_mask & PANGO2_FONT_MASK_FACEID)
     pango2_font_description_set_faceid_static (desc, desc_to_merge->faceid);
+  if (new_mask & PANGO2_FONT_MASK_PALETTE)
+    desc->palette = desc_to_merge->palette;
 
   desc->mask |= new_mask;
 }
@@ -872,7 +877,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 +930,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 +1191,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 +1248,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 +1256,31 @@ faceid_from_variations (Pango2FontDescription *desc)
  *
  * The string must have the form
  *
- *     "\[FAMILY-LIST] \[STYLE-OPTIONS] \[SIZE] \[VARIATIONS]",
+ *     "\[FAMILY-LIST] \[STYLE-OPTIONS] \[SIZE] \[VARIATIONS] \[FACEID] \[PALETTE]",
  *
  * 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".
+ *
+ * PALETTE must have the form "\@palette=name" with the literal string
+ * "palette", and the name of a color palette. The name can either be
+ * one of the standard names "default", "light" or "dark", a name referring
+ * to a palette by index ("palette0", "palette1", …) or a custom
+ * name. See [method@Pango2.HbFace.new_synthetic] for information on
+ * how to associate custom names with palettes in the font.
+ *
+ * The VARIATION, FACEID and PALETTE 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 +1333,39 @@ 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 palettes, faceid and variations at the end of the string */
+      if (parse_palette (p, wordlen, &desc->palette))
+        {
+          desc->mask |= PANGO2_FONT_MASK_PALETTE;
+          last = p;
+        }
+      else 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_PALETTE |
+                        PANGO2_FONT_MASK_FACEID |
+                        PANGO2_FONT_MASK_VARIATIONS)) !=
+                       (PANGO2_FONT_MASK_PALETTE |
+                        PANGO2_FONT_MASK_FACEID |
+                        PANGO2_FONT_MASK_VARIATIONS));
 
   p = getword (str, last, &wordlen, ",");
   /* Look for a size */
@@ -1428,7 +1478,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,18 +1537,20 @@ 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);
     }
 
+  if (desc->mask & PANGO2_FONT_MASK_PALETTE)
+    {
+      g_string_append (result, " @");
+      g_string_append_printf (result, "palette=%s", g_quark_to_string (desc->palette));
+    }
+
   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 +1792,58 @@ pango2_font_description_get_faceid (const Pango2FontDescription *desc)
 
   return desc->faceid;
 }
+
+/**
+ * pango2_font_description_set_palette:
+ * @palette: the name of the palette
+ *
+ * Sets the palette field of a font description.
+ *
+ * Predefined palette names are "default", "light" and "dark", and
+ * names referring to a palette by index ("palette0", "palette1", …).
+ *
+ * Other names can be associated with palettes when constructing
+ * [class@Pango2.HbFace] objects.
+ */
+void
+pango2_font_description_set_palette (Pango2FontDescription *desc,
+                                     const char            *palette)
+{
+  g_return_if_fail (desc != NULL);
+
+  desc->palette = g_quark_from_string (palette);
+  desc->mask |= PANGO2_FONT_MASK_PALETTE;
+}
+
+/**
+ * pango2_font_description_get_palette:
+ * @desc: a `Pango2FontDescription`
+ *
+ * Gets the palette field of a font description.
+ *
+ * Return value: (nullable): the palette field of the font description
+ */
+const char *
+pango2_font_description_get_palette (const Pango2FontDescription *desc)
+{
+  g_return_val_if_fail (desc != NULL, NULL);
+
+  if (desc->palette)
+    return g_quark_to_string (desc->palette);
+
+  return NULL;
+}
+
+/*< 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-description.h b/pango2/pango-font-description.h
index 160ee50dc..a8fbcc458 100644
--- a/pango2/pango-font-description.h
+++ b/pango2/pango-font-description.h
@@ -171,6 +171,7 @@ typedef enum {
   PANGO2_FONT_MASK_GRAVITY    = 1 << 6,
   PANGO2_FONT_MASK_VARIATIONS = 1 << 7,
   PANGO2_FONT_MASK_FACEID     = 1 << 8,
+  PANGO2_FONT_MASK_PALETTE    = 1 << 9,
 } Pango2FontMask;
 
 /* CSS scale factors (1.2 factor between each size) */
@@ -298,6 +299,12 @@ void                    pango2_font_description_set_faceid_static (Pango2FontDes
 PANGO2_AVAILABLE_IN_ALL
 const char *            pango2_font_description_get_faceid        (const Pango2FontDescription *desc) 
G_GNUC_PURE;
 
+PANGO2_AVAILABLE_IN_ALL
+void                    pango2_font_description_set_palette       (Pango2FontDescription *desc,
+                                                                   const char            *palette);
+PANGO2_AVAILABLE_IN_ALL
+const char *            pango2_font_description_get_palette       (const Pango2FontDescription *desc) 
G_GNUC_PURE;
+
 PANGO2_AVAILABLE_IN_ALL
 Pango2FontMask          pango2_font_description_get_set_fields    (const Pango2FontDescription *desc) 
G_GNUC_PURE;
 PANGO2_AVAILABLE_IN_ALL
diff --git a/pango2/pango-font-private.h b/pango2/pango-font-private.h
index ca8d3a775..4278be5f5 100644
--- a/pango2/pango-font-private.h
+++ b/pango2/pango-font-private.h
@@ -42,6 +42,7 @@ struct _Pango2Font
   Pango2Gravity gravity;
   Pango2Matrix ctm;
 
+  GQuark palette;
 #ifdef HAVE_CAIRO
   cairo_font_options_t *options;
 #endif
@@ -107,6 +108,13 @@ pango2_font_set_ctm (Pango2Font         *font,
   font->ctm = ctm ? *ctm : matrix_init;
 }
 
+static inline void
+pango2_font_set_color_palette (Pango2Font *font,
+                               GQuark      palette)
+{
+  font->palette = palette;
+}
+
 gboolean pango2_font_is_hinted         (Pango2Font  *font);
 void     pango2_font_get_scale_factors (Pango2Font  *font,
                                         double      *x_scale,
@@ -114,12 +122,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..07fef32a3 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"
@@ -167,6 +167,7 @@ pango2_fontset_cached_hash (const Pango2FontsetCached *fontset)
 
   return (hash ^
           GPOINTER_TO_UINT (fontset->language) ^
+          fontset->palette ^
 #ifdef HAVE_CAIRO
           cairo_font_options_hash (fontset->font_options) ^
 #endif
@@ -178,6 +179,7 @@ pango2_fontset_cached_equal (const Pango2FontsetCached *a,
                              const Pango2FontsetCached *b)
 {
   return a->language == b->language &&
+         a->palette == b->palette &&
 #ifdef HAVE_CAIRO
          cairo_font_options_equal (a->font_options, b->font_options) &&
 #endif
@@ -553,6 +555,8 @@ pango2_font_map_default_load_fontset (Pango2FontMap               *self,
   lookup.language = language;
   lookup.description = (Pango2FontDescription *)description;
   lookup.ctm = ctm;
+  lookup.palette = context->palette;
+
 #ifdef HAVE_CAIRO
   lookup.font_options = (cairo_font_options_t *)pango2_cairo_context_get_merged_font_options (context);
 #endif
@@ -562,6 +566,7 @@ pango2_font_map_default_load_fontset (Pango2FontMap               *self,
     goto done;
 
   fontset = pango2_fontset_cached_new (description, language, self->dpi, ctm);
+  fontset->palette = context->palette;
 #ifdef HAVE_CAIRO
   fontset->font_options = cairo_font_options_copy (pango2_cairo_context_get_merged_font_options (context));
 #endif
diff --git a/pango2/pango-fontset-cached-private.h b/pango2/pango-fontset-cached-private.h
index 95059a791..c1c74f5f0 100644
--- a/pango2/pango-fontset-cached-private.h
+++ b/pango2/pango-fontset-cached-private.h
@@ -44,6 +44,7 @@ struct _Pango2FontsetCached
   GList cache_link;
   GHashTable *cache;
 
+  GQuark palette;
 #ifdef HAVE_CAIRO
   cairo_font_options_t *font_options;
 #endif
diff --git a/pango2/pango-fontset-cached.c b/pango2/pango-fontset-cached.c
index 9feaca795..f3a65c4d2 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"
@@ -40,6 +42,7 @@ pango2_fontset_cached_init (Pango2FontsetCached *fontset)
   fontset->items = g_ptr_array_new_with_free_func (g_object_unref);
   fontset->cache = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
   fontset->language = NULL;
+  fontset->palette = g_quark_from_static_string (PANGO2_COLOR_PALETTE_DEFAULT);
 #ifdef HAVE_CAIRO
   fontset->font_options = NULL;
 #endif
@@ -81,6 +84,27 @@ 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);
+  if (pango2_font_description_get_set_fields  (self->description) & PANGO2_FONT_MASK_PALETTE)
+    pango2_font_set_color_palette (font, pango2_font_description_get_palette_quark (self->description));
+  else
+    pango2_font_set_color_palette (font, self->palette);
+#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 +153,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 +189,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 +255,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 +302,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..2cc0e562e 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,8 @@ struct _Pango2HbFace
   Pango2LanguageSet *languages;
   gboolean embolden;
   gboolean synthetic;
+  PaletteMapEntry *palettes;
+  unsigned int n_palettes;
 };
 
 Pango2LanguageSet *     pango2_hb_face_get_language_set  (Pango2HbFace          *self);
@@ -50,3 +57,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..2850a93dd 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,7 @@ pango2_hb_face_finalize (GObject *object)
 {
   Pango2HbFace *self = PANGO2_HB_FACE (object);
 
+  g_free (self->palettes);
   g_free (self->faceid);
   if (self->face)
     hb_face_destroy (self->face);
@@ -483,6 +488,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 +636,65 @@ 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)
+{
+  for (unsigned int i = 0; i < hb_ot_color_palette_get_count (hbface); 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;
+
+  for (unsigned int i = 0; i < self->n_palettes; i++)
+    {
+      if (self->palettes[i].name == palette)
+        return self->palettes[i].index;
+    }
+
+  if (palette == quark_default_palette)
+    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;
+    }
+
+  ensure_hb_face (self);
+
+  if (hb_ot_color_has_palettes (self->face))
+    {
+      /* 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 +945,36 @@ pango2_hb_face_get_transform (Pango2HbFace *self)
   return self->transform;
 }
 
+/**
+ * pango2_hb_face_get_palette_names:
+ * @self: a `Pango2HbFace`
+ *
+ * Returns custom palette names for this `PangoHbFace`.
+ *
+ * Note that this function only returns names that were added
+ * with [method@Pango2.HbFaceBuilder.set_palette_name], not
+ * predefined names.
+ *
+ * Returns: (nullable) (transfer container): a %NULL-terminated array
+ *   or strings, free with g_free()
+ */
+char **
+pango2_hb_face_get_palette_names (Pango2HbFace *self)
+{
+  char **ret;
+
+  if (self->n_palettes == 0)
+    return NULL;
+
+  ret = g_new (char *, self->n_palettes + 1);
+
+  for (int i = 0; i < self->n_palettes; i++)
+    ret[i] = (char *) g_quark_to_string (self->palettes[i].name);
+  ret[self->n_palettes] = NULL;
+
+  return ret;
+}
+
 /* }}} */
 /* {{{ Pango2HbFaceBuilder */
 
@@ -896,6 +994,7 @@ struct _Pango2HbFaceBuilder {
   unsigned int n_variations;
   char *name;
   Pango2FontDescription *description;
+  GArray *palettes;
 };
 
 G_DEFINE_BOXED_TYPE (Pango2HbFaceBuilder, pango2_hb_face_builder,
@@ -917,6 +1016,7 @@ face_builder_new (void)
   builder->n_variations = 0;
   builder->name = NULL;
   builder->description = NULL;
+  builder->palettes = NULL;
 
   return builder;
 }
@@ -947,6 +1047,8 @@ pango2_hb_face_builder_copy (const Pango2HbFaceBuilder *src)
   builder->name = g_strdup (src->name);
   if (src->description)
     builder->description = pango2_font_description_copy_static (src->description);
+  if (src->palettes)
+    builder->palettes = g_array_copy (src->palettes);
 
   return builder;
 }
@@ -969,6 +1071,8 @@ pango2_hb_face_builder_free (Pango2HbFaceBuilder *builder)
   g_free (builder->name);
   if (builder->description)
     pango2_font_description_free (builder->description);
+  if (builder->palettes)
+    g_array_free (builder->palettes, TRUE);
 
   g_free (builder);
 }
@@ -1025,6 +1129,11 @@ 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);
+  if (face->palettes)
+    {
+      builder->palettes = g_array_new (FALSE, FALSE, sizeof (PaletteMapEntry));
+      g_array_append_vals (builder->palettes, face->palettes, face->n_palettes);
+    }
 
   return builder;
 }
@@ -1070,6 +1179,12 @@ pango2_hb_face_builder_get_face (Pango2HbFaceBuilder *builder)
 
   set_name_and_description (self, builder->name, builder->description);
 
+  if (builder->palettes)
+    {
+      self->palettes = g_memdup2 (builder->palettes->data, sizeof (PaletteMapEntry) * 
builder->palettes->len);
+      self->n_palettes = builder->palettes->len;
+    }
+
   return self;
 }
 
@@ -1189,6 +1304,68 @@ pango2_hb_face_builder_set_variations (Pango2HbFaceBuilder  *builder,
   builder->n_variations = n_variations;
 }
 
+static gboolean
+name_is_valid (const char *name)
+{
+  /* 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_builder_set_palette_name:
+ * @builder: a `Pango2HbFaceBuilder`
+ * @name: the name for the palette
+ * @palette_index: palette index
+ *
+ * Assigns a name to the palette index.
+ *
+ * Names must consists of ASCII letters and digits,
+ * separated by either '-' or '_'. The first character
+ * must be a letter.
+ */
+void
+pango2_hb_face_builder_set_palette_name (Pango2HbFaceBuilder *builder,
+                                         const char          *name,
+                                         unsigned int         palette_index)
+{
+  PaletteMapEntry add;
+
+  g_return_if_fail (name_is_valid (name));
+
+  add.name = g_quark_from_string (name);
+  add.index = palette_index;
+
+  if (!builder->palettes)
+    builder->palettes = g_array_new (FALSE, FALSE, sizeof (PaletteMapEntry));
+
+  for (int i = 0; i < builder->palettes->len; i++)
+    {
+      PaletteMapEntry *entry = &g_array_index (builder->palettes, PaletteMapEntry, i);
+      if (entry->name == add.name)
+        {
+          entry->index = add.index;
+          return;
+        }
+    }
+
+  g_array_append_val (builder->palettes, add);
+}
+
 /* }}} */
 
 /* vim:set foldmethod=marker expandtab: */
diff --git a/pango2/pango-hbface.h b/pango2/pango-hbface.h
index b110ff99e..e892d7ff0 100644
--- a/pango2/pango-hbface.h
+++ b/pango2/pango-hbface.h
@@ -66,6 +66,9 @@ gboolean                pango2_hb_face_get_embolden      (Pango2HbFace
 PANGO2_AVAILABLE_IN_ALL
 const Pango2Matrix *    pango2_hb_face_get_transform     (Pango2HbFace                *self);
 
+PANGO2_AVAILABLE_IN_ALL
+char **                 pango2_hb_face_get_palette_names (Pango2HbFace                *self);
+
 
 typedef struct _Pango2HbFaceBuilder Pango2HbFaceBuilder;
 
@@ -108,5 +111,9 @@ PANGO2_AVAILABLE_IN_ALL
 void                    pango2_hb_face_builder_set_variations   (Pango2HbFaceBuilder         *builder,
                                                                  const hb_variation_t        *variations,
                                                                  unsigned int                 n_variations);
+PANGO2_AVAILABLE_IN_ALL
+void                    pango2_hb_face_builder_set_palette_name (Pango2HbFaceBuilder         *builder,
+                                                                 const char                  *name,
+                                                                 unsigned int                 palette_index);
 
 G_END_DECLS
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..95c787239 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"
@@ -92,10 +94,26 @@ create_cairo_font_face (Pango2Font *font)
   return NULL;
 }
 
+#ifdef CAIRO_COLOR_PALETTE_DEFAULT
+
+static int
+find_palette_index_for_font (Pango2Font *font)
+{
+  Pango2FontFace *face = pango2_font_get_face (font);
+
+  if (PANGO2_IS_HB_FACE (face))
+    return pango2_hb_face_get_palette_index (PANGO2_HB_FACE (face), font->palette);
+
+  return CAIRO_COLOR_PALETTE_DEFAULT;
+}
+
+#endif
+
 static cairo_scaled_font_t *
 _pango2_cairo_font_private_get_scaled_font (Pango2CairoFontPrivate *cf_priv)
 {
   cairo_font_face_t *font_face;
+  cairo_font_options_t *options;
 
   if (G_LIKELY (cf_priv->scaled_font))
     return cf_priv->scaled_font;
@@ -113,10 +131,20 @@ _pango2_cairo_font_private_get_scaled_font (Pango2CairoFontPrivate *cf_priv)
   if (G_UNLIKELY (font_face == NULL))
     goto done;
 
+  options = cairo_font_options_copy (cf_priv->data->options);
+
+#ifdef CAIRO_COLOR_PALETTE_DEFAULT
+  if (cairo_font_options_get_color_palette (options) == CAIRO_COLOR_PALETTE_DEFAULT)
+    cairo_font_options_set_color_palette (options,
+                                          find_palette_index_for_font (cf_priv->cfont));
+#endif
+
   cf_priv->scaled_font = cairo_scaled_font_create (font_face,
                                                    &cf_priv->data->font_matrix,
                                                    &cf_priv->data->ctm,
-                                                   cf_priv->data->options);
+                                                   options);
+
+  cairo_font_options_destroy (options);
 
   cairo_font_face_destroy (font_face);
 
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..19bebacf4 100644
--- a/tests/test-font.c
+++ b/tests/test-font.c
@@ -499,7 +499,7 @@ 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;
 
@@ -528,6 +528,72 @@ test_faceid (void)
   pango2_font_description_free (desc);
 }
 
+static void
+test_palette (void)
+{
+  const char *test = "Cantarell Bold Italic 32 @palette=light @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_PALETTE|
+                                                                     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_palette (desc), ==, "light");
+  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 @palette=dark @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_PALETTE|
+                                                                     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_palette (desc), ==, "dark");
+  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 gboolean
 font_info_cb (Pango2UserFace     *face,
               int                size,
@@ -657,6 +723,8 @@ 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/palette", test_palette);
+  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]