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