Imported Upstream version 1.4.6
[platform/upstream/harfbuzz.git] / util / options.cc
index c05cee6..0f2e207 100644 (file)
 #ifdef HAVE_FREETYPE
 #include <hb-ft.h>
 #endif
+#ifdef HAVE_OT
+#include <hb-ot.h>
+#endif
+
+struct supported_font_funcs_t {
+       char name[4];
+       void (*func) (hb_font_t *);
+} supported_font_funcs[] =
+{
+#ifdef HAVE_FREETYPE
+  {"ft",       hb_ft_font_set_funcs},
+#endif
+#ifdef HAVE_OT
+  {"ot",       hb_ot_font_set_funcs},
+#endif
+};
 
 
 void
@@ -39,6 +55,7 @@ fail (hb_bool_t suggest_help, const char *format, ...)
   va_list vap;
   va_start (vap, format);
   msg = g_strdup_vprintf (format, vap);
+  va_end (vap);
   const char *prgname = g_get_prgname ();
   g_printerr ("%s: %s\n", prgname, msg);
   if (suggest_help)
@@ -157,7 +174,7 @@ parse_margin (const char *name G_GNUC_UNUSED,
 {
   view_options_t *view_opts = (view_options_t *) data;
   view_options_t::margin_t &m = view_opts->margin;
-  switch (sscanf (arg, "%lf %lf %lf %lf", &m.t, &m.r, &m.b, &m.l)) {
+  switch (sscanf (arg, "%lf%*[ ,]%lf%*[ ,]%lf%*[ ,]%lf", &m.t, &m.r, &m.b, &m.l)) {
     case 1: m.r = m.t;
     case 2: m.b = m.t;
     case 3: m.l = m.r;
@@ -196,166 +213,83 @@ list_shapers (const char *name G_GNUC_UNUSED,
 }
 
 
-
-static void
-parse_space (char **pp)
-{
-  char c;
-#define ISSPACE(c) ((c)==' '||(c)=='\f'||(c)=='\n'||(c)=='\r'||(c)=='\t'||(c)=='\v')
-  while (c = **pp, ISSPACE (c))
-    (*pp)++;
-#undef ISSPACE
-}
-
-static hb_bool_t
-parse_char (char **pp, char c)
-{
-  parse_space (pp);
-
-  if (**pp != c)
-    return false;
-
-  (*pp)++;
-  return true;
-}
-
-static hb_bool_t
-parse_uint (char **pp, unsigned int *pv)
+static gboolean
+parse_features (const char *name G_GNUC_UNUSED,
+               const char *arg,
+               gpointer    data,
+               GError    **error G_GNUC_UNUSED)
 {
-  char *p = *pp;
-  unsigned int v;
+  shape_options_t *shape_opts = (shape_options_t *) data;
+  char *s = (char *) arg;
+  char *p;
 
-  v = strtol (p, pp, 0);
+  shape_opts->num_features = 0;
+  g_free (shape_opts->features);
+  shape_opts->features = NULL;
 
-  if (p == *pp)
-    return false;
+  if (!*s)
+    return true;
 
-  *pv = v;
-  return true;
-}
+  /* count the features first, so we can allocate memory */
+  p = s;
+  do {
+    shape_opts->num_features++;
+    p = strchr (p, ',');
+    if (p)
+      p++;
+  } while (p);
 
+  shape_opts->features = (hb_feature_t *) calloc (shape_opts->num_features, sizeof (*shape_opts->features));
 
-static hb_bool_t
-parse_feature_value_prefix (char **pp, hb_feature_t *feature)
-{
-  if (parse_char (pp, '-'))
-    feature->value = 0;
-  else {
-    parse_char (pp, '+');
-    feature->value = 1;
+  /* now do the actual parsing */
+  p = s;
+  shape_opts->num_features = 0;
+  while (p && *p) {
+    char *end = strchr (p, ',');
+    if (hb_feature_from_string (p, end ? end - p : -1, &shape_opts->features[shape_opts->num_features]))
+      shape_opts->num_features++;
+    p = end ? end + 1 : NULL;
   }
 
   return true;
 }
 
-static hb_bool_t
-parse_feature_tag (char **pp, hb_feature_t *feature)
-{
-  char *p = *pp, c;
-
-  parse_space (pp);
-
-#define ISALNUM(c) (('a' <= (c) && (c) <= 'z') || ('A' <= (c) && (c) <= 'Z') || ('0' <= (c) && (c) <= '9'))
-  while (c = **pp, ISALNUM(c))
-    (*pp)++;
-#undef ISALNUM
-
-  if (p == *pp)
-    return false;
-
-  feature->tag = hb_tag_from_string (p, *pp - p);
-  return true;
-}
-
-static hb_bool_t
-parse_feature_indices (char **pp, hb_feature_t *feature)
-{
-  parse_space (pp);
-
-  hb_bool_t has_start;
-
-  feature->start = 0;
-  feature->end = (unsigned int) -1;
-
-  if (!parse_char (pp, '['))
-    return true;
-
-  has_start = parse_uint (pp, &feature->start);
-
-  if (parse_char (pp, ':')) {
-    parse_uint (pp, &feature->end);
-  } else {
-    if (has_start)
-      feature->end = feature->start + 1;
-  }
-
-  return parse_char (pp, ']');
-}
-
-static hb_bool_t
-parse_feature_value_postfix (char **pp, hb_feature_t *feature)
-{
-  return !parse_char (pp, '=') || parse_uint (pp, &feature->value);
-}
-
-
-static hb_bool_t
-parse_one_feature (char **pp, hb_feature_t *feature)
-{
-  return parse_feature_value_prefix (pp, feature) &&
-        parse_feature_tag (pp, feature) &&
-        parse_feature_indices (pp, feature) &&
-        parse_feature_value_postfix (pp, feature) &&
-        (parse_char (pp, ',') || **pp == '\0');
-}
-
-static void
-skip_one_feature (char **pp)
-{
-  char *e;
-  e = strchr (*pp, ',');
-  if (e)
-    *pp = e + 1;
-  else
-    *pp = *pp + strlen (*pp);
-}
-
 static gboolean
-parse_features (const char *name G_GNUC_UNUSED,
+parse_variations (const char *name G_GNUC_UNUSED,
                const char *arg,
                gpointer    data,
                GError    **error G_GNUC_UNUSED)
 {
-  shape_options_t *shape_opts = (shape_options_t *) data;
+  font_options_t *font_opts = (font_options_t *) data;
   char *s = (char *) arg;
   char *p;
 
-  shape_opts->num_features = 0;
-  g_free (shape_opts->features);
-  shape_opts->features = NULL;
+  font_opts->num_variations = 0;
+  g_free (font_opts->variations);
+  font_opts->variations = NULL;
 
   if (!*s)
     return true;
 
-  /* count the features first, so we can allocate memory */
+  /* count the variations first, so we can allocate memory */
   p = s;
   do {
-    shape_opts->num_features++;
+    font_opts->num_variations++;
     p = strchr (p, ',');
     if (p)
       p++;
   } while (p);
 
-  shape_opts->features = (hb_feature_t *) calloc (shape_opts->num_features, sizeof (*shape_opts->features));
+  font_opts->variations = (hb_variation_t *) calloc (font_opts->num_variations, sizeof (*font_opts->variations));
 
   /* now do the actual parsing */
   p = s;
-  shape_opts->num_features = 0;
-  while (*p) {
-    if (parse_one_feature (&p, &shape_opts->features[shape_opts->num_features]))
-      shape_opts->num_features++;
-    else
-      skip_one_feature (&p);
+  font_opts->num_variations = 0;
+  while (p && *p) {
+    char *end = strchr (p, ',');
+    if (hb_variation_from_string (p, end ? end - p : -1, &font_opts->variations[font_opts->num_variations]))
+      font_opts->num_variations++;
+    p = end ? end + 1 : NULL;
   }
 
   return true;
@@ -368,17 +302,16 @@ view_options_t::add_options (option_parser_t *parser)
   GOptionEntry entries[] =
   {
     {"annotate",       0, 0, G_OPTION_ARG_NONE,        &this->annotate,                "Annotate output rendering",                            NULL},
-    {"background",     0, 0, G_OPTION_ARG_STRING,      &this->back,                    "Set background color (default: "DEFAULT_BACK")",       "red/#rrggbb/#rrggbbaa"},
-    {"foreground",     0, 0, G_OPTION_ARG_STRING,      &this->fore,                    "Set foreground color (default: "DEFAULT_FORE")",       "red/#rrggbb/#rrggbbaa"},
+    {"background",     0, 0, G_OPTION_ARG_STRING,      &this->back,                    "Set background color (default: " DEFAULT_BACK ")",     "rrggbb/rrggbbaa"},
+    {"foreground",     0, 0, G_OPTION_ARG_STRING,      &this->fore,                    "Set foreground color (default: " DEFAULT_FORE ")",     "rrggbb/rrggbbaa"},
     {"line-space",     0, 0, G_OPTION_ARG_DOUBLE,      &this->line_space,              "Set space between lines (default: 0)",                 "units"},
-    {"margin",         0, 0, G_OPTION_ARG_CALLBACK,    (gpointer) &parse_margin,       "Margin around output (default: "G_STRINGIFY(DEFAULT_MARGIN)")","one to four numbers"},
-    {"font-size",      0, 0, G_OPTION_ARG_DOUBLE,      &this->font_size,               "Font size (default: "G_STRINGIFY(DEFAULT_FONT_SIZE)")","size"},
+    {"margin",         0, 0, G_OPTION_ARG_CALLBACK,    (gpointer) &parse_margin,       "Margin around output (default: " G_STRINGIFY(DEFAULT_MARGIN) ")","one to four numbers"},
     {NULL}
   };
   parser->add_group (entries,
                     "view",
                     "View options:",
-                    "Options controlling output rendering",
+                    "Options for output rendering",
                     this);
 }
 
@@ -391,24 +324,31 @@ shape_options_t::add_options (option_parser_t *parser)
                              G_OPTION_ARG_CALLBACK,    (gpointer) &list_shapers,       "List available shapers and quit",      NULL},
     {"shaper",         0, G_OPTION_FLAG_HIDDEN,
                              G_OPTION_ARG_CALLBACK,    (gpointer) &parse_shapers,      "Hidden duplicate of --shapers",        NULL},
-    {"shapers",                0, 0, G_OPTION_ARG_CALLBACK,    (gpointer) &parse_shapers,      "Comma-separated list of shapers to try","list"},
+    {"shapers",                0, 0, G_OPTION_ARG_CALLBACK,    (gpointer) &parse_shapers,      "Set comma-separated list of shapers to try","list"},
     {"direction",      0, 0, G_OPTION_ARG_STRING,      &this->direction,               "Set text direction (default: auto)",   "ltr/rtl/ttb/btt"},
     {"language",       0, 0, G_OPTION_ARG_STRING,      &this->language,                "Set text language (default: $LANG)",   "langstr"},
     {"script",         0, 0, G_OPTION_ARG_STRING,      &this->script,                  "Set text script (default: auto)",      "ISO-15924 tag"},
+    {"bot",            0, 0, G_OPTION_ARG_NONE,        &this->bot,                     "Treat text as beginning-of-paragraph", NULL},
+    {"eot",            0, 0, G_OPTION_ARG_NONE,        &this->eot,                     "Treat text as end-of-paragraph",       NULL},
+    {"preserve-default-ignorables",0, 0, G_OPTION_ARG_NONE,    &this->preserve_default_ignorables,     "Preserve Default-Ignorable characters",        NULL},
     {"utf8-clusters",  0, 0, G_OPTION_ARG_NONE,        &this->utf8_clusters,           "Use UTF8 byte indices, not char indices",      NULL},
+    {"cluster-level",  0, 0, G_OPTION_ARG_INT,         &this->cluster_level,           "Cluster merging level (default: 0)",   "0/1/2"},
     {"normalize-glyphs",0, 0, G_OPTION_ARG_NONE,       &this->normalize_glyphs,        "Rearrange glyph clusters in nominal order",    NULL},
+    {"num-iterations", 0, 0, G_OPTION_ARG_INT,         &this->num_iterations,          "Run shaper N times (default: 1)",      "N"},
     {NULL}
   };
   parser->add_group (entries,
                     "shape",
                     "Shape options:",
-                    "Options controlling the shaping process",
+                    "Options for the shaping process",
                     this);
 
   const gchar *features_help = "Comma-separated list of font features\n"
     "\n"
     "    Features can be enabled or disabled, either globally or limited to\n"
-    "    specific character ranges.\n"
+    "    specific character ranges.  The format for specifying feature settings\n"
+    "    follows.  All valid CSS font-feature-settings values other than 'normal'\n"
+    "    and 'inherited' are also accepted, though, not documented below.\n"
     "\n"
     "    The range indices refer to the positions between Unicode characters,\n"
     "    unless the --utf8-clusters is provided, in which case range indices\n"
@@ -437,7 +377,7 @@ shape_options_t::add_options (option_parser_t *parser)
     "\n"
     "    Mixing it all:\n"
     "\n"
-    "      \"kern[3:5]=0\" 1         3         5         # Turn feature off for range";
+    "      \"aalt[3:5]=2\" 2         3         5         # Turn 2nd alternate on for range";
 
   GOptionEntry entries2[] =
   {
@@ -447,23 +387,97 @@ shape_options_t::add_options (option_parser_t *parser)
   parser->add_group (entries2,
                     "features",
                     "Features options:",
-                    "Options controlling font features used",
+                    "Options for font features used",
                     this);
 }
 
+static gboolean
+parse_font_size (const char *name G_GNUC_UNUSED,
+                const char *arg,
+                gpointer    data,
+                GError    **error G_GNUC_UNUSED)
+{
+  font_options_t *font_opts = (font_options_t *) data;
+  if (0 == strcmp (arg, "upem"))
+  {
+    font_opts->font_size_y = font_opts->font_size_x = FONT_SIZE_UPEM;
+    return true;
+  }
+  switch (sscanf (arg, "%lf%*[ ,]%lf", &font_opts->font_size_x, &font_opts->font_size_y)) {
+    case 1: font_opts->font_size_y = font_opts->font_size_x;
+    case 2: return true;
+    default:
+      g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+                  "%s argument should be one to four space-separated numbers",
+                  name);
+      return false;
+  }
+}
 void
 font_options_t::add_options (option_parser_t *parser)
 {
+  char *text = NULL;
+
+  {
+    ASSERT_STATIC (ARRAY_LENGTH_CONST (supported_font_funcs) > 0);
+    GString *s = g_string_new (NULL);
+    g_string_printf (s, "Set font functions implementation to use (default: %s)\n\n    Supported font function implementations are: %s",
+                    supported_font_funcs[0].name,
+                    supported_font_funcs[0].name);
+    for (unsigned int i = 1; i < ARRAY_LENGTH (supported_font_funcs); i++)
+    {
+      g_string_append_c (s, '/');
+      g_string_append (s, supported_font_funcs[i].name);
+    }
+    text = g_string_free (s, FALSE);
+    parser->free_later (text);
+  }
+
+  char *font_size_text;
+  if (default_font_size == FONT_SIZE_UPEM)
+    font_size_text = (char *) "Font size (default: upem)";
+  else
+  {
+    font_size_text = g_strdup_printf ("Font size (default: %d)", default_font_size);
+    parser->free_later (font_size_text);
+  }
+
   GOptionEntry entries[] =
   {
-    {"font-file",      0, 0, G_OPTION_ARG_STRING,      &this->font_file,               "Font file-name",                                       "filename"},
-    {"face-index",     0, 0, G_OPTION_ARG_INT,         &this->face_index,              "Face index (default: 0)",                              "index"},
+    {"font-file",      0, 0, G_OPTION_ARG_STRING,      &this->font_file,               "Set font file-name",                   "filename"},
+    {"face-index",     0, 0, G_OPTION_ARG_INT,         &this->face_index,              "Set face index (default: 0)",          "index"},
+    {"font-size",      0, default_font_size ? 0 : G_OPTION_FLAG_HIDDEN,
+                             G_OPTION_ARG_CALLBACK,    (gpointer) &parse_font_size,    font_size_text,                         "1/2 numbers or 'upem'"},
+    {"font-funcs",     0, 0, G_OPTION_ARG_STRING,      &this->font_funcs,              text,                                   "impl"},
     {NULL}
   };
   parser->add_group (entries,
                     "font",
                     "Font options:",
-                    "Options controlling the font",
+                    "Options for the font",
+                    this);
+
+  const gchar *variations_help = "Comma-separated list of font variations\n"
+    "\n"
+    "    Variations are set globally. The format for specifying variation settings\n"
+    "    follows.  All valid CSS font-variation-settings values other than 'normal'\n"
+    "    and 'inherited' are also accepted, though, not documented below.\n"
+    "\n"
+    "    The format is a tag, optionally followed by an equals sign, followed by a\n"
+    "    number. For example:\n"
+    "\n"
+    "      \"wght=500\"\n"
+    "      \"slnt=-7.5\"\n";
+
+  GOptionEntry entries2[] =
+  {
+    {"variations",     0, 0, G_OPTION_ARG_CALLBACK,    (gpointer) &parse_variations,   variations_help,        "list"},
+    {NULL}
+  };
+  parser->add_group (entries2,
+                    "variations",
+                    "Varitions options:",
+                    "Options for font variations used",
                     this);
 }
 
@@ -473,29 +487,43 @@ text_options_t::add_options (option_parser_t *parser)
   GOptionEntry entries[] =
   {
     {"text",           0, 0, G_OPTION_ARG_STRING,      &this->text,                    "Set input text",                       "string"},
-    {"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"},
+    {"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"},
+    {"text-before",    0, 0, G_OPTION_ARG_STRING,      &this->text_before,             "Set text context before each line",    "string"},
+    {"text-after",     0, 0, G_OPTION_ARG_STRING,      &this->text_after,              "Set text context after each line",     "string"},
     {NULL}
   };
   parser->add_group (entries,
                     "text",
                     "Text options:",
-                    "Options controlling the input text",
+                    "Options for the input text",
                     this);
 }
 
 void
 output_options_t::add_options (option_parser_t *parser)
 {
+  const char *text;
+
+  if (NULL == supported_formats)
+    text = "Set output serialization format";
+  else
+  {
+    char *items = g_strjoinv ("/", const_cast<char **> (supported_formats));
+    text = g_strdup_printf ("Set output format\n\n    Supported output formats are: %s", items);
+    g_free (items);
+    parser->free_later ((char *) text);
+  }
+
   GOptionEntry entries[] =
   {
     {"output-file",    0, 0, G_OPTION_ARG_STRING,      &this->output_file,             "Set output file-name (default: stdout)","filename"},
-    {"output-format",  0, 0, G_OPTION_ARG_STRING,      &this->output_format,           "Set output format",                    "format"},
+    {"output-format",  0, 0, G_OPTION_ARG_STRING,      &this->output_format,           text,                                   "format"},
     {NULL}
   };
   parser->add_group (entries,
                     "output",
-                    "Output options:",
-                    "Options controlling the output",
+                    "Output destination & format options:",
+                    "Options for the destination & form of the output",
                     this);
 }
 
@@ -525,8 +553,8 @@ font_options_t::get_font (void) const
       /* read it */
       GString *gs = g_string_new (NULL);
       char buf[BUFSIZ];
-#ifdef HAVE__SETMODE
-      _setmode (fileno (stdin), _O_BINARY);
+#if defined(_WIN32) || defined(__CYGWIN__)
+      setmode (fileno (stdin), O_BINARY);
 #endif
       while (!feof (stdin)) {
        size_t ret = fread (buf, 1, sizeof (buf), stdin);
@@ -574,6 +602,9 @@ font_options_t::get_font (void) const
       }
     }
 
+    if (debug)
+      mm = HB_MEMORY_MODE_DUPLICATE;
+
     blob = hb_blob_create (font_data, len, mm, user_data, destroy);
   }
 
@@ -584,13 +615,49 @@ font_options_t::get_font (void) const
 
   font = hb_font_create (face);
 
-  unsigned int upem = hb_face_get_upem (face);
-  hb_font_set_scale (font, upem, upem);
+  if (font_size_x == FONT_SIZE_UPEM)
+    font_size_x = hb_face_get_upem (face);
+  if (font_size_y == FONT_SIZE_UPEM)
+    font_size_y = hb_face_get_upem (face);
+
+  int scale_x = (int) scalbnf (font_size_x, subpixel_bits);
+  int scale_y = (int) scalbnf (font_size_y, subpixel_bits);
+  hb_font_set_scale (font, scale_x, scale_y);
   hb_face_destroy (face);
 
-#ifdef HAVE_FREETYPE
-  hb_ft_font_set_funcs (font);
-#endif
+  hb_font_set_variations (font, variations, num_variations);
+
+  void (*set_font_funcs) (hb_font_t *) = NULL;
+  if (!font_funcs)
+  {
+    set_font_funcs = supported_font_funcs[0].func;
+  }
+  else
+  {
+    for (unsigned int i = 0; i < ARRAY_LENGTH (supported_font_funcs); i++)
+      if (0 == g_ascii_strcasecmp (font_funcs, supported_font_funcs[i].name))
+      {
+       set_font_funcs = supported_font_funcs[i].func;
+       break;
+      }
+    if (!set_font_funcs)
+    {
+      GString *s = g_string_new (NULL);
+      for (unsigned int i = 0; i < ARRAY_LENGTH (supported_font_funcs); i++)
+      {
+        if (i)
+         g_string_append_c (s, '/');
+       g_string_append (s, supported_font_funcs[i].name);
+      }
+      char *p = g_string_free (s, FALSE);
+      fail (false, "Unknown font function implementation `%s'; supported values are: %s; default is %s",
+           font_funcs,
+           p,
+           supported_font_funcs[0].name);
+      //free (p);
+    }
+  }
+  set_font_funcs (font);
 
   return font;
 }
@@ -600,25 +667,26 @@ const char *
 text_options_t::get_line (unsigned int *len)
 {
   if (text) {
-    if (text_len == (unsigned int) -1)
-      text_len = strlen (text);
+    if (!line) line = text;
+    if (line_len == (unsigned int) -1)
+      line_len = strlen (line);
 
-    if (!text_len) {
+    if (!line_len) {
       *len = 0;
       return NULL;
     }
 
-    const char *ret = text;
-    const char *p = (const char *) memchr (text, '\n', text_len);
+    const char *ret = line;
+    const char *p = (const char *) memchr (line, '\n', line_len);
     unsigned int ret_len;
     if (!p) {
-      ret_len = text_len;
-      text += ret_len;
-      text_len = 0;
+      ret_len = line_len;
+      line += ret_len;
+      line_len = 0;
     } else {
       ret_len = p - ret;
-      text += ret_len + 1;
-      text_len -= ret_len + 1;
+      line += ret_len + 1;
+      line_len -= ret_len + 1;
     }
 
     *len = ret_len;
@@ -669,8 +737,8 @@ output_options_t::get_file_handle (void)
   if (output_file)
     fp = fopen (output_file, "wb");
   else {
-#ifdef HAVE__SETMODE
-    _setmode (fileno (stdout), _O_BINARY);
+#if defined(_WIN32) || defined(__CYGWIN__)
+    setmode (fileno (stdout), O_BINARY);
 #endif
     fp = stdout;
   }
@@ -697,19 +765,27 @@ format_options_t::add_options (option_parser_t *parser)
 {
   GOptionEntry entries[] =
   {
-    {"no-glyph-names", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,    &this->show_glyph_names,        "Use glyph indices instead of names",   NULL},
-    {"no-positions",   0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,    &this->show_positions,          "Do not show glyph positions",          NULL},
-    {"no-clusters",    0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,    &this->show_clusters,           "Do not show cluster mapping",          NULL},
-    {"show-text",      0, 0,                     G_OPTION_ARG_NONE,    &this->show_text,               "Show input text",                      NULL},
-    {"show-unicode",   0, 0,                     G_OPTION_ARG_NONE,    &this->show_unicode,            "Show input Unicode codepoints",        NULL},
-    {"show-line-num",  0, 0,                     G_OPTION_ARG_NONE,    &this->show_line_num,           "Show line numbers",                    NULL},
-    {"verbose",                0, G_OPTION_FLAG_NO_ARG,  G_OPTION_ARG_CALLBACK,(gpointer) &parse_verbose,      "Show everything",                      NULL},
+    {"show-text",      0, 0, G_OPTION_ARG_NONE,        &this->show_text,               "Prefix each line of output with its corresponding input text",         NULL},
+    {"show-unicode",   0, 0, G_OPTION_ARG_NONE,        &this->show_unicode,            "Prefix each line of output with its corresponding input codepoint(s)", NULL},
+    {"show-line-num",  0, 0, G_OPTION_ARG_NONE,        &this->show_line_num,           "Prefix each line of output with its corresponding input line number",  NULL},
+    {"verbose",                0, G_OPTION_FLAG_NO_ARG,
+                             G_OPTION_ARG_CALLBACK,    (gpointer) &parse_verbose,      "Prefix each line of output with all of the above",                     NULL},
+    {"no-glyph-names", 0, G_OPTION_FLAG_REVERSE,
+                             G_OPTION_ARG_NONE,        &this->show_glyph_names,        "Output glyph indices instead of names",                                NULL},
+    {"no-positions",   0, G_OPTION_FLAG_REVERSE,
+                             G_OPTION_ARG_NONE,        &this->show_positions,          "Do not output glyph positions",                                        NULL},
+    {"no-clusters",    0, G_OPTION_FLAG_REVERSE,
+                             G_OPTION_ARG_NONE,        &this->show_clusters,           "Do not output cluster indices",                                        NULL},
+    {"show-extents",   0, 0, G_OPTION_ARG_NONE,        &this->show_extents,            "Output glyph extents",                                                 NULL},
     {NULL}
   };
   parser->add_group (entries,
-                    "format",
-                    "Format options:",
-                    "Options controlling the formatting of buffer contents",
+                    "output-syntax",
+                    "Output syntax:\n"
+         "    text: [<glyph name or index>=<glyph cluster index within input>@<horizontal displacement>,<vertical displacement>+<horizontal advance>,<vertical advance>|...]\n"
+         "    json: [{\"g\": <glyph name or index>, \"ax\": <horizontal advance>, \"ay\": <vertical advance>, \"dx\": <horizontal displacement>, \"dy\": <vertical displacement>, \"cl\": <glyph cluster index within input>}, ...]\n"
+         "\nOutput syntax options:",
+                    "Options for the syntax of the output",
                     this);
 }
 
@@ -734,45 +810,23 @@ format_options_t::serialize_unicode (hb_buffer_t *buffer,
 void
 format_options_t::serialize_glyphs (hb_buffer_t *buffer,
                                    hb_font_t   *font,
-                                   hb_bool_t    utf8_clusters,
+                                   hb_buffer_serialize_format_t output_format,
+                                   hb_buffer_serialize_flags_t flags,
                                    GString     *gs)
 {
-  unsigned int num_glyphs = hb_buffer_get_length (buffer);
-  hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, NULL);
-  hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffer, NULL);
-
   g_string_append_c (gs, '[');
-  for (unsigned int i = 0; i < num_glyphs; i++)
-  {
-    if (i)
-      g_string_append_c (gs, '|');
-
-    char glyph_name[128];
-    if (show_glyph_names) {
-      hb_font_glyph_to_string (font, info->codepoint, glyph_name, sizeof (glyph_name));
-      g_string_append_printf (gs, "%s", glyph_name);
-    } else
-      g_string_append_printf (gs, "%u", info->codepoint);
-
-    if (show_clusters) {
-      g_string_append_printf (gs, "=%u", info->cluster);
-      if (utf8_clusters)
-       g_string_append (gs, "u8");
-    }
-
-    if (show_positions && (pos->x_offset || pos->y_offset)) {
-      g_string_append_c (gs, '@');
-      if (pos->x_offset) g_string_append_printf (gs, "%d", pos->x_offset);
-      if (pos->y_offset) g_string_append_printf (gs, ",%d", pos->y_offset);
-    }
-    if (show_positions && (pos->x_advance || pos->y_advance)) {
-      g_string_append_c (gs, '+');
-      if (pos->x_advance) g_string_append_printf (gs, "%d", pos->x_advance);
-      if (pos->y_advance) g_string_append_printf (gs, ",%d", pos->y_advance);
-    }
-
-    info++;
-    pos++;
+  unsigned int num_glyphs = hb_buffer_get_length (buffer);
+  unsigned int start = 0;
+
+  while (start < num_glyphs) {
+    char buf[1024];
+    unsigned int consumed;
+    start += hb_buffer_serialize_glyphs (buffer, start, num_glyphs,
+                                        buf, sizeof (buf), &consumed,
+                                        font, output_format, flags);
+    if (!consumed)
+      break;
+    g_string_append (gs, buf);
   }
   g_string_append_c (gs, ']');
 }
@@ -789,7 +843,6 @@ format_options_t::serialize_buffer_of_text (hb_buffer_t  *buffer,
                                            const char   *text,
                                            unsigned int  text_len,
                                            hb_font_t    *font,
-                                           hb_bool_t     utf8_clusters,
                                            GString      *gs)
 {
   if (show_text) {
@@ -821,10 +874,11 @@ format_options_t::serialize_buffer_of_glyphs (hb_buffer_t  *buffer,
                                              const char   *text,
                                              unsigned int  text_len,
                                              hb_font_t    *font,
-                                             hb_bool_t     utf8_clusters,
+                                             hb_buffer_serialize_format_t output_format,
+                                             hb_buffer_serialize_flags_t format_flags,
                                              GString      *gs)
 {
   serialize_line_no (line_no, gs);
-  serialize_glyphs (buffer, font, utf8_clusters, gs);
+  serialize_glyphs (buffer, font, output_format, format_flags, gs);
   g_string_append_c (gs, '\n');
 }