Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright  2011,2012  Google, Inc.
      3  *
      4  *  This is part of HarfBuzz, a text shaping library.
      5  *
      6  * Permission is hereby granted, without written agreement and without
      7  * license or royalty fees, to use, copy, modify, and distribute this
      8  * software and its documentation for any purpose, provided that the
      9  * above copyright notice and the following two paragraphs appear in
     10  * all copies of this software.
     11  *
     12  * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
     13  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
     14  * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
     15  * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
     16  * DAMAGE.
     17  *
     18  * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
     19  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
     20  * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
     21  * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
     22  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
     23  *
     24  * Google Author(s): Behdad Esfahbod
     25  */
     26 
     27 #include "options.hh"
     28 
     29 #ifdef HAVE_FREETYPE
     30 #include <hb-ft.h>
     31 #endif
     32 #include <hb-ot.h>
     33 
     34 static struct supported_font_funcs_t {
     35 	char name[4];
     36 	void (*func) (hb_font_t *);
     37 } supported_font_funcs[] =
     38 {
     39 #ifdef HAVE_FREETYPE
     40   {"ft",	hb_ft_font_set_funcs},
     41 #endif
     42   {"ot",	hb_ot_font_set_funcs},
     43 };
     44 
     45 
     46 void
     47 fail (hb_bool_t suggest_help, const char *format, ...)
     48 {
     49   const char *msg;
     50 
     51   va_list vap;
     52   va_start (vap, format);
     53   msg = g_strdup_vprintf (format, vap);
     54   va_end (vap);
     55   const char *prgname = g_get_prgname ();
     56   g_printerr ("%s: %s\n", prgname, msg);
     57   if (suggest_help)
     58     g_printerr ("Try `%s --help' for more information.\n", prgname);
     59 
     60   exit (1);
     61 }
     62 
     63 
     64 static gchar *
     65 shapers_to_string ()
     66 {
     67   GString *shapers = g_string_new (nullptr);
     68   const char **shaper_list = hb_shape_list_shapers ();
     69 
     70   for (; *shaper_list; shaper_list++) {
     71     g_string_append (shapers, *shaper_list);
     72     g_string_append_c (shapers, ',');
     73   }
     74   g_string_truncate (shapers, MAX (0, (gint)shapers->len - 1));
     75 
     76   return g_string_free (shapers, false);
     77 }
     78 
     79 static G_GNUC_NORETURN gboolean
     80 show_version (const char *name G_GNUC_UNUSED,
     81 	      const char *arg G_GNUC_UNUSED,
     82 	      gpointer    data G_GNUC_UNUSED,
     83 	      GError    **error G_GNUC_UNUSED)
     84 {
     85   g_printf ("%s (%s) %s\n", g_get_prgname (), PACKAGE_NAME, PACKAGE_VERSION);
     86 
     87   char *shapers = shapers_to_string ();
     88   g_printf ("Available shapers: %s\n", shapers);
     89   g_free (shapers);
     90   if (strcmp (HB_VERSION_STRING, hb_version_string ()))
     91     g_printf ("Linked HarfBuzz library has a different version: %s\n", hb_version_string ());
     92 
     93   exit(0);
     94 }
     95 
     96 
     97 void
     98 option_parser_t::add_main_options ()
     99 {
    100   GOptionEntry entries[] =
    101   {
    102     {"version",		0, G_OPTION_FLAG_NO_ARG,
    103 			      G_OPTION_ARG_CALLBACK,	(gpointer) &show_version,	"Show version numbers",			nullptr},
    104     {nullptr}
    105   };
    106   g_option_context_add_main_entries (context, entries, nullptr);
    107 }
    108 
    109 static gboolean
    110 pre_parse (GOptionContext *context G_GNUC_UNUSED,
    111 	   GOptionGroup *group G_GNUC_UNUSED,
    112 	   gpointer data,
    113 	   GError **error)
    114 {
    115   option_group_t *option_group = (option_group_t *) data;
    116   option_group->pre_parse (error);
    117   return *error == nullptr;
    118 }
    119 
    120 static gboolean
    121 post_parse (GOptionContext *context G_GNUC_UNUSED,
    122 	    GOptionGroup *group G_GNUC_UNUSED,
    123 	    gpointer data,
    124 	    GError **error)
    125 {
    126   option_group_t *option_group = static_cast<option_group_t *>(data);
    127   option_group->post_parse (error);
    128   return *error == nullptr;
    129 }
    130 
    131 void
    132 option_parser_t::add_group (GOptionEntry   *entries,
    133 			    const gchar    *name,
    134 			    const gchar    *description,
    135 			    const gchar    *help_description,
    136 			    option_group_t *option_group)
    137 {
    138   GOptionGroup *group = g_option_group_new (name, description, help_description,
    139 					    static_cast<gpointer>(option_group), nullptr);
    140   g_option_group_add_entries (group, entries);
    141   g_option_group_set_parse_hooks (group, pre_parse, post_parse);
    142   g_option_context_add_group (context, group);
    143 }
    144 
    145 void
    146 option_parser_t::parse (int *argc, char ***argv)
    147 {
    148   setlocale (LC_ALL, "");
    149 
    150   GError *parse_error = nullptr;
    151   if (!g_option_context_parse (context, argc, argv, &parse_error))
    152   {
    153     if (parse_error != nullptr) {
    154       fail (true, "%s", parse_error->message);
    155       //g_error_free (parse_error);
    156     } else
    157       fail (true, "Option parse error");
    158   }
    159 }
    160 
    161 
    162 static gboolean
    163 parse_margin (const char *name G_GNUC_UNUSED,
    164 	      const char *arg,
    165 	      gpointer    data,
    166 	      GError    **error G_GNUC_UNUSED)
    167 {
    168   view_options_t *view_opts = (view_options_t *) data;
    169   view_options_t::margin_t &m = view_opts->margin;
    170   switch (sscanf (arg, "%lf%*[ ,]%lf%*[ ,]%lf%*[ ,]%lf", &m.t, &m.r, &m.b, &m.l)) {
    171     case 1: m.r = m.t; HB_FALLTHROUGH;
    172     case 2: m.b = m.t; HB_FALLTHROUGH;
    173     case 3: m.l = m.r; HB_FALLTHROUGH;
    174     case 4: return true;
    175     default:
    176       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
    177 		   "%s argument should be one to four space-separated numbers",
    178 		   name);
    179       return false;
    180   }
    181 }
    182 
    183 
    184 static gboolean
    185 parse_shapers (const char *name G_GNUC_UNUSED,
    186 	       const char *arg,
    187 	       gpointer    data,
    188 	       GError    **error)
    189 {
    190   shape_options_t *shape_opts = (shape_options_t *) data;
    191   char **shapers = g_strsplit (arg, ",", 0);
    192 
    193   for (char **shaper = shapers; *shaper; shaper++) {
    194     bool found = false;
    195     for (const char **hb_shaper = hb_shape_list_shapers (); *hb_shaper; hb_shaper++) {
    196       if (strcmp (*shaper, *hb_shaper) == 0) {
    197         found = true;
    198         break;
    199       }
    200     }
    201     if (!found) {
    202       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
    203 		   "Unknown or unsupported shaper: %s", *shaper);
    204       g_strfreev (shapers);
    205       return false;
    206     }
    207   }
    208 
    209   g_strfreev (shape_opts->shapers);
    210   shape_opts->shapers = shapers;
    211   return true;
    212 }
    213 
    214 static G_GNUC_NORETURN gboolean
    215 list_shapers (const char *name G_GNUC_UNUSED,
    216 	      const char *arg G_GNUC_UNUSED,
    217 	      gpointer    data G_GNUC_UNUSED,
    218 	      GError    **error G_GNUC_UNUSED)
    219 {
    220   for (const char **shaper = hb_shape_list_shapers (); *shaper; shaper++)
    221     g_printf ("%s\n", *shaper);
    222 
    223   exit(0);
    224 }
    225 
    226 
    227 static gboolean
    228 parse_features (const char *name G_GNUC_UNUSED,
    229 	        const char *arg,
    230 	        gpointer    data,
    231 	        GError    **error G_GNUC_UNUSED)
    232 {
    233   shape_options_t *shape_opts = (shape_options_t *) data;
    234   char *s = (char *) arg;
    235   char *p;
    236 
    237   shape_opts->num_features = 0;
    238   g_free (shape_opts->features);
    239   shape_opts->features = nullptr;
    240 
    241   if (!*s)
    242     return true;
    243 
    244   /* count the features first, so we can allocate memory */
    245   p = s;
    246   do {
    247     shape_opts->num_features++;
    248     p = strchr (p, ',');
    249     if (p)
    250       p++;
    251   } while (p);
    252 
    253   shape_opts->features = (hb_feature_t *) calloc (shape_opts->num_features, sizeof (*shape_opts->features));
    254   if (!shape_opts->features)
    255     return false;
    256 
    257   /* now do the actual parsing */
    258   p = s;
    259   shape_opts->num_features = 0;
    260   while (p && *p) {
    261     char *end = strchr (p, ',');
    262     if (hb_feature_from_string (p, end ? end - p : -1, &shape_opts->features[shape_opts->num_features]))
    263       shape_opts->num_features++;
    264     p = end ? end + 1 : nullptr;
    265   }
    266 
    267   return true;
    268 }
    269 
    270 static gboolean
    271 parse_variations (const char *name G_GNUC_UNUSED,
    272 	        const char *arg,
    273 	        gpointer    data,
    274 	        GError    **error G_GNUC_UNUSED)
    275 {
    276   font_options_t *font_opts = (font_options_t *) data;
    277   char *s = (char *) arg;
    278   char *p;
    279 
    280   font_opts->num_variations = 0;
    281   g_free (font_opts->variations);
    282   font_opts->variations = nullptr;
    283 
    284   if (!*s)
    285     return true;
    286 
    287   /* count the variations first, so we can allocate memory */
    288   p = s;
    289   do {
    290     font_opts->num_variations++;
    291     p = strchr (p, ',');
    292     if (p)
    293       p++;
    294   } while (p);
    295 
    296   font_opts->variations = (hb_variation_t *) calloc (font_opts->num_variations, sizeof (*font_opts->variations));
    297   if (!font_opts->variations)
    298     return false;
    299 
    300   /* now do the actual parsing */
    301   p = s;
    302   font_opts->num_variations = 0;
    303   while (p && *p) {
    304     char *end = strchr (p, ',');
    305     if (hb_variation_from_string (p, end ? end - p : -1, &font_opts->variations[font_opts->num_variations]))
    306       font_opts->num_variations++;
    307     p = end ? end + 1 : nullptr;
    308   }
    309 
    310   return true;
    311 }
    312 
    313 static gboolean
    314 parse_text (const char *name G_GNUC_UNUSED,
    315 	    const char *arg,
    316 	    gpointer    data,
    317 	    GError    **error G_GNUC_UNUSED)
    318 {
    319   text_options_t *text_opts = (text_options_t *) data;
    320 
    321   if (text_opts->text)
    322   {
    323     g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
    324 		 "Either --text or --unicodes can be provided but not both");
    325     return false;
    326   }
    327 
    328   text_opts->text_len = -1;
    329   text_opts->text = g_strdup (arg);
    330   return true;
    331 }
    332 
    333 
    334 static gboolean
    335 parse_unicodes (const char *name G_GNUC_UNUSED,
    336 	        const char *arg,
    337 	        gpointer    data,
    338 	        GError    **error G_GNUC_UNUSED)
    339 {
    340   text_options_t *text_opts = (text_options_t *) data;
    341 
    342   if (text_opts->text)
    343   {
    344     g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
    345 		 "Either --text or --unicodes can be provided but not both");
    346     return false;
    347   }
    348 
    349   GString *gs = g_string_new (nullptr);
    350   char *s = (char *) arg;
    351   char *p;
    352 
    353   while (s && *s)
    354   {
    355     while (*s && strchr ("<+>{},;&#\\xXuUnNiI\n\t\v\f\r ", *s))
    356       s++;
    357     if (!*s)
    358       break;
    359 
    360     errno = 0;
    361     hb_codepoint_t u = strtoul (s, &p, 16);
    362     if (errno || s == p)
    363     {
    364       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
    365 		   "Failed parsing Unicode values at: '%s'", s);
    366       return false;
    367     }
    368 
    369     g_string_append_unichar (gs, u);
    370 
    371     s = p;
    372   }
    373 
    374   text_opts->text_len = gs->len;
    375   text_opts->text = g_string_free (gs, FALSE);
    376   return true;
    377 }
    378 
    379 
    380 void
    381 view_options_t::add_options (option_parser_t *parser)
    382 {
    383   GOptionEntry entries[] =
    384   {
    385     {"annotate",	0, 0, G_OPTION_ARG_NONE,	&this->annotate,		"Annotate output rendering",				nullptr},
    386     {"background",	0, 0, G_OPTION_ARG_STRING,	&this->back,			"Set background color (default: " DEFAULT_BACK ")",	"rrggbb/rrggbbaa"},
    387     {"foreground",	0, 0, G_OPTION_ARG_STRING,	&this->fore,			"Set foreground color (default: " DEFAULT_FORE ")",	"rrggbb/rrggbbaa"},
    388     {"line-space",	0, 0, G_OPTION_ARG_DOUBLE,	&this->line_space,		"Set space between lines (default: 0)",			"units"},
    389     {"margin",		0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_margin,	"Margin around output (default: " G_STRINGIFY(DEFAULT_MARGIN) ")","one to four numbers"},
    390     {nullptr}
    391   };
    392   parser->add_group (entries,
    393 		     "view",
    394 		     "View options:",
    395 		     "Options for output rendering",
    396 		     this);
    397 }
    398 
    399 void
    400 shape_options_t::add_options (option_parser_t *parser)
    401 {
    402   GOptionEntry entries[] =
    403   {
    404     {"list-shapers",	0, G_OPTION_FLAG_NO_ARG,
    405 			      G_OPTION_ARG_CALLBACK,	(gpointer) &list_shapers,	"List available shapers and quit",	nullptr},
    406     {"shaper",		0, G_OPTION_FLAG_HIDDEN,
    407 			      G_OPTION_ARG_CALLBACK,	(gpointer) &parse_shapers,	"Hidden duplicate of --shapers",	nullptr},
    408     {"shapers",		0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_shapers,	"Set comma-separated list of shapers to try","list"},
    409     {"direction",	0, 0, G_OPTION_ARG_STRING,	&this->direction,		"Set text direction (default: auto)",	"ltr/rtl/ttb/btt"},
    410     {"language",	0, 0, G_OPTION_ARG_STRING,	&this->language,		"Set text language (default: $LANG)",	"langstr"},
    411     {"script",		0, 0, G_OPTION_ARG_STRING,	&this->script,			"Set text script (default: auto)",	"ISO-15924 tag"},
    412     {"bot",		0, 0, G_OPTION_ARG_NONE,	&this->bot,			"Treat text as beginning-of-paragraph",	nullptr},
    413     {"eot",		0, 0, G_OPTION_ARG_NONE,	&this->eot,			"Treat text as end-of-paragraph",	nullptr},
    414     {"preserve-default-ignorables",0, 0, G_OPTION_ARG_NONE,	&this->preserve_default_ignorables,	"Preserve Default-Ignorable characters",	nullptr},
    415     {"remove-default-ignorables",0, 0, G_OPTION_ARG_NONE,	&this->remove_default_ignorables,	"Remove Default-Ignorable characters",	nullptr},
    416     {"invisible-glyph",	0, 0, G_OPTION_ARG_INT,		&this->invisible_glyph,		"Glyph value to replace Default-Ignorables with",	nullptr},
    417     {"utf8-clusters",	0, 0, G_OPTION_ARG_NONE,	&this->utf8_clusters,		"Use UTF8 byte indices, not char indices",	nullptr},
    418     {"cluster-level",	0, 0, G_OPTION_ARG_INT,		&this->cluster_level,		"Cluster merging level (default: 0)",	"0/1/2"},
    419     {"normalize-glyphs",0, 0, G_OPTION_ARG_NONE,	&this->normalize_glyphs,	"Rearrange glyph clusters in nominal order",	nullptr},
    420     {"verify",		0, 0, G_OPTION_ARG_NONE,	&this->verify,			"Perform sanity checks on shaping results",	nullptr},
    421     {"num-iterations", 'n', 0, G_OPTION_ARG_INT,		&this->num_iterations,		"Run shaper N times (default: 1)",	"N"},
    422     {nullptr}
    423   };
    424   parser->add_group (entries,
    425 		     "shape",
    426 		     "Shape options:",
    427 		     "Options for the shaping process",
    428 		     this);
    429 
    430   const gchar *features_help = "Comma-separated list of font features\n"
    431     "\n"
    432     "    Features can be enabled or disabled, either globally or limited to\n"
    433     "    specific character ranges.  The format for specifying feature settings\n"
    434     "    follows.  All valid CSS font-feature-settings values other than 'normal'\n"
    435     "    and 'inherited' are also accepted, though, not documented below.\n"
    436     "\n"
    437     "    The range indices refer to the positions between Unicode characters,\n"
    438     "    unless the --utf8-clusters is provided, in which case range indices\n"
    439     "    refer to UTF-8 byte indices. The position before the first character\n"
    440     "    is always 0.\n"
    441     "\n"
    442     "    The format is Python-esque.  Here is how it all works:\n"
    443     "\n"
    444     "      Syntax:       Value:    Start:    End:\n"
    445     "\n"
    446     "    Setting value:\n"
    447     "      \"kern\"        1         0                  # Turn feature on\n"
    448     "      \"+kern\"       1         0                  # Turn feature on\n"
    449     "      \"-kern\"       0         0                  # Turn feature off\n"
    450     "      \"kern=0\"      0         0                  # Turn feature off\n"
    451     "      \"kern=1\"      1         0                  # Turn feature on\n"
    452     "      \"aalt=2\"      2         0                  # Choose 2nd alternate\n"
    453     "\n"
    454     "    Setting index:\n"
    455     "      \"kern[]\"      1         0                  # Turn feature on\n"
    456     "      \"kern[:]\"     1         0                  # Turn feature on\n"
    457     "      \"kern[5:]\"    1         5                  # Turn feature on, partial\n"
    458     "      \"kern[:5]\"    1         0         5         # Turn feature on, partial\n"
    459     "      \"kern[3:5]\"   1         3         5         # Turn feature on, range\n"
    460     "      \"kern[3]\"     1         3         3+1       # Turn feature on, single char\n"
    461     "\n"
    462     "    Mixing it all:\n"
    463     "\n"
    464     "      \"aalt[3:5]=2\" 2         3         5         # Turn 2nd alternate on for range";
    465 
    466   GOptionEntry entries2[] =
    467   {
    468     {"features",	0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_features,	features_help,	"list"},
    469     {nullptr}
    470   };
    471   parser->add_group (entries2,
    472 		     "features",
    473 		     "Features options:",
    474 		     "Options for font features used",
    475 		     this);
    476 }
    477 
    478 static gboolean
    479 parse_font_size (const char *name G_GNUC_UNUSED,
    480 		 const char *arg,
    481 		 gpointer    data,
    482 		 GError    **error G_GNUC_UNUSED)
    483 {
    484   font_options_t *font_opts = (font_options_t *) data;
    485   if (0 == strcmp (arg, "upem"))
    486   {
    487     font_opts->font_size_y = font_opts->font_size_x = FONT_SIZE_UPEM;
    488     return true;
    489   }
    490   switch (sscanf (arg, "%lf%*[ ,]%lf", &font_opts->font_size_x, &font_opts->font_size_y)) {
    491     case 1: font_opts->font_size_y = font_opts->font_size_x; HB_FALLTHROUGH;
    492     case 2: return true;
    493     default:
    494       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
    495 		   "%s argument should be one or two space-separated numbers",
    496 		   name);
    497       return false;
    498   }
    499 }
    500 
    501 static gboolean
    502 parse_font_ppem (const char *name G_GNUC_UNUSED,
    503 		 const char *arg,
    504 		 gpointer    data,
    505 		 GError    **error G_GNUC_UNUSED)
    506 {
    507   font_options_t *font_opts = (font_options_t *) data;
    508   switch (sscanf (arg, "%d%*[ ,]%d", &font_opts->x_ppem, &font_opts->y_ppem)) {
    509     case 1: font_opts->y_ppem = font_opts->x_ppem; HB_FALLTHROUGH;
    510     case 2: return true;
    511     default:
    512       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
    513 		   "%s argument should be one or two space-separated numbers",
    514 		   name);
    515       return false;
    516   }
    517 }
    518 
    519 void
    520 font_options_t::add_options (option_parser_t *parser)
    521 {
    522   char *text = nullptr;
    523 
    524   {
    525     static_assert ((ARRAY_LENGTH_CONST (supported_font_funcs) > 0),
    526 		   "No supported font-funcs found.");
    527     GString *s = g_string_new (nullptr);
    528     g_string_printf (s, "Set font functions implementation to use (default: %s)\n\n    Supported font function implementations are: %s",
    529 		     supported_font_funcs[0].name,
    530 		     supported_font_funcs[0].name);
    531     for (unsigned int i = 1; i < ARRAY_LENGTH (supported_font_funcs); i++)
    532     {
    533       g_string_append_c (s, '/');
    534       g_string_append (s, supported_font_funcs[i].name);
    535     }
    536     text = g_string_free (s, FALSE);
    537     parser->free_later (text);
    538   }
    539 
    540   char *font_size_text;
    541   if (default_font_size == FONT_SIZE_UPEM)
    542     font_size_text = (char *) "Font size (default: upem)";
    543   else
    544   {
    545     font_size_text = g_strdup_printf ("Font size (default: %d)", default_font_size);
    546     parser->free_later (font_size_text);
    547   }
    548 
    549   GOptionEntry entries[] =
    550   {
    551     {"font-file",	0, 0, G_OPTION_ARG_STRING,	&this->font_file,		"Set font file-name",				"filename"},
    552     {"face-index",	0, 0, G_OPTION_ARG_INT,		&this->face_index,		"Set face index (default: 0)",			"index"},
    553     {"font-size",	0, default_font_size ? 0 : G_OPTION_FLAG_HIDDEN,
    554 			      G_OPTION_ARG_CALLBACK,	(gpointer) &parse_font_size,	font_size_text,					"1/2 integers or 'upem'"},
    555     {"font-ppem",	0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_font_ppem,	"Set x,y pixels per EM (default: 0; disabled)",	"1/2 integers"},
    556     {"font-ptem",	0, 0, G_OPTION_ARG_DOUBLE,	&this->ptem,			"Set font point-size (default: 0; disabled)",	"point-size"},
    557     {"font-funcs",	0, 0, G_OPTION_ARG_STRING,	&this->font_funcs,		text,						"impl"},
    558     {"ft-load-flags",	0, 0, G_OPTION_ARG_INT,		&this->ft_load_flags,		"Set FreeType load-flags (default: 2)",		"integer"},
    559     {nullptr}
    560   };
    561   parser->add_group (entries,
    562 		     "font",
    563 		     "Font options:",
    564 		     "Options for the font",
    565 		     this);
    566 
    567   const gchar *variations_help = "Comma-separated list of font variations\n"
    568     "\n"
    569     "    Variations are set globally. The format for specifying variation settings\n"
    570     "    follows.  All valid CSS font-variation-settings values other than 'normal'\n"
    571     "    and 'inherited' are also accepted, though, not documented below.\n"
    572     "\n"
    573     "    The format is a tag, optionally followed by an equals sign, followed by a\n"
    574     "    number. For example:\n"
    575     "\n"
    576     "      \"wght=500\"\n"
    577     "      \"slnt=-7.5\"\n";
    578 
    579   GOptionEntry entries2[] =
    580   {
    581     {"variations",	0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_variations,	variations_help,	"list"},
    582     {nullptr}
    583   };
    584   parser->add_group (entries2,
    585 		     "variations",
    586 		     "Variations options:",
    587 		     "Options for font variations used",
    588 		     this);
    589 }
    590 
    591 void
    592 text_options_t::add_options (option_parser_t *parser)
    593 {
    594   GOptionEntry entries[] =
    595   {
    596     {"text",		0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_text,		"Set input text",			"string"},
    597     {"text-file",	0, 0, G_OPTION_ARG_STRING,	&this->text_file,		"Set input text file-name\n\n    If no text is provided, standard input is used for input.\n",		"filename"},
    598     {"unicodes",      'u', 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_unicodes,		"Set input Unicode codepoints",		"list of hex numbers"},
    599     {"text-before",	0, 0, G_OPTION_ARG_STRING,	&this->text_before,		"Set text context before each line",	"string"},
    600     {"text-after",	0, 0, G_OPTION_ARG_STRING,	&this->text_after,		"Set text context after each line",	"string"},
    601     {nullptr}
    602   };
    603   parser->add_group (entries,
    604 		     "text",
    605 		     "Text options:",
    606 		     "Options for the input text",
    607 		     this);
    608 }
    609 
    610 void
    611 output_options_t::add_options (option_parser_t *parser)
    612 {
    613   const char *text;
    614 
    615   if (nullptr == supported_formats)
    616     text = "Set output serialization format";
    617   else
    618   {
    619     char *items = g_strjoinv ("/", const_cast<char **> (supported_formats));
    620     text = g_strdup_printf ("Set output format\n\n    Supported output formats are: %s", items);
    621     g_free (items);
    622     parser->free_later ((char *) text);
    623   }
    624 
    625   GOptionEntry entries[] =
    626   {
    627     {"output-file",   'o', 0, G_OPTION_ARG_STRING,	&this->output_file,		"Set output file-name (default: stdout)","filename"},
    628     {"output-format", 'O', 0, G_OPTION_ARG_STRING,	&this->output_format,		text,					"format"},
    629     {nullptr}
    630   };
    631   parser->add_group (entries,
    632 		     "output",
    633 		     "Output destination & format options:",
    634 		     "Options for the destination & form of the output",
    635 		     this);
    636 }
    637 
    638 
    639 
    640 hb_font_t *
    641 font_options_t::get_font () const
    642 {
    643   if (font)
    644     return font;
    645 
    646   /* Create the blob */
    647   if (!font_file)
    648     fail (true, "No font file set");
    649 
    650   const char *font_path = font_file;
    651 
    652   if (0 == strcmp (font_path, "-"))
    653   {
    654 #if defined(_WIN32) || defined(__CYGWIN__)
    655     setmode (fileno (stdin), O_BINARY);
    656     font_path = "STDIN";
    657 #else
    658     font_path = "/dev/stdin";
    659 #endif
    660   }
    661 
    662   blob = hb_blob_create_from_file (font_path);
    663 
    664   if (blob == hb_blob_get_empty ())
    665     fail (false, "Couldn't read or find %s, or it was empty.", font_path);
    666 
    667   /* Create the face */
    668   hb_face_t *face = hb_face_create (blob, face_index);
    669   hb_blob_destroy (blob);
    670 
    671 
    672   font = hb_font_create (face);
    673 
    674   if (font_size_x == FONT_SIZE_UPEM)
    675     font_size_x = hb_face_get_upem (face);
    676   if (font_size_y == FONT_SIZE_UPEM)
    677     font_size_y = hb_face_get_upem (face);
    678 
    679   hb_font_set_ppem (font, x_ppem, y_ppem);
    680   hb_font_set_ptem (font, ptem);
    681 
    682   int scale_x = (int) scalbnf (font_size_x, subpixel_bits);
    683   int scale_y = (int) scalbnf (font_size_y, subpixel_bits);
    684   hb_font_set_scale (font, scale_x, scale_y);
    685   hb_face_destroy (face);
    686 
    687   hb_font_set_variations (font, variations, num_variations);
    688 
    689   void (*set_font_funcs) (hb_font_t *) = nullptr;
    690   if (!font_funcs)
    691   {
    692     set_font_funcs = supported_font_funcs[0].func;
    693   }
    694   else
    695   {
    696     for (unsigned int i = 0; i < ARRAY_LENGTH (supported_font_funcs); i++)
    697       if (0 == g_ascii_strcasecmp (font_funcs, supported_font_funcs[i].name))
    698       {
    699 	set_font_funcs = supported_font_funcs[i].func;
    700 	break;
    701       }
    702     if (!set_font_funcs)
    703     {
    704       GString *s = g_string_new (nullptr);
    705       for (unsigned int i = 0; i < ARRAY_LENGTH (supported_font_funcs); i++)
    706       {
    707         if (i)
    708 	  g_string_append_c (s, '/');
    709 	g_string_append (s, supported_font_funcs[i].name);
    710       }
    711       char *p = g_string_free (s, FALSE);
    712       fail (false, "Unknown font function implementation `%s'; supported values are: %s; default is %s",
    713 	    font_funcs,
    714 	    p,
    715 	    supported_font_funcs[0].name);
    716       //free (p);
    717     }
    718   }
    719   set_font_funcs (font);
    720 #ifdef HAVE_FREETYPE
    721   hb_ft_font_set_load_flags (font, ft_load_flags);
    722 #endif
    723 
    724   return font;
    725 }
    726 
    727 
    728 const char *
    729 text_options_t::get_line (unsigned int *len)
    730 {
    731   if (text) {
    732     if (!line)
    733     {
    734       line = text;
    735       line_len = text_len;
    736     }
    737     if (line_len == (unsigned int) -1)
    738       line_len = strlen (line);
    739 
    740     if (!line_len) {
    741       *len = 0;
    742       return nullptr;
    743     }
    744 
    745     const char *ret = line;
    746     const char *p = (const char *) memchr (line, '\n', line_len);
    747     unsigned int ret_len;
    748     if (!p) {
    749       ret_len = line_len;
    750       line += ret_len;
    751       line_len = 0;
    752     } else {
    753       ret_len = p - ret;
    754       line += ret_len + 1;
    755       line_len -= ret_len + 1;
    756     }
    757 
    758     *len = ret_len;
    759     return ret;
    760   }
    761 
    762   if (!fp) {
    763     if (!text_file)
    764       fail (true, "At least one of text or text-file must be set");
    765 
    766     if (0 != strcmp (text_file, "-"))
    767       fp = fopen (text_file, "r");
    768     else
    769       fp = stdin;
    770 
    771     if (!fp)
    772       fail (false, "Failed opening text file `%s': %s",
    773 	    text_file, strerror (errno));
    774 
    775     gs = g_string_new (nullptr);
    776   }
    777 
    778   g_string_set_size (gs, 0);
    779   char buf[BUFSIZ];
    780   while (fgets (buf, sizeof (buf), fp)) {
    781     unsigned int bytes = strlen (buf);
    782     if (bytes && buf[bytes - 1] == '\n') {
    783       bytes--;
    784       g_string_append_len (gs, buf, bytes);
    785       break;
    786     }
    787       g_string_append_len (gs, buf, bytes);
    788   }
    789   if (ferror (fp))
    790     fail (false, "Failed reading text: %s",
    791 	  strerror (errno));
    792   *len = gs->len;
    793   return !*len && feof (fp) ? nullptr : gs->str;
    794 }
    795 
    796 
    797 FILE *
    798 output_options_t::get_file_handle ()
    799 {
    800   if (fp)
    801     return fp;
    802 
    803   if (output_file)
    804     fp = fopen (output_file, "wb");
    805   else {
    806 #if defined(_WIN32) || defined(__CYGWIN__)
    807     setmode (fileno (stdout), O_BINARY);
    808 #endif
    809     fp = stdout;
    810   }
    811   if (!fp)
    812     fail (false, "Cannot open output file `%s': %s",
    813 	  g_filename_display_name (output_file), strerror (errno));
    814 
    815   return fp;
    816 }
    817 
    818 static gboolean
    819 parse_verbose (const char *name G_GNUC_UNUSED,
    820 	       const char *arg G_GNUC_UNUSED,
    821 	       gpointer    data G_GNUC_UNUSED,
    822 	       GError    **error G_GNUC_UNUSED)
    823 {
    824   format_options_t *format_opts = (format_options_t *) data;
    825   format_opts->show_text = format_opts->show_unicode = format_opts->show_line_num = true;
    826   return true;
    827 }
    828 
    829 static gboolean
    830 parse_ned (const char *name G_GNUC_UNUSED,
    831 	   const char *arg G_GNUC_UNUSED,
    832 	   gpointer    data G_GNUC_UNUSED,
    833 	   GError    **error G_GNUC_UNUSED)
    834 {
    835   format_options_t *format_opts = (format_options_t *) data;
    836   format_opts->show_clusters = format_opts->show_advances = false;
    837   return true;
    838 }
    839 
    840 void
    841 format_options_t::add_options (option_parser_t *parser)
    842 {
    843   GOptionEntry entries[] =
    844   {
    845     {"show-text",	0, 0, G_OPTION_ARG_NONE,	&this->show_text,		"Prefix each line of output with its corresponding input text",		nullptr},
    846     {"show-unicode",	0, 0, G_OPTION_ARG_NONE,	&this->show_unicode,		"Prefix each line of output with its corresponding input codepoint(s)",	nullptr},
    847     {"show-line-num",	0, 0, G_OPTION_ARG_NONE,	&this->show_line_num,		"Prefix each line of output with its corresponding input line number",	nullptr},
    848     {"verbose",	      'v', G_OPTION_FLAG_NO_ARG,
    849 			      G_OPTION_ARG_CALLBACK,	(gpointer) &parse_verbose,	"Prefix each line of output with all of the above",			nullptr},
    850     {"no-glyph-names",	0, G_OPTION_FLAG_REVERSE,
    851 			      G_OPTION_ARG_NONE,	&this->show_glyph_names,	"Output glyph indices instead of names",				nullptr},
    852     {"no-positions",	0, G_OPTION_FLAG_REVERSE,
    853 			      G_OPTION_ARG_NONE,	&this->show_positions,		"Do not output glyph positions",					nullptr},
    854     {"no-advances",	0, G_OPTION_FLAG_REVERSE,
    855 			      G_OPTION_ARG_NONE,	&this->show_advances,		"Do not output glyph advances",						nullptr},
    856     {"no-clusters",	0, G_OPTION_FLAG_REVERSE,
    857 			      G_OPTION_ARG_NONE,	&this->show_clusters,		"Do not output cluster indices",					nullptr},
    858     {"show-extents",	0, 0, G_OPTION_ARG_NONE,	&this->show_extents,		"Output glyph extents",							nullptr},
    859     {"show-flags",	0, 0, G_OPTION_ARG_NONE,	&this->show_flags,		"Output glyph flags",							nullptr},
    860     {"ned",	      'v', G_OPTION_FLAG_NO_ARG,
    861 			      G_OPTION_ARG_CALLBACK,	(gpointer) &parse_ned,		"No Extra Data; Do not output clusters or advances",			nullptr},
    862     {"trace",	      'V', 0, G_OPTION_ARG_NONE,	&this->trace,			"Output interim shaping results",					nullptr},
    863     {nullptr}
    864   };
    865   parser->add_group (entries,
    866 		     "output-syntax",
    867 		     "Output syntax:\n"
    868          "    text: [<glyph name or index>=<glyph cluster index within input>@<horizontal displacement>,<vertical displacement>+<horizontal advance>,<vertical advance>|...]\n"
    869          "    json: [{\"g\": <glyph name or index>, \"ax\": <horizontal advance>, \"ay\": <vertical advance>, \"dx\": <horizontal displacement>, \"dy\": <vertical displacement>, \"cl\": <glyph cluster index within input>}, ...]\n"
    870          "\nOutput syntax options:",
    871 		     "Options for the syntax of the output",
    872 		     this);
    873 }
    874 
    875 void
    876 format_options_t::serialize_unicode (hb_buffer_t *buffer,
    877 				     GString     *gs)
    878 {
    879   unsigned int num_glyphs = hb_buffer_get_length (buffer);
    880   hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, nullptr);
    881 
    882   g_string_append_c (gs, '<');
    883   for (unsigned int i = 0; i < num_glyphs; i++)
    884   {
    885     if (i)
    886       g_string_append_c (gs, ',');
    887     g_string_append_printf (gs, "U+%04X", info->codepoint);
    888     info++;
    889   }
    890   g_string_append_c (gs, '>');
    891 }
    892 
    893 void
    894 format_options_t::serialize_glyphs (hb_buffer_t *buffer,
    895 				    hb_font_t   *font,
    896 				    hb_buffer_serialize_format_t output_format,
    897 				    hb_buffer_serialize_flags_t flags,
    898 				    GString     *gs)
    899 {
    900   g_string_append_c (gs, '[');
    901   unsigned int num_glyphs = hb_buffer_get_length (buffer);
    902   unsigned int start = 0;
    903 
    904   while (start < num_glyphs)
    905   {
    906     char buf[1024];
    907     unsigned int consumed;
    908     start += hb_buffer_serialize_glyphs (buffer, start, num_glyphs,
    909 					 buf, sizeof (buf), &consumed,
    910 					 font, output_format, flags);
    911     if (!consumed)
    912       break;
    913     g_string_append (gs, buf);
    914   }
    915   g_string_append_c (gs, ']');
    916 }
    917 void
    918 format_options_t::serialize_line_no (unsigned int  line_no,
    919 				     GString      *gs)
    920 {
    921   if (show_line_num)
    922     g_string_append_printf (gs, "%d: ", line_no);
    923 }
    924 void
    925 format_options_t::serialize_buffer_of_text (hb_buffer_t  *buffer,
    926 					    unsigned int  line_no,
    927 					    const char   *text,
    928 					    unsigned int  text_len,
    929 					    hb_font_t    *font,
    930 					    GString      *gs)
    931 {
    932   if (show_text)
    933   {
    934     serialize_line_no (line_no, gs);
    935     g_string_append_c (gs, '(');
    936     g_string_append_len (gs, text, text_len);
    937     g_string_append_c (gs, ')');
    938     g_string_append_c (gs, '\n');
    939   }
    940 
    941   if (show_unicode)
    942   {
    943     serialize_line_no (line_no, gs);
    944     serialize_unicode (buffer, gs);
    945     g_string_append_c (gs, '\n');
    946   }
    947 }
    948 void
    949 format_options_t::serialize_message (unsigned int  line_no,
    950 				     const char   *type,
    951 				     const char   *msg,
    952 				     GString      *gs)
    953 {
    954   serialize_line_no (line_no, gs);
    955   g_string_append_printf (gs, "%s: %s", type, msg);
    956   g_string_append_c (gs, '\n');
    957 }
    958 void
    959 format_options_t::serialize_buffer_of_glyphs (hb_buffer_t  *buffer,
    960 					      unsigned int  line_no,
    961 					      const char   *text,
    962 					      unsigned int  text_len,
    963 					      hb_font_t    *font,
    964 					      hb_buffer_serialize_format_t output_format,
    965 					      hb_buffer_serialize_flags_t format_flags,
    966 					      GString      *gs)
    967 {
    968   serialize_line_no (line_no, gs);
    969   serialize_glyphs (buffer, font, output_format, format_flags, gs);
    970   g_string_append_c (gs, '\n');
    971 }
    972 
    973 void
    974 subset_options_t::add_options (option_parser_t *parser)
    975 {
    976   GOptionEntry entries[] =
    977   {
    978     {"layout", 0, 0, G_OPTION_ARG_NONE,  &this->keep_layout,   "Keep OpenType Layout tables",   nullptr},
    979     {"no-hinting", 0, 0, G_OPTION_ARG_NONE,  &this->drop_hints,   "Whether to drop hints",   nullptr},
    980     {"desubroutinize", 0, 0, G_OPTION_ARG_NONE,  &this->desubroutinize,   "Remove CFF/CFF2 use of subroutines",   nullptr},
    981 
    982     {nullptr}
    983   };
    984   parser->add_group (entries,
    985          "subset",
    986          "Subset options:",
    987          "Options subsetting",
    988          this);
    989 }
    990