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