Imported Upstream version 0.9.3
[platform/upstream/harfbuzz.git] / util / options.cc
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
200 static void
201 parse_space (char **pp)
202 {
203   char c;
204 #define ISSPACE(c) ((c)==' '||(c)=='\f'||(c)=='\n'||(c)=='\r'||(c)=='\t'||(c)=='\v')
205   while (c = **pp, ISSPACE (c))
206     (*pp)++;
207 #undef ISSPACE
208 }
209
210 static hb_bool_t
211 parse_char (char **pp, char c)
212 {
213   parse_space (pp);
214
215   if (**pp != c)
216     return false;
217
218   (*pp)++;
219   return true;
220 }
221
222 static hb_bool_t
223 parse_uint (char **pp, unsigned int *pv)
224 {
225   char *p = *pp;
226   unsigned int v;
227
228   v = strtol (p, pp, 0);
229
230   if (p == *pp)
231     return false;
232
233   *pv = v;
234   return true;
235 }
236
237
238 static hb_bool_t
239 parse_feature_value_prefix (char **pp, hb_feature_t *feature)
240 {
241   if (parse_char (pp, '-'))
242     feature->value = 0;
243   else {
244     parse_char (pp, '+');
245     feature->value = 1;
246   }
247
248   return true;
249 }
250
251 static hb_bool_t
252 parse_feature_tag (char **pp, hb_feature_t *feature)
253 {
254   char *p = *pp, c;
255
256   parse_space (pp);
257
258 #define ISALNUM(c) (('a' <= (c) && (c) <= 'z') || ('A' <= (c) && (c) <= 'Z') || ('0' <= (c) && (c) <= '9'))
259   while (c = **pp, ISALNUM(c))
260     (*pp)++;
261 #undef ISALNUM
262
263   if (p == *pp)
264     return false;
265
266   feature->tag = hb_tag_from_string (p, *pp - p);
267   return true;
268 }
269
270 static hb_bool_t
271 parse_feature_indices (char **pp, hb_feature_t *feature)
272 {
273   parse_space (pp);
274
275   hb_bool_t has_start;
276
277   feature->start = 0;
278   feature->end = (unsigned int) -1;
279
280   if (!parse_char (pp, '['))
281     return true;
282
283   has_start = parse_uint (pp, &feature->start);
284
285   if (parse_char (pp, ':')) {
286     parse_uint (pp, &feature->end);
287   } else {
288     if (has_start)
289       feature->end = feature->start + 1;
290   }
291
292   return parse_char (pp, ']');
293 }
294
295 static hb_bool_t
296 parse_feature_value_postfix (char **pp, hb_feature_t *feature)
297 {
298   return !parse_char (pp, '=') || parse_uint (pp, &feature->value);
299 }
300
301
302 static hb_bool_t
303 parse_one_feature (char **pp, hb_feature_t *feature)
304 {
305   return parse_feature_value_prefix (pp, feature) &&
306          parse_feature_tag (pp, feature) &&
307          parse_feature_indices (pp, feature) &&
308          parse_feature_value_postfix (pp, feature) &&
309          (parse_char (pp, ',') || **pp == '\0');
310 }
311
312 static void
313 skip_one_feature (char **pp)
314 {
315   char *e;
316   e = strchr (*pp, ',');
317   if (e)
318     *pp = e + 1;
319   else
320     *pp = *pp + strlen (*pp);
321 }
322
323 static gboolean
324 parse_features (const char *name G_GNUC_UNUSED,
325                 const char *arg,
326                 gpointer    data,
327                 GError    **error G_GNUC_UNUSED)
328 {
329   shape_options_t *shape_opts = (shape_options_t *) data;
330   char *s = (char *) arg;
331   char *p;
332
333   shape_opts->num_features = 0;
334   g_free (shape_opts->features);
335   shape_opts->features = NULL;
336
337   if (!*s)
338     return true;
339
340   /* count the features first, so we can allocate memory */
341   p = s;
342   do {
343     shape_opts->num_features++;
344     p = strchr (p, ',');
345     if (p)
346       p++;
347   } while (p);
348
349   shape_opts->features = (hb_feature_t *) calloc (shape_opts->num_features, sizeof (*shape_opts->features));
350
351   /* now do the actual parsing */
352   p = s;
353   shape_opts->num_features = 0;
354   while (*p) {
355     if (parse_one_feature (&p, &shape_opts->features[shape_opts->num_features]))
356       shape_opts->num_features++;
357     else
358       skip_one_feature (&p);
359   }
360
361   return true;
362 }
363
364
365 void
366 view_options_t::add_options (option_parser_t *parser)
367 {
368   GOptionEntry entries[] =
369   {
370     {"annotate",        0, 0, G_OPTION_ARG_NONE,        &this->annotate,                "Annotate output rendering",                            NULL},
371     {"background",      0, 0, G_OPTION_ARG_STRING,      &this->back,                    "Set background color (default: "DEFAULT_BACK")",       "red/#rrggbb/#rrggbbaa"},
372     {"foreground",      0, 0, G_OPTION_ARG_STRING,      &this->fore,                    "Set foreground color (default: "DEFAULT_FORE")",       "red/#rrggbb/#rrggbbaa"},
373     {"line-space",      0, 0, G_OPTION_ARG_DOUBLE,      &this->line_space,              "Set space between lines (default: 0)",                 "units"},
374     {"margin",          0, 0, G_OPTION_ARG_CALLBACK,    (gpointer) &parse_margin,       "Margin around output (default: "G_STRINGIFY(DEFAULT_MARGIN)")","one to four numbers"},
375     {"font-size",       0, 0, G_OPTION_ARG_DOUBLE,      &this->font_size,               "Font size (default: "G_STRINGIFY(DEFAULT_FONT_SIZE)")","size"},
376     {NULL}
377   };
378   parser->add_group (entries,
379                      "view",
380                      "View options:",
381                      "Options controlling output rendering",
382                      this);
383 }
384
385 void
386 shape_options_t::add_options (option_parser_t *parser)
387 {
388   GOptionEntry entries[] =
389   {
390     {"list-shapers",    0, G_OPTION_FLAG_NO_ARG,
391                               G_OPTION_ARG_CALLBACK,    (gpointer) &list_shapers,       "List available shapers and quit",      NULL},
392     {"shaper",          0, G_OPTION_FLAG_HIDDEN,
393                               G_OPTION_ARG_CALLBACK,    (gpointer) &parse_shapers,      "Hidden duplicate of --shapers",        NULL},
394     {"shapers",         0, 0, G_OPTION_ARG_CALLBACK,    (gpointer) &parse_shapers,      "Comma-separated list of shapers to try","list"},
395     {"direction",       0, 0, G_OPTION_ARG_STRING,      &this->direction,               "Set text direction (default: auto)",   "ltr/rtl/ttb/btt"},
396     {"language",        0, 0, G_OPTION_ARG_STRING,      &this->language,                "Set text language (default: $LANG)",   "langstr"},
397     {"script",          0, 0, G_OPTION_ARG_STRING,      &this->script,                  "Set text script (default: auto)",      "ISO-15924 tag"},
398     {"utf8-clusters",   0, 0, G_OPTION_ARG_NONE,        &this->utf8_clusters,           "Use UTF8 byte indices, not char indices",      NULL},
399     {"normalize-glyphs",0, 0, G_OPTION_ARG_NONE,        &this->normalize_glyphs,        "Rearrange glyph clusters in nominal order",    NULL},
400     {NULL}
401   };
402   parser->add_group (entries,
403                      "shape",
404                      "Shape options:",
405                      "Options controlling the shaping process",
406                      this);
407
408   const gchar *features_help = "Comma-separated list of font features\n"
409     "\n"
410     "    Features can be enabled or disabled, either globally or limited to\n"
411     "    specific character ranges.\n"
412     "\n"
413     "    The range indices refer to the positions between Unicode characters,\n"
414     "    unless the --utf8-clusters is provided, in which case range indices\n"
415     "    refer to UTF-8 byte indices. The position before the first character\n"
416     "    is always 0.\n"
417     "\n"
418     "    The format is Python-esque.  Here is how it all works:\n"
419     "\n"
420     "      Syntax:       Value:    Start:    End:\n"
421     "\n"
422     "    Setting value:\n"
423     "      \"kern\"        1         0         ∞         # Turn feature on\n"
424     "      \"+kern\"       1         0         ∞         # Turn feature on\n"
425     "      \"-kern\"       0         0         ∞         # Turn feature off\n"
426     "      \"kern=0\"      0         0         ∞         # Turn feature off\n"
427     "      \"kern=1\"      1         0         ∞         # Turn feature on\n"
428     "      \"aalt=2\"      2         0         ∞         # Choose 2nd alternate\n"
429     "\n"
430     "    Setting index:\n"
431     "      \"kern[]\"      1         0         ∞         # Turn feature on\n"
432     "      \"kern[:]\"     1         0         ∞         # Turn feature on\n"
433     "      \"kern[5:]\"    1         5         ∞         # Turn feature on, partial\n"
434     "      \"kern[:5]\"    1         0         5         # Turn feature on, partial\n"
435     "      \"kern[3:5]\"   1         3         5         # Turn feature on, range\n"
436     "      \"kern[3]\"     1         3         3+1       # Turn feature on, single char\n"
437     "\n"
438     "    Mixing it all:\n"
439     "\n"
440     "      \"kern[3:5]=0\" 1         3         5         # Turn feature off for range";
441
442   GOptionEntry entries2[] =
443   {
444     {"features",        0, 0, G_OPTION_ARG_CALLBACK,    (gpointer) &parse_features,     features_help,  "list"},
445     {NULL}
446   };
447   parser->add_group (entries2,
448                      "features",
449                      "Features options:",
450                      "Options controlling font features used",
451                      this);
452 }
453
454 void
455 font_options_t::add_options (option_parser_t *parser)
456 {
457   GOptionEntry entries[] =
458   {
459     {"font-file",       0, 0, G_OPTION_ARG_STRING,      &this->font_file,               "Font file-name",                                       "filename"},
460     {"face-index",      0, 0, G_OPTION_ARG_INT,         &this->face_index,              "Face index (default: 0)",                              "index"},
461     {NULL}
462   };
463   parser->add_group (entries,
464                      "font",
465                      "Font options:",
466                      "Options controlling the font",
467                      this);
468 }
469
470 void
471 text_options_t::add_options (option_parser_t *parser)
472 {
473   GOptionEntry entries[] =
474   {
475     {"text",            0, 0, G_OPTION_ARG_STRING,      &this->text,                    "Set input text",                       "string"},
476     {"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.",            "filename"},
477     {NULL}
478   };
479   parser->add_group (entries,
480                      "text",
481                      "Text options:",
482                      "Options controlling the input text",
483                      this);
484 }
485
486 void
487 output_options_t::add_options (option_parser_t *parser)
488 {
489   GOptionEntry entries[] =
490   {
491     {"output-file",     0, 0, G_OPTION_ARG_STRING,      &this->output_file,             "Set output file-name (default: stdout)","filename"},
492     {"output-format",   0, 0, G_OPTION_ARG_STRING,      &this->output_format,           "Set output format",                    "format"},
493     {NULL}
494   };
495   parser->add_group (entries,
496                      "output",
497                      "Output options:",
498                      "Options controlling the output",
499                      this);
500 }
501
502
503
504 hb_font_t *
505 font_options_t::get_font (void) const
506 {
507   if (font)
508     return font;
509
510   hb_blob_t *blob = NULL;
511
512   /* Create the blob */
513   {
514     char *font_data;
515     unsigned int len = 0;
516     hb_destroy_func_t destroy;
517     void *user_data;
518     hb_memory_mode_t mm;
519
520     /* This is a hell of a lot of code for just reading a file! */
521     if (!font_file)
522       fail (true, "No font file set");
523
524     if (0 == strcmp (font_file, "-")) {
525       /* read it */
526       GString *gs = g_string_new (NULL);
527       char buf[BUFSIZ];
528 #ifdef HAVE__SETMODE
529       _setmode (fileno (stdin), _O_BINARY);
530 #endif
531       while (!feof (stdin)) {
532         size_t ret = fread (buf, 1, sizeof (buf), stdin);
533         if (ferror (stdin))
534           fail (false, "Failed reading font from standard input: %s",
535                 strerror (errno));
536         g_string_append_len (gs, buf, ret);
537       }
538       len = gs->len;
539       font_data = g_string_free (gs, false);
540       user_data = font_data;
541       destroy = (hb_destroy_func_t) g_free;
542       mm = HB_MEMORY_MODE_WRITABLE;
543     } else {
544       GError *error = NULL;
545       GMappedFile *mf = g_mapped_file_new (font_file, false, &error);
546       if (mf) {
547         font_data = g_mapped_file_get_contents (mf);
548         len = g_mapped_file_get_length (mf);
549         if (len) {
550           destroy = (hb_destroy_func_t) g_mapped_file_unref;
551           user_data = (void *) mf;
552           mm = HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE;
553         } else
554           g_mapped_file_unref (mf);
555       } else {
556         fail (false, "%s", error->message);
557         //g_error_free (error);
558       }
559       if (!len) {
560         /* GMappedFile is buggy, it doesn't fail if file isn't regular.
561          * Try reading.
562          * https://bugzilla.gnome.org/show_bug.cgi?id=659212 */
563         GError *error = NULL;
564         gsize l;
565         if (g_file_get_contents (font_file, &font_data, &l, &error)) {
566           len = l;
567           destroy = (hb_destroy_func_t) g_free;
568           user_data = (void *) font_data;
569           mm = HB_MEMORY_MODE_WRITABLE;
570         } else {
571           fail (false, "%s", error->message);
572           //g_error_free (error);
573         }
574       }
575     }
576
577     blob = hb_blob_create (font_data, len, mm, user_data, destroy);
578   }
579
580   /* Create the face */
581   hb_face_t *face = hb_face_create (blob, face_index);
582   hb_blob_destroy (blob);
583
584
585   font = hb_font_create (face);
586
587   unsigned int upem = hb_face_get_upem (face);
588   hb_font_set_scale (font, upem, upem);
589   hb_face_destroy (face);
590
591 #ifdef HAVE_FREETYPE
592   hb_ft_font_set_funcs (font);
593 #endif
594
595   return font;
596 }
597
598
599 const char *
600 text_options_t::get_line (unsigned int *len)
601 {
602   if (text) {
603     if (text_len == (unsigned int) -1)
604       text_len = strlen (text);
605
606     if (!text_len) {
607       *len = 0;
608       return NULL;
609     }
610
611     const char *ret = text;
612     const char *p = (const char *) memchr (text, '\n', text_len);
613     unsigned int ret_len;
614     if (!p) {
615       ret_len = text_len;
616       text += ret_len;
617       text_len = 0;
618     } else {
619       ret_len = p - ret;
620       text += ret_len + 1;
621       text_len -= ret_len + 1;
622     }
623
624     *len = ret_len;
625     return ret;
626   }
627
628   if (!fp) {
629     if (!text_file)
630       fail (true, "At least one of text or text-file must be set");
631
632     if (0 != strcmp (text_file, "-"))
633       fp = fopen (text_file, "r");
634     else
635       fp = stdin;
636
637     if (!fp)
638       fail (false, "Failed opening text file `%s': %s",
639             text_file, strerror (errno));
640
641     gs = g_string_new (NULL);
642   }
643
644   g_string_set_size (gs, 0);
645   char buf[BUFSIZ];
646   while (fgets (buf, sizeof (buf), fp)) {
647     unsigned int bytes = strlen (buf);
648     if (bytes && buf[bytes - 1] == '\n') {
649       bytes--;
650       g_string_append_len (gs, buf, bytes);
651       break;
652     }
653       g_string_append_len (gs, buf, bytes);
654   }
655   if (ferror (fp))
656     fail (false, "Failed reading text: %s",
657           strerror (errno));
658   *len = gs->len;
659   return !*len && feof (fp) ? NULL : gs->str;
660 }
661
662
663 FILE *
664 output_options_t::get_file_handle (void)
665 {
666   if (fp)
667     return fp;
668
669   if (output_file)
670     fp = fopen (output_file, "wb");
671   else {
672 #ifdef HAVE__SETMODE
673     _setmode (fileno (stdout), _O_BINARY);
674 #endif
675     fp = stdout;
676   }
677   if (!fp)
678     fail (false, "Cannot open output file `%s': %s",
679           g_filename_display_name (output_file), strerror (errno));
680
681   return fp;
682 }
683
684 static gboolean
685 parse_verbose (const char *name G_GNUC_UNUSED,
686                const char *arg G_GNUC_UNUSED,
687                gpointer    data G_GNUC_UNUSED,
688                GError    **error G_GNUC_UNUSED)
689 {
690   format_options_t *format_opts = (format_options_t *) data;
691   format_opts->show_text = format_opts->show_unicode = format_opts->show_line_num = true;
692   return true;
693 }
694
695 void
696 format_options_t::add_options (option_parser_t *parser)
697 {
698   GOptionEntry entries[] =
699   {
700     {"no-glyph-names",  0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,    &this->show_glyph_names,        "Use glyph indices instead of names",   NULL},
701     {"no-positions",    0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,    &this->show_positions,          "Do not show glyph positions",          NULL},
702     {"no-clusters",     0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,    &this->show_clusters,           "Do not show cluster mapping",          NULL},
703     {"show-text",       0, 0,                     G_OPTION_ARG_NONE,    &this->show_text,               "Show input text",                      NULL},
704     {"show-unicode",    0, 0,                     G_OPTION_ARG_NONE,    &this->show_unicode,            "Show input Unicode codepoints",        NULL},
705     {"show-line-num",   0, 0,                     G_OPTION_ARG_NONE,    &this->show_line_num,           "Show line numbers",                    NULL},
706     {"verbose",         0, G_OPTION_FLAG_NO_ARG,  G_OPTION_ARG_CALLBACK,(gpointer) &parse_verbose,      "Show everything",                      NULL},
707     {NULL}
708   };
709   parser->add_group (entries,
710                      "format",
711                      "Format options:",
712                      "Options controlling the formatting of buffer contents",
713                      this);
714 }
715
716 void
717 format_options_t::serialize_unicode (hb_buffer_t *buffer,
718                                      GString     *gs)
719 {
720   unsigned int num_glyphs = hb_buffer_get_length (buffer);
721   hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, NULL);
722
723   g_string_append_c (gs, '<');
724   for (unsigned int i = 0; i < num_glyphs; i++)
725   {
726     if (i)
727       g_string_append_c (gs, ',');
728     g_string_append_printf (gs, "U+%04X", info->codepoint);
729     info++;
730   }
731   g_string_append_c (gs, '>');
732 }
733
734 void
735 format_options_t::serialize_glyphs (hb_buffer_t *buffer,
736                                     hb_font_t   *font,
737                                     hb_bool_t    utf8_clusters,
738                                     GString     *gs)
739 {
740   unsigned int num_glyphs = hb_buffer_get_length (buffer);
741   hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, NULL);
742   hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffer, NULL);
743
744   g_string_append_c (gs, '[');
745   for (unsigned int i = 0; i < num_glyphs; i++)
746   {
747     if (i)
748       g_string_append_c (gs, '|');
749
750     char glyph_name[128];
751     if (show_glyph_names) {
752       hb_font_glyph_to_string (font, info->codepoint, glyph_name, sizeof (glyph_name));
753       g_string_append_printf (gs, "%s", glyph_name);
754     } else
755       g_string_append_printf (gs, "%u", info->codepoint);
756
757     if (show_clusters) {
758       g_string_append_printf (gs, "=%u", info->cluster);
759       if (utf8_clusters)
760         g_string_append (gs, "u8");
761     }
762
763     if (show_positions && (pos->x_offset || pos->y_offset)) {
764       g_string_append_c (gs, '@');
765       if (pos->x_offset) g_string_append_printf (gs, "%d", pos->x_offset);
766       if (pos->y_offset) g_string_append_printf (gs, ",%d", pos->y_offset);
767     }
768     if (show_positions && (pos->x_advance || pos->y_advance)) {
769       g_string_append_c (gs, '+');
770       if (pos->x_advance) g_string_append_printf (gs, "%d", pos->x_advance);
771       if (pos->y_advance) g_string_append_printf (gs, ",%d", pos->y_advance);
772     }
773
774     info++;
775     pos++;
776   }
777   g_string_append_c (gs, ']');
778 }
779 void
780 format_options_t::serialize_line_no (unsigned int  line_no,
781                                      GString      *gs)
782 {
783   if (show_line_num)
784     g_string_append_printf (gs, "%d: ", line_no);
785 }
786 void
787 format_options_t::serialize_buffer_of_text (hb_buffer_t  *buffer,
788                                             unsigned int  line_no,
789                                             const char   *text,
790                                             unsigned int  text_len,
791                                             hb_font_t    *font,
792                                             hb_bool_t     utf8_clusters,
793                                             GString      *gs)
794 {
795   if (show_text) {
796     serialize_line_no (line_no, gs);
797     g_string_append_c (gs, '(');
798     g_string_append_len (gs, text, text_len);
799     g_string_append_c (gs, ')');
800     g_string_append_c (gs, '\n');
801   }
802
803   if (show_unicode) {
804     serialize_line_no (line_no, gs);
805     serialize_unicode (buffer, gs);
806     g_string_append_c (gs, '\n');
807   }
808 }
809 void
810 format_options_t::serialize_message (unsigned int  line_no,
811                                      const char   *msg,
812                                      GString      *gs)
813 {
814   serialize_line_no (line_no, gs);
815   g_string_append_printf (gs, "%s", msg);
816   g_string_append_c (gs, '\n');
817 }
818 void
819 format_options_t::serialize_buffer_of_glyphs (hb_buffer_t  *buffer,
820                                               unsigned int  line_no,
821                                               const char   *text,
822                                               unsigned int  text_len,
823                                               hb_font_t    *font,
824                                               hb_bool_t     utf8_clusters,
825                                               GString      *gs)
826 {
827   serialize_line_no (line_no, gs);
828   serialize_glyphs (buffer, font, utf8_clusters, gs);
829   g_string_append_c (gs, '\n');
830 }