[HarfBuzz] harfbuzz-ng: Branch 'master' - 19 commits

Behdad Esfahbod behdad at kemper.freedesktop.org
Wed Sep 14 22:06:51 PDT 2011


 configure.ac                       |    2 
 src/hb-buffer-private.hh           |    3 
 src/hb-buffer.cc                   |   26 ++
 src/hb-common.cc                   |   29 +-
 src/hb-common.h                    |   12 -
 src/hb-graphite2.cc                |   56 ++--
 src/hb-graphite2.h                 |   13 -
 src/hb-icu.cc                      |    2 
 src/hb-ot-shape-complex-private.hh |   56 ++--
 src/hb-ot-tag.cc                   |   16 +
 test/test-buffer.c                 |    4 
 test/test-common.c                 |   79 +++---
 test/test-ot-tag.c                 |   27 +-
 util/Makefile.am                   |    2 
 util/common.cc                     |    7 
 util/common.hh                     |    2 
 util/hb-view.cc                    |  255 ++-------------------
 util/options.cc                    |  332 +++++++++++++++++++++------
 util/options.hh                    |  205 +++++++++++++++--
 util/view-cairo.cc                 |  440 +++++++++++++++++++++++++++++++++++++
 util/view-cairo.hh                 |   63 +++++
 21 files changed, 1186 insertions(+), 445 deletions(-)

New commits:
commit c4611cb66f8e3a133ec00e3ace62ef19d9b95b28
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Tue Sep 13 13:33:11 2011 -0400

    Fix test

diff --git a/src/hb-graphite2.h b/src/hb-graphite2.h
index 3037868..68bd019 100644
--- a/src/hb-graphite2.h
+++ b/src/hb-graphite2.h
@@ -26,6 +26,7 @@
 #ifndef HB_GRAPHITE2_H
 #define HB_GRAPHITE2_H
 
+#include "hb-common.h"
 #include "hb-shape.h"
 
 HB_BEGIN_DECLS
diff --git a/src/hb-ot-shape.h b/src/hb-ot-shape.h
index 644b11e..1897e84 100644
--- a/src/hb-ot-shape.h
+++ b/src/hb-ot-shape.h
@@ -27,6 +27,7 @@
 #ifndef HB_OT_SHAPE_H
 #define HB_OT_SHAPE_H
 
+#include "hb-common.h"
 #include "hb-shape.h"
 
 
diff --git a/src/hb-uniscribe.h b/src/hb-uniscribe.h
index 7dc830e..dbcacd7 100644
--- a/src/hb-uniscribe.h
+++ b/src/hb-uniscribe.h
@@ -27,6 +27,7 @@
 #ifndef HB_UNISCRIBE_H
 #define HB_UNISCRIBE_H
 
+#include "hb-common.h"
 #include "hb-shape.h"
 
 #define _WIN32_WINNT 0x0500
commit b9b10ad78b1f977494a3a42b58f8040fe16505a3
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Tue Sep 13 13:30:39 2011 -0400

    [util] Refactor hb-view completely
    
    Now we can use the same code to do other utils...

diff --git a/configure.ac b/configure.ac
index 2dc9d7d..8245181 100644
--- a/configure.ac
+++ b/configure.ac
@@ -49,6 +49,8 @@ m4_define([hb_libtool_current],
 HB_LIBTOOL_VERSION_INFO=hb_libtool_current:hb_libtool_revision:hb_libtool_age
 AC_SUBST(HB_LIBTOOL_VERSION_INFO)
 
+GTK_DOC_CHECK([1.15],[--flavour no-tmpl])
+
 # Functions and headers
 AC_CHECK_FUNCS(mprotect sysconf getpagesize mmap)
 AC_CHECK_HEADERS(unistd.h sys/mman.h)
diff --git a/util/Makefile.am b/util/Makefile.am
index 7939975..a899c26 100644
--- a/util/Makefile.am
+++ b/util/Makefile.am
@@ -18,6 +18,8 @@ hb_view_SOURCES = \
 	common.hh \
 	options.cc \
 	options.hh \
+	view-cairo.cc \
+	view-cairo.hh \
 	$(NULL)
 hb_view_CPPFLAGS = \
 	-I$(top_srcdir)/src/ \
diff --git a/util/common.cc b/util/common.cc
index 7326b8c..b319914 100644
--- a/util/common.cc
+++ b/util/common.cc
@@ -27,14 +27,17 @@
 #include "common.hh"
 
 void
-fail (const char *format, ...)
+fail (hb_bool_t suggest_help, const char *format, ...)
 {
   const char *msg;
 
   va_list vap;
   va_start (vap, format);
   msg = g_strdup_vprintf (format, vap);
-  g_printerr ("%s: %s\n", g_get_prgname (), msg);
+  const char *prgname = g_get_prgname ();
+  g_printerr ("%s: %s\n", prgname, msg);
+  if (suggest_help)
+    g_printerr ("Try `%s --help' for more information.\n", prgname);
 
   exit (1);
 }
diff --git a/util/common.hh b/util/common.hh
index 58dec6f..45ac6ef 100644
--- a/util/common.hh
+++ b/util/common.hh
@@ -45,7 +45,7 @@
 #include <glib/gprintf.h>
 
 
-void fail (const char *format, ...) G_GNUC_NORETURN;
+void fail (hb_bool_t suggest_help, const char *format, ...) G_GNUC_NORETURN;
 
 
 #endif
diff --git a/util/hb-view.cc b/util/hb-view.cc
index 419b7d5..d745c21 100644
--- a/util/hb-view.cc
+++ b/util/hb-view.cc
@@ -26,234 +26,50 @@
  */
 
 #include "common.hh"
-
-
-#include <cairo-ft.h>
-#include <hb-ft.h>
-
 #include "options.hh"
 
-
-/* Ugh, global vars.  Ugly, but does the job */
-static int width = 0;
-static int height = 0;
-static cairo_surface_t *surface = NULL;
-static cairo_pattern_t *fore_pattern = NULL;
-static cairo_pattern_t *back_pattern = NULL;
-static cairo_font_face_t *cairo_face;
-
-
-
-static cairo_glyph_t *
-_hb_cr_text_glyphs (cairo_t *cr,
-		    const char *utf8, int len,
-		    unsigned int *pnum_glyphs)
-{
-  cairo_scaled_font_t *scaled_font = cairo_get_scaled_font (cr);
-  FT_Face ft_face = cairo_ft_scaled_font_lock_face (scaled_font);
-  hb_font_t *hb_font = hb_ft_font_create (ft_face, NULL);
-  hb_buffer_t *hb_buffer;
-  cairo_glyph_t *cairo_glyphs;
-  hb_glyph_info_t *hb_glyph;
-  hb_glyph_position_t *hb_position;
-  unsigned int num_glyphs, i;
-  hb_position_t x, y;
-
-  hb_buffer = hb_buffer_create ();
-
-
-  hb_buffer_add_utf8 (hb_buffer, utf8, len, 0, len);
-
-  if (!shape_opts->shape (hb_font, hb_buffer))
-    fail ("All shapers failed");
-
-  num_glyphs = hb_buffer_get_length (hb_buffer);
-  hb_glyph = hb_buffer_get_glyph_infos (hb_buffer, NULL);
-  hb_position = hb_buffer_get_glyph_positions (hb_buffer, NULL);
-  cairo_glyphs = cairo_glyph_allocate (num_glyphs);
-  x = 0;
-  y = 0;
-  for (i = 0; i < num_glyphs; i++)
-    {
-      cairo_glyphs[i].index = hb_glyph->codepoint;
-      cairo_glyphs[i].x = ( hb_position->x_offset + x) * (1./64);
-      cairo_glyphs[i].y = (-hb_position->y_offset + y) * (1./64);
-      x +=  hb_position->x_advance;
-      y += -hb_position->y_advance;
-
-      hb_glyph++;
-      hb_position++;
-    }
-  hb_buffer_destroy (hb_buffer);
-  hb_font_destroy (hb_font);
-  cairo_ft_scaled_font_unlock_face (scaled_font);
-
-  if (pnum_glyphs)
-    *pnum_glyphs = num_glyphs;
-  return cairo_glyphs;
-}
-
-static cairo_t *
-create_context (void)
-{
-  cairo_t *cr;
-  unsigned int fr, fg, fb, fa, br, bg, bb, ba;
-
-  if (surface)
-    cairo_surface_destroy (surface);
-  if (back_pattern)
-    cairo_pattern_destroy (back_pattern);
-  if (fore_pattern)
-    cairo_pattern_destroy (fore_pattern);
-
-  br = bg = bb = ba = 255;
-  sscanf (view_opts->back + (*view_opts->back=='#'), "%2x%2x%2x%2x", &br, &bg, &bb, &ba);
-  fr = fg = fb = 0; fa = 255;
-  sscanf (view_opts->fore + (*view_opts->fore=='#'), "%2x%2x%2x%2x", &fr, &fg, &fb, &fa);
-
-  if (!view_opts->annotate && ba == 255 && fa == 255 && br == bg && bg == bb && fr == fg && fg == fb) {
-    /* grayscale.  use A8 surface */
-    surface = cairo_image_surface_create (CAIRO_FORMAT_A8, width, height);
-    cr = cairo_create (surface);
-    cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
-    cairo_set_source_rgba (cr, 1., 1., 1., br / 255.);
-    cairo_paint (cr);
-    back_pattern = cairo_pattern_reference (cairo_get_source (cr));
-    cairo_set_source_rgba (cr, 1., 1., 1., fr / 255.);
-    fore_pattern = cairo_pattern_reference (cairo_get_source (cr));
-  } else {
-    /* color.  use (A)RGB surface */
-    if (ba != 255)
-      surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
-    else
-      surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
-    cr = cairo_create (surface);
-    cairo_set_source_rgba (cr, br / 255., bg / 255., bb / 255., ba / 255.);
-    cairo_paint (cr);
-    back_pattern = cairo_pattern_reference (cairo_get_source (cr));
-    cairo_set_source_rgba (cr, fr / 255., fg / 255., fb / 255., fa / 255.);
-    fore_pattern = cairo_pattern_reference (cairo_get_source (cr));
-  }
-
-  cairo_set_font_face (cr, cairo_face);
-
-  return cr;
-}
-
-static void
-draw (void)
-{
-  cairo_t *cr;
-  cairo_font_extents_t font_extents;
-
-  cairo_glyph_t *glyphs = NULL;
-  unsigned int num_glyphs = 0;
-
-  const char *end, *p = text;
-  double x, y;
-
-  cr= create_context ();
-
-  cairo_set_font_size (cr, font_opts->font_size);
-  cairo_font_extents (cr, &font_extents);
-
-  height = 0;
-  width = 0;
-
-  x = view_opts->margin.l;
-  y = view_opts->margin.t;
-
-  do {
-    cairo_text_extents_t extents;
-
-    end = strchr (p, '\n');
-    if (!end)
-      end = p + strlen (p);
-
-    if (p != text)
-	y += view_opts->line_space;
-
-    if (p != end) {
-      glyphs = _hb_cr_text_glyphs (cr, p, end - p, &num_glyphs);
-
-      cairo_glyph_extents (cr, glyphs, num_glyphs, &extents);
-
-      y += ceil (font_extents.ascent);
-      width = MAX (width, extents.x_advance);
-      cairo_save (cr);
-      cairo_translate (cr, x, y);
-      if (view_opts->annotate) {
-        unsigned int i;
-        cairo_save (cr);
-
-	/* Draw actual glyph origins */
-	cairo_set_source_rgba (cr, 1., 0., 0., .5);
-	cairo_set_line_width (cr, 5);
-	cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
-	for (i = 0; i < num_glyphs; i++) {
-	  cairo_move_to (cr, glyphs[i].x, glyphs[i].y);
-	  cairo_rel_line_to (cr, 0, 0);
-	}
-	cairo_stroke (cr);
-
-        cairo_restore (cr);
-      }
-      cairo_show_glyphs (cr, glyphs, num_glyphs);
-      cairo_restore (cr);
-      y += ceil (font_extents.height - ceil (font_extents.ascent));
-
-      cairo_glyph_free (glyphs);
-    }
-
-    p = end + 1;
-  } while (*end);
-
-  height = y + view_opts->margin.b;
-  width += view_opts->margin.l + view_opts->margin.r;
-
-  cairo_destroy (cr);
-}
-
-
+#include "view-cairo.hh"
 
 int
 main (int argc, char **argv)
 {
-  static FT_Library ft_library;
-  static FT_Face ft_face;
-  cairo_status_t status;
-
   setlocale (LC_ALL, "");
 
-  parse_options (argc, argv);
+  option_parser_t options ("[FONT-FILE] [TEXT]");
 
-  FT_Init_FreeType (&ft_library);
-  if (FT_New_Face (ft_library, font_opts->font_file, font_opts->face_index, &ft_face)) {
-    fprintf (stderr, "Failed to open font file `%s'\n", font_opts->font_file);
-    exit (1);
-  }
-  cairo_face = cairo_ft_font_face_create_for_ft_face (ft_face, 0);
+  shape_options_t shaper (&options);
+  font_options_t font_opts (&options);
+  text_options_t input (&options);
 
-  draw ();
-  draw ();
+  view_cairo_t output (&options);
 
-  status = cairo_surface_write_to_png (surface, out_file);
-  if (status != CAIRO_STATUS_SUCCESS) {
-    fprintf (stderr, "Failed to write output file `%s': %s\n",
-	     out_file, cairo_status_to_string (status));
-    exit (1);
-  }
+  options.parse (&argc, &argv);
 
-  if (debug) {
-    cairo_pattern_destroy (fore_pattern);
-    cairo_pattern_destroy (back_pattern);
-    cairo_surface_destroy (surface);
-    cairo_font_face_destroy (cairo_face);
-    cairo_debug_reset_static_data ();
+  argc--, argv++;
+  if (argc && !font_opts.font_file) font_opts.font_file = argv[0], argc--, argv++;
+  if (argc && !input.text && !input.text_file) input.text = argv[0], argc--, argv++;
+  if (argc)
+    fail (TRUE, "Too many arguments on the command line");
+  if (!font_opts.font_file || (!input.text && !input.text_file))
+    options.usage ();
 
-    FT_Done_Face (ft_face);
-    FT_Done_FreeType (ft_library);
+  output.init (&font_opts);
+
+  hb_buffer_t *buffer = hb_buffer_create ();
+  unsigned int text_len;
+  const char *text;
+  while ((text = input.get_line (&text_len)))
+  {
+    if (!shaper.shape (text, text_len,
+			   font_opts.get_font (),
+			   buffer))
+      fail (FALSE, "All shapers failed");
+
+    output.consume_line (buffer, text, text_len);
   }
+  hb_buffer_destroy (buffer);
+
+  output.finish (&font_opts);
 
   return 0;
 }
diff --git a/util/options.cc b/util/options.cc
index 6fc4029..eb3ce69 100644
--- a/util/options.cc
+++ b/util/options.cc
@@ -26,14 +26,107 @@
 
 #include "options.hh"
 
+#if HAVE_FREETYPE
+#include <hb-ft.h>
+#endif
 
-view_options_t view_opts[1];
-shape_options_t shape_opts[1];
-font_options_t font_opts[1];
 
-const char *text;
-const char *out_file = "/dev/stdout";
-hb_bool_t debug = FALSE;
+bool debug = FALSE;
+
+static gchar *
+shapers_to_string (void)
+{
+  GString *shapers = g_string_new (NULL);
+  const char **shaper_list = hb_shape_list_shapers ();
+
+  for (; *shaper_list; shaper_list++) {
+    g_string_append (shapers, *shaper_list);
+    g_string_append_c (shapers, ',');
+  }
+  g_string_truncate (shapers, MAX (0, (gint)shapers->len - 1));
+
+  return g_string_free (shapers, FALSE);
+}
+
+static G_GNUC_NORETURN gboolean
+show_version (const char *name G_GNUC_UNUSED,
+	      const char *arg G_GNUC_UNUSED,
+	      gpointer    data G_GNUC_UNUSED,
+	      GError    **error G_GNUC_UNUSED)
+{
+  g_printf ("%s (%s) %s\n", g_get_prgname (), PACKAGE_NAME, PACKAGE_VERSION);
+
+  char *shapers = shapers_to_string ();
+  g_printf ("Available shapers: %s\n", shapers);
+  g_free (shapers);
+  if (strcmp (HB_VERSION_STRING, hb_version_string ()))
+    g_printf ("Linked HarfBuzz library has a different version: %s\n", hb_version_string ());
+
+  exit(0);
+}
+
+
+void
+option_parser_t::add_main_options (void)
+{
+  GOptionEntry entries[] =
+  {
+    {"version",		0, G_OPTION_FLAG_NO_ARG,
+			      G_OPTION_ARG_CALLBACK,	(gpointer) &show_version,	"Show version numbers",			NULL},
+    {"debug",		0, 0, G_OPTION_ARG_NONE,	&debug,				"Free all resources before exit",	NULL},
+    {NULL}
+  };
+  g_option_context_add_main_entries (context, entries, NULL);
+}
+
+static gboolean
+pre_parse (GOptionContext *context G_GNUC_UNUSED,
+	   GOptionGroup *group G_GNUC_UNUSED,
+	   gpointer data,
+	   GError **error)
+{
+  option_group_t *option_group = (option_group_t *) data;
+  option_group->pre_parse (error);
+  return *error == NULL;
+}
+
+static gboolean
+post_parse (GOptionContext *context G_GNUC_UNUSED,
+	    GOptionGroup *group G_GNUC_UNUSED,
+	    gpointer data,
+	    GError **error)
+{
+  option_group_t *option_group = static_cast<option_group_t *>(data);
+  option_group->post_parse (error);
+  return *error == NULL;
+}
+
+void
+option_parser_t::add_group (GOptionEntry   *entries,
+			    const gchar    *name,
+			    const gchar    *description,
+			    const gchar    *help_description,
+			    option_group_t *option_group)
+{
+  GOptionGroup *group = g_option_group_new (name, description, help_description,
+					    static_cast<gpointer>(option_group), NULL);
+  g_option_group_add_entries (group, entries);
+  g_option_group_set_parse_hooks (group, pre_parse, post_parse);
+  g_option_context_add_group (context, group);
+}
+
+void
+option_parser_t::parse (int *argc, char ***argv)
+{
+  GError *parse_error = NULL;
+  if (!g_option_context_parse (context, argc, argv, &parse_error))
+  {
+    if (parse_error != NULL)
+      fail (TRUE, "%s", parse_error->message);
+    else
+      fail (TRUE, "Option parse error");
+  }
+}
 
 
 static gboolean
@@ -232,54 +325,8 @@ parse_features (const char *name G_GNUC_UNUSED,
 }
 
 
-static gchar *
-shapers_to_string (void)
-{
-  GString *shapers = g_string_new (NULL);
-  const char **shaper_list = hb_shape_list_shapers ();
-
-  for (; *shaper_list; shaper_list++) {
-    g_string_append (shapers, *shaper_list);
-    g_string_append_c (shapers, ',');
-  }
-  g_string_truncate (shapers, MAX (0, (gint)shapers->len - 1));
-
-  return g_string_free (shapers, FALSE);
-}
-
-static G_GNUC_NORETURN gboolean
-show_version (const char *name G_GNUC_UNUSED,
-	      const char *arg G_GNUC_UNUSED,
-	      gpointer    data G_GNUC_UNUSED,
-	      GError    **error G_GNUC_UNUSED)
-{
-  g_printf ("%s (%s) %s\n", g_get_prgname (), PACKAGE_NAME, PACKAGE_VERSION);
-
-  char *shapers = shapers_to_string ();
-  g_printf ("Available shapers: %s\n", shapers);
-  g_free (shapers);
-  if (strcmp (HB_VERSION_STRING, hb_version_string ()))
-    g_printf ("Linked HarfBuzz library has a different version: %s\n", hb_version_string ());
-
-  exit(0);
-}
-
-
-static void
-option_context_add_entries (GOptionContext *context,
-			    GOptionEntry   *entries,
-			    const gchar    *name,
-			    const gchar    *description,
-			    const gchar    *help_description,
-			    gpointer        user_data)
-{
-  GOptionGroup *group = g_option_group_new (name, description, help_description, user_data, NULL);
-  g_option_group_add_entries (group, entries);
-  g_option_context_add_group (context, group);
-}
-
 void
-view_options_t::add_options (GOptionContext *context)
+view_options_t::add_options (option_parser_t *parser)
 {
   GOptionEntry entries[] =
   {
@@ -290,15 +337,15 @@ view_options_t::add_options (GOptionContext *context)
     {"margin",		0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_margin,	"Margin around output (default: "G_STRINGIFY(DEFAULT_MARGIN)")","one to four numbers"},
     {NULL}
   };
-  option_context_add_entries (context, entries,
-			      "view",
-			      "View options:",
-			      "Options controlling the output rendering",
-			      this);
+  parser->add_group (entries,
+		     "view",
+		     "View options:",
+		     "Options controlling the output rendering",
+		     this);
 }
 
 void
-shape_options_t::add_options (GOptionContext *context)
+shape_options_t::add_options (option_parser_t *parser)
 {
   GOptionEntry entries[] =
   {
@@ -309,65 +356,149 @@ shape_options_t::add_options (GOptionContext *context)
     {"features",	0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_features,	"Font features to apply to text",	"TODO"},
     {NULL}
   };
-  option_context_add_entries (context, entries,
-			      "shape",
-			      "Shape options:",
-			      "Options controlling the shaping process",
-			      this);
+  parser->add_group (entries,
+		     "shape",
+		     "Shape options:",
+		     "Options controlling the shaping process",
+		     this);
 }
 
 void
-font_options_t::add_options (GOptionContext *context)
+font_options_t::add_options (option_parser_t *parser)
 {
   GOptionEntry entries[] =
   {
-    {"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,		"Font file-name",					"filename"},
+    {"face-index",	0, 0, G_OPTION_ARG_INT,		&this->face_index,		"Face index (default: 0)",                              "index"},
     {"font-size",	0, 0, G_OPTION_ARG_DOUBLE,	&this->font_size,		"Font size (default: "G_STRINGIFY(DEFAULT_FONT_SIZE)")","size"},
     {NULL}
   };
-  option_context_add_entries (context, entries,
-			      "font",
-			      "Font options:",
-			      "Options controlling the font",
-			      NULL);
+  parser->add_group (entries,
+		     "font",
+		     "Font options:",
+		     "Options controlling the font",
+		     this);
 }
 
 void
-parse_options (int argc, char *argv[])
+text_options_t::add_options (option_parser_t *parser)
 {
   GOptionEntry entries[] =
   {
-    {"version",		0, G_OPTION_FLAG_NO_ARG,
-			      G_OPTION_ARG_CALLBACK,	(gpointer) &show_version,	"Show version numbers",			NULL},
-    {"debug",		0, 0, G_OPTION_ARG_NONE,	&debug,				"Free all resources before exit",	NULL},
-    {"output",		0, 0, G_OPTION_ARG_STRING,	&out_file,			"Set output file name",			"filename"},
+    {"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",		"filename"},
     {NULL}
   };
-  GError *parse_error = NULL;
-  GOptionContext *context;
+  parser->add_group (entries,
+		     "text",
+		     "Text options:",
+		     "Options controlling the input text",
+		     this);
+}
+
+void
+output_options_t::add_options (option_parser_t *parser)
+{
+  GOptionEntry entries[] =
+  {
+    {"output",		0, 0, G_OPTION_ARG_STRING,	&this->output_file,		"Set output file-name (default: stdout)","filename"},
+    {"format",		0, 0, G_OPTION_ARG_STRING,	&this->output_format,		"Set output format",			"format"},
+    {NULL}
+  };
+  parser->add_group (entries,
+		     "output",
+		     "Output options:",
+		     "Options controlling the output",
+		     this);
+}
 
-  context = g_option_context_new ("- FONT-FILE TEXT");
 
-  g_option_context_add_main_entries (context, entries, NULL);
-  view_opts->add_options (context);
-  shape_opts->add_options (context);
-  font_opts->add_options (context);
 
-  if (!g_option_context_parse (context, &argc, &argv, &parse_error))
+hb_font_t *
+font_options_t::get_font (void) const
+{
+  if (font)
+    return font;
+
+  hb_blob_t *blob = NULL;
+
+  /* Create the blob */
   {
-    if (parse_error != NULL)
-      fail ("%s", parse_error->message);
-    else
-      fail ("Option parse error");
-    exit(1);
+    const char *font_data;
+    unsigned int len;
+    hb_destroy_func_t destroy;
+    void *user_data;
+    hb_memory_mode_t mm;
+
+    if (!font_file)
+      fail (TRUE, "No font file set");
+
+    GMappedFile *mf = g_mapped_file_new (font_file, FALSE, NULL);
+    if (!mf)
+      fail (FALSE, "Failed opening font file `%s'", font_file);
+    font_data = g_mapped_file_get_contents (mf);
+    len = g_mapped_file_get_length (mf);
+    destroy = (hb_destroy_func_t) g_mapped_file_unref;
+    user_data = (void *) mf;
+    mm = HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE;
+
+    blob = hb_blob_create (font_data, len, mm, user_data, destroy);
+  }
+
+  /* Create the face */
+  hb_face_t *face = hb_face_create (blob, face_index);
+  hb_blob_destroy (blob);
+
+
+  font = hb_font_create (face);
+
+  unsigned int upem = hb_face_get_upem (face);
+  hb_font_set_scale (font, font_size * upem, font_size * upem);
+  hb_face_destroy (face);
+
+#if HAVE_FREETYPE
+  hb_ft_font_set_funcs (font);
+#endif
+
+  return font;
+}
+
+
+const char *
+text_options_t::get_line (unsigned int *len)
+{
+  if (!text) {
+    if (!text_file)
+      fail (TRUE, "At least one of text or text-file must be set");
+
+    GMappedFile *mf = g_mapped_file_new (text_file, FALSE, NULL);
+    if (!mf)
+      fail (FALSE, "Failed opening text file `%s'", text_file);
+    text = g_mapped_file_get_contents (mf);
+    text_len = g_mapped_file_get_length (mf);
   }
-  g_option_context_free(context);
 
-  if (argc != 3) {
-    g_printerr ("Usage: %s [OPTION...] FONT-FILE TEXT\n", g_get_prgname ());
-    exit (1);
+  if (text_len == (unsigned int) -1)
+    text_len = strlen (text);
+
+  if (!text_len) {
+    *len = 0;
+    return NULL;
+  }
+
+  const char *ret = text;
+  const char *p = (const char *) memchr (text, '\n', text_len);
+  unsigned int ret_len;
+  if (!p) {
+    ret_len = text_len;
+    text += ret_len;
+    text_len = 0;
+  } else {
+    ret_len = p - ret;
+    text += ret_len + 1;
+    text_len -= ret_len + 1;
   }
 
-  font_opts->font_file = argv[1];
-  text = argv[2];
+  *len = ret_len;
+  return ret;
 }
diff --git a/util/options.hh b/util/options.hh
index 422dffc..404fa73 100644
--- a/util/options.hh
+++ b/util/options.hh
@@ -30,42 +30,94 @@
 #define OPTIONS_HH
 
 
+extern bool debug;
+
+struct option_group_t
+{
+  virtual void add_options (struct option_parser_t *parser) = 0;
+
+  virtual void pre_parse (GError **error G_GNUC_UNUSED) {};
+  virtual void post_parse (GError **error G_GNUC_UNUSED) {};
+};
+
+
+struct option_parser_t
+{
+  option_parser_t (const char *usage) {
+    memset (this, 0, sizeof (*this));
+    usage_str = usage;
+    context = g_option_context_new (usage);
+
+    add_main_options ();
+  }
+  ~option_parser_t (void) {
+    g_option_context_free (context);
+  }
+
+  void add_main_options (void);
+
+  void add_group (GOptionEntry   *entries,
+		  const gchar    *name,
+		  const gchar    *description,
+		  const gchar    *help_description,
+		  option_group_t *option_group);
+
+  void parse (int *argc, char ***argv);
+
+  G_GNUC_NORETURN void usage (void) {
+    g_printerr ("Usage: %s [OPTION...] %s\n", g_get_prgname (), usage_str);
+    exit (1);
+  }
+
+  const char *usage_str;
+  GOptionContext *context;
+};
+
+
 #define DEFAULT_MARGIN 18
 #define DEFAULT_FORE "#000000"
 #define DEFAULT_BACK "#FFFFFF"
 
-extern struct view_options_t
+struct view_options_t : option_group_t
 {
-  view_options_t (void) {
-    memset (this, 0, sizeof (*this));
+  view_options_t (option_parser_t *parser) {
+    annotate = false;
     fore = DEFAULT_FORE;
     back = DEFAULT_BACK;
+    line_space = 0;
     margin.t = margin.r = margin.b = margin.l = DEFAULT_MARGIN;
+
+    add_options (parser);
   }
 
-  void add_options (GOptionContext *context);
+  void add_options (option_parser_t *parser);
 
-  hb_bool_t annotate;
+  bool annotate;
   const char *fore;
   const char *back;
   double line_space;
   struct margin_t {
     double t, r, b, l;
   } margin;
-} view_opts[1];
+};
 
 
-extern struct shape_options_t
+struct shape_options_t : option_group_t
 {
-  shape_options_t (void) {
-    memset (this, 0, sizeof (*this));
+  shape_options_t (option_parser_t *parser) {
+    direction = language = script = NULL;
+    features = NULL;
+    num_features = 0;
+    shapers = NULL;
+
+    add_options (parser);
   }
   ~shape_options_t (void) {
     free (features);
     g_free (shapers);
   }
 
-  void add_options (GOptionContext *context);
+  void add_options (option_parser_t *parser);
 
   void setup_buffer (hb_buffer_t *buffer) {
     hb_buffer_set_direction (buffer, hb_direction_from_string (direction, -1));
@@ -73,7 +125,10 @@ extern struct shape_options_t
     hb_buffer_set_language (buffer, hb_language_from_string (language, -1));
   }
 
-  bool shape (hb_font_t *font, hb_buffer_t *buffer) {
+  bool shape (const char *text, int text_len,
+	      hb_font_t *font, hb_buffer_t *buffer) {
+    hb_buffer_reset (buffer);
+    hb_buffer_add_utf8 (buffer, text, text_len, 0, text_len);
     setup_buffer (buffer);
     return hb_shape_full (font, buffer, features, num_features, NULL, shapers);
   }
@@ -84,31 +139,113 @@ extern struct shape_options_t
   hb_feature_t *features;
   unsigned int num_features;
   char **shapers;
-} shape_opts[1];
+};
 
 
 #define DEFAULT_FONT_SIZE 36
 
-extern struct font_options_t
+struct font_options_t : option_group_t
 {
-  font_options_t (void) {
-    memset (this, 0, sizeof (*this));
+  font_options_t (option_parser_t *parser) {
+    font_file = NULL;
+    face_index = 0;
     font_size = DEFAULT_FONT_SIZE;
+
+    font = NULL;
+
+    add_options (parser);
+  }
+  ~font_options_t (void) {
+    hb_font_destroy (font);
   }
 
-  void add_options (GOptionContext *context);
+  void add_options (option_parser_t *parser);
+
+  hb_font_t *get_font (void) const;
 
   const char *font_file;
   int face_index;
   double font_size;
-} font_opts[1];
 
+  private:
+  mutable hb_font_t *font;
+};
+
+
+struct text_options_t : option_group_t
+{
+  text_options_t (option_parser_t *parser) {
+    text = NULL;
+    text_file = NULL;
+
+    file = NULL;
+    text_len = (unsigned int) -1;
+
+    add_options (parser);
+  }
+  ~text_options_t (void) {
+    if (file)
+      g_mapped_file_unref (file);
+  }
+
+  void add_options (option_parser_t *parser);
+
+  void post_parse (GError **error G_GNUC_UNUSED) {
+    if (text && text_file)
+      g_set_error (error,
+		   G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+		   "Only one of text and text-file must be set");
+
+  };
+
+  const char *get_line (unsigned int *len);
+
+  const char *text;
+  const char *text_file;
+
+  private:
+  mutable GMappedFile *file;
+  mutable unsigned int text_len;
+};
+
+
+struct output_options_t : option_group_t
+{
+  output_options_t (option_parser_t *parser) {
+    output_file = NULL;
+    output_format = NULL;
+
+    add_options (parser);
+  }
+
+  void add_options (option_parser_t *parser);
+
+  void post_parse (GError **error G_GNUC_UNUSED)
+  {
+    if (output_file && !output_format) {
+      output_format = strrchr (output_file, '.');
+      if (output_format)
+	  output_format++; /* skip the dot */
+    }
+
+      if (!output_file) {
+#if defined(_MSC_VER) || defined(__MINGW32__)
+        output_file = "CON"; /* XXX right? */
+#else
+        output_file = "/dev/stdout";
+#endif
+      }
+  }
 
-extern const char *text;
-extern const char *out_file;
-extern hb_bool_t debug;
+  virtual void init (const font_options_t *font_opts) = 0;
+  virtual void consume_line (hb_buffer_t  *buffer,
+			     const char   *text,
+			     unsigned int  text_len) = 0;
+  virtual void finish (const font_options_t *font_opts) = 0;
 
-void parse_options (int argc, char *argv[]);
+  const char *output_file;
+  const char *output_format;
+};
 
 
 #endif
diff --git a/util/view-cairo.cc b/util/view-cairo.cc
new file mode 100644
index 0000000..b09a1fe
--- /dev/null
+++ b/util/view-cairo.cc
@@ -0,0 +1,440 @@
+/*
+ * Copyright © 2011  Google, Inc.
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Google Author(s): Behdad Esfahbod
+ */
+
+#include "view-cairo.hh"
+
+
+#ifdef CAIRO_HAS_SVG_SURFACE
+#  include <cairo-svg.h>
+#endif
+#ifdef CAIRO_HAS_PDF_SURFACE
+#  include <cairo-pdf.h>
+#endif
+#ifdef CAIRO_HAS_PS_SURFACE
+#  include <cairo-ps.h>
+#  if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
+#    define HAS_EPS 1
+
+static cairo_surface_t *
+_cairo_eps_surface_create (const char *filename,
+			   double      width,
+			   double      height)
+{
+  cairo_surface_t *surface;
+
+  surface = cairo_ps_surface_create (filename, width, height);
+  cairo_ps_surface_set_eps (surface, TRUE);
+
+  return surface;
+}
+
+#  else
+#    undef HAS_EPS
+#  endif
+#endif
+
+struct line_t {
+  cairo_glyph_t *glyphs;
+  unsigned int num_glyphs;
+  char *utf8;
+  unsigned int utf8_len;
+  cairo_text_cluster_t *clusters;
+  unsigned int num_clusters;
+  cairo_text_cluster_flags_t cluster_flags;
+
+  void finish (void) {
+    if (glyphs)
+      cairo_glyph_free (glyphs);
+    if (clusters)
+      cairo_text_cluster_free (clusters);
+    if (utf8)
+      g_free (utf8);
+  }
+};
+
+void
+view_cairo_t::init (const font_options_t *font_opts)
+{
+  lines = g_array_new (FALSE, FALSE, sizeof (line_t));
+  upem = hb_face_get_upem (hb_font_get_face (font_opts->get_font ()));
+}
+
+void
+view_cairo_t::consume_line (hb_buffer_t  *buffer,
+			    const char   *text,
+			    unsigned int  text_len)
+{
+  line_t l = {0};
+
+  l.num_glyphs = hb_buffer_get_length (buffer);
+  hb_glyph_info_t *hb_glyph = hb_buffer_get_glyph_infos (buffer, NULL);
+  hb_glyph_position_t *hb_position = hb_buffer_get_glyph_positions (buffer, NULL);
+  l.glyphs = cairo_glyph_allocate (l.num_glyphs + 1);
+  l.utf8 = g_strndup (text, text_len);
+  l.utf8_len = text_len;
+  l.num_clusters = 1;
+  for (unsigned int i = 1; i < l.num_glyphs; i++)
+    if (hb_glyph[i].cluster != hb_glyph[i-1].cluster)
+      l.num_clusters++;
+  l.clusters = cairo_text_cluster_allocate (l.num_clusters);
+
+  if ((l.num_glyphs && !l.glyphs) ||
+      (l.utf8_len && !l.utf8) ||
+      (l.num_clusters && !l.clusters))
+  {
+    l.finish ();
+    return;
+  }
+
+  hb_position_t x = 0, y = 0;
+  int i;
+  for (i = 0; i < (int) l.num_glyphs; i++)
+    {
+      l.glyphs[i].index = hb_glyph[i].codepoint;
+      l.glyphs[i].x = ( hb_position->x_offset + x) / double (upem);
+      l.glyphs[i].y = (-hb_position->y_offset + y) / double (upem);
+      x +=  hb_position->x_advance;
+      y += -hb_position->y_advance;
+
+      hb_position++;
+    }
+  l.glyphs[i].index = 0;
+  l.glyphs[i].x = x;
+  l.glyphs[i].y = y;
+
+  memset ((void *) l.clusters, 0, l.num_clusters * sizeof (l.clusters[0]));
+  bool backward = HB_DIRECTION_IS_BACKWARD (hb_buffer_get_direction (buffer));
+  l.cluster_flags = backward ? CAIRO_TEXT_CLUSTER_FLAG_BACKWARD : (cairo_text_cluster_flags_t) 0;
+  g_assert (l.num_glyphs);
+  unsigned int cluster = 0;
+  l.clusters[cluster].num_glyphs++;
+  if (backward) {
+    for (i = l.num_glyphs - 2; i >= 0; i--) {
+      if (hb_glyph[i].cluster != hb_glyph[i+1].cluster) {
+        g_assert (hb_glyph[i].cluster > hb_glyph[i+1].cluster);
+	l.clusters[cluster].num_bytes += hb_glyph[i].cluster - hb_glyph[i+1].cluster;
+        cluster++;
+      }
+      l.clusters[cluster].num_glyphs++;
+    }
+    l.clusters[cluster].num_bytes += text_len - hb_glyph[0].cluster;
+  } else {
+    for (i = 1; i < (int) l.num_glyphs; i++) {
+      if (hb_glyph[i].cluster != hb_glyph[i-1].cluster) {
+        g_assert (hb_glyph[i].cluster > hb_glyph[i-1].cluster);
+	l.clusters[cluster].num_bytes += hb_glyph[i].cluster - hb_glyph[i-1].cluster;
+        cluster++;
+      }
+      l.clusters[cluster].num_glyphs++;
+    }
+    l.clusters[cluster].num_bytes += text_len - hb_glyph[i - 1].cluster;
+  }
+
+  g_array_append_val (lines, l);
+}
+
+void
+view_cairo_t::finish (const font_options_t *font_opts)
+{
+  render (font_opts);
+
+  for (unsigned int i = 0; i < lines->len; i++) {
+    line_t &line = g_array_index (lines, line_t, i);
+    line.finish ();
+  }
+
+  g_array_unref (lines);
+}
+
+double
+view_cairo_t::line_width (unsigned int i)
+{
+  line_t &line = g_array_index (lines, line_t, i);
+  return line.glyphs[line.num_glyphs].x / double (upem);
+}
+
+void
+view_cairo_t::get_surface_size (cairo_scaled_font_t *scaled_font,
+				double *w, double *h)
+{
+  cairo_font_extents_t font_extents;
+
+  cairo_scaled_font_extents (scaled_font, &font_extents);
+
+  *h = font_extents.ascent + font_extents.descent + ((int) lines->len - 1) * font_extents.height;
+  *w = 0;
+  for (unsigned int i = 0; i < lines->len; i++)
+    *w = MAX (*w, line_width (i));
+
+  *w += margin.l + margin.r;
+  *h += margin.t + margin.b;
+}
+
+cairo_scaled_font_t *
+view_cairo_t::create_scaled_font (const font_options_t *font_opts)
+{
+  hb_font_t *font = hb_font_reference (font_opts->get_font ());
+
+  cairo_font_face_t *cairo_face = cairo_ft_font_face_create_for_ft_face (hb_ft_font_get_face (font), 0);
+  cairo_matrix_t ctm, font_matrix;
+  cairo_font_options_t *font_options;
+
+  cairo_matrix_init_identity (&ctm);
+  cairo_matrix_init_scale (&font_matrix, font_opts->font_size, font_opts->font_size);
+  font_options = cairo_font_options_create ();
+  cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_NONE);
+  cairo_font_options_set_hint_metrics (font_options, CAIRO_HINT_METRICS_OFF);
+
+  cairo_scaled_font_t *scaled_font = cairo_scaled_font_create (cairo_face, &font_matrix, &ctm, font_options);
+
+  cairo_font_options_destroy (font_options);
+  cairo_font_face_destroy (cairo_face);
+
+  static cairo_user_data_key_t key;
+  if (cairo_scaled_font_set_user_data (scaled_font, &key, (void *) font, (cairo_destroy_func_t) hb_font_destroy))
+    hb_font_destroy (font);
+
+  return scaled_font;
+}
+
+struct finalize_closure_t {
+  void (*callback)(finalize_closure_t *);
+  cairo_surface_t *surface;
+  const char *filename;
+};
+static cairo_user_data_key_t finalize_closure_key;
+
+#ifdef CAIRO_HAS_PNG_FUNCTIONS
+
+static void
+finalize_png (finalize_closure_t *closure)
+{
+  cairo_status_t status;
+  status = cairo_surface_write_to_png (closure->surface, closure->filename);
+  if (status != CAIRO_STATUS_SUCCESS)
+    fail (FALSE, "Failed to write output to `%s': %s",
+	  closure->filename, cairo_status_to_string (status));
+}
+
+static cairo_surface_t *
+_cairo_png_surface_create (const char *filename,
+			   double width,
+			   double height,
+			   cairo_content_t content)
+{
+  cairo_surface_t *surface;
+  int w = ceil (width);
+  int h = ceil (height);
+
+  switch (content) {
+    case CAIRO_CONTENT_ALPHA:
+      surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h);
+      break;
+    default:
+    case CAIRO_CONTENT_COLOR:
+      surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h);
+      break;
+    case CAIRO_CONTENT_COLOR_ALPHA:
+      surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
+      break;
+  }
+  cairo_status_t status = cairo_surface_status (surface);
+  if (status != CAIRO_STATUS_SUCCESS)
+    fail (FALSE, "Failed to create cairo surface: %s",
+	  cairo_status_to_string (status));
+
+  finalize_closure_t *closure = g_new0 (finalize_closure_t, 1);
+  closure->callback = finalize_png;
+  closure->surface = surface;
+  closure->filename = filename;
+
+  if (cairo_surface_set_user_data (surface, &finalize_closure_key, (void *) closure, (cairo_destroy_func_t) g_free))
+    g_free ((void *) closure);
+
+  return surface;
+}
+
+#endif
+
+void
+view_cairo_t::render (const font_options_t *font_opts)
+{
+  cairo_scaled_font_t *scaled_font = create_scaled_font (font_opts);
+  double w, h;
+  get_surface_size (scaled_font, &w, &h);
+  cairo_t *cr = create_context (w, h);
+  cairo_set_scaled_font (cr, scaled_font);
+  cairo_scaled_font_destroy (scaled_font);
+
+  draw (cr);
+
+  finalize_closure_t *closure = (finalize_closure_t *)
+				cairo_surface_get_user_data (cairo_get_target (cr),
+							     &finalize_closure_key);
+  if (closure)
+    closure->callback (closure);
+
+  cairo_status_t status = cairo_status (cr);
+  if (status != CAIRO_STATUS_SUCCESS)
+    fail (FALSE, "Failed: %s",
+	  cairo_status_to_string (status));
+  cairo_destroy (cr);
+}
+
+cairo_t *
+view_cairo_t::create_context (double w, double h)
+{
+  cairo_surface_t *(*constructor) (const char *filename,
+				   double width,
+				   double height) = NULL;
+  cairo_surface_t *(*constructor2) (const char *filename,
+				    double width,
+				    double height,
+				    cairo_content_t content) = NULL;
+
+  const char *extension = output_format;
+  if (!extension)
+    extension = "png";
+  if (0)
+    ;
+  #ifdef CAIRO_HAS_PNG_FUNCTIONS
+    else if (0 == strcasecmp (extension, "png"))
+      constructor2 = _cairo_png_surface_create;
+  #endif
+  #ifdef CAIRO_HAS_SVG_SURFACE
+    else if (0 == strcasecmp (extension, "svg"))
+      constructor = cairo_svg_surface_create;
+  #endif
+  #ifdef CAIRO_HAS_PDF_SURFACE
+    else if (0 == strcasecmp (extension, "pdf"))
+      constructor = cairo_pdf_surface_create;
+  #endif
+  #ifdef CAIRO_HAS_PS_SURFACE
+    else if (0 == strcasecmp (extension, "ps"))
+      constructor = cairo_ps_surface_create;
+   #ifdef HAS_EPS
+    else if (0 == strcasecmp (extension, "eps"))
+      constructor = _cairo_eps_surface_create;
+   #endif
+  #endif
+
+
+  unsigned int fr, fg, fb, fa, br, bg, bb, ba;
+  br = bg = bb = ba = 255;
+  sscanf (back + (*back=='#'), "%2x%2x%2x%2x", &br, &bg, &bb, &ba);
+  fr = fg = fb = 0; fa = 255;
+  sscanf (fore + (*fore=='#'), "%2x%2x%2x%2x", &fr, &fg, &fb, &fa);
+
+  cairo_content_t content;
+  if (!annotate && ba == 255 && br == bg && bg == bb && fr == fg && fg == fb)
+    content = CAIRO_CONTENT_ALPHA;
+  else if (ba == 255)
+    content = CAIRO_CONTENT_COLOR;
+  else
+    content = CAIRO_CONTENT_COLOR_ALPHA;
+
+  cairo_surface_t *surface;
+  if (constructor)
+    surface = constructor (output_file, w, h);
+  else if (constructor2)
+    surface = constructor2 (output_file, w, h, content);
+  else
+    fail (FALSE, "Unknown output format `%s'", extension);
+
+  cairo_t *cr = cairo_create (surface);
+  content = cairo_surface_get_content (surface);
+
+  switch (content) {
+    case CAIRO_CONTENT_ALPHA:
+      cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+      cairo_set_source_rgba (cr, 1., 1., 1., br / 255.);
+      cairo_paint (cr);
+      cairo_set_source_rgba (cr, 1., 1., 1., (fr / 255.) * (fa / 255.) + (br / 255) * (1 - (fa / 255.)));
+      break;
+    default:
+    case CAIRO_CONTENT_COLOR:
+    case CAIRO_CONTENT_COLOR_ALPHA:
+      cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+      cairo_set_source_rgba (cr, br / 255., bg / 255., bb / 255., ba / 255.);
+      cairo_paint (cr);
+      cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+      cairo_set_source_rgba (cr, fr / 255., fg / 255., fb / 255., fa / 255.);
+      break;
+  }
+
+  cairo_surface_destroy (surface);
+  return cr;
+}
+
+void
+view_cairo_t::draw (cairo_t *cr)
+{
+  cairo_save (cr);
+
+  cairo_font_extents_t font_extents;
+  cairo_font_extents (cr, &font_extents);
+  cairo_translate (cr, margin.l, margin.t);
+  for (unsigned int i = 0; i < lines->len; i++)
+  {
+    line_t &l = g_array_index (lines, line_t, i);
+
+    if (i)
+      cairo_translate (cr, 0, line_space);
+
+    cairo_translate (cr, 0, font_extents.ascent);
+
+    if (annotate) {
+      cairo_save (cr);
+
+      /* Draw actual glyph origins */
+      cairo_set_source_rgba (cr, 1., 0., 0., .5);
+      cairo_set_line_width (cr, 5);
+      cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
+      for (unsigned i = 0; i < l.num_glyphs; i++) {
+	cairo_move_to (cr, l.glyphs[i].x, l.glyphs[i].y);
+	cairo_rel_line_to (cr, 0, 0);
+      }
+      cairo_stroke (cr);
+
+      cairo_restore (cr);
+    }
+
+    if (cairo_surface_get_type (cairo_get_target (cr)) == CAIRO_SURFACE_TYPE_IMAGE) {
+      /* cairo_show_glyphs() doesn't support subpixel positioining */
+      cairo_glyph_path (cr, l.glyphs, l.num_glyphs);
+      cairo_fill (cr);
+    } else
+      cairo_show_text_glyphs (cr,
+			      l.utf8, l.utf8_len,
+			      l.glyphs, l.num_glyphs,
+			      l.clusters, l.num_clusters,
+			      l.cluster_flags);
+
+    cairo_translate (cr, 0, font_extents.height - font_extents.ascent);
+  }
+
+  cairo_restore (cr);
+}
diff --git a/util/view-cairo.hh b/util/view-cairo.hh
new file mode 100644
index 0000000..4b09d6f
--- /dev/null
+++ b/util/view-cairo.hh
@@ -0,0 +1,63 @@
+/*
+ * Copyright © 2011  Google, Inc.
+ *
+ *  This is part of HarfBuzz, a text shaping library.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and its documentation for any purpose, provided that the
+ * above copyright notice and the following two paragraphs appear in
+ * all copies of this software.
+ *
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
+ * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
+ * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+ * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
+ * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ * Google Author(s): Behdad Esfahbod
+ */
+
+#include "options.hh"
+
+#include <cairo-ft.h>
+#include <hb-ft.h>
+
+#ifndef VIEW_CAIRO_HH
+#define VIEW_CAIRO_HH
+
+struct view_cairo_t : output_options_t, view_options_t {
+  view_cairo_t (option_parser_t *parser)
+	       : output_options_t (parser),
+	         view_options_t (parser) {}
+  ~view_cairo_t (void) {
+    if (debug)
+      cairo_debug_reset_static_data ();
+  }
+
+  void init (const font_options_t *font_opts);
+  void consume_line (hb_buffer_t  *buffer,
+		     const char   *text,
+		     unsigned int  text_len);
+  void finish (const font_options_t *font_opts);
+
+  protected:
+
+  void render (const font_options_t *font_opts);
+  cairo_scaled_font_t *create_scaled_font (const font_options_t *font_opts);
+  void get_surface_size (cairo_scaled_font_t *scaled_font, double *w, double *h);
+  cairo_t *create_context (double w, double h);
+  void draw (cairo_t *cr);
+  double line_width (unsigned int i);
+
+  GArray *lines;
+  unsigned int upem;
+};
+
+#endif
commit bc4b07b05ea9e39eb9f966eb2c3e1c737efa77ff
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Sep 8 17:08:32 2011 -0400

    More reshuffling

diff --git a/util/hb-view.cc b/util/hb-view.cc
index 5ffe9c1..419b7d5 100644
--- a/util/hb-view.cc
+++ b/util/hb-view.cc
@@ -149,7 +149,7 @@ draw (void)
   cairo_glyph_t *glyphs = NULL;
   unsigned int num_glyphs = 0;
 
-  const char *end, *p = shape_opts->text;
+  const char *end, *p = text;
   double x, y;
 
   cr= create_context ();
@@ -170,7 +170,7 @@ draw (void)
     if (!end)
       end = p + strlen (p);
 
-    if (p != shape_opts->text)
+    if (p != text)
 	y += view_opts->line_space;
 
     if (p != end) {
diff --git a/util/options.cc b/util/options.cc
index 72f8e5d..6fc4029 100644
--- a/util/options.cc
+++ b/util/options.cc
@@ -31,6 +31,7 @@ view_options_t view_opts[1];
 shape_options_t shape_opts[1];
 font_options_t font_opts[1];
 
+const char *text;
 const char *out_file = "/dev/stdout";
 hb_bool_t debug = FALSE;
 
@@ -38,9 +39,10 @@ hb_bool_t debug = FALSE;
 static gboolean
 parse_margin (const char *name G_GNUC_UNUSED,
 	      const char *arg,
-	      gpointer    data G_GNUC_UNUSED,
+	      gpointer    data,
 	      GError    **error 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)) {
     case 1: m.r = m.t;
@@ -59,9 +61,10 @@ parse_margin (const char *name G_GNUC_UNUSED,
 static gboolean
 parse_shapers (const char *name G_GNUC_UNUSED,
 	       const char *arg,
-	       gpointer    data G_GNUC_UNUSED,
+	       gpointer    data,
 	       GError    **error G_GNUC_UNUSED)
 {
+  shape_options_t *shape_opts = (shape_options_t *) data;
   shape_opts->shapers = g_strsplit (arg, ",", 0);
   return TRUE;
 }
@@ -191,9 +194,10 @@ skip_one_feature (char **pp)
 static gboolean
 parse_features (const char *name G_GNUC_UNUSED,
 	        const char *arg,
-	        gpointer    data G_GNUC_UNUSED,
+	        gpointer    data,
 	        GError    **error G_GNUC_UNUSED)
 {
+  shape_options_t *shape_opts = (shape_options_t *) data;
   char *s = (char *) arg;
   char *p;
 
@@ -266,66 +270,66 @@ option_context_add_entries (GOptionContext *context,
 			    GOptionEntry   *entries,
 			    const gchar    *name,
 			    const gchar    *description,
-			    const gchar    *help_description)
+			    const gchar    *help_description,
+			    gpointer        user_data)
 {
-  if (0) {
-    GOptionGroup *group = g_option_group_new (name, description, help_description, NULL, NULL);
-    g_option_group_add_entries (group, entries);
-    g_option_context_add_group (context, group);
-  } else {
-    g_option_context_add_main_entries (context, entries, NULL);
-  }
+  GOptionGroup *group = g_option_group_new (name, description, help_description, user_data, NULL);
+  g_option_group_add_entries (group, entries);
+  g_option_context_add_group (context, group);
 }
 
 void
-option_context_add_view_opts (GOptionContext *context)
+view_options_t::add_options (GOptionContext *context)
 {
   GOptionEntry entries[] =
   {
-    {"annotate",	0, 0, G_OPTION_ARG_NONE,	&view_opts->annotate,		"Annotate output rendering",				NULL},
-    {"background",	0, 0, G_OPTION_ARG_STRING,	&view_opts->back,		"Set background color (default: "DEFAULT_BACK")",	"red/#rrggbb/#rrggbbaa"},
-    {"foreground",	0, 0, G_OPTION_ARG_STRING,	&view_opts->fore,		"Set foreground color (default: "DEFAULT_FORE")",	"red/#rrggbb/#rrggbbaa"},
-    {"line-space",	0, 0, G_OPTION_ARG_DOUBLE,	&view_opts->line_space,		"Set space between lines (default: 0)",			"units"},
+    {"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"},
+    {"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"},
     {NULL}
   };
   option_context_add_entries (context, entries,
 			      "view",
 			      "View options:",
-			      "Options controlling the output rendering");
+			      "Options controlling the output rendering",
+			      this);
 }
 
 void
-option_context_add_shape_opts (GOptionContext *context)
+shape_options_t::add_options (GOptionContext *context)
 {
   GOptionEntry entries[] =
   {
     {"shapers",		0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_shapers,	"Comma-separated list of shapers",	"list"},
-    {"direction",	0, 0, G_OPTION_ARG_STRING,	&shape_opts->direction,		"Set text direction (default: auto)",	"ltr/rtl/ttb/btt"},
-    {"language",	0, 0, G_OPTION_ARG_STRING,	&shape_opts->language,		"Set text language (default: $LANG)",	"langstr"},
-    {"script",		0, 0, G_OPTION_ARG_STRING,	&shape_opts->script,		"Set text script (default: auto)",	"ISO-15924 tag"},
+    {"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"},
     {"features",	0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_features,	"Font features to apply to text",	"TODO"},
     {NULL}
   };
   option_context_add_entries (context, entries,
 			      "shape",
 			      "Shape options:",
-			      "Options controlling the shaping process");
+			      "Options controlling the shaping process",
+			      this);
 }
 
 void
-option_context_add_font_opts (GOptionContext *context)
+font_options_t::add_options (GOptionContext *context)
 {
   GOptionEntry entries[] =
   {
-    {"face-index",	0, 0, G_OPTION_ARG_INT,		&font_opts->face_index,		"Face index (default: 0)",				"index"},
-    {"font-size",	0, 0, G_OPTION_ARG_DOUBLE,	&font_opts->font_size,		"Font size (default: "G_STRINGIFY(DEFAULT_FONT_SIZE)")","size"},
+    {"face-index",	0, 0, G_OPTION_ARG_INT,		&this->face_index,		"Face index (default: 0)",				"index"},
+    {"font-size",	0, 0, G_OPTION_ARG_DOUBLE,	&this->font_size,		"Font size (default: "G_STRINGIFY(DEFAULT_FONT_SIZE)")","size"},
     {NULL}
   };
   option_context_add_entries (context, entries,
 			      "font",
 			      "Font options:",
-			      "Options controlling the font");
+			      "Options controlling the font",
+			      NULL);
 }
 
 void
@@ -345,9 +349,9 @@ parse_options (int argc, char *argv[])
   context = g_option_context_new ("- FONT-FILE TEXT");
 
   g_option_context_add_main_entries (context, entries, NULL);
-  option_context_add_view_opts (context);
-  option_context_add_shape_opts (context);
-  option_context_add_font_opts (context);
+  view_opts->add_options (context);
+  shape_opts->add_options (context);
+  font_opts->add_options (context);
 
   if (!g_option_context_parse (context, &argc, &argv, &parse_error))
   {
@@ -365,5 +369,5 @@ parse_options (int argc, char *argv[])
   }
 
   font_opts->font_file = argv[1];
-  shape_opts->text = argv[2];
+  text = argv[2];
 }
diff --git a/util/options.hh b/util/options.hh
index a6eea53..422dffc 100644
--- a/util/options.hh
+++ b/util/options.hh
@@ -43,6 +43,8 @@ extern struct view_options_t
     margin.t = margin.r = margin.b = margin.l = DEFAULT_MARGIN;
   }
 
+  void add_options (GOptionContext *context);
+
   hb_bool_t annotate;
   const char *fore;
   const char *back;
@@ -63,6 +65,8 @@ extern struct shape_options_t
     g_free (shapers);
   }
 
+  void add_options (GOptionContext *context);
+
   void setup_buffer (hb_buffer_t *buffer) {
     hb_buffer_set_direction (buffer, hb_direction_from_string (direction, -1));
     hb_buffer_set_script (buffer, hb_script_from_string (script, -1));
@@ -74,7 +78,6 @@ extern struct shape_options_t
     return hb_shape_full (font, buffer, features, num_features, NULL, shapers);
   }
 
-  const char *text;
   const char *direction;
   const char *language;
   const char *script;
@@ -93,20 +96,18 @@ extern struct font_options_t
     font_size = DEFAULT_FONT_SIZE;
   }
 
+  void add_options (GOptionContext *context);
+
   const char *font_file;
   int face_index;
   double font_size;
 } font_opts[1];
 
 
+extern const char *text;
 extern const char *out_file;
 extern hb_bool_t debug;
 
-
-void option_context_add_view_opts	(GOptionContext *context);
-void option_context_add_shape_opts	(GOptionContext *context);
-void option_context_add_font_opts	(GOptionContext *context);
-
 void parse_options (int argc, char *argv[]);
 
 
commit 516857eb51bbb79ff4adf44e3fefbf460f9ee8f8
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Sep 8 16:50:24 2011 -0400

    [util] Simplify more

diff --git a/util/options.hh b/util/options.hh
index 2b77d7b..a6eea53 100644
--- a/util/options.hh
+++ b/util/options.hh
@@ -64,12 +64,9 @@ extern struct shape_options_t
   }
 
   void setup_buffer (hb_buffer_t *buffer) {
-    if (direction)
-      hb_buffer_set_direction (buffer, hb_direction_from_string (direction, -1));
-    if (script)
-      hb_buffer_set_script (buffer, hb_script_from_string (script, -1));
-    if (language)
-      hb_buffer_set_language (buffer, hb_language_from_string (language, -1));
+    hb_buffer_set_direction (buffer, hb_direction_from_string (direction, -1));
+    hb_buffer_set_script (buffer, hb_script_from_string (script, -1));
+    hb_buffer_set_language (buffer, hb_language_from_string (language, -1));
   }
 
   bool shape (hb_font_t *font, hb_buffer_t *buffer) {
commit 4f4b114a5592c2f5d128ee795f159b438ad97829
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Sep 8 16:49:02 2011 -0400

    [util] Move code around

diff --git a/util/hb-view.cc b/util/hb-view.cc
index dd31f94..5ffe9c1 100644
--- a/util/hb-view.cc
+++ b/util/hb-view.cc
@@ -61,16 +61,10 @@ _hb_cr_text_glyphs (cairo_t *cr,
 
   hb_buffer = hb_buffer_create ();
 
-  if (shape_opts->direction)
-    hb_buffer_set_direction (hb_buffer, hb_direction_from_string (shape_opts->direction, -1));
-  if (shape_opts->script)
-    hb_buffer_set_script (hb_buffer, hb_script_from_string (shape_opts->script, -1));
-  if (shape_opts->language)
-    hb_buffer_set_language (hb_buffer, hb_language_from_string (shape_opts->language, -1));
 
   hb_buffer_add_utf8 (hb_buffer, utf8, len, 0, len);
 
-  if (!hb_shape_full (hb_font, hb_buffer, shape_opts->features, shape_opts->num_features, NULL, shape_opts->shapers))
+  if (!shape_opts->shape (hb_font, hb_buffer))
     fail ("All shapers failed");
 
   num_glyphs = hb_buffer_get_length (hb_buffer);
diff --git a/util/options.hh b/util/options.hh
index e4f1316..2b77d7b 100644
--- a/util/options.hh
+++ b/util/options.hh
@@ -63,6 +63,20 @@ extern struct shape_options_t
     g_free (shapers);
   }
 
+  void setup_buffer (hb_buffer_t *buffer) {
+    if (direction)
+      hb_buffer_set_direction (buffer, hb_direction_from_string (direction, -1));
+    if (script)
+      hb_buffer_set_script (buffer, hb_script_from_string (script, -1));
+    if (language)
+      hb_buffer_set_language (buffer, hb_language_from_string (language, -1));
+  }
+
+  bool shape (hb_font_t *font, hb_buffer_t *buffer) {
+    setup_buffer (buffer);
+    return hb_shape_full (font, buffer, features, num_features, NULL, shapers);
+  }
+
   const char *text;
   const char *direction;
   const char *language;
commit 46d86a73a103b061144018c3fe947b57548fc58f
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Sep 8 16:43:15 2011 -0400

    Minor
    
    We now support using -1 for NUL-terminated strings.

diff --git a/util/hb-view.cc b/util/hb-view.cc
index ff4a33a..dd31f94 100644
--- a/util/hb-view.cc
+++ b/util/hb-view.cc
@@ -68,8 +68,6 @@ _hb_cr_text_glyphs (cairo_t *cr,
   if (shape_opts->language)
     hb_buffer_set_language (hb_buffer, hb_language_from_string (shape_opts->language, -1));
 
-  if (len < 0)
-    len = strlen (utf8);
   hb_buffer_add_utf8 (hb_buffer, utf8, len, 0, len);
 
   if (!hb_shape_full (hb_font, hb_buffer, shape_opts->features, shape_opts->num_features, NULL, shape_opts->shapers))
commit 90e312cb85df7a6dc350cb62138ab950790e3d15
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Sep 8 16:42:37 2011 -0400

    [util] Move code around

diff --git a/util/hb-view.cc b/util/hb-view.cc
index d89835c..ff4a33a 100644
--- a/util/hb-view.cc
+++ b/util/hb-view.cc
@@ -253,9 +253,6 @@ main (int argc, char **argv)
   }
 
   if (debug) {
-    free (shape_opts->features);
-    g_free (shape_opts->shapers);
-
     cairo_pattern_destroy (fore_pattern);
     cairo_pattern_destroy (back_pattern);
     cairo_surface_destroy (surface);
@@ -268,5 +265,3 @@ main (int argc, char **argv)
 
   return 0;
 }
-
-
diff --git a/util/options.hh b/util/options.hh
index 716d1ad..e4f1316 100644
--- a/util/options.hh
+++ b/util/options.hh
@@ -58,6 +58,10 @@ extern struct shape_options_t
   shape_options_t (void) {
     memset (this, 0, sizeof (*this));
   }
+  ~shape_options_t (void) {
+    free (features);
+    g_free (shapers);
+  }
 
   const char *text;
   const char *direction;
commit 109cb382898f491eed733dba4ef5ba12de94aaf6
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Sep 8 16:00:04 2011 -0400

    [util] Further refactor option parsing

diff --git a/util/options.cc b/util/options.cc
index 3809b1a..72f8e5d 100644
--- a/util/options.cc
+++ b/util/options.cc
@@ -261,31 +261,82 @@ show_version (const char *name G_GNUC_UNUSED,
 }
 
 
+static void
+option_context_add_entries (GOptionContext *context,
+			    GOptionEntry   *entries,
+			    const gchar    *name,
+			    const gchar    *description,
+			    const gchar    *help_description)
+{
+  if (0) {
+    GOptionGroup *group = g_option_group_new (name, description, help_description, NULL, NULL);
+    g_option_group_add_entries (group, entries);
+    g_option_context_add_group (context, group);
+  } else {
+    g_option_context_add_main_entries (context, entries, NULL);
+  }
+}
+
 void
-parse_options (int argc, char *argv[])
+option_context_add_view_opts (GOptionContext *context)
 {
   GOptionEntry entries[] =
   {
-    {"version",		0, G_OPTION_FLAG_NO_ARG,
-			      G_OPTION_ARG_CALLBACK,	(gpointer) &show_version,	"Show version numbers",			NULL},
-    {"debug",		0, 0, G_OPTION_ARG_NONE,	&debug,				"Free all resources before exit",	NULL},
-    {"output",		0, 0, G_OPTION_ARG_STRING,	&out_file,			"Set output file name",			"filename"},
-
-    {"annotate",	0, 0, G_OPTION_ARG_NONE,	&view_opts->annotate,		"Annotate output rendering",		NULL},
-    {"background",	0, 0, G_OPTION_ARG_STRING,	&view_opts->back,		"Set background color",			"red/#rrggbb/#rrggbbaa"},
-    {"foreground",	0, 0, G_OPTION_ARG_STRING,	&view_opts->fore,		"Set foreground color",			"red/#rrggbb/#rrggbbaa"},
-    {"line-space",	0, 0, G_OPTION_ARG_DOUBLE,	&view_opts->line_space,		"Set space between lines (default: 0)",	"units"},
-    {"margin",		0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_margin,	"Margin around output",			"one to four numbers"},
+    {"annotate",	0, 0, G_OPTION_ARG_NONE,	&view_opts->annotate,		"Annotate output rendering",				NULL},
+    {"background",	0, 0, G_OPTION_ARG_STRING,	&view_opts->back,		"Set background color (default: "DEFAULT_BACK")",	"red/#rrggbb/#rrggbbaa"},
+    {"foreground",	0, 0, G_OPTION_ARG_STRING,	&view_opts->fore,		"Set foreground color (default: "DEFAULT_FORE")",	"red/#rrggbb/#rrggbbaa"},
+    {"line-space",	0, 0, G_OPTION_ARG_DOUBLE,	&view_opts->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"},
+    {NULL}
+  };
+  option_context_add_entries (context, entries,
+			      "view",
+			      "View options:",
+			      "Options controlling the output rendering");
+}
 
+void
+option_context_add_shape_opts (GOptionContext *context)
+{
+  GOptionEntry entries[] =
+  {
     {"shapers",		0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_shapers,	"Comma-separated list of shapers",	"list"},
     {"direction",	0, 0, G_OPTION_ARG_STRING,	&shape_opts->direction,		"Set text direction (default: auto)",	"ltr/rtl/ttb/btt"},
     {"language",	0, 0, G_OPTION_ARG_STRING,	&shape_opts->language,		"Set text language (default: $LANG)",	"langstr"},
     {"script",		0, 0, G_OPTION_ARG_STRING,	&shape_opts->script,		"Set text script (default: auto)",	"ISO-15924 tag"},
     {"features",	0, 0, G_OPTION_ARG_CALLBACK,	(gpointer) &parse_features,	"Font features to apply to text",	"TODO"},
+    {NULL}
+  };
+  option_context_add_entries (context, entries,
+			      "shape",
+			      "Shape options:",
+			      "Options controlling the shaping process");
+}
 
-    {"face-index",	0, 0, G_OPTION_ARG_INT,		&font_opts->face_index,		"Face index (default: 0)",		"index"},
-    {"font-size",	0, 0, G_OPTION_ARG_DOUBLE,	&font_opts->font_size,		"Font size",				"size"},
+void
+option_context_add_font_opts (GOptionContext *context)
+{
+  GOptionEntry entries[] =
+  {
+    {"face-index",	0, 0, G_OPTION_ARG_INT,		&font_opts->face_index,		"Face index (default: 0)",				"index"},
+    {"font-size",	0, 0, G_OPTION_ARG_DOUBLE,	&font_opts->font_size,		"Font size (default: "G_STRINGIFY(DEFAULT_FONT_SIZE)")","size"},
+    {NULL}
+  };
+  option_context_add_entries (context, entries,
+			      "font",
+			      "Font options:",
+			      "Options controlling the font");
+}
 
+void
+parse_options (int argc, char *argv[])
+{
+  GOptionEntry entries[] =
+  {
+    {"version",		0, G_OPTION_FLAG_NO_ARG,
+			      G_OPTION_ARG_CALLBACK,	(gpointer) &show_version,	"Show version numbers",			NULL},
+    {"debug",		0, 0, G_OPTION_ARG_NONE,	&debug,				"Free all resources before exit",	NULL},
+    {"output",		0, 0, G_OPTION_ARG_STRING,	&out_file,			"Set output file name",			"filename"},
     {NULL}
   };
   GError *parse_error = NULL;
@@ -294,6 +345,9 @@ parse_options (int argc, char *argv[])
   context = g_option_context_new ("- FONT-FILE TEXT");
 
   g_option_context_add_main_entries (context, entries, NULL);
+  option_context_add_view_opts (context);
+  option_context_add_shape_opts (context);
+  option_context_add_font_opts (context);
 
   if (!g_option_context_parse (context, &argc, &argv, &parse_error))
   {
diff --git a/util/options.hh b/util/options.hh
index 9be0b6b..716d1ad 100644
--- a/util/options.hh
+++ b/util/options.hh
@@ -30,13 +30,17 @@
 #define OPTIONS_HH
 
 
+#define DEFAULT_MARGIN 18
+#define DEFAULT_FORE "#000000"
+#define DEFAULT_BACK "#FFFFFF"
+
 extern struct view_options_t
 {
   view_options_t (void) {
     memset (this, 0, sizeof (*this));
-    fore = "#000000";
-    back = "#ffffff";
-    margin.t = margin.r = margin.b = margin.l = 18.;
+    fore = DEFAULT_FORE;
+    back = DEFAULT_BACK;
+    margin.t = margin.r = margin.b = margin.l = DEFAULT_MARGIN;
   }
 
   hb_bool_t annotate;
@@ -48,6 +52,7 @@ extern struct view_options_t
   } margin;
 } view_opts[1];
 
+
 extern struct shape_options_t
 {
   shape_options_t (void) {
@@ -63,11 +68,14 @@ extern struct shape_options_t
   char **shapers;
 } shape_opts[1];
 
+
+#define DEFAULT_FONT_SIZE 36
+
 extern struct font_options_t
 {
   font_options_t (void) {
     memset (this, 0, sizeof (*this));
-    font_size = 36.;
+    font_size = DEFAULT_FONT_SIZE;
   }
 
   const char *font_file;
@@ -80,6 +88,10 @@ extern const char *out_file;
 extern hb_bool_t debug;
 
 
+void option_context_add_view_opts	(GOptionContext *context);
+void option_context_add_shape_opts	(GOptionContext *context);
+void option_context_add_font_opts	(GOptionContext *context);
+
 void parse_options (int argc, char *argv[]);
 
 
commit bc187e5ac7433f5561b0e97e8c62172c73883f3f
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Thu Sep 8 13:35:17 2011 -0400

    Refine Indic scripts, following Martin Hosken's recommendation

diff --git a/src/hb-ot-shape-complex-private.hh b/src/hb-ot-shape-complex-private.hh
index 6582d59..e4c8424 100644
--- a/src/hb-ot-shape-complex-private.hh
+++ b/src/hb-ot-shape-complex-private.hh
@@ -73,50 +73,62 @@ hb_ot_shape_complex_categorize (const hb_segment_properties_t *props)
     case HB_SCRIPT_SYRIAC:
       return hb_ot_complex_shaper_arabic;
 
-    /* TODO: These are all the scripts that the ucd/IndicSyllabicCategory.txt covers.
-     * Quite possibly many of these need no shaping, and some other are encoded visually.
-     * Needs to be refined.
+#if 0
+    /* Note:
+     *
+     * These disabled scripts are listed in ucd/IndicSyllabicCategory.txt, but according
+     * to Martin Hosken do not require complex shaping.
+     *
+     * TODO We currently keep data for these scripts in our indic table.  Need to fix the
+     * generator to not do that.
      */
-    case HB_SCRIPT_BALINESE:
+
+    /* Simple? */
     case HB_SCRIPT_BATAK:
-    case HB_SCRIPT_BENGALI:
     case HB_SCRIPT_BRAHMI:
+    case HB_SCRIPT_HANUNOO:
+    case HB_SCRIPT_MEETEI_MAYEK:
+    case HB_SCRIPT_SAURASHTRA:
+
+    /* Simple */
+    case HB_SCRIPT_KAYAH_LI:
+    case HB_SCRIPT_LAO:
+    case HB_SCRIPT_LIMBU:
+    case HB_SCRIPT_PHAGS_PA:
+    case HB_SCRIPT_SYLOTI_NAGRI:
+    case HB_SCRIPT_TAGALOG:
+    case HB_SCRIPT_TAGBANWA:
+    case HB_SCRIPT_TAI_LE:
+    case HB_SCRIPT_TAI_VIET:
+    case HB_SCRIPT_THAI:
+    case HB_SCRIPT_TIBETAN:
+
+    /* May need Indic treatment in the future? */
+    case HB_SCRIPT_MYANMAR:
+#endif
+
+    case HB_SCRIPT_BALINESE:
+    case HB_SCRIPT_BENGALI:
     case HB_SCRIPT_BUGINESE:
     case HB_SCRIPT_BUHID:
     case HB_SCRIPT_CHAM:
     case HB_SCRIPT_DEVANAGARI:
     case HB_SCRIPT_GUJARATI:
     case HB_SCRIPT_GURMUKHI:
-    case HB_SCRIPT_HANUNOO:
     case HB_SCRIPT_JAVANESE:
     case HB_SCRIPT_KAITHI:
     case HB_SCRIPT_KANNADA:
-    case HB_SCRIPT_KAYAH_LI:
     case HB_SCRIPT_KHAROSHTHI:
     case HB_SCRIPT_KHMER:
-    case HB_SCRIPT_LAO:
     case HB_SCRIPT_LEPCHA:
-    case HB_SCRIPT_LIMBU:
     case HB_SCRIPT_MALAYALAM:
-    case HB_SCRIPT_MEETEI_MAYEK:
-    case HB_SCRIPT_MYANMAR:
     case HB_SCRIPT_NEW_TAI_LUE:
-    case HB_SCRIPT_ORIYA:
-    case HB_SCRIPT_PHAGS_PA:
-    case HB_SCRIPT_REJANG:
-    case HB_SCRIPT_SAURASHTRA:
+    case HB_SCRIPT_ORIYA: case HB_SCRIPT_REJANG:
     case HB_SCRIPT_SINHALA:
     case HB_SCRIPT_SUNDANESE:
-    case HB_SCRIPT_SYLOTI_NAGRI:
-    case HB_SCRIPT_TAGALOG:
-    case HB_SCRIPT_TAGBANWA:
-    case HB_SCRIPT_TAI_LE:
     case HB_SCRIPT_TAI_THAM:
-    case HB_SCRIPT_TAI_VIET:
     case HB_SCRIPT_TAMIL:
     case HB_SCRIPT_TELUGU:
-    case HB_SCRIPT_THAI:
-    case HB_SCRIPT_TIBETAN:
       return hb_ot_complex_shaper_indic;
 
     /* ^--- Add new shapers here */
commit 738d096a06822e63b3894bd817ecb90e5fb94f73
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Fri Sep 2 13:31:19 2011 -0400

    Pass through unknown ISO 639-3 language tags to OpenType engine
    
    In hb_ot_tag_from_language(), if first component of an unknown
    language is three letters long, use it directly as OpenType language
    tag (after case conversion and padding).

diff --git a/src/hb-ot-tag.cc b/src/hb-ot-tag.cc
index dcae8f1..ac60e96 100644
--- a/src/hb-ot-tag.cc
+++ b/src/hb-ot-tag.cc
@@ -651,6 +651,14 @@ hb_ot_tag_from_language (hb_language_t language)
     return HB_TAG('Z','H','S',' ');
   }
 
+  s = strchr (lang_str, '-');
+  if (!s)
+    s = lang_str + strlen (lang_str);
+  if (s - lang_str == 3) {
+    /* Assume it's ISO-639-3 and upper-case and use it. */
+    return hb_tag_from_string (lang_str, s - lang_str) & ~0x20202000;
+  }
+
   return HB_OT_TAG_DEFAULT_LANGUAGE;
 }
 
diff --git a/test/test-ot-tag.c b/test/test-ot-tag.c
index a00799d..81b6678 100644
--- a/test/test-ot-tag.c
+++ b/test/test-ot-tag.c
@@ -213,12 +213,17 @@ test_ot_tag_language (void)
 
   test_tag_from_language ("dflt", "asdf-asdf-wer-x-hbot-zxc");
 
+  test_tag_from_language ("dflt", "xy");
+  test_tag_from_language ("XYZ", "xyz"); /* Unknown ISO 639-3 */
+  test_tag_from_language ("XYZ", "xyz-qw"); /* Unknown ISO 639-3 */
+
   /* Test that x-hbot overrides the base language */
   test_tag_from_language ("ABC", "fa-x-hbotabc-zxc");
   test_tag_from_language ("ABC", "fa-ir-x-hbotabc-zxc");
   test_tag_from_language ("ABC", "zh-x-hbotabc-zxc");
   test_tag_from_language ("ABC", "zh-cn-x-hbotabc-zxc");
   test_tag_from_language ("ABC", "zh-xy-x-hbotabc-zxc");
+  test_tag_from_language ("ABC", "xyz-xy-x-hbotabc-zxc");
 }
 
 int
commit ea02cbf03c084b3ead6e9e4c9af07b3b47608d5b
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Fri Sep 2 12:39:20 2011 -0400

    [graphite] Don't preload glyphs
    
    Doesn't seem to be slower.

diff --git a/src/hb-graphite2.cc b/src/hb-graphite2.cc
index f56d32c..a3dc1b4 100644
--- a/src/hb-graphite2.cc
+++ b/src/hb-graphite2.cc
@@ -153,7 +153,7 @@ _hb_gr_face_get_data (hb_face_t *face)
   }
 
   data->face = face;
-  data->grface = gr_make_face (data, &hb_gr_get_table, gr_face_preloadGlyphs);
+  data->grface = gr_make_face (data, &hb_gr_get_table, gr_face_default);
 
 
   if (unlikely (!hb_face_set_user_data (face, &hb_gr_data_key, data,
commit 210a06f3d7bd2df55ebd1743da74f327c5a7a967
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Fri Aug 26 13:39:49 2011 +0200

    Minor

diff --git a/src/hb-graphite2.h b/src/hb-graphite2.h
index f3b7a68..3037868 100644
--- a/src/hb-graphite2.h
+++ b/src/hb-graphite2.h
@@ -26,7 +26,6 @@
 #ifndef HB_GRAPHITE2_H
 #define HB_GRAPHITE2_H
 
-#include "hb.h"
 #include "hb-shape.h"
 
 HB_BEGIN_DECLS
@@ -34,11 +33,12 @@ HB_BEGIN_DECLS
 
 #define HB_GRAPHITE_TAG_Silf HB_TAG('S','i','l','f')
 
-hb_bool_t hb_graphite_shape (hb_font_t    *font,
-			hb_buffer_t  *buffer,
-			const hb_feature_t *features,
-			unsigned int  num_features,
-            const char * const *shaper_options);
+hb_bool_t
+hb_graphite_shape (hb_font_t          *font,
+		   hb_buffer_t        *buffer,
+		   const hb_feature_t *features,
+		   unsigned int        num_features,
+		   const char * const *shaper_options);
 
 HB_END_DECLS
 
diff --git a/src/hb-ot-shape.h b/src/hb-ot-shape.h
index 1897e84..644b11e 100644
--- a/src/hb-ot-shape.h
+++ b/src/hb-ot-shape.h
@@ -27,7 +27,6 @@
 #ifndef HB_OT_SHAPE_H
 #define HB_OT_SHAPE_H
 
-#include "hb-common.h"
 #include "hb-shape.h"
 
 
diff --git a/src/hb-uniscribe.h b/src/hb-uniscribe.h
index dbcacd7..7dc830e 100644
--- a/src/hb-uniscribe.h
+++ b/src/hb-uniscribe.h
@@ -27,7 +27,6 @@
 #ifndef HB_UNISCRIBE_H
 #define HB_UNISCRIBE_H
 
-#include "hb-common.h"
 #include "hb-shape.h"
 
 #define _WIN32_WINNT 0x0500
commit 4a8d2e379a34b19bccc72bc3e2d9ace3fdd27733
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Fri Aug 26 09:40:54 2011 +0200

    [graphite2] Chop a few more lines

diff --git a/src/hb-graphite2.cc b/src/hb-graphite2.cc
index dd338c7..f56d32c 100644
--- a/src/hb-graphite2.cc
+++ b/src/hb-graphite2.cc
@@ -59,10 +59,9 @@ static struct hb_gr_face_data_t {
 } _hb_gr_face_data_nil = {NULL, NULL};
 
 static struct hb_gr_font_data_t {
-  hb_font_t *font;
   gr_font   *grfont;
   gr_face   *grface;
-} _hb_gr_font_data_nil = {NULL, NULL, NULL};
+} _hb_gr_font_data_nil = {NULL, NULL};
 
 
 static const void *hb_gr_get_table (const void *data, unsigned int tag, size_t *len)
@@ -107,12 +106,9 @@ static const void *hb_gr_get_table (const void *data, unsigned int tag, size_t *
   return d;
 }
 
-static float hb_gr_get_advance (const void *data, unsigned short gid)
+static float hb_gr_get_advance (const void *hb_font, unsigned short gid)
 {
-  hb_gr_font_data_t *font = (hb_gr_font_data_t *) data;
-  if (!font->font)
-    return 0;
-  return (float) hb_font_get_glyph_h_advance (font->font, gid);
+  return hb_font_get_glyph_h_advance ((hb_font_t *) hb_font, gid);
 }
 
 static void _hb_gr_face_data_destroy (void *data)
@@ -193,11 +189,10 @@ _hb_gr_font_get_data (hb_font_t *font)
     return &_hb_gr_font_data_nil;
   }
 
-  data->font = font;
   data->grface = _hb_gr_face_get_data (font->face)->grface;
   int scale;
   hb_font_get_scale (font, &scale, NULL);
-  data->grfont = gr_make_font_with_advance_fn (scale, data, &hb_gr_get_advance, data->grface);
+  data->grfont = gr_make_font_with_advance_fn (scale, font, &hb_gr_get_advance, data->grface);
 
 
   if (unlikely (!hb_font_set_user_data (font, &hb_gr_data_key, data,
commit 81ec289da799bd2f50da9382507c606d2c779ab9
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Fri Aug 26 09:33:06 2011 +0200

    Minor

diff --git a/src/hb-graphite2.cc b/src/hb-graphite2.cc
index 145ca4f..dd338c7 100644
--- a/src/hb-graphite2.cc
+++ b/src/hb-graphite2.cc
@@ -88,7 +88,7 @@ static const void *hb_gr_get_table (const void *data, unsigned int tag, size_t *
   {
     p = (hb_gr_tablelist_t *) malloc (sizeof (hb_gr_tablelist_t));
     if (!p) {
-      hb_blob_destroy(blob);
+      hb_blob_destroy (blob);
       return NULL;
     }
     p->next = NULL;
@@ -124,7 +124,7 @@ static void _hb_gr_face_data_destroy (void *data)
     hb_gr_tablelist_t *old = tlist;
     hb_blob_destroy (tlist->blob);
     tlist = tlist->next;
-    free(old);
+    free (old);
   }
   gr_face_destroy (f->grface);
 }
@@ -150,9 +150,9 @@ _hb_gr_face_get_data (hb_face_t *face)
 
 
   hb_blob_t *silf_blob = hb_face_reference_table (face, HB_GRAPHITE_TAG_Silf);
-  if (!hb_blob_get_length(silf_blob))
+  if (!hb_blob_get_length (silf_blob))
   {
-    hb_blob_destroy(silf_blob);
+    hb_blob_destroy (silf_blob);
     return &_hb_gr_face_data_nil;
   }
 
@@ -189,7 +189,7 @@ _hb_gr_font_get_data (hb_font_t *font)
   hb_blob_t *silf_blob = hb_face_reference_table (font->face, HB_GRAPHITE_TAG_Silf);
   if (!hb_blob_get_length (silf_blob))
   {
-    hb_blob_destroy(silf_blob);
+    hb_blob_destroy (silf_blob);
     return &_hb_gr_font_data_nil;
   }
 
@@ -269,20 +269,20 @@ hb_graphite_shape (hb_font_t          *font,
   *p = 0;
 
   hb_tag_t script_tag[2];
-  hb_ot_tags_from_script (hb_buffer_get_script(buffer), &script_tag[0], &script_tag[1]);
+  hb_ot_tags_from_script (hb_buffer_get_script (buffer), &script_tag[0], &script_tag[1]);
 
   seg = gr_make_seg (data->grfont, data->grface,
 		     script_tag[1] == HB_TAG_NONE ? script_tag[0] : script_tag[1],
 		     feats,
 		     gr_utf32, text, charlen,
-		     2 | (hb_buffer_get_direction(buffer) == HB_DIRECTION_RTL ? 1 : 0));
+		     2 | (hb_buffer_get_direction (buffer) == HB_DIRECTION_RTL ? 1 : 0));
   if (!seg) goto dieout;
 
   glyphlen = gr_seg_n_slots (seg);
   clusters = (hb_gr_cluster_t *) calloc (charlen, sizeof (hb_gr_cluster_t));
   if (!glyphlen || !clusters) goto dieout;
 
-  gids = (unsigned short *) malloc (glyphlen * sizeof (unsigned short));
+  gids = (uint16_t *) malloc (glyphlen * sizeof (uint16_t));
   if (!gids) goto dieout;
 
   pg = gids;
@@ -315,14 +315,14 @@ hb_graphite_shape (hb_font_t          *font,
   }
   ci++;
 
-  buffer->clear_output();
+  buffer->clear_output ();
   for (unsigned int i = 0; i < ci; ++i)
     buffer->replace_glyphs (clusters[i].num_chars, clusters[i].num_glyphs, gids + clusters[i].base_glyph);
-  buffer->swap_buffers();
+  buffer->swap_buffers ();
 
   hb_glyph_position_t *pPos;
-  for (pPos = hb_buffer_get_glyph_positions(buffer, NULL), is = gr_seg_first_slot(seg);
-       is; pPos++, is = gr_slot_next_in_segment(is))
+  for (pPos = hb_buffer_get_glyph_positions (buffer, NULL), is = gr_seg_first_slot (seg);
+       is; pPos++, is = gr_slot_next_in_segment (is))
   {
     pPos->x_offset = gr_slot_origin_X(is) - curradvx;
     pPos->y_offset = gr_slot_origin_Y(is) - curradvy;
commit 3380de5abbaff535e1cf57ea7e5c2a7c4fdcfe66
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Fri Aug 26 09:30:49 2011 +0200

    [graphite] Use buffer->replace_glyphs()

diff --git a/src/hb-graphite2.cc b/src/hb-graphite2.cc
index 0bd53cb..145ca4f 100644
--- a/src/hb-graphite2.cc
+++ b/src/hb-graphite2.cc
@@ -291,7 +291,6 @@ hb_graphite_shape (hb_font_t          *font,
     unsigned int before = gr_slot_before (is);
     unsigned int after = gr_slot_after (is);
     *pg = gr_slot_gid (is);
-    *pg = hb_be_uint16 (*pg); // XXX insane: swap bytes so be16 can swap them back
     pg++;
     while (clusters[ci].base_char > before && ci)
     {
@@ -318,7 +317,7 @@ hb_graphite_shape (hb_font_t          *font,
 
   buffer->clear_output();
   for (unsigned int i = 0; i < ci; ++i)
-    buffer->replace_glyphs_be16(clusters[i].num_chars, clusters[i].num_glyphs, gids + clusters[i].base_glyph);
+    buffer->replace_glyphs (clusters[i].num_chars, clusters[i].num_glyphs, gids + clusters[i].base_glyph);
   buffer->swap_buffers();
 
   hb_glyph_position_t *pPos;
commit 9ebe8c0286856d46430ae184ba7303bd34485883
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Fri Aug 26 09:29:42 2011 +0200

    Add buffer->replace_glyphs()

diff --git a/src/hb-buffer-private.hh b/src/hb-buffer-private.hh
index f3f3ab0..4292222 100644
--- a/src/hb-buffer-private.hh
+++ b/src/hb-buffer-private.hh
@@ -102,6 +102,9 @@ struct _hb_buffer_t {
   HB_INTERNAL void replace_glyphs_be16 (unsigned int num_in,
 					unsigned int num_out,
 					const uint16_t *glyph_data_be);
+  HB_INTERNAL void replace_glyphs (unsigned int num_in,
+				   unsigned int num_out,
+				   const uint16_t *glyph_data);
   HB_INTERNAL void replace_glyph (hb_codepoint_t glyph_index);
   /* Makes a copy of the glyph at idx to output and replace glyph_index */
   HB_INTERNAL void output_glyph (hb_codepoint_t glyph_index);
diff --git a/src/hb-buffer.cc b/src/hb-buffer.cc
index c3cff08..3e25b15 100644
--- a/src/hb-buffer.cc
+++ b/src/hb-buffer.cc
@@ -268,6 +268,32 @@ hb_buffer_t::replace_glyphs_be16 (unsigned int num_in,
 }
 
 void
+hb_buffer_t::replace_glyphs (unsigned int num_in,
+			     unsigned int num_out,
+			     const uint16_t *glyph_data)
+{
+  if (!make_room_for (num_in, num_out)) return;
+
+  hb_glyph_info_t orig_info = info[idx];
+  for (unsigned int i = 1; i < num_in; i++)
+  {
+    hb_glyph_info_t *inf = &info[idx + i];
+    orig_info.cluster = MIN (orig_info.cluster, inf->cluster);
+  }
+
+  hb_glyph_info_t *pinfo = &out_info[out_len];
+  for (unsigned int i = 0; i < num_out; i++)
+  {
+    *pinfo = orig_info;
+    pinfo->codepoint = glyph_data[i];
+    pinfo++;
+  }
+
+  idx  += num_in;
+  out_len += num_out;
+}
+
+void
 hb_buffer_t::output_glyph (hb_codepoint_t glyph_index)
 {
   if (!make_room_for (0, 1)) return;
commit a5edb1031c204464da4f852ba3d90e8cc20cd20e
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Fri Aug 26 09:27:13 2011 +0200

    Minor

diff --git a/src/hb-graphite2.cc b/src/hb-graphite2.cc
index 0749a93..0bd53cb 100644
--- a/src/hb-graphite2.cc
+++ b/src/hb-graphite2.cc
@@ -297,7 +297,7 @@ hb_graphite_shape (hb_font_t          *font,
     {
       clusters[ci-1].num_chars += clusters[ci].num_chars;
       clusters[ci-1].num_glyphs += clusters[ci].num_glyphs;
-      --ci;
+      ci--;
     }
 
     if (gr_slot_can_insert_before (is) && clusters[ci].num_chars && before >= clusters[ci].base_char + clusters[ci].num_chars)
@@ -307,16 +307,17 @@ hb_graphite_shape (hb_font_t          *font,
       c->num_chars = before - c->base_char;
       c->base_glyph = ic;
       c->num_glyphs = 0;
-      ++ci;
+      ci++;
     }
-    ++clusters[ci].num_glyphs;
+    clusters[ci].num_glyphs++;
 
     if (clusters[ci].base_char + clusters[ci].num_chars < after + 1)
 	clusters[ci].num_chars = after + 1 - clusters[ci].base_char;
   }
+  ci++;
 
   buffer->clear_output();
-  for (unsigned int i = 0; i <= ci; ++i)
+  for (unsigned int i = 0; i < ci; ++i)
     buffer->replace_glyphs_be16(clusters[i].num_chars, clusters[i].num_glyphs, gids + clusters[i].base_glyph);
   buffer->swap_buffers();
 
commit 290e3ee51727df75d136ccfff79831b94d1583b6
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Fri Aug 26 09:25:04 2011 +0200

    [graphite] Only pass the first part language tag to graphite
    
    Still not sure about:
    
    1) Case.  We pass lowercase for now.  Would be nice if graphite was
    uppercase 3letter like OpenType,
    
    2) Padding.  IMO, tag padding is always with spaces, but Martin was
    talking about NUL bytes.

diff --git a/src/hb-graphite2.cc b/src/hb-graphite2.cc
index 76b8a2f..0749a93 100644
--- a/src/hb-graphite2.cc
+++ b/src/hb-graphite2.cc
@@ -236,9 +236,10 @@ hb_graphite_shape (hb_font_t          *font,
 
   if (!charlen) return TRUE;
 
-  /* XXX(behdad): Do we need OT lang tag here? */
   const char *lang = hb_language_to_string (hb_buffer_get_language (buffer));
-  gr_feature_val *feats = gr_face_featureval_for_lang (data->grface, lang ? hb_tag_from_string (lang, -1) : 0);
+  const char *lang_end = strchr (lang, '-');
+  int lang_len = lang_end ? lang_end - lang : -1;
+  gr_feature_val *feats = gr_face_featureval_for_lang (data->grface, lang ? hb_tag_from_string (lang, lang_len) : 0);
 
   while (num_features--)
   {
commit 4c9fe88d30036340fe592bcbc375049b84602b8b
Author: Behdad Esfahbod <behdad at behdad.org>
Date:   Fri Aug 26 09:18:53 2011 +0200

    [API] Make all _from_string() functions take a len parameter
    
    Can be -1 for NUL-terminated string.  This is useful for passing parts
    of a larger string to a function without having to copy or modify the
    string first.
    
    Affected functions:
    
    	hb_tag_t hb_tag_from_string()
    	hb_direction_from_string()
    	hb_language_from_string()
    	hb_script_from_string()

diff --git a/src/hb-common.cc b/src/hb-common.cc
index 48382ca..6093289 100644
--- a/src/hb-common.cc
+++ b/src/hb-common.cc
@@ -40,15 +40,17 @@
 /* hb_tag_t */
 
 hb_tag_t
-hb_tag_from_string (const char *s)
+hb_tag_from_string (const char *s, int len)
 {
   char tag[4];
   unsigned int i;
 
-  if (!s || !*s)
+  if (!s || !len || !*s)
     return HB_TAG_NONE;
 
-  for (i = 0; i < 4 && s[i]; i++)
+  if (len < 0 || len > 4)
+    len = 4;
+  for (i = 0; i < (unsigned) len && s[i]; i++)
     tag[i] = s[i];
   for (; i < 4; i++)
     tag[i] = ' ';
@@ -67,9 +69,9 @@ const char direction_strings[][4] = {
 };
 
 hb_direction_t
-hb_direction_from_string (const char *str)
+hb_direction_from_string (const char *str, int len)
 {
-  if (unlikely (!str || !*str))
+  if (unlikely (!str || !len || !*str))
     return HB_DIRECTION_INVALID;
 
   /* Lets match loosely: just match the first letter, such that
@@ -167,11 +169,18 @@ static struct hb_static_lang_set_t : hb_lockable_set_t<hb_language_item_t, hb_st
 } langs;
 
 hb_language_t
-hb_language_from_string (const char *str)
+hb_language_from_string (const char *str, int len)
 {
-  if (!str || !*str)
+  if (!str || !len || !*str)
     return HB_LANGUAGE_INVALID;
 
+  char strbuf[32];
+  if (len >= 0) {
+    len = MIN (len, (int) sizeof (strbuf) - 1);
+    str = (char *) memcpy (strbuf, str, len);
+    strbuf[len] = '\0';
+  }
+
   hb_language_item_t *item = langs.find_or_insert (str, langs.lock);
 
   return likely (item) ? item->lang : HB_LANGUAGE_INVALID;
@@ -197,7 +206,7 @@ hb_language_get_default (void)
     /* I hear that setlocale() doesn't honor env vars on Windows,
      * but for now we ignore that. */
 
-    default_language = hb_language_from_string (setlocale (LC_CTYPE, NULL));
+    default_language = hb_language_from_string (setlocale (LC_CTYPE, NULL), -1);
   }
 
   return default_language;
@@ -241,9 +250,9 @@ hb_script_from_iso15924_tag (hb_tag_t tag)
 }
 
 hb_script_t
-hb_script_from_string (const char *s)
+hb_script_from_string (const char *s, int len)
 {
-  return hb_script_from_iso15924_tag (hb_tag_from_string (s));
+  return hb_script_from_iso15924_tag (hb_tag_from_string (s, len));
 }
 
 hb_tag_t
diff --git a/src/hb-common.h b/src/hb-common.h
index e4f1991..b7fef32 100644
--- a/src/hb-common.h
+++ b/src/hb-common.h
@@ -89,7 +89,8 @@ typedef uint32_t hb_tag_t;
 
 #define HB_TAG_NONE HB_TAG(0,0,0,0)
 
-hb_tag_t hb_tag_from_string (const char *s);
+/* len=-1 means s is NUL-terminated */
+hb_tag_t hb_tag_from_string (const char *s, int len);
 
 
 /* hb_direction_t */
@@ -102,8 +103,9 @@ typedef enum {
   HB_DIRECTION_BTT
 } hb_direction_t;
 
+/* len=-1 means s is NUL-terminated */
 hb_direction_t
-hb_direction_from_string (const char *str);
+hb_direction_from_string (const char *str, int len);
 
 const char *
 hb_direction_to_string (hb_direction_t direction);
@@ -119,8 +121,9 @@ hb_direction_to_string (hb_direction_t direction);
 
 typedef struct _hb_language_t *hb_language_t;
 
+/* len=-1 means s is NUL-terminated */
 hb_language_t
-hb_language_from_string (const char *str);
+hb_language_from_string (const char *str, int len);
 
 const char *
 hb_language_to_string (hb_language_t language);
@@ -293,8 +296,9 @@ hb_script_t
 hb_script_from_iso15924_tag (hb_tag_t tag);
 
 /* suger for tag_from_string() then script_from_iso15924_tag */
+/* len=-1 means s is NUL-terminated */
 hb_script_t
-hb_script_from_string (const char *s);
+hb_script_from_string (const char *s, int len);
 
 hb_tag_t
 hb_script_to_iso15924_tag (hb_script_t script);
diff --git a/src/hb-graphite2.cc b/src/hb-graphite2.cc
index dfeab9f..76b8a2f 100644
--- a/src/hb-graphite2.cc
+++ b/src/hb-graphite2.cc
@@ -238,7 +238,7 @@ hb_graphite_shape (hb_font_t          *font,
 
   /* XXX(behdad): Do we need OT lang tag here? */
   const char *lang = hb_language_to_string (hb_buffer_get_language (buffer));
-  gr_feature_val *feats = gr_face_featureval_for_lang (data->grface, lang ? hb_tag_from_string (lang) : 0);
+  gr_feature_val *feats = gr_face_featureval_for_lang (data->grface, lang ? hb_tag_from_string (lang, -1) : 0);
 
   while (num_features--)
   {
diff --git a/src/hb-icu.cc b/src/hb-icu.cc
index 82e4989..0f5ed1c 100644
--- a/src/hb-icu.cc
+++ b/src/hb-icu.cc
@@ -46,7 +46,7 @@ hb_icu_script_to_script (UScriptCode script)
   if (unlikely (script == USCRIPT_INVALID_CODE))
     return HB_SCRIPT_INVALID;
 
-  return hb_script_from_string (uscript_getShortName (script));
+  return hb_script_from_string (uscript_getShortName (script), -1);
 }
 
 UScriptCode
diff --git a/src/hb-ot-tag.cc b/src/hb-ot-tag.cc
index e910f95..dcae8f1 100644
--- a/src/hb-ot-tag.cc
+++ b/src/hb-ot-tag.cc
@@ -664,12 +664,12 @@ hb_ot_tag_to_language (hb_tag_t tag)
 
   for (i = 0; i < ARRAY_LENGTH (ot_languages); i++)
     if (ot_languages[i].tag == tag)
-      return hb_language_from_string (ot_languages[i].language);
+      return hb_language_from_string (ot_languages[i].language, -1);
 
   /* If tag starts with ZH, it's Chinese */
   if ((tag & 0xFFFF0000)  == 0x5A480000) {
     switch (tag) {
-      case HB_TAG('Z','H','H',' '): return hb_language_from_string ("zh-hk"); /* Hong Kong */
+      case HB_TAG('Z','H','H',' '): return hb_language_from_string ("zh-hk", -1); /* Hong Kong */
       default: {
         /* Encode the tag... */
 	unsigned char buf[14] = "zh-x-hbot";
@@ -680,7 +680,7 @@ hb_ot_tag_to_language (hb_tag_t tag)
 	if (buf[12] == 0x20)
 	  buf[12] = '\0';
 	buf[13] = '\0';
-	return hb_language_from_string ((char *) buf);
+	return hb_language_from_string ((char *) buf, -1);
       }
     }
   }
@@ -695,7 +695,7 @@ hb_ot_tag_to_language (hb_tag_t tag)
     if (buf[9] == 0x20)
       buf[9] = '\0';
     buf[10] = '\0';
-    return hb_language_from_string ((char *) buf);
+    return hb_language_from_string ((char *) buf, -1);
   }
 }
 
diff --git a/test/test-buffer.c b/test/test-buffer.c
index ec32e15..ab818d0 100644
--- a/test/test-buffer.c
+++ b/test/test-buffer.c
@@ -124,8 +124,8 @@ test_buffer_properties (fixture_t *fixture, gconstpointer user_data)
   hb_buffer_set_script (b, HB_SCRIPT_ARABIC);
   g_assert (hb_buffer_get_script (b) == HB_SCRIPT_ARABIC);
 
-  hb_buffer_set_language (b, hb_language_from_string ("fa"));
-  g_assert (hb_buffer_get_language (b) == hb_language_from_string ("Fa"));
+  hb_buffer_set_language (b, hb_language_from_string ("fa", -1));
+  g_assert (hb_buffer_get_language (b) == hb_language_from_string ("Fa", -1));
 
 
   /* test reset clears properties */
diff --git a/test/test-common.c b/test/test-common.c
index d5cbfc7..e00e601 100644
--- a/test/test-common.c
+++ b/test/test-common.c
@@ -79,14 +79,15 @@ test_types_direction (void)
   g_assert_cmpint (HB_DIRECTION_REVERSE (HB_DIRECTION_TTB), ==, HB_DIRECTION_BTT);
   g_assert_cmpint (HB_DIRECTION_REVERSE (HB_DIRECTION_BTT), ==, HB_DIRECTION_TTB);
 
-  g_assert_cmpint (HB_DIRECTION_INVALID, ==, hb_direction_from_string (NULL));
-  g_assert_cmpint (HB_DIRECTION_INVALID, ==, hb_direction_from_string (""));
-  g_assert_cmpint (HB_DIRECTION_INVALID, ==, hb_direction_from_string ("x"));
-  g_assert_cmpint (HB_DIRECTION_RTL, ==, hb_direction_from_string ("r"));
-  g_assert_cmpint (HB_DIRECTION_RTL, ==, hb_direction_from_string ("rtl"));
-  g_assert_cmpint (HB_DIRECTION_RTL, ==, hb_direction_from_string ("RtL"));
-  g_assert_cmpint (HB_DIRECTION_RTL, ==, hb_direction_from_string ("right-to-left"));
-  g_assert_cmpint (HB_DIRECTION_TTB, ==, hb_direction_from_string ("ttb"));
+  g_assert_cmpint (HB_DIRECTION_INVALID, ==, hb_direction_from_string (NULL, -1));
+  g_assert_cmpint (HB_DIRECTION_INVALID, ==, hb_direction_from_string ("", -1));
+  g_assert_cmpint (HB_DIRECTION_INVALID, ==, hb_direction_from_string ("t", 0));
+  g_assert_cmpint (HB_DIRECTION_INVALID, ==, hb_direction_from_string ("x", -1));
+  g_assert_cmpint (HB_DIRECTION_RTL, ==, hb_direction_from_string ("r", -1));
+  g_assert_cmpint (HB_DIRECTION_RTL, ==, hb_direction_from_string ("rtl", -1));
+  g_assert_cmpint (HB_DIRECTION_RTL, ==, hb_direction_from_string ("RtL", -1));
+  g_assert_cmpint (HB_DIRECTION_RTL, ==, hb_direction_from_string ("right-to-left", -1));
+  g_assert_cmpint (HB_DIRECTION_TTB, ==, hb_direction_from_string ("ttb", -1));
 
   g_assert (0 == strcmp ("ltr", hb_direction_to_string (HB_DIRECTION_LTR)));
   g_assert (0 == strcmp ("rtl", hb_direction_to_string (HB_DIRECTION_RTL)));
@@ -102,14 +103,20 @@ test_types_tag (void)
 
   g_assert_cmphex (HB_TAG ('a','B','c','D'), ==, 0x61426344);
 
-  g_assert_cmphex (hb_tag_from_string ("aBcDe"), ==, 0x61426344);
-  g_assert_cmphex (hb_tag_from_string ("aBcD"),  ==, 0x61426344);
-  g_assert_cmphex (hb_tag_from_string ("aBc"),   ==, 0x61426320);
-  g_assert_cmphex (hb_tag_from_string ("aB"),    ==, 0x61422020);
-  g_assert_cmphex (hb_tag_from_string ("a"),     ==, 0x61202020);
-
-  g_assert_cmphex (hb_tag_from_string (""),      ==, HB_TAG_NONE);
-  g_assert_cmphex (hb_tag_from_string (NULL),    ==, HB_TAG_NONE);
+  g_assert_cmphex (hb_tag_from_string ("aBcDe", -1), ==, 0x61426344);
+  g_assert_cmphex (hb_tag_from_string ("aBcD", -1),  ==, 0x61426344);
+  g_assert_cmphex (hb_tag_from_string ("aBc", -1),   ==, 0x61426320);
+  g_assert_cmphex (hb_tag_from_string ("aB", -1),    ==, 0x61422020);
+  g_assert_cmphex (hb_tag_from_string ("a", -1),     ==, 0x61202020);
+  g_assert_cmphex (hb_tag_from_string ("aBcDe",  1), ==, 0x61202020);
+  g_assert_cmphex (hb_tag_from_string ("aBcDe",  2), ==, 0x61422020);
+  g_assert_cmphex (hb_tag_from_string ("aBcDe",  3), ==, 0x61426320);
+  g_assert_cmphex (hb_tag_from_string ("aBcDe",  4), ==, 0x61426344);
+  g_assert_cmphex (hb_tag_from_string ("aBcDe",  4), ==, 0x61426344);
+
+  g_assert_cmphex (hb_tag_from_string ("", -1),      ==, HB_TAG_NONE);
+  g_assert_cmphex (hb_tag_from_string ("x", 0),      ==, HB_TAG_NONE);
+  g_assert_cmphex (hb_tag_from_string (NULL, -1),    ==, HB_TAG_NONE);
 }
 
 static void
@@ -127,23 +134,26 @@ test_types_script (void)
   g_assert_cmpint (HB_SCRIPT_INVALID, ==, (hb_script_t) HB_TAG_NONE);
   g_assert_cmphex (HB_SCRIPT_ARABIC, !=, HB_SCRIPT_LATIN);
 
-  g_assert_cmphex (HB_SCRIPT_INVALID, ==, hb_script_from_string (NULL));
-  g_assert_cmphex (HB_SCRIPT_INVALID, ==, hb_script_from_string (""));
-  g_assert_cmphex (HB_SCRIPT_UNKNOWN, ==, hb_script_from_string ("x"));
+  g_assert_cmphex (HB_SCRIPT_INVALID, ==, hb_script_from_string (NULL, -1));
+  g_assert_cmphex (HB_SCRIPT_INVALID, ==, hb_script_from_string ("", -1));
+  g_assert_cmphex (HB_SCRIPT_INVALID, ==, hb_script_from_string ("x", 0));
+  g_assert_cmphex (HB_SCRIPT_UNKNOWN, ==, hb_script_from_string ("x", -1));
 
-  g_assert_cmphex (HB_SCRIPT_ARABIC, ==, hb_script_from_string ("arab"));
-  g_assert_cmphex (HB_SCRIPT_ARABIC, ==, hb_script_from_string ("Arab"));
-  g_assert_cmphex (HB_SCRIPT_ARABIC, ==, hb_script_from_string ("ARAB"));
+  g_assert_cmphex (HB_SCRIPT_ARABIC, ==, hb_script_from_string ("arab", -1));
+  g_assert_cmphex (HB_SCRIPT_ARABIC, ==, hb_script_from_string ("Arab", -1));
+  g_assert_cmphex (HB_SCRIPT_ARABIC, ==, hb_script_from_string ("ARAB", -1));
+  g_assert_cmphex (HB_SCRIPT_ARABIC, ==, hb_script_from_string ("Arabic", 6));
+  g_assert_cmphex (HB_SCRIPT_ARABIC, !=, hb_script_from_string ("Arabic", 3));
 
   g_assert_cmphex (HB_SCRIPT_ARABIC, ==, hb_script_from_iso15924_tag (arab));
   g_assert_cmphex (HB_SCRIPT_ARABIC, ==, hb_script_from_iso15924_tag (Arab));
   g_assert_cmphex (HB_SCRIPT_ARABIC, ==, hb_script_from_iso15924_tag (ARAB));
 
   /* Arbitrary tags that look like may be valid ISO 15924 should be preserved. */
-  g_assert_cmphex (HB_SCRIPT_UNKNOWN, !=, hb_script_from_string ("wWyZ"));
+  g_assert_cmphex (HB_SCRIPT_UNKNOWN, !=, hb_script_from_string ("wWyZ", -1));
   g_assert_cmphex (HB_SCRIPT_UNKNOWN, !=, hb_script_from_iso15924_tag (wWyZ));
   /* Otherwise, UNKNOWN should be returned. */
-  g_assert_cmphex (HB_SCRIPT_UNKNOWN, ==, hb_script_from_string ("x123"));
+  g_assert_cmphex (HB_SCRIPT_UNKNOWN, ==, hb_script_from_string ("x123", -1));
   g_assert_cmphex (HB_SCRIPT_UNKNOWN, ==, hb_script_from_iso15924_tag (x123));
 
   g_assert_cmphex (hb_script_to_iso15924_tag (HB_SCRIPT_ARABIC), ==, Arab);
@@ -157,10 +167,10 @@ test_types_script (void)
 static void
 test_types_language (void)
 {
-  hb_language_t fa = hb_language_from_string ("fa");
-  hb_language_t fa_IR = hb_language_from_string ("fa_IR");
-  hb_language_t fa_ir = hb_language_from_string ("fa-ir");
-  hb_language_t en = hb_language_from_string ("en");
+  hb_language_t fa = hb_language_from_string ("fa", -1);
+  hb_language_t fa_IR = hb_language_from_string ("fa_IR", -1);
+  hb_language_t fa_ir = hb_language_from_string ("fa-ir", -1);
+  hb_language_t en = hb_language_from_string ("en", -1);
 
   g_assert (HB_LANGUAGE_INVALID == NULL);
 
@@ -172,11 +182,14 @@ test_types_language (void)
   g_assert (en != fa);
 
   /* Test recall */
-  g_assert (en == hb_language_from_string ("en"));
-  g_assert (en == hb_language_from_string ("eN"));
-
-  g_assert (HB_LANGUAGE_INVALID == hb_language_from_string (NULL));
-  g_assert (HB_LANGUAGE_INVALID == hb_language_from_string (""));
+  g_assert (en == hb_language_from_string ("en", -1));
+  g_assert (en == hb_language_from_string ("eN", -1));
+  g_assert (en == hb_language_from_string ("Enx", 2));
+
+  g_assert (HB_LANGUAGE_INVALID == hb_language_from_string (NULL, -1));
+  g_assert (HB_LANGUAGE_INVALID == hb_language_from_string ("", -1));
+  g_assert (HB_LANGUAGE_INVALID == hb_language_from_string ("en", 0));
+  g_assert (HB_LANGUAGE_INVALID != hb_language_from_string ("en", 1));
   g_assert (NULL == hb_language_to_string (HB_LANGUAGE_INVALID));
 
   /* Not sure how to test this better.  Setting env vars
diff --git a/test/test-ot-tag.c b/test/test-ot-tag.c
index d440dbd..a00799d 100644
--- a/test/test-ot-tag.c
+++ b/test/test-ot-tag.c
@@ -40,7 +40,7 @@ test_simple_tags (const char *s, hb_script_t script)
   hb_script_t t1, t2;
 
   g_test_message ("Testing script %c%c%c%c: tag %s", HB_UNTAG (hb_script_to_iso15924_tag (script)), s);
-  tag = hb_tag_from_string (s);
+  tag = hb_tag_from_string (s, -1);
 
   hb_ot_tags_from_script (script, &t1, &t2);
 
@@ -57,8 +57,8 @@ test_indic_tags (const char *s1, const char *s2, hb_script_t script)
   hb_script_t t1, t2;
 
   g_test_message ("Testing script %c%c%c%c: new tag %s, old tag %s", HB_UNTAG (hb_script_to_iso15924_tag (script)), s1, s2);
-  tag1 = hb_tag_from_string (s1);
-  tag2 = hb_tag_from_string (s2);
+  tag1 = hb_tag_from_string (s1, -1);
+  tag2 = hb_tag_from_string (s2, -1);
 
   hb_ot_tags_from_script (script, &t1, &t2);
 
@@ -85,14 +85,14 @@ test_ot_tag_script_degenerate (void)
   test_simple_tags ("DFLT", HB_SCRIPT_INVALID);
 
   /* Spaces are replaced */
-  g_assert_cmphex (hb_ot_tag_to_script (HB_TAG_CHAR4 ("be  ")), ==, hb_script_from_string ("Beee"));
+  g_assert_cmphex (hb_ot_tag_to_script (HB_TAG_CHAR4 ("be  ")), ==, hb_script_from_string ("Beee", -1));
 }
 
 static void
 test_ot_tag_script_simple (void)
 {
   /* Arbitrary non-existent script */
-  test_simple_tags ("wwyz", hb_script_from_string ("wWyZ"));
+  test_simple_tags ("wwyz", hb_script_from_string ("wWyZ", -1));
 
   /* These we don't really care about */
   test_simple_tags ("zyyy", HB_SCRIPT_COMMON);
@@ -141,8 +141,8 @@ test_ot_tag_script_indic (void)
 static void
 test_language_two_way (const char *tag_s, const char *lang_s)
 {
-  hb_language_t lang = hb_language_from_string (lang_s);
-  hb_tag_t tag = hb_tag_from_string (tag_s);
+  hb_language_t lang = hb_language_from_string (lang_s, -1);
+  hb_tag_t tag = hb_tag_from_string (tag_s, -1);
 
   g_test_message ("Testing language %s <-> tag %s", lang_s, tag_s);
 
@@ -153,8 +153,8 @@ test_language_two_way (const char *tag_s, const char *lang_s)
 static void
 test_tag_from_language (const char *tag_s, const char *lang_s)
 {
-  hb_language_t lang = hb_language_from_string (lang_s);
-  hb_tag_t tag = hb_tag_from_string (tag_s);
+  hb_language_t lang = hb_language_from_string (lang_s, -1);
+  hb_tag_t tag = hb_tag_from_string (tag_s, -1);
 
   g_test_message ("Testing language %s -> tag %s", lang_s, tag_s);
 
@@ -164,8 +164,8 @@ test_tag_from_language (const char *tag_s, const char *lang_s)
 static void
 test_tag_to_language (const char *tag_s, const char *lang_s)
 {
-  hb_language_t lang = hb_language_from_string (lang_s);
-  hb_tag_t tag = hb_tag_from_string (tag_s);
+  hb_language_t lang = hb_language_from_string (lang_s, -1);
+  hb_tag_t tag = hb_tag_from_string (tag_s, -1);
 
   g_test_message ("Testing tag %s -> language %s", tag_s, lang_s);
 
diff --git a/util/hb-view.cc b/util/hb-view.cc
index cdb90b6..d89835c 100644
--- a/util/hb-view.cc
+++ b/util/hb-view.cc
@@ -62,11 +62,11 @@ _hb_cr_text_glyphs (cairo_t *cr,
   hb_buffer = hb_buffer_create ();
 
   if (shape_opts->direction)
-    hb_buffer_set_direction (hb_buffer, hb_direction_from_string (shape_opts->direction));
+    hb_buffer_set_direction (hb_buffer, hb_direction_from_string (shape_opts->direction, -1));
   if (shape_opts->script)
-    hb_buffer_set_script (hb_buffer, hb_script_from_string (shape_opts->script));
+    hb_buffer_set_script (hb_buffer, hb_script_from_string (shape_opts->script, -1));
   if (shape_opts->language)
-    hb_buffer_set_language (hb_buffer, hb_language_from_string (shape_opts->language));
+    hb_buffer_set_language (hb_buffer, hb_language_from_string (shape_opts->language, -1));
 
   if (len < 0)
     len = strlen (utf8);
diff --git a/util/options.cc b/util/options.cc
index d623243..3809b1a 100644
--- a/util/options.cc
+++ b/util/options.cc
@@ -133,10 +133,7 @@ parse_feature_tag (char **pp, hb_feature_t *feature)
   if (p == *pp)
     return FALSE;
 
-  **pp = '\0';
-  feature->tag = hb_tag_from_string (p);
-  **pp = c;
-
+  feature->tag = hb_tag_from_string (p, *pp - p);
   return TRUE;
 }
 



More information about the HarfBuzz mailing list