Upgrade to latest harfbuzz
[framework/uifw/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_free (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     {NULL}
400   };
401   parser->add_group (entries,
402                      "shape",
403                      "Shape options:",
404                      "Options controlling the shaping process",
405                      this);
406
407   const gchar *features_help = "Comma-separated list of font features\n"
408     "\n"
409     "    Features can be enabled or disabled, either globally or limited to\n"
410     "    specific character ranges.\n"
411     "\n"
412     "    The range indices refer to the positions between Unicode characters,\n"
413     "    unless the --utf8-clusters is provided, in which case range indices\n"
414     "    refer to UTF-8 byte indices. The position before the first character\n"
415     "    is always 0.\n"
416     "\n"
417     "    The format is Python-esque.  Here is how it all works:\n"
418     "\n"
419     "      Syntax:       Value:    Start:    End:\n"
420     "\n"
421     "    Setting value:\n"
422     "      \"kern\"        1         0         ∞         # Turn feature on\n"
423     "      \"+kern\"       1         0         ∞         # Turn feature on\n"
424     "      \"-kern\"       0         0         ∞         # Turn feature off\n"
425     "      \"kern=0\"      0         0         ∞         # Turn feature off\n"
426     "      \"kern=1\"      1         0         ∞         # Turn feature on\n"
427     "      \"aalt=2\"      2         0         ∞         # Choose 2nd alternate\n"
428     "\n"
429     "    Setting index:\n"
430     "      \"kern[]\"      1         0         ∞         # Turn feature on\n"
431     "      \"kern[:]\"     1         0         ∞         # Turn feature on\n"
432     "      \"kern[5:]\"    1         5         ∞         # Turn feature on, partial\n"
433     "      \"kern[:5]\"    1         0         5         # Turn feature on, partial\n"
434     "      \"kern[3:5]\"   1         3         5         # Turn feature on, range\n"
435     "      \"kern[3]\"     1         3         3+1       # Turn feature on, single char\n"
436     "\n"
437     "    Mixing it all:\n"
438     "\n"
439     "      \"kern[3:5]=0\" 1         3         5         # Turn feature off for range";
440
441   GOptionEntry entries2[] =
442   {
443     {"features",        0, 0, G_OPTION_ARG_CALLBACK,    (gpointer) &parse_features,     features_help,  "list"},
444     {NULL}
445   };
446   parser->add_group (entries2,
447                      "features",
448                      "Features options:",
449                      "Options controlling font features used",
450                      this);
451 }
452
453 void
454 font_options_t::add_options (option_parser_t *parser)
455 {
456   GOptionEntry entries[] =
457   {
458     {"font-file",       0, 0, G_OPTION_ARG_STRING,      &this->font_file,               "Font file-name",                                       "filename"},
459     {"face-index",      0, 0, G_OPTION_ARG_INT,         &this->face_index,              "Face index (default: 0)",                              "index"},
460     {NULL}
461   };
462   parser->add_group (entries,
463                      "font",
464                      "Font options:",
465                      "Options controlling the font",
466                      this);
467 }
468
469 void
470 text_options_t::add_options (option_parser_t *parser)
471 {
472   GOptionEntry entries[] =
473   {
474     {"text",            0, 0, G_OPTION_ARG_STRING,      &this->text,                    "Set input text",                       "string"},
475     {"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"},
476     {NULL}
477   };
478   parser->add_group (entries,
479                      "text",
480                      "Text options:",
481                      "Options controlling the input text",
482                      this);
483 }
484
485 void
486 output_options_t::add_options (option_parser_t *parser)
487 {
488   GOptionEntry entries[] =
489   {
490     {"output-file",     0, 0, G_OPTION_ARG_STRING,      &this->output_file,             "Set output file-name (default: stdout)","filename"},
491     {"output-format",   0, 0, G_OPTION_ARG_STRING,      &this->output_format,           "Set output format",                    "format"},
492     {NULL}
493   };
494   parser->add_group (entries,
495                      "output",
496                      "Output options:",
497                      "Options controlling the output",
498                      this);
499 }
500
501
502
503 hb_font_t *
504 font_options_t::get_font (void) const
505 {
506   if (font)
507     return font;
508
509   hb_blob_t *blob = NULL;
510
511   /* Create the blob */
512   {
513     char *font_data;
514     unsigned int len = 0;
515     hb_destroy_func_t destroy;
516     void *user_data;
517     hb_memory_mode_t mm;
518
519     /* This is a hell of a lot of code for just reading a file! */
520     if (!font_file)
521       fail (true, "No font file set");
522
523     if (0 == strcmp (font_file, "-")) {
524       /* read it */
525       GString *gs = g_string_new (NULL);
526       char buf[BUFSIZ];
527 #ifdef HAVE__SETMODE
528       _setmode (fileno (stdin), _O_BINARY);
529 #endif
530       while (!feof (stdin)) {
531         size_t ret = fread (buf, 1, sizeof (buf), stdin);
532         if (ferror (stdin))
533           fail (false, "Failed reading font from standard input: %s",
534                 strerror (errno));
535         g_string_append_len (gs, buf, ret);
536       }
537       len = gs->len;
538       font_data = g_string_free (gs, false);
539       user_data = font_data;
540       destroy = (hb_destroy_func_t) g_free;
541       mm = HB_MEMORY_MODE_WRITABLE;
542     } else {
543       GError *error = NULL;
544       GMappedFile *mf = g_mapped_file_new (font_file, false, &error);
545       if (mf) {
546         font_data = g_mapped_file_get_contents (mf);
547         len = g_mapped_file_get_length (mf);
548         if (len) {
549           destroy = (hb_destroy_func_t) g_mapped_file_unref;
550           user_data = (void *) mf;
551           mm = HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE;
552         } else
553           g_mapped_file_unref (mf);
554       } else {
555         fail (false, "%s", error->message);
556         //g_error_free (error);
557       }
558       if (!len) {
559         /* GMappedFile is buggy, it doesn't fail if file isn't regular.
560          * Try reading.
561          * https://bugzilla.gnome.org/show_bug.cgi?id=659212 */
562         GError *error = NULL;
563         gsize l;
564         if (g_file_get_contents (font_file, &font_data, &l, &error)) {
565           len = l;
566           destroy = (hb_destroy_func_t) g_free;
567           user_data = (void *) font_data;
568           mm = HB_MEMORY_MODE_WRITABLE;
569         } else {
570           fail (false, "%s", error->message);
571           //g_error_free (error);
572         }
573       }
574     }
575
576     blob = hb_blob_create (font_data, len, mm, user_data, destroy);
577   }
578
579   /* Create the face */
580   hb_face_t *face = hb_face_create (blob, face_index);
581   hb_blob_destroy (blob);
582
583
584   font = hb_font_create (face);
585
586   unsigned int upem = hb_face_get_upem (face);
587   hb_font_set_scale (font, upem, upem);
588   hb_face_destroy (face);
589
590 #ifdef HAVE_FREETYPE
591   hb_ft_font_set_funcs (font);
592 #endif
593
594   return font;
595 }
596
597
598 const char *
599 text_options_t::get_line (unsigned int *len)
600 {
601   if (text) {
602     if (text_len == (unsigned int) -1)
603       text_len = strlen (text);
604
605     if (!text_len) {
606       *len = 0;
607       return NULL;
608     }
609
610     const char *ret = text;
611     const char *p = (const char *) memchr (text, '\n', text_len);
612     unsigned int ret_len;
613     if (!p) {
614       ret_len = text_len;
615       text += ret_len;
616       text_len = 0;
617     } else {
618       ret_len = p - ret;
619       text += ret_len + 1;
620       text_len -= ret_len + 1;
621     }
622
623     *len = ret_len;
624     return ret;
625   }
626
627   if (!fp) {
628     if (!text_file)
629       fail (true, "At least one of text or text-file must be set");
630
631     if (0 != strcmp (text_file, "-"))
632       fp = fopen (text_file, "r");
633     else
634       fp = stdin;
635
636     if (!fp)
637       fail (false, "Failed opening text file `%s': %s",
638             text_file, strerror (errno));
639
640     gs = g_string_new (NULL);
641   }
642
643   g_string_set_size (gs, 0);
644   char buf[BUFSIZ];
645   while (fgets (buf, sizeof (buf), fp)) {
646     unsigned int bytes = strlen (buf);
647     if (bytes && buf[bytes - 1] == '\n') {
648       bytes--;
649       g_string_append_len (gs, buf, bytes);
650       break;
651     }
652       g_string_append_len (gs, buf, bytes);
653   }
654   if (ferror (fp))
655     fail (false, "Failed reading text: %s",
656           strerror (errno));
657   *len = gs->len;
658   return !*len && feof (fp) ? NULL : gs->str;
659 }
660
661
662 FILE *
663 output_options_t::get_file_handle (void)
664 {
665   if (fp)
666     return fp;
667
668   if (output_file)
669     fp = fopen (output_file, "wb");
670   else {
671 #ifdef HAVE__SETMODE
672     _setmode (fileno (stdout), _O_BINARY);
673 #endif
674     fp = stdout;
675   }
676   if (!fp)
677     fail (false, "Cannot open output file `%s': %s",
678           g_filename_display_name (output_file), strerror (errno));
679
680   return fp;
681 }
682
683 static gboolean
684 parse_verbose (const char *name G_GNUC_UNUSED,
685                const char *arg G_GNUC_UNUSED,
686                gpointer    data G_GNUC_UNUSED,
687                GError    **error G_GNUC_UNUSED)
688 {
689   format_options_t *format_opts = (format_options_t *) data;
690   format_opts->show_text = format_opts->show_unicode = format_opts->show_line_num = true;
691   return true;
692 }
693
694 void
695 format_options_t::add_options (option_parser_t *parser)
696 {
697   GOptionEntry entries[] =
698   {
699     {"no-glyph-names",  0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,    &this->show_glyph_names,        "Use glyph indices instead of names",   NULL},
700     {"no-positions",    0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,    &this->show_positions,          "Do not show glyph positions",          NULL},
701     {"no-clusters",     0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,    &this->show_clusters,           "Do not show cluster mapping",          NULL},
702     {"show-text",       0, 0,                     G_OPTION_ARG_NONE,    &this->show_text,               "Show input text",                      NULL},
703     {"show-unicode",    0, 0,                     G_OPTION_ARG_NONE,    &this->show_unicode,            "Show input Unicode codepoints",        NULL},
704     {"show-line-num",   0, 0,                     G_OPTION_ARG_NONE,    &this->show_line_num,           "Show line numbers",                    NULL},
705     {"verbose",         0, G_OPTION_FLAG_NO_ARG,  G_OPTION_ARG_CALLBACK,(gpointer) &parse_verbose,      "Show everything",                      NULL},
706     {NULL}
707   };
708   parser->add_group (entries,
709                      "format",
710                      "Format options:",
711                      "Options controlling the formatting of buffer contents",
712                      this);
713 }
714
715 void
716 format_options_t::serialize_unicode (hb_buffer_t *buffer,
717                                      GString     *gs)
718 {
719   unsigned int num_glyphs = hb_buffer_get_length (buffer);
720   hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, NULL);
721
722   g_string_append_c (gs, '<');
723   for (unsigned int i = 0; i < num_glyphs; i++)
724   {
725     if (i)
726       g_string_append_c (gs, ',');
727     g_string_append_printf (gs, "U+%04X", info->codepoint);
728     info++;
729   }
730   g_string_append_c (gs, '>');
731 }
732
733 void
734 format_options_t::serialize_glyphs (hb_buffer_t *buffer,
735                                     hb_font_t   *font,
736                                     hb_bool_t    utf8_clusters,
737                                     GString     *gs)
738 {
739   unsigned int num_glyphs = hb_buffer_get_length (buffer);
740   hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, NULL);
741   hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffer, NULL);
742
743   g_string_append_c (gs, '[');
744   for (unsigned int i = 0; i < num_glyphs; i++)
745   {
746     if (i)
747       g_string_append_c (gs, '|');
748
749     char glyph_name[32];
750     if (show_glyph_names) {
751       hb_font_get_glyph_name (font, info->codepoint, glyph_name, sizeof (glyph_name));
752       g_string_append_printf (gs, "%s", glyph_name);
753     } else
754       g_string_append_printf (gs, "%u", info->codepoint);
755
756     if (show_clusters) {
757       g_string_append_printf (gs, "=%u", info->cluster);
758       if (utf8_clusters)
759         g_string_append (gs, "u8");
760     }
761
762     if (show_positions && (pos->x_offset || pos->y_offset)) {
763       g_string_append_c (gs, '@');
764       if (pos->x_offset) g_string_append_printf (gs, "%d", pos->x_offset);
765       if (pos->y_offset) g_string_append_printf (gs, ",%d", pos->y_offset);
766     }
767     if (show_positions && (pos->x_advance || pos->y_advance)) {
768       g_string_append_c (gs, '+');
769       if (pos->x_advance) g_string_append_printf (gs, "%d", pos->x_advance);
770       if (pos->y_advance) g_string_append_printf (gs, ",%d", pos->y_advance);
771     }
772
773     info++;
774     pos++;
775   }
776   g_string_append_c (gs, ']');
777 }
778 void
779 format_options_t::serialize_line_no (unsigned int  line_no,
780                                      GString      *gs)
781 {
782   if (show_line_num)
783     g_string_append_printf (gs, "%d: ", line_no);
784 }
785 void
786 format_options_t::serialize_buffer_of_text (hb_buffer_t  *buffer,
787                                             unsigned int  line_no,
788                                             const char   *text,
789                                             unsigned int  text_len,
790                                             hb_font_t    *font,
791                                             hb_bool_t     utf8_clusters,
792                                             GString      *gs)
793 {
794   if (show_text) {
795     serialize_line_no (line_no, gs);
796     g_string_append_c (gs, '(');
797     g_string_append_len (gs, text, text_len);
798     g_string_append_c (gs, ')');
799     g_string_append_c (gs, '\n');
800   }
801
802   if (show_unicode) {
803     serialize_line_no (line_no, gs);
804     serialize_unicode (buffer, gs);
805     g_string_append_c (gs, '\n');
806   }
807 }
808 void
809 format_options_t::serialize_message (unsigned int  line_no,
810                                      const char   *msg,
811                                      GString      *gs)
812 {
813   serialize_line_no (line_no, gs);
814   g_string_append_printf (gs, "%s", msg);
815   g_string_append_c (gs, '\n');
816 }
817 void
818 format_options_t::serialize_buffer_of_glyphs (hb_buffer_t  *buffer,
819                                               unsigned int  line_no,
820                                               const char   *text,
821                                               unsigned int  text_len,
822                                               hb_font_t    *font,
823                                               hb_bool_t     utf8_clusters,
824                                               GString      *gs)
825 {
826   serialize_line_no (line_no, gs);
827   serialize_glyphs (buffer, font, utf8_clusters, gs);
828   g_string_append_c (gs, '\n');
829 }