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 | |
27 | static GtkWidget *the_label; |
28 | static GtkWidget *settings; |
29 | static GtkWidget *description; |
30 | static GtkWidget *font; |
31 | static GtkWidget *script_lang; |
32 | static GtkWidget *resetbutton; |
33 | static GtkWidget *stack; |
34 | static GtkWidget *the_entry; |
35 | static GtkWidget *variations_heading; |
36 | static GtkWidget *variations_grid; |
37 | static GtkWidget *instance_combo; |
38 | static GtkWidget *edit_toggle; |
39 | |
40 | typedef struct { |
41 | unsigned int tag; |
42 | const char *name; |
43 | GtkWidget *icon; |
44 | GtkWidget *dflt; |
45 | GtkWidget *feat; |
46 | } FeatureItem; |
47 | |
48 | static GList *feature_items; |
49 | |
50 | typedef struct { |
51 | unsigned int start; |
52 | unsigned int end; |
53 | PangoFontDescription *desc; |
54 | char *features; |
55 | PangoLanguage *language; |
56 | } Range; |
57 | |
58 | static GList *ranges; |
59 | |
60 | static void add_font_variations (GString *s); |
61 | |
62 | static void |
63 | free_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 | |
73 | static int |
74 | compare_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 | |
91 | static void |
92 | ensure_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 | |
118 | set: |
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 | |
128 | static const char * |
129 | get_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 | |
149 | static void update_display (void); |
150 | |
151 | static void |
152 | set_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 | |
159 | static void |
160 | feat_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 | |
187 | static void |
188 | feat_toggled_cb (GtkCheckButton *check_button, |
189 | gpointer data) |
190 | { |
191 | set_inconsistent (button: check_button, FALSE); |
192 | } |
193 | |
194 | static void |
195 | add_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 | |
253 | static void |
254 | add_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 | |
311 | static void |
312 | update_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 | |
449 | static PangoFont * |
450 | get_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 | |
461 | typedef 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 | |
468 | static guint |
469 | tag_pair_hash (gconstpointer data) |
470 | { |
471 | const TagPair *pair = data; |
472 | |
473 | return pair->script_tag + pair->lang_tag; |
474 | } |
475 | |
476 | static gboolean |
477 | tag_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 | |
485 | static int |
486 | script_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 | |
505 | static void |
506 | update_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 | |
621 | static void |
622 | update_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 | |
745 | static void |
746 | adjustment_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 | |
758 | static void |
759 | entry_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 | |
770 | static void unset_instance (GtkAdjustment *adjustment); |
771 | |
772 | typedef struct { |
773 | guint32 tag; |
774 | GtkAdjustment *adjustment; |
775 | } Axis; |
776 | |
777 | static GHashTable *axes; |
778 | |
779 | static void |
780 | add_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 | |
802 | static guint |
803 | axes_hash (gconstpointer v) |
804 | { |
805 | const Axis *p = v; |
806 | |
807 | return p->tag; |
808 | } |
809 | |
810 | static gboolean |
811 | axes_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 | |
819 | static void |
820 | add_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 | |
865 | typedef struct { |
866 | char *name; |
867 | unsigned int index; |
868 | } Instance; |
869 | |
870 | static guint |
871 | instance_hash (gconstpointer v) |
872 | { |
873 | const Instance *p = v; |
874 | |
875 | return g_str_hash (v: p->name); |
876 | } |
877 | |
878 | static gboolean |
879 | instance_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 | |
887 | static void |
888 | free_instance (gpointer data) |
889 | { |
890 | Instance *instance = data; |
891 | |
892 | g_free (mem: instance->name); |
893 | g_free (mem: instance); |
894 | } |
895 | |
896 | static GHashTable *instances; |
897 | |
898 | static void |
899 | add_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 | |
921 | static void |
922 | unset_instance (GtkAdjustment *adjustment) |
923 | { |
924 | if (instance_combo) |
925 | gtk_combo_box_set_active (GTK_COMBO_BOX (instance_combo), index_: 0); |
926 | } |
927 | |
928 | static void |
929 | instance_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 | |
987 | out: |
988 | g_free (mem: text); |
989 | g_clear_object (&pango_font); |
990 | g_free (mem: ai); |
991 | g_free (mem: coords); |
992 | } |
993 | |
994 | static gboolean |
995 | matches_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 | |
1019 | static void |
1020 | add_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 */ |
1045 | static float |
1046 | denorm_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 | |
1056 | static void |
1057 | update_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 | |
1132 | done: |
1133 | g_clear_object (&pango_font); |
1134 | g_free (mem: ai); |
1135 | g_free (mem: design_coords); |
1136 | } |
1137 | |
1138 | G_MODULE_EXPORT void |
1139 | font_features_font_changed (void) |
1140 | { |
1141 | update_script_combo (); |
1142 | update_features (); |
1143 | update_font_variations (); |
1144 | } |
1145 | |
1146 | G_MODULE_EXPORT void |
1147 | font_features_script_changed (void) |
1148 | { |
1149 | update_features (); |
1150 | update_display (); |
1151 | } |
1152 | |
1153 | G_MODULE_EXPORT void |
1154 | font_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 | |
1180 | static char *text; |
1181 | |
1182 | static void |
1183 | switch_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 | |
1190 | static void |
1191 | switch_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 | |
1199 | G_MODULE_EXPORT void |
1200 | font_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 | |
1208 | G_MODULE_EXPORT void |
1209 | font_features_stop_edit (void) |
1210 | { |
1211 | g_signal_emit_by_name (instance: edit_toggle, detailed_signal: "clicked" ); |
1212 | } |
1213 | |
1214 | static gboolean |
1215 | entry_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 | |
1231 | GtkWidget * |
1232 | do_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 | |