mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-28 22:41:43 +00:00
9de3b24c20
Add more features to the list, allow selecting script/language from the set that is supported by the font, indicate which features are present in the font for the selected script/language, and expand the default specimen to cover latin, cyrillic and greek.
527 lines
15 KiB
C
527 lines
15 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>
|
|
#include <pango/pangofc-font.h>
|
|
#include <hb.h>
|
|
#include <hb-ot.h>
|
|
#include <hb-ft.h>
|
|
|
|
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;
|
|
gint i, j, k, l;
|
|
FT_Face ft_face;
|
|
PangoFont *pango_font;
|
|
GHashTable *tags;
|
|
GHashTableIter iter;
|
|
TagPair *pair;
|
|
|
|
store = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT);
|
|
|
|
pango_font = get_pango_font ();
|
|
ft_face = pango_fc_font_lock_face (PANGO_FC_FONT (pango_font)),
|
|
hb_font = hb_ft_font_create (ft_face, NULL);
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
hb_face_destroy (hb_face);
|
|
}
|
|
|
|
pango_fc_font_unlock_face (PANGO_FC_FONT (pango_font));
|
|
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;
|
|
FT_Face ft_face;
|
|
hb_font_t *hb_font;
|
|
|
|
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 ();
|
|
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 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
hb_face_destroy (hb_face);
|
|
}
|
|
|
|
pango_fc_font_unlock_face (PANGO_FC_FONT (pango_font));
|
|
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;
|
|
}
|