options.cc revision 407f80d62589774f845ef1a6a0a7d841b09d57c6
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
34void
35fail (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
51hb_bool_t debug = false;
52
53static gchar *
54shapers_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
68static G_GNUC_NORETURN gboolean
69show_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
86void
87option_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
99static gboolean
100pre_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
110static gboolean
111post_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
121void
122option_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
135void
136option_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
152static gboolean
153parse_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
174static gboolean
175parse_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
186static G_GNUC_NORETURN gboolean
187list_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
199static gboolean
200parse_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
241void
242view_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
261void
262shape_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
333void
334font_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
349void
350text_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
367void
368output_options_t::add_options (option_parser_t *parser)
369{
370  GOptionEntry entries[] =
371  {
372    {"output-file",	0, 0, G_OPTION_ARG_STRING,	&this->output_file,		"Set output file-name (default: stdout)","filename"},
373    {"output-format",	0, 0, G_OPTION_ARG_STRING,	&this->output_format,		"Set output format",			"format"},
374    {NULL}
375  };
376  parser->add_group (entries,
377		     "output",
378		     "Output options:",
379		     "Options controlling the output",
380		     this);
381}
382
383
384
385hb_font_t *
386font_options_t::get_font (void) const
387{
388  if (font)
389    return font;
390
391  hb_blob_t *blob = NULL;
392
393  /* Create the blob */
394  {
395    char *font_data;
396    unsigned int len = 0;
397    hb_destroy_func_t destroy;
398    void *user_data;
399    hb_memory_mode_t mm;
400
401    /* This is a hell of a lot of code for just reading a file! */
402    if (!font_file)
403      fail (true, "No font file set");
404
405    if (0 == strcmp (font_file, "-")) {
406      /* read it */
407      GString *gs = g_string_new (NULL);
408      char buf[BUFSIZ];
409#ifdef HAVE__SETMODE
410      _setmode (fileno (stdin), _O_BINARY);
411#endif
412      while (!feof (stdin)) {
413	size_t ret = fread (buf, 1, sizeof (buf), stdin);
414	if (ferror (stdin))
415	  fail (false, "Failed reading font from standard input: %s",
416		strerror (errno));
417	g_string_append_len (gs, buf, ret);
418      }
419      len = gs->len;
420      font_data = g_string_free (gs, false);
421      user_data = font_data;
422      destroy = (hb_destroy_func_t) g_free;
423      mm = HB_MEMORY_MODE_WRITABLE;
424    } else {
425      GError *error = NULL;
426      GMappedFile *mf = g_mapped_file_new (font_file, false, &error);
427      if (mf) {
428	font_data = g_mapped_file_get_contents (mf);
429	len = g_mapped_file_get_length (mf);
430	if (len) {
431	  destroy = (hb_destroy_func_t) g_mapped_file_unref;
432	  user_data = (void *) mf;
433	  mm = HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE;
434	} else
435	  g_mapped_file_unref (mf);
436      } else {
437	fail (false, "%s", error->message);
438	//g_error_free (error);
439      }
440      if (!len) {
441	/* GMappedFile is buggy, it doesn't fail if file isn't regular.
442	 * Try reading.
443	 * https://bugzilla.gnome.org/show_bug.cgi?id=659212 */
444        GError *error = NULL;
445	gsize l;
446	if (g_file_get_contents (font_file, &font_data, &l, &error)) {
447	  len = l;
448	  destroy = (hb_destroy_func_t) g_free;
449	  user_data = (void *) font_data;
450	  mm = HB_MEMORY_MODE_WRITABLE;
451	} else {
452	  fail (false, "%s", error->message);
453	  //g_error_free (error);
454	}
455      }
456    }
457
458    blob = hb_blob_create (font_data, len, mm, user_data, destroy);
459  }
460
461  /* Create the face */
462  hb_face_t *face = hb_face_create (blob, face_index);
463  hb_blob_destroy (blob);
464
465
466  font = hb_font_create (face);
467
468  unsigned int upem = hb_face_get_upem (face);
469  hb_font_set_scale (font, upem, upem);
470  hb_face_destroy (face);
471
472#ifdef HAVE_FREETYPE
473  hb_ft_font_set_funcs (font);
474#endif
475
476  return font;
477}
478
479
480const char *
481text_options_t::get_line (unsigned int *len)
482{
483  if (text) {
484    if (text_len == (unsigned int) -1)
485      text_len = strlen (text);
486
487    if (!text_len) {
488      *len = 0;
489      return NULL;
490    }
491
492    const char *ret = text;
493    const char *p = (const char *) memchr (text, '\n', text_len);
494    unsigned int ret_len;
495    if (!p) {
496      ret_len = text_len;
497      text += ret_len;
498      text_len = 0;
499    } else {
500      ret_len = p - ret;
501      text += ret_len + 1;
502      text_len -= ret_len + 1;
503    }
504
505    *len = ret_len;
506    return ret;
507  }
508
509  if (!fp) {
510    if (!text_file)
511      fail (true, "At least one of text or text-file must be set");
512
513    if (0 != strcmp (text_file, "-"))
514      fp = fopen (text_file, "r");
515    else
516      fp = stdin;
517
518    if (!fp)
519      fail (false, "Failed opening text file `%s': %s",
520	    text_file, strerror (errno));
521
522    gs = g_string_new (NULL);
523  }
524
525  g_string_set_size (gs, 0);
526  char buf[BUFSIZ];
527  while (fgets (buf, sizeof (buf), fp)) {
528    unsigned int bytes = strlen (buf);
529    if (bytes && buf[bytes - 1] == '\n') {
530      bytes--;
531      g_string_append_len (gs, buf, bytes);
532      break;
533    }
534      g_string_append_len (gs, buf, bytes);
535  }
536  if (ferror (fp))
537    fail (false, "Failed reading text: %s",
538	  strerror (errno));
539  *len = gs->len;
540  return !*len && feof (fp) ? NULL : gs->str;
541}
542
543
544FILE *
545output_options_t::get_file_handle (void)
546{
547  if (fp)
548    return fp;
549
550  if (output_file)
551    fp = fopen (output_file, "wb");
552  else {
553#ifdef HAVE__SETMODE
554    _setmode (fileno (stdout), _O_BINARY);
555#endif
556    fp = stdout;
557  }
558  if (!fp)
559    fail (false, "Cannot open output file `%s': %s",
560	  g_filename_display_name (output_file), strerror (errno));
561
562  return fp;
563}
564
565static gboolean
566parse_verbose (const char *name G_GNUC_UNUSED,
567	       const char *arg G_GNUC_UNUSED,
568	       gpointer    data G_GNUC_UNUSED,
569	       GError    **error G_GNUC_UNUSED)
570{
571  format_options_t *format_opts = (format_options_t *) data;
572  format_opts->show_text = format_opts->show_unicode = format_opts->show_line_num = true;
573  return true;
574}
575
576void
577format_options_t::add_options (option_parser_t *parser)
578{
579  GOptionEntry entries[] =
580  {
581    {"no-glyph-names",	0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,	&this->show_glyph_names,	"Use glyph indices instead of names",	NULL},
582    {"no-positions",	0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,	&this->show_positions,		"Do not show glyph positions",		NULL},
583    {"no-clusters",	0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,	&this->show_clusters,		"Do not show cluster mapping",		NULL},
584    {"show-text",	0, 0,			  G_OPTION_ARG_NONE,	&this->show_text,		"Show input text",			NULL},
585    {"show-unicode",	0, 0,			  G_OPTION_ARG_NONE,	&this->show_unicode,		"Show input Unicode codepoints",	NULL},
586    {"show-line-num",	0, 0,			  G_OPTION_ARG_NONE,	&this->show_line_num,		"Show line numbers",			NULL},
587    {"verbose",		0, G_OPTION_FLAG_NO_ARG,  G_OPTION_ARG_CALLBACK,(gpointer) &parse_verbose,	"Show everything",			NULL},
588    {NULL}
589  };
590  parser->add_group (entries,
591		     "format",
592		     "Format options:",
593		     "Options controlling the formatting of buffer contents",
594		     this);
595}
596
597void
598format_options_t::serialize_unicode (hb_buffer_t *buffer,
599				     GString     *gs)
600{
601  unsigned int num_glyphs = hb_buffer_get_length (buffer);
602  hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, NULL);
603
604  g_string_append_c (gs, '<');
605  for (unsigned int i = 0; i < num_glyphs; i++)
606  {
607    if (i)
608      g_string_append_c (gs, ',');
609    g_string_append_printf (gs, "U+%04X", info->codepoint);
610    info++;
611  }
612  g_string_append_c (gs, '>');
613}
614
615void
616format_options_t::serialize_glyphs (hb_buffer_t *buffer,
617				    hb_font_t   *font,
618				    hb_bool_t    utf8_clusters,
619				    GString     *gs)
620{
621  unsigned int num_glyphs = hb_buffer_get_length (buffer);
622  hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, NULL);
623  hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffer, NULL);
624  hb_direction_t direction = hb_buffer_get_direction (buffer);
625
626  g_string_append_c (gs, '[');
627  for (unsigned int i = 0; i < num_glyphs; i++)
628  {
629    if (i)
630      g_string_append_c (gs, '|');
631
632    char glyph_name[128];
633    if (show_glyph_names) {
634      hb_font_glyph_to_string (font, info->codepoint, glyph_name, sizeof (glyph_name));
635      g_string_append_printf (gs, "%s", glyph_name);
636    } else
637      g_string_append_printf (gs, "%u", info->codepoint);
638
639    if (show_clusters) {
640      g_string_append_printf (gs, "=%u", info->cluster);
641      if (utf8_clusters)
642	g_string_append (gs, "u8");
643    }
644
645    if (show_positions && (pos->x_offset || pos->y_offset)) {
646      g_string_append_printf (gs, "@%d,%d", pos->x_offset, pos->y_offset);
647    }
648    if (show_positions) {
649      g_string_append_c (gs, '+');
650      if (HB_DIRECTION_IS_HORIZONTAL (direction) || pos->x_advance)
651	g_string_append_printf (gs, "%d", pos->x_advance);
652      if (HB_DIRECTION_IS_VERTICAL (direction) || pos->y_advance)
653	g_string_append_printf (gs, ",%d", pos->y_advance);
654    }
655
656    info++;
657    pos++;
658  }
659  g_string_append_c (gs, ']');
660}
661void
662format_options_t::serialize_line_no (unsigned int  line_no,
663				     GString      *gs)
664{
665  if (show_line_num)
666    g_string_append_printf (gs, "%d: ", line_no);
667}
668void
669format_options_t::serialize_buffer_of_text (hb_buffer_t  *buffer,
670					    unsigned int  line_no,
671					    const char   *text,
672					    unsigned int  text_len,
673					    hb_font_t    *font,
674					    hb_bool_t     utf8_clusters,
675					    GString      *gs)
676{
677  if (show_text) {
678    serialize_line_no (line_no, gs);
679    g_string_append_c (gs, '(');
680    g_string_append_len (gs, text, text_len);
681    g_string_append_c (gs, ')');
682    g_string_append_c (gs, '\n');
683  }
684
685  if (show_unicode) {
686    serialize_line_no (line_no, gs);
687    serialize_unicode (buffer, gs);
688    g_string_append_c (gs, '\n');
689  }
690}
691void
692format_options_t::serialize_message (unsigned int  line_no,
693				     const char   *msg,
694				     GString      *gs)
695{
696  serialize_line_no (line_no, gs);
697  g_string_append_printf (gs, "%s", msg);
698  g_string_append_c (gs, '\n');
699}
700void
701format_options_t::serialize_buffer_of_glyphs (hb_buffer_t  *buffer,
702					      unsigned int  line_no,
703					      const char   *text,
704					      unsigned int  text_len,
705					      hb_font_t    *font,
706					      hb_bool_t     utf8_clusters,
707					      GString      *gs)
708{
709  serialize_line_no (line_no, gs);
710  serialize_glyphs (buffer, font, utf8_clusters, gs);
711  g_string_append_c (gs, '\n');
712}
713