[gtk+/wip/matthiasc/font-variations: 55/62] wip: add font features in the font chooser



commit 9953f4e0e8011692b845f209746435fa4f61c255
Author: Matthias Clasen <mclasen redhat com>
Date:   Wed Dec 27 18:39:30 2017 -0500

    wip: add font features in the font chooser

 gtk/gtkfontchooserwidget.c     |  513 +++++++++++++++++++++++++++++++++++++++-
 gtk/meson.build                |    4 +
 gtk/open-type-layout.h         |  155 ++++++++++++
 gtk/ui/gtkfontchooserwidget.ui |   24 ++
 4 files changed, 695 insertions(+), 1 deletions(-)
---
diff --git a/gtk/gtkfontchooserwidget.c b/gtk/gtkfontchooserwidget.c
index 44f2858..d2c80ab 100644
--- a/gtk/gtkfontchooserwidget.c
+++ b/gtk/gtkfontchooserwidget.c
@@ -58,15 +58,22 @@
 #include "gtkrevealer.h"
 #include "gtklistbox.h"
 #include "gtkpopover.h"
+#include "gtkradiobutton.h"
+#include "gtkcheckbutton.h"
 
 #if defined(HAVE_HARFBUZZ) && defined(HAVE_PANGOFT)
 #include <pango/pangofc-font.h>
+#include <hb.h>
+#include <hb-ot.h>
+#include <hb-ft.h>
 #include <freetype/freetype.h>
 #include <freetype/ftmm.h>
 #include "language-names.h"
 #include "script-names.h"
 #endif
 
+#include "open-type-layout.h"
+
 /**
  * SECTION:gtkfontchooserwidget
  * @Short_description: A widget for selecting fonts
@@ -134,6 +141,9 @@ struct _GtkFontChooserWidgetPrivate
 
   GtkWidget *font_grid;
 
+  GtkWidget       *feature_box;
+  GtkWidget       *feature_language_combo;
+
   PangoFontMap         *font_map;
 
   PangoFontDescription *font_desc;
@@ -152,6 +162,8 @@ struct _GtkFontChooserWidgetPrivate
   gboolean updating_variations;
 
   GtkFontChooserLevel level;
+
+  GList *feature_items;
 };
 
 /* Keep in line with GtkTreeStore defined in gtkfontchooserwidget.ui */
@@ -204,6 +216,7 @@ static void     gtk_font_chooser_widget_set_cell_size          (GtkFontChooserWi
 static void     gtk_font_chooser_widget_load_fonts             (GtkFontChooserWidget *fontchooser,
                                                                 gboolean              force);
 static void     gtk_font_chooser_widget_populate_filters       (GtkFontChooserWidget *fontchooser);
+static void     gtk_font_chooser_widget_populate_features      (GtkFontChooserWidget *fontchooser);
 static gboolean visible_func                                   (GtkTreeModel *model,
                                                                GtkTreeIter  *iter,
                                                                gpointer      user_data);
@@ -241,6 +254,8 @@ static void language_filter_activated (GtkFontChooserWidget *fontchooser);
 static void popover_notify (GObject              *object,
                             GParamSpec           *pspec,
                             GtkFontChooserWidget *fontchooser);
+static void collect_font_features (GtkFontChooserWidget *fontchooser,
+                                   PangoAttrList *attrs);
 
 static void gtk_font_chooser_widget_iface_init (GtkFontChooserIface *iface);
 
@@ -630,6 +645,8 @@ gtk_font_chooser_widget_update_preview_attributes (GtkFontChooserWidget *fontcho
   /* Force current font */
   pango_attr_list_insert (attrs, pango_attr_font_desc_new (priv->font_desc));
 
+  collect_font_features (fontchooser, attrs);
+
   gtk_entry_set_attributes (GTK_ENTRY (priv->preview), attrs);
   pango_attr_list_unref (attrs);
 }
@@ -740,6 +757,8 @@ gtk_font_chooser_widget_class_init (GtkFontChooserWidgetClass *klass)
   gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, size_slider);
   gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, grid);
   gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, font_grid);
+  gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, feature_box);
+  gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, feature_language_combo);
 
   gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, filter_popover);
   gtk_widget_class_bind_template_child_private (widget_class, GtkFontChooserWidget, script_filter_revealer);
@@ -873,6 +892,7 @@ gtk_font_chooser_widget_init (GtkFontChooserWidget *fontchooser)
   /* Load data and set initial style-dependent parameters */
   gtk_font_chooser_widget_load_fonts (fontchooser, TRUE);
   gtk_font_chooser_widget_populate_filters (fontchooser);
+  gtk_font_chooser_widget_populate_features (fontchooser);
   gtk_font_chooser_widget_set_cell_size (fontchooser);
   gtk_font_chooser_widget_take_font_desc (fontchooser, NULL);
 }
@@ -1081,7 +1101,6 @@ script_filter_func (GtkListBoxRow *row,
 {
   GtkFontChooserWidget *fontchooser = data;
   GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
-  PangoScript script;
   const char *term;
 
   if (priv->script_term == NULL)
@@ -1755,6 +1774,8 @@ gtk_font_chooser_widget_finalize (GObject *object)
   g_free (priv->script_term);
   g_free (priv->language_term);
 
+  g_list_free_full (priv->feature_items, g_free);
+
   G_OBJECT_CLASS (gtk_font_chooser_widget_parent_class)->finalize (object);
 }
 
@@ -2134,6 +2155,495 @@ gtk_font_chooser_widget_update_font_variations (GtkFontChooserWidget *fontchoose
   pango_fc_font_unlock_face (PANGO_FC_FONT (pango_font));
   g_object_unref (pango_font);
 }
+
+static void
+add_script (GtkListStore *store,
+            guint         script_index,
+            hb_tag_t      script,
+            guint         lang_index,
+            hb_tag_t      lang)
+{
+  char scriptbuf[5];
+  char langbuf[5];
+  const char *scriptname;
+  const char *langname;
+  char *name;
+
+  hb_tag_to_string (script, scriptbuf);
+  scriptbuf[4] = 0;
+
+  hb_tag_to_string (lang, langbuf);
+  langbuf[4] = 0;
+
+  if (script == HB_OT_TAG_DEFAULT_SCRIPT)
+    scriptname = "Default";
+  else if (script == HB_TAG ('m','a','t','h'))
+    scriptname = "Math";
+  else
+    scriptname = get_script_name_for_tag (script);
+
+  if (!scriptname)
+    scriptname = scriptbuf;
+
+  langname = get_language_name_for_tag (lang);
+  if (!langname)
+    langname = langbuf;
+
+  if (lang == HB_OT_TAG_DEFAULT_LANGUAGE)
+    name = g_strdup_printf ("%s Script", scriptname);
+  else
+    name = g_strdup (langname);
+
+  gtk_list_store_insert_with_values (store, NULL, -1,
+                                     0, name,
+                                     1, script_index,
+                                     2, lang_index,
+                                     3, lang,
+                                     -1);
+  g_free (name);
+}
+
+static int
+feature_language_sort_func (GtkTreeModel *model,
+                            GtkTreeIter  *a,
+                            GtkTreeIter  *b,
+                            gpointer      user_data)
+{
+  char *sa, *sb;
+  int ret;
+
+  gtk_tree_model_get (model, a, 0, &sa, -1);
+  gtk_tree_model_get (model, b, 0, &sb, -1);
+
+  ret = strcmp (sa, sb);
+
+  g_free (sa);
+  g_free (sb);
+
+  return ret;
+}
+
+typedef struct {
+  hb_tag_t script_tag;
+  hb_tag_t lang_tag;
+  unsigned int script_index;
+  unsigned int lang_index;
+} TagPair;
+
+static guint
+tag_pair_hash (gconstpointer data)
+{
+  const TagPair *pair = data;
+
+  return pair->script_tag + pair->lang_tag;
+}
+
+static gboolean
+tag_pair_equal (gconstpointer a, gconstpointer b)
+{
+  const TagPair *pair_a = a;
+  const TagPair *pair_b = b;
+
+  return pair_a->script_tag == pair_b->script_tag && pair_a->lang_tag == pair_b->lang_tag;
+}
+
+static void
+update_language_combo (GtkFontChooserWidget *fontchooser,
+                       hb_face_t            *hb_face)
+{
+  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
+  GtkListStore *store;
+  gint i, j, k;
+  hb_tag_t scripts[80];
+  unsigned int n_scripts;
+  unsigned int count;
+  hb_tag_t table[2] = { HB_OT_TAG_GSUB, HB_OT_TAG_GPOS };
+  GHashTable *tags;
+  GHashTableIter iter;
+  TagPair *pair;
+
+  tags = g_hash_table_new_full (tag_pair_hash, tag_pair_equal, g_free, NULL);
+
+  pair = g_new (TagPair, 1);
+  pair->script_tag = HB_OT_TAG_DEFAULT_SCRIPT;
+  pair->lang_tag = HB_OT_TAG_DEFAULT_LANGUAGE;
+  g_hash_table_add (tags, pair);
+
+  n_scripts = 0;
+  for (i = 0; i < 2; i++)
+    {
+      count = G_N_ELEMENTS (scripts);
+      hb_ot_layout_table_get_script_tags (hb_face, table[i], n_scripts, &count, scripts);
+      g_print ("found %d scripts in %s\n", count, i == 0 ? "GSUB" : "GPOS");
+      n_scripts += count;
+    }
+
+  for (j = 0; j < n_scripts; j++)
+    {
+      hb_tag_t languages[80];
+      unsigned int n_languages;
+
+      n_languages = 0;
+      for (i = 0; i < 2; i++)
+        {
+          count = G_N_ELEMENTS (languages);
+          hb_ot_layout_script_get_language_tags (hb_face, table[i], j, n_languages, &count, languages);
+          g_print ("found %d languages for script %d in %s\n", count, j, i == 0 ? "GSUB" : "GPOS");
+          n_languages += count;
+        }
+
+      pair = g_new (TagPair, 1);
+      pair->script_tag = scripts[j];
+      pair->lang_tag = HB_OT_TAG_DEFAULT_LANGUAGE;
+      pair->script_index = j;
+      pair->lang_index = HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX;
+      g_hash_table_add (tags, pair);
+
+      for (k = 0; k < n_languages; k++)
+        {
+          pair = g_new (TagPair, 1);
+          pair->script_tag = scripts[j];
+          pair->lang_tag = languages[k];
+          pair->script_index = j;
+          pair->lang_index = k;
+          g_hash_table_add (tags, pair);
+        }
+    }
+
+  store = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT);
+
+  g_hash_table_iter_init (&iter, tags);
+  while (g_hash_table_iter_next (&iter, (gpointer *)&pair, NULL))
+    add_script (store, pair->script_index, pair->script_tag, pair->lang_index, pair->lang_tag);
+
+  g_hash_table_unref (tags);
+
+  gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (store),
+                                           feature_language_sort_func, NULL, NULL);
+  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
+                                        GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
+                                        GTK_SORT_ASCENDING);
+  gtk_combo_box_set_model (GTK_COMBO_BOX (priv->feature_language_combo), GTK_TREE_MODEL (store));
+  gtk_combo_box_set_active (GTK_COMBO_BOX (priv->feature_language_combo), 0);
+}
+
+typedef struct {
+  hb_tag_t tag;
+  const char *name;
+  GtkWidget *feat;
+} FeatureItem;
+
+static const char *
+get_feature_display_name (hb_tag_t tag)
+{
+  int i;
+
+  for (i = 0; i < G_N_ELEMENTS (open_type_layout_features); i++)
+    {
+      if (tag == open_type_layout_features[i].tag)
+        return g_dpgettext2 (NULL, "OpenType layout", open_type_layout_features[i].name);
+    }
+
+  return NULL;
+}
+
+static void
+feat_clicked (GtkWidget *feat,
+              gpointer   data)
+{
+  g_signal_handlers_block_by_func (feat, feat_clicked, NULL);
+
+  if (gtk_check_button_get_inconsistent (GTK_CHECK_BUTTON (feat)))
+    {
+      gtk_check_button_set_inconsistent (GTK_CHECK_BUTTON (feat), FALSE);
+      gtk_widget_set_opacity (gtk_widget_get_first_child (feat), 1);
+      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (feat), TRUE);
+    }
+  else if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (feat)))
+    {
+      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (feat), FALSE);
+    }
+  else
+    {
+      gtk_check_button_set_inconsistent (GTK_CHECK_BUTTON (feat), TRUE);
+      gtk_widget_set_opacity (gtk_widget_get_first_child (feat), 0);
+    }
+
+  g_signal_handlers_unblock_by_func (feat, feat_clicked, NULL);
+}
+
+static void
+add_check_group (GtkFontChooserWidget *fontchooser,
+                 const char  *title,
+                 const char **tags)
+{
+  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
+  GtkWidget *label;
+  GtkWidget *group;
+  PangoAttrList *attrs;
+  int i;
+
+  group = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+  gtk_widget_set_halign (group, GTK_ALIGN_START);
+
+  label = gtk_label_new (title);
+  gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+  gtk_widget_set_halign (label, GTK_ALIGN_START);
+  g_object_set (label, "margin-top", 10, "margin-bottom", 10, NULL);
+  attrs = pango_attr_list_new ();
+  pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
+  gtk_label_set_attributes (GTK_LABEL (label), attrs);
+  pango_attr_list_unref (attrs);
+  gtk_container_add (GTK_CONTAINER (group), label);
+
+  for (i = 0; tags[i]; i++)
+    {
+      hb_tag_t tag;
+      GtkWidget *feat;
+      FeatureItem *item;
+
+      tag = hb_tag_from_string (tags[i], -1);
+
+      feat = gtk_check_button_new_with_label (get_feature_display_name (tag));
+      gtk_check_button_set_inconsistent (GTK_CHECK_BUTTON (feat), TRUE);
+      gtk_widget_set_opacity (gtk_widget_get_first_child (feat), 0);
+
+      g_signal_connect_swapped (feat, "notify::active", G_CALLBACK 
(gtk_font_chooser_widget_update_preview_attributes), fontchooser);
+      g_signal_connect_swapped (feat, "notify::inconsistent", G_CALLBACK 
(gtk_font_chooser_widget_update_preview_attributes), fontchooser);
+      g_signal_connect (feat, "clicked", G_CALLBACK (feat_clicked), NULL);
+
+      gtk_container_add (GTK_CONTAINER (group), feat);
+
+      item = g_new (FeatureItem, 1);
+      item->name = tags[i];
+      item->tag = tag;
+      item->feat = feat;
+
+      priv->feature_items = g_list_prepend (priv->feature_items, item);
+    }
+
+  gtk_container_add (GTK_CONTAINER (priv->feature_box), group);
+}
+
+static void
+add_radio_group (GtkFontChooserWidget *fontchooser,
+                 const char  *title,
+                 const char **tags)
+{
+  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
+  GtkWidget *label;
+  GtkWidget *group;
+  int i;
+  GtkWidget *group_button = NULL;
+  PangoAttrList *attrs;
+
+  group = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+  gtk_widget_set_halign (group, GTK_ALIGN_START);
+
+  label = gtk_label_new (title);
+  gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+  gtk_widget_set_halign (label, GTK_ALIGN_START);
+  g_object_set (label, "margin-top", 10, "margin-bottom", 10, NULL);
+  attrs = pango_attr_list_new ();
+  pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
+  gtk_label_set_attributes (GTK_LABEL (label), attrs);
+  pango_attr_list_unref (attrs);
+  gtk_container_add (GTK_CONTAINER (group), label);
+
+  for (i = 0; tags[i]; i++)
+    {
+      hb_tag_t tag;
+      GtkWidget *feat;
+      FeatureItem *item;
+      const char *name;
+
+      tag = hb_tag_from_string (tags[i], -1);
+      name = get_feature_display_name (tag);
+
+      feat = gtk_radio_button_new_with_label_from_widget (GTK_RADIO_BUTTON (group_button),
+                                                          name ? name : _("Default"));
+      if (group_button == NULL)
+        group_button = feat;
+
+      g_signal_connect_swapped (feat, "notify::active", G_CALLBACK 
(gtk_font_chooser_widget_update_preview_attributes), fontchooser);
+      g_object_set_data (G_OBJECT (feat), "default", group_button);
+
+      gtk_container_add (GTK_CONTAINER (group), feat);
+
+      item = g_new (FeatureItem, 1);
+      item->name = tags[i];
+      item->tag = tag;
+      item->feat = feat;
+
+      priv->feature_items = g_list_prepend (priv->feature_items, item);
+    }
+
+  gtk_container_add (GTK_CONTAINER (priv->feature_box), group);
+}
+
+static void
+gtk_font_chooser_widget_populate_features (GtkFontChooserWidget *fontchooser)
+{
+  const char *ligatures[] = { "liga", "dlig", "hlig", "clig", NULL };
+  const char *letter_case[] = { "smcp", "c2sc", "pcap", "c2pc", "unic", "cpsp", "case", NULL };
+  const char *number_case[] = { "xxxx", "lnum", "onum", NULL };
+  const char *number_spacing[] = { "xxxx", "pnum", "tnum", NULL };
+  const char *number_formatting[] = { "zero", "nalt", NULL };
+  const char *char_variants[] = { "swsh", "cswh", "calt", "falt", "hist", "salt", "jalt", "titl", "rand", 
NULL };
+
+  add_check_group (fontchooser, _("Ligatures"), ligatures);
+  add_check_group (fontchooser, _("Letter Case"), letter_case);
+  add_radio_group (fontchooser, _("Number Case"), number_case);
+  add_radio_group (fontchooser, _("Number Spacing"), number_spacing);
+  add_check_group (fontchooser, _("Number Formatting"), number_formatting);
+  add_check_group (fontchooser, _("Character Variants"), char_variants);
+}
+
+static void
+gtk_font_chooser_widget_update_font_features (GtkFontChooserWidget *fontchooser)
+{
+  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
+  PangoFont *pango_font;
+  FT_Face ft_face;
+  hb_font_t *hb_font;
+  hb_tag_t script_tag = FT_MAKE_TAG('l','a','t','n');
+  hb_tag_t lang_tag = FT_MAKE_TAG('D','E','U',' ');
+  guint script_index = 0;
+  guint lang_index = 0;
+  int i, j;
+  GList *l;
+
+  for (l = priv->feature_items; l; l = l->next)
+    {
+      FeatureItem *item = l->data;
+      gtk_widget_hide (item->feat);
+      gtk_widget_hide (gtk_widget_get_parent (item->feat));
+    }
+
+  pango_font = pango_context_load_font (gtk_widget_get_pango_context (GTK_WIDGET (fontchooser)),
+                                        priv->font_desc);
+  ft_face = pango_fc_font_lock_face (PANGO_FC_FONT (pango_font)),
+  hb_font = hb_ft_font_create (ft_face, NULL);
+
+  if (hb_font)
+    {
+      hb_tag_t table[2] = { HB_OT_TAG_GSUB, HB_OT_TAG_GPOS };
+      hb_face_t *hb_face;
+      hb_tag_t features[80];
+      unsigned int count;
+      unsigned int n_features;
+
+      hb_face = hb_font_get_face (hb_font);
+
+      update_language_combo (fontchooser, hb_face);
+
+      n_features = 0;
+      for (i = 0; i < 2; i++)
+        {
+          hb_ot_layout_table_find_script (hb_face, table[i], script_tag, &script_index);
+          hb_ot_layout_script_find_language (hb_face, table[i], script_index, lang_tag, &lang_index);
+          count = G_N_ELEMENTS (features);
+          hb_ot_layout_language_get_feature_tags (hb_face,
+                                                  table[i],
+                                                  script_index,
+                                                  lang_index,
+                                                  n_features,
+                                                  &count,
+                                                  features);
+          n_features += count;
+        }
+
+      for (j = 0; j < n_features; j++)
+        {
+          for (l = priv->feature_items; l; l = l->next)
+            {
+              FeatureItem *item = l->data;
+              if (item->tag != features[j])
+                continue;
+
+              gtk_widget_show (item->feat);
+              gtk_widget_show (gtk_widget_get_parent (item->feat));
+
+              if (GTK_IS_RADIO_BUTTON (item->feat))
+                {
+                  GtkWidget *def = GTK_WIDGET (g_object_get_data (G_OBJECT (item->feat), "default"));
+                  gtk_widget_show (def);
+                }
+              else if (GTK_IS_CHECK_BUTTON (item->feat))
+                {
+                  gtk_check_button_set_inconsistent (GTK_CHECK_BUTTON (item->feat), TRUE);
+                  gtk_widget_set_opacity (gtk_widget_get_first_child (item->feat), 0);
+                }
+            }
+        }
+
+      hb_face_destroy (hb_face);
+    }
+
+  pango_fc_font_unlock_face (PANGO_FC_FONT (pango_font));
+  g_object_unref (pango_font);
+}
+
+static void
+collect_font_features (GtkFontChooserWidget *fontchooser,
+                       PangoAttrList *attrs)
+{
+  GtkFontChooserWidgetPrivate *priv = fontchooser->priv;
+  GString *s;
+  GList *l;
+  GtkTreeIter iter;
+
+  s = g_string_new ("kern 1, curs 1, lfbd 1, rfbd 1, mark 1, mkmk 1, mset 1, ccmp 1, rlig 1, rclt 1, rvrn 
1");
+
+  for (l = priv->feature_items; l; l = l->next)
+    {
+      FeatureItem *item = l->data;
+
+      if (!gtk_widget_is_sensitive (item->feat))
+        continue;
+
+      if (GTK_IS_RADIO_BUTTON (item->feat))
+        {
+          if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (item->feat)) &&
+              strcmp (item->name, "xxxx") != 0)
+            {
+              g_string_append (s, ", ");
+              g_string_append (s, item->name);
+              g_string_append (s, " 1");
+            }
+        }
+      else if (GTK_IS_CHECK_BUTTON (item->feat))
+        {
+          if (gtk_check_button_get_inconsistent (GTK_CHECK_BUTTON (item->feat)))
+            continue;
+
+          g_string_append (s, ", ");
+          g_string_append (s, item->name);
+          if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (item->feat)))
+            g_string_append (s, " 1");
+          else
+            g_string_append (s, " 0");
+        }
+    }
+
+  pango_attr_list_insert (attrs, pango_attr_font_features_new (s->str));
+  g_string_free (s, TRUE);
+
+  if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (priv->feature_language_combo), &iter))
+    {
+      GtkTreeModel *model;
+      const char *lang;
+      hb_tag_t lang_tag;
+
+      model = gtk_combo_box_get_model (GTK_COMBO_BOX (priv->feature_language_combo));
+      gtk_tree_model_get (model, &iter,
+                          3, &lang_tag,
+                          -1);
+      lang = hb_language_to_string (hb_ot_tag_to_language (lang_tag));
+      pango_attr_list_insert (attrs, pango_attr_language_new (pango_language_from_string (lang)));
+    }
+}
+
 #endif
 
 static void
@@ -2182,6 +2692,7 @@ gtk_font_chooser_widget_merge_font_desc (GtkFontChooserWidget       *fontchooser
 
   gtk_font_chooser_widget_update_preview_attributes (fontchooser);
 #if defined(HAVE_HARFBUZZ) && defined(HAVE_PANGOFT)
+  gtk_font_chooser_widget_update_font_features (fontchooser);
   gtk_font_chooser_widget_update_font_variations (fontchooser);
 #endif
 
diff --git a/gtk/meson.build b/gtk/meson.build
index 0a95643..d04c358 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -827,6 +827,10 @@ gtk_deps = [
   graphene_dep,
 ]
 
+if harfbuzz_dep.found() and pangoft_dep.found()
+  gtk_deps += [ harfbuzz_dep, ]
+endif
+
 if x11_enabled
   x11_data_prefix = dependency('x11').get_pkgconfig_variable('prefix')
 
diff --git a/gtk/open-type-layout.h b/gtk/open-type-layout.h
new file mode 100644
index 0000000..22521f3
--- /dev/null
+++ b/gtk/open-type-layout.h
@@ -0,0 +1,155 @@
+/* Registered OpenType layout tags, see
+ * https://www.microsoft.com/typography/otspec/featuretags.htm
+ */
+
+typedef struct {
+  unsigned int tag;
+  const char *name;
+} NamedTag;
+
+#define MAKE_TAG(a,b,c,d) (unsigned int)(((a) << 24) | ((b) << 16) | ((c) <<  8) | (d))
+
+static NamedTag open_type_layout_features[] = {
+  { MAKE_TAG('a','a','l','t'), NC_("OpenType layout", "Access All Alternates") },
+  { MAKE_TAG('a','b','v','f'), NC_("OpenType layout", "Above-base Forms") },
+  { MAKE_TAG('a','b','v','m'), NC_("OpenType layout", "Above-base Mark Positioning") },
+  { MAKE_TAG('a','b','v','s'), NC_("OpenType layout", "Above-base Substitutions") },
+  { MAKE_TAG('a','f','r','c'), NC_("OpenType layout", "Alternative Fractions") },
+  { MAKE_TAG('a','k','h','n'), NC_("OpenType layout", "Akhands") },
+  { MAKE_TAG('b','l','w','f'), NC_("OpenType layout", "Below-base Forms") },
+  { MAKE_TAG('b','l','w','m'), NC_("OpenType layout", "Below-base Mark Positioning") },
+  { MAKE_TAG('b','l','w','s'), NC_("OpenType layout", "Below-base Substitutions") },
+  { MAKE_TAG('c','a','l','t'), NC_("OpenType layout", "Contextual Alternates") },
+  { MAKE_TAG('c','a','s','e'), NC_("OpenType layout", "Case-Sensitive Forms") },
+  { MAKE_TAG('c','c','m','p'), NC_("OpenType layout", "Glyph Composition / Decomposition") },
+  { MAKE_TAG('c','f','a','r'), NC_("OpenType layout", "Conjunct Form After Ro") },
+  { MAKE_TAG('c','j','c','t'), NC_("OpenType layout", "Conjunct Forms") },
+  { MAKE_TAG('c','l','i','g'), NC_("OpenType layout", "Contextual Ligatures") },
+  { MAKE_TAG('c','p','c','t'), NC_("OpenType layout", "Centered CJK Punctuation") },
+  { MAKE_TAG('c','p','s','p'), NC_("OpenType layout", "Capital Spacing") },
+  { MAKE_TAG('c','s','w','h'), NC_("OpenType layout", "Contextual Swash") },
+  { MAKE_TAG('c','u','r','s'), NC_("OpenType layout", "Cursive Positioning") },
+  { MAKE_TAG('c','2','p','c'), NC_("OpenType layout", "Petite Capitals From Capitals") },
+  { MAKE_TAG('c','2','s','c'), NC_("OpenType layout", "Small Capitals From Capitals") },
+  { MAKE_TAG('d','i','s','t'), NC_("OpenType layout", "Distances") },
+  { MAKE_TAG('d','l','i','g'), NC_("OpenType layout", "Discretionary Ligatures") },
+  { MAKE_TAG('d','n','o','m'), NC_("OpenType layout", "Denominators") },
+  { MAKE_TAG('d','t','l','s'), NC_("OpenType layout", "Dotless Forms") },
+  { MAKE_TAG('e','x','p','t'), NC_("OpenType layout", "Expert Forms") },
+  { MAKE_TAG('f','a','l','t'), NC_("OpenType layout", "Final Glyph on Line Alternates") },
+  { MAKE_TAG('f','i','n','2'), NC_("OpenType layout", "Terminal Forms #2") },
+  { MAKE_TAG('f','i','n','3'), NC_("OpenType layout", "Terminal Forms #3") },
+  { MAKE_TAG('f','i','n','a'), NC_("OpenType layout", "Terminal Forms") },
+  { MAKE_TAG('f','l','a','c'), NC_("OpenType layout", "Flattened accent forms") },
+  { MAKE_TAG('f','r','a','c'), NC_("OpenType layout", "Fractions") },
+  { MAKE_TAG('f','w','i','d'), NC_("OpenType layout", "Full Widths") },
+  { MAKE_TAG('h','a','l','f'), NC_("OpenType layout", "Half Forms") },
+  { MAKE_TAG('h','a','l','n'), NC_("OpenType layout", "Halant Forms") },
+  { MAKE_TAG('h','a','l','t'), NC_("OpenType layout", "Alternate Half Widths") },
+  { MAKE_TAG('h','i','s','t'), NC_("OpenType layout", "Historical Forms") },
+  { MAKE_TAG('h','k','n','a'), NC_("OpenType layout", "Horizontal Kana Alternates") },
+  { MAKE_TAG('h','l','i','g'), NC_("OpenType layout", "Historical Ligatures") },
+  { MAKE_TAG('h','n','g','l'), NC_("OpenType layout", "Hangul") },
+  { MAKE_TAG('h','o','j','o'), NC_("OpenType layout", "Hojo Kanji Forms") },
+  { MAKE_TAG('h','w','i','d'), NC_("OpenType layout", "Half Widths") },
+  { MAKE_TAG('i','n','i','t'), NC_("OpenType layout", "Initial Forms") },
+  { MAKE_TAG('i','s','o','l'), NC_("OpenType layout", "Isolated Forms") },
+  { MAKE_TAG('i','t','a','l'), NC_("OpenType layout", "Italics") },
+  { MAKE_TAG('j','a','l','t'), NC_("OpenType layout", "Justification Alternates") },
+  { MAKE_TAG('j','p','7','8'), NC_("OpenType layout", "JIS78 Forms") },
+  { MAKE_TAG('j','p','8','3'), NC_("OpenType layout", "JIS83 Forms") },
+  { MAKE_TAG('j','p','9','0'), NC_("OpenType layout", "JIS90 Forms") },
+  { MAKE_TAG('j','p','0','4'), NC_("OpenType layout", "JIS2004 Forms") },
+  { MAKE_TAG('k','e','r','n'), NC_("OpenType layout", "Kerning") },
+  { MAKE_TAG('l','f','b','d'), NC_("OpenType layout", "Left Bounds") },
+  { MAKE_TAG('l','i','g','a'), NC_("OpenType layout", "Standard Ligatures") },
+  { MAKE_TAG('l','j','m','o'), NC_("OpenType layout", "Leading Jamo Forms") },
+  { MAKE_TAG('l','n','u','m'), NC_("OpenType layout", "Lining Figures") },
+  { MAKE_TAG('l','o','c','l'), NC_("OpenType layout", "Localized Forms") },
+  { MAKE_TAG('l','t','r','a'), NC_("OpenType layout", "Left-to-right alternates") },
+  { MAKE_TAG('l','t','r','m'), NC_("OpenType layout", "Left-to-right mirrored forms") },
+  { MAKE_TAG('m','a','r','k'), NC_("OpenType layout", "Mark Positioning") },
+  { MAKE_TAG('m','e','d','2'), NC_("OpenType layout", "Medial Forms #2") },
+  { MAKE_TAG('m','e','d','i'), NC_("OpenType layout", "Medial Forms") },
+  { MAKE_TAG('m','g','r','k'), NC_("OpenType layout", "Mathematical Greek") },
+  { MAKE_TAG('m','k','m','k'), NC_("OpenType layout", "Mark to Mark Positioning") },
+  { MAKE_TAG('m','s','e','t'), NC_("OpenType layout", "Mark Positioning via Substitution") },
+  { MAKE_TAG('n','a','l','t'), NC_("OpenType layout", "Alternate Annotation Forms") },
+  { MAKE_TAG('n','l','c','k'), NC_("OpenType layout", "NLC Kanji Forms") },
+  { MAKE_TAG('n','u','k','t'), NC_("OpenType layout", "Nukta Forms") },
+  { MAKE_TAG('n','u','m','r'), NC_("OpenType layout", "Numerators") },
+  { MAKE_TAG('o','n','u','m'), NC_("OpenType layout", "Oldstyle Figures") },
+  { MAKE_TAG('o','p','b','d'), NC_("OpenType layout", "Optical Bounds") },
+  { MAKE_TAG('o','r','d','n'), NC_("OpenType layout", "Ordinals") },
+  { MAKE_TAG('o','r','n','m'), NC_("OpenType layout", "Ornaments") },
+  { MAKE_TAG('p','a','l','t'), NC_("OpenType layout", "Proportional Alternate Widths") },
+  { MAKE_TAG('p','c','a','p'), NC_("OpenType layout", "Petite Capitals") },
+  { MAKE_TAG('p','k','n','a'), NC_("OpenType layout", "Proportional Kana") },
+  { MAKE_TAG('p','n','u','m'), NC_("OpenType layout", "Proportional Figures") },
+  { MAKE_TAG('p','r','e','f'), NC_("OpenType layout", "Pre-Base Forms") },
+  { MAKE_TAG('p','r','e','s'), NC_("OpenType layout", "Pre-base Substitutions") },
+  { MAKE_TAG('p','s','t','f'), NC_("OpenType layout", "Post-base Forms") },
+  { MAKE_TAG('p','s','t','s'), NC_("OpenType layout", "Post-base Substitutions") },
+  { MAKE_TAG('p','w','i','d'), NC_("OpenType layout", "Proportional Widths") },
+  { MAKE_TAG('q','w','i','d'), NC_("OpenType layout", "Quarter Widths") },
+  { MAKE_TAG('r','a','n','d'), NC_("OpenType layout", "Randomize") },
+  { MAKE_TAG('r','c','l','t'), NC_("OpenType layout", "Required Contextual Alternates") },
+  { MAKE_TAG('r','k','r','f'), NC_("OpenType layout", "Rakar Forms") },
+  { MAKE_TAG('r','l','i','g'), NC_("OpenType layout", "Required Ligatures") },
+  { MAKE_TAG('r','p','h','f'), NC_("OpenType layout", "Reph Forms") },
+  { MAKE_TAG('r','t','b','d'), NC_("OpenType layout", "Right Bounds") },
+  { MAKE_TAG('r','t','l','a'), NC_("OpenType layout", "Right-to-left alternates") },
+  { MAKE_TAG('r','t','l','m'), NC_("OpenType layout", "Right-to-left mirrored forms") },
+  { MAKE_TAG('r','u','b','y'), NC_("OpenType layout", "Ruby Notation Forms") },
+  { MAKE_TAG('r','v','r','n'), NC_("OpenType layout", "Required Variation Alternates") },
+  { MAKE_TAG('s','a','l','t'), NC_("OpenType layout", "Stylistic Alternates") },
+  { MAKE_TAG('s','i','n','f'), NC_("OpenType layout", "Scientific Inferiors") },
+  { MAKE_TAG('s','i','z','e'), NC_("OpenType layout", "Optical size") },
+  { MAKE_TAG('s','m','c','p'), NC_("OpenType layout", "Small Capitals") },
+  { MAKE_TAG('s','m','p','l'), NC_("OpenType layout", "Simplified Forms") },
+  { MAKE_TAG('s','s','0','1'), NC_("OpenType layout", "Stylistic Set 1") },
+  { MAKE_TAG('s','s','0','2'), NC_("OpenType layout", "Stylistic Set 2") },
+  { MAKE_TAG('s','s','0','3'), NC_("OpenType layout", "Stylistic Set 3") },
+  { MAKE_TAG('s','s','0','4'), NC_("OpenType layout", "Stylistic Set 4") },
+  { MAKE_TAG('s','s','0','5'), NC_("OpenType layout", "Stylistic Set 5") },
+  { MAKE_TAG('s','s','0','6'), NC_("OpenType layout", "Stylistic Set 6") },
+  { MAKE_TAG('s','s','0','7'), NC_("OpenType layout", "Stylistic Set 7") },
+  { MAKE_TAG('s','s','0','8'), NC_("OpenType layout", "Stylistic Set 8") },
+  { MAKE_TAG('s','s','0','9'), NC_("OpenType layout", "Stylistic Set 9") },
+  { MAKE_TAG('s','s','1','0'), NC_("OpenType layout", "Stylistic Set 10") },
+  { MAKE_TAG('s','s','1','1'), NC_("OpenType layout", "Stylistic Set 11") },
+  { MAKE_TAG('s','s','1','2'), NC_("OpenType layout", "Stylistic Set 12") },
+  { MAKE_TAG('s','s','1','3'), NC_("OpenType layout", "Stylistic Set 13") },
+  { MAKE_TAG('s','s','1','4'), NC_("OpenType layout", "Stylistic Set 14") },
+  { MAKE_TAG('s','s','1','5'), NC_("OpenType layout", "Stylistic Set 15") },
+  { MAKE_TAG('s','s','1','6'), NC_("OpenType layout", "Stylistic Set 16") },
+  { MAKE_TAG('s','s','1','7'), NC_("OpenType layout", "Stylistic Set 17") },
+  { MAKE_TAG('s','s','1','8'), NC_("OpenType layout", "Stylistic Set 18") },
+  { MAKE_TAG('s','s','1','9'), NC_("OpenType layout", "Stylistic Set 19") },
+  { MAKE_TAG('s','s','2','0'), NC_("OpenType layout", "Stylistic Set 20") },
+  { MAKE_TAG('s','s','t','y'), NC_("OpenType layout", "Math script style alternates") },
+  { MAKE_TAG('s','t','c','h'), NC_("OpenType layout", "Stretching Glyph Decomposition") },
+  { MAKE_TAG('s','u','b','s'), NC_("OpenType layout", "Subscript") },
+  { MAKE_TAG('s','u','p','s'), NC_("OpenType layout", "Superscript") },
+  { MAKE_TAG('s','w','s','h'), NC_("OpenType layout", "Swash") },
+  { MAKE_TAG('t','i','t','l'), NC_("OpenType layout", "Titling") },
+  { MAKE_TAG('t','j','m','o'), NC_("OpenType layout", "Trailing Jamo Forms") },
+  { MAKE_TAG('t','n','a','m'), NC_("OpenType layout", "Traditional Name Forms") },
+  { MAKE_TAG('t','n','u','m'), NC_("OpenType layout", "Tabular Figures") },
+  { MAKE_TAG('t','r','a','d'), NC_("OpenType layout", "Traditional Forms") },
+  { MAKE_TAG('t','w','i','d'), NC_("OpenType layout", "Third Widths") },
+  { MAKE_TAG('u','n','i','c'), NC_("OpenType layout", "Unicase") },
+  { MAKE_TAG('v','a','l','t'), NC_("OpenType layout", "Alternate Vertical Metrics") },
+  { MAKE_TAG('v','a','t','u'), NC_("OpenType layout", "Vattu Variants") },
+  { MAKE_TAG('v','e','r','t'), NC_("OpenType layout", "Vertical Writing") },
+  { MAKE_TAG('v','h','a','l'), NC_("OpenType layout", "Alternate Vertical Half Metrics") },
+  { MAKE_TAG('v','j','m','o'), NC_("OpenType layout", "Vowel Jamo Forms") },
+  { MAKE_TAG('v','k','n','a'), NC_("OpenType layout", "Vertical Kana Alternates") },
+  { MAKE_TAG('v','k','r','n'), NC_("OpenType layout", "Vertical Kerning") },
+  { MAKE_TAG('v','p','a','l'), NC_("OpenType layout", "Proportional Alternate Vertical Metrics") },
+  { MAKE_TAG('v','r','t','2'), NC_("OpenType layout", "Vertical Alternates and Rotation") },
+  { MAKE_TAG('v','r','t','r'), NC_("OpenType layout", "Vertical Alternates for Rotation") },
+  { MAKE_TAG('z','e','r','o'), NC_("OpenType layout", "Slashed Zero") },
+};
+
+#undef MAKE_TAG
diff --git a/gtk/ui/gtkfontchooserwidget.ui b/gtk/ui/gtkfontchooserwidget.ui
index 2182cc7..34ea6ca 100644
--- a/gtk/ui/gtkfontchooserwidget.ui
+++ b/gtk/ui/gtkfontchooserwidget.ui
@@ -214,6 +214,30 @@
             <property name="top-attach">1</property>
           </packing>
         </child>
+        <child>
+          <object class="GtkExpander" id="feature_expander">
+            <property name="label" translatable="yes">Font Features</property>
+            <child>
+              <object class="GtkBox" id="feature_box">
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkComboBox" id="feature_language_combo">
+                    <child>
+                      <object class="GtkCellRendererText"/>
+                      <attributes>
+                        <attribute name="text">0</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="left-attach">0</property>
+            <property name="top-attach">2</property>
+          </packing>
+        </child>
       </object>
     </child>
   </template>


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