gtk3/demos/gtk-demo/font_features.c
Chun-wei Fan b8e78f83ce Font features demo: Port to newer Pango
...if Pango 1.44.0+ and HarfBuzz 2.2.0+ are available, otherwise we
stick on to the current code path.

This way, we can have people build and run the demo even without
PangoFT2 if Pango 1.44.0 or later and HarfBuzz 2.2.0 or later is
installed.
2021-11-29 12:25:53 +08:00

572 lines
16 KiB
C

/* Pango/Font Features
*
* This example demonstrates support for OpenType font features with
* Pango attributes. The attributes can be used manually or via Pango
* markup.
*
* It can also be used to explore available features in OpenType fonts
* and their effect.
*/
#include <gtk/gtk.h>
#if !(PANGO_VERSION_CHECK(1,44,0) && HB_VERSION_ATLEAST(2,2,0))
#define FONT_FEATURES_USE_PANGOFT2 1
#endif
#ifdef FONT_FEATURES_USE_PANGOFT2
#include <pango/pangofc-font.h>
#include <hb.h>
#include <hb-ot.h>
#include <hb-ft.h>
#else
#include <hb-ot.h>
#endif
static GtkWidget *label;
static GtkWidget *settings;
static GtkWidget *font;
static GtkWidget *script_lang;
static GtkWidget *resetbutton;
static GtkWidget *numcasedefault;
static GtkWidget *numspacedefault;
static GtkWidget *fractiondefault;
static GtkWidget *stack;
static GtkWidget *entry;
#define num_features 40
static GtkWidget *toggle[num_features];
static GtkWidget *icon[num_features];
static const char *feature_names[num_features] = {
"kern", "liga", "dlig", "hlig", "clig", "smcp", "c2sc", "pcap", "c2pc", "unic",
"cpsp", "case", "lnum", "onum", "pnum", "tnum", "frac", "afrc", "zero", "nalt",
"sinf", "swsh", "cswh", "locl", "calt", "hist", "salt", "titl", "rand", "subs",
"sups", "init", "medi", "fina", "isol", "ss01", "ss02", "ss03", "ss04", "ss05"
};
static void
update_display (void)
{
GString *s;
char *font_desc;
char *font_settings;
const char *text;
gboolean has_feature;
int i;
hb_tag_t lang_tag;
GtkTreeModel *model;
GtkTreeIter iter;
const char *lang;
text = gtk_entry_get_text (GTK_ENTRY (entry));
font_desc = gtk_font_chooser_get_font (GTK_FONT_CHOOSER (font));
s = g_string_new ("");
has_feature = FALSE;
for (i = 0; i < num_features; i++)
{
if (!gtk_widget_is_sensitive (toggle[i]))
continue;
if (GTK_IS_RADIO_BUTTON (toggle[i]))
{
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle[i])))
{
if (has_feature)
g_string_append (s, ", ");
g_string_append (s, gtk_buildable_get_name (GTK_BUILDABLE (toggle[i])));
g_string_append (s, " 1");
has_feature = TRUE;
}
}
else
{
if (has_feature)
g_string_append (s, ", ");
g_string_append (s, gtk_buildable_get_name (GTK_BUILDABLE (toggle[i])));
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle[i])))
g_string_append (s, " 1");
else
g_string_append (s, " 0");
has_feature = TRUE;
}
}
font_settings = g_string_free (s, FALSE);
gtk_label_set_text (GTK_LABEL (settings), font_settings);
if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (script_lang), &iter))
{
model = gtk_combo_box_get_model (GTK_COMBO_BOX (script_lang));
gtk_tree_model_get (model, &iter,
3, &lang_tag,
-1);
lang = hb_language_to_string (hb_ot_tag_to_language (lang_tag));
}
else
lang = NULL;
s = g_string_new ("");
g_string_append_printf (s, "<span font_desc='%s' font_features='%s'", font_desc, font_settings);
if (lang)
g_string_append_printf (s, " lang='%s'", lang);
g_string_append_printf (s, ">%s</span>", text);
gtk_label_set_markup (GTK_LABEL (label), s->str);
g_string_free (s, TRUE);
g_free (font_desc);
g_free (font_settings);
}
static PangoFont *
get_pango_font (void)
{
PangoFontDescription *desc;
PangoContext *context;
PangoFontMap *map;
desc = gtk_font_chooser_get_font_desc (GTK_FONT_CHOOSER (font));
context = gtk_widget_get_pango_context (font);
map = pango_context_get_font_map (context);
return pango_font_map_load_font (map, context, desc);
}
static struct { const char *name; hb_script_t script; } script_names[] = {
{ "Common", HB_SCRIPT_COMMON },
{ "Inherited", HB_SCRIPT_INHERITED },
{ "Unknown", HB_SCRIPT_UNKNOWN },
{ "Arabic", HB_SCRIPT_ARABIC },
{ "Armenian", HB_SCRIPT_ARMENIAN },
{ "Bengali", HB_SCRIPT_BENGALI },
{ "Cyrillic", HB_SCRIPT_CYRILLIC },
{ "Devanagari", HB_SCRIPT_DEVANAGARI },
{ "Georgian", HB_SCRIPT_GEORGIAN },
{ "Greek", HB_SCRIPT_GREEK },
{ "Gujarati", HB_SCRIPT_GUJARATI },
{ "Gurmukhi", HB_SCRIPT_GURMUKHI },
{ "Hangul", HB_SCRIPT_HANGUL },
{ "Han", HB_SCRIPT_HAN },
{ "Hebrew", HB_SCRIPT_HEBREW },
{ "Hiragana", HB_SCRIPT_HIRAGANA },
{ "Kannada", HB_SCRIPT_KANNADA },
{ "Katakana", HB_SCRIPT_KATAKANA },
{ "Lao", HB_SCRIPT_LAO },
{ "Latin", HB_SCRIPT_LATIN },
{ "Malayalam", HB_SCRIPT_MALAYALAM },
{ "Oriya", HB_SCRIPT_ORIYA },
{ "Tamil", HB_SCRIPT_TAMIL },
{ "Telugu", HB_SCRIPT_TELUGU },
{ "Thai", HB_SCRIPT_THAI },
{ "Tibetan", HB_SCRIPT_TIBETAN },
{ "Bopomofo", HB_SCRIPT_BOPOMOFO }
/* FIXME: complete */
};
static struct { const char *name; hb_tag_t tag; } language_names[] = {
{ "Arabic", HB_TAG ('A','R','A',' ') },
{ "Romanian", HB_TAG ('R','O','M',' ') },
{ "Skolt Sami", HB_TAG ('S','K','S',' ') },
{ "Northern Sami", HB_TAG ('N','S','M',' ') },
{ "Kildin Sami", HB_TAG ('K','S','M',' ') },
{ "Moldavian", HB_TAG ('M','O','L',' ') },
{ "Turkish", HB_TAG ('T','R','K',' ') },
{ "Azerbaijani", HB_TAG ('A','Z','E',' ') },
{ "Crimean Tatar", HB_TAG ('C','R','T',' ') },
{ "Serbian", HB_TAG ('S','R','B',' ') },
{ "German", HB_TAG ('D','E','U',' ') }
/* FIXME: complete */
};
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_script_combo (void)
{
GtkListStore *store;
hb_font_t *hb_font = NULL;
gint i, j, k, l;
PangoFont *pango_font;
GHashTable *tags;
GHashTableIter iter;
TagPair *pair;
gboolean cleanup_hb_face = FALSE;
#ifdef FONT_FEATURES_USE_PANGOFT2
FT_Face ft_face;
#endif
store = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT);
pango_font = get_pango_font ();
#ifdef FONT_FEATURES_USE_PANGOFT2
if (PANGO_IS_FC_FONT (pango_font)
{
ft_face = pango_fc_font_lock_face (PANGO_FC_FONT (pango_font)),
hb_font = hb_ft_font_create (ft_face, NULL);
cleanup_hb_face = TRUE;
}
#else
hb_font = pango_font_get_hb_font (pango_font);
#endif
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_insert (tags, pair, g_strdup ("Default"));
if (hb_font)
{
hb_tag_t tables[2] = { HB_OT_TAG_GSUB, HB_OT_TAG_GPOS };
hb_face_t *hb_face;
hb_face = hb_font_get_face (hb_font);
for (i= 0; i < 2; i++)
{
hb_tag_t scripts[80];
unsigned int script_count = G_N_ELEMENTS (scripts);
hb_ot_layout_table_get_script_tags (hb_face, tables[i], 0, &script_count, scripts);
for (j = 0; j < script_count; j++)
{
hb_tag_t languages[80];
unsigned int language_count = G_N_ELEMENTS (languages);
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);
hb_ot_layout_script_get_language_tags (hb_face, tables[i], j, 0, &language_count, languages);
for (k = 0; k < language_count; 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);
}
}
}
if (cleanup_hb_face)
hb_face_destroy (hb_face);
}
#ifdef FONT_FEATURES_USE_PANGOFT2
if (PANGO_IS_FC_FONT (pango_font)
pango_fc_font_unlock_face (PANGO_FC_FONT (pango_font));
#endif
g_object_unref (pango_font);
g_hash_table_iter_init (&iter, tags);
while (g_hash_table_iter_next (&iter, (gpointer *)&pair, NULL))
{
const char *scriptname;
char scriptbuf[5];
const char *langname;
char langbuf[5];
char *name;
if (pair->script_tag == HB_OT_TAG_DEFAULT_SCRIPT)
scriptname = "Default";
else if (pair->script_tag == HB_TAG ('m','a','t','h'))
scriptname = "Math";
else
{
hb_script_t script;
hb_tag_to_string (pair->script_tag, scriptbuf);
scriptbuf[4] = 0;
scriptname = scriptbuf;
script = hb_script_from_iso15924_tag (pair->script_tag);
for (k = 0; k < G_N_ELEMENTS (script_names); k++)
{
if (script == script_names[k].script)
{
scriptname = script_names[k].name;
break;
}
}
}
if (pair->lang_tag == HB_OT_TAG_DEFAULT_LANGUAGE)
langname = "Default";
else
{
hb_tag_to_string (pair->lang_tag, langbuf);
langbuf[4] = 0;
langname = langbuf;
for (l = 0; l < G_N_ELEMENTS (language_names); l++)
{
if (pair->lang_tag == language_names[l].tag)
{
langname = language_names[l].name;
break;
}
}
}
name = g_strdup_printf ("%s - %s", scriptname, langname);
gtk_list_store_insert_with_values (store, NULL, -1,
0, name,
1, pair->script_index,
2, pair->lang_index,
3, pair->lang_tag,
-1);
g_free (name);
}
g_hash_table_destroy (tags);
gtk_combo_box_set_model (GTK_COMBO_BOX (script_lang), GTK_TREE_MODEL (store));
gtk_combo_box_set_active (GTK_COMBO_BOX (script_lang), 0);
}
static void
update_features (void)
{
gint i, j, k;
GtkTreeModel *model;
GtkTreeIter iter;
guint script_index, lang_index;
PangoFont *pango_font;
hb_font_t *hb_font = NULL;
gboolean cleanup_hb_face = FALSE;
#ifdef FONT_FEATURES_USE_PANGOFT2
FT_Face ft_face;
#endif
for (i = 0; i < num_features; i++)
gtk_widget_set_opacity (icon[i], 0);
/* set feature presence checks from the font features */
if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (script_lang), &iter))
return;
model = gtk_combo_box_get_model (GTK_COMBO_BOX (script_lang));
gtk_tree_model_get (model, &iter,
1, &script_index,
2, &lang_index,
-1);
pango_font = get_pango_font ();
#ifdef FONT_FEATURES_USE_PANGOFT2
if (PANGO_IS_FC_FONT (pango_font)
{
ft_face = pango_fc_font_lock_face (PANGO_FC_FONT (pango_font)),
hb_font = hb_ft_font_create (ft_face, NULL);
cleanup_hb_face = TRUE;
}
#else
hb_font = pango_font_get_hb_font (pango_font);
#endif
if (hb_font)
{
hb_tag_t tables[2] = { HB_OT_TAG_GSUB, HB_OT_TAG_GPOS };
hb_face_t *hb_face;
hb_face = hb_font_get_face (hb_font);
for (i = 0; i < 2; i++)
{
hb_tag_t features[80];
unsigned int count = G_N_ELEMENTS(features);
hb_ot_layout_language_get_feature_tags (hb_face,
tables[i],
script_index,
lang_index,
0,
&count,
features);
for (j = 0; j < count; j++)
{
for (k = 0; k < num_features; k++)
{
if (hb_tag_from_string (feature_names[k], -1) == features[j])
gtk_widget_set_opacity (icon[k], 0.5);
}
}
}
if (cleanup_hb_face)
hb_face_destroy (hb_face);
}
#ifdef FONT_FEATURES_USE_PANGOFT2
if (PANGO_IS_FC_FONT (pango_font)
pango_fc_font_unlock_face (PANGO_FC_FONT (pango_font));
#endif
g_object_unref (pango_font);
}
static void
font_changed (void)
{
update_script_combo ();
}
static void
script_changed (void)
{
update_features ();
update_display ();
}
static void
reset_features (void)
{
int i;
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (numcasedefault), TRUE);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (numspacedefault), TRUE);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fractiondefault), TRUE);
for (i = 0; i < num_features; i++)
{
if (!GTK_IS_RADIO_BUTTON (toggle[i]))
{
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle[i]), FALSE);
gtk_widget_set_sensitive (toggle[i], FALSE);
}
}
}
static char *text;
static void
switch_to_entry (void)
{
text = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry)));
gtk_stack_set_visible_child_name (GTK_STACK (stack), "entry");
}
static void
switch_to_label (void)
{
g_free (text);
text = NULL;
gtk_stack_set_visible_child_name (GTK_STACK (stack), "label");
update_display ();
}
static gboolean
entry_key_press (GtkEntry *entry, GdkEventKey *event)
{
if (event->keyval == GDK_KEY_Escape)
{
gtk_entry_set_text (GTK_ENTRY (entry), text);
switch_to_label ();
return GDK_EVENT_STOP;
}
return GDK_EVENT_PROPAGATE;
}
GtkWidget *
do_font_features (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkBuilder *builder;
int i;
builder = gtk_builder_new_from_resource ("/font_features/font-features.ui");
gtk_builder_add_callback_symbol (builder, "update_display", update_display);
gtk_builder_add_callback_symbol (builder, "font_changed", font_changed);
gtk_builder_add_callback_symbol (builder, "script_changed", script_changed);
gtk_builder_add_callback_symbol (builder, "reset", reset_features);
gtk_builder_add_callback_symbol (builder, "switch_to_entry", switch_to_entry);
gtk_builder_add_callback_symbol (builder, "switch_to_label", switch_to_label);
gtk_builder_add_callback_symbol (builder, "entry_key_press", G_CALLBACK (entry_key_press));
gtk_builder_connect_signals (builder, NULL);
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
label = GTK_WIDGET (gtk_builder_get_object (builder, "label"));
settings = GTK_WIDGET (gtk_builder_get_object (builder, "settings"));
resetbutton = GTK_WIDGET (gtk_builder_get_object (builder, "reset"));
font = GTK_WIDGET (gtk_builder_get_object (builder, "font"));
script_lang = GTK_WIDGET (gtk_builder_get_object (builder, "script_lang"));
numcasedefault = GTK_WIDGET (gtk_builder_get_object (builder, "numcasedefault"));
numspacedefault = GTK_WIDGET (gtk_builder_get_object (builder, "numspacedefault"));
fractiondefault = GTK_WIDGET (gtk_builder_get_object (builder, "fractiondefault"));
stack = GTK_WIDGET (gtk_builder_get_object (builder, "stack"));
entry = GTK_WIDGET (gtk_builder_get_object (builder, "entry"));
for (i = 0; i < num_features; i++)
{
char *iname;
toggle[i] = GTK_WIDGET (gtk_builder_get_object (builder, feature_names[i]));
iname = g_strconcat (feature_names[i], "_pres", NULL);
icon[i] = GTK_WIDGET (gtk_builder_get_object (builder, iname));
g_free (iname);
}
font_changed ();
g_signal_connect (window, "destroy",
G_CALLBACK (gtk_widget_destroyed), &window);
g_object_unref (builder);
}
if (!gtk_widget_get_visible (window))
gtk_window_present (GTK_WINDOW (window));
else
gtk_widget_destroy (window);
return window;
}