1/* Pango/Font Features
2 *
3 * This example demonstrates support for OpenType font features with
4 * Pango attributes. The attributes can be used manually or via Pango
5 * markup.
6 *
7 * It can also be used to explore available features in OpenType fonts
8 * and their effect.
9 */
10
11#include <gtk/gtk.h>
12#include <pango/pangofc-font.h>
13#include <hb.h>
14#include <hb-ot.h>
15#include <hb-ft.h>
16
17static GtkWidget *label;
18static GtkWidget *settings;
19static GtkWidget *font;
20static GtkWidget *script_lang;
21static GtkWidget *resetbutton;
22static GtkWidget *numcasedefault;
23static GtkWidget *numspacedefault;
24static GtkWidget *fractiondefault;
25static GtkWidget *stack;
26static GtkWidget *entry;
27
28#define num_features 40
29
30static GtkWidget *toggle[num_features];
31static GtkWidget *icon[num_features];
32static const char *feature_names[num_features] = {
33 "kern", "liga", "dlig", "hlig", "clig", "smcp", "c2sc", "pcap", "c2pc", "unic",
34 "cpsp", "case", "lnum", "onum", "pnum", "tnum", "frac", "afrc", "zero", "nalt",
35 "sinf", "swsh", "cswh", "locl", "calt", "hist", "salt", "titl", "rand", "subs",
36 "sups", "init", "medi", "fina", "isol", "ss01", "ss02", "ss03", "ss04", "ss05"
37};
38
39static void
40update_display (void)
41{
42 GString *s;
43 char *font_desc;
44 char *font_settings;
45 const char *text;
46 gboolean has_feature;
47 int i;
48 hb_tag_t lang_tag;
49 GtkTreeModel *model;
50 GtkTreeIter iter;
51 const char *lang;
52
53 text = gtk_entry_get_text (GTK_ENTRY (entry));
54
55 font_desc = gtk_font_chooser_get_font (GTK_FONT_CHOOSER (font));
56
57 s = g_string_new ("");
58
59 has_feature = FALSE;
60 for (i = 0; i < num_features; i++)
61 {
62 if (!gtk_widget_is_sensitive (toggle[i]))
63 continue;
64
65 if (GTK_IS_RADIO_BUTTON (toggle[i]))
66 {
67 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle[i])))
68 {
69 if (has_feature)
70 g_string_append (s, ", ");
71 g_string_append (s, gtk_buildable_get_name (GTK_BUILDABLE (toggle[i])));
72 g_string_append (s, " 1");
73 has_feature = TRUE;
74 }
75 }
76 else
77 {
78 if (has_feature)
79 g_string_append (s, ", ");
80 g_string_append (s, gtk_buildable_get_name (GTK_BUILDABLE (toggle[i])));
81 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle[i])))
82 g_string_append (s, " 1");
83 else
84 g_string_append (s, " 0");
85 has_feature = TRUE;
86 }
87 }
88
89 font_settings = g_string_free (s, FALSE);
90
91 gtk_label_set_text (GTK_LABEL (settings), font_settings);
92
93
94 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (script_lang), &iter))
95 {
96 model = gtk_combo_box_get_model (GTK_COMBO_BOX (script_lang));
97 gtk_tree_model_get (model, &iter,
98 3, &lang_tag,
99 -1);
100
101 lang = hb_language_to_string (hb_ot_tag_to_language (lang_tag));
102 }
103 else
104 lang = NULL;
105
106 s = g_string_new ("");
107 g_string_append_printf (s, "<span font_desc='%s' font_features='%s'", font_desc, font_settings);
108 if (lang)
109 g_string_append_printf (s, " lang='%s'", lang);
110 g_string_append_printf (s, ">%s</span>", text);
111
112 gtk_label_set_markup (GTK_LABEL (label), s->str);
113
114 g_string_free (s, TRUE);
115
116 g_free (font_desc);
117 g_free (font_settings);
118}
119
120static PangoFont *
121get_pango_font (void)
122{
123 PangoFontDescription *desc;
124 PangoContext *context;
125 PangoFontMap *map;
126
127 desc = gtk_font_chooser_get_font_desc (GTK_FONT_CHOOSER (font));
128 context = gtk_widget_get_pango_context (font);
129 map = pango_context_get_font_map (context);
130
131 return pango_font_map_load_font (map, context, desc);
132}
133
134static struct { const char *name; hb_script_t script; } script_names[] = {
135 { "Common", HB_SCRIPT_COMMON },
136 { "Inherited", HB_SCRIPT_INHERITED },
137 { "Unknown", HB_SCRIPT_UNKNOWN },
138 { "Arabic", HB_SCRIPT_ARABIC },
139 { "Armenian", HB_SCRIPT_ARMENIAN },
140 { "Bengali", HB_SCRIPT_BENGALI },
141 { "Cyrillic", HB_SCRIPT_CYRILLIC },
142 { "Devanagari", HB_SCRIPT_DEVANAGARI },
143 { "Georgian", HB_SCRIPT_GEORGIAN },
144 { "Greek", HB_SCRIPT_GREEK },
145 { "Gujarati", HB_SCRIPT_GUJARATI },
146 { "Gurmukhi", HB_SCRIPT_GURMUKHI },
147 { "Hangul", HB_SCRIPT_HANGUL },
148 { "Han", HB_SCRIPT_HAN },
149 { "Hebrew", HB_SCRIPT_HEBREW },
150 { "Hiragana", HB_SCRIPT_HIRAGANA },
151 { "Kannada", HB_SCRIPT_KANNADA },
152 { "Katakana", HB_SCRIPT_KATAKANA },
153 { "Lao", HB_SCRIPT_LAO },
154 { "Latin", HB_SCRIPT_LATIN },
155 { "Malayalam", HB_SCRIPT_MALAYALAM },
156 { "Oriya", HB_SCRIPT_ORIYA },
157 { "Tamil", HB_SCRIPT_TAMIL },
158 { "Telugu", HB_SCRIPT_TELUGU },
159 { "Thai", HB_SCRIPT_THAI },
160 { "Tibetan", HB_SCRIPT_TIBETAN },
161 { "Bopomofo", HB_SCRIPT_BOPOMOFO }
162 /* FIXME: complete */
163};
164
165static struct { const char *name; hb_tag_t tag; } language_names[] = {
166 { "Arabic", HB_TAG ('A','R','A',' ') },
167 { "Romanian", HB_TAG ('R','O','M',' ') },
168 { "Skolt Sami", HB_TAG ('S','K','S',' ') },
169 { "Northern Sami", HB_TAG ('N','S','M',' ') },
170 { "Kildin Sami", HB_TAG ('K','S','M',' ') },
171 { "Moldavian", HB_TAG ('M','O','L',' ') },
172 { "Turkish", HB_TAG ('T','R','K',' ') },
173 { "Azerbaijani", HB_TAG ('A','Z','E',' ') },
174 { "Crimean Tatar", HB_TAG ('C','R','T',' ') },
175 { "Serbian", HB_TAG ('S','R','B',' ') },
176 { "German", HB_TAG ('D','E','U',' ') }
177 /* FIXME: complete */
178};
179
180typedef struct {
181 hb_tag_t script_tag;
182 hb_tag_t lang_tag;
183 unsigned int script_index;
184 unsigned int lang_index;
185} TagPair;
186
187static guint
188tag_pair_hash (gconstpointer data)
189{
190 const TagPair *pair = data;
191
192 return pair->script_tag + pair->lang_tag;
193}
194
195static gboolean
196tag_pair_equal (gconstpointer a, gconstpointer b)
197{
198 const TagPair *pair_a = a;
199 const TagPair *pair_b = b;
200
201 return pair_a->script_tag == pair_b->script_tag && pair_a->lang_tag == pair_b->lang_tag;
202}
203
204static void
205update_script_combo (void)
206{
207 GtkListStore *store;
208 hb_font_t *hb_font;
209 gint i, j, k, l;
210 FT_Face ft_face;
211 PangoFont *pango_font;
212 GHashTable *tags;
213 GHashTableIter iter;
214 TagPair *pair;
215
216 store = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT);
217
218 pango_font = get_pango_font ();
219 ft_face = pango_fc_font_lock_face (PANGO_FC_FONT (pango_font)),
220 hb_font = hb_ft_font_create (ft_face, NULL);
221
222 tags = g_hash_table_new_full (tag_pair_hash, tag_pair_equal, g_free, NULL);
223
224 pair = g_new (TagPair, 1);
225 pair->script_tag = HB_OT_TAG_DEFAULT_SCRIPT;
226 pair->lang_tag = HB_OT_TAG_DEFAULT_LANGUAGE;
227 g_hash_table_insert (tags, pair, g_strdup ("Default"));
228
229 if (hb_font)
230 {
231 hb_tag_t tables[2] = { HB_OT_TAG_GSUB, HB_OT_TAG_GPOS };
232 hb_face_t *hb_face;
233
234 hb_face = hb_font_get_face (hb_font);
235
236 for (i= 0; i < 2; i++)
237 {
238 hb_tag_t scripts[80];
239 unsigned int script_count = G_N_ELEMENTS (scripts);
240
241 hb_ot_layout_table_get_script_tags (hb_face, tables[i], 0, &script_count, scripts);
242 for (j = 0; j < script_count; j++)
243 {
244 hb_tag_t languages[80];
245 unsigned int language_count = G_N_ELEMENTS (languages);
246
247 pair = g_new (TagPair, 1);
248 pair->script_tag = scripts[j];
249 pair->lang_tag = HB_OT_TAG_DEFAULT_LANGUAGE;
250 pair->script_index = j;
251 pair->lang_index = HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX;
252 g_hash_table_add (tags, pair);
253
254 hb_ot_layout_script_get_language_tags (hb_face, tables[i], j, 0, &language_count, languages);
255 for (k = 0; k < language_count; k++)
256 {
257 pair = g_new (TagPair, 1);
258 pair->script_tag = scripts[j];
259 pair->lang_tag = languages[k];
260 pair->script_index = j;
261 pair->lang_index = k;
262 g_hash_table_add (tags, pair);
263 }
264 }
265 }
266
267 hb_face_destroy (hb_face);
268 }
269
270 pango_fc_font_unlock_face (PANGO_FC_FONT (pango_font));
271 g_object_unref (pango_font);
272
273 g_hash_table_iter_init (&iter, tags);
274 while (g_hash_table_iter_next (&iter, (gpointer *)&pair, NULL))
275 {
276 const char *scriptname;
277 char scriptbuf[5];
278 const char *langname;
279 char langbuf[5];
280 char *name;
281
282 if (pair->script_tag == HB_OT_TAG_DEFAULT_SCRIPT)
283 scriptname = "Default";
284 else if (pair->script_tag == HB_TAG ('m','a','t','h'))
285 scriptname = "Math";
286 else
287 {
288 hb_script_t script;
289
290 hb_tag_to_string (pair->script_tag, scriptbuf);
291 scriptbuf[4] = 0;
292 scriptname = scriptbuf;
293
294 script = hb_script_from_iso15924_tag (pair->script_tag);
295 for (k = 0; k < G_N_ELEMENTS (script_names); k++)
296 {
297 if (script == script_names[k].script)
298 {
299 scriptname = script_names[k].name;
300 break;
301 }
302 }
303 }
304
305 if (pair->lang_tag == HB_OT_TAG_DEFAULT_LANGUAGE)
306 langname = "Default";
307 else
308 {
309 hb_tag_to_string (pair->lang_tag, langbuf);
310 langbuf[4] = 0;
311 langname = langbuf;
312
313 for (l = 0; l < G_N_ELEMENTS (language_names); l++)
314 {
315 if (pair->lang_tag == language_names[l].tag)
316 {
317 langname = language_names[l].name;
318 break;
319 }
320 }
321 }
322
323 name = g_strdup_printf ("%s - %s", scriptname, langname);
324
325 gtk_list_store_insert_with_values (store, NULL, -1,
326 0, name,
327 1, pair->script_index,
328 2, pair->lang_index,
329 3, pair->lang_tag,
330 -1);
331
332 g_free (name);
333 }
334
335 g_hash_table_destroy (tags);
336
337 gtk_combo_box_set_model (GTK_COMBO_BOX (script_lang), GTK_TREE_MODEL (store));
338 gtk_combo_box_set_active (GTK_COMBO_BOX (script_lang), 0);
339}
340
341static void
342update_features (void)
343{
344 gint i, j, k;
345 GtkTreeModel *model;
346 GtkTreeIter iter;
347 guint script_index, lang_index;
348 PangoFont *pango_font;
349 FT_Face ft_face;
350 hb_font_t *hb_font;
351
352 for (i = 0; i < num_features; i++)
353 gtk_widget_set_opacity (icon[i], 0);
354
355 /* set feature presence checks from the font features */
356
357 if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (script_lang), &iter))
358 return;
359
360 model = gtk_combo_box_get_model (GTK_COMBO_BOX (script_lang));
361 gtk_tree_model_get (model, &iter,
362 1, &script_index,
363 2, &lang_index,
364 -1);
365
366 pango_font = get_pango_font ();
367 ft_face = pango_fc_font_lock_face (PANGO_FC_FONT (pango_font)),
368 hb_font = hb_ft_font_create (ft_face, NULL);
369
370 if (hb_font)
371 {
372 hb_tag_t tables[2] = { HB_OT_TAG_GSUB, HB_OT_TAG_GPOS };
373 hb_face_t *hb_face;
374
375 hb_face = hb_font_get_face (hb_font);
376
377 for (i = 0; i < 2; i++)
378 {
379 hb_tag_t features[80];
380 unsigned int count = G_N_ELEMENTS(features);
381
382 hb_ot_layout_language_get_feature_tags (hb_face,
383 tables[i],
384 script_index,
385 lang_index,
386 0,
387 &count,
388 features);
389
390 for (j = 0; j < count; j++)
391 {
392 for (k = 0; k < num_features; k++)
393 {
394 if (hb_tag_from_string (feature_names[k], -1) == features[j])
395 gtk_widget_set_opacity (icon[k], 0.5);
396 }
397 }
398 }
399
400 hb_face_destroy (hb_face);
401 }
402
403 pango_fc_font_unlock_face (PANGO_FC_FONT (pango_font));
404 g_object_unref (pango_font);
405}
406
407static void
408font_changed (void)
409{
410 update_script_combo ();
411}
412
413static void
414script_changed (void)
415{
416 update_features ();
417 update_display ();
418}
419
420static void
421reset_features (void)
422{
423 int i;
424
425 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (numcasedefault), TRUE);
426 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (numspacedefault), TRUE);
427 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fractiondefault), TRUE);
428 for (i = 0; i < num_features; i++)
429 {
430 if (!GTK_IS_RADIO_BUTTON (toggle[i]))
431 {
432 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle[i]), FALSE);
433 gtk_widget_set_sensitive (toggle[i], FALSE);
434 }
435 }
436}
437
438static char *text;
439
440static void
441switch_to_entry (void)
442{
443 text = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry)));
444 gtk_stack_set_visible_child_name (GTK_STACK (stack), "entry");
445}
446
447static void
448switch_to_label (void)
449{
450 g_free (text);
451 text = NULL;
452 gtk_stack_set_visible_child_name (GTK_STACK (stack), "label");
453 update_display ();
454}
455
456static gboolean
457entry_key_press (GtkEntry *entry, GdkEventKey *event)
458{
459 if (event->keyval == GDK_KEY_Escape)
460 {
461 gtk_entry_set_text (GTK_ENTRY (entry), text);
462 switch_to_label ();
463 return GDK_EVENT_STOP;
464 }
465
466 return GDK_EVENT_PROPAGATE;
467}
468
469GtkWidget *
470do_font_features (GtkWidget *do_widget)
471{
472 static GtkWidget *window = NULL;
473
474 if (!window)
475 {
476 GtkBuilder *builder;
477 int i;
478
479 builder = gtk_builder_new_from_resource ("/font_features/font-features.ui");
480
481 gtk_builder_add_callback_symbol (builder, "update_display", update_display);
482 gtk_builder_add_callback_symbol (builder, "font_changed", font_changed);
483 gtk_builder_add_callback_symbol (builder, "script_changed", script_changed);
484 gtk_builder_add_callback_symbol (builder, "reset", reset_features);
485 gtk_builder_add_callback_symbol (builder, "switch_to_entry", switch_to_entry);
486 gtk_builder_add_callback_symbol (builder, "switch_to_label", switch_to_label);
487 gtk_builder_add_callback_symbol (builder, "entry_key_press", G_CALLBACK (entry_key_press));
488 gtk_builder_connect_signals (builder, NULL);
489
490 window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
491 label = GTK_WIDGET (gtk_builder_get_object (builder, "label"));
492 settings = GTK_WIDGET (gtk_builder_get_object (builder, "settings"));
493 resetbutton = GTK_WIDGET (gtk_builder_get_object (builder, "reset"));
494 font = GTK_WIDGET (gtk_builder_get_object (builder, "font"));
495 script_lang = GTK_WIDGET (gtk_builder_get_object (builder, "script_lang"));
496 numcasedefault = GTK_WIDGET (gtk_builder_get_object (builder, "numcasedefault"));
497 numspacedefault = GTK_WIDGET (gtk_builder_get_object (builder, "numspacedefault"));
498 fractiondefault = GTK_WIDGET (gtk_builder_get_object (builder, "fractiondefault"));
499 stack = GTK_WIDGET (gtk_builder_get_object (builder, "stack"));
500 entry = GTK_WIDGET (gtk_builder_get_object (builder, "entry"));
501
502 for (i = 0; i < num_features; i++)
503 {
504 char *iname;
505
506 toggle[i] = GTK_WIDGET (gtk_builder_get_object (builder, feature_names[i]));
507 iname = g_strconcat (feature_names[i], "_pres", NULL);
508 icon[i] = GTK_WIDGET (gtk_builder_get_object (builder, iname));
509 g_free (iname);
510 }
511
512 font_changed ();
513
514 g_signal_connect (window, "destroy",
515 G_CALLBACK (gtk_widget_destroyed), &window);
516
517 g_object_unref (builder);
518 }
519
520 if (!gtk_widget_get_visible (window))
521 gtk_window_present (GTK_WINDOW (window));
522 else
523 gtk_widget_destroy (window);
524
525 return window;
526}
527