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 
     33 
     34 void
     35 fail (hb_bool_t suggest_help, const char *format, ...)
     36 {
     37   const char *msg;
     38 
     39   va_list vap;
     40   va_start (vap, format);
     41   msg = g_strdup_vprintf (format, vap);
     42   const char *prgname = g_get_prgname ();
     43   g_printerr ("%s: %s\n", prgname, msg);
     44   if (suggest_help)
     45     g_printerr ("Try `%s --help' for more information.\n", prgname);
     46 
     47   exit (1);
     48 }
     49 
     50 
     51 hb_bool_t debug = false;
     52 
     53 static gchar *
     54 shapers_to_string (void)
     55 {
     56   GString *shapers = g_string_new (NULL);
     57   const char **shaper_list = hb_shape_list_shapers ();
     58 
     59   for (; *shaper_list; shaper_list++) {
     60     g_string_append (shapers, *shaper_list);
     61     g_string_append_c (shapers, ',');
     62   }
     63   g_string_truncate (shapers, MAX (0, (gint)shapers->len - 1));
     64 
     65   return g_string_free (shapers, false);
     66 }
     67 
     68 static G_GNUC_NORETURN gboolean
     69 show_version (const char *name G_GNUC_UNUSED,
     70 	      const char *arg G_GNUC_UNUSED,
     71 	      gpointer    data G_GNUC_UNUSED,
     72 	      GError    **error G_GNUC_UNUSED)
     73 {
     74   g_printf ("%s (%s) %s\n", g_get_prgname (), PACKAGE_NAME, PACKAGE_VERSION);
     75 
     76   char *shapers = shapers_to_string ();
     77   g_printf ("Available shapers: %s\n", shapers);
     78   g_free (shapers);
     79   if (strcmp (HB_VERSION_STRING, hb_version_string ()))
     80     g_printf ("Linked HarfBuzz library has a different version: %s\n", hb_version_string ());
     81 
     82   exit(0);
     83 }
     84 
     85 
     86 void
     87 option_parser_t::add_main_options (void)
     88 {
     89   GOptionEntry entries[] =
     90   {
     91     {"version",		0, G_OPTION_FLAG_NO_ARG,
     92 			      G_OPTION_ARG_CALLBACK,	(gpointer) &show_version,	"Show version numbers",			NULL},
     93     {"debug",		0, 0, G_OPTION_ARG_NONE,	&debug,				"Free all resources before exit",	NULL},
     94     {NULL}
     95   };
     96   g_option_context_add_main_entries (context, entries, NULL);
     97 }
     98 
     99 static gboolean
    100 pre_parse (GOptionContext *context G_GNUC_UNUSED,
    101 	   GOptionGroup *group G_GNUC_UNUSED,
    102 	   gpointer data,
    103 	   GError **error)
    104 {
    105   option_group_t *option_group = (option_group_t *) data;
    106   option_group->pre_parse (error);
    107   return *error == NULL;
    108 }
    109 
    110 static gboolean
    111 post_parse (GOptionContext *context G_GNUC_UNUSED,
    112 	    GOptionGroup *group G_GNUC_UNUSED,
    113 	    gpointer data,
    114 	    GError **error)
    115 {
    116   option_group_t *option_group = static_cast<option_group_t *>(data);
    117   option_group->post_parse (error);
    118   return *error == NULL;
    119 }
    120 
    121 void
    122 option_parser_t::add_group (GOptionEntry   *entries,
    123 			    const gchar    *name,
    124 			    const gchar    *description,
    125 			    const gchar    *help_description,
    126 			    option_group_t *option_group)
    127 {
    128   GOptionGroup *group = g_option_group_new (name, description, help_description,
    129 					    static_cast<gpointer>(option_group), NULL);
    130   g_option_group_add_entries (group, entries);
    131   g_option_group_set_parse_hooks (group, pre_parse, post_parse);
    132   g_option_context_add_group (context, group);
    133 }
    134 
    135 void
    136 option_parser_t::parse (int *argc, char ***argv)
    137 {
    138   setlocale (LC_ALL, "");
    139 
    140   GError *parse_error = NULL;
    141   if (!g_option_context_parse (context, argc, argv, &parse_error))
    142   {
    143     if (parse_error != NULL) {
    144       fail (true, "%s", parse_error->message);
    145       //g_error_free (parse_error);
    146     } else
    147       fail (true, "Option parse error");
    148   }
    149 }
    150 
    151 
    152 static gboolean
    153 parse_margin (const char *name G_GNUC_UNUSED,
    154 	      const char *arg,
    155 	      gpointer    data,
    156 	      GError    **error G_GNUC_UNUSED)
    157 {
    158   view_options_t *view_opts = (view_options_t *) data;
    159   view_options_t::margin_t &m = view_opts->margin;
    160   switch (sscanf (arg, "%lf %lf %lf %lf", &m.t, &m.r, &m.b, &m.l)) {
    161     case 1: m.r = m.t;
    162     case 2: m.b = m.t;
    163     case 3: m.l = m.r;
    164     case 4: return true;
    165     default:
    166       g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
    167 		   "%s argument should be one to four space-separated numbers",
    168 		   name);
    169       return false;
    170   }
    171 }
    172 
    173 
    174 static gboolean
    175 parse_shapers (const char *name G_GNUC_UNUSED,
    176 	       const char *arg,
    177 	       gpointer    data,
    178 	       GError    **error G_GNUC_UNUSED)
    179 {
    180   shape_options_t *shape_opts = (shape_options_t *) data;
    181   g_strfreev (shape_opts->shapers);
    182   shape_opts->shapers = g_strsplit (arg, ",", 0);
    183   return true;
    184 }
    185 
    186 static G_GNUC_NORETURN gboolean
    187 list_shapers (const char *name G_GNUC_UNUSED,
    188 	      const char *arg G_GNUC_UNUSED,
    189 	      gpointer    data G_GNUC_UNUSED,
    190 	      GError    **error G_GNUC_UNUSED)
    191 {
    192   for (const char **shaper = hb_shape_list_shapers (); *shaper; shaper++)
    193     g_printf ("%s\n", *shaper);
    194 
    195   exit(0);
    196 }
    197 
    198 
    199 static gboolean
    200 parse_features (const char *name G_GNUC_UNUSED,
    201 	        const char *arg,
    202 	        gpointer    data,
    203 	        GError    **error G_GNUC_UNUSED)
    204 {
    205   shape_options_t *shape_opts = (shape_options_t *) data;
    206   char *s = (char *) arg;
    207   char *p;
    208 
    209   shape_opts->num_features = 0;
    210   g_free (shape_opts->features);
    211   shape_opts->features = NULL;
    212 
    213   if (!*s)
    214     return true;
    215 
    216   /* count the features first, so we can allocate memory */
    217   p = s;
    218   do {
    219     shape_opts->num_features++;
    220     p = strchr (p, ',');
    221     if (p)
    222       p++;
    223   } while (p);
    224 
    225   shape_opts->features = (hb_feature_t *) calloc (shape_opts->num_features, sizeof (*shape_opts->features));
    226 
    227   /* now do the actual parsing */
    228   p = s;
    229   shape_opts->num_features = 0;
    230   while (p && *p) {
    231     char *end = strchr (p, ',');
    232     if (hb_feature_from_string (p, end ? end - p : -1, &shape_opts->features[shape_opts->num_features]))
    233       shape_opts->num_features++;
    234     p = end ? end + 1 : NULL;
    235   }
    236 
    237   return true;
    238 }
    239 
    240 
    241 void
    242 view_options_t::add_options (option_parser_t *parser)
    243 {
    244   GOptionEntry entries[] =
    245   {
    246     {"annotate",	0, 0, G_OPTION_ARG_NONE,	&this->annotate,		"Annotate output rendering",				NULL},
    247     {"background",	0, 0, G_OPTION_ARG_STRING,	&this->back,			"Set background color (default: " DEFAULT_BACK ")",	"red/#rrggbb/#rrggbbaa"},
    248     {"foreground",	0, 0, G_OPTION_ARG_STRING,	&this->fore,			"Set foreground color (default: " DEFAULT_FORE ")",	"red/#rrggbb/#rrggbbaa"},
    249     {"line-space",	0, 0, G_OPTION_ARG_DOUBLE,	&this->line_space,		"Set space between lines (default: 0)",			"units"},
    250     {"margin",		0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_margin,	"Margin around output (default: " G_STRINGIFY(DEFAULT_MARGIN) ")","one to four numbers"},
    251     {"font-size",	0, 0, G_OPTION_ARG_DOUBLE,	&this->font_size,		"Font size (default: " G_STRINGIFY(DEFAULT_FONT_SIZE) ")","size"},
    252     {NULL}
    253   };
    254   parser->add_group (entries,
    255 		     "view",
    256 		     "View options:",
    257 		     "Options controlling output rendering",
    258 		     this);
    259 }
    260 
    261 void
    262 shape_options_t::add_options (option_parser_t *parser)
    263 {
    264   GOptionEntry entries[] =
    265   {
    266     {"list-shapers",	0, G_OPTION_FLAG_NO_ARG,
    267 			      G_OPTION_ARG_CALLBACK,	(gpointer) &list_shapers,	"List available shapers and quit",	NULL},
    268     {"shaper",		0, G_OPTION_FLAG_HIDDEN,
    269 			      G_OPTION_ARG_CALLBACK,	(gpointer) &parse_shapers,	"Hidden duplicate of --shapers",	NULL},
    270     {"shapers",		0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_shapers,	"Comma-separated list of shapers to try","list"},
    271     {"direction",	0, 0, G_OPTION_ARG_STRING,	&this->direction,		"Set text direction (default: auto)",	"ltr/rtl/ttb/btt"},
    272     {"language",	0, 0, G_OPTION_ARG_STRING,	&this->language,		"Set text language (default: $LANG)",	"langstr"},
    273     {"script",		0, 0, G_OPTION_ARG_STRING,	&this->script,			"Set text script (default: auto)",	"ISO-15924 tag"},
    274     {"bot",		0, 0, G_OPTION_ARG_NONE,	&this->bot,			"Treat text as beginning-of-paragraph",	NULL},
    275     {"eot",		0, 0, G_OPTION_ARG_NONE,	&this->eot,			"Treat text as end-of-paragraph",	NULL},
    276     {"preserve-default-ignorables",0, 0, G_OPTION_ARG_NONE,	&this->preserve_default_ignorables,	"Preserve Default-Ignorable characters",	NULL},
    277     {"utf8-clusters",	0, 0, G_OPTION_ARG_NONE,	&this->utf8_clusters,		"Use UTF8 byte indices, not char indices",	NULL},
    278     {"normalize-glyphs",0, 0, G_OPTION_ARG_NONE,	&this->normalize_glyphs,	"Rearrange glyph clusters in nominal order",	NULL},
    279     {NULL}
    280   };
    281   parser->add_group (entries,
    282 		     "shape",
    283 		     "Shape options:",
    284 		     "Options controlling the shaping process",
    285 		     this);
    286 
    287   const gchar *features_help = "Comma-separated list of font features\n"
    288     "\n"
    289     "    Features can be enabled or disabled, either globally or limited to\n"
    290     "    specific character ranges.\n"
    291     "\n"
    292     "    The range indices refer to the positions between Unicode characters,\n"
    293     "    unless the --utf8-clusters is provided, in which case range indices\n"
    294     "    refer to UTF-8 byte indices. The position before the first character\n"
    295     "    is always 0.\n"
    296     "\n"
    297     "    The format is Python-esque.  Here is how it all works:\n"
    298     "\n"
    299     "      Syntax:       Value:    Start:    End:\n"
    300     "\n"
    301     "    Setting value:\n"
    302     "      \"kern\"        1         0                  # Turn feature on\n"
    303     "      \"+kern\"       1         0                  # Turn feature on\n"
    304     "      \"-kern\"       0         0                  # Turn feature off\n"
    305     "      \"kern=0\"      0         0                  # Turn feature off\n"
    306     "      \"kern=1\"      1         0                  # Turn feature on\n"
    307     "      \"aalt=2\"      2         0                  # Choose 2nd alternate\n"
    308     "\n"
    309     "    Setting index:\n"
    310     "      \"kern[]\"      1         0                  # Turn feature on\n"
    311     "      \"kern[:]\"     1         0                  # Turn feature on\n"
    312     "      \"kern[5:]\"    1         5                  # Turn feature on, partial\n"
    313     "      \"kern[:5]\"    1         0         5         # Turn feature on, partial\n"
    314     "      \"kern[3:5]\"   1         3         5         # Turn feature on, range\n"
    315     "      \"kern[3]\"     1         3         3+1       # Turn feature on, single char\n"
    316     "\n"
    317     "    Mixing it all:\n"
    318     "\n"
    319     "      \"aalt[3:5]=2\" 2         3         5         # Turn 2nd alternate on for range";
    320 
    321   GOptionEntry entries2[] =
    322   {
    323     {"features",	0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_features,	features_help,	"list"},
    324     {NULL}
    325   };
    326   parser->add_group (entries2,
    327 		     "features",
    328 		     "Features options:",
    329 		     "Options controlling font features used",
    330 		     this);
    331 }
    332 
    333 void
    334 font_options_t::add_options (option_parser_t *parser)
    335 {
    336   GOptionEntry entries[] =
    337   {
    338     {"font-file",	0, 0, G_OPTION_ARG_STRING,	&this->font_file,		"Font file-name",					"filename"},
    339     {"face-index",	0, 0, G_OPTION_ARG_INT,		&this->face_index,		"Face index (default: 0)",                              "index"},
    340     {NULL}
    341   };
    342   parser->add_group (entries,
    343 		     "font",
    344 		     "Font options:",
    345 		     "Options controlling the font",
    346 		     this);
    347 }
    348 
    349 void
    350 text_options_t::add_options (option_parser_t *parser)
    351 {
    352   GOptionEntry entries[] =
    353   {
    354     {"text",		0, 0, G_OPTION_ARG_STRING,	&this->text,			"Set input text",			"string"},
    355     {"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"},
    356     {"text-before",	0, 0, G_OPTION_ARG_STRING,	&this->text_before,		"Set text context before each line",	"string"},
    357     {"text-after",	0, 0, G_OPTION_ARG_STRING,	&this->text_after,		"Set text context after each line",	"string"},
    358     {NULL}
    359   };
    360   parser->add_group (entries,
    361 		     "text",
    362 		     "Text options:",
    363 		     "Options controlling the input text",
    364 		     this);
    365 }
    366 
    367 void
    368 output_options_t::add_options (option_parser_t *parser)
    369 {
    370   const char *text;
    371 
    372   if (NULL == supported_formats)
    373     text = "Set output format";
    374   else
    375     text = g_strdup_printf ("Set output format\n\n    Supported formats are: %s", supported_formats);
    376 
    377   GOptionEntry entries[] =
    378   {
    379     {"output-file",	0, 0, G_OPTION_ARG_STRING,	&this->output_file,		"Set output file-name (default: stdout)","filename"},
    380     {"output-format",	0, 0, G_OPTION_ARG_STRING,	&this->output_format,		text,					"format"},
    381     {NULL}
    382   };
    383   parser->add_group (entries,
    384 		     "output",
    385 		     "Output options:",
    386 		     "Options controlling the output",
    387 		     this);
    388 }
    389 
    390 
    391 
    392 hb_font_t *
    393 font_options_t::get_font (void) const
    394 {
    395   if (font)
    396     return font;
    397 
    398   hb_blob_t *blob = NULL;
    399 
    400   /* Create the blob */
    401   {
    402     char *font_data;
    403     unsigned int len = 0;
    404     hb_destroy_func_t destroy;
    405     void *user_data;
    406     hb_memory_mode_t mm;
    407 
    408     /* This is a hell of a lot of code for just reading a file! */
    409     if (!font_file)
    410       fail (true, "No font file set");
    411 
    412     if (0 == strcmp (font_file, "-")) {
    413       /* read it */
    414       GString *gs = g_string_new (NULL);
    415       char buf[BUFSIZ];
    416 #if defined(_WIN32) || defined(__CYGWIN__)
    417       setmode (fileno (stdin), _O_BINARY);
    418 #endif
    419       while (!feof (stdin)) {
    420 	size_t ret = fread (buf, 1, sizeof (buf), stdin);
    421 	if (ferror (stdin))
    422 	  fail (false, "Failed reading font from standard input: %s",
    423 		strerror (errno));
    424 	g_string_append_len (gs, buf, ret);
    425       }
    426       len = gs->len;
    427       font_data = g_string_free (gs, false);
    428       user_data = font_data;
    429       destroy = (hb_destroy_func_t) g_free;
    430       mm = HB_MEMORY_MODE_WRITABLE;
    431     } else {
    432       GError *error = NULL;
    433       GMappedFile *mf = g_mapped_file_new (font_file, false, &error);
    434       if (mf) {
    435 	font_data = g_mapped_file_get_contents (mf);
    436 	len = g_mapped_file_get_length (mf);
    437 	if (len) {
    438 	  destroy = (hb_destroy_func_t) g_mapped_file_unref;
    439 	  user_data = (void *) mf;
    440 	  mm = HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE;
    441 	} else
    442 	  g_mapped_file_unref (mf);
    443       } else {
    444 	fail (false, "%s", error->message);
    445 	//g_error_free (error);
    446       }
    447       if (!len) {
    448 	/* GMappedFile is buggy, it doesn't fail if file isn't regular.
    449 	 * Try reading.
    450 	 * https://bugzilla.gnome.org/show_bug.cgi?id=659212 */
    451         GError *error = NULL;
    452 	gsize l;
    453 	if (g_file_get_contents (font_file, &font_data, &l, &error)) {
    454 	  len = l;
    455 	  destroy = (hb_destroy_func_t) g_free;
    456 	  user_data = (void *) font_data;
    457 	  mm = HB_MEMORY_MODE_WRITABLE;
    458 	} else {
    459 	  fail (false, "%s", error->message);
    460 	  //g_error_free (error);
    461 	}
    462       }
    463     }
    464 
    465     blob = hb_blob_create (font_data, len, mm, user_data, destroy);
    466   }
    467 
    468   /* Create the face */
    469   hb_face_t *face = hb_face_create (blob, face_index);
    470   hb_blob_destroy (blob);
    471 
    472 
    473   font = hb_font_create (face);
    474 
    475   unsigned int upem = hb_face_get_upem (face);
    476   hb_font_set_scale (font, upem, upem);
    477   hb_face_destroy (face);
    478 
    479 #ifdef HAVE_FREETYPE
    480   hb_ft_font_set_funcs (font);
    481 #endif
    482 
    483   return font;
    484 }
    485 
    486 
    487 const char *
    488 text_options_t::get_line (unsigned int *len)
    489 {
    490   if (text) {
    491     if (text_len == (unsigned int) -1)
    492       text_len = strlen (text);
    493 
    494     if (!text_len) {
    495       *len = 0;
    496       return NULL;
    497     }
    498 
    499     const char *ret = text;
    500     const char *p = (const char *) memchr (text, '\n', text_len);
    501     unsigned int ret_len;
    502     if (!p) {
    503       ret_len = text_len;
    504       text += ret_len;
    505       text_len = 0;
    506     } else {
    507       ret_len = p - ret;
    508       text += ret_len + 1;
    509       text_len -= ret_len + 1;
    510     }
    511 
    512     *len = ret_len;
    513     return ret;
    514   }
    515 
    516   if (!fp) {
    517     if (!text_file)
    518       fail (true, "At least one of text or text-file must be set");
    519 
    520     if (0 != strcmp (text_file, "-"))
    521       fp = fopen (text_file, "r");
    522     else
    523       fp = stdin;
    524 
    525     if (!fp)
    526       fail (false, "Failed opening text file `%s': %s",
    527 	    text_file, strerror (errno));
    528 
    529     gs = g_string_new (NULL);
    530   }
    531 
    532   g_string_set_size (gs, 0);
    533   char buf[BUFSIZ];
    534   while (fgets (buf, sizeof (buf), fp)) {
    535     unsigned int bytes = strlen (buf);
    536     if (bytes && buf[bytes - 1] == '\n') {
    537       bytes--;
    538       g_string_append_len (gs, buf, bytes);
    539       break;
    540     }
    541       g_string_append_len (gs, buf, bytes);
    542   }
    543   if (ferror (fp))
    544     fail (false, "Failed reading text: %s",
    545 	  strerror (errno));
    546   *len = gs->len;
    547   return !*len && feof (fp) ? NULL : gs->str;
    548 }
    549 
    550 
    551 FILE *
    552 output_options_t::get_file_handle (void)
    553 {
    554   if (fp)
    555     return fp;
    556 
    557   if (output_file)
    558     fp = fopen (output_file, "wb");
    559   else {
    560 #if defined(_WIN32) || defined(__CYGWIN__)
    561     setmode (fileno (stdout), _O_BINARY);
    562 #endif
    563     fp = stdout;
    564   }
    565   if (!fp)
    566     fail (false, "Cannot open output file `%s': %s",
    567 	  g_filename_display_name (output_file), strerror (errno));
    568 
    569   return fp;
    570 }
    571 
    572 static gboolean
    573 parse_verbose (const char *name G_GNUC_UNUSED,
    574 	       const char *arg G_GNUC_UNUSED,
    575 	       gpointer    data G_GNUC_UNUSED,
    576 	       GError    **error G_GNUC_UNUSED)
    577 {
    578   format_options_t *format_opts = (format_options_t *) data;
    579   format_opts->show_text = format_opts->show_unicode = format_opts->show_line_num = true;
    580   return true;
    581 }
    582 
    583 void
    584 format_options_t::add_options (option_parser_t *parser)
    585 {
    586   GOptionEntry entries[] =
    587   {
    588     {"no-glyph-names",	0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,	&this->show_glyph_names,	"Use glyph indices instead of names",	NULL},
    589     {"no-positions",	0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,	&this->show_positions,		"Do not show glyph positions",		NULL},
    590     {"no-clusters",	0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,	&this->show_clusters,		"Do not show cluster mapping",		NULL},
    591     {"show-text",	0, 0,			  G_OPTION_ARG_NONE,	&this->show_text,		"Show input text",			NULL},
    592     {"show-unicode",	0, 0,			  G_OPTION_ARG_NONE,	&this->show_unicode,		"Show input Unicode codepoints",	NULL},
    593     {"show-line-num",	0, 0,			  G_OPTION_ARG_NONE,	&this->show_line_num,		"Show line numbers",			NULL},
    594     {"verbose",		0, G_OPTION_FLAG_NO_ARG,  G_OPTION_ARG_CALLBACK,(gpointer) &parse_verbose,	"Show everything",			NULL},
    595     {NULL}
    596   };
    597   parser->add_group (entries,
    598 		     "format",
    599 		     "Format options:",
    600 		     "Options controlling the formatting of buffer contents",
    601 		     this);
    602 }
    603 
    604 void
    605 format_options_t::serialize_unicode (hb_buffer_t *buffer,
    606 				     GString     *gs)
    607 {
    608   unsigned int num_glyphs = hb_buffer_get_length (buffer);
    609   hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, NULL);
    610 
    611   g_string_append_c (gs, '<');
    612   for (unsigned int i = 0; i < num_glyphs; i++)
    613   {
    614     if (i)
    615       g_string_append_c (gs, ',');
    616     g_string_append_printf (gs, "U+%04X", info->codepoint);
    617     info++;
    618   }
    619   g_string_append_c (gs, '>');
    620 }
    621 
    622 void
    623 format_options_t::serialize_glyphs (hb_buffer_t *buffer,
    624 				    hb_font_t   *font,
    625 				    hb_buffer_serialize_format_t output_format,
    626 				    hb_buffer_serialize_flags_t flags,
    627 				    GString     *gs)
    628 {
    629   g_string_append_c (gs, '[');
    630   unsigned int num_glyphs = hb_buffer_get_length (buffer);
    631   unsigned int start = 0;
    632 
    633   while (start < num_glyphs) {
    634     char buf[1024];
    635     unsigned int consumed;
    636     start += hb_buffer_serialize_glyphs (buffer, start, num_glyphs,
    637 					 buf, sizeof (buf), &consumed,
    638 					 font, output_format, flags);
    639     if (!consumed)
    640       break;
    641     g_string_append (gs, buf);
    642   }
    643   g_string_append_c (gs, ']');
    644 }
    645 void
    646 format_options_t::serialize_line_no (unsigned int  line_no,
    647 				     GString      *gs)
    648 {
    649   if (show_line_num)
    650     g_string_append_printf (gs, "%d: ", line_no);
    651 }
    652 void
    653 format_options_t::serialize_buffer_of_text (hb_buffer_t  *buffer,
    654 					    unsigned int  line_no,
    655 					    const char   *text,
    656 					    unsigned int  text_len,
    657 					    hb_font_t    *font,
    658 					    GString      *gs)
    659 {
    660   if (show_text) {
    661     serialize_line_no (line_no, gs);
    662     g_string_append_c (gs, '(');
    663     g_string_append_len (gs, text, text_len);
    664     g_string_append_c (gs, ')');
    665     g_string_append_c (gs, '\n');
    666   }
    667 
    668   if (show_unicode) {
    669     serialize_line_no (line_no, gs);
    670     serialize_unicode (buffer, gs);
    671     g_string_append_c (gs, '\n');
    672   }
    673 }
    674 void
    675 format_options_t::serialize_message (unsigned int  line_no,
    676 				     const char   *msg,
    677 				     GString      *gs)
    678 {
    679   serialize_line_no (line_no, gs);
    680   g_string_append_printf (gs, "%s", msg);
    681   g_string_append_c (gs, '\n');
    682 }
    683 void
    684 format_options_t::serialize_buffer_of_glyphs (hb_buffer_t  *buffer,
    685 					      unsigned int  line_no,
    686 					      const char   *text,
    687 					      unsigned int  text_len,
    688 					      hb_font_t    *font,
    689 					      hb_buffer_serialize_format_t output_format,
    690 					      hb_buffer_serialize_flags_t format_flags,
    691 					      GString      *gs)
    692 {
    693   serialize_line_no (line_no, gs);
    694   serialize_glyphs (buffer, font, output_format, format_flags, gs);
    695   g_string_append_c (gs, '\n');
    696 }
    697