[util] Add hb-shape utility

Like hb-view, but prints out buffer contents.

The output format is kinda cryptic.  Suggestions welcome.
diff --git a/configure.ac b/configure.ac
index ac41742..2c5e728 100644
--- a/configure.ac
+++ b/configure.ac
@@ -103,12 +103,6 @@
 fi
 AM_CONDITIONAL(HAVE_CAIRO, $have_cairo)
 
-PKG_CHECK_MODULES(CAIRO_PNG, cairo-png, have_cairo_png=true, have_cairo_png=false)
-if $have_cairo_png; then
-	AC_DEFINE(HAVE_CAIRO_PNG, 1, [Have cairo-png support in cairo graphics library])
-fi
-AM_CONDITIONAL(HAVE_CAIRO_PNG, $have_cairo_png)
-
 PKG_CHECK_MODULES(CAIRO_FT, cairo-ft, have_cairo_ft=true, have_cairo_ft=false)
 if $have_cairo_ft; then
 	AC_DEFINE(HAVE_CAIRO_FT, 1, [Have cairo-ft support in cairo graphics library])
diff --git a/util/Makefile.am b/util/Makefile.am
index a899c26..981cb34 100644
--- a/util/Makefile.am
+++ b/util/Makefile.am
@@ -8,39 +8,51 @@
 
 bin_PROGRAMS =
 
-if HAVE_GLIB
-if HAVE_FREETYPE
-if HAVE_CAIRO_FT
-if HAVE_CAIRO_PNG
-hb_view_SOURCES = \
-	hb-view.cc \
-	common.cc \
-	common.hh \
-	options.cc \
-	options.hh \
-	view-cairo.cc \
-	view-cairo.hh \
-	$(NULL)
-hb_view_CPPFLAGS = \
+AM_CPPFLAGS = \
 	-I$(top_srcdir)/src/ \
 	-I$(top_builddir)/src/ \
 	$(GLIB_CFLAGS) \
 	$(FREETYPE_CFLAGS) \
 	$(CAIRO_FT_CFLAGS) \
-	$(CAIRO_PNG_CFLAGS) \
 	$(NULL)
-hb_view_LDADD = \
+LDADD = \
 	$(top_builddir)/src/libharfbuzz.la \
 	-lm \
 	$(GLIB_LIBS) \
 	$(FREETYPE_LIBS) \
+	$(NULL)
+
+if HAVE_GLIB
+if HAVE_FREETYPE
+if HAVE_CAIRO_FT
+hb_view_SOURCES = \
+	hb-view.cc \
+	options.cc \
+	options.hh \
+	helper-cairo.cc \
+	helper-cairo.hh \
+	view-cairo.cc \
+	view-cairo.hh \
+	$(NULL)
+hb_view_LDADD = \
+	$(LDADD) \
+	$(CAIRO_LIBS) \
 	$(CAIRO_FT_LIBS) \
-	$(CAIRO_PNG_LIBS) \
 	$(NULL)
 bin_PROGRAMS += hb-view
 endif
 endif
 endif
+
+if HAVE_GLIB
+if HAVE_FREETYPE
+hb_shape_SOURCES = \
+	hb-shape.cc \
+	options.cc \
+	options.hh \
+	$(NULL)
+bin_PROGRAMS += hb-shape
+endif
 endif
 
 -include $(top_srcdir)/git.mk
diff --git a/util/common.cc b/util/common.cc
deleted file mode 100644
index b319914..0000000
--- a/util/common.cc
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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 "common.hh"
-
-void
-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);
-  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
deleted file mode 100644
index a9a16f4..0000000
--- a/util/common.hh
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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
- */
-
-
-#ifndef COMMON_HH
-#define COMMON_HH
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <stdlib.h>
-#include <stddef.h>
-#include <string.h>
-#include <assert.h>
-#include <stdio.h>
-#include <math.h>
-#include <locale.h>
-#include <errno.h>
-#include <fcntl.h>
-#ifdef HAVE_IO_H
-#include <io.h> /* for _setmode() under Windows */
-#endif
-
-
-#include <hb.h>
-#include <glib.h>
-#include <glib/gprintf.h>
-
-
-void fail (hb_bool_t suggest_help, const char *format, ...) G_GNUC_NORETURN;
-
-
-#endif
diff --git a/util/hb-shape.cc b/util/hb-shape.cc
new file mode 100644
index 0000000..906bac2
--- /dev/null
+++ b/util/hb-shape.cc
@@ -0,0 +1,78 @@
+/*
+ * Copyright © 2010  Behdad Esfahbod
+ * 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 "hb-view.hh"
+
+struct output_buffer_t : output_options_t, format_options_t
+{
+  output_buffer_t (option_parser_t *parser)
+		  : output_options_t (parser),
+		    format_options_t (parser) {}
+
+  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:
+  GString *gs;
+  hb_font_t *font;
+};
+
+void
+output_buffer_t::init (const font_options_t *font_opts)
+{
+  get_file_handle ();
+  font = hb_font_reference (font_opts->get_font ());
+  gs = g_string_new (NULL);
+}
+
+void
+output_buffer_t::consume_line (hb_buffer_t  *buffer,
+			       const char   *text,
+			       unsigned int  text_len)
+{
+  g_string_set_size (gs, 0);
+  serialize (buffer, font, gs);
+  fprintf (fp, "%s\n", gs->str);
+}
+
+void
+output_buffer_t::finish (const font_options_t *font_opts)
+{
+  g_string_free (gs, TRUE);
+  gs = NULL;
+  hb_font_destroy (font);
+  font = NULL;
+}
+
+int
+main (int argc, char **argv)
+{
+  return hb_view_t<output_buffer_t>::main (argc, argv);
+}
diff --git a/util/hb-view.cc b/util/hb-view.cc
index d745c21..3bd1364 100644
--- a/util/hb-view.cc
+++ b/util/hb-view.cc
@@ -25,51 +25,11 @@
  * Google Author(s): Behdad Esfahbod
  */
 
-#include "common.hh"
-#include "options.hh"
-
+#include "hb-view.hh"
 #include "view-cairo.hh"
 
 int
 main (int argc, char **argv)
 {
-  setlocale (LC_ALL, "");
-
-  option_parser_t options ("[FONT-FILE] [TEXT]");
-
-  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);
-  }
-  hb_buffer_destroy (buffer);
-
-  output.finish (&font_opts);
-
-  return 0;
+  return hb_view_t<view_cairo_t>::main (argc, argv);
 }
diff --git a/util/hb-view.hh b/util/hb-view.hh
new file mode 100644
index 0000000..1799cca
--- /dev/null
+++ b/util/hb-view.hh
@@ -0,0 +1,79 @@
+/*
+ * 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 "common.hh"
+#include "options.hh"
+
+#ifndef HB_VIEW_HH
+#define HB_VIEW_HH
+
+template <typename output_t>
+struct hb_view_t
+{
+  static int
+  main (int argc, char **argv)
+  {
+    option_parser_t options ("[FONT-FILE] [TEXT]");
+
+    shape_options_t shaper (&options);
+    font_options_t font_opts (&options);
+    text_options_t input (&options);
+
+    output_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);
+    }
+    hb_buffer_destroy (buffer);
+
+    output.finish (&font_opts);
+
+    return 0;
+  }
+};
+
+#endif
diff --git a/util/helper-cairo.cc b/util/helper-cairo.cc
new file mode 100644
index 0000000..3a6d913
--- /dev/null
+++ b/util/helper-cairo.cc
@@ -0,0 +1,375 @@
+/*
+ * 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 "helper-cairo.hh"
+
+#include <cairo-ft.h>
+#include <hb-ft.h>
+
+#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_for_stream (cairo_write_func_t  write_func,
+				      void               *closure,
+				      double              width,
+				      double              height)
+{
+  cairo_surface_t *surface;
+
+  surface = cairo_ps_surface_create_for_stream (write_func, closure, width, height);
+  cairo_ps_surface_set_eps (surface, TRUE);
+
+  return surface;
+}
+
+#  else
+#    undef HAS_EPS
+#  endif
+#endif
+
+cairo_scaled_font_t *
+helper_cairo_create_scaled_font (const font_options_t *font_opts,
+				 double font_size)
+{
+  hb_font_t *font = hb_font_reference (font_opts->get_font ());
+
+  cairo_font_face_t *cairo_face;
+  FT_Face ft_face = hb_ft_font_get_face (font);
+  if (!ft_face)
+    /* This allows us to get some boxes at least... */
+    cairo_face = cairo_toy_font_face_create ("@cairo:sans",
+					     CAIRO_FONT_SLANT_NORMAL,
+					     CAIRO_FONT_WEIGHT_NORMAL);
+  else
+    cairo_face = cairo_ft_font_face_create_for_ft_face (ft_face, 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_size, 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;
+  cairo_write_func_t write_func;
+  void *closure;
+};
+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_stream (closure->surface,
+					      closure->write_func,
+					      closure->closure);
+  if (status != CAIRO_STATUS_SUCCESS)
+    fail (FALSE, "Failed to write output: %s",
+	  cairo_status_to_string (status));
+}
+
+static cairo_surface_t *
+_cairo_png_surface_create_for_stream (cairo_write_func_t write_func,
+				      void *closure,
+				      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 *png_closure = g_new0 (finalize_closure_t, 1);
+  png_closure->callback = finalize_png;
+  png_closure->surface = surface;
+  png_closure->write_func = write_func;
+  png_closure->closure = closure;
+
+  if (cairo_surface_set_user_data (surface,
+				   &finalize_closure_key,
+				   (void *) png_closure,
+				   (cairo_destroy_func_t) g_free))
+    g_free ((void *) closure);
+
+  return surface;
+}
+
+#endif
+
+static cairo_status_t
+stdio_write_func (void                *closure,
+		  const unsigned char *data,
+		  unsigned int         size)
+{
+  FILE *fp = (FILE *) closure;
+
+  while (size) {
+    size_t ret = fwrite (data, 1, size, fp);
+    size -= ret;
+    data += ret;
+    if (size && ferror (fp))
+      fail (FALSE, "Failed to write output: %s", strerror (errno));
+  }
+
+  return CAIRO_STATUS_SUCCESS;
+}
+
+cairo_t *
+helper_cairo_create_context (double w, double h,
+			     view_options_t *view_opts,
+			     output_options_t *out_opts)
+{
+  cairo_surface_t *(*constructor) (cairo_write_func_t write_func,
+				   void *closure,
+				   double width,
+				   double height) = NULL;
+  cairo_surface_t *(*constructor2) (cairo_write_func_t write_func,
+				    void *closure,
+				    double width,
+				    double height,
+				    cairo_content_t content) = NULL;
+
+  const char *extension = out_opts->output_format;
+  if (!extension)
+    extension = "png";
+  if (0)
+    ;
+  #ifdef CAIRO_HAS_PNG_FUNCTIONS
+    else if (0 == strcasecmp (extension, "png"))
+      constructor2 = _cairo_png_surface_create_for_stream;
+  #endif
+  #ifdef CAIRO_HAS_SVG_SURFACE
+    else if (0 == strcasecmp (extension, "svg"))
+      constructor = cairo_svg_surface_create_for_stream;
+  #endif
+  #ifdef CAIRO_HAS_PDF_SURFACE
+    else if (0 == strcasecmp (extension, "pdf"))
+      constructor = cairo_pdf_surface_create_for_stream;
+  #endif
+  #ifdef CAIRO_HAS_PS_SURFACE
+    else if (0 == strcasecmp (extension, "ps"))
+      constructor = cairo_ps_surface_create_for_stream;
+   #ifdef HAS_EPS
+    else if (0 == strcasecmp (extension, "eps"))
+      constructor = _cairo_eps_surface_create_for_stream;
+   #endif
+  #endif
+
+
+  unsigned int fr, fg, fb, fa, br, bg, bb, ba;
+  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);
+
+  cairo_content_t content;
+  if (!view_opts->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;
+  FILE *f = out_opts->get_file_handle ();
+  if (constructor)
+    surface = constructor (stdio_write_func, f, w, h);
+  else if (constructor2)
+    surface = constructor2 (stdio_write_func, f, 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
+helper_cairo_destroy_context (cairo_t *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);
+}
+
+
+void
+helper_cairo_line_from_buffer (helper_cairo_line_t *l,
+			       hb_buffer_t         *buffer,
+			       const char          *text,
+			       unsigned int         text_len,
+			       double               scale)
+{
+  memset (l, 0, sizeof (*l));
+
+  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);
+
+  if (text) {
+    l->utf8 = g_strndup (text, text_len);
+    l->utf8_len = text_len;
+    l->num_clusters = l->num_glyphs ? 1 : 0;
+    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) * scale;
+    l->glyphs[i].y = (-hb_position->y_offset + y) * scale;
+    x +=  hb_position->x_advance;
+    y += -hb_position->y_advance;
+
+    hb_position++;
+  }
+  l->glyphs[i].index = -1;
+  l->glyphs[i].x = x * scale;
+  l->glyphs[i].y = y * scale;
+
+  if (l->num_clusters) {
+    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;
+    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;
+    }
+  }
+}
diff --git a/util/helper-cairo.hh b/util/helper-cairo.hh
new file mode 100644
index 0000000..5edfc3a
--- /dev/null
+++ b/util/helper-cairo.hh
@@ -0,0 +1,79 @@
+/*
+ * 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.h>
+
+#ifndef HELPER_CAIRO_HH
+#define HELPER_CAIRO_HH
+
+
+cairo_scaled_font_t *
+helper_cairo_create_scaled_font (const font_options_t *font_opts,
+				 double font_size);
+
+
+cairo_t *
+helper_cairo_create_context (double w, double h,
+			     view_options_t *view_opts,
+			     output_options_t *out_opts);
+
+void
+helper_cairo_destroy_context (cairo_t *cr);
+
+
+struct helper_cairo_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);
+  }
+
+  double get_width (void) {
+    return glyphs[num_glyphs].x;
+  }
+};
+
+void
+helper_cairo_line_from_buffer (helper_cairo_line_t *l,
+			       hb_buffer_t         *buffer,
+			       const char          *text,
+			       unsigned int         text_len,
+			       double               scale);
+
+#endif
diff --git a/util/options.cc b/util/options.cc
index 96d131f..21ab973 100644
--- a/util/options.cc
+++ b/util/options.cc
@@ -31,6 +31,23 @@
 #endif
 
 
+void
+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);
+  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);
+}
+
+
 bool debug = FALSE;
 
 static gchar *
@@ -118,6 +135,8 @@
 void
 option_parser_t::parse (int *argc, char ***argv)
 {
+  setlocale (LC_ALL, "");
+
   GError *parse_error = NULL;
   if (!g_option_context_parse (context, argc, argv, &parse_error))
   {
@@ -590,3 +609,63 @@
 
   return fp;
 }
+
+
+void
+format_options_t::add_options (option_parser_t *parser)
+{
+  GOptionEntry entries[] =
+  {
+    {"no-glyph-names",	0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,	&this->show_glyph_names,	"Use glyph indices instead of names",	NULL},
+    {"no-positions",	0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,	&this->show_positions,		"Do not show glyph positions",		NULL},
+    {"no-clusters",	0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,	&this->show_clusters,		"Do not show cluster mapping",		NULL},
+    {NULL}
+  };
+  parser->add_group (entries,
+		     "format",
+		     "Format options:",
+		     "Options controlling the formatting of buffer contents",
+		     this);
+}
+
+void
+format_options_t::serialize (hb_buffer_t *buffer,
+			     hb_font_t   *font,
+			     GString     *gs)
+{
+  FT_Face ft_face = show_glyph_names ? hb_ft_font_get_face (font) : NULL;
+
+  unsigned int num_glyphs = hb_buffer_get_length (buffer);
+  hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, NULL);
+  hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffer, NULL);
+
+  g_string_append_c (gs, '<');
+  for (unsigned int i = 0; i < (int) num_glyphs; i++)
+  {
+    if (i)
+      g_string_append_c (gs, '|');
+    char glyph_name[30];
+    if (show_glyph_names && !FT_Get_Glyph_Name (ft_face, info->codepoint, glyph_name, sizeof (glyph_name)))
+      g_string_append_printf (gs, "%s", glyph_name);
+    else
+      g_string_append_printf (gs, "gid%d", info->codepoint);
+
+    if (show_clusters)
+      g_string_append_printf (gs, "=%d", info->cluster);
+
+    if (show_positions && (pos->x_offset || pos->y_offset)) {
+      g_string_append_c (gs, '@');
+      if (pos->x_offset) g_string_append_printf (gs, "%d", pos->x_offset);
+      if (pos->y_offset) g_string_append_printf (gs, ",%d", pos->y_offset);
+    }
+    if (show_positions && (pos->x_advance || pos->y_advance)) {
+      g_string_append_c (gs, '+');
+      if (pos->x_advance) g_string_append_printf (gs, "%d", pos->x_advance);
+      if (pos->y_advance) g_string_append_printf (gs, ",%d", pos->y_advance);
+    }
+
+    info++;
+    pos++;
+  }
+  g_string_append_c (gs, '>');
+}
diff --git a/util/options.hh b/util/options.hh
index e8747a6..af9bedb 100644
--- a/util/options.hh
+++ b/util/options.hh
@@ -24,12 +24,34 @@
  * Google Author(s): Behdad Esfahbod
  */
 
-#include "common.hh"
-
 #ifndef OPTIONS_HH
 #define OPTIONS_HH
 
 
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include <math.h>
+#include <locale.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_IO_H
+#include <io.h> /* for _setmode() under Windows */
+#endif
+
+#include <hb.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+
+void fail (hb_bool_t suggest_help, const char *format, ...) G_GNUC_NORETURN;
+
+
 extern bool debug;
 
 struct option_group_t
@@ -248,12 +270,37 @@
 			     unsigned int  text_len) = 0;
   virtual void finish (const font_options_t *font_opts) = 0;
 
-  protected:
   const char *output_file;
   const char *output_format;
 
+  protected:
+
   mutable FILE *fp;
 };
 
+struct format_options_t : option_group_t
+{
+  format_options_t (option_parser_t *parser) {
+    show_glyph_names = true;
+    show_positions = true;
+    show_clusters = true;
+
+    add_options (parser);
+  }
+  ~format_options_t (void) {
+  }
+
+  void add_options (option_parser_t *parser);
+
+  void serialize (hb_buffer_t *buffer,
+		  hb_font_t   *font,
+		  GString     *gs);
+
+  protected:
+  bool show_glyph_names;
+  bool show_positions;
+  bool show_clusters;
+};
+
 
 #endif
diff --git a/util/view-cairo.cc b/util/view-cairo.cc
index e3c49a4..4671147 100644
--- a/util/view-cairo.cc
+++ b/util/view-cairo.cc
@@ -26,60 +26,10 @@
 
 #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_for_stream (cairo_write_func_t  write_func,
-				      void               *closure,
-				      double              width,
-				      double              height)
-{
-  cairo_surface_t *surface;
-
-  surface = cairo_ps_surface_create_for_stream (write_func, closure, 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));
+  lines = g_array_new (FALSE, FALSE, sizeof (helper_cairo_line_t));
   scale = double (font_size) / hb_face_get_upem (hb_font_get_face (font_opts->get_font ()));
 }
 
@@ -88,82 +38,8 @@
 			    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);
-
-  if (text) {
-    l.utf8 = g_strndup (text, text_len);
-    l.utf8_len = text_len;
-    l.num_clusters = l.num_glyphs ? 1 : 0;
-    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);
-  } else {
-    l.utf8_len = 0;
-    l.num_clusters = 0;
-  }
-
-  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) * scale;
-    l.glyphs[i].y = (-hb_position->y_offset + y) * scale;
-    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;
-
-  if (l.num_clusters) {
-    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;
-    unsigned int cluster = 0;
-    if (!l.num_glyphs)
-      goto done;
-    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;
-    }
-  }
-
-done:
+  helper_cairo_line_t l;
+  helper_cairo_line_from_buffer (&l, buffer, text, text_len, scale);
   g_array_append_val (lines, l);
 }
 
@@ -173,20 +49,12 @@
   render (font_opts);
 
   for (unsigned int i = 0; i < lines->len; i++) {
-    line_t &line = g_array_index (lines, line_t, i);
+    helper_cairo_line_t &line = g_array_index (lines, helper_cairo_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 * scale;
-}
-
 void
 view_cairo_t::get_surface_size (cairo_scaled_font_t *scaled_font,
 				double *w, double *h)
@@ -199,252 +67,29 @@
      + 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));
+  for (unsigned int i = 0; i < lines->len; i++) {
+    helper_cairo_line_t &line = g_array_index (lines, helper_cairo_line_t, i);
+    double line_width = line.get_width ();
+    *w = MAX (*w, line_width);
+  }
 
   *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;
-  FT_Face ft_face = hb_ft_font_get_face (font);
-  if (!ft_face)
-    /* This allows us to get some boxes at least... */
-    cairo_face = cairo_toy_font_face_create ("@cairo:sans",
-					     CAIRO_FONT_SLANT_NORMAL,
-					     CAIRO_FONT_WEIGHT_NORMAL);
-  else
-    cairo_face = cairo_ft_font_face_create_for_ft_face (ft_face, 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_size, 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;
-  cairo_write_func_t write_func;
-  void *closure;
-};
-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_stream (closure->surface,
-					      closure->write_func,
-					      closure->closure);
-  if (status != CAIRO_STATUS_SUCCESS)
-    fail (FALSE, "Failed to write output: %s",
-	  cairo_status_to_string (status));
-}
-
-static cairo_surface_t *
-_cairo_png_surface_create_for_stream (cairo_write_func_t write_func,
-				      void *closure,
-				      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 *png_closure = g_new0 (finalize_closure_t, 1);
-  png_closure->callback = finalize_png;
-  png_closure->surface = surface;
-  png_closure->write_func = write_func;
-  png_closure->closure = closure;
-
-  if (cairo_surface_set_user_data (surface,
-				   &finalize_closure_key,
-				   (void *) png_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);
+  cairo_scaled_font_t *scaled_font = helper_cairo_create_scaled_font (font_opts, font_size);
   double w, h;
   get_surface_size (scaled_font, &w, &h);
-  cairo_t *cr = create_context (w, h);
+  cairo_t *cr = helper_cairo_create_context (w, h, this, this);
   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);
-}
-
-static cairo_status_t
-stdio_write_func (void                *closure,
-		  const unsigned char *data,
-		  unsigned int         size)
-{
-  FILE *fp = (FILE *) closure;
-
-  while (size) {
-    size_t ret = fwrite (data, 1, size, fp);
-    size -= ret;
-    data += ret;
-    if (size && ferror (fp))
-      fail (FALSE, "Failed to write output: %s", strerror (errno));
-  }
-
-  return CAIRO_STATUS_SUCCESS;
-}
-
-cairo_t *
-view_cairo_t::create_context (double w, double h)
-{
-  cairo_surface_t *(*constructor) (cairo_write_func_t write_func,
-				   void *closure,
-				   double width,
-				   double height) = NULL;
-  cairo_surface_t *(*constructor2) (cairo_write_func_t write_func,
-				    void *closure,
-				    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_for_stream;
-  #endif
-  #ifdef CAIRO_HAS_SVG_SURFACE
-    else if (0 == strcasecmp (extension, "svg"))
-      constructor = cairo_svg_surface_create_for_stream;
-  #endif
-  #ifdef CAIRO_HAS_PDF_SURFACE
-    else if (0 == strcasecmp (extension, "pdf"))
-      constructor = cairo_pdf_surface_create_for_stream;
-  #endif
-  #ifdef CAIRO_HAS_PS_SURFACE
-    else if (0 == strcasecmp (extension, "ps"))
-      constructor = cairo_ps_surface_create_for_stream;
-   #ifdef HAS_EPS
-    else if (0 == strcasecmp (extension, "eps"))
-      constructor = _cairo_eps_surface_create_for_stream;
-   #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;
-  FILE *f = get_file_handle ();
-  if (constructor)
-    surface = constructor (stdio_write_func, f, w, h);
-  else if (constructor2)
-    surface = constructor2 (stdio_write_func, f, 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;
+  helper_cairo_destroy_context (cr);
 }
 
 void
@@ -457,7 +102,7 @@
   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);
+    helper_cairo_line_t &l = g_array_index (lines, helper_cairo_line_t, i);
 
     if (i)
       cairo_translate (cr, 0, line_space);
diff --git a/util/view-cairo.hh b/util/view-cairo.hh
index 863039b..564f5c7 100644
--- a/util/view-cairo.hh
+++ b/util/view-cairo.hh
@@ -25,13 +25,12 @@
  */
 
 #include "options.hh"
-
-#include <cairo-ft.h>
-#include <hb-ft.h>
+#include "helper-cairo.hh"
 
 #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),
@@ -50,11 +49,8 @@
   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;
   double scale;