1/* Pango/Font Explorer
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 * If the selected font supports OpenType font variations, then the
11 * axes are also offered for customization.
12 */
13
14#include <gtk/gtk.h>
15#include <hb.h>
16#include <hb-ot.h>
17#include <glib/gi18n.h>
18
19#include "open-type-layout.h"
20#include "fontplane.h"
21#include "script-names.h"
22#include "language-names.h"
23
24
25#define MAKE_TAG(a,b,c,d) (unsigned int)(((a) << 24) | ((b) << 16) | ((c) << 8) | (d))
26
27static GtkWidget *the_label;
28static GtkWidget *settings;
29static GtkWidget *description;
30static GtkWidget *font;
31static GtkWidget *script_lang;
32static GtkWidget *resetbutton;
33static GtkWidget *stack;
34static GtkWidget *the_entry;
35static GtkWidget *variations_heading;
36static GtkWidget *variations_grid;
37static GtkWidget *instance_combo;
38static GtkWidget *edit_toggle;
39
40typedef struct {
41 unsigned int tag;
42 const char *name;
43 GtkWidget *icon;
44 GtkWidget *dflt;
45 GtkWidget *feat;
46} FeatureItem;
47
48static GList *feature_items;
49
50typedef struct {
51 unsigned int start;
52 unsigned int end;
53 PangoFontDescription *desc;
54 char *features;
55 PangoLanguage *language;
56} Range;
57
58static GList *ranges;
59
60static void add_font_variations (GString *s);
61
62static void
63free_range (gpointer data)
64{
65 Range *range = data;
66
67 if (range->desc)
68 pango_font_description_free (desc: range->desc);
69 g_free (mem: range->features);
70 g_free (mem: range);
71}
72
73static int
74compare_range (gconstpointer a, gconstpointer b)
75{
76 const Range *ra = a;
77 const Range *rb = b;
78
79 if (ra->start < rb->start)
80 return -1;
81 else if (ra->start > rb->start)
82 return 1;
83 else if (ra->end < rb->end)
84 return 1;
85 else if (ra->end > rb->end)
86 return -1;
87
88 return 0;
89}
90
91static void
92ensure_range (unsigned int start,
93 unsigned int end,
94 PangoFontDescription *desc,
95 const char *features,
96 PangoLanguage *language)
97{
98 GList *l;
99 Range *range;
100
101 for (l = ranges; l; l = l->next)
102 {
103 Range *r = l->data;
104
105 if (r->start == start && r->end == end)
106 {
107 range = r;
108 goto set;
109 }
110 }
111
112 range = g_new0 (Range, 1);
113 range->start = start;
114 range->end = end;
115
116 ranges = g_list_insert_sorted (list: ranges, data: range, func: compare_range);
117
118set:
119 if (range->desc)
120 pango_font_description_free (desc: range->desc);
121 if (desc)
122 range->desc = pango_font_description_copy (desc);
123 g_free (mem: range->features);
124 range->features = g_strdup (str: features);
125 range->language = language;
126}
127
128static const char *
129get_feature_display_name (unsigned int tag)
130{
131 int i;
132 static char buf[5] = { 0, };
133
134 if (tag == MAKE_TAG ('x', 'x', 'x', 'x'))
135 return _("Default");
136
137 for (i = 0; i < G_N_ELEMENTS (open_type_layout_features); i++)
138 {
139 if (tag == open_type_layout_features[i].tag)
140 return g_dpgettext2 (NULL, context: "OpenType layout", msgid: open_type_layout_features[i].name);
141 }
142
143 hb_tag_to_string (tag, buf);
144 g_warning ("unknown OpenType layout feature tag: %s", buf);
145
146 return buf;
147}
148
149static void update_display (void);
150
151static void
152set_inconsistent (GtkCheckButton *button,
153 gboolean inconsistent)
154{
155 gtk_check_button_set_inconsistent (GTK_CHECK_BUTTON (button), inconsistent);
156 gtk_widget_set_opacity (widget: gtk_widget_get_first_child (GTK_WIDGET (button)), opacity: inconsistent ? 0.0 : 1.0);
157}
158
159static void
160feat_pressed (GtkGestureClick *gesture,
161 int n_press,
162 double x,
163 double y,
164 GtkWidget *feat)
165{
166 const guint button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
167
168 if (button == GDK_BUTTON_PRIMARY)
169 {
170 g_signal_handlers_block_by_func (feat, feat_pressed, NULL);
171
172 if (gtk_check_button_get_inconsistent (GTK_CHECK_BUTTON (feat)))
173 {
174 set_inconsistent (GTK_CHECK_BUTTON (feat), FALSE);
175 gtk_check_button_set_active (GTK_CHECK_BUTTON (feat), TRUE);
176 }
177
178 g_signal_handlers_unblock_by_func (feat, feat_pressed, NULL);
179 }
180 else if (button == GDK_BUTTON_SECONDARY)
181 {
182 gboolean inconsistent = gtk_check_button_get_inconsistent (GTK_CHECK_BUTTON (feat));
183 set_inconsistent (GTK_CHECK_BUTTON (feat), inconsistent: !inconsistent);
184 }
185}
186
187static void
188feat_toggled_cb (GtkCheckButton *check_button,
189 gpointer data)
190{
191 set_inconsistent (button: check_button, FALSE);
192}
193
194static void
195add_check_group (GtkWidget *box,
196 const char *title,
197 const char **tags)
198{
199 GtkWidget *label;
200 GtkWidget *group;
201 PangoAttrList *attrs;
202 int i;
203
204 group = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0);
205 gtk_widget_set_halign (widget: group, align: GTK_ALIGN_START);
206
207 label = gtk_label_new (str: title);
208 gtk_label_set_xalign (GTK_LABEL (label), xalign: 0.0);
209 gtk_widget_set_halign (widget: label, align: GTK_ALIGN_START);
210 g_object_set (object: label, first_property_name: "margin-top", 10, "margin-bottom", 10, NULL);
211 attrs = pango_attr_list_new ();
212 pango_attr_list_insert (list: attrs, attr: pango_attr_weight_new (weight: PANGO_WEIGHT_BOLD));
213 gtk_label_set_attributes (GTK_LABEL (label), attrs);
214 pango_attr_list_unref (list: attrs);
215 gtk_box_append (GTK_BOX (group), child: label);
216
217 for (i = 0; tags[i]; i++)
218 {
219 unsigned int tag;
220 GtkWidget *feat;
221 FeatureItem *item;
222 GtkGesture *gesture;
223
224 tag = hb_tag_from_string (str: tags[i], len: -1);
225
226 feat = gtk_check_button_new_with_label (label: get_feature_display_name (tag));
227 set_inconsistent (GTK_CHECK_BUTTON (feat), TRUE);
228
229 g_signal_connect (feat, "notify::active", G_CALLBACK (update_display), NULL);
230 g_signal_connect (feat, "notify::inconsistent", G_CALLBACK (update_display), NULL);
231 g_signal_connect (feat, "toggled", G_CALLBACK (feat_toggled_cb), NULL);
232
233 gesture = gtk_gesture_click_new ();
234 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), GDK_BUTTON_SECONDARY);
235 g_signal_connect (gesture, "pressed", G_CALLBACK (feat_pressed), feat);
236 gtk_widget_add_controller (widget: feat, GTK_EVENT_CONTROLLER (gesture));
237
238 gtk_box_append (GTK_BOX (group), child: feat);
239
240 item = g_new (FeatureItem, 1);
241 item->name = tags[i];
242 item->tag = tag;
243 item->icon = NULL;
244 item->dflt = NULL;
245 item->feat = feat;
246
247 feature_items = g_list_prepend (list: feature_items, data: item);
248 }
249
250 gtk_box_append (GTK_BOX (box), child: group);
251}
252
253static void
254add_radio_group (GtkWidget *box,
255 const char *title,
256 const char **tags)
257{
258 GtkWidget *label;
259 GtkWidget *group;
260 int i;
261 GtkWidget *group_button = NULL;
262 PangoAttrList *attrs;
263
264 group = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0);
265 gtk_widget_set_halign (widget: group, align: GTK_ALIGN_START);
266
267 label = gtk_label_new (str: title);
268 gtk_label_set_xalign (GTK_LABEL (label), xalign: 0.0);
269 gtk_widget_set_halign (widget: label, align: GTK_ALIGN_START);
270 g_object_set (object: label, first_property_name: "margin-top", 10, "margin-bottom", 10, NULL);
271 attrs = pango_attr_list_new ();
272 pango_attr_list_insert (list: attrs, attr: pango_attr_weight_new (weight: PANGO_WEIGHT_BOLD));
273 gtk_label_set_attributes (GTK_LABEL (label), attrs);
274 pango_attr_list_unref (list: attrs);
275 gtk_box_append (GTK_BOX (group), child: label);
276
277 for (i = 0; tags[i]; i++)
278 {
279 unsigned int tag;
280 GtkWidget *feat;
281 FeatureItem *item;
282 const char *name;
283
284 tag = hb_tag_from_string (str: tags[i], len: -1);
285 name = get_feature_display_name (tag);
286
287 feat = gtk_check_button_new_with_label (label: name ? name : _("Default"));
288 if (group_button == NULL)
289 group_button = feat;
290 else
291 gtk_check_button_set_group (GTK_CHECK_BUTTON (feat), GTK_CHECK_BUTTON (group_button));
292
293 g_signal_connect (feat, "notify::active", G_CALLBACK (update_display), NULL);
294 g_object_set_data (G_OBJECT (feat), key: "default", data: group_button);
295
296 gtk_box_append (GTK_BOX (group), child: feat);
297
298 item = g_new (FeatureItem, 1);
299 item->name = tags[i];
300 item->tag = tag;
301 item->icon = NULL;
302 item->dflt = NULL;
303 item->feat = feat;
304
305 feature_items = g_list_prepend (list: feature_items, data: item);
306 }
307
308 gtk_box_append (GTK_BOX (box), child: group);
309}
310
311static void
312update_display (void)
313{
314 GString *s;
315 const char *text;
316 gboolean has_feature;
317 GtkTreeIter iter;
318 GtkTreeModel *model;
319 PangoFontDescription *desc;
320 GList *l;
321 PangoAttrList *attrs;
322 PangoAttribute *attr;
323 int ins, bound;
324 guint start, end;
325 PangoLanguage *lang;
326 char *font_desc;
327 char *features;
328
329 text = gtk_editable_get_text (GTK_EDITABLE (the_entry));
330
331 if (gtk_label_get_selection_bounds (GTK_LABEL (the_label), start: &ins, end: &bound))
332 {
333 start = g_utf8_offset_to_pointer (str: text, offset: ins) - text;
334 end = g_utf8_offset_to_pointer (str: text, offset: bound) - text;
335 }
336 else
337 {
338 start = PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING;
339 end = PANGO_ATTR_INDEX_TO_TEXT_END;
340 }
341
342 desc = gtk_font_chooser_get_font_desc (GTK_FONT_CHOOSER (font));
343
344 s = g_string_new (init: "");
345 add_font_variations (s);
346 if (s->len > 0)
347 {
348 pango_font_description_set_variations (desc, variations: s->str);
349 g_string_free (string: s, TRUE);
350 }
351
352 font_desc = pango_font_description_to_string (desc);
353
354 s = g_string_new (init: "");
355
356 has_feature = FALSE;
357 for (l = feature_items; l; l = l->next)
358 {
359 FeatureItem *item = l->data;
360
361 if (!gtk_widget_is_sensitive (widget: item->feat))
362 continue;
363
364 if (GTK_IS_CHECK_BUTTON (item->feat))
365 {
366 if (g_object_get_data (G_OBJECT (item->feat), key: "default"))
367 {
368 if (gtk_check_button_get_active (GTK_CHECK_BUTTON (item->feat)) &&
369 strcmp (s1: item->name, s2: "xxxx") != 0)
370 {
371 if (has_feature)
372 g_string_append (string: s, val: ", ");
373 g_string_append (string: s, val: item->name);
374 g_string_append (string: s, val: " 1");
375 has_feature = TRUE;
376 }
377 }
378 else
379 {
380 if (gtk_check_button_get_inconsistent (GTK_CHECK_BUTTON (item->feat)))
381 continue;
382
383 if (has_feature)
384 g_string_append (string: s, val: ", ");
385 g_string_append (string: s, val: item->name);
386 if (gtk_check_button_get_active (GTK_CHECK_BUTTON (item->feat)))
387 g_string_append (string: s, val: " 1");
388 else
389 g_string_append (string: s, val: " 0");
390 has_feature = TRUE;
391 }
392 }
393 }
394
395 features = g_string_free (string: s, FALSE);
396
397 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (script_lang), iter: &iter))
398 {
399 hb_tag_t lang_tag;
400
401 model = gtk_combo_box_get_model (GTK_COMBO_BOX (script_lang));
402 gtk_tree_model_get (tree_model: model, iter: &iter,
403 3, &lang_tag,
404 -1);
405
406 lang = pango_language_from_string (language: hb_language_to_string (language: hb_ot_tag_to_language (tag: lang_tag)));
407 }
408 else
409 lang = NULL;
410
411 ensure_range (start, end, desc, features, language: lang);
412
413 attrs = pango_attr_list_new ();
414
415 for (l = ranges; l; l = l->next)
416 {
417 Range *range = l->data;
418
419 attr = pango_attr_font_desc_new (desc: range->desc);
420 attr->start_index = range->start;
421 attr->end_index = range->end;
422 pango_attr_list_insert (list: attrs, attr);
423
424 attr = pango_attr_font_features_new (features: range->features);
425 attr->start_index = range->start;
426 attr->end_index = range->end;
427 pango_attr_list_insert (list: attrs, attr);
428
429 if (range->language)
430 {
431 attr = pango_attr_language_new (language: range->language);
432 attr->start_index = range->start;
433 attr->end_index = range->end;
434 pango_attr_list_insert (list: attrs, attr);
435 }
436 }
437
438 gtk_label_set_text (GTK_LABEL (description), str: font_desc);
439 gtk_label_set_text (GTK_LABEL (settings), str: features);
440 gtk_label_set_text (GTK_LABEL (the_label), str: text);
441 gtk_label_set_attributes (GTK_LABEL (the_label), attrs);
442
443 g_free (mem: font_desc);
444 pango_font_description_free (desc);
445 g_free (mem: features);
446 pango_attr_list_unref (list: attrs);
447}
448
449static PangoFont *
450get_pango_font (void)
451{
452 PangoFontDescription *desc;
453 PangoContext *context;
454
455 desc = gtk_font_chooser_get_font_desc (GTK_FONT_CHOOSER (font));
456 context = gtk_widget_get_pango_context (widget: font);
457
458 return pango_context_load_font (context, desc);
459}
460
461typedef struct {
462 hb_tag_t script_tag;
463 hb_tag_t lang_tag;
464 unsigned int script_index;
465 unsigned int lang_index;
466} TagPair;
467
468static guint
469tag_pair_hash (gconstpointer data)
470{
471 const TagPair *pair = data;
472
473 return pair->script_tag + pair->lang_tag;
474}
475
476static gboolean
477tag_pair_equal (gconstpointer a, gconstpointer b)
478{
479 const TagPair *pair_a = a;
480 const TagPair *pair_b = b;
481
482 return pair_a->script_tag == pair_b->script_tag && pair_a->lang_tag == pair_b->lang_tag;
483}
484
485static int
486script_sort_func (GtkTreeModel *model,
487 GtkTreeIter *a,
488 GtkTreeIter *b,
489 gpointer user_data)
490{
491 char *sa, *sb;
492 int ret;
493
494 gtk_tree_model_get (tree_model: model, iter: a, 0, &sa, -1);
495 gtk_tree_model_get (tree_model: model, iter: b, 0, &sb, -1);
496
497 ret = strcmp (s1: sa, s2: sb);
498
499 g_free (mem: sa);
500 g_free (mem: sb);
501
502 return ret;
503}
504
505static void
506update_script_combo (void)
507{
508 GtkListStore *store;
509 hb_font_t *hb_font;
510 int i, j, k;
511 PangoFont *pango_font;
512 GHashTable *tags;
513 GHashTableIter iter;
514 TagPair *pair;
515 char *lang;
516 hb_tag_t active;
517 GtkTreeIter active_iter;
518 gboolean have_active = FALSE;
519
520 lang = gtk_font_chooser_get_language (GTK_FONT_CHOOSER (font));
521
522 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
523 active = hb_ot_tag_from_language (language: hb_language_from_string (str: lang, len: -1));
524 G_GNUC_END_IGNORE_DEPRECATIONS
525
526 g_free (mem: lang);
527
528 store = gtk_list_store_new (n_columns: 4, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT);
529
530 pango_font = get_pango_font ();
531 hb_font = pango_font_get_hb_font (font: pango_font);
532
533 tags = g_hash_table_new_full (hash_func: tag_pair_hash, key_equal_func: tag_pair_equal, key_destroy_func: g_free, NULL);
534
535 pair = g_new (TagPair, 1);
536 pair->script_tag = HB_OT_TAG_DEFAULT_SCRIPT;
537 pair->lang_tag = HB_OT_TAG_DEFAULT_LANGUAGE;
538 g_hash_table_add (hash_table: tags, key: pair);
539
540 if (hb_font)
541 {
542 hb_tag_t tables[2] = { HB_OT_TAG_GSUB, HB_OT_TAG_GPOS };
543 hb_face_t *hb_face;
544
545 hb_face = hb_font_get_face (font: hb_font);
546
547 for (i= 0; i < 2; i++)
548 {
549 hb_tag_t scripts[80];
550 unsigned int script_count = G_N_ELEMENTS (scripts);
551
552 hb_ot_layout_table_get_script_tags (face: hb_face, table_tag: tables[i], start_offset: 0, script_count: &script_count, script_tags: scripts);
553 for (j = 0; j < script_count; j++)
554 {
555 hb_tag_t languages[80];
556 unsigned int language_count = G_N_ELEMENTS (languages);
557
558 hb_ot_layout_script_get_language_tags (face: hb_face, table_tag: tables[i], script_index: j, start_offset: 0, language_count: &language_count, language_tags: languages);
559 for (k = 0; k < language_count; k++)
560 {
561 pair = g_new (TagPair, 1);
562 pair->script_tag = scripts[j];
563 pair->lang_tag = languages[k];
564 pair->script_index = j;
565 pair->lang_index = k;
566 g_hash_table_add (hash_table: tags, key: pair);
567 }
568 }
569 }
570 }
571
572 g_object_unref (object: pango_font);
573
574 g_hash_table_iter_init (iter: &iter, hash_table: tags);
575 while (g_hash_table_iter_next (iter: &iter, key: (gpointer *)&pair, NULL))
576 {
577 const char *langname;
578 char langbuf[5];
579 GtkTreeIter tree_iter;
580
581 if (pair->lang_tag == HB_OT_TAG_DEFAULT_LANGUAGE)
582 langname = NC_("Language", "Default");
583 else
584 {
585 langname = get_language_name_for_tag (tag: pair->lang_tag);
586 if (!langname)
587 {
588 hb_tag_to_string (tag: pair->lang_tag, buf: langbuf);
589 langbuf[4] = 0;
590 langname = langbuf;
591 }
592 }
593
594 gtk_list_store_insert_with_values (list_store: store, iter: &tree_iter, position: -1,
595 0, langname,
596 1, pair->script_index,
597 2, pair->lang_index,
598 3, pair->lang_tag,
599 -1);
600 if (pair->lang_tag == active)
601 {
602 have_active = TRUE;
603 active_iter = tree_iter;
604 }
605 }
606
607 g_hash_table_destroy (hash_table: tags);
608
609 gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (store),
610 sort_func: script_sort_func, NULL, NULL);
611 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
612 GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
613 order: GTK_SORT_ASCENDING);
614 gtk_combo_box_set_model (GTK_COMBO_BOX (script_lang), GTK_TREE_MODEL (store));
615 if (have_active)
616 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (script_lang), iter: &active_iter);
617 else
618 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (script_lang), iter: 0);
619}
620
621static void
622update_features (void)
623{
624 int i, j;
625 GtkTreeModel *model;
626 GtkTreeIter iter;
627 guint script_index, lang_index;
628 PangoFont *pango_font;
629 hb_font_t *hb_font;
630 GList *l;
631
632 for (l = feature_items; l; l = l->next)
633 {
634 FeatureItem *item = l->data;
635 gtk_widget_hide (widget: item->feat);
636 gtk_widget_hide (widget: gtk_widget_get_parent (widget: item->feat));
637 if (strcmp (s1: item->name, s2: "xxxx") == 0)
638 gtk_check_button_set_active (GTK_CHECK_BUTTON (item->feat), TRUE);
639 }
640
641 /* set feature presence checks from the font features */
642
643 if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (script_lang), iter: &iter))
644 return;
645
646 model = gtk_combo_box_get_model (GTK_COMBO_BOX (script_lang));
647 gtk_tree_model_get (tree_model: model, iter: &iter,
648 1, &script_index,
649 2, &lang_index,
650 -1);
651
652 pango_font = get_pango_font ();
653 hb_font = pango_font_get_hb_font (font: pango_font);
654
655 if (hb_font)
656 {
657 hb_tag_t tables[2] = { HB_OT_TAG_GSUB, HB_OT_TAG_GPOS };
658 hb_face_t *hb_face;
659 char *feat;
660
661 hb_face = hb_font_get_face (font: hb_font);
662
663 for (i = 0; i < 2; i++)
664 {
665 hb_tag_t features[80];
666 unsigned int count = G_N_ELEMENTS(features);
667
668 hb_ot_layout_language_get_feature_tags (face: hb_face,
669 table_tag: tables[i],
670 script_index,
671 language_index: lang_index,
672 start_offset: 0,
673 feature_count: &count,
674 feature_tags: features);
675
676 for (j = 0; j < count; j++)
677 {
678#if 0
679 char buf[5];
680 hb_tag_to_string (features[j], buf);
681 buf[4] = 0;
682 g_print ("%s present in %s\n", buf, i == 0 ? "GSUB" : "GPOS");
683#endif
684 for (l = feature_items; l; l = l->next)
685 {
686 FeatureItem *item = l->data;
687
688 if (item->tag == features[j])
689 {
690 gtk_widget_show (widget: item->feat);
691 gtk_widget_show (widget: gtk_widget_get_parent (widget: item->feat));
692 if (GTK_IS_CHECK_BUTTON (item->feat))
693 {
694 GtkWidget *def = GTK_WIDGET (g_object_get_data (G_OBJECT (item->feat), "default"));
695 if (def)
696 {
697 gtk_widget_show (widget: def);
698 gtk_widget_show (widget: gtk_widget_get_parent (widget: def));
699 gtk_check_button_set_active (GTK_CHECK_BUTTON (def), TRUE);
700 }
701 else
702 set_inconsistent (GTK_CHECK_BUTTON (item->feat), TRUE);
703 }
704 }
705 }
706 }
707 }
708
709 feat = gtk_font_chooser_get_font_features (GTK_FONT_CHOOSER (font));
710 if (feat)
711 {
712 for (l = feature_items; l; l = l->next)
713 {
714 FeatureItem *item = l->data;
715 char buf[5];
716 char *p;
717
718 hb_tag_to_string (tag: item->tag, buf);
719 buf[4] = 0;
720
721 p = strstr (haystack: feat, needle: buf);
722 if (p)
723 {
724 if (GTK_IS_CHECK_BUTTON (item->feat) && g_object_get_data (G_OBJECT (item->feat), key: "default"))
725 {
726 gtk_check_button_set_active (GTK_CHECK_BUTTON (item->feat), setting: p[6] == '1');
727 }
728 else if (GTK_IS_CHECK_BUTTON (item->feat))
729 {
730 set_inconsistent (GTK_CHECK_BUTTON (item->feat), FALSE);
731 gtk_check_button_set_active (GTK_CHECK_BUTTON (item->feat), setting: p[6] == '1');
732 }
733 }
734 }
735
736 g_free (mem: feat);
737 }
738 }
739
740 g_object_unref (object: pango_font);
741}
742
743#define FixedToFloat(f) (((float)(f))/65536.0)
744
745static void
746adjustment_changed (GtkAdjustment *adjustment,
747 GtkEntry *entry)
748{
749 char *str;
750
751 str = g_strdup_printf (format: "%g", gtk_adjustment_get_value (adjustment));
752 gtk_editable_set_text (GTK_EDITABLE (entry), text: str);
753 g_free (mem: str);
754
755 update_display ();
756}
757
758static void
759entry_activated (GtkEntry *entry,
760 GtkAdjustment *adjustment)
761{
762 double value;
763 char *err = NULL;
764
765 value = g_strtod (nptr: gtk_editable_get_text (GTK_EDITABLE (entry)), endptr: &err);
766 if (err != NULL)
767 gtk_adjustment_set_value (adjustment, value);
768}
769
770static void unset_instance (GtkAdjustment *adjustment);
771
772typedef struct {
773 guint32 tag;
774 GtkAdjustment *adjustment;
775} Axis;
776
777static GHashTable *axes;
778
779static void
780add_font_variations (GString *s)
781{
782 GHashTableIter iter;
783 Axis *axis;
784 char buf[G_ASCII_DTOSTR_BUF_SIZE];
785 const char *sep = "";
786
787 g_hash_table_iter_init (iter: &iter, hash_table: axes);
788 while (g_hash_table_iter_next (iter: &iter, key: (gpointer *)NULL, value: (gpointer *)&axis))
789 {
790 char tag[5];
791 double value;
792
793 hb_tag_to_string (tag: axis->tag, buf: tag);
794 tag[4] = '\0';
795 value = gtk_adjustment_get_value (adjustment: axis->adjustment);
796
797 g_string_append_printf (string: s, format: "%s%s=%s", sep, tag, g_ascii_dtostr (buffer: buf, buf_len: sizeof (buf), d: value));
798 sep = ",";
799 }
800}
801
802static guint
803axes_hash (gconstpointer v)
804{
805 const Axis *p = v;
806
807 return p->tag;
808}
809
810static gboolean
811axes_equal (gconstpointer v1, gconstpointer v2)
812{
813 const Axis *p1 = v1;
814 const Axis *p2 = v2;
815
816 return p1->tag == p2->tag;
817}
818
819static void
820add_axis (hb_face_t *hb_face,
821 hb_ot_var_axis_info_t *ax,
822 float value,
823 int i)
824{
825 GtkWidget *axis_label;
826 GtkWidget *axis_entry;
827 GtkWidget *axis_scale;
828 GtkAdjustment *adjustment;
829 Axis *axis;
830 char name[20];
831 unsigned int name_len = 20;
832
833 hb_ot_name_get_utf8 (face: hb_face, name_id: ax->name_id, HB_LANGUAGE_INVALID, text_size: &name_len, text: name);
834
835 axis_label = gtk_label_new (str: name);
836 gtk_widget_set_halign (widget: axis_label, align: GTK_ALIGN_START);
837 gtk_widget_set_valign (widget: axis_label, align: GTK_ALIGN_BASELINE);
838 gtk_grid_attach (GTK_GRID (variations_grid), child: axis_label, column: 0, row: i, width: 1, height: 1);
839 adjustment = gtk_adjustment_new (value, lower: ax->min_value, upper: ax->max_value,
840 step_increment: 1.0, page_increment: 10.0, page_size: 0.0);
841 axis_scale = gtk_scale_new (orientation: GTK_ORIENTATION_HORIZONTAL, adjustment);
842 gtk_scale_add_mark (GTK_SCALE (axis_scale), value: ax->default_value, position: GTK_POS_TOP, NULL);
843 gtk_widget_set_valign (widget: axis_scale, align: GTK_ALIGN_BASELINE);
844 gtk_widget_set_hexpand (widget: axis_scale, TRUE);
845 gtk_widget_set_size_request (widget: axis_scale, width: 100, height: -1);
846 gtk_scale_set_draw_value (GTK_SCALE (axis_scale), FALSE);
847 gtk_grid_attach (GTK_GRID (variations_grid), child: axis_scale, column: 1, row: i, width: 1, height: 1);
848 axis_entry = gtk_entry_new ();
849 gtk_widget_set_valign (widget: axis_entry, align: GTK_ALIGN_BASELINE);
850 gtk_editable_set_width_chars (GTK_EDITABLE (axis_entry), n_chars: 4);
851 gtk_grid_attach (GTK_GRID (variations_grid), child: axis_entry, column: 2, row: i, width: 1, height: 1);
852
853 axis = g_new (Axis, 1);
854 axis->tag = ax->tag;
855 axis->adjustment = adjustment;
856 g_hash_table_add (hash_table: axes, key: axis);
857
858 adjustment_changed (adjustment, GTK_ENTRY (axis_entry));
859
860 g_signal_connect (adjustment, "value-changed", G_CALLBACK (adjustment_changed), axis_entry);
861 g_signal_connect (adjustment, "value-changed", G_CALLBACK (unset_instance), NULL);
862 g_signal_connect (axis_entry, "activate", G_CALLBACK (entry_activated), adjustment);
863}
864
865typedef struct {
866 char *name;
867 unsigned int index;
868} Instance;
869
870static guint
871instance_hash (gconstpointer v)
872{
873 const Instance *p = v;
874
875 return g_str_hash (v: p->name);
876}
877
878static gboolean
879instance_equal (gconstpointer v1, gconstpointer v2)
880{
881 const Instance *p1 = v1;
882 const Instance *p2 = v2;
883
884 return g_str_equal (v1: p1->name, v2: p2->name);
885}
886
887static void
888free_instance (gpointer data)
889{
890 Instance *instance = data;
891
892 g_free (mem: instance->name);
893 g_free (mem: instance);
894}
895
896static GHashTable *instances;
897
898static void
899add_instance (hb_face_t *face,
900 unsigned int index,
901 GtkWidget *combo,
902 int pos)
903{
904 Instance *instance;
905 hb_ot_name_id_t name_id;
906 char name[20];
907 unsigned int name_len = 20;
908
909 instance = g_new0 (Instance, 1);
910
911 name_id = hb_ot_var_named_instance_get_subfamily_name_id (face, instance_index: index);
912 hb_ot_name_get_utf8 (face, name_id, HB_LANGUAGE_INVALID, text_size: &name_len, text: name);
913
914 instance->name = g_strdup (str: name);
915 instance->index = index;
916
917 g_hash_table_add (hash_table: instances, key: instance);
918 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), text: instance->name);
919}
920
921static void
922unset_instance (GtkAdjustment *adjustment)
923{
924 if (instance_combo)
925 gtk_combo_box_set_active (GTK_COMBO_BOX (instance_combo), index_: 0);
926}
927
928static void
929instance_changed (GtkComboBox *combo)
930{
931 char *text;
932 Instance *instance;
933 Instance ikey;
934 int i;
935 unsigned int coords_length;
936 float *coords = NULL;
937 hb_ot_var_axis_info_t *ai = NULL;
938 unsigned int n_axes;
939 PangoFont *pango_font = NULL;
940 hb_font_t *hb_font;
941 hb_face_t *hb_face;
942
943 text = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (combo));
944 if (text[0] == '\0')
945 goto out;
946
947 ikey.name = text;
948 instance = g_hash_table_lookup (hash_table: instances, key: &ikey);
949 if (!instance)
950 {
951 g_print (format: "did not find instance %s\n", text);
952 goto out;
953 }
954
955 pango_font = get_pango_font ();
956 hb_font = pango_font_get_hb_font (font: pango_font);
957 hb_face = hb_font_get_face (font: hb_font);
958
959 n_axes = hb_ot_var_get_axis_infos (face: hb_face, start_offset: 0, NULL, NULL);
960 ai = g_new (hb_ot_var_axis_info_t, n_axes);
961 hb_ot_var_get_axis_infos (face: hb_face, start_offset: 0, axes_count: &n_axes, axes_array: ai);
962
963 coords = g_new (float, n_axes);
964 hb_ot_var_named_instance_get_design_coords (face: hb_face,
965 instance_index: instance->index,
966 coords_length: &coords_length,
967 coords);
968
969 for (i = 0; i < n_axes; i++)
970 {
971 Axis *axis;
972 Axis akey;
973 double value;
974
975 value = coords[ai[i].axis_index];
976
977 akey.tag = ai[i].tag;
978 axis = g_hash_table_lookup (hash_table: axes, key: &akey);
979 if (axis)
980 {
981 g_signal_handlers_block_by_func (axis->adjustment, unset_instance, NULL);
982 gtk_adjustment_set_value (adjustment: axis->adjustment, value);
983 g_signal_handlers_unblock_by_func (axis->adjustment, unset_instance, NULL);
984 }
985 }
986
987out:
988 g_free (mem: text);
989 g_clear_object (&pango_font);
990 g_free (mem: ai);
991 g_free (mem: coords);
992}
993
994static gboolean
995matches_instance (hb_face_t *hb_face,
996 unsigned int index,
997 unsigned int n_axes,
998 float *coords)
999{
1000 float *instance_coords;
1001 unsigned int coords_length;
1002 int i;
1003
1004 instance_coords = g_new (float, n_axes);
1005 coords_length = n_axes;
1006
1007 hb_ot_var_named_instance_get_design_coords (face: hb_face,
1008 instance_index: index,
1009 coords_length: &coords_length,
1010 coords: instance_coords);
1011
1012 for (i = 0; i < n_axes; i++)
1013 if (instance_coords[i] != coords[i])
1014 return FALSE;
1015
1016 return TRUE;
1017}
1018
1019static void
1020add_font_plane (int i)
1021{
1022 GtkWidget *plane;
1023 Axis *weight_axis;
1024 Axis *width_axis;
1025
1026 Axis key;
1027
1028 key.tag = MAKE_TAG('w','g','h','t');
1029 weight_axis = g_hash_table_lookup (hash_table: axes, key: &key);
1030 key.tag = MAKE_TAG('w','d','t','h');
1031 width_axis = g_hash_table_lookup (hash_table: axes, key: &key);
1032
1033 if (weight_axis && width_axis)
1034 {
1035 plane = gtk_font_plane_new (width_adj: weight_axis->adjustment,
1036 weight_adj: width_axis->adjustment);
1037
1038 gtk_widget_set_size_request (widget: plane, width: 300, height: 300);
1039 gtk_widget_set_halign (widget: plane, align: GTK_ALIGN_CENTER);
1040 gtk_grid_attach (GTK_GRID (variations_grid), child: plane, column: 0, row: i, width: 3, height: 1);
1041 }
1042}
1043
1044/* FIXME: This doesn't work if the font has an avar table */
1045static float
1046denorm_coord (hb_ot_var_axis_info_t *axis, int coord)
1047{
1048 float r = coord / 16384.0;
1049
1050 if (coord < 0)
1051 return axis->default_value + r * (axis->default_value - axis->min_value);
1052 else
1053 return axis->default_value + r * (axis->max_value - axis->default_value);
1054}
1055
1056static void
1057update_font_variations (void)
1058{
1059 GtkWidget *child;
1060 PangoFont *pango_font = NULL;
1061 hb_font_t *hb_font;
1062 hb_face_t *hb_face;
1063 unsigned int n_axes;
1064 hb_ot_var_axis_info_t *ai = NULL;
1065 float *design_coords = NULL;
1066 const int *coords;
1067 unsigned int length;
1068 int i;
1069
1070 while ((child = gtk_widget_get_first_child (widget: variations_grid)))
1071 gtk_grid_remove (GTK_GRID (variations_grid), child);
1072
1073 instance_combo = NULL;
1074
1075 g_hash_table_remove_all (hash_table: axes);
1076 g_hash_table_remove_all (hash_table: instances);
1077
1078 pango_font = get_pango_font ();
1079 hb_font = pango_font_get_hb_font (font: pango_font);
1080 hb_face = hb_font_get_face (font: hb_font);
1081
1082 n_axes = hb_ot_var_get_axis_infos (face: hb_face, start_offset: 0, NULL, NULL);
1083 if (n_axes == 0)
1084 goto done;
1085
1086 ai = g_new0 (hb_ot_var_axis_info_t, n_axes);
1087 design_coords = g_new (float, n_axes);
1088
1089 hb_ot_var_get_axis_infos (face: hb_face, start_offset: 0, axes_count: &n_axes, axes_array: ai);
1090 coords = hb_font_get_var_coords_normalized (font: hb_font, length: &length);
1091 for (i = 0; i < length; i++)
1092 design_coords[i] = denorm_coord (axis: &ai[i], coord: coords[i]);
1093
1094 if (hb_ot_var_get_named_instance_count (face: hb_face) > 0)
1095 {
1096 GtkWidget *label;
1097 GtkWidget *combo;
1098
1099 label = gtk_label_new (str: "Instance");
1100 gtk_label_set_xalign (GTK_LABEL (label), xalign: 0);
1101 gtk_widget_set_halign (widget: label, align: GTK_ALIGN_START);
1102 gtk_widget_set_valign (widget: label, align: GTK_ALIGN_BASELINE);
1103 gtk_grid_attach (GTK_GRID (variations_grid), child: label, column: 0, row: -1, width: 2, height: 1);
1104
1105 combo = gtk_combo_box_text_new ();
1106 gtk_widget_set_valign (widget: combo, align: GTK_ALIGN_BASELINE);
1107
1108 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), text: "");
1109
1110 for (i = 0; i < hb_ot_var_get_named_instance_count (face: hb_face); i++)
1111 add_instance (face: hb_face, index: i, combo, pos: i);
1112
1113 for (i = 0; i < hb_ot_var_get_named_instance_count (face: hb_face); i++)
1114 {
1115 if (matches_instance (hb_face, index: i, n_axes, coords: design_coords))
1116 {
1117 gtk_combo_box_set_active (GTK_COMBO_BOX (combo), index_: i + 1);
1118 break;
1119 }
1120 }
1121
1122 gtk_grid_attach (GTK_GRID (variations_grid), child: combo, column: 1, row: -1, width: 2, height: 1);
1123 g_signal_connect (combo, "changed", G_CALLBACK (instance_changed), NULL);
1124 instance_combo = combo;
1125 }
1126
1127 for (i = 0; i < n_axes; i++)
1128 add_axis (hb_face, ax: &ai[i], value: design_coords[i], i);
1129
1130 add_font_plane (i: n_axes);
1131
1132done:
1133 g_clear_object (&pango_font);
1134 g_free (mem: ai);
1135 g_free (mem: design_coords);
1136}
1137
1138G_MODULE_EXPORT void
1139font_features_font_changed (void)
1140{
1141 update_script_combo ();
1142 update_features ();
1143 update_font_variations ();
1144}
1145
1146G_MODULE_EXPORT void
1147font_features_script_changed (void)
1148{
1149 update_features ();
1150 update_display ();
1151}
1152
1153G_MODULE_EXPORT void
1154font_features_reset_features (void)
1155{
1156 GList *l;
1157
1158 gtk_label_select_region (GTK_LABEL (the_label), start_offset: 0, end_offset: 0);
1159
1160 g_list_free_full (list: ranges, free_func: free_range);
1161 ranges = NULL;
1162
1163 for (l = feature_items; l; l = l->next)
1164 {
1165 FeatureItem *item = l->data;
1166
1167 if (GTK_IS_CHECK_BUTTON (item->feat))
1168 {
1169 if (strcmp (s1: item->name, s2: "xxxx") == 0)
1170 gtk_check_button_set_active (GTK_CHECK_BUTTON (item->feat), TRUE);
1171 else
1172 {
1173 gtk_check_button_set_active (GTK_CHECK_BUTTON (item->feat), FALSE);
1174 set_inconsistent (GTK_CHECK_BUTTON (item->feat), TRUE);
1175 }
1176 }
1177 }
1178}
1179
1180static char *text;
1181
1182static void
1183switch_to_entry (void)
1184{
1185 text = g_strdup (str: gtk_editable_get_text (GTK_EDITABLE (the_entry)));
1186 gtk_stack_set_visible_child_name (GTK_STACK (stack), name: "entry");
1187 gtk_widget_grab_focus (widget: the_entry);
1188}
1189
1190static void
1191switch_to_label (void)
1192{
1193 g_free (mem: text);
1194 text = NULL;
1195 gtk_stack_set_visible_child_name (GTK_STACK (stack), name: "label");
1196 update_display ();
1197}
1198
1199G_MODULE_EXPORT void
1200font_features_toggle_edit (void)
1201{
1202 if (strcmp (s1: gtk_stack_get_visible_child_name (GTK_STACK (stack)), s2: "label") == 0)
1203 switch_to_entry ();
1204 else
1205 switch_to_label ();
1206}
1207
1208G_MODULE_EXPORT void
1209font_features_stop_edit (void)
1210{
1211 g_signal_emit_by_name (instance: edit_toggle, detailed_signal: "clicked");
1212}
1213
1214static gboolean
1215entry_key_press (GtkEventController *controller,
1216 guint keyval,
1217 guint keycode,
1218 GdkModifierType modifiers,
1219 GtkEntry *entry)
1220{
1221 if (keyval == GDK_KEY_Escape)
1222 {
1223 gtk_editable_set_text (GTK_EDITABLE (entry), text);
1224 font_features_stop_edit ();
1225 return GDK_EVENT_STOP;
1226 }
1227
1228 return GDK_EVENT_PROPAGATE;
1229}
1230
1231GtkWidget *
1232do_font_features (GtkWidget *do_widget)
1233{
1234 static GtkWidget *window = NULL;
1235
1236 if (!window)
1237 {
1238 GtkBuilder *builder;
1239 GtkWidget *feature_list;
1240 GtkEventController *controller;
1241
1242 builder = gtk_builder_new_from_resource (resource_path: "/font_features/font-features.ui");
1243
1244 window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
1245 feature_list = GTK_WIDGET (gtk_builder_get_object (builder, "feature_list"));
1246 the_label = GTK_WIDGET (gtk_builder_get_object (builder, "label"));
1247 settings = GTK_WIDGET (gtk_builder_get_object (builder, "settings"));
1248 description = GTK_WIDGET (gtk_builder_get_object (builder, "description"));
1249 resetbutton = GTK_WIDGET (gtk_builder_get_object (builder, "reset"));
1250 font = GTK_WIDGET (gtk_builder_get_object (builder, "font"));
1251 script_lang = GTK_WIDGET (gtk_builder_get_object (builder, "script_lang"));
1252 stack = GTK_WIDGET (gtk_builder_get_object (builder, "stack"));
1253 the_entry = GTK_WIDGET (gtk_builder_get_object (builder, "entry"));
1254 edit_toggle = GTK_WIDGET (gtk_builder_get_object (builder, "edit_toggle"));
1255
1256 controller = gtk_event_controller_key_new ();
1257 g_object_set_data_full (G_OBJECT (the_entry), key: "controller", g_object_ref (controller), destroy: g_object_unref);
1258 g_signal_connect (controller, "key-pressed", G_CALLBACK (entry_key_press), the_entry);
1259 gtk_widget_add_controller (widget: the_entry, controller);
1260
1261 add_check_group (box: feature_list, _("Kerning"), tags: (const char *[]){ "kern", NULL });
1262 add_check_group (box: feature_list, _("Ligatures"), tags: (const char *[]){ "liga",
1263 "dlig",
1264 "hlig",
1265 "clig",
1266 "rlig", NULL });
1267 add_check_group (box: feature_list, _("Letter Case"), tags: (const char *[]){ "smcp",
1268 "c2sc",
1269 "pcap",
1270 "c2pc",
1271 "unic",
1272 "cpsp",
1273 "case",NULL });
1274 add_radio_group (box: feature_list, _("Number Case"), tags: (const char *[]){ "xxxx",
1275 "lnum",
1276 "onum", NULL });
1277 add_radio_group (box: feature_list, _("Number Spacing"), tags: (const char *[]){ "xxxx",
1278 "pnum",
1279 "tnum", NULL });
1280 add_radio_group (box: feature_list, _("Fractions"), tags: (const char *[]){ "xxxx",
1281 "frac",
1282 "afrc", NULL });
1283 add_check_group (box: feature_list, _("Numeric Extras"), tags: (const char *[]){ "zero",
1284 "nalt",
1285 "sinf", NULL });
1286 add_check_group (box: feature_list, _("Character Alternatives"), tags: (const char *[]){ "swsh",
1287 "cswh",
1288 "locl",
1289 "calt",
1290 "falt",
1291 "hist",
1292 "salt",
1293 "jalt",
1294 "titl",
1295 "rand",
1296 "subs",
1297 "sups",
1298 "ordn",
1299 "ltra",
1300 "ltrm",
1301 "rtla",
1302 "rtlm",
1303 "rclt", NULL });
1304 add_check_group (box: feature_list, _("Positional Alternatives"), tags: (const char *[]){ "init",
1305 "medi",
1306 "med2",
1307 "fina",
1308 "fin2",
1309 "fin3",
1310 "isol", NULL });
1311 add_check_group (box: feature_list, _("Width Variants"), tags: (const char *[]){ "fwid",
1312 "hwid",
1313 "halt",
1314 "pwid",
1315 "palt",
1316 "twid",
1317 "qwid", NULL });
1318 add_check_group (box: feature_list, _("Alternative Stylistic Sets"), tags: (const char *[]){ "ss01",
1319 "ss02",
1320 "ss03",
1321 "ss04",
1322 "ss05",
1323 "ss06",
1324 "ss07",
1325 "ss08",
1326 "ss09",
1327 "ss10",
1328 "ss11",
1329 "ss12",
1330 "ss13",
1331 "ss14",
1332 "ss15",
1333 "ss16",
1334 "ss17",
1335 "ss18",
1336 "ss19",
1337 "ss20", NULL });
1338 add_check_group (box: feature_list, _("Mathematical"), tags: (const char *[]){ "dtls",
1339 "flac",
1340 "mgrk",
1341 "ssty", NULL });
1342 add_check_group (box: feature_list, _("Optical Bounds"), tags: (const char *[]){ "opbd",
1343 "lfbd",
1344 "rtbd", NULL });
1345 feature_items = g_list_reverse (list: feature_items);
1346
1347 variations_heading = GTK_WIDGET (gtk_builder_get_object (builder, "variations_heading"));
1348 variations_grid = GTK_WIDGET (gtk_builder_get_object (builder, "variations_grid"));
1349 if (instances == NULL)
1350 instances = g_hash_table_new_full (hash_func: instance_hash, key_equal_func: instance_equal, NULL, value_destroy_func: free_instance);
1351 else
1352 g_hash_table_remove_all (hash_table: instances);
1353
1354 if (axes == NULL)
1355 axes = g_hash_table_new_full (hash_func: axes_hash, key_equal_func: axes_equal, NULL, value_destroy_func: g_free);
1356 else
1357 g_hash_table_remove_all (hash_table: axes);
1358
1359 font_features_font_changed ();
1360
1361 g_object_add_weak_pointer (G_OBJECT (window), weak_pointer_location: (gpointer *)&window);
1362
1363 g_object_unref (object: builder);
1364
1365 update_display ();
1366 }
1367
1368 if (!gtk_widget_get_visible (widget: window))
1369 gtk_window_present (GTK_WINDOW (window));
1370 else
1371 gtk_window_destroy (GTK_WINDOW (window));
1372
1373 return window;
1374}
1375

source code of gtk/demos/gtk-demo/font_features.c