[util] Refactor hb-view completely

Now we can use the same code to do other utils...
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 @@
 	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);
+  shape_options_t shaper (&options);
+  font_options_t font_opts (&options);
+  text_options_t input (&options);
+
+  view_cairo_t output (&options);
+
+  options.parse (&argc, &argv);
+
+  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 ();
+
+  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);
   }
-  cairo_face = cairo_ft_font_face_create_for_ft_face (ft_face, 0);
+  hb_buffer_destroy (buffer);
 
-  draw ();
-  draw ();
-
-  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);
-  }
-
-  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 ();
-
-    FT_Done_Face (ft_face);
-    FT_Done_FreeType (ft_library);
-  }
+  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 @@
 }
 
 
-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 @@
     {"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 @@
     {"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);
+}
 
-  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))
+void
+output_options_t::add_options (option_parser_t *parser)
+{
+  GOptionEntry entries[] =
   {
-    if (parse_error != NULL)
-      fail ("%s", parse_error->message);
-    else
-      fail ("Option parse error");
-    exit(1);
-  }
-  g_option_context_free(context);
+    {"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);
+}
 
-  if (argc != 3) {
-    g_printerr ("Usage: %s [OPTION...] FONT-FILE TEXT\n", g_get_prgname ());
-    exit (1);
+
+
+hb_font_t *
+font_options_t::get_font (void) const
+{
+  if (font)
+    return font;
+
+  hb_blob_t *blob = NULL;
+
+  /* Create the blob */
+  {
+    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);
   }
 
-  font_opts->font_file = argv[1];
-  text = argv[2];
+  /* 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);
+  }
+
+  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;
+  }
+
+  *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 @@
     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 @@
   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;
+};
 
 
-extern const char *text;
-extern const char *out_file;
-extern hb_bool_t debug;
+struct text_options_t : option_group_t
+{
+  text_options_t (option_parser_t *parser) {
+    text = NULL;
+    text_file = NULL;
 
-void parse_options (int argc, char *argv[]);
+    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
+      }
+  }
+
+  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;
+
+  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