| /* | 
 |  * Copyright © 2023  Behdad Esfahbod | 
 |  * | 
 |  *  This is part of HarfBuzz, a text shaping library. | 
 |  * | 
 |  * Permission is hereby granted, without written agreement and without | 
 |  * license or royalty fees, to use, copy, modify, and distribute this | 
 |  * software and its documentation for any purpose, provided that the | 
 |  * above copyright notice and the following two paragraphs appear in | 
 |  * all copies of this software. | 
 |  * | 
 |  * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR | 
 |  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES | 
 |  * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN | 
 |  * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH | 
 |  * DAMAGE. | 
 |  * | 
 |  * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, | 
 |  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | 
 |  * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS | 
 |  * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO | 
 |  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | 
 |  * | 
 |  * Google Author(s): Behdad Esfahbod | 
 |  */ | 
 |  | 
 | #include "batch.hh" | 
 | #include "font-options.hh" | 
 |  | 
 | #ifdef HB_HAS_GOBJECT | 
 | #include <hb-gobject.h> | 
 | #endif | 
 |  | 
 | #ifdef HAVE_CHAFA | 
 | # include <chafa.h> | 
 | #endif | 
 |  | 
 | const unsigned DEFAULT_FONT_SIZE = FONT_SIZE_UPEM; | 
 | const unsigned SUBPIXEL_BITS = 0; | 
 |  | 
 | static void | 
 | _hb_ot_name_get_utf8 (hb_face_t       *face, | 
 | 		      hb_ot_name_id_t  name_id, | 
 | 		      hb_language_t    language, | 
 | 		      unsigned int    *text_size /* IN/OUT */, | 
 | 		      char            *text      /* OUT */) | 
 | { | 
 |   static hb_language_t en = hb_language_from_string ("en", -1); | 
 |  | 
 |   unsigned len = *text_size; | 
 |   if (!hb_ot_name_get_utf8 (face, name_id, | 
 | 			    language, | 
 | 			    &len, text)) | 
 |   { | 
 |     len = *text_size; | 
 |     hb_ot_name_get_utf8 (face, name_id, | 
 | 			 en, | 
 | 			 &len, text); | 
 |   } | 
 |   *text_size = len; | 
 | } | 
 |  | 
 | struct info_t : | 
 |        option_parser_t, | 
 |        font_options_t | 
 | { | 
 |   void add_options () | 
 |   { | 
 |     font_options_t::add_options (this); | 
 |  | 
 |     GOptionEntry misc_entries[] = | 
 |     { | 
 |       {"direction",	0, 0, G_OPTION_ARG_STRING,	&this->direction_str,		"Set direction (default: ltr)",		"ltr/rtl/ttb/btt"}, | 
 |       {"script",	0, 0, G_OPTION_ARG_STRING,	&this->script_str,		"Set script (default: none)",		"ISO-15924 tag; eg. 'Latn'"}, | 
 |       {"language",	0, 0, G_OPTION_ARG_STRING,	&this->language_str,		"Set language (default: $LANG)",	"BCP 47 tag; eg. 'en'"}, | 
 |       {"ot-script",	0, 0, G_OPTION_ARG_STRING,	&this->ot_script_str,		"Set OpenType script tag (default: none)","tag; eg. 'latn'"}, | 
 |       {"ot-language",	0, 0, G_OPTION_ARG_STRING,	&this->ot_language_str,		"Set OpenType language tag (default: none)",	"tag; eg. 'ENG'"}, | 
 |  | 
 |       {nullptr} | 
 |     }; | 
 |     add_group (misc_entries, | 
 | 	       "misc", | 
 | 	       "Miscellaneous options:", | 
 | 	       "Miscellaneous options affecting queries", | 
 | 	       this, | 
 | 	       false /* We add below. */); | 
 |  | 
 |     GOptionEntry query_entries[] = | 
 |     { | 
 |       {"all",		'a', 0, G_OPTION_ARG_NONE,	&this->all,			"Show everything",		nullptr}, | 
 |  | 
 |       {"show-all",	0, 0, G_OPTION_ARG_NONE,	&this->show_all,		"Show all short information (default)",	nullptr}, | 
 |       {"show-face-count",0, 0, G_OPTION_ARG_NONE,	&this->show_face_count,		"Show face count",		nullptr}, | 
 |       {"show-family",	0, 0, G_OPTION_ARG_NONE,	&this->show_family,		"Show family name",		nullptr}, | 
 |       {"show-subfamily",0, 0, G_OPTION_ARG_NONE,	&this->show_subfamily,		"Show subfamily name",		nullptr}, | 
 |       {"show-unique-name",0, 0, G_OPTION_ARG_NONE,	&this->show_unique_name,	"Show unique name",		nullptr}, | 
 |       {"show-full-name",0, 0, G_OPTION_ARG_NONE,	&this->show_full_name,		"Show full name",		nullptr}, | 
 |       {"show-postscript-name",0, 0, G_OPTION_ARG_NONE,	&this->show_postscript_name,	"Show Postscript name",		nullptr}, | 
 |       {"show-version",	0, 0, G_OPTION_ARG_NONE,	&this->show_version,		"Show version",			nullptr}, | 
 |       {"show-technology",0, 0, G_OPTION_ARG_NONE,	&this->show_technology,		"Show technology",		nullptr}, | 
 |       {"show-unicode-count",0, 0, G_OPTION_ARG_NONE,	&this->show_unicode_count,	"Show Unicode count",		nullptr}, | 
 |       {"show-glyph-count",0, 0, G_OPTION_ARG_NONE,	&this->show_glyph_count,	"Show glyph count",		nullptr}, | 
 |       {"show-upem",	0, 0, G_OPTION_ARG_NONE,	&this->show_upem,		"Show Units-Per-EM",		nullptr}, | 
 |       {"show-extents",	0, 0, G_OPTION_ARG_NONE,	&this->show_extents,		"Show extents",			nullptr}, | 
 |  | 
 |       {"get-name",	0, 0, G_OPTION_ARG_STRING_ARRAY,&this->get_name,		"Get name",			"name id; eg. '13'"}, | 
 |       {"get-style",	0, 0, G_OPTION_ARG_STRING_ARRAY,&this->get_style,		"Get style",			"style tag; eg. 'wght'"}, | 
 |       {"get-metric",	0, 0, G_OPTION_ARG_STRING_ARRAY,&this->get_metric,		"Get metric",			"metric tag; eg. 'hasc'"}, | 
 |       {"get-baseline",	0, 0, G_OPTION_ARG_STRING_ARRAY,&this->get_baseline,		"Get baseline",			"baseline tag; eg. 'hang'"}, | 
 |       {"get-meta",	0, 0, G_OPTION_ARG_STRING_ARRAY,&this->get_meta,		"Get meta information",		"tag tag; eg. 'dlng'"}, | 
 |       {"get-table",	0, 0, G_OPTION_ARG_STRING,	&this->get_table,		"Get font table",		"table tag; eg. 'cmap'"}, | 
 |  | 
 |       {"list-all",	0, 0, G_OPTION_ARG_NONE,	&this->list_all,		"List all long information",	nullptr}, | 
 |       {"list-names",	0, 0, G_OPTION_ARG_NONE,	&this->list_names,		"List names",			nullptr}, | 
 | #ifdef HB_HAS_GOBJECT | 
 |       {"list-style",	0, 0, G_OPTION_ARG_NONE,	&this->list_style,		"List style",			nullptr}, | 
 |       {"list-metrics",	0, 0, G_OPTION_ARG_NONE,	&this->list_metrics,		"List metrics",			nullptr}, | 
 |       {"list-baselines",0, 0, G_OPTION_ARG_NONE,	&this->list_baselines,		"List baselines",		nullptr}, | 
 | #endif | 
 |       {"list-tables",	'l', 0, G_OPTION_ARG_NONE,	&this->list_tables,		"List tables",			nullptr}, | 
 |       {"list-unicodes",	0, 0, G_OPTION_ARG_NONE,	&this->list_unicodes,		"List characters",		nullptr}, | 
 |       {"list-glyphs",	0, 0, G_OPTION_ARG_NONE,	&this->list_glyphs,		"List glyphs",			nullptr}, | 
 |       {"list-scripts",	0, 0, G_OPTION_ARG_NONE,	&this->list_scripts,		"List layout scripts",		nullptr}, | 
 |       {"list-features",	0, 0, G_OPTION_ARG_NONE,	&this->list_features,		"List layout features",		nullptr}, | 
 | #ifndef HB_NO_VAR | 
 |       {"list-variations",0, 0, G_OPTION_ARG_NONE,	&this->list_variations,		"List variations",		nullptr}, | 
 | #endif | 
 |       {"list-palettes",	0, 0, G_OPTION_ARG_NONE,	&this->list_palettes,		"List color palettes",		nullptr}, | 
 |       {"list-meta",	0, 0, G_OPTION_ARG_NONE,	&this->list_meta,		"List meta information",	nullptr}, | 
 |  | 
 |       {nullptr} | 
 |     }; | 
 |     add_group (query_entries, | 
 | 	       "query", | 
 | 	       "Query options:", | 
 | 	       "Options to query the font instance", | 
 | 	       this, | 
 | 	       true); | 
 |  | 
 |     GOptionEntry entries[] = | 
 |     { | 
 |       {"quiet",		'q', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,	&this->verbose,	"Generate machine-readable output",	nullptr}, | 
 |       {G_OPTION_REMAINING,	0, G_OPTION_FLAG_IN_MAIN, | 
 | 				G_OPTION_ARG_CALLBACK,	(gpointer) &collect_rest,	nullptr,	"[FONT-FILE]"}, | 
 |       {nullptr} | 
 |     }; | 
 |     add_main_group (entries, this); | 
 |  | 
 |     option_parser_t::add_options (); | 
 |   } | 
 |  | 
 |   static gboolean | 
 |   collect_rest (const char *name G_GNUC_UNUSED, | 
 | 		const char *arg, | 
 | 		gpointer    data, | 
 | 		GError    **error) | 
 |   { | 
 |     info_t *thiz = (info_t *) data; | 
 |  | 
 |     if (!thiz->font_file) | 
 |     { | 
 |       thiz->font_file = g_strdup (arg); | 
 |       return true; | 
 |     } | 
 |  | 
 |     g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, | 
 | 		 "Too many arguments on the command line"); | 
 |     return false; | 
 |   } | 
 |  | 
 |  | 
 |   protected: | 
 |  | 
 |   hb_bool_t verbose = true; | 
 |   hb_bool_t first_item = true; | 
 |  | 
 |   char *direction_str = nullptr; | 
 |   char *script_str = nullptr; | 
 |   char *language_str = nullptr; | 
 |   hb_direction_t direction = HB_DIRECTION_LTR; | 
 |   hb_script_t script = HB_SCRIPT_INVALID; | 
 |   hb_language_t language = HB_LANGUAGE_INVALID; | 
 |   char *ot_script_str = nullptr; | 
 |   char *ot_language_str = nullptr; | 
 |  | 
 |   hb_bool_t all = false; | 
 |  | 
 |   hb_bool_t show_all = false; | 
 |   hb_bool_t show_face_count = false; | 
 |   hb_bool_t show_family = false; | 
 |   hb_bool_t show_subfamily = false; | 
 |   hb_bool_t show_unique_name = false; | 
 |   hb_bool_t show_full_name = false; | 
 |   hb_bool_t show_postscript_name = false; | 
 |   hb_bool_t show_version = false; | 
 |   hb_bool_t show_technology = false; | 
 |   hb_bool_t show_unicode_count = false; | 
 |   hb_bool_t show_glyph_count = false; | 
 |   hb_bool_t show_upem = false; | 
 |   hb_bool_t show_extents = false; | 
 |  | 
 |   char **get_name = nullptr; | 
 |   char **get_style = nullptr; | 
 |   char **get_metric = nullptr; | 
 |   char **get_baseline = nullptr; | 
 |   char **get_meta = nullptr; | 
 |   char *get_table = nullptr; | 
 |  | 
 |   hb_bool_t list_all = false; | 
 |   hb_bool_t list_names = false; | 
 | #ifdef HB_HAS_GOBJECT | 
 |   hb_bool_t list_style = false; | 
 |   hb_bool_t list_metrics = false; | 
 |   hb_bool_t list_baselines = false; | 
 | #endif | 
 |   hb_bool_t list_tables = false; | 
 |   hb_bool_t list_unicodes = false; | 
 |   hb_bool_t list_glyphs = false; | 
 |   hb_bool_t list_scripts = false; | 
 |   hb_bool_t list_features = false; | 
 | #ifndef HB_NO_VAR | 
 |   hb_bool_t list_variations = false; | 
 | #endif | 
 |   hb_bool_t list_palettes = false; | 
 |   hb_bool_t list_meta = false; | 
 |  | 
 |   public: | 
 |  | 
 |   void | 
 |   post_parse (GError **error) | 
 |   { | 
 |     if (direction_str) | 
 |       direction = hb_direction_from_string (direction_str, -1); | 
 |     if (script_str) | 
 |       script = hb_script_from_string (script_str, -1); | 
 |     language = hb_language_get_default (); | 
 |     if (language_str) | 
 |       language = hb_language_from_string (language_str, -1); | 
 |   } | 
 |  | 
 |   int | 
 |   operator () (int argc, char **argv) | 
 |   { | 
 |     add_options (); | 
 |  | 
 |     if (argc == 2) | 
 |       show_all = true; | 
 |  | 
 |     parse (&argc, &argv); | 
 |  | 
 |     if (all) | 
 |     { | 
 |       show_all = | 
 |       list_all = | 
 |       true; | 
 |     } | 
 |  | 
 |     if (show_all) | 
 |     { | 
 |       show_face_count = | 
 |       show_family = | 
 |       show_subfamily = | 
 |       show_unique_name = | 
 |       show_full_name = | 
 |       show_postscript_name = | 
 |       show_version = | 
 |       show_technology = | 
 |       show_unicode_count = | 
 |       show_glyph_count = | 
 |       show_upem = | 
 |       show_extents = | 
 |       true; | 
 |       first_item = false; | 
 |     } | 
 |  | 
 |     if (list_all) | 
 |     { | 
 |       list_names = | 
 | #ifdef HB_HAS_GOBJECT | 
 |       list_style = | 
 |       list_metrics = | 
 |       list_baselines = | 
 | #endif | 
 |       list_tables = | 
 |       list_unicodes = | 
 |       list_glyphs = | 
 |       list_scripts = | 
 |       list_features = | 
 | #ifndef HB_NO_VAR | 
 |       list_variations = | 
 | #endif | 
 |       list_palettes = | 
 |       list_meta = | 
 |       true; | 
 |     } | 
 |  | 
 |     if (show_face_count)  _show_face_count (); | 
 |     if (show_family)	  _show_family (); | 
 |     if (show_subfamily)	  _show_subfamily (); | 
 |     if (show_unique_name) _show_unique_name (); | 
 |     if (show_full_name)	  _show_full_name (); | 
 |     if (show_postscript_name)_show_postscript_name (); | 
 |     if (show_version)	  _show_version (); | 
 |     if (show_technology)  _show_technology (); | 
 |     if (show_unicode_count)_show_unicode_count (); | 
 |     if (show_glyph_count) _show_glyph_count (); | 
 |     if (show_upem)	  _show_upem (); | 
 |     if (show_extents)	  _show_extents (); | 
 |  | 
 |     if (get_name)	  _get_name (); | 
 |     if (get_style)	  _get_style (); | 
 |     if (get_metric)	  _get_metric (); | 
 |     if (get_baseline)	  _get_baseline (); | 
 |     if (get_meta)	  _get_meta (); | 
 |     if (get_table)	  _get_table (); | 
 |  | 
 |     if (list_names)	  _list_names (); | 
 | #ifdef HB_HAS_GOBJECT | 
 |     if (list_style)	  _list_style (); | 
 |     if (list_metrics)	  _list_metrics (); | 
 |     if (list_baselines)	  _list_baselines (); | 
 | #endif | 
 |     if (list_tables)	  _list_tables (); | 
 |     if (list_unicodes)	  _list_unicodes (); | 
 |     if (list_glyphs)	  _list_glyphs (); | 
 |     if (list_scripts)	  _list_scripts (); | 
 |     if (list_features)	  _list_features (); | 
 | #ifndef HB_NO_VAR | 
 |     if (list_variations)  _list_variations (); | 
 | #endif | 
 |     if (list_palettes)	  _list_palettes (); | 
 |     if (list_meta)	  _list_meta (); | 
 |  | 
 |     return 0; | 
 |   } | 
 |  | 
 |   protected: | 
 |  | 
 |   void separator () | 
 |   { | 
 |     if (first_item) | 
 |     { | 
 |       first_item = false; | 
 |       return; | 
 |     } | 
 |     printf ("\n===\n\n"); | 
 |   } | 
 |  | 
 |   void | 
 |   _show_face_count () | 
 |   { | 
 |     printf ("Face count: %u\n", hb_face_count (blob)); | 
 |   } | 
 |  | 
 |   void | 
 |   _show_name (const char *label, hb_ot_name_id_t name_id) | 
 |   { | 
 |     if (verbose) | 
 |     { | 
 |       printf ("%s: ", label); | 
 |     } | 
 |  | 
 |     char name[16384]; | 
 |     unsigned name_len = sizeof name; | 
 |     _hb_ot_name_get_utf8 (face, name_id, | 
 | 			  language, | 
 | 			  &name_len, name); | 
 |  | 
 |     printf ("%s\n", name); | 
 |   } | 
 |   void _show_family ()		{ _show_name ("Family", 1); } | 
 |   void _show_subfamily () | 
 |   { | 
 |     hb_ot_name_id_t name_id = 2; | 
 |  | 
 |     unsigned named_instance = hb_font_get_var_named_instance (font); | 
 |     if (named_instance != HB_FONT_NO_VAR_NAMED_INSTANCE) | 
 |       name_id = hb_ot_var_named_instance_get_subfamily_name_id (face, named_instance); | 
 |  | 
 |     _show_name ("Subfamily", name_id); | 
 |   } | 
 |   void _show_unique_name ()	{ _show_name ("Unique name", 3); } | 
 |   void _show_full_name ()	{ _show_name ("Full name", 4); } | 
 |   void _show_postscript_name () | 
 |   { | 
 |     hb_ot_name_id_t name_id = 6; | 
 |  | 
 |     unsigned named_instance = hb_font_get_var_named_instance (font); | 
 |     if (named_instance != HB_FONT_NO_VAR_NAMED_INSTANCE) | 
 |       name_id = hb_ot_var_named_instance_get_postscript_name_id (face, named_instance); | 
 |  | 
 |  | 
 |     _show_name ("Postscript name", name_id); | 
 |   } | 
 |   void _show_version ()		{ _show_name ("Version", 5); } | 
 |  | 
 |   bool _has_blob (hb_tag_t tag) | 
 |   { | 
 |     hb_blob_t *blob = hb_face_reference_table (face, tag); | 
 |     bool ret = hb_blob_get_length (blob); | 
 |     hb_blob_destroy (blob); | 
 |     return ret; | 
 |   } | 
 |  | 
 |   void _show_technology () | 
 |   { | 
 |     if (_has_blob (HB_TAG('g','l','y','f'))) | 
 |       printf ("Has TrueType outlines\n"); | 
 |     if (_has_blob (HB_TAG('C','F','F',' ')) || _has_blob (HB_TAG('C','F','F','2'))) | 
 |       printf ("Has Postscript outlines\n"); | 
 |  | 
 |     if (_has_blob (HB_TAG('f','p','g','m')) || _has_blob (HB_TAG('p','r','e','p')) || _has_blob (HB_TAG('c','v','t',' '))) | 
 |       printf ("Has TrueType hinting\n"); | 
 |  | 
 |     if (_has_blob (HB_TAG('G','S','U','B')) || _has_blob (HB_TAG('G','P','O','S'))) | 
 |       printf ("Has OpenType layout\n"); | 
 |     if (_has_blob (HB_TAG('m','o','r','x')) || _has_blob (HB_TAG('k','e','r','x'))) | 
 |       printf ("Has AAT layout\n"); | 
 |     if (_has_blob (HB_TAG('S','i','l','f'))) | 
 |       printf ("Has Graphite layout\n"); | 
 |     if (_has_blob (HB_TAG('k','e','r','n'))) | 
 |       printf ("Has legacy kerning\n"); | 
 |  | 
 |     if (_has_blob (HB_TAG('E','B','D','T'))) | 
 |       printf ("Has monochrome bitmaps\n"); | 
 |  | 
 |     if (_has_blob (HB_TAG('C','B','D','T')) || _has_blob (HB_TAG('s','b','i','x'))) | 
 |       printf ("Has color bitmaps\n"); | 
 |     if (_has_blob (HB_TAG('S','V','G',' '))) | 
 |       printf ("Has color SVGs\n"); | 
 |     if (_has_blob (HB_TAG('C','O','L','R'))) | 
 |       printf ("Has color paintings\n"); | 
 |  | 
 |     if (_has_blob (HB_TAG('f','v','a','r')))  printf ("Has variations\n"); | 
 |   } | 
 |  | 
 |   void _show_unicode_count () | 
 |   { | 
 |     if (verbose) | 
 |     { | 
 |       printf ("Unicode count: "); | 
 |     } | 
 |  | 
 |     hb_set_t *unicodes = hb_set_create (); | 
 |     hb_face_collect_unicodes (face, unicodes); | 
 |  | 
 |     printf ("%u\n", hb_set_get_population (unicodes)); | 
 |  | 
 |     hb_set_destroy (unicodes); | 
 |   } | 
 |  | 
 |   void _show_glyph_count () | 
 |   { | 
 |     if (verbose) | 
 |     { | 
 |       printf ("Glyph count: "); | 
 |     } | 
 |  | 
 |     printf ("%u\n", hb_face_get_glyph_count (face)); | 
 |   } | 
 |  | 
 |   void _show_upem () | 
 |   { | 
 |     if (verbose) | 
 |     { | 
 |       printf ("Units-Per-EM: "); | 
 |     } | 
 |  | 
 |     printf ("%u\n", hb_face_get_upem (face)); | 
 |   } | 
 |  | 
 |   void _show_extents () | 
 |   { | 
 |     hb_font_extents_t extents; | 
 |     hb_font_get_extents_for_direction (font, direction, &extents); | 
 |  | 
 |     if (verbose) printf ("Ascender: "); | 
 |     printf ("%d\n", extents.ascender); | 
 |  | 
 |     if (verbose) printf ("Descender: "); | 
 |     printf ("%d\n", extents.descender); | 
 |  | 
 |     if (verbose) printf ("Line gap: "); | 
 |     printf ("%d\n", extents.line_gap); | 
 |   } | 
 |  | 
 |   void _get_name () | 
 |   { | 
 |     for (char **p = get_name; *p; p++) | 
 |     { | 
 |       hb_ot_name_id_t name_id = (hb_ot_name_id_t) atoi (*p); | 
 |       _show_name (*p, name_id); | 
 |     } | 
 |   } | 
 |  | 
 |   void _get_style () | 
 |   { | 
 |     for (char **p = get_style; *p; p++) | 
 |     { | 
 |       hb_style_tag_t tag = (hb_style_tag_t) hb_tag_from_string (*p, -1); | 
 |  | 
 |       if (verbose) | 
 | 	printf ("Style %c%c%c%c: ", HB_UNTAG (tag)); | 
 |  | 
 |       float v = hb_style_get_value (font, tag); | 
 |       printf ("%g\n", (double) v); | 
 |     } | 
 |   } | 
 |  | 
 |   void _get_metric () | 
 |   { | 
 |     bool fallback = false; | 
 |     for (char **p = get_metric; *p; p++) | 
 |     { | 
 |       hb_ot_metrics_tag_t tag = (hb_ot_metrics_tag_t) hb_tag_from_string (*p, -1); | 
 |       hb_position_t position; | 
 |  | 
 |       if (verbose) | 
 | 	printf ("Metric %c%c%c%c: ", HB_UNTAG (tag)); | 
 |  | 
 |       if (hb_ot_metrics_get_position (font, tag, &position)) | 
 | 	printf ("%d	\n", position); | 
 |       else | 
 |       { | 
 | 	hb_ot_metrics_get_position_with_fallback (font, tag, &position); | 
 | 	printf ("%d	*\n", position); | 
 | 	fallback = true; | 
 |       } | 
 |     } | 
 |  | 
 |     if (verbose && fallback) | 
 |     { | 
 |       printf ("\n[*] Fallback value\n"); | 
 |     } | 
 |   } | 
 |  | 
 |   void _get_baseline () | 
 |   { | 
 |     hb_tag_t script_tags[HB_OT_MAX_TAGS_PER_SCRIPT]; | 
 |     hb_tag_t language_tags[HB_OT_MAX_TAGS_PER_LANGUAGE]; | 
 |     unsigned script_count = HB_OT_MAX_TAGS_PER_SCRIPT; | 
 |     unsigned language_count = HB_OT_MAX_TAGS_PER_LANGUAGE; | 
 |  | 
 |     hb_ot_tags_from_script_and_language (script, language, | 
 | 					 &script_count, script_tags, | 
 | 					 &language_count, language_tags); | 
 |  | 
 |     hb_tag_t script_tag = script_count ? script_tags[script_count - 1] : HB_TAG_NONE; | 
 |     hb_tag_t language_tag = language_count ? language_tags[0] : HB_TAG_NONE; | 
 |  | 
 |     if (ot_script_str) | 
 |       script_tag = hb_tag_from_string (ot_script_str, -1); | 
 |     if (ot_language_str) | 
 |       language_tag = hb_tag_from_string (ot_language_str, -1); | 
 |  | 
 |  | 
 |     bool fallback = false; | 
 |     for (char **p = get_baseline; *p; p++) | 
 |     { | 
 |       hb_ot_layout_baseline_tag_t tag = (hb_ot_layout_baseline_tag_t) hb_tag_from_string (*p, -1); | 
 |       hb_position_t position; | 
 |  | 
 |       if (verbose) | 
 | 	printf ("Baseline %c%c%c%c: ", HB_UNTAG (tag)); | 
 |  | 
 |       if (hb_ot_layout_get_baseline (font, tag, direction, script_tag, language_tag, &position)) | 
 | 	printf ("%d	\n", position); | 
 |       else | 
 |       { | 
 | 	hb_ot_layout_get_baseline_with_fallback (font, tag, direction, script_tag, language_tag, &position); | 
 | 	printf ("%d	*\n", position); | 
 | 	fallback = true; | 
 |       } | 
 |     } | 
 |  | 
 |     if (verbose && fallback) | 
 |     { | 
 |       printf ("\n[*] Fallback value\n"); | 
 |     } | 
 |   } | 
 |  | 
 |   void _get_meta () | 
 |   { | 
 |     for (char **p = get_meta; *p; p++) | 
 |     { | 
 |       hb_ot_meta_tag_t tag = (hb_ot_meta_tag_t) hb_tag_from_string (*p, -1); | 
 |  | 
 |       hb_blob_t *blob = hb_ot_meta_reference_entry (face, tag); | 
 |  | 
 |       if (verbose) | 
 | 	printf ("Meta %c%c%c%c: ", HB_UNTAG (tag)); | 
 |  | 
 |       printf ("%.*s\n", | 
 | 	      (int) hb_blob_get_length (blob), | 
 | 	      hb_blob_get_data (blob, nullptr)); | 
 |  | 
 |       hb_blob_destroy (blob); | 
 |     } | 
 |   } | 
 |  | 
 |   void | 
 |   _get_table () | 
 |   { | 
 |     hb_blob_t *blob = hb_face_reference_table (face, hb_tag_from_string (get_table, -1)); | 
 |     unsigned count = 0; | 
 |     const char *data = hb_blob_get_data (blob, &count); | 
 |     fwrite (data, 1, count, stdout); | 
 |     hb_blob_destroy (blob); | 
 |   } | 
 |  | 
 |   void _list_names () | 
 |   { | 
 |     if (verbose) | 
 |     { | 
 |       separator (); | 
 |       printf ("Name information:\n\n"); | 
 |       printf ("Id: Name			Text\n------------------------------------\n"); | 
 |     } | 
 |  | 
 | #ifdef HB_HAS_GOBJECT | 
 |     GEnumClass *enum_class = (GEnumClass *) g_type_class_ref ((GType) HB_GOBJECT_TYPE_OT_NAME_ID_PREDEFINED); | 
 | #endif | 
 |  | 
 |     unsigned count; | 
 |     const auto *entries = hb_ot_name_list_names (face, &count); | 
 |     for (unsigned i = 0; i < count; i++) | 
 |     { | 
 |       char name[16384]; | 
 |       unsigned name_len = sizeof name; | 
 |       _hb_ot_name_get_utf8 (face, entries[i].name_id, | 
 | 			    language, | 
 | 			    &name_len, name); | 
 |  | 
 | #ifdef HB_HAS_GOBJECT | 
 |       if (verbose) | 
 |       { | 
 | 	GEnumValue *enum_value = g_enum_get_value (enum_class, entries[i].name_id); | 
 | 	printf ("%u: %-27s	%s\n", entries[i].name_id, enum_value ? enum_value->value_nick : "", name); | 
 |       } | 
 |       else | 
 | #endif | 
 | 	printf ("%u	%s\n", entries[i].name_id, name); | 
 |     } | 
 |   } | 
 |  | 
 | #ifdef HB_HAS_GOBJECT | 
 |   void _list_style () | 
 |   { | 
 |     if (verbose) | 
 |     { | 
 |       separator (); | 
 |       printf ("Style information:\n\n"); | 
 |       printf ("Tag:  Name				Value\n---------------------------------------------\n"); | 
 |     } | 
 |  | 
 |     GEnumClass *enum_class = (GEnumClass *) g_type_class_ref ((GType) HB_GOBJECT_TYPE_STYLE_TAG); | 
 |  | 
 |     unsigned count = enum_class->n_values; | 
 |     const auto *entries = enum_class->values; | 
 |     for (unsigned i = 0; i < count; i++) | 
 |     { | 
 | 	float v = hb_style_get_value (font, (hb_style_tag_t) entries[i].value); | 
 | 	printf ("%c%c%c%c", HB_UNTAG(entries[i].value)); | 
 | 	if (verbose) | 
 | 	  printf (": %-33s", entries[i].value_nick); | 
 | 	printf ("	%g\n", (double) v); | 
 |     } | 
 |   } | 
 |  | 
 |   void _list_metrics () | 
 |   { | 
 |     if (verbose) | 
 |     { | 
 |       separator (); | 
 |       printf ("Metrics information:\n\n"); | 
 |       printf ("Tag:  Name				Value\n---------------------------------------------\n"); | 
 |     } | 
 |  | 
 |     GEnumClass *enum_class = (GEnumClass *) g_type_class_ref ((GType) HB_GOBJECT_TYPE_OT_METRICS_TAG); | 
 |  | 
 |     bool any_fallback = false; | 
 |  | 
 |     unsigned count = enum_class->n_values; | 
 |     const auto *entries = enum_class->values; | 
 |     for (unsigned i = 0; i < count; i++) | 
 |     { | 
 | 	bool fallback = false; | 
 | 	hb_position_t v; | 
 | 	if (!hb_ot_metrics_get_position (font, | 
 | 					(hb_ot_metrics_tag_t) entries[i].value, | 
 | 					&v)) | 
 | 	{ | 
 | 	  hb_ot_metrics_get_position_with_fallback (font, | 
 | 						    (hb_ot_metrics_tag_t) entries[i].value, | 
 | 						    &v); | 
 | 	  any_fallback = fallback = true; | 
 | 	} | 
 | 	printf ("%c%c%c%c", HB_UNTAG(entries[i].value)); | 
 | 	if (verbose) | 
 | 	  printf (": %-33s", entries[i].value_nick); | 
 | 	printf ("	%d	", v); | 
 |  | 
 | 	if (fallback) | 
 | 	  printf ("*"); | 
 | 	printf ("\n"); | 
 |     } | 
 |  | 
 |     if (verbose && any_fallback) | 
 |     { | 
 |       printf ("\n[*] Fallback value\n"); | 
 |     } | 
 |   } | 
 |  | 
 |   void _list_baselines () | 
 |   { | 
 |     hb_tag_t script_tags[HB_OT_MAX_TAGS_PER_SCRIPT]; | 
 |     hb_tag_t language_tags[HB_OT_MAX_TAGS_PER_LANGUAGE]; | 
 |     unsigned script_count = HB_OT_MAX_TAGS_PER_SCRIPT; | 
 |     unsigned language_count = HB_OT_MAX_TAGS_PER_LANGUAGE; | 
 |  | 
 |     hb_ot_tags_from_script_and_language (script, language, | 
 | 					 &script_count, script_tags, | 
 | 					 &language_count, language_tags); | 
 |  | 
 |     hb_tag_t script_tag = script_count ? script_tags[script_count - 1] : HB_TAG_NONE; | 
 |     hb_tag_t language_tag = language_count ? language_tags[0] : HB_TAG_NONE; | 
 |  | 
 |     if (ot_script_str) | 
 |       script_tag = hb_tag_from_string (ot_script_str, -1); | 
 |     if (ot_language_str) | 
 |       language_tag = hb_tag_from_string (ot_language_str, -1); | 
 |  | 
 |  | 
 |     if (verbose) | 
 |     { | 
 |       separator (); | 
 |       printf ("Baselines information:\n\n"); | 
 |       printf ("Tag:  Name				Value\n---------------------------------------------\n"); | 
 |     } | 
 |  | 
 |     GEnumClass *enum_class = (GEnumClass *) g_type_class_ref ((GType) HB_GOBJECT_TYPE_OT_LAYOUT_BASELINE_TAG); | 
 |  | 
 |     bool any_fallback = false; | 
 |  | 
 |     unsigned count = enum_class->n_values; | 
 |     const auto *entries = enum_class->values; | 
 |     for (unsigned i = 0; i < count; i++) | 
 |     { | 
 | 	bool fallback = false; | 
 | 	hb_position_t v; | 
 | 	if (!hb_ot_layout_get_baseline (font, (hb_ot_layout_baseline_tag_t) entries[i].value, | 
 | 					direction, script_tag, language_tag, | 
 | 					&v)) | 
 | 	{ | 
 | 	  hb_ot_layout_get_baseline_with_fallback (font, (hb_ot_layout_baseline_tag_t) entries[i].value, | 
 | 						   direction, script_tag, language_tag, | 
 | 						   &v); | 
 | 	  any_fallback = fallback = true; | 
 | 	} | 
 | 	printf ("%c%c%c%c", HB_UNTAG(entries[i].value)); | 
 | 	if (verbose) | 
 | 	  printf (": %-33s", entries[i].value_nick); | 
 | 	printf ("	%d	", v); | 
 |  | 
 | 	if (fallback) | 
 | 	  printf ("*"); | 
 | 	printf ("\n"); | 
 |     } | 
 |  | 
 |     if (verbose && any_fallback) | 
 |     { | 
 |       printf ("\n[*] Fallback value\n"); | 
 |     } | 
 |   } | 
 | #endif | 
 |  | 
 |   void _list_tables () | 
 |   { | 
 |     if (verbose) | 
 |     { | 
 |       separator (); | 
 |       printf ("Table information:\n\n"); | 
 |       printf ("Tag	Size\n------------\n"); | 
 |     } | 
 |  | 
 |     unsigned count = hb_face_get_table_tags (face, 0, nullptr, nullptr); | 
 |     hb_tag_t *tags = (hb_tag_t *) calloc (count, sizeof (hb_tag_t)); | 
 |     hb_face_get_table_tags (face, 0, &count, tags); | 
 |  | 
 |     for (unsigned i = 0; i < count; i++) | 
 |     { | 
 |       hb_tag_t tag = tags[i]; | 
 |  | 
 |       hb_blob_t *blob = hb_face_reference_table (face, tag); | 
 |  | 
 |       printf ("%c%c%c%c %8u bytes\n", HB_UNTAG (tag), hb_blob_get_length (blob)); | 
 |  | 
 |       hb_blob_destroy (blob); | 
 |     } | 
 |  | 
 |     free (tags); | 
 |   } | 
 |  | 
 |   void | 
 |   _list_unicodes () | 
 |   { | 
 |     if (verbose) | 
 |     { | 
 |       separator (); | 
 |       printf ("Character-set information:\n\n"); | 
 |       printf ("Unicode	Glyph name\n------------------\n"); | 
 |     } | 
 |  | 
 |     hb_set_t *unicodes = hb_set_create (); | 
 |     hb_map_t *cmap = hb_map_create (); | 
 |  | 
 |     hb_face_collect_nominal_glyph_mapping (face, cmap, unicodes); | 
 |  | 
 |     for (hb_codepoint_t u = HB_SET_VALUE_INVALID; | 
 | 	 hb_set_next (unicodes, &u);) | 
 |     { | 
 |       hb_codepoint_t gid = hb_map_get (cmap, u); | 
 |  | 
 |       char glyphname[64]; | 
 |       hb_font_glyph_to_string (font, gid, | 
 | 			       glyphname, sizeof glyphname); | 
 |  | 
 |       printf ("U+%04X	%s\n", u, glyphname); | 
 |     } | 
 |  | 
 |     hb_map_destroy (cmap); | 
 |  | 
 |  | 
 |     /* List variation-selector sequences. */ | 
 |     hb_set_t *vars = hb_set_create (); | 
 |  | 
 |     hb_face_collect_variation_selectors (face, vars); | 
 |  | 
 |     for (hb_codepoint_t vs = HB_SET_VALUE_INVALID; | 
 | 	 hb_set_next (vars, &vs);) | 
 |     { | 
 |       hb_set_clear (unicodes); | 
 |       hb_face_collect_variation_unicodes (face, vs, unicodes); | 
 |  | 
 |       for (hb_codepoint_t u = HB_SET_VALUE_INVALID; | 
 | 	   hb_set_next (unicodes, &u);) | 
 |       { | 
 | 	hb_codepoint_t gid = 0; | 
 | 	HB_UNUSED bool b = hb_font_get_variation_glyph (font, u, vs, &gid); | 
 | 	assert (b); | 
 |  | 
 | 	char glyphname[64]; | 
 | 	hb_font_glyph_to_string (font, gid, | 
 | 				 glyphname, sizeof glyphname); | 
 |  | 
 | 	printf ("U+%04X,U+%04X	%s\n", vs, u, glyphname); | 
 |       } | 
 |     } | 
 |  | 
 |     hb_set_destroy (vars); | 
 |     hb_set_destroy (unicodes); | 
 |   } | 
 |  | 
 |   void | 
 |   _list_glyphs () | 
 |   { | 
 |     if (verbose) | 
 |     { | 
 |       separator (); | 
 |       printf ("Glyph-set information:\n\n"); | 
 |       printf ("GlyphID	Glyph name\n------------------\n"); | 
 |     } | 
 |  | 
 |     unsigned num_glyphs = hb_face_get_glyph_count (face); | 
 |  | 
 |     for (hb_codepoint_t gid = 0; gid < num_glyphs; gid++) | 
 |     { | 
 |       char glyphname[64]; | 
 |       hb_font_glyph_to_string (font, gid, | 
 | 			       glyphname, sizeof glyphname); | 
 |  | 
 |       printf ("%u	%s\n", gid, glyphname); | 
 |     } | 
 |   } | 
 |  | 
 |   void | 
 |   _list_scripts () | 
 |   { | 
 |     if (verbose) | 
 |     { | 
 |       separator (); | 
 |       printf ("Layout script information:\n\n"); | 
 |     } | 
 |  | 
 |     hb_tag_t table_tags[] = {HB_OT_TAG_GSUB, HB_OT_TAG_GPOS, HB_TAG_NONE}; | 
 |  | 
 |     for (unsigned int i = 0; table_tags[i]; i++) | 
 |     { | 
 |       if (verbose) printf ("Table: "); | 
 |       printf ("%c%c%c%c\n", HB_UNTAG (table_tags[i])); | 
 |  | 
 |       hb_tag_t script_array[32]; | 
 |       unsigned script_count = sizeof script_array / sizeof script_array[0]; | 
 |       unsigned script_offset = 0; | 
 |       do | 
 |       { | 
 | 	hb_ot_layout_table_get_script_tags (face, table_tags[i], | 
 | 					    script_offset, | 
 | 					    &script_count, | 
 | 					    script_array); | 
 |  | 
 | 	for (unsigned script_index = 0; script_index < script_count; script_index++) | 
 | 	{ | 
 | 	  printf ("	"); | 
 | 	  if (verbose) printf ("Script: "); | 
 |  | 
 | 	  hb_tag_t hb_sc = hb_script_to_iso15924_tag (hb_ot_tag_to_script (script_array[script_index])); | 
 | 	  if (script_array[script_index] == HB_TAG ('D','F','L','T')) | 
 | 	    hb_sc = HB_SCRIPT_COMMON; | 
 |  | 
 | 	  printf ("%c%c%c%c (%c%c%c%c)\n", | 
 | 		  HB_UNTAG (hb_sc), | 
 | 		  HB_UNTAG (script_array[script_index])); | 
 |  | 
 | 	  hb_tag_t language_array[32]; | 
 | 	  unsigned language_count = sizeof language_array / sizeof language_array[0]; | 
 | 	  unsigned language_offset = 0; | 
 | 	  do | 
 | 	  { | 
 | 	    hb_ot_layout_script_get_language_tags (face, table_tags[i], | 
 | 						   script_offset + script_index, | 
 | 						   language_offset, | 
 | 						   &language_count, | 
 | 						   language_array); | 
 |  | 
 | 	    for (unsigned language_index = 0; language_index < language_count; language_index++) | 
 | 	    { | 
 | 	      printf ("		"); | 
 | 	      if (verbose) printf ("Language: "); | 
 | 	      printf ("%s (%c%c%c%c)\n", | 
 | 		      hb_language_to_string (hb_ot_tag_to_language (language_array[language_index])), | 
 | 		      HB_UNTAG (language_array[language_index])); | 
 | 	    } | 
 |  | 
 | 	    language_offset += language_count; | 
 | 	  } | 
 | 	  while (language_count == sizeof language_array / sizeof language_array[0]); | 
 | 	} | 
 |  | 
 | 	script_offset += script_count; | 
 |       } | 
 |       while (script_count == sizeof script_array / sizeof script_array[0]); | 
 |  | 
 |     } | 
 |  | 
 |   } | 
 |  | 
 |   void | 
 |   _list_features_no_script () | 
 |   { | 
 |     if (verbose) | 
 |     { | 
 |       printf ("Showing all font features with duplicates removed.\n\n"); | 
 |     } | 
 |  | 
 |     hb_tag_t table_tags[] = {HB_OT_TAG_GSUB, HB_OT_TAG_GPOS, HB_TAG_NONE}; | 
 |  | 
 |     hb_set_t *features = hb_set_create (); | 
 |  | 
 |     for (unsigned int i = 0; table_tags[i]; i++) | 
 |     { | 
 |       if (verbose) printf ("Table: "); | 
 |       printf ("%c%c%c%c\n", HB_UNTAG (table_tags[i])); | 
 |  | 
 |       hb_set_clear (features); | 
 |       hb_tag_t feature_array[32]; | 
 |       unsigned feature_count = sizeof feature_array / sizeof feature_array[0]; | 
 |       unsigned feature_offset = 0; | 
 |       do | 
 |       { | 
 | 	hb_ot_layout_table_get_feature_tags (face, table_tags[i], | 
 | 					     feature_offset, | 
 | 					     &feature_count, | 
 | 					     feature_array); | 
 |  | 
 | 	for (unsigned feature_index = 0; feature_index < feature_count; feature_index++) | 
 | 	{ | 
 | 	  if (hb_set_has (features, feature_array[feature_index])) | 
 | 	    continue; | 
 | 	  hb_set_add (features, feature_array[feature_index]); | 
 |  | 
 | 	  hb_ot_name_id_t label_id; | 
 |  | 
 | 	  hb_ot_layout_feature_get_name_ids (face, | 
 | 					     table_tags[i], | 
 | 					     feature_offset + feature_index, | 
 | 					     &label_id, | 
 | 					     nullptr, | 
 | 					     nullptr, | 
 | 					     nullptr, | 
 | 					     nullptr); | 
 |  | 
 | 	  char name[64]; | 
 | 	  unsigned name_len = sizeof name; | 
 |  | 
 | 	  _hb_ot_name_get_utf8 (face, label_id, | 
 | 				language, | 
 | 				&name_len, name); | 
 |  | 
 | 	  printf ("	"); | 
 | 	  if (verbose) printf ("Feature: "); | 
 | 	  printf ("%c%c%c%c", HB_UNTAG (feature_array[feature_index])); | 
 |  | 
 | 	  if (*name) | 
 | 	    printf ("	%s", name); | 
 |  | 
 | 	  printf ("\n"); | 
 | 	} | 
 |  | 
 | 	feature_offset += feature_count; | 
 |       } | 
 |       while (feature_count == sizeof feature_array / sizeof feature_array[0]); | 
 |     } | 
 |  | 
 |     hb_set_destroy (features); | 
 |   } | 
 |  | 
 |   void | 
 |   _list_features () | 
 |   { | 
 |     if (verbose) | 
 |     { | 
 |       separator (); | 
 |       printf ("Layout features information:\n\n"); | 
 |     } | 
 |  | 
 |     hb_tag_t table_tags[] = {HB_OT_TAG_GSUB, HB_OT_TAG_GPOS, HB_TAG_NONE}; | 
 |  | 
 |     if (script == HB_SCRIPT_INVALID && !ot_script_str) | 
 |     { | 
 |       _list_features_no_script (); | 
 |       return; | 
 |     } | 
 |  | 
 |     for (unsigned int i = 0; table_tags[i]; i++) | 
 |     { | 
 |       if (verbose) printf ("Table: "); | 
 |       printf ("%c%c%c%c\n", HB_UNTAG (table_tags[i])); | 
 |  | 
 |       auto table_tag = table_tags[i]; | 
 |  | 
 |       hb_tag_t script_tags[HB_OT_MAX_TAGS_PER_SCRIPT]; | 
 |       hb_tag_t language_tags[HB_OT_MAX_TAGS_PER_LANGUAGE]; | 
 |       unsigned script_count = HB_OT_MAX_TAGS_PER_SCRIPT; | 
 |       unsigned language_count = HB_OT_MAX_TAGS_PER_LANGUAGE; | 
 |  | 
 |       hb_ot_tags_from_script_and_language (script, language, | 
 | 					   &script_count, script_tags, | 
 | 					   &language_count, language_tags); | 
 |  | 
 |       if (ot_script_str) | 
 |       { | 
 | 	script_tags[0] = hb_tag_from_string (ot_script_str, -1); | 
 | 	script_count = 1; | 
 |       } | 
 |       if (ot_language_str) | 
 |       { | 
 | 	language_tags[0] = hb_tag_from_string (ot_language_str, -1); | 
 | 	language_count = 1; | 
 |       } | 
 |  | 
 |       unsigned script_index; | 
 |       hb_tag_t chosen_script; | 
 |       hb_ot_layout_table_select_script (face, table_tag, | 
 | 					script_count, script_tags, | 
 | 					&script_index, &chosen_script); | 
 |  | 
 |       unsigned language_index; | 
 |       hb_tag_t chosen_language; | 
 |       hb_ot_layout_script_select_language2 (face, table_tag, | 
 | 					   script_index, | 
 | 					   language_count, language_tags, | 
 | 					   &language_index, &chosen_language); | 
 |  | 
 |       if (verbose) | 
 |       { | 
 |         if (chosen_script) | 
 | 	{ | 
 | 	  printf ("	Script: %c%c%c%c\n", HB_UNTAG (chosen_script)); | 
 | 	  if (chosen_language) | 
 | 	    printf ("	Language: %c%c%c%c\n", HB_UNTAG (chosen_language)); | 
 | 	  else | 
 | 	    printf ("	Language: Default\n"); | 
 | 	} | 
 |       } | 
 |  | 
 |       unsigned feature_array[32]; | 
 |       unsigned feature_count = sizeof feature_array / sizeof feature_array[0]; | 
 |       unsigned feature_offset = 0; | 
 |       do | 
 |       { | 
 | 	hb_ot_layout_language_get_feature_indexes (face, table_tag, | 
 | 						   script_index, language_index, | 
 | 						   feature_offset, | 
 | 						   &feature_count, | 
 | 						   feature_array); | 
 |  | 
 | 	for (unsigned feature_index = 0; feature_index < feature_count; feature_index++) | 
 | 	{ | 
 | 	  hb_ot_name_id_t label_id; | 
 |  | 
 | 	  hb_ot_layout_feature_get_name_ids (face, | 
 | 					     table_tags[i], | 
 | 					     feature_array[feature_index], | 
 | 					     &label_id, | 
 | 					     nullptr, | 
 | 					     nullptr, | 
 | 					     nullptr, | 
 | 					     nullptr); | 
 |  | 
 | 	  char name[64]; | 
 | 	  unsigned name_len = sizeof name; | 
 |  | 
 | 	  _hb_ot_name_get_utf8 (face, label_id, | 
 | 				language, | 
 | 				&name_len, name); | 
 |  | 
 | 	  printf ("	"); | 
 | 	  if (verbose) printf ("Feature: "); | 
 | 	  hb_tag_t feature_tag; | 
 | 	  unsigned f_count = 1; | 
 | 	  hb_ot_layout_table_get_feature_tags (face, table_tag, | 
 | 					       feature_array[feature_index], | 
 | 					       &f_count, &feature_tag); | 
 | 	  printf ("%c%c%c%c", HB_UNTAG (feature_tag)); | 
 |  | 
 | 	  if (*name) | 
 | 	    printf ("	%s", name); | 
 |  | 
 | 	  printf ("\n"); | 
 | 	} | 
 |  | 
 | 	feature_offset += feature_count; | 
 |       } | 
 |       while (feature_count == sizeof feature_array / sizeof feature_array[0]); | 
 |     } | 
 |   } | 
 |  | 
 | #ifndef HB_NO_VAR | 
 |   void | 
 |   _list_variations () | 
 |   { | 
 |     if (verbose) | 
 |     { | 
 |       separator (); | 
 |       printf ("Variations information:\n\n"); | 
 |     } | 
 |  | 
 |     hb_ot_var_axis_info_t *axes; | 
 |  | 
 |     unsigned count = hb_ot_var_get_axis_infos (face, 0, nullptr, nullptr); | 
 |     axes = (hb_ot_var_axis_info_t *) calloc (count, sizeof (hb_ot_var_axis_info_t)); | 
 |     hb_ot_var_get_axis_infos (face, 0, &count, axes); | 
 |  | 
 |     bool has_hidden = false; | 
 |  | 
 |     if (verbose && count) | 
 |     { | 
 |       printf ("Varitation axes:\n\n"); | 
 |       printf ("Tag	Minimum	Default	Maximum	Name\n------------------------------------\n"); | 
 |     } | 
 |     for (unsigned i = 0; i < count; i++) | 
 |     { | 
 |       const auto &axis = axes[i]; | 
 |       if (axis.flags & HB_OT_VAR_AXIS_FLAG_HIDDEN) | 
 | 	has_hidden = true; | 
 |  | 
 |       char name[64]; | 
 |       unsigned name_len = sizeof name; | 
 |  | 
 |       _hb_ot_name_get_utf8 (face, axis.name_id, | 
 | 			    language, | 
 | 			    &name_len, name); | 
 |  | 
 |       printf ("%c%c%c%c%s	%g	%g	%g	%s\n", | 
 | 	      HB_UNTAG (axis.tag), | 
 | 	      axis.flags & HB_OT_VAR_AXIS_FLAG_HIDDEN ? "*" : "", | 
 | 	      (double) axis.min_value, | 
 | 	      (double) axis.default_value, | 
 | 	      (double) axis.max_value, | 
 | 	      name); | 
 |     } | 
 |     if (verbose && has_hidden) | 
 |       printf ("\n[*] Hidden axis\n"); | 
 |  | 
 |     free (axes); | 
 |     axes = nullptr; | 
 |  | 
 |     count = hb_ot_var_get_named_instance_count (face); | 
 |     if (count) | 
 |     { | 
 |       if (verbose) | 
 |       { | 
 | 	printf ("\n\nNamed instances:\n\n"); | 
 |       printf ("Index	Name				Position\n------------------------------------------------\n"); | 
 |       } | 
 |  | 
 |       for (unsigned i = 0; i < count; i++) | 
 |       { | 
 | 	char name[64]; | 
 | 	unsigned name_len = sizeof name; | 
 |  | 
 | 	hb_ot_name_id_t name_id = hb_ot_var_named_instance_get_subfamily_name_id (face, i); | 
 | 	_hb_ot_name_get_utf8 (face, name_id, | 
 | 			      language, | 
 | 			      &name_len, name); | 
 |  | 
 | 	unsigned coords_count = hb_ot_var_named_instance_get_design_coords (face, i, nullptr, nullptr); | 
 | 	float* coords; | 
 | 	coords = (float *) calloc (coords_count, sizeof (float)); | 
 | 	hb_ot_var_named_instance_get_design_coords (face, i, &coords_count, coords); | 
 |  | 
 | 	printf ("%u	%-32s", i, name); | 
 | 	for (unsigned j = 0; j < coords_count; j++) | 
 | 	  printf ("%g, ", (double) coords[j]); | 
 | 	printf ("\n"); | 
 |  | 
 | 	free (coords); | 
 |       } | 
 |     } | 
 |   } | 
 | #endif | 
 |  | 
 | #ifdef HAVE_CHAFA | 
 |   GString * | 
 |   _palette_chafa_str (unsigned palette_index) | 
 |   { | 
 |     unsigned count = hb_ot_color_palette_get_colors (face, palette_index, 0, | 
 | 						     nullptr, nullptr); | 
 |  | 
 |     hb_color_t *palette = (hb_color_t *) malloc (count * sizeof (hb_color_t)); | 
 |     hb_ot_color_palette_get_colors (face, palette_index, 0, | 
 | 				    &count, palette); | 
 |  | 
 | #define REPEAT 16 | 
 |     hb_color_t *data = (hb_color_t *) malloc (count * REPEAT * sizeof (hb_color_t)); | 
 |     for (unsigned i = 0; i < count; i++) | 
 |       for (unsigned j = 0; j < REPEAT; j++) | 
 | 	data[i * REPEAT + j] = palette[i]; | 
 |     free (palette); | 
 |     palette = nullptr; | 
 |  | 
 |     chafa_set_n_threads (1); // https://github.com/hpjansson/chafa/issues/125#issuecomment-1397475217 | 
 | 			     // | 
 |     gchar **environ = g_get_environ (); | 
 |     ChafaTermInfo *term_info = chafa_term_db_detect (chafa_term_db_get_default (), | 
 | 						     environ); | 
 |  | 
 |     ChafaCanvasMode mode; | 
 |     ChafaPixelMode pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; | 
 |     if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT)) | 
 |       mode = CHAFA_CANVAS_MODE_TRUECOLOR; | 
 |     else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_256)) | 
 |       mode = CHAFA_CANVAS_MODE_INDEXED_240; | 
 |     else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_16)) | 
 |       mode = CHAFA_CANVAS_MODE_INDEXED_16; | 
 |     else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_INVERT_COLORS)) | 
 |       mode = CHAFA_CANVAS_MODE_FGBG_BGFG; | 
 |     else | 
 |       mode = CHAFA_CANVAS_MODE_FGBG; | 
 |  | 
 |     ChafaSymbolMap *symbol_map = chafa_symbol_map_new (); | 
 |     chafa_symbol_map_add_by_tags (symbol_map, | 
 | 				  (ChafaSymbolTags) (CHAFA_SYMBOL_TAG_BLOCK)); | 
 |  | 
 |     ChafaCanvasConfig *config = chafa_canvas_config_new (); | 
 |     chafa_canvas_config_set_canvas_mode (config, mode); | 
 |     chafa_canvas_config_set_pixel_mode (config, pixel_mode); | 
 |     chafa_canvas_config_set_cell_geometry (config, REPEAT, 1); | 
 |     chafa_canvas_config_set_geometry (config, 2 * count, 1); | 
 |     chafa_canvas_config_set_symbol_map (config, symbol_map); | 
 |     chafa_canvas_config_set_color_extractor (config, CHAFA_COLOR_EXTRACTOR_MEDIAN); | 
 |     chafa_canvas_config_set_work_factor (config, 1.0f); | 
 |  | 
 |     ChafaCanvas *canvas = chafa_canvas_new (config); | 
 |     chafa_canvas_draw_all_pixels (canvas, | 
 | 				  G_BYTE_ORDER == G_BIG_ENDIAN | 
 | 				    ? CHAFA_PIXEL_BGRA8_UNASSOCIATED | 
 | 				    : CHAFA_PIXEL_ARGB8_UNASSOCIATED, | 
 | 				  (const guint8 *) data, | 
 | 				  count * REPEAT, | 
 | 				  1, | 
 | 				  sizeof (hb_color_t)); | 
 |  | 
 |     free (data); | 
 |  | 
 |     auto gs = chafa_canvas_print (canvas, term_info); | 
 |  | 
 |     chafa_canvas_unref (canvas); | 
 |     chafa_canvas_config_unref (config); | 
 |     chafa_symbol_map_unref (symbol_map); | 
 |     chafa_term_info_unref (term_info); | 
 |     g_strfreev (environ); | 
 |  | 
 |     return gs; | 
 |   } | 
 | #endif | 
 |  | 
 |   void | 
 |   _list_palettes () | 
 |   { | 
 |     if (verbose) | 
 |     { | 
 |       separator (); | 
 |       printf ("Color palettes information:\n"); | 
 |     } | 
 |  | 
 |     { | 
 |       if (verbose) | 
 |       { | 
 | 	printf ("\nPalettes:\n\n"); | 
 | 	printf ("Index	Flags	Name\n--------------------\n"); | 
 |       } | 
 |       unsigned count = hb_ot_color_palette_get_count (face); | 
 |       for (unsigned i = 0; i < count; i++) | 
 |       { | 
 | 	hb_ot_name_id_t name_id = hb_ot_color_palette_get_name_id (face, i); | 
 | 	hb_ot_color_palette_flags_t flags = hb_ot_color_palette_get_flags (face, i); | 
 |  | 
 | 	char name[64]; | 
 | 	unsigned name_len = sizeof name; | 
 |  | 
 | 	_hb_ot_name_get_utf8 (face, name_id, | 
 | 			      language, | 
 | 			      &name_len, name); | 
 |         const char *type = ""; | 
 | 	if (flags) | 
 | 	{ | 
 | 	  if (flags & HB_OT_COLOR_PALETTE_FLAG_USABLE_WITH_LIGHT_BACKGROUND) | 
 |           { | 
 | 	    if (flags & HB_OT_COLOR_PALETTE_FLAG_USABLE_WITH_DARK_BACKGROUND) | 
 | 	      type = "Both"; | 
 |             else | 
 | 	      type = "Light"; | 
 |           } | 
 |           else { | 
 | 	    type = "Dark"; | 
 |           } | 
 | 	} | 
 |  | 
 | #ifdef HAVE_CHAFA | 
 | 	char *chafa_env = getenv ("HB_CHAFA"); | 
 | 	bool use_chafa = !chafa_env || atoi (chafa_env); | 
 | 	if (verbose && use_chafa && isatty (fileno (stdout))) | 
 | 	{ | 
 | 	  GString *chafa_str = _palette_chafa_str (i); | 
 | 	  printf ("%u	%s	%-23s	%*s\n", i, type, name, | 
 | 		  (int) chafa_str->len, chafa_str->str); | 
 | 	  g_string_free (chafa_str, TRUE); | 
 | 	} | 
 | 	else | 
 | #endif | 
 | 	  printf ("%u	%s	%s\n", i, type, name); | 
 |       } | 
 |     } | 
 |  | 
 |     { | 
 |       if (verbose) | 
 |       { | 
 | 	printf ("\nColors:\n\n"); | 
 | 	printf ("Index	Name\n------------\n"); | 
 |       } | 
 |       unsigned count = hb_ot_color_palette_get_colors (face, 0, 0, nullptr, nullptr); | 
 |       for (unsigned i = 0; i < count; i++) | 
 |       { | 
 | 	hb_ot_name_id_t name_id = hb_ot_color_palette_color_get_name_id (face, i); | 
 |  | 
 | 	char name[64]; | 
 | 	unsigned name_len = sizeof name; | 
 | 	_hb_ot_name_get_utf8 (face, name_id, | 
 | 			      language, | 
 | 			      &name_len, name); | 
 |  | 
 | 	printf ("%u	%s\n", i, name); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   void | 
 |   _list_meta () | 
 |   { | 
 |     if (verbose) | 
 |     { | 
 |       separator (); | 
 |       printf ("Meta information:\n"); | 
 |     } | 
 |  | 
 |     { | 
 |       if (verbose) | 
 |       { | 
 | 	printf ("\nTag	Data\n------------\n"); | 
 |       } | 
 |       unsigned count = hb_ot_meta_get_entry_tags (face, 0, nullptr, nullptr); | 
 |       for (unsigned i = 0; i < count; i++) | 
 |       { | 
 | 	hb_ot_meta_tag_t tag; | 
 | 	unsigned len = 1; | 
 | 	hb_ot_meta_get_entry_tags (face, i, &len, &tag); | 
 |  | 
 | 	hb_blob_t *blob = hb_ot_meta_reference_entry (face, tag); | 
 |  | 
 | 	printf ("%c%c%c%c	%.*s\n", HB_UNTAG (tag), | 
 | 		(int) hb_blob_get_length (blob), | 
 | 		hb_blob_get_data (blob, nullptr)); | 
 |  | 
 | 	hb_blob_destroy (blob); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 | }; | 
 |  | 
 |  | 
 | template <typename consumer_t, | 
 | 	  typename font_options_type> | 
 | struct main_font_t : | 
 |        option_parser_t, | 
 |        font_options_type, | 
 |        consumer_t | 
 | { | 
 |   int operator () (int argc, char **argv) | 
 |   { | 
 |     add_options (); | 
 |  | 
 |     if (argc == 2) | 
 |       consumer_t::show_all = true; | 
 |  | 
 |     parse (&argc, &argv); | 
 |  | 
 |     consumer_t::operator () (this); | 
 |  | 
 |     return 0; | 
 |   } | 
 | }; | 
 |  | 
 | int | 
 | main (int argc, char **argv) | 
 | { | 
 |   return batch_main<info_t> (argc, argv); | 
 | } |