[cairo-commit] 2 commits - boilerplate/cairo-boilerplate.c boilerplate/cairo-boilerplate-script.c boilerplate/cairo-boilerplate-script-private.h boilerplate/Makefile.sources build/configure.ac.features configure.ac doc/public src/cairo-base85-stream.c src/cairo-cache.c src/cairo-cache-private.h src/cairo-deflate-stream.c src/cairo-ft-font.c src/cairo-ft-private.h src/cairo-gstate.c src/cairo.h src/cairoint.h src/cairo-output-stream.c src/cairo-output-stream-private.h src/cairo-path-fixed.c src/cairo-path-fixed-private.h src/cairo-pattern.c src/cairo-pdf-operators.c src/cairo-ps-surface.c src/cairo-scaled-font.c src/cairo-scaled-font-private.h src/cairo-script.h src/cairo-script-surface.c src/cairo-types-private.h src/Makefile.sources test/any2ppm.c test/Makefile.am test/mime-data.script.ref.png util/cairo-script util/Makefile.am

Chris Wilson ickle at kemper.freedesktop.org
Thu Nov 13 03:39:57 PST 2008


 boilerplate/Makefile.sources                   |    3 
 boilerplate/cairo-boilerplate-script-private.h |   57 
 boilerplate/cairo-boilerplate-script.c         |  125 
 boilerplate/cairo-boilerplate.c                |   16 
 build/configure.ac.features                    |    2 
 configure.ac                                   |   14 
 doc/public/tmpl/cairo-surface.sgml             |    1 
 src/Makefile.sources                           |    3 
 src/cairo-base85-stream.c                      |    1 
 src/cairo-cache-private.h                      |    2 
 src/cairo-cache.c                              |   41 
 src/cairo-deflate-stream.c                     |    1 
 src/cairo-ft-font.c                            |   12 
 src/cairo-ft-private.h                         |    3 
 src/cairo-gstate.c                             |    3 
 src/cairo-output-stream-private.h              |   19 
 src/cairo-output-stream.c                      |   43 
 src/cairo-path-fixed-private.h                 |   11 
 src/cairo-path-fixed.c                         |  164 
 src/cairo-pattern.c                            |  255 +
 src/cairo-pdf-operators.c                      |    1 
 src/cairo-ps-surface.c                         |    2 
 src/cairo-scaled-font-private.h                |    9 
 src/cairo-scaled-font.c                        |    5 
 src/cairo-script-surface.c                     | 2598 +++++++++++
 src/cairo-script.h                             |   74 
 src/cairo-types-private.h                      |    1 
 src/cairo.h                                    |    4 
 src/cairoint.h                                 |   18 
 test/Makefile.am                               |    2 
 test/any2ppm.c                                 |   92 
 test/mime-data.script.ref.png                  |binary
 util/Makefile.am                               |    2 
 util/cairo-script/COPYING                      |   17 
 util/cairo-script/Makefile.am                  |   21 
 util/cairo-script/cairo-script-file.c          | 1018 ++++
 util/cairo-script/cairo-script-hash.c          |  448 +
 util/cairo-script/cairo-script-interpreter.c   |  473 ++
 util/cairo-script/cairo-script-interpreter.h   |  104 
 util/cairo-script/cairo-script-objects.c       |  666 ++
 util/cairo-script/cairo-script-operators.c     | 5791 +++++++++++++++++++++++++
 util/cairo-script/cairo-script-private.h       |  853 +++
 util/cairo-script/cairo-script-scanner.c       | 1180 +++++
 util/cairo-script/cairo-script-stack.c         |  196 
 44 files changed, 14309 insertions(+), 42 deletions(-)

New commits:
commit cdfffc7420e005b2a7d1979feef8bd304183126c
Author: Chris Wilson <chris at chris-wilson.co.uk>
Date:   Thu Nov 13 11:07:45 2008 +0000

    Add CairoScript interpreter
    
    Add a CairoScript interpreter library and use it to replay the test output
    for the CairoScript backend. The library is also used by the currently
    standalone Sphinx debugger [git://anongit.freedesktop.org/~ickle/sphinx].
    The syntax/operator semantics are not yet finalized, but are expected to
    mature before the next stable release.

diff --git a/configure.ac b/configure.ac
index a307493..9bc22e1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -236,18 +236,12 @@ CAIRO_ENABLE_SURFACE_BACKEND(directfb, directfb, no, [
 
 dnl ===========================================================================
 
+any2ppm_cs=no
 CAIRO_ENABLE_SURFACE_BACKEND(script, script, no, [
-  test_script="yes"
-  csi_CFLAGS=
-  csi_LIBS=-lcairo-script-interpreter
-  if test "x$test_script" = "xyes"; then
-    AC_DEFINE([CAIRO_CAN_TEST_SCRIPT_SURFACE], 1,
-	      [Define to 1 if the CairoScript backend can be tested])
-  else
-    AC_MSG_WARN([CairoScript backend will not be tested])
-  fi
-  AC_SUBST(csi_CFLAGS)
-  AC_SUBST(csi_LIBS)
+  test_script=yes
+  any2ppm_cs=yes
+  AC_DEFINE([CAIRO_CAN_TEST_SCRIPT_SURFACE], 1,
+	    [Define to 1 if the CairoScript backend can be tested])
 ])
 
 dnl ===========================================================================
@@ -494,7 +488,8 @@ dnl Build the external converter if we have any of the test backends
 AM_CONDITIONAL(BUILD_ANY2PPM,
 	       test "x$any2ppm_svg" = "xyes" \
 	         -o "x$any2ppm_pdf" = "xyes" \
-		 -o "x$any2ppm_ps"  = "xyes")
+		 -o "x$any2ppm_ps"  = "xyes" \
+		 -o "x$any2ppm_cs"  = "xyes")
 
 dnl ===========================================================================
 dnl The tracing utility requires LD_PRELOAD, so only build it for systems
@@ -558,6 +553,7 @@ test/Makefile
 test/pdiff/Makefile
 perf/Makefile
 util/Makefile
+util/cairo-script/Makefile
 util/cairo-trace/Makefile
 util/cairo-trace/cairo-trace
 doc/Makefile
diff --git a/test/Makefile.am b/test/Makefile.am
index 498f616..5517977 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1170,10 +1170,10 @@ png_flatten_LDADD = $(top_builddir)/src/libcairo.la $(CAIRO_LDADD)
 
 if BUILD_ANY2PPM
 check_PROGRAMS += any2ppm
-any2ppm_CFLAGS = $(POPPLER_CFLAGS) $(LIBRSVG_CFLAGS) $(LIBSPECTRE_CFLAGS) $(csi_CFLAGS)
+any2ppm_CFLAGS = $(POPPLER_CFLAGS) $(LIBRSVG_CFLAGS) $(LIBSPECTRE_CFLAGS)
 # add LDADD, so poppler/librsvg uses "our" cairo
 any2ppm_LDFLAGS = $(CAIRO_TEST_UNDEFINED_LDFLAGS)
-any2ppm_LDADD  = $(top_builddir)/src/libcairo.la $(CAIRO_LDADD) $(POPPLER_LIBS) $(LIBRSVG_LIBS) $(LIBSPECTRE_LIBS) $(csi_LIBS)
+any2ppm_LDADD = $(top_builddir)/util/cairo-script/libcairo-script-interpreter.la $(top_builddir)/src/libcairo.la $(CAIRO_LDADD) $(POPPLER_LIBS) $(LIBRSVG_LIBS) $(LIBSPECTRE_LIBS)
 endif
 
 if CAIRO_CAN_TEST_PDF_SURFACE
diff --git a/test/any2ppm.c b/test/any2ppm.c
index 1bd4285..c4cf085 100644
--- a/test/any2ppm.c
+++ b/test/any2ppm.c
@@ -171,12 +171,15 @@ write_ppm (cairo_surface_t *surface, int fd)
     format = cairo_image_surface_get_format (surface);
     if (format == CAIRO_FORMAT_ARGB32) {
 	/* see if we can convert to a standard ppm type and trim a few bytes */
-	cairo_bool_t uses_alpha = FALSE;
 	const unsigned char *alpha = data;
-	for (j = height * stride; j-- && uses_alpha == FALSE; alpha += 4)
-	    uses_alpha = *alpha != 0xff;
-	if (! uses_alpha)
-	    format = CAIRO_FORMAT_RGB24;
+	for (j = height; j--; alpha += stride) {
+	    for (i = 0; i < width; i++) {
+		if ((*(unsigned int *) (alpha+4*i) & 0xff000000) != 0xff000000)
+		    goto done;
+	    }
+	}
+	format = CAIRO_FORMAT_RGB24;
+ done: ;
     }
 
     switch (format) {
@@ -236,14 +239,12 @@ write_ppm (cairo_surface_t *surface, int fd)
 #if CAIRO_CAN_TEST_SCRIPT_SURFACE
 static cairo_surface_t *
 _create_image (void *closure,
-	       double width, double height,
-	       csi_object_t *dictionary)
+	       double width, double height)
+	       //csi_object_t *dictionary)
 {
     cairo_surface_t **out = closure;
-    cairo_surface_t *surface;
-
-    surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
-    return *out = cairo_surface_reference (surface);
+    *out = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+    return cairo_surface_reference (*out);
 }
 
 static const char *
@@ -253,17 +254,21 @@ _cairo_script_render_page (const char *filename,
     cairo_script_interpreter_t *csi;
     cairo_surface_t *surface = NULL;
     cairo_status_t status;
+    const cairo_script_interpreter_hooks_t hooks = {
+	.closure = &surface,
+	.surface_create = _create_image,
+    };
 
-    csi = csi_create ();
-    csi_set_surface_create_function (csi, _create_image, &surface);
-    csi_run (csi, filename);
-    status = csi_destroy (csi);
-    if (status || surface == NULL) {
-	cairo_surface_destroy (surface);
+    csi = cairo_script_interpreter_create ();
+    cairo_script_interpreter_install_hooks (csi, &hooks);
+    cairo_script_interpreter_run (csi, filename);
+    status = cairo_script_interpreter_destroy (csi);
+    if (surface == NULL) {
 	return "cairo-script interpreter failed";
     }
 
-    status = cairo_surface_status (surface);
+    if (status == CAIRO_STATUS_SUCCESS)
+	status = cairo_surface_status (surface);
     if (status) {
 	cairo_surface_destroy (surface);
 	return cairo_status_to_string (status);
diff --git a/util/Makefile.am b/util/Makefile.am
index 42c831c..f272e4c 100644
--- a/util/Makefile.am
+++ b/util/Makefile.am
@@ -1,6 +1,6 @@
 include $(top_srcdir)/build/Makefile.am.common
 
-SUBDIRS = .
+SUBDIRS = . cairo-script
 
 if BUILD_TRACE
 SUBDIRS += cairo-trace
diff --git a/util/cairo-script/COPYING b/util/cairo-script/COPYING
new file mode 100644
index 0000000..66ad784
--- /dev/null
+++ b/util/cairo-script/COPYING
@@ -0,0 +1,17 @@
+Cairo is free software.
+
+Every source file in the implementation of cairo is available to be
+redistributed and/or modified under the terms of either the GNU Lesser
+General Public License (LGPL) version 2.1 or the Mozilla Public
+License (MPL) version 1.1.  Some files are available under more
+liberal terms, but we believe that in all cases, each file may be used
+under either the LGPL or the MPL.
+
+See the following files in this directory for the precise terms and
+conditions of either license:
+
+	COPYING-LGPL-2.1
+	COPYING-MPL-1.1
+
+Please see each file in the implementation for copyright and licensing
+information, (in the opening comment of each file).
diff --git a/util/cairo-script/Makefile.am b/util/cairo-script/Makefile.am
new file mode 100644
index 0000000..348519f
--- /dev/null
+++ b/util/cairo-script/Makefile.am
@@ -0,0 +1,21 @@
+lib_LTLIBRARIES = libcairo-script-interpreter.la
+
+cairoincludedir=$(includedir)/cairo
+cairoinclude_HEADERS = cairo-script-interpreter.h
+libcairo_script_interpreter_la_SOURCES = \
+	cairo-script-private.h \
+	cairo-script-file.c \
+	cairo-script-hash.c \
+	cairo-script-interpreter.c \
+	cairo-script-objects.c \
+	cairo-script-operators.c \
+	cairo-script-scanner.c \
+	cairo-script-stack.c \
+	$(NULL)
+libcairo_script_interpreter_la_CPPFLAGS = -I$(top_srcdir)/src
+libcairo_script_interpreter_la_CFLAGS = $(CAIRO_CFLAGS)
+libcairo_script_interpreter_la_LDFLAGS = -version-info $(CAIRO_LIBTOOL_VERSION_INFO) -no-undefined $(export_symbols)
+libcairo_script_interpreter_la_LIBADD = -lz $(CAIRO_LIBS) -L$(top_builddir)/src -lcairo
+
+EXTRA_DIST = \
+	COPYING
diff --git a/util/cairo-script/cairo-script-file.c b/util/cairo-script/cairo-script-file.c
new file mode 100644
index 0000000..c21ef63
--- /dev/null
+++ b/util/cairo-script/cairo-script-file.c
@@ -0,0 +1,1018 @@
+/*
+ * Copyright © 2008 Chris Wilson <chris at chris-wilson.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Chris Wilson.
+ *
+ * Contributor(s):
+ *	Chris Wilson <chris at chris-wilson.co.uk>
+ */
+
+#include "cairo-script-private.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <zlib.h>
+
+#define CHUNK_SIZE 32768
+
+csi_status_t
+csi_file_new (csi_t *ctx,
+	      csi_object_t *obj,
+	      const char *path, const char *mode)
+{
+    csi_file_t *file;
+
+    file = _csi_slab_alloc (ctx, sizeof (csi_file_t));
+    if (file == NULL)
+	return _csi_error (CAIRO_STATUS_NO_MEMORY);
+
+    file->base.type = CSI_OBJECT_TYPE_FILE;
+    file->base.ref = 1;
+
+    file->data = NULL;
+    file->type = STDIO;
+    file->src = fopen (path, mode);
+    if (file->src == NULL) {
+	_csi_slab_free (ctx, file, sizeof (csi_file_t));
+	return _csi_error (CAIRO_STATUS_FILE_NOT_FOUND);
+    }
+
+    file->data = _csi_alloc (ctx, CHUNK_SIZE);
+    if (file->data == NULL) {
+	_csi_slab_free (ctx, file, sizeof (csi_file_t));
+	return _csi_error (CAIRO_STATUS_NO_MEMORY);
+    }
+    file->bp = file->data;
+    file->rem = 0;
+
+    obj->type = CSI_OBJECT_TYPE_FILE;
+    obj->datum.file = file;
+    return CAIRO_STATUS_SUCCESS;
+}
+
+csi_status_t
+csi_file_new_for_bytes (csi_t *ctx,
+			csi_object_t *obj,
+			const char *bytes,
+			unsigned int length)
+{
+    csi_file_t *file;
+
+    file = _csi_slab_alloc (ctx, sizeof (csi_file_t));
+    if (file == NULL)
+	return _csi_error (CAIRO_STATUS_NO_MEMORY);
+
+    file->base.type = CSI_OBJECT_TYPE_FILE;
+    file->base.ref = 1;
+
+    file->type = BYTES;
+    file->src  = (uint8_t *) bytes;
+    file->data = (uint8_t *) bytes;
+    file->bp   = (uint8_t *) bytes;
+    file->rem  = length;
+
+    obj->type = CSI_OBJECT_TYPE_FILE;
+    obj->datum.file = file;
+    return CAIRO_STATUS_SUCCESS;
+}
+
+csi_status_t
+csi_file_new_from_string (csi_t *ctx,
+			  csi_object_t *obj,
+			  csi_string_t *src)
+{
+    csi_file_t *file;
+
+    file = _csi_slab_alloc (ctx, sizeof (csi_file_t));
+    if (file == NULL)
+	return _csi_error (CAIRO_STATUS_NO_MEMORY);
+
+    file->base.type = CSI_OBJECT_TYPE_FILE;
+    file->base.ref = 1;
+
+    file->type = BYTES;
+    file->src = src; src->base.ref++;
+    file->data = src->string;
+    file->bp   = file->data;
+    file->rem  = src->len;
+
+    obj->type = CSI_OBJECT_TYPE_FILE;
+    obj->datum.file = file;
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_csi_file_new_filter (csi_t *ctx,
+		      csi_object_t *obj,
+		      csi_object_t *src,
+		      const csi_filter_funcs_t *funcs,
+		      void *data)
+{
+    csi_file_t *file;
+    csi_object_t src_file;
+    csi_status_t status;
+
+    file = _csi_slab_alloc (ctx, sizeof (csi_file_t));
+    if (file == NULL)
+	return _csi_error (CAIRO_STATUS_NO_MEMORY);
+
+    obj->type = CSI_OBJECT_TYPE_FILE;
+    obj->datum.file = file;
+
+    file->base.type = CSI_OBJECT_TYPE_FILE;
+    file->base.ref = 1;
+
+    file->type = FILTER;
+    file->data = data;
+    file->filter = funcs;
+    status = csi_object_as_file (ctx, src, &src_file);
+    if (status) {
+	csi_object_free (ctx, obj);
+	return status;
+    }
+    file->src = src_file.datum.file;
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+
+#if 0
+csi_status_t
+csi_file_new_from_stream (csi_t *ctx,
+			  FILE *file,
+			  csi_object_t **out)
+{
+    csi_file_t *obj;
+
+    obj = (csi_file_t *) _csi_object_new (ctx, CSI_OBJECT_TYPE_FILE);
+    if (obj == NULL)
+	return _csi_error (CAIRO_STATUS_NO_MEMORY);
+
+    obj->type = STDIO;
+    obj->src = file;
+    obj->data = _csi_alloc (ctx, CHUNK_SIZE);
+    if (obj->data == NULL) {
+	csi_object_free (&obj->base);
+	return _csi_error (CAIRO_STATUS_UNDEFINED_FILENAME_ERROR);
+    }
+    obj->bp = obj->data;
+    obj->rem = 0;
+
+    *out = &obj->base;
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static csi_object_t *
+_csi_file_new_from_procedure (csi_t *ctx, csi_object_t *src)
+{
+    csi_file_t *obj;
+
+    obj = (csi_file_t *) _csi_object_new (ctx, CSI_OBJECT_TYPE_FILE);
+    if (obj == NULL)
+	return NULL;
+
+    obj->type = PROCEDURE;
+    obj->src = csi_object_reference (src);
+    obj->data = NULL;
+
+    return &obj->base;
+}
+#endif
+
+typedef struct _ascii85_decode_data {
+    uint8_t buf[CHUNK_SIZE];
+    uint8_t *bp;
+    short bytes_available;
+    short eod;
+} _ascii85_decode_data_t;
+
+static int
+_getc_skip_whitespace (csi_file_t *src)
+{
+    int c;
+
+    do switch ((c = csi_file_getc (src))) {
+    case 0x0:
+    case 0x9:
+    case 0xa:
+    case 0xc:
+    case 0xd:
+    case 0x20:
+	continue;
+
+    default:
+	return c;
+    } while (TRUE);
+
+    return c;
+}
+
+static void
+_ascii85_decode (csi_file_t *file)
+{
+    _ascii85_decode_data_t *data = file->data;
+    unsigned int n;
+
+    if (data->eod)
+	return;
+
+    data->bp = data->buf;
+
+    n = 0;
+    do {
+	unsigned int v = _getc_skip_whitespace (file->src);
+	if (v == 'z') {
+	    data->buf[n+0] = 0;
+	    data->buf[n+1] = 0;
+	    data->buf[n+2] = 0;
+	    data->buf[n+3] = 0;
+	} else if (v == '~') {
+	    v = _getc_skip_whitespace (file->src);
+	    /* c == '>' || IO_ERROR */
+	    data->eod = TRUE;
+	    break;
+	} else if (v < '!' || v > 'u') {
+	    /* IO_ERROR */
+	    data->eod = TRUE;
+	    break;
+	} else {
+	    unsigned int i;
+
+	    v -= '!';
+	    for (i = 1; i < 5; i++) {
+		int c = _getc_skip_whitespace (file->src);
+		if (c == '~') { /* short tuple */
+		    c = _getc_skip_whitespace (file->src);
+		    /* c == '>' || IO_ERROR */
+		    data->eod = TRUE;
+		    switch (i) {
+		    case 0:
+		    case 1:
+			/* IO_ERROR */
+			break;
+		    case 2:
+			v = v * (85*85*85) + 85*85*85 -1;
+			goto odd1;
+		    case 3:
+			v = v * (85*85) + 85*85 -1;
+			goto odd2;
+		    case 4:
+			v = v * 85 + 84;
+			data->buf[n+2] = v >> 8 & 0xff;
+odd2:
+			data->buf[n+1] = v >> 16 & 0xff;
+odd1:
+			data->buf[n+0] = v >> 24 & 0xff;
+			data->bytes_available = n + i - 1;
+			return;
+		    }
+		    break;
+		}
+		v = 85*v + c-'!';
+	    }
+
+	    data->buf[n+0] = v >> 24 & 0xff;
+	    data->buf[n+1] = v >> 16 & 0xff;
+	    data->buf[n+2] = v >> 8 & 0xff;
+	    data->buf[n+3] = v >> 0 & 0xff;
+	}
+	n += 4;
+    } while (n < sizeof (data->buf) && data->eod == FALSE);
+
+    data->bytes_available = n;
+}
+
+static int
+_ascii85_decode_getc (csi_file_t *file)
+{
+    _ascii85_decode_data_t *data = file->data;
+
+    if (data->bytes_available == 0) {
+	_ascii85_decode (file);
+
+	if (data->bytes_available == 0)
+	    return EOF;
+    }
+
+    data->bytes_available--;
+    return *data->bp++;
+}
+
+static void
+_ascii85_decode_putc (csi_file_t *file, int c)
+{
+    _ascii85_decode_data_t *data = file->data;
+    data->bytes_available++;
+    data->bp--;
+}
+
+static int
+_ascii85_decode_read (csi_file_t *file, uint8_t *buf, int len)
+{
+    _ascii85_decode_data_t *data = file->data;
+
+    if (data->bytes_available == 0) {
+	_ascii85_decode (file);
+
+	if (data->bytes_available == 0)
+	    return 0;
+    }
+
+    if (len > data->bytes_available)
+	len = data->bytes_available;
+    memcpy (buf, data->bp, len);
+    data->bp += len;
+    data->bytes_available -= len;
+    return len;
+}
+
+csi_status_t
+csi_file_new_ascii85_decode (csi_t *ctx,
+			     csi_object_t *obj,
+			     csi_dictionary_t *dict,
+			     csi_object_t *src)
+{
+    static const csi_filter_funcs_t funcs = {
+	_ascii85_decode_getc,
+	_ascii85_decode_putc,
+	_ascii85_decode_read,
+	_csi_free,
+    };
+    _ascii85_decode_data_t *data;
+
+    data = _csi_alloc0 (ctx, sizeof (_ascii85_decode_data_t));
+    if (data == NULL)
+	return _csi_error (CAIRO_STATUS_NO_MEMORY);
+
+    return _csi_file_new_filter (ctx, obj, src, &funcs, data);
+}
+
+typedef struct _deflate_decode_data {
+    z_stream zlib_stream;
+
+    uint8_t in[CHUNK_SIZE];
+    uint8_t out[CHUNK_SIZE];
+
+    int bytes_available;
+    uint8_t *bp;
+} _deflate_decode_data_t;
+
+static void
+_deflate_decode (csi_file_t *file)
+{
+    _deflate_decode_data_t *data = file->data;
+    uint8_t *bp;
+    int len;
+
+    data->zlib_stream.next_out = data->out;
+    data->zlib_stream.avail_out = sizeof (data->out);
+
+    bp = data->in;
+    len = sizeof (data->in);
+    if (data->zlib_stream.avail_in) {
+	memmove (data->in,
+		 data->zlib_stream.next_in,
+		 data->zlib_stream.avail_in);
+	len -= data->zlib_stream.avail_in;
+	bp += data->zlib_stream.avail_in;
+    }
+
+    len = csi_file_read (file->src, bp, len);
+
+    data->zlib_stream.next_in = data->in;
+    data->zlib_stream.avail_in += len;
+
+    inflate (&data->zlib_stream, len == 0 ? Z_FINISH : Z_NO_FLUSH);
+
+    data->bytes_available = data->zlib_stream.next_out - data->out;
+    data->bp = data->out;
+}
+
+static int
+_deflate_decode_getc (csi_file_t *file)
+{
+    _deflate_decode_data_t *data = file->data;
+
+    if (data->bytes_available == 0) {
+	_deflate_decode (file);
+
+	if (data->bytes_available == 0)
+	    return EOF;
+    }
+
+    data->bytes_available--;
+    return *data->bp++;
+}
+
+static void
+_deflate_decode_putc (csi_file_t *file, int c)
+{
+    _deflate_decode_data_t *data = file->data;
+    data->bytes_available++;
+    data->bp--;
+}
+
+static int
+_deflate_decode_read (csi_file_t *file, uint8_t *buf, int len)
+{
+    _deflate_decode_data_t *data = file->data;
+
+    if (data->bytes_available == 0) {
+	_deflate_decode (file);
+
+	if (data->bytes_available == 0)
+	    return 0;
+    }
+
+    if (len > (int) data->bytes_available)
+	len = data->bytes_available;
+    memcpy (buf, data->bp, len);
+    data->bp += len;
+    data->bytes_available -= len;
+    return len;
+}
+
+static void
+_deflate_destroy (csi_t *ctx, void *closure)
+{
+    _deflate_decode_data_t *data;
+
+    data = closure;
+
+    inflateEnd (&data->zlib_stream);
+
+    _csi_free (ctx, data);
+}
+
+csi_status_t
+csi_file_new_deflate_decode (csi_t *ctx,
+			     csi_object_t *obj,
+			     csi_dictionary_t *dict,
+			     csi_object_t *src)
+{
+    static const csi_filter_funcs_t funcs = {
+	_deflate_decode_getc,
+	_deflate_decode_putc,
+	_deflate_decode_read,
+	_deflate_destroy,
+    };
+    _deflate_decode_data_t *data;
+
+    data = _csi_alloc (ctx, sizeof (_deflate_decode_data_t));
+    if (data == NULL)
+	return _csi_error (CAIRO_STATUS_NO_MEMORY);
+
+    data->zlib_stream.zalloc = Z_NULL;
+    data->zlib_stream.zfree = Z_NULL;
+    data->zlib_stream.opaque = Z_NULL;
+    data->zlib_stream.next_in = data->in;
+    data->zlib_stream.avail_in = 0;
+    data->zlib_stream.next_out = data->out;
+    data->zlib_stream.avail_out = sizeof (data->out);
+    data->bytes_available = 0;
+
+    if (inflateInit (&data->zlib_stream) != Z_OK) {
+	_csi_free (ctx, data);
+	return _csi_error (CAIRO_STATUS_NO_MEMORY);
+    }
+
+    return _csi_file_new_filter (ctx, obj, src, &funcs, data);
+}
+
+#if 0
+static int
+hex_value (int c)
+{
+    if (c < '0')
+	return EOF;
+    if (c <= '9')
+	return c - '0';
+    c |= 32;
+    if (c < 'a')
+	return EOF;
+    if (c <= 'f')
+	return c - 'a' + 0xa;
+    return EOF;
+}
+
+/* Adobe Type 1 Font Format book: p63 */
+typedef struct _decrypt_data {
+    uint8_t putback[32];
+    uint8_t nputback;
+    csi_bool_t is_hexadecimal;
+    unsigned short R;
+    int eod;
+} _decrypt_data_t;
+
+static uint8_t
+_decrypt (unsigned short *R, uint8_t cypher)
+{
+#define c1 52845
+#define c2 22719
+    uint8_t plain;
+
+    plain = cypher ^ (*R >> 8);
+    *R = (cypher + *R) * c1 + c2;
+    return plain;
+#undef c1
+#undef c2
+}
+
+int
+csi_decrypt (uint8_t *in, int length,
+	     unsigned short salt, int binary,
+	     uint8_t *out)
+{
+    const uint8_t * const end = in + length;
+    uint8_t *base = out;
+
+    while (in < end) {
+	int c;
+
+	if (! binary) {
+	    int c_hi = -1, c_lo = 0;
+
+	    while (in < end && (c_hi = *in++)) {
+		switch (c_hi) {
+		case 0x0:
+		case 0x9:
+		case 0xa:
+		case 0xc:
+		case 0xd:
+		case 0x20:
+		    continue;
+
+		default:
+		    break;
+		}
+	    }
+	    if (c_hi < 0)
+		break;
+
+	    while (in < end && (c_lo = *in++)) {
+		switch (c_lo) {
+		case 0x0:
+		case 0x9:
+		case 0xa:
+		case 0xc:
+		case 0xd:
+		case 0x20:
+		    continue;
+
+		default:
+		    break;
+		}
+	    }
+
+	    c = (hex_value (c_hi) << 4) | hex_value (c_lo);
+	} else
+	    c = *in++;
+
+	*out++ = _decrypt (&salt, c);
+    }
+
+    return out - base;
+}
+
+static uint8_t
+_encrypt (unsigned short *R, uint8_t plain)
+{
+#define c1 52845
+#define c2 22719
+    uint8_t cypher;
+
+    cypher = plain ^ (*R >> 8);
+    *R = (cypher + *R) * c1 + c2;
+    return cypher;
+#undef c1
+#undef c2
+}
+
+int
+csi_encrypt (uint8_t *in, int length,
+	     unsigned short salt, int discard, int binary,
+	     uint8_t *out)
+{
+    const char hex[]="0123456789abcdef";
+    const uint8_t * const end = in + length;
+    uint8_t *base = out;
+    int col = 0;
+
+    while (discard--) {
+	if (! binary) {
+	    int c = _encrypt (&salt, ' ');
+	    *out++ = hex[(c >> 4) & 0xf];
+	    *out++ = hex[(c >> 0) & 0xf];
+	} else
+	    *out++ = _encrypt (&salt, 0);
+    }
+
+    while (in < end) {
+	int c;
+
+	c = _encrypt (&salt, *in++);
+	if (! binary) {
+	    if (col == 78) {
+		*out++ = '\n';
+		col = 0;
+	    }
+	    *out++ = hex[(c >> 4) & 0xf];
+	    *out++ = hex[(c >> 0) & 0xf];
+	    col += 2;
+	} else
+	    *out++ = c;
+    }
+
+    return out - base;
+}
+
+static int
+_decrypt_getc (csi_file_t *file)
+{
+    _decrypt_data_t *data = file->data;
+    int c;
+
+    if (data->nputback)
+	return data->putback[--data->nputback];
+
+    if (data->is_hexadecimal) {
+	int c_hi, c_lo;
+
+	c_hi = _getc_skip_whitespace (file->src);
+	c_lo = _getc_skip_whitespace (file->src);
+	c = (hex_value (c_hi) << 4) | hex_value (c_lo);
+    } else
+	c = csi_file_getc (file->src);
+
+    if (c == EOF)
+	return EOF;
+
+    return _decrypt (&data->R, c);
+}
+
+static void
+_decrypt_putc (csi_file_t *file, int c)
+{
+    _decrypt_data_t *data;
+
+    data = file->data;
+
+    data->putback[data->nputback++] = c;
+}
+
+csi_object_t *
+csi_file_new_decrypt (csi_t *ctx, csi_object_t *src, int salt, int discard)
+{
+    csi_object_t *obj;
+    _decrypt_data_t *data;
+    int n;
+
+    data = _csi_alloc0 (ctx, sizeof (_decrypt_data_t));
+    if (data == NULL)
+	return NULL;
+
+    data->R = salt;
+
+    obj = _csi_file_new_filter (ctx, src,
+				_decrypt_getc,
+				_decrypt_putc,
+				NULL,
+				_csi_free,
+				data);
+    if (obj == NULL)
+	return NULL;
+
+    /* XXX determine encoding, eexec only? */
+    data->is_hexadecimal = salt != 4330;
+    for (n = 0; n < discard; n++) {
+	int c;
+	c = csi_file_getc (obj);
+	if (c == EOF) {
+	    return obj;
+	}
+    }
+    return obj;
+}
+#endif
+
+csi_status_t
+_csi_file_execute (csi_t *ctx, csi_file_t *obj)
+{
+    return _csi_scan_file (ctx, &ctx->scanner, obj);
+}
+
+int
+csi_file_getc (csi_file_t *file)
+{
+    int c;
+
+    if (_csi_unlikely (file->src == NULL))
+	return EOF;
+
+    switch (file->type) {
+    case STDIO:
+	if (_csi_likely (file->rem)) {
+	    c = *file->bp++;
+	    file->rem--;
+	} else {
+	    file->rem = fread (file->bp = file->data, 1, CHUNK_SIZE, file->src);
+    case BYTES:
+	    if (_csi_likely (file->rem)) {
+		c = *file->bp++;
+		file->rem--;
+	    } else
+		c = EOF;
+	}
+	break;
+
+    case PROCEDURE:
+#if 0
+	if (file->data == NULL) {
+	    csi_status_t status;
+	    csi_object_t *string;
+
+RERUN_PROCEDURE:
+	    status = csi_object_execute (file->src);
+	    if (status)
+		return EOF;
+
+	    string = csi_pop_operand (file->base.ctx);
+	    if (string == NULL)
+		return EOF;
+	    file->data = csi_object_as_file (file->base.ctx, string);
+	    csi_object_free (string);
+	    if (file->data == NULL)
+		return EOF;
+	}
+	c = csi_file_getc (file->data);
+	if (c == EOF) {
+	    csi_object_free (file->data);
+	    file->data = NULL;
+	    goto RERUN_PROCEDURE;
+	}
+#else
+	c = EOF;
+#endif
+	break;
+
+    case FILTER:
+	c = file->filter->filter_getc (file);
+	break;
+
+    default:
+	c = EOF;
+	break;
+    }
+
+    return c;
+}
+
+int
+csi_file_read (csi_file_t *file, void *buf, int len)
+{
+    int ret;
+
+    if (file->src == NULL)
+	return 0;
+
+    switch (file->type) {
+    case STDIO:
+	if (file->rem > 0) {
+	    ret = len;
+	    if (file->rem < ret)
+		ret = file->rem;
+	    memcpy (buf, file->bp, ret);
+	    file->bp  += ret;
+	    file->rem -= ret;
+	} else
+	    ret = fread (buf, 1, len, file->src);
+	break;
+
+    case BYTES:
+	if (file->rem > 0) {
+	    ret = len;
+	    if (file->rem < ret)
+		ret = file->rem;
+	    memcpy (buf, file->bp, ret);
+	    file->bp  += ret;
+	    file->rem -= ret;
+	} else
+	    ret = 0;
+	break;
+
+    case PROCEDURE:
+#if 0
+	if (file->data == NULL) {
+	    csi_status_t status;
+	    csi_object_t *string;
+
+RERUN_PROCEDURE:
+	    status = csi_object_execute (file->src);
+	    if (status)
+		return 0;
+
+	    string = csi_pop_operand (file->base.ctx);
+	    if (string == NULL)
+		return 0;
+	    file->data = csi_object_as_file (file->base.ctx, string);
+	    csi_object_free (string);
+	    if (file->data == NULL)
+		return 0;
+	}
+	ret = csi_file_read (file->data, buf, len);
+	if (ret == 0) {
+	    csi_object_free (file->data);
+	    file->data = NULL;
+	    goto RERUN_PROCEDURE;
+	}
+#else
+	ret = 0;
+#endif
+	break;
+
+    case FILTER:
+	ret = file->filter->filter_read (file, buf, len);
+	break;
+
+    default:
+	ret = 0;
+	break;
+    }
+
+    return ret;
+}
+
+void
+csi_file_putc (csi_file_t *file, int c)
+{
+    if (file->src == NULL)
+	return;
+
+    switch ((int) file->type) {
+    case STDIO:
+    case BYTES:
+	file->bp--;
+	file->rem++;
+	break;
+    case FILTER:
+	file->filter->filter_putc (file, c);
+	break;
+    default:
+	break;
+    }
+}
+
+void
+csi_file_flush (csi_file_t *file)
+{
+    int c;
+
+    if (file->src == NULL)
+	return;
+
+    switch ((int) file->type) {
+    case FILTER: /* need to eat EOD */
+	while ((c = csi_file_getc (file)) != EOF)
+	    ;
+	break;
+    default:
+	break;
+    }
+}
+
+void
+csi_file_close (csi_t *ctx, csi_file_t *file)
+{
+    if (file->src == NULL)
+	return;
+
+    switch (file->type) {
+    case STDIO:
+	fclose (file->src);
+	break;
+    case BYTES:
+	if (file->src != file->data) {
+	    csi_string_t *src = file->src;
+	    if (src != NULL && --src->base.ref == 0)
+		csi_string_free (ctx, src);
+	}
+	break;
+    case FILTER:
+	{
+	    csi_file_t *src = file->src;
+	    if (src != NULL && --src->base.ref == 0)
+		_csi_file_free (ctx, src);
+	}
+	break;
+    case PROCEDURE:
+    default:
+	break;
+    }
+    file->src = NULL;
+}
+
+void
+_csi_file_free (csi_t *ctx, csi_file_t *file)
+{
+    csi_file_flush (file);
+    /* XXX putback */
+    csi_file_close (ctx, file);
+
+    switch (file->type) {
+    case BYTES:
+	break;
+    case PROCEDURE:
+#if 0
+	csi_object_free (ctx, file->data);
+#endif
+	break;
+    case STDIO:
+	_csi_free (ctx, file->data);
+	break;
+    case FILTER:
+	file->filter->filter_destroy (ctx, file->data);
+	break;
+    default:
+	break;
+    }
+
+    _csi_slab_free (ctx, file, sizeof (csi_file_t));
+}
+
+csi_status_t
+_csi_file_as_string (csi_t *ctx,
+		     csi_file_t *file,
+		     csi_object_t *obj)
+{
+    char *bytes;
+    unsigned int len;
+    unsigned int allocated;
+    csi_status_t status;
+
+    len = 0;
+    allocated = 16384;
+    bytes = _csi_alloc (ctx, allocated);
+    if (bytes == NULL)
+	return _csi_error (CAIRO_STATUS_NO_MEMORY);
+
+    len = 0;
+    do {
+	int ret;
+
+	ret = csi_file_read (file, bytes + len, allocated - len);
+	if (ret == 0)
+	    break;
+
+	len += ret;
+	if (len > allocated / 2) {
+	    char *newbytes;
+	    int newlen;
+
+	    if (_csi_unlikely (allocated > INT32_MAX / 2))
+		return _csi_error (CAIRO_STATUS_NO_MEMORY);
+
+	    newlen = allocated * 2;
+	    newbytes = _csi_realloc (ctx, bytes, newlen);
+	    if (_csi_unlikely (newbytes == NULL)) {
+		_csi_free (ctx, bytes);
+		return _csi_error (CAIRO_STATUS_NO_MEMORY);
+	    }
+	    bytes = newbytes;
+	    allocated = newlen;
+	}
+    } while (TRUE);
+
+    status = csi_string_new (ctx, obj, bytes, len);
+    if (status) {
+	_csi_free (ctx, bytes);
+	return status;
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
diff --git a/util/cairo-script/cairo-script-hash.c b/util/cairo-script/cairo-script-hash.c
new file mode 100644
index 0000000..c1e6bc2
--- /dev/null
+++ b/util/cairo-script/cairo-script-hash.c
@@ -0,0 +1,448 @@
+/* cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2004 Red Hat, Inc.
+ * Copyright © 2005 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Red Hat, Inc.
+ *
+ * Contributor(s):
+ *      Keith Packard <keithp at keithp.com>
+ *	Graydon Hoare <graydon at redhat.com>
+ *	Carl Worth <cworth at cworth.org>
+ */
+
+#include "cairo-script-private.h"
+
+#include <stdlib.h>
+
+/*
+ * An entry can be in one of three states:
+ *
+ * FREE: Entry has never been used, terminates all searches.
+ *       Appears in the table as a %NULL pointer.
+ *
+ * DEAD: Entry had been live in the past. A dead entry can be reused
+ *       but does not terminate a search for an exact entry.
+ *       Appears in the table as a pointer to DEAD_ENTRY.
+ *
+ * LIVE: Entry is currently being used.
+ *       Appears in the table as any non-%NULL, non-DEAD_ENTRY pointer.
+ */
+
+#define DEAD_ENTRY ((csi_hash_entry_t *) 0x1)
+
+#define ENTRY_IS_FREE(entry) ((entry) == NULL)
+#define ENTRY_IS_DEAD(entry) ((entry) == DEAD_ENTRY)
+#define ENTRY_IS_LIVE(entry) ((entry) >  DEAD_ENTRY)
+
+/* We expect keys will not be destroyed frequently, so our table does not
+ * contain any explicit shrinking code nor any chain-coalescing code for
+ * entries randomly deleted by memory pressure (except during rehashing, of
+ * course). These assumptions are potentially bad, but they make the
+ * implementation straightforward.
+ *
+ * Revisit later if evidence appears that we're using excessive memory from
+ * a mostly-dead table.
+ *
+ * This table is open-addressed with double hashing. Each table size is a
+ * prime chosen to be a little more than double the high water mark for a
+ * given arrangement, so the tables should remain < 50% full. The table
+ * size makes for the "first" hash modulus; a second prime (2 less than the
+ * first prime) serves as the "second" hash modulus, which is co-prime and
+ * thus guarantees a complete permutation of table indices.
+ *
+ * This structure, and accompanying table, is borrowed/modified from the
+ * file xserver/render/glyph.c in the freedesktop.org x server, with
+ * permission (and suggested modification of doubling sizes) by Keith
+ * Packard.
+ */
+
+static const csi_hash_table_arrangement_t hash_table_arrangements [] = {
+    { 16,		43,		41		},
+    { 32,		73,		71		},
+    { 64,		151,		149		},
+    { 128,		283,		281		},
+    { 256,		571,		569		},
+    { 512,		1153,		1151		},
+    { 1024,		2269,		2267		},
+    { 2048,		4519,		4517		},
+    { 4096,		9013,		9011		},
+    { 8192,		18043,		18041		},
+    { 16384,		36109,		36107		},
+    { 32768,		72091,		72089		},
+    { 65536,		144409,		144407		},
+    { 131072,		288361,		288359		},
+    { 262144,		576883,		576881		},
+    { 524288,		1153459,	1153457		},
+    { 1048576,		2307163,	2307161		},
+    { 2097152,		4613893,	4613891		},
+    { 4194304,		9227641,	9227639		},
+    { 8388608,		18455029,	18455027	},
+    { 16777216,		36911011,	36911009	},
+    { 33554432,		73819861,	73819859	},
+    { 67108864,		147639589,	147639587	},
+    { 134217728,	295279081,	295279079	},
+    { 268435456,	590559793,	590559791	}
+};
+
+#define NUM_HASH_TABLE_ARRANGEMENTS ARRAY_LENGTH (hash_table_arrangements)
+
+/**
+ * _csi_hash_table_create:
+ * @keys_equal: a function to return %TRUE if two keys are equal
+ *
+ * Creates a new hash table which will use the keys_equal() function
+ * to compare hash keys. Data is provided to the hash table in the
+ * form of user-derived versions of #csi_hash_entry_t. A hash entry
+ * must be able to hold both a key (including a hash code) and a
+ * value. Sometimes only the key will be necessary, (as in
+ * _csi_hash_table_remove), and other times both a key and a value
+ * will be necessary, (as in _csi_hash_table_insert).
+ *
+ * See #csi_hash_entry_t for more details.
+ *
+ * Return value: the new hash table or %NULL if out of memory.
+ **/
+csi_status_t
+_csi_hash_table_init (csi_hash_table_t *hash_table,
+		      csi_hash_keys_equal_func_t keys_equal)
+{
+    hash_table->keys_equal = keys_equal;
+
+    hash_table->arrangement = &hash_table_arrangements[0];
+
+    hash_table->entries = calloc (hash_table->arrangement->size,
+				  sizeof(csi_hash_entry_t *));
+    if (hash_table->entries == NULL)
+	return _csi_error (CAIRO_STATUS_NO_MEMORY);
+
+    hash_table->live_entries = 0;
+    hash_table->iterating = 0;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+/**
+ * _csi_hash_table_destroy:
+ * @hash_table: an empty hash table to destroy
+ *
+ * Immediately destroys the given hash table, freeing all resources
+ * associated with it.
+ *
+ * WARNING: The hash_table must have no live entries in it before
+ * _csi_hash_table_destroy is called. It is a fatal error otherwise,
+ * and this function will halt. The rationale for this behavior is to
+ * avoid memory leaks and to avoid needless complication of the API
+ * with destroy notifiy callbacks.
+ *
+ * WARNING: The hash_table must have no running iterators in it when
+ * _csi_hash_table_destroy is called. It is a fatal error otherwise,
+ * and this function will halt.
+ **/
+void
+_csi_hash_table_fini (csi_hash_table_t *hash_table)
+{
+    free (hash_table->entries);
+}
+
+static csi_hash_entry_t **
+_csi_hash_table_lookup_unique_key (csi_hash_table_t *hash_table,
+				     csi_hash_entry_t *key)
+{
+    unsigned long table_size, i, idx, step;
+    csi_hash_entry_t **entry;
+
+    table_size = hash_table->arrangement->size;
+    idx = key->hash % table_size;
+
+    entry = &hash_table->entries[idx];
+    if (! ENTRY_IS_LIVE (*entry))
+	return entry;
+
+    i = 1;
+    step = key->hash % hash_table->arrangement->rehash;
+    if (step == 0)
+	step = 1;
+    do {
+	idx += step;
+	if (idx >= table_size)
+	    idx -= table_size;
+
+	entry = &hash_table->entries[idx];
+	if (! ENTRY_IS_LIVE (*entry))
+	    return entry;
+    } while (++i < table_size);
+
+    return NULL;
+}
+
+/**
+ * _csi_hash_table_resize:
+ * @hash_table: a hash table
+ *
+ * Resize the hash table if the number of entries has gotten much
+ * bigger or smaller than the ideal number of entries for the current
+ * size.
+ *
+ * Return value: %CAIRO_STATUS_SUCCESS if successful or
+ * %CAIRO_STATUS_NO_MEMORY if out of memory.
+ **/
+static csi_status_t
+_csi_hash_table_resize (csi_hash_table_t *hash_table)
+{
+    csi_hash_table_t tmp;
+    unsigned long new_size, i;
+
+    /* This keeps the hash table between 25% and 50% full. */
+    unsigned long high = hash_table->arrangement->high_water_mark;
+    unsigned long low = high >> 2;
+
+    if (hash_table->live_entries >= low && hash_table->live_entries <= high)
+	return CAIRO_STATUS_SUCCESS;
+
+    tmp = *hash_table;
+
+    if (hash_table->live_entries > high) {
+	tmp.arrangement = hash_table->arrangement + 1;
+	/* This code is being abused if we can't make a table big enough. */
+    } else { /* hash_table->live_entries < low */
+	/* Can't shrink if we're at the smallest size */
+	if (hash_table->arrangement == &hash_table_arrangements[0])
+	    return CAIRO_STATUS_SUCCESS;
+	tmp.arrangement = hash_table->arrangement - 1;
+    }
+
+    new_size = tmp.arrangement->size;
+    tmp.entries = calloc (new_size, sizeof (csi_hash_entry_t*));
+    if (tmp.entries == NULL)
+	return _csi_error (CAIRO_STATUS_NO_MEMORY);
+
+    for (i = 0; i < hash_table->arrangement->size; ++i) {
+	if (ENTRY_IS_LIVE (hash_table->entries[i])) {
+	    *_csi_hash_table_lookup_unique_key (&tmp, hash_table->entries[i])
+		= hash_table->entries[i];
+	}
+    }
+
+    free (hash_table->entries);
+    hash_table->entries = tmp.entries;
+    hash_table->arrangement = tmp.arrangement;
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+/**
+ * _csi_hash_table_lookup:
+ * @hash_table: a hash table
+ * @key: the key of interest
+ *
+ * Performs a lookup in @hash_table looking for an entry which has a
+ * key that matches @key, (as determined by the keys_equal() function
+ * passed to _csi_hash_table_create).
+ *
+ * Return value: the matching entry, of %NULL if no match was found.
+ **/
+void *
+_csi_hash_table_lookup (csi_hash_table_t *hash_table,
+			csi_hash_entry_t *key)
+{
+    csi_hash_entry_t **entry;
+    unsigned long table_size, i, idx, step;
+
+    table_size = hash_table->arrangement->size;
+    idx = key->hash % table_size;
+    entry = &hash_table->entries[idx];
+
+    if (ENTRY_IS_LIVE (*entry)) {
+	if (hash_table->keys_equal (key, *entry))
+	    return *entry;
+    } else if (ENTRY_IS_FREE (*entry))
+	return NULL;
+
+    i = 1;
+    step = key->hash % hash_table->arrangement->rehash;
+    if (step == 0)
+	step = 1;
+    do {
+	idx += step;
+	if (idx >= table_size)
+	    idx -= table_size;
+
+	entry = &hash_table->entries[idx];
+	if (ENTRY_IS_LIVE (*entry)) {
+	    if (hash_table->keys_equal (key, *entry))
+		return *entry;
+	} else if (ENTRY_IS_FREE (*entry))
+	    return NULL;
+    } while (++i < table_size);
+
+    return NULL;
+}
+
+/**
+ * _csi_hash_table_insert:
+ * @hash_table: a hash table
+ * @key_and_value: an entry to be inserted
+ *
+ * Insert the entry #key_and_value into the hash table.
+ *
+ * WARNING: There must not be an existing entry in the hash table
+ * with a matching key.
+ *
+ * WARNING: It is a fatal error to insert an element while
+ * an iterator is running
+ *
+ * Instead of using insert to replace an entry, consider just editing
+ * the entry obtained with _csi_hash_table_lookup. Or if absolutely
+ * necessary, use _csi_hash_table_remove first.
+ *
+ * Return value: %CAIRO_STATUS_SUCCESS if successful or
+ * %CAIRO_STATUS_NO_MEMORY if insufficient memory is available.
+ **/
+csi_status_t
+_csi_hash_table_insert (csi_hash_table_t *hash_table,
+			  csi_hash_entry_t *key_and_value)
+{
+    csi_status_t status;
+
+    hash_table->live_entries++;
+    status = _csi_hash_table_resize (hash_table);
+    if (_csi_unlikely (status)) {
+	/* abort the insert... */
+	hash_table->live_entries--;
+	return status;
+    }
+
+    *_csi_hash_table_lookup_unique_key (hash_table,
+					  key_and_value) = key_and_value;
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static csi_hash_entry_t **
+_csi_hash_table_lookup_exact_key (csi_hash_table_t *hash_table,
+				    csi_hash_entry_t *key)
+{
+    unsigned long table_size, i, idx, step;
+    csi_hash_entry_t **entry;
+
+    table_size = hash_table->arrangement->size;
+    idx = key->hash % table_size;
+
+    entry = &hash_table->entries[idx];
+    if (*entry == key)
+	return entry;
+
+    i = 1;
+    step = key->hash % hash_table->arrangement->rehash;
+    if (step == 0)
+	step = 1;
+    do {
+	idx += step;
+	if (idx >= table_size)
+	    idx -= table_size;
+
+	entry = &hash_table->entries[idx];
+	if (*entry == key)
+	    return entry;
+    } while (++i < table_size);
+
+    return NULL;
+}
+/**
+ * _csi_hash_table_remove:
+ * @hash_table: a hash table
+ * @key: key of entry to be removed
+ *
+ * Remove an entry from the hash table which points to @key.
+ *
+ * Return value: %CAIRO_STATUS_SUCCESS if successful or
+ * %CAIRO_STATUS_NO_MEMORY if out of memory.
+ **/
+void
+_csi_hash_table_remove (csi_hash_table_t *hash_table,
+			  csi_hash_entry_t *key)
+{
+    *_csi_hash_table_lookup_exact_key (hash_table, key) = DEAD_ENTRY;
+    hash_table->live_entries--;
+
+    /* Check for table resize. Don't do this when iterating as this will
+     * reorder elements of the table and cause the iteration to potentially
+     * skip some elements. */
+    if (hash_table->iterating == 0) {
+	/* This call _can_ fail, but only in failing to allocate new
+	 * memory to shrink the hash table. It does leave the table in a
+	 * consistent state, and we've already succeeded in removing the
+	 * entry, so we don't examine the failure status of this call. */
+	_csi_hash_table_resize (hash_table);
+    }
+}
+
+/**
+ * _csi_hash_table_foreach:
+ * @hash_table: a hash table
+ * @hash_callback: function to be called for each live entry
+ * @closure: additional argument to be passed to @hash_callback
+ *
+ * Call @hash_callback for each live entry in the hash table, in a
+ * non-specified order.
+ *
+ * Entries in @hash_table may be removed by code executed from @hash_callback.
+ *
+ * Entries may not be inserted to @hash_table, nor may @hash_table
+ * be destroyed by code executed from @hash_callback. The relevant
+ * functions will halt in these cases.
+ **/
+void
+_csi_hash_table_foreach (csi_hash_table_t	      *hash_table,
+			 csi_hash_callback_func_t  hash_callback,
+			 void			      *closure)
+{
+    unsigned long i;
+    csi_hash_entry_t *entry;
+
+    if (hash_table == NULL)
+	return;
+
+    /* Mark the table for iteration */
+    ++hash_table->iterating;
+    for (i = 0; i < hash_table->arrangement->size; i++) {
+	entry = hash_table->entries[i];
+	if (ENTRY_IS_LIVE(entry))
+	    hash_callback (entry, closure);
+    }
+    /* If some elements were deleted during the iteration,
+     * the table may need resizing. Just do this every time
+     * as the check is inexpensive.
+     */
+    if (--hash_table->iterating == 0) {
+	/* Should we fail to shrink the hash table, it is left unaltered,
+	 * and we don't need to propagate the error status. */
+	_csi_hash_table_resize (hash_table);
+    }
+}
diff --git a/util/cairo-script/cairo-script-interpreter.c b/util/cairo-script/cairo-script-interpreter.c
new file mode 100644
index 0000000..2163046
--- /dev/null
+++ b/util/cairo-script/cairo-script-interpreter.c
@@ -0,0 +1,473 @@
+/*
+ * Copyright © 2008 Chris Wilson <chris at chris-wilson.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Chris Wilson.
+ *
+ * Contributor(s):
+ *	Chris Wilson <chris at chris-wilson.co.uk>
+ */
+
+#include <cairo.h>
+
+#include "cairo-script-private.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <byteswap.h>
+#include <math.h>
+
+csi_status_t
+_csi_error (csi_status_t status)
+{
+    return status;
+}
+
+/* XXX track global/local memory, cap etc, mark/sweep GC */
+void *
+_csi_alloc (csi_t *ctx, int size)
+{
+    if (_csi_unlikely (ctx->status))
+	return NULL;
+
+    return malloc (size);
+}
+
+void *
+_csi_alloc0 (csi_t *ctx, int size)
+{
+    void *ptr;
+
+    ptr = _csi_alloc (ctx, size);
+    if (_csi_likely (ptr != NULL))
+	memset (ptr, 0, size);
+
+    return ptr;
+}
+
+void *
+_csi_realloc (csi_t *ctx, void *ptr, int size)
+{
+    if (_csi_unlikely (ctx->status))
+	return NULL;
+
+    return realloc (ptr, size);
+}
+
+void
+_csi_free (csi_t *ctx, void *ptr)
+{
+    if (_csi_unlikely (ptr == NULL))
+	return;
+
+    free (ptr);
+}
+
+void *
+_csi_slab_alloc (csi_t *ctx, int size)
+{
+    if (_csi_unlikely (ctx->status))
+	return NULL;
+
+    return malloc (size);
+}
+
+void
+_csi_slab_free (csi_t *ctx, void *ptr, int size)
+{
+    if (_csi_unlikely (ptr == NULL))
+	return;
+
+    free (ptr);
+}
+
+static csi_status_t
+_add_operator (csi_t *ctx,
+	       csi_dictionary_t *dict,
+	       const csi_operator_def_t *def)
+{
+    csi_object_t name;
+    csi_object_t operator;
+    csi_status_t status;
+
+    status = csi_name_new_static (ctx, &name, def->name);
+    if (status)
+	return status;
+
+    status = csi_operator_new (ctx, &operator, def->op);
+    if (status)
+	return status;
+
+    return csi_dictionary_put (ctx, dict, name.datum.name, &operator);
+}
+
+static csi_status_t
+_add_integer_constant (csi_t *ctx,
+		       csi_dictionary_t *dict,
+		       const csi_integer_constant_def_t *def)
+{
+    csi_object_t name;
+    csi_object_t constant;
+    csi_status_t status;
+
+    status = csi_name_new_static (ctx, &name, def->name);
+    if (status)
+	return status;
+
+    status = csi_integer_new (ctx, &constant, def->value);
+    if (status)
+	return status;
+
+    return csi_dictionary_put (ctx, dict, name.datum.name, &constant);
+}
+
+static csi_status_t
+_init_dictionaries (csi_t *ctx)
+{
+    csi_status_t status;
+    csi_stack_t *stack;
+    csi_object_t obj;
+    csi_dictionary_t *dict;
+    const csi_operator_def_t *odef;
+    const csi_integer_constant_def_t *idef;
+
+    stack = &ctx->dstack;
+
+    status = _csi_stack_init (ctx, stack, 4);
+    if (status)
+	return status;
+
+    /* systemdict */
+    status = csi_dictionary_new (ctx, &obj);
+    if (status)
+	return status;
+
+    status = _csi_stack_push (ctx, stack, &obj);
+    if (status)
+	return status;
+
+    dict = obj.datum.dictionary;
+
+    /* fill systemdict with operators */
+    for (odef = _csi_operators (); odef->name != NULL; odef++) {
+	status = _add_operator (ctx, dict, odef);
+	if (status)
+	    return status;
+    }
+
+    /* add constants */
+    for (idef = _csi_integer_constants (); idef->name != NULL; idef++) {
+	status = _add_integer_constant (ctx, dict, idef);
+	if (status)
+	    return status;
+    }
+
+    /* and seal */
+    //dict.type &= ~CSI_OBJECT_ATTR_WRITABLE;
+
+
+    /* globaldict */
+    status = csi_dictionary_new (ctx, &obj);
+    if (status)
+	return status;
+    status = _csi_stack_push (ctx, stack, &obj);
+    if (status)
+	return status;
+
+    /* userdict */
+    status = csi_dictionary_new (ctx, &obj);
+    if (status)
+	return status;
+    status = _csi_stack_push (ctx, stack, &obj);
+    if (status)
+	return status;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+/* intern string */
+
+typedef struct _cairo_intern_string {
+    csi_hash_entry_t hash_entry;
+    int len;
+    char *string;
+} csi_intern_string_t;
+
+static unsigned long
+_intern_string_hash (const char *str, int len)
+{
+    const signed char *p = (const signed char *) str;
+    unsigned int h = *p;
+
+    for (p += 1; --len; p++)
+	h = (h << 5) - h + *p;
+
+    return h;
+}
+
+static cairo_bool_t
+_intern_string_equal (const void *_a, const void *_b)
+{
+    const csi_intern_string_t *a = _a;
+    const csi_intern_string_t *b = _b;
+
+    if (a->len != b->len)
+	return FALSE;
+
+    return memcmp (a->string, b->string, a->len) == 0;
+}
+
+static void
+_csi_init (csi_t *ctx)
+{
+    csi_status_t status;
+
+    ctx->status = CSI_STATUS_SUCCESS;
+    ctx->ref_count = 1;
+
+    status = _csi_hash_table_init (&ctx->strings, _intern_string_equal);
+    if (status)
+	goto FAIL;
+
+    status = _csi_stack_init (ctx, &ctx->ostack, 2048);
+    if (status)
+	goto FAIL;
+    status = _init_dictionaries (ctx);
+    if (status)
+	goto FAIL;
+
+    status = _csi_scanner_init (ctx, &ctx->scanner);
+    if (status)
+	goto FAIL;
+
+    return;
+
+FAIL:
+    if (ctx->status == CSI_STATUS_SUCCESS)
+	ctx->status = status;
+}
+
+static void
+_intern_string_pluck (void *entry, void *closure)
+{
+    csi_t *ctx = closure;
+
+    _csi_hash_table_remove (&ctx->strings, entry);
+    _csi_free (ctx, entry);
+}
+
+static void
+_csi_fini (csi_t *ctx)
+{
+    _csi_stack_fini (ctx, &ctx->ostack);
+    _csi_stack_fini (ctx, &ctx->dstack);
+    _csi_scanner_fini (ctx, &ctx->scanner);
+
+    _csi_hash_table_foreach (&ctx->strings, _intern_string_pluck, ctx);
+    _csi_hash_table_fini (&ctx->strings);
+}
+
+csi_status_t
+_csi_name_define (csi_t *ctx, csi_name_t name, csi_object_t *obj)
+{
+    return csi_dictionary_put (ctx,
+			ctx->dstack.objects[ctx->dstack.len-1].datum.dictionary,
+			name,
+			obj);
+}
+
+csi_status_t
+_csi_name_lookup (csi_t *ctx, csi_name_t name, csi_object_t *obj)
+{
+    int i;
+
+    for (i = ctx->dstack.len; i--; ) {
+	csi_dictionary_t *dict;
+	csi_dictionary_entry_t *entry;
+
+	dict = ctx->dstack.objects[i].datum.dictionary;
+	entry = _csi_hash_table_lookup (&dict->hash_table,
+					(csi_hash_entry_t *) &name);
+	if (entry != NULL) {
+	    *obj = entry->value;
+	    return CSI_STATUS_SUCCESS;
+	}
+    }
+
+    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+}
+
+csi_status_t
+_csi_name_undefine (csi_t *ctx, csi_name_t name)
+{
+    unsigned int i;
+
+    for (i = ctx->dstack.len; --i; ) {
+	if (csi_dictionary_has (ctx->dstack.objects[i].datum.dictionary,
+				name))
+	{
+	    csi_dictionary_remove (ctx,
+				   ctx->dstack.objects[i].datum.dictionary,
+				   name);
+	    return CSI_STATUS_SUCCESS;
+	}
+    }
+
+    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+}
+
+csi_status_t
+_csi_intern_string (csi_t *ctx, const char **str_inout, int len)
+{
+    char *str = (char *) *str_inout;
+    csi_intern_string_t tmpl, *istring;
+    csi_status_t status = CSI_STATUS_SUCCESS;
+
+    if (len < 0)
+	len = strlen (str);
+    tmpl.hash_entry.hash = _intern_string_hash (str, len);
+    tmpl.len = len;
+    tmpl.string = (char *) str;
+
+    istring = _csi_hash_table_lookup (&ctx->strings, &tmpl.hash_entry);
+    if (istring == NULL) {
+	istring = _csi_alloc (ctx, sizeof (csi_intern_string_t) + len + 1);
+	if (istring != NULL) {
+	    istring->hash_entry.hash = tmpl.hash_entry.hash;
+	    istring->len = tmpl.len;
+	    istring->string = (char *) (istring + 1);
+	    memcpy (istring->string, str, len);
+	    istring->string[len] = '\0';
+
+	    status = _csi_hash_table_insert (&ctx->strings,
+					     &istring->hash_entry);
+	    if (_csi_unlikely (status)) {
+		_csi_free (ctx, istring);
+		return status;
+	    }
+	} else
+	    return _csi_error (CSI_STATUS_NO_MEMORY);
+    }
+
+    *str_inout = istring->string;
+    return CSI_STATUS_SUCCESS;
+}
+
+/* Public */
+
+static csi_t _csi_nil = { -1, CSI_STATUS_NO_MEMORY };
+
+csi_t *
+cairo_script_interpreter_create (void)
+{
+    csi_t *ctx;
+
+    ctx = calloc (1, sizeof (csi_t));
+    if (ctx == NULL)
+	return (csi_t *) &_csi_nil;
+
+    _csi_init (ctx);
+
+    return ctx;
+}
+
+void
+cairo_script_interpreter_install_hooks (csi_t *ctx,
+					const csi_hooks_t *hooks)
+{
+    if (ctx->status)
+	return;
+
+    ctx->hooks = *hooks;
+}
+
+cairo_status_t
+cairo_script_interpreter_run (csi_t *ctx, const char *filename)
+{
+    csi_object_t file;
+
+    if (ctx->status)
+	return ctx->status;
+
+    ctx->status = csi_file_new (ctx, &file, filename, "r");
+    if (ctx->status)
+	return ctx->status;
+
+    file.type |= CSI_OBJECT_ATTR_EXECUTABLE;
+
+    ctx->status = csi_object_execute (ctx, &file);
+    csi_object_free (ctx, &file);
+
+    return ctx->status;
+}
+
+cairo_status_t
+cairo_script_interpreter_feed_string (csi_t *ctx, const char *line, int len)
+{
+    csi_object_t file;
+
+    if (ctx->status)
+	return ctx->status;
+
+    if (len < 0)
+	len = strlen (line);
+    ctx->status = csi_file_new_for_bytes (ctx, &file, line, len);
+    if (ctx->status)
+	return ctx->status;
+
+    file.type |= CSI_OBJECT_ATTR_EXECUTABLE;
+
+    ctx->status = csi_object_execute (ctx, &file);
+    csi_object_free (ctx, &file);
+
+    return ctx->status;
+}
+
+csi_t *
+cairo_script_interpreter_reference (csi_t *ctx)
+{
+    ctx->ref_count++;
+    return ctx;
+}
+slim_hidden_def (cairo_script_interpreter_reference);
+
+cairo_status_t
+cairo_script_interpreter_destroy (csi_t *ctx)
+{
+    csi_status_t status;
+
+    status = ctx->status;
+    if (--ctx->ref_count)
+	return status;
+
+    _csi_fini (ctx);
+    free (ctx);
+
+    return status;
+}
+slim_hidden_def (cairo_script_interpreter_destroy);
diff --git a/util/cairo-script/cairo-script-interpreter.h b/util/cairo-script/cairo-script-interpreter.h
new file mode 100644
index 0000000..21a3a15
--- /dev/null
+++ b/util/cairo-script/cairo-script-interpreter.h
@@ -0,0 +1,104 @@
+/* cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2008 Chris Wilson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Chris Wilson
+ *
+ * Contributor(s):
+ *	Chris Wilson <chris at chris-wilson.co.uk>
+ */
+
+#ifndef CAIRO_SCRIPT_INTERPRETER_H
+#define CAIRO_SCRIPT_INTERPRETER_H
+
+#include <cairo.h>
+
+#if CAIRO_HAS_SCRIPT_SURFACE
+
+CAIRO_BEGIN_DECLS
+
+typedef struct _cairo_script_interpreter cairo_script_interpreter_t;
+
+/* XXX expose csi_dictionary_t and pass to hooks */
+typedef void
+(*csi_destroy_func_t) (void *closure,
+			       void *ptr);
+typedef cairo_surface_t *
+(*csi_surface_create_func_t) (void *closure,
+			      double width,
+			      double height);
+typedef cairo_t *
+(*csi_context_create_func_t) (void *closure,
+			      cairo_surface_t *surface);
+typedef void
+(*csi_show_page_func_t) (void *closure,
+			 cairo_t *cr);
+
+typedef void
+(*csi_copy_page_func_t) (void *closure,
+			 cairo_t *cr);
+
+typedef struct _cairo_script_interpreter_hooks {
+    void *closure;
+    csi_surface_create_func_t surface_create;
+    csi_destroy_func_t surface_destroy;
+    csi_context_create_func_t context_create;
+    csi_destroy_func_t context_destroy;
+    csi_show_page_func_t show_page;
+    csi_copy_page_func_t copy_page;
+} cairo_script_interpreter_hooks_t;
+
+cairo_public cairo_script_interpreter_t *
+cairo_script_interpreter_create (void);
+
+cairo_public void
+cairo_script_interpreter_install_hooks (cairo_script_interpreter_t *ctx,
+					const cairo_script_interpreter_hooks_t *hooks);
+
+cairo_public cairo_status_t
+cairo_script_interpreter_run (cairo_script_interpreter_t *ctx,
+			      const char *filename);
+
+cairo_public cairo_status_t
+cairo_script_interpreter_feed_string (cairo_script_interpreter_t *ctx,
+				      const char *line,
+				      int len);
+
+cairo_public cairo_script_interpreter_t *
+cairo_script_interpreter_reference (cairo_script_interpreter_t *ctx);
+
+cairo_public cairo_status_t
+cairo_script_interpreter_destroy (cairo_script_interpreter_t *ctx);
+
+CAIRO_END_DECLS
+
+#else  /*CAIRO_HAS_SCRIPT_SURFACE*/
+# error Cairo was not compiled with support for the CairoScript backend
+#endif /*CAIRO_HAS_SCRIPT_SURFACE*/
+
+#endif /*CAIRO_SCRIPT_INTERPRETER_H*/
diff --git a/util/cairo-script/cairo-script-objects.c b/util/cairo-script/cairo-script-objects.c
new file mode 100644
index 0000000..f4726ce
--- /dev/null
+++ b/util/cairo-script/cairo-script-objects.c
@@ -0,0 +1,666 @@
+/*
+ * Copyright © 2008 Chris Wilson <chris at chris-wilson.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Chris Wilson.
+ *
+ * Contributor(s):
+ *	Chris Wilson <chris at chris-wilson.co.uk>
+ */
+
+#include "cairo-script-private.h"
+
+#include <string.h>
+
+csi_status_t
+csi_array_new (csi_t *ctx,
+	       csi_object_t *obj)
+
+{
+    csi_array_t *array;
+    csi_status_t status;
+
+    array = _csi_slab_alloc (ctx, sizeof (csi_array_t));
+    if (_csi_unlikely (array == NULL))
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+
+    array->base.type = CSI_OBJECT_TYPE_ARRAY;
+    array->base.ref = 1;
+    status = _csi_stack_init (ctx, &array->stack, 32);
+    if (_csi_unlikely (status)) {
+	_csi_slab_free (ctx, array, sizeof (csi_array_t));
+	return status;
+    }
+
+    obj->type = CSI_OBJECT_TYPE_ARRAY;
+    obj->datum.array = array;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+csi_status_t
+csi_array_put (csi_t *ctx,
+	       csi_array_t *array,
+	       csi_integer_t elem,
+	       csi_object_t *value)
+{
+    if (_csi_unlikely (elem < 0))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    if (_csi_unlikely (elem >= array->stack.len)) {
+	csi_status_t status;
+
+	status = _csi_stack_grow (ctx, &array->stack, elem + 1);
+	if (_csi_unlikely (status))
+	    return status;
+
+	memset (array->stack.objects + array->stack.len,
+		0, (elem - array->stack.len + 1) * sizeof (csi_object_t));
+	array->stack.len = elem + 1;
+    }
+
+    csi_object_free (ctx, &array->stack.objects[elem]);
+    array->stack.objects[elem] = *csi_object_reference (value);
+
+    return CSI_STATUS_SUCCESS;
+}
+
+csi_status_t
+csi_array_get (csi_t *ctx,
+	       csi_array_t *array,
+	       csi_integer_t elem,
+	       csi_object_t *value)
+{
+    if (_csi_unlikely (elem < 0))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    if (_csi_unlikely (elem > array->stack.len))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    *value = array->stack.objects[elem];
+    return CSI_STATUS_SUCCESS;
+}
+
+csi_status_t
+csi_array_append (csi_t *ctx,
+		  csi_array_t *array,
+		  csi_object_t *obj)
+{
+    return _csi_stack_push (ctx, &array->stack, csi_object_reference (obj));
+}
+
+inline csi_status_t
+_csi_array_execute (csi_t *ctx, csi_array_t *array)
+{
+    csi_integer_t i;
+    csi_status_t status;
+
+    if (_csi_unlikely (array->stack.len == 0))
+	return CSI_STATUS_SUCCESS;
+
+    for (i = 0; i < array->stack.len; i++) {
+	csi_object_t *obj = &array->stack.objects[i];
+
+	if (obj->type & CSI_OBJECT_ATTR_EXECUTABLE) {
+	    if (obj->type == (CSI_OBJECT_TYPE_ARRAY |
+			      CSI_OBJECT_ATTR_EXECUTABLE))
+	    {
+		status = _csi_push_ostack_copy (ctx, &array->stack.objects[i]);
+	    }
+	    else
+		status = csi_object_execute (ctx, &array->stack.objects[i]);
+	} else
+	    status = _csi_push_ostack_copy (ctx, &array->stack.objects[i]);
+	if (_csi_unlikely (status))
+	    return status;
+    }
+
+    return CSI_STATUS_SUCCESS;
+}
+
+void
+csi_array_free (csi_t *ctx, csi_array_t *array)
+{
+    _csi_stack_fini (ctx, &array->stack);
+    _csi_slab_free (ctx, array, sizeof (csi_array_t));
+}
+
+csi_status_t
+csi_boolean_new (csi_t *ctx,
+		 csi_object_t *obj,
+		 csi_boolean_t v)
+{
+    obj->type = CSI_OBJECT_TYPE_BOOLEAN;
+    obj->datum.boolean = v;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+static cairo_bool_t
+_dictionary_name_equal (const void *_a, const void *_b)
+{
+    const csi_dictionary_entry_t *a = _a;
+    const csi_dictionary_entry_t *b = _b;
+    return a->hash_entry.hash == b->hash_entry.hash;
+}
+
+csi_status_t
+csi_dictionary_new (csi_t *ctx,
+		    csi_object_t *obj)
+
+{
+    csi_dictionary_t *dict;
+    csi_status_t status;
+
+    dict = _csi_slab_alloc (ctx, sizeof (csi_dictionary_t));
+    if (_csi_unlikely (dict == NULL))
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+
+    dict->base.type = CSI_OBJECT_TYPE_DICTIONARY;
+    dict->base.ref = 1;
+    status = _csi_hash_table_init (&dict->hash_table, _dictionary_name_equal);
+    if (_csi_unlikely (status)) {
+	_csi_slab_free (ctx, dict, sizeof (csi_dictionary_t));
+	return status;
+    }
+
+    obj->type = CSI_OBJECT_TYPE_DICTIONARY;
+    obj->datum.dictionary = dict;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+struct _dictionary_entry_pluck {
+    csi_t *ctx;
+    csi_hash_table_t *hash_table;
+};
+
+static void
+_dictionary_entry_pluck (void *entry, void *data)
+{
+    csi_dictionary_entry_t *dict_entry;
+    struct _dictionary_entry_pluck *pluck_data;
+
+    dict_entry = entry;
+    pluck_data = data;
+
+    _csi_hash_table_remove (pluck_data->hash_table, entry);
+    csi_object_free (pluck_data->ctx, &dict_entry->value);
+    _csi_slab_free (pluck_data->ctx, entry, sizeof (csi_dictionary_entry_t));
+}
+
+void
+csi_dictionary_free (csi_t *ctx,
+		     csi_dictionary_t *dict)
+{
+    struct _dictionary_entry_pluck data;
+
+    data.ctx = ctx;
+    data.hash_table = &dict->hash_table;
+    _csi_hash_table_foreach (&dict->hash_table,
+			     _dictionary_entry_pluck,
+			     &data);
+    _csi_hash_table_fini (&dict->hash_table);
+
+    _csi_slab_free (ctx, dict, sizeof (csi_dictionary_t));
+}
+
+csi_status_t
+csi_dictionary_put (csi_t *ctx,
+		    csi_dictionary_t *dict,
+		    csi_name_t name,
+		    csi_object_t *value)
+{
+    csi_dictionary_entry_t *entry;
+    csi_status_t status;
+
+    entry = _csi_hash_table_lookup (&dict->hash_table,
+				    (csi_hash_entry_t *) &name);
+    if (entry != NULL) {
+	/* replace the existing entry */
+	csi_object_free (ctx, &entry->value);
+	entry->value = *csi_object_reference (value);
+	return CSI_STATUS_SUCCESS;
+    }
+
+    entry = _csi_slab_alloc (ctx, sizeof (*entry));
+    if (_csi_unlikely (entry == NULL))
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+
+    entry->hash_entry.hash = name;
+    status = _csi_hash_table_insert (&dict->hash_table, &entry->hash_entry);
+    if (_csi_unlikely (status)) {
+	_csi_slab_free (ctx, entry, sizeof (*entry));
+	return status;
+    }
+
+    entry->value = *csi_object_reference (value);
+
+    return CSI_STATUS_SUCCESS;
+}
+
+csi_status_t
+csi_dictionary_get (csi_t *ctx,
+		    csi_dictionary_t *dict,
+		    csi_name_t name,
+		    csi_object_t *value)
+{
+    csi_dictionary_entry_t *entry;
+
+    entry = _csi_hash_table_lookup (&dict->hash_table,
+				    (csi_hash_entry_t *) &name);
+    if (_csi_unlikely (entry == NULL))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    *value = entry->value;
+    return CSI_STATUS_SUCCESS;
+}
+
+csi_boolean_t
+csi_dictionary_has (csi_dictionary_t *dict,
+		    csi_name_t name)
+{
+    return _csi_hash_table_lookup (&dict->hash_table,
+				   (csi_hash_entry_t *) &name) != NULL;
+}
+
+void
+csi_dictionary_remove (csi_t *ctx,
+		       csi_dictionary_t *dict,
+		       csi_name_t name)
+{
+    csi_dictionary_entry_t *entry;
+
+    entry = _csi_hash_table_lookup (&dict->hash_table,
+				    (csi_hash_entry_t *) &name);
+    if (entry != NULL) {
+	_csi_hash_table_remove (&dict->hash_table, &entry->hash_entry);
+	csi_object_free (ctx, &entry->value);
+	_csi_slab_free (ctx, entry, sizeof (csi_dictionary_entry_t));
+    }
+}
+
+csi_status_t
+csi_integer_new (csi_t *ctx,
+		 csi_object_t *obj,
+		 csi_integer_t v)
+{
+    obj->type = CSI_OBJECT_TYPE_INTEGER;
+    obj->datum.integer = v;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+csi_status_t
+csi_matrix_new (csi_t *ctx,
+		csi_object_t *obj)
+{
+    csi_matrix_t *matrix;
+
+    matrix = _csi_slab_alloc (ctx, sizeof (csi_matrix_t));
+    if (_csi_unlikely (matrix == NULL))
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+
+    matrix->base.type = CSI_OBJECT_TYPE_MATRIX;
+    matrix->base.ref = 1;
+    cairo_matrix_init_identity (&matrix->matrix);
+
+    obj->type = CSI_OBJECT_TYPE_MATRIX;
+    obj->datum.matrix = matrix;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+csi_status_t
+csi_matrix_new_from_array (csi_t *ctx,
+			   csi_object_t *obj,
+			   csi_array_t *array)
+{
+    csi_matrix_t *matrix;
+
+    if (_csi_unlikely (array->stack.len != 6))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    matrix = _csi_slab_alloc (ctx, sizeof (csi_matrix_t));
+    if (_csi_unlikely (matrix == NULL))
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+
+    matrix->base.type = CSI_OBJECT_TYPE_MATRIX;
+    matrix->base.ref = 1;
+    cairo_matrix_init (&matrix->matrix,
+		       csi_number_get_value (&array->stack.objects[0]),
+		       csi_number_get_value (&array->stack.objects[1]),
+		       csi_number_get_value (&array->stack.objects[2]),
+		       csi_number_get_value (&array->stack.objects[3]),
+		       csi_number_get_value (&array->stack.objects[4]),
+		       csi_number_get_value (&array->stack.objects[5]));
+
+    obj->type = CSI_OBJECT_TYPE_MATRIX;
+    obj->datum.matrix = matrix;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+csi_status_t
+csi_matrix_new_from_matrix (csi_t *ctx,
+			    csi_object_t *obj,
+			    const cairo_matrix_t *m)
+{
+    csi_matrix_t *matrix;
+
+    matrix = _csi_slab_alloc (ctx, sizeof (csi_matrix_t));
+    if (_csi_unlikely (matrix == NULL))
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+
+    matrix->base.type = CSI_OBJECT_TYPE_MATRIX;
+    matrix->base.ref = 1;
+    matrix->matrix = *m;
+
+    obj->type = CSI_OBJECT_TYPE_MATRIX;
+    obj->datum.matrix = matrix;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+csi_status_t
+csi_matrix_new_from_values (csi_t *ctx,
+			    csi_object_t *obj,
+			    double v[6])
+{
+    csi_matrix_t *matrix;
+
+    matrix = _csi_slab_alloc (ctx, sizeof (csi_matrix_t));
+    if (_csi_unlikely (matrix == NULL))
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+
+    matrix->base.type = CSI_OBJECT_TYPE_MATRIX;
+    matrix->base.ref = 1;
+    cairo_matrix_init (&matrix->matrix, v[0], v[1], v[2], v[3], v[4], v[5]);
+
+    obj->type = CSI_OBJECT_TYPE_MATRIX;
+    obj->datum.matrix = matrix;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+void
+csi_matrix_free (csi_t *ctx,
+		 csi_matrix_t *obj)
+{
+    _csi_slab_free (ctx, obj, sizeof (csi_matrix_t));
+}
+
+
+csi_status_t
+csi_name_new (csi_t *ctx,
+	      csi_object_t *obj,
+	      const char *str,
+	      int len)
+{
+    csi_status_t status;
+
+    status = _csi_intern_string (ctx, &str, len);
+    if (_csi_unlikely (status))
+	return status;
+
+    obj->type = CSI_OBJECT_TYPE_NAME;
+    obj->datum.name = (csi_name_t) str;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+csi_status_t
+csi_name_new_static (csi_t *ctx,
+		     csi_object_t *obj,
+		     const char *str)
+{
+    csi_status_t status;
+
+    status = _csi_intern_string (ctx, &str, -1);
+    if (_csi_unlikely (status))
+	return status;
+
+    obj->type = CSI_OBJECT_TYPE_NAME;
+    obj->datum.name = (csi_name_t) str;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+csi_status_t
+csi_operator_new (csi_t *ctx,
+		  csi_object_t *obj,
+		  csi_operator_t op)
+{
+    obj->type = CSI_OBJECT_TYPE_OPERATOR | CSI_OBJECT_ATTR_EXECUTABLE;
+    obj->datum.op = op;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+csi_status_t
+csi_real_new (csi_t *ctx,
+	      csi_object_t *obj,
+	      csi_real_t v)
+{
+    obj->type = CSI_OBJECT_TYPE_REAL;
+    obj->datum.real = v;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+csi_status_t
+csi_string_new (csi_t *ctx,
+		csi_object_t *obj,
+		const char *str,
+		int len)
+{
+    csi_string_t *string;
+
+    string = _csi_slab_alloc (ctx, sizeof (csi_string_t));
+    if (_csi_unlikely (string == NULL))
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+
+    string->base.type = CSI_OBJECT_TYPE_STRING;
+    string->base.ref = 1;
+
+    if (len < 0)
+	len = strlen (str);
+    if (_csi_unlikely (len >= INT32_MAX)) {
+	_csi_slab_free (ctx, string, sizeof (csi_string_t));
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+    }
+    string->string = _csi_alloc (ctx, len + 1);
+    if (_csi_unlikely (string->string == NULL)) {
+	_csi_slab_free (ctx, string, sizeof (csi_string_t));
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+    }
+    memcpy (string->string, str, len);
+    string->string[len] = '\0';
+    string->len = len;
+
+    obj->type = CSI_OBJECT_TYPE_STRING;
+    obj->datum.string = string;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+static inline csi_status_t
+_csi_string_execute (csi_t *ctx, csi_string_t *string)
+{
+    csi_status_t status;
+    csi_object_t obj;
+
+    if (_csi_unlikely (string->len == 0))
+	return CSI_STATUS_SUCCESS;
+
+    status = csi_file_new_for_bytes (ctx, &obj, string->string, string->len);
+    if (_csi_unlikely (status))
+	return status;
+
+    status = _csi_scan_file (ctx, &ctx->scanner, obj.datum.file);
+    csi_object_free (ctx, &obj);
+
+    return status;
+}
+
+void
+csi_string_free (csi_t *ctx, csi_string_t *string)
+{
+    _csi_free (ctx, string->string);
+    _csi_slab_free (ctx, string, sizeof (csi_string_t));
+}
+
+csi_status_t
+csi_object_execute (csi_t *ctx, csi_object_t *obj)
+{
+    csi_status_t status;
+    csi_object_t indirect;
+
+ INDIRECT:
+    switch (obj->type & CSI_OBJECT_TYPE_MASK) {
+    case CSI_OBJECT_TYPE_NAME:
+	status = _csi_name_lookup (ctx, obj->datum.name, &indirect);
+	if (_csi_unlikely (status))
+	    return status;
+	if (indirect.type & CSI_OBJECT_ATTR_EXECUTABLE) {
+	    obj = &indirect;
+	    goto INDIRECT;
+	} else
+	    return _csi_push_ostack_copy (ctx, &indirect);
+
+    case CSI_OBJECT_TYPE_OPERATOR:
+	return obj->datum.op (ctx);
+
+    case CSI_OBJECT_TYPE_ARRAY:
+	return _csi_array_execute (ctx, obj->datum.array);
+    case CSI_OBJECT_TYPE_FILE:
+	return _csi_file_execute (ctx, obj->datum.file);
+    case CSI_OBJECT_TYPE_STRING:
+	return _csi_string_execute (ctx, obj->datum.string);
+
+    default:
+	return _csi_push_ostack_copy (ctx, obj);
+    }
+}
+
+csi_object_t *
+csi_object_reference (csi_object_t *obj)
+{
+    if (CSI_OBJECT_IS_CAIRO (obj)) {
+	switch (obj->type & CSI_OBJECT_TYPE_MASK) {
+	case CSI_OBJECT_TYPE_CONTEXT:
+	    cairo_reference (obj->datum.cr);
+	    break;
+	case CSI_OBJECT_TYPE_FONT:
+	    cairo_font_face_reference (obj->datum.font_face);
+	    break;
+	case CSI_OBJECT_TYPE_PATTERN:
+	    cairo_pattern_reference (obj->datum.pattern);
+	    break;
+	case CSI_OBJECT_TYPE_SCALED_FONT:
+	    cairo_scaled_font_reference (obj->datum.scaled_font);
+	    break;
+	case CSI_OBJECT_TYPE_SURFACE:
+	    cairo_surface_reference (obj->datum.surface);
+	    break;
+	}
+    } else if (CSI_OBJECT_IS_COMPOUND (obj)) {
+	obj->datum.object->ref++;
+    }
+
+    return obj;
+}
+
+void
+csi_object_free (csi_t *ctx,
+		 csi_object_t *obj)
+{
+    if (CSI_OBJECT_IS_CAIRO (obj)) {
+	switch (obj->type & CSI_OBJECT_TYPE_MASK) {
+	case CSI_OBJECT_TYPE_CONTEXT:
+	    cairo_destroy (obj->datum.cr);
+	    break;
+	case CSI_OBJECT_TYPE_FONT:
+	    cairo_font_face_destroy (obj->datum.font_face);
+	    break;
+	case CSI_OBJECT_TYPE_PATTERN:
+	    cairo_pattern_destroy (obj->datum.pattern);
+	    break;
+	case CSI_OBJECT_TYPE_SCALED_FONT:
+	    cairo_scaled_font_destroy (obj->datum.scaled_font);
+	    break;
+	case CSI_OBJECT_TYPE_SURFACE:
+	    cairo_surface_destroy (obj->datum.surface);
+	    break;
+	}
+    } else if (CSI_OBJECT_IS_COMPOUND (obj)) {
+	if (--obj->datum.object->ref)
+	    return;
+
+	switch (obj->type & CSI_OBJECT_TYPE_MASK) {
+	case CSI_OBJECT_TYPE_ARRAY:
+	    csi_array_free (ctx, obj->datum.array);
+	    break;
+	case CSI_OBJECT_TYPE_DICTIONARY:
+	    csi_dictionary_free (ctx, obj->datum.dictionary);
+	    break;
+	case CSI_OBJECT_TYPE_FILE:
+	    _csi_file_free (ctx, obj->datum.file);
+	    break;
+	case CSI_OBJECT_TYPE_MATRIX:
+	    csi_matrix_free (ctx, obj->datum.matrix);
+	    break;
+	case CSI_OBJECT_TYPE_STRING:
+	    csi_string_free (ctx, obj->datum.string);
+	    break;
+	default:
+	    break;
+	}
+    }
+}
+
+csi_status_t
+csi_object_as_file (csi_t *ctx,
+		    csi_object_t *src,
+		    csi_object_t *file)
+{
+
+    switch ((int) csi_object_get_type (src)) {
+    case CSI_OBJECT_TYPE_FILE:
+	*file = *csi_object_reference (src);
+	return CSI_STATUS_SUCCESS;
+    case CSI_OBJECT_TYPE_STRING:
+	 return csi_file_new_from_string (ctx, file, src->datum.string);
+    case CSI_OBJECT_TYPE_ARRAY:
+#if 0
+	if (src->type & CSI_OBJECT_ATTR_EXECUTABLE)
+	    return _csi_file_new_from_procedure (cs, src);
+#endif
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+}
diff --git a/util/cairo-script/cairo-script-operators.c b/util/cairo-script/cairo-script-operators.c
new file mode 100644
index 0000000..dc1c208
--- /dev/null
+++ b/util/cairo-script/cairo-script-operators.c
@@ -0,0 +1,5791 @@
+/*
+ * Copyright © 2008 Chris Wilson <chris at chris-wilson.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Chris Wilson.
+ *
+ * Contributor(s):
+ *	Chris Wilson <chris at chris-wilson.co.uk>
+ */
+
+/* TODO real matrix and path types */
+
+#include "cairo-script-private.h"
+
+#include <stdio.h> /* snprintf */
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <byteswap.h>
+
+typedef struct _csi_proxy {
+    csi_t *ctx;
+    void *ptr;
+    csi_dictionary_t *dictionary;
+    csi_destroy_func_t destroy_func;
+    void *destroy_data;
+} csi_proxy_t;
+
+typedef struct _csi_blob {
+    csi_list_t list;
+    unsigned long hash;
+    uint8_t *bytes;
+    unsigned int len;
+} csi_blob_t;
+
+static const cairo_user_data_key_t _csi_proxy_key;
+static const cairo_user_data_key_t _csi_blob_key;
+
+enum mime_type {
+    MIME_TYPE_NONE = 0,
+    MIME_TYPE_PNG
+};
+
+#define check(CNT) do {\
+    if (_csi_unlikely (! _csi_check_ostack (ctx, (CNT)))) \
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT); \
+} while (0)
+#define pop(CNT) _csi_pop_ostack (ctx, (CNT))
+#define push(OBJ) _csi_push_ostack (ctx, (OBJ))
+
+static csi_proxy_t *
+_csi_proxy_create (csi_t *ctx,
+		   void *ptr,
+		   csi_dictionary_t *dictionary,
+		   csi_destroy_func_t destroy_func,
+		   void *destroy_data)
+{
+    csi_proxy_t *proxy;
+
+    proxy = _csi_slab_alloc (ctx, sizeof (csi_proxy_t));
+    if (proxy == NULL)
+	return NULL;
+
+    proxy->ctx = cairo_script_interpreter_reference (ctx);
+    proxy->ptr = ptr;
+    proxy->destroy_func = destroy_func;
+    proxy->destroy_data = destroy_data;
+    proxy->dictionary = dictionary;
+    if (dictionary != NULL)
+	dictionary->base.ref++;
+
+    return proxy;
+}
+
+static void
+_csi_proxy_destroy (void *closure)
+{
+    csi_proxy_t *proxy = closure;
+    csi_t *ctx = proxy->ctx;
+
+    /* XXX this doesn't work because user_data_destroy is called too late.
+     * Considering another hook into the (cairo internal) object system.
+     */
+    if (proxy->destroy_func != NULL)
+	proxy->destroy_func (proxy->destroy_data, proxy->ptr);
+
+    if (proxy->dictionary != NULL && --proxy->dictionary->base.ref == 0)
+	csi_dictionary_free (ctx, proxy->dictionary);
+
+    _csi_slab_free (ctx, proxy, sizeof (csi_proxy_t));
+    cairo_script_interpreter_destroy (ctx);
+}
+
+static unsigned long
+_csi_blob_hash (const uint8_t *bytes, int len)
+{
+    /* very simple! */
+    unsigned long hash = 5381;
+    unsigned long *data = (unsigned long *) bytes;
+    len /= sizeof (unsigned long);
+    while (len--) {
+	unsigned long c = *data++;
+	hash *= 33;
+	hash ^= c;
+    }
+    return hash;
+}
+
+static csi_boolean_t
+_csi_blob_equal (const csi_list_t *link, void *data)
+{
+    csi_blob_t *A, *B;
+
+    A = csi_container_of (link, csi_blob_t, list);
+    B = data;
+
+    if (A->len != B->len)
+	return FALSE;
+
+    if (A->hash == 0)
+	A->hash = _csi_blob_hash (A->bytes, A->len);
+    if (B->hash == 0)
+	B->hash = _csi_blob_hash (B->bytes, B->len);
+    if (A->hash != B->hash)
+	return FALSE;
+
+    return memcmp (A->bytes, B->bytes, A->len) == 0;
+}
+
+static void
+_csi_blob_init (csi_blob_t *blob, uint8_t *bytes, int len)
+{
+    blob->hash = 0;
+    blob->len = len;
+    blob->bytes = bytes;
+}
+
+static csi_list_t *
+_csi_list_unlink (csi_list_t *head, csi_list_t *link)
+{
+    if (link->next != NULL)
+	link->next->prev = link->prev;
+    if (link->prev != NULL)
+	link->prev->next = link->next;
+    else
+	head = link->next;
+    return head;
+}
+
+static csi_list_t *
+_csi_list_prepend (csi_list_t *head, csi_list_t *link)
+{
+    if (head != NULL)
+	head->prev = link;
+    link->next = head;
+    link->prev = NULL;
+    return link;
+}
+
+static csi_list_t *
+_csi_list_find (csi_list_t *head,
+		csi_boolean_t (*predicate) (const csi_list_t *link, void *data),
+		void *data)
+{
+    while (head != NULL) {
+	if (predicate (head, data))
+	    return head;
+	head = head->next;
+    }
+
+    return NULL;
+}
+
+static csi_status_t
+_csi_ostack_get_boolean (csi_t *ctx, unsigned int i, csi_boolean_t *out)
+{
+    csi_object_t *obj;
+
+    obj = _csi_peek_ostack (ctx, i);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_BOOLEAN:
+	*out = obj->datum.boolean;
+	break;
+    case CSI_OBJECT_TYPE_INTEGER:
+	*out = !! obj->datum.integer;
+	break;
+    case CSI_OBJECT_TYPE_REAL:
+	*out = obj->datum.real != 0.;
+	break;
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_csi_ostack_get_integer (csi_t *ctx, unsigned int i, csi_integer_t *out)
+{
+    csi_object_t *obj;
+
+    obj = _csi_peek_ostack (ctx, i);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_BOOLEAN:
+	*out = obj->datum.boolean;
+	break;
+    case CSI_OBJECT_TYPE_INTEGER:
+	*out = obj->datum.integer;
+	break;
+    case CSI_OBJECT_TYPE_REAL:
+	*out = obj->datum.real;
+	break;
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_csi_ostack_get_number (csi_t *ctx, unsigned int i, double *out)
+{
+    csi_object_t *obj;
+
+    obj = _csi_peek_ostack (ctx, i);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_BOOLEAN:
+	*out = obj->datum.boolean;
+	break;
+    case CSI_OBJECT_TYPE_INTEGER:
+	*out = obj->datum.integer;
+	break;
+    case CSI_OBJECT_TYPE_REAL:
+	*out = obj->datum.real;
+	break;
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_csi_ostack_get_name (csi_t *ctx, unsigned int i, csi_name_t *out)
+{
+    csi_object_t *obj;
+
+    obj = _csi_peek_ostack (ctx, i);
+    if (_csi_unlikely (csi_object_get_type (obj) != CSI_OBJECT_TYPE_NAME))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    *out = obj->datum.name;
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_csi_ostack_get_context (csi_t *ctx, unsigned int i, cairo_t **out)
+{
+    csi_object_t *obj;
+
+    obj = _csi_peek_ostack (ctx, i);
+    if (_csi_unlikely (csi_object_get_type (obj) != CSI_OBJECT_TYPE_CONTEXT))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    *out = obj->datum.cr;
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_csi_ostack_get_font_face (csi_t *ctx, unsigned int i, cairo_font_face_t **out)
+{
+    csi_object_t *obj;
+
+    obj = _csi_peek_ostack (ctx, i);
+    if (_csi_unlikely (csi_object_get_type (obj) != CSI_OBJECT_TYPE_FONT))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    *out = obj->datum.font_face;
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_csi_ostack_get_pattern (csi_t *ctx, unsigned int i, cairo_pattern_t **out)
+{
+    csi_object_t *obj;
+
+    obj = _csi_peek_ostack (ctx, i);
+    if (_csi_unlikely (csi_object_get_type (obj) != CSI_OBJECT_TYPE_PATTERN))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    *out = obj->datum.pattern;
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_csi_ostack_get_scaled_font (csi_t *ctx, unsigned int i,
+			     cairo_scaled_font_t **out)
+{
+    csi_object_t *obj;
+
+    obj = _csi_peek_ostack (ctx, i);
+    if (_csi_unlikely
+	(csi_object_get_type (obj) != CSI_OBJECT_TYPE_SCALED_FONT))
+    {
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    *out = obj->datum.scaled_font;
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_csi_ostack_get_surface (csi_t *ctx, unsigned int i, cairo_surface_t **out)
+{
+    csi_object_t *obj;
+
+    obj = _csi_peek_ostack (ctx, i);
+    if (_csi_unlikely (csi_object_get_type (obj) != CSI_OBJECT_TYPE_SURFACE))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    *out = obj->datum.surface;
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_csi_ostack_get_array (csi_t *ctx, unsigned int i, csi_array_t **out)
+{
+    csi_object_t *obj;
+
+    obj = _csi_peek_ostack (ctx, i);
+    if (_csi_unlikely (csi_object_get_type (obj) != CSI_OBJECT_TYPE_ARRAY))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    *out = obj->datum.array;
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_csi_ostack_get_procedure (csi_t *ctx, unsigned int i, csi_array_t **out)
+{
+    csi_object_t *obj;
+
+    obj = _csi_peek_ostack (ctx, i);
+    if (_csi_unlikely (! csi_object_is_procedure (obj)))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    *out = obj->datum.array;
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_csi_ostack_get_dictionary (csi_t *ctx, unsigned int i, csi_dictionary_t **out)
+{
+    csi_object_t *obj;
+
+    obj = _csi_peek_ostack (ctx, i);
+    if (_csi_unlikely
+	(csi_object_get_type (obj) != CSI_OBJECT_TYPE_DICTIONARY))
+    {
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    *out = obj->datum.dictionary;
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_csi_ostack_get_matrix (csi_t *ctx, unsigned int i, cairo_matrix_t *out)
+{
+    csi_object_t *obj;
+
+    obj = _csi_peek_ostack (ctx, i);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_MATRIX:
+	*out = obj->datum.matrix->matrix;
+	return CSI_STATUS_SUCCESS;
+
+    case CSI_OBJECT_TYPE_ARRAY:
+	if (obj->datum.array->stack.len == 6) {
+	    cairo_matrix_init (out,
+			       csi_number_get_value (&obj->datum.array->stack.objects[0]),
+			       csi_number_get_value (&obj->datum.array->stack.objects[1]),
+			       csi_number_get_value (&obj->datum.array->stack.objects[2]),
+			       csi_number_get_value (&obj->datum.array->stack.objects[3]),
+			       csi_number_get_value (&obj->datum.array->stack.objects[4]),
+			       csi_number_get_value (&obj->datum.array->stack.objects[5]));
+	    return CSI_STATUS_SUCCESS;
+	}
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+}
+
+static csi_status_t
+_csi_dictionary_get_integer (csi_t *ctx,
+			     csi_dictionary_t *dict,
+			     const char *name,
+			     csi_boolean_t optional,
+			     long *value)
+{
+    csi_status_t status;
+    csi_object_t key, obj;
+
+    status = csi_name_new_static (ctx, &key, name);
+    if (_csi_unlikely (status))
+	return status;
+
+    if (optional && ! csi_dictionary_has (dict, key.datum.name))
+	return CSI_STATUS_SUCCESS;
+
+    status = csi_dictionary_get (ctx, dict, key.datum.name, &obj);
+    if (_csi_unlikely (status))
+	return status;
+
+    switch ((int) csi_object_get_type (&obj)) {
+    case CSI_OBJECT_TYPE_BOOLEAN:
+	*value = obj.datum.boolean;
+	break;
+    case CSI_OBJECT_TYPE_INTEGER:
+	*value = obj.datum.integer;
+	break;
+    case CSI_OBJECT_TYPE_REAL:
+	*value = obj.datum.real;
+	break;
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_csi_dictionary_get_number (csi_t *ctx,
+			    csi_dictionary_t *dict,
+			    const char *name,
+			    csi_boolean_t optional,
+			    double *value)
+{
+    csi_status_t status;
+    csi_object_t key, obj;
+
+    status = csi_name_new_static (ctx, &key, name);
+    if (_csi_unlikely (status))
+	return status;
+
+    if (optional && ! csi_dictionary_has (dict, key.datum.name))
+	return CSI_STATUS_SUCCESS;
+
+    status = csi_dictionary_get (ctx, dict, key.datum.name, &obj);
+    if (_csi_unlikely (status))
+	return status;
+
+    *value = csi_number_get_value (&obj);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_csi_ostack_get_string (csi_t *ctx, unsigned int i, csi_string_t **out)
+{
+    csi_object_t *obj;
+
+    obj = _csi_peek_ostack (ctx, i);
+    if (_csi_unlikely (csi_object_get_type (obj) != CSI_OBJECT_TYPE_STRING))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    *out = obj->datum.string;
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_csi_ostack_get_string_constant (csi_t *ctx, unsigned int i, const char **out)
+{
+    csi_object_t *obj;
+
+    obj = _csi_peek_ostack (ctx, i);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_NAME:
+	*out = (const char *) obj->datum.name;
+	break;
+    case CSI_OBJECT_TYPE_STRING:
+	*out = obj->datum.string->string;
+	break;
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_do_cairo_op (csi_t *ctx, void (*op) (cairo_t *))
+{
+    cairo_t *cr;
+    csi_status_t status;
+
+    check (1);
+
+    status = _csi_ostack_get_context (ctx, 0, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    op (cr);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+end_dict_construction (csi_t *ctx)
+{
+    csi_object_t obj;
+    csi_dictionary_t *dict;
+    csi_status_t status;
+
+    status = csi_dictionary_new (ctx, &obj);
+    if (_csi_unlikely (status))
+	return status;
+
+    dict = obj.datum.dictionary;
+    do {
+	csi_object_t *name, *value;
+
+	check (1);
+
+	value = _csi_peek_ostack (ctx, 0);
+	if (csi_object_get_type (value) == CSI_OBJECT_TYPE_MARK) {
+	    pop (1);
+	    break;
+	}
+
+	check (1);
+
+	name = _csi_peek_ostack (ctx, 0);
+	if (_csi_unlikely
+	    (csi_object_get_type (name) != CSI_OBJECT_TYPE_NAME))
+	{
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	}
+
+	status = csi_dictionary_put (ctx, dict, name->datum.name, value);
+	if (_csi_unlikely (status))
+	    return status;
+
+	pop (2);
+    } while (TRUE);
+
+    return push (&obj);
+}
+
+static csi_status_t
+end_array_construction (csi_t *ctx)
+{
+    csi_object_t obj;
+    csi_array_t *array;
+    csi_status_t status;
+
+    status = csi_array_new (ctx, &obj);
+    if (_csi_unlikely (status))
+	return status;
+
+    array = obj.datum.array;
+    do {
+	csi_object_t *value;
+
+	check (1);
+
+	value = _csi_peek_ostack (ctx, 0);
+	if (csi_object_get_type (value) == CSI_OBJECT_TYPE_MARK) {
+	    pop (1);
+	    break;
+	}
+
+	status = csi_array_append (ctx, array, value);
+	if (_csi_unlikely (status))
+	    return status;
+
+	pop (1);
+    } while (TRUE);
+
+    /* and reverse */
+    if (array->stack.len) {
+	unsigned int i, j;
+
+	for (i = 0, j = array->stack.len; i < --j; i++) {
+	    csi_object_t tmp;
+
+	    tmp = array->stack.objects[i];
+	    array->stack.objects[i] = array->stack.objects[j];
+	    array->stack.objects[j] = tmp;
+	}
+    }
+
+    return push (&obj);
+}
+
+static csi_status_t
+_alpha (csi_t *ctx)
+{
+    csi_object_t obj;
+    csi_status_t status;
+    double a;
+
+    check (1);
+
+    status = _csi_ostack_get_number (ctx, 0, &a);
+    if (_csi_unlikely (status))
+	return status;
+
+    pop (1);
+
+    obj.type = CSI_OBJECT_TYPE_PATTERN;
+    obj.datum.pattern = cairo_pattern_create_rgba (0, 0, 0, a);
+    return push (&obj);
+}
+
+static csi_status_t
+_add (csi_t *ctx)
+{
+    csi_object_t *A;
+    csi_object_t *B;
+    csi_object_type_t type_a, type_b;
+
+    check (2);
+
+    B = _csi_peek_ostack (ctx, 0);
+    A = _csi_peek_ostack (ctx, 1);
+
+    type_a = csi_object_get_type (A);
+    if (_csi_unlikely (! (type_a == CSI_OBJECT_TYPE_INTEGER ||
+			    type_a == CSI_OBJECT_TYPE_REAL)))
+    {
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    type_b = csi_object_get_type (B);
+    if (_csi_unlikely (! (type_b == CSI_OBJECT_TYPE_INTEGER ||
+			    type_b == CSI_OBJECT_TYPE_REAL)))
+    {
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    pop (2);
+
+    if (type_a == CSI_OBJECT_TYPE_REAL &&
+	type_b == CSI_OBJECT_TYPE_REAL)
+    {
+	return _csi_push_ostack_real (ctx, A->datum.real + B->datum.real);
+
+    }
+    else if (type_a == CSI_OBJECT_TYPE_INTEGER &&
+	     type_b == CSI_OBJECT_TYPE_INTEGER)
+    {
+	return _csi_push_ostack_integer (ctx,
+					 A->datum.integer + B->datum.integer);
+    }
+    else
+    {
+	double v;
+
+	if (type_a == CSI_OBJECT_TYPE_REAL)
+	    v = A->datum.real;
+	else
+	    v = A->datum.integer;
+
+	if (type_b == CSI_OBJECT_TYPE_REAL)
+	    v += B->datum.real;
+	else
+	    v += B->datum.integer;
+
+	return _csi_push_ostack_real (ctx, v);
+    }
+}
+
+static csi_status_t
+_add_color_stop (csi_t *ctx)
+{
+    csi_status_t status;
+    double offset, r, g, b, a;
+    cairo_pattern_t *pattern;
+
+    check (6);
+
+    status = _csi_ostack_get_number (ctx, 0, &a);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &b);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 2, &g);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 3, &r);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 4, &offset);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_pattern (ctx, 5, &pattern);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_pattern_add_color_stop_rgba (pattern, offset, r, g, b, a);
+
+    pop (5);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_and (csi_t *ctx)
+{
+    csi_object_t *a, *b;
+
+    check (2);
+
+    a = _csi_peek_ostack (ctx, 0);
+    b = _csi_peek_ostack (ctx, 1);
+    if (_csi_unlikely (csi_object_get_type (a) != csi_object_get_type (b)))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    pop (2);
+    switch ((int) csi_object_get_type (a)) {
+    case CSI_OBJECT_TYPE_INTEGER:
+	return _csi_push_ostack_integer (ctx,
+					 a->datum.integer & b->datum.integer);
+    case CSI_OBJECT_TYPE_BOOLEAN:
+	return _csi_push_ostack_boolean (ctx,
+					 a->datum.boolean & b->datum.boolean);
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+}
+
+static csi_status_t
+_arc (csi_t *ctx)
+{
+    csi_status_t status;
+    double x, y, r;
+    double theta1, theta2;
+    cairo_t *cr;
+
+    check (6);
+
+    status = _csi_ostack_get_number (ctx, 0, &theta2);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &theta1);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 2, &r);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 3, &y);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 4, &x);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 5, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    /* XXX handle path object */
+
+    cairo_arc (cr, x, y, r, theta1, theta2);
+    pop (5);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_arc_negative (csi_t *ctx)
+{
+    csi_status_t status;
+    double x, y, r;
+    double theta1, theta2;
+    cairo_t *cr;
+
+    check (6);
+
+    status = _csi_ostack_get_number (ctx, 0, &theta2);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &theta1);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 2, &r);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 3, &y);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 4, &x);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 5, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    /* XXX handle path object */
+
+    cairo_arc_negative (cr, x, y, r, theta1, theta2);
+    pop (5);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_array (csi_t *ctx)
+{
+    csi_object_t obj;
+    csi_status_t status;
+
+    status = csi_array_new (ctx, &obj);
+    if (_csi_unlikely (status))
+	return status;
+
+    return push (&obj);
+}
+
+static csi_status_t
+_bind_substitute (csi_t *ctx, csi_array_t *array)
+{
+    csi_status_t status;
+    csi_integer_t i, n;
+    csi_dictionary_t *dict;
+
+    /* perform operator substitution on the executable array (procedure) */
+    dict = ctx->dstack.objects[0].datum.dictionary;
+    n = array->stack.len;
+    for (i = 0; i < n; i++) {
+	csi_object_t *obj = &array->stack.objects[i];
+
+	if (obj->type == (CSI_OBJECT_TYPE_NAME | CSI_OBJECT_ATTR_EXECUTABLE)) {
+	    csi_dictionary_entry_t *entry;
+
+	    entry = _csi_hash_table_lookup (&dict->hash_table,
+					    (csi_hash_entry_t *)
+					    &obj->datum.name);
+	    if (entry != NULL)
+		*obj = entry->value;
+	} else if (csi_object_is_procedure (obj)) {
+	    status = _bind_substitute (ctx, obj->datum.array);
+	    if (_csi_unlikely (status))
+		return status;
+	}
+    }
+
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_idiom_substitute (csi_t *ctx, csi_array_t *array)
+{
+#if 0
+    csi_status_t status;
+    csi_integer_t i, j;
+
+    /* XXX substring search, build array once then search for
+     * longest matching idiom, repeat. */
+
+    /* scan the top-most array for sequences we can pre-compile */
+
+    /* now recurse for subroutines */
+    j = array->stack.len;
+    for (i = 0; i < j; i++) {
+	csi_object_t *obj = &array->stack.objects[i];
+
+	if (csi_object_is_procedure (obj)) {
+	    status = _idiom_substitute (ctx, obj->datum.array);
+	    if (_csi_unlikely (_cairo_is_error (status))
+		return status;
+	}
+    }
+#endif
+
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_bind (csi_t *ctx)
+{
+    csi_array_t *array;
+    csi_status_t status;
+
+    check (1);
+
+    status = _csi_ostack_get_procedure (ctx, 0, &array);
+    if (_csi_unlikely (status))
+	return status;
+
+    status = _bind_substitute (ctx, array);
+    if (_csi_unlikely (status))
+	return status;
+
+    status = _idiom_substitute (ctx, array);
+    if (_csi_unlikely (status))
+	return status;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_bitshift (csi_t *ctx)
+{
+    long v, shift;
+    csi_status_t status;
+
+    check (2);
+
+    status = _csi_ostack_get_integer (ctx, 0, &shift);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_integer (ctx, 1, &v);
+    if (_csi_unlikely (status))
+	return status;
+
+    if (shift < 0) {
+	shift = -shift;
+	v <<= shift;
+    } else
+	v >>= shift;
+
+    pop (1);
+    _csi_peek_ostack (ctx, 0)->datum.integer = v;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_clip (csi_t *ctx)
+{
+    return _do_cairo_op (ctx, cairo_clip);
+}
+
+static csi_status_t
+_clip_preserve (csi_t *ctx)
+{
+    return _do_cairo_op (ctx, cairo_clip_preserve);
+}
+
+static csi_status_t
+_close_path (csi_t *ctx)
+{
+    return _do_cairo_op (ctx, cairo_close_path);
+}
+
+static csi_status_t
+_context (csi_t *ctx)
+{
+    csi_object_t obj;
+    csi_status_t status;
+    cairo_surface_t *surface;
+    cairo_t *cr;
+    csi_proxy_t *proxy;
+
+    check (1);
+
+    status = _csi_ostack_get_surface (ctx, 0, &surface);
+    if (_csi_unlikely (status))
+	return status;
+
+    cr = cairo_create (surface);
+
+    proxy = _csi_proxy_create (ctx, cr, NULL,
+			       ctx->hooks.context_destroy,
+			       ctx->hooks.closure);
+    if (_csi_unlikely (proxy == NULL)) {
+	cairo_destroy (cr);
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+    }
+
+    status = cairo_set_user_data (cr, &_csi_proxy_key,
+				  proxy, _csi_proxy_destroy);
+    if (_csi_unlikely (status)) {
+	_csi_proxy_destroy (proxy);
+	cairo_destroy (cr);
+	return status;
+    }
+
+    pop (1);
+    obj.type = CSI_OBJECT_TYPE_CONTEXT;
+    obj.datum.cr = cr;
+    return push (&obj);
+}
+
+static csi_status_t
+_copy (csi_t *ctx)
+{
+    csi_object_t *obj;
+
+    check (1);
+
+    obj = csi_object_reference (_csi_peek_ostack (ctx, 0));
+    pop (1);
+
+    switch ((int) csi_object_get_type (obj)) {
+	/*XXX array, string, dictionary, etc */
+    case CSI_OBJECT_TYPE_INTEGER:
+	{
+	    long i, n;
+
+	    n = obj->datum.integer;
+	    if (_csi_unlikely (n < 0))
+		return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	    check (n);
+
+	    for (i = n; i--; ) {
+		csi_status_t status;
+
+		status = _csi_push_ostack_copy (ctx,
+						_csi_peek_ostack (ctx, n-1));
+		if (_csi_unlikely (status))
+		    return status;
+	    }
+	    break;
+	}
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_copy_page (csi_t *ctx)
+{
+    csi_object_t *obj;
+
+    check (1);
+
+    obj = _csi_peek_ostack (ctx, 0);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_CONTEXT:
+	cairo_copy_page (obj->datum.cr);
+	if (ctx->hooks.copy_page != NULL)
+	    ctx->hooks.copy_page (ctx->hooks.closure, obj->datum.cr);
+	break;
+    case CSI_OBJECT_TYPE_SURFACE:
+	cairo_surface_copy_page (obj->datum.surface);
+	/* XXX hook? */
+	break;
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_curve_to (csi_t *ctx)
+{
+    csi_status_t status;
+    double x1, y1;
+    double x2, y2;
+    double x3, y3;
+    cairo_t *cr;
+
+    check (7);
+
+    status = _csi_ostack_get_number (ctx, 0, &y3);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &x3);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 2, &y2);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 3, &x2);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 4, &y1);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 5, &x1);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 6, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    /* XXX handle path object */
+
+    cairo_curve_to (cr, x1, y1, x2, y2, x3, y3);
+    pop (6);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_def (csi_t *ctx)
+{
+    csi_name_t name;
+    csi_status_t status;
+
+    check (2);
+
+    status = _csi_ostack_get_name (ctx, 1, &name);
+    if (_csi_unlikely (status))
+	return status;
+
+    status = _csi_name_define (ctx, name, _csi_peek_ostack (ctx, 0));
+    if (_csi_unlikely (status))
+	return status;
+
+    pop (2);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_dict (csi_t *ctx)
+{
+    csi_object_t obj;
+    csi_status_t status;
+
+    status = csi_dictionary_new (ctx, &obj);
+    if (_csi_unlikely (status))
+	return status;
+
+    return push (&obj);
+}
+
+static csi_status_t
+_div (csi_t *ctx)
+{
+    csi_object_t *A;
+    csi_object_t *B;
+    csi_object_type_t type_a, type_b;
+
+    check (2);
+
+    B = _csi_peek_ostack (ctx, 0);
+    A = _csi_peek_ostack (ctx, 1);
+
+    type_a = csi_object_get_type (A);
+    if (_csi_unlikely (! (type_a == CSI_OBJECT_TYPE_INTEGER ||
+			    type_a == CSI_OBJECT_TYPE_REAL)))
+    {
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    type_b = csi_object_get_type (B);
+    if (_csi_unlikely (! (type_b == CSI_OBJECT_TYPE_INTEGER ||
+			    type_b == CSI_OBJECT_TYPE_REAL)))
+    {
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    pop (2);
+
+    if (type_a == CSI_OBJECT_TYPE_REAL &&
+	type_b == CSI_OBJECT_TYPE_REAL)
+    {
+	return _csi_push_ostack_real (ctx, A->datum.real / B->datum.real);
+
+    }
+    else if (type_a == CSI_OBJECT_TYPE_INTEGER &&
+	     type_b == CSI_OBJECT_TYPE_INTEGER)
+    {
+	return _csi_push_ostack_integer (ctx,
+					 A->datum.integer / B->datum.integer);
+    }
+    else
+    {
+	double v;
+
+	if (type_a == CSI_OBJECT_TYPE_REAL)
+	    v = A->datum.real;
+	else
+	    v = A->datum.integer;
+
+	if (type_b == CSI_OBJECT_TYPE_REAL)
+	    v /= B->datum.real;
+	else
+	    v /= B->datum.integer;
+
+	return _csi_push_ostack_real (ctx, v);
+    }
+}
+
+static csi_status_t
+_dup (csi_t *ctx)
+{
+    check (1);
+
+    return _csi_push_ostack_copy (ctx, _csi_peek_ostack (ctx, 0));
+}
+
+static csi_status_t
+_eq (csi_t *ctx)
+{
+    csi_object_t *a, *b;
+    csi_boolean_t v;
+
+    check (2);
+
+    b = _csi_peek_ostack (ctx, 0);
+    a = _csi_peek_ostack (ctx, 1);
+
+    if (csi_object_get_type (a) != csi_object_get_type (b)) {
+	switch ((int) csi_object_get_type (a)) {
+	case CSI_OBJECT_TYPE_BOOLEAN:
+	    switch ((int) csi_object_get_type (b)) {
+	    case CSI_OBJECT_TYPE_INTEGER:
+		v = a->datum.boolean == !! b->datum.integer;
+		break;
+	    case CSI_OBJECT_TYPE_REAL:
+		v = a->datum.boolean == (b->datum.real != 0);
+		break;
+	    default:
+		return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	    }
+	    break;
+
+	case CSI_OBJECT_TYPE_INTEGER:
+	    switch ((int) csi_object_get_type (b)) {
+	    case CSI_OBJECT_TYPE_BOOLEAN:
+		v = a->datum.integer == b->datum.boolean;
+		break;
+	    case CSI_OBJECT_TYPE_REAL:
+		v = a->datum.integer == b->datum.real;
+		break;
+	    default:
+		return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	    }
+	    break;
+
+	case CSI_OBJECT_TYPE_REAL:
+	    switch ((int) csi_object_get_type (b)) {
+	    case CSI_OBJECT_TYPE_BOOLEAN:
+		v = a->datum.real == b->datum.boolean;
+		break;
+	    case CSI_OBJECT_TYPE_INTEGER:
+		v = a->datum.real == b->datum.integer;
+		break;
+	    default:
+		return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	    }
+	    break;
+
+	default:
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	}
+    } else {
+	if (CSI_OBJECT_IS_CAIRO (a)) {
+	    v = a->datum.ptr == b->datum.ptr;
+	} else if (CSI_OBJECT_IS_COMPOUND (a)) {
+	    v = a->datum.object == b->datum.object;
+	} else switch ((int) csi_object_get_type (a)) {
+	    case CSI_OBJECT_TYPE_BOOLEAN:
+		v = a->datum.boolean == b->datum.boolean;
+		break;
+	    case CSI_OBJECT_TYPE_INTEGER:
+		v = a->datum.integer == b->datum.integer;
+		break;
+	    case CSI_OBJECT_TYPE_REAL:
+		v = a->datum.real == b->datum.real;
+		break;
+	    default:
+		return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	}
+    }
+
+    pop (2);
+    return _csi_push_ostack_boolean (ctx, v);
+}
+
+static csi_status_t
+_exch (csi_t *ctx)
+{
+    return _csi_stack_exch (&ctx->ostack);
+}
+
+static csi_status_t
+_false (csi_t *ctx)
+{
+    return _csi_push_ostack_boolean (ctx, FALSE);
+}
+
+static csi_status_t
+_fill (csi_t *ctx)
+{
+    return _do_cairo_op (ctx, cairo_fill);
+}
+
+static csi_status_t
+_fill_preserve (csi_t *ctx)
+{
+    return _do_cairo_op (ctx, cairo_fill_preserve);
+}
+
+static csi_status_t
+_filter (csi_t *ctx)
+{
+    csi_object_t *src;
+    csi_dictionary_t *dict = NULL;
+    csi_status_t status;
+    const char *name;
+    const struct filters {
+	const char *name;
+	csi_status_t (*constructor) (csi_t *t,
+				       csi_object_t *,
+				       csi_dictionary_t *,
+				       csi_object_t *);
+    } filters[] = {
+	{ "ascii85", csi_file_new_ascii85_decode },
+	{ "deflate", csi_file_new_deflate_decode },
+#if 0
+	{ "lzw", csi_file_new_lzw_decode },
+#endif
+	{ NULL, NULL }
+    }, *filter;
+    int cnt;
+
+    check (2);
+
+    status = _csi_ostack_get_string_constant (ctx, 0, &name);
+    if (_csi_unlikely (status))
+	return status;
+
+    src = _csi_peek_ostack (ctx, 1);
+    cnt = 2;
+    if (csi_object_get_type (src) == CSI_OBJECT_TYPE_DICTIONARY) {
+	dict = src->datum.dictionary;
+
+	check (3);
+
+	src = _csi_peek_ostack (ctx, 2);
+	cnt = 3;
+    }
+
+    for (filter = filters; filter->name != NULL; filter++) {
+	if (strcmp (name, filter->name) == 0) {
+	    csi_object_t file;
+
+	    status = filter->constructor (ctx, &file, dict, src);
+	    if (_csi_unlikely (status))
+		return status;
+
+	    pop (cnt);
+	    return push (&file);
+	}
+    }
+
+    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+}
+
+static cairo_status_t
+_type3_init (cairo_scaled_font_t *scaled_font,
+	     cairo_t *cr,
+	     cairo_font_extents_t *metrics)
+{
+    cairo_font_face_t *face;
+    csi_proxy_t *proxy;
+    csi_t *ctx;
+    csi_dictionary_t *font;
+    csi_object_t key;
+    csi_object_t obj;
+    csi_array_t *array;
+    csi_status_t status;
+
+    face = cairo_scaled_font_get_font_face (scaled_font);
+    proxy = cairo_font_face_get_user_data (face, &_csi_proxy_key);
+    if (_csi_unlikely (proxy == NULL))
+	return CAIRO_STATUS_NO_MEMORY;
+
+    ctx = proxy->ctx;
+    font = proxy->dictionary;
+
+    status = csi_name_new_static (ctx, &key, "metrics");
+    if (_csi_unlikely (status))
+	return CAIRO_STATUS_NO_MEMORY;
+
+    if (! csi_dictionary_has (font, key.datum.name))
+	return CAIRO_STATUS_SUCCESS;
+
+    status = csi_dictionary_get (ctx, font, key.datum.name, &obj);
+    if (_csi_unlikely (status))
+	return status;
+
+    if (csi_object_get_type (&obj) != CSI_OBJECT_TYPE_ARRAY)
+	return CAIRO_STATUS_USER_FONT_ERROR;
+
+    array = obj.datum.array;
+    if (array->stack.len != 5)
+	return CAIRO_STATUS_USER_FONT_ERROR;
+
+    metrics->ascent  = csi_number_get_value (&array->stack.objects[0]);
+    metrics->descent = csi_number_get_value (&array->stack.objects[1]);
+    metrics->height  = csi_number_get_value (&array->stack.objects[2]);
+    metrics->max_x_advance = csi_number_get_value (&array->stack.objects[3]);
+    metrics->max_y_advance = csi_number_get_value (&array->stack.objects[4]);
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_type3_lookup (cairo_scaled_font_t *scaled_font,
+	       unsigned long unicode,
+	       unsigned long *glyph)
+{
+    cairo_font_face_t *face;
+    csi_proxy_t *proxy;
+    csi_t *ctx;
+    csi_dictionary_t *font;
+    csi_object_t obj, key;
+    csi_array_t *array;
+    char buf[12];
+    csi_integer_t i;
+    cairo_status_t status;
+
+    face = cairo_scaled_font_get_font_face (scaled_font);
+    proxy = cairo_font_face_get_user_data (face, &_csi_proxy_key);
+    if (_csi_unlikely (proxy == NULL))
+	return CAIRO_STATUS_USER_FONT_ERROR;
+
+    ctx = proxy->ctx;
+    font = proxy->dictionary;
+
+    status = csi_name_new_static (ctx, &key, "encoding");
+    if (_csi_unlikely (status))
+	return CAIRO_STATUS_USER_FONT_ERROR;
+
+    if (! csi_dictionary_has (font, key.datum.name)) {
+	*glyph = unicode;
+	return CAIRO_STATUS_SUCCESS;
+    }
+
+    status = csi_dictionary_get (ctx, font, key.datum.name, &obj);
+    if (_csi_unlikely (status))
+	return CAIRO_STATUS_USER_FONT_ERROR;
+
+    if (_csi_unlikely (csi_object_get_type (&obj) != CSI_OBJECT_TYPE_ARRAY))
+	return CAIRO_STATUS_USER_FONT_ERROR;
+
+    snprintf (buf, sizeof (buf), "uni%04lu", unicode);
+    array = obj.datum.array;
+    for (i = 0; i < array->stack.len; i++) {
+	csi_object_t *name;
+
+	name = &array->stack.objects[i];
+	if (csi_object_get_type (name) != CSI_OBJECT_TYPE_NAME)
+	    continue;
+
+	if (strcmp ((char *) name->datum.name, buf) == 0) {
+	    *glyph = i;
+	    return CAIRO_STATUS_SUCCESS;
+	}
+    }
+
+    return CAIRO_STATUS_USER_FONT_ERROR;
+}
+
+static cairo_status_t
+_type3_render (cairo_scaled_font_t *scaled_font,
+	       unsigned long glyph_index,
+	       cairo_t *cr,
+	       cairo_text_extents_t *metrics)
+{
+    cairo_font_face_t *face;
+    csi_proxy_t *proxy;
+    csi_t *ctx;
+    csi_dictionary_t *font;
+    csi_array_t *glyphs;
+    csi_object_t *glyph;
+    csi_object_t key;
+    csi_object_t obj;
+    csi_object_t render;
+    csi_status_t status;
+
+    face = cairo_scaled_font_get_font_face (scaled_font);
+    proxy = cairo_font_face_get_user_data (face, &_csi_proxy_key);
+    if (_csi_unlikely (proxy == NULL))
+	return CAIRO_STATUS_USER_FONT_ERROR;
+
+    ctx = proxy->ctx;
+    font = proxy->dictionary;
+
+    status = csi_name_new_static (ctx, &key, "glyphs");
+    if (_csi_unlikely (status))
+	return CAIRO_STATUS_USER_FONT_ERROR;
+
+    status = csi_dictionary_get (ctx, font, key.datum.name, &obj);
+    if (_csi_unlikely (status))
+	return CAIRO_STATUS_USER_FONT_ERROR;
+
+    if (_csi_unlikely (csi_object_get_type (&obj) != CSI_OBJECT_TYPE_ARRAY))
+	return CAIRO_STATUS_USER_FONT_ERROR;
+
+    glyphs = obj.datum.array;
+    glyph = &glyphs->stack.objects[glyph_index];
+    if (csi_object_get_type (glyph) == CSI_OBJECT_TYPE_NULL)
+	return CAIRO_STATUS_SUCCESS; /* .notdef */
+
+    if (_csi_unlikely (csi_object_get_type (glyph) != CSI_OBJECT_TYPE_DICTIONARY))
+	return CAIRO_STATUS_USER_FONT_ERROR;
+
+    status = csi_name_new_static (ctx, &key, "metrics");
+    if (_csi_unlikely (status))
+	return CAIRO_STATUS_USER_FONT_ERROR;
+
+    font = glyph->datum.dictionary;
+    if (csi_dictionary_has (font, key.datum.name)) {
+	csi_array_t *array;
+
+	status = csi_dictionary_get (ctx, font, key.datum.name, &obj);
+	if (_csi_unlikely (status))
+	    return CAIRO_STATUS_USER_FONT_ERROR;
+
+	if (_csi_unlikely (csi_object_get_type (&obj) !=
+			     CSI_OBJECT_TYPE_ARRAY))
+	    return CAIRO_STATUS_USER_FONT_ERROR;
+
+	array = obj.datum.array;
+	if (_csi_unlikely (array->stack.len != 6))
+	    return CAIRO_STATUS_USER_FONT_ERROR;
+
+	metrics->x_bearing = csi_number_get_value (&array->stack.objects[0]);
+	metrics->y_bearing = csi_number_get_value (&array->stack.objects[1]);
+	metrics->width = csi_number_get_value (&array->stack.objects[2]);
+	metrics->height = csi_number_get_value (&array->stack.objects[3]);
+	metrics->x_advance = csi_number_get_value (&array->stack.objects[4]);
+	metrics->y_advance = csi_number_get_value (&array->stack.objects[5]);
+    }
+
+    status = csi_name_new_static (ctx, &key, "render");
+    if (_csi_unlikely (status))
+	return CAIRO_STATUS_USER_FONT_ERROR;
+
+    status = csi_dictionary_get (ctx, font, key.datum.name, &render);
+    if (_csi_unlikely (status))
+	return CAIRO_STATUS_USER_FONT_ERROR;
+
+    if (_csi_unlikely (! csi_object_is_procedure (&render)))
+	return CAIRO_STATUS_USER_FONT_ERROR;
+
+    obj.type = CSI_OBJECT_TYPE_CONTEXT;
+    obj.datum.cr = cairo_reference (cr);
+    status = push (&obj);
+    if (_csi_unlikely (status)) {
+	cairo_destroy (cr);
+	return CAIRO_STATUS_USER_FONT_ERROR;
+    }
+
+    status = csi_object_execute (ctx, &render);
+    pop (1);
+    return CAIRO_STATUS_USER_FONT_ERROR;
+}
+
+static csi_status_t
+_font_type3 (csi_t *ctx,
+	     csi_dictionary_t *font,
+	     cairo_font_face_t **font_face_out)
+{
+    cairo_font_face_t *font_face;
+
+    font_face = cairo_user_font_face_create ();
+    cairo_user_font_face_set_init_func (font_face, _type3_init);
+    cairo_user_font_face_set_unicode_to_glyph_func (font_face, _type3_lookup);
+    cairo_user_font_face_set_render_glyph_func (font_face, _type3_render);
+
+    *font_face_out = font_face;
+    return CSI_STATUS_SUCCESS;
+}
+
+#if CAIRO_HAS_FT_FONT
+#include <cairo-ft.h>
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+static FT_Library _ft_lib;
+
+struct _ft_face_data {
+    csi_t *ctx;
+    csi_blob_t blob;
+    FT_Face face;
+    csi_string_t *source;
+    cairo_font_face_t *font_face;
+};
+
+static void
+_ft_done_face (void *closure)
+{
+    struct _ft_face_data *data = closure;
+    csi_t *ctx;
+
+    ctx = data->ctx;
+
+    if (data->face != NULL)
+	FT_Done_Face (data->face);
+
+    ctx->_faces = _csi_list_unlink (ctx->_faces, &data->blob.list);
+
+    if (--data->source->base.ref == 0)
+	csi_string_free (ctx, data->source);
+    _csi_slab_free (ctx, data, sizeof (*data));
+
+    cairo_script_interpreter_destroy (ctx);
+}
+
+static csi_status_t
+_ft_create_for_source (csi_t *ctx,
+		       csi_string_t *source,
+		       int index, int load_flags,
+		       cairo_font_face_t **font_face_out)
+{
+    csi_blob_t tmpl;
+    struct _ft_face_data *data;
+    csi_list_t *link;
+    FT_Face face;
+    FT_Error err;
+    cairo_font_face_t *font_face;
+    csi_status_t status;
+
+    /* check for an existing FT_Face (kept alive by the font cache) */
+    /* XXX index/flags */
+    _csi_blob_init (&tmpl, (uint8_t *) source->string, source->len);
+    link = _csi_list_find (ctx->_faces, _csi_blob_equal, &tmpl);
+    if (link) {
+	if (--source->base.ref == 0)
+	    csi_string_free (ctx, source);
+	data = csi_container_of (link, struct _ft_face_data, blob.list);
+	*font_face_out = cairo_font_face_reference (data->font_face);
+	return CSI_STATUS_SUCCESS;
+    }
+
+    /* no existing font_face, create new FT_Face */
+    if (_ft_lib == NULL) {
+	err = FT_Init_FreeType (&_ft_lib);
+	if (_csi_unlikely (err != FT_Err_Ok))
+	    return _csi_error (CSI_STATUS_NO_MEMORY);
+    }
+
+    err = FT_New_Memory_Face (_ft_lib,
+			      (uint8_t *) source->string,
+			      source->len, index,
+			      &face);
+    if (_csi_unlikely (err != FT_Err_Ok)) {
+	if (err == FT_Err_Out_Of_Memory)
+	    return _csi_error (CSI_STATUS_NO_MEMORY);
+
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    data = _csi_slab_alloc (ctx, sizeof (*data));
+    ctx->_faces = _csi_list_prepend (ctx->_faces, &data->blob.list);
+    data->ctx = cairo_script_interpreter_reference (ctx);
+    data->blob.hash = tmpl.hash;
+    data->blob.bytes = tmpl.bytes;
+    data->blob.len = tmpl.len;
+    data->face = face;
+    data->source = source;
+
+    font_face = cairo_ft_font_face_create_for_ft_face (face, load_flags);
+    status = cairo_font_face_set_user_data (font_face,
+					    &_csi_blob_key,
+					    data, _ft_done_face);
+    if (_csi_unlikely (status)) {
+	_ft_done_face (data);
+	cairo_font_face_destroy (font_face);
+	return status;
+    }
+
+    data->font_face = font_face;
+    *font_face_out = font_face;
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_ft_create_for_pattern (csi_t *ctx,
+			csi_string_t *string,
+			cairo_font_face_t **font_face_out)
+{
+    csi_blob_t tmpl;
+    struct _ft_face_data *data;
+    csi_list_t *link;
+    cairo_font_face_t *font_face;
+    FcPattern *pattern, *resolved;
+    FcResult result;
+    csi_status_t status;
+
+    _csi_blob_init (&tmpl, (uint8_t *) string->string, string->len);
+    link = _csi_list_find (ctx->_faces, _csi_blob_equal, &tmpl);
+    if (link) {
+	if (--string->base.ref == 0)
+	    csi_string_free (ctx, string);
+	data = csi_container_of (link, struct _ft_face_data, blob.list);
+	*font_face_out = cairo_font_face_reference (data->font_face);
+	return CSI_STATUS_SUCCESS;
+    }
+
+    pattern = FcNameParse ((FcChar8 *) string->string);
+    if (_csi_unlikely (pattern == NULL))
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+
+    FcConfigSubstitute (NULL, pattern, FcMatchPattern);
+    FcDefaultSubstitute (pattern);
+
+    resolved = FcFontMatch (NULL, pattern, &result);
+    if (_csi_unlikely (resolved == NULL)) {
+	FcPatternDestroy (pattern);
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+    }
+
+    font_face = cairo_ft_font_face_create_for_pattern (resolved);
+
+    FcPatternDestroy (resolved);
+    FcPatternDestroy (pattern);
+
+    data = _csi_slab_alloc (ctx, sizeof (*data));
+    ctx->_faces = _csi_list_prepend (ctx->_faces, &data->blob.list);
+    data->ctx = cairo_script_interpreter_reference (ctx);
+    data->blob.hash = tmpl.hash;
+    data->blob.bytes = tmpl.bytes;
+    data->blob.len = tmpl.len;
+    data->face = NULL;
+    data->source = string;
+
+    status = cairo_font_face_set_user_data (font_face,
+					    &_csi_blob_key,
+					    data, _ft_done_face);
+    if (_csi_unlikely (status)) {
+	_ft_done_face (data);
+	cairo_font_face_destroy (font_face);
+	return status;
+    }
+
+    data->font_face = font_face;
+    *font_face_out = font_face;
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_ft_type42_create (csi_t *ctx,
+		   csi_dictionary_t *font,
+		   cairo_font_face_t **font_face_out)
+{
+    csi_object_t key;
+    csi_status_t status;
+
+    /* two basic sub-types, either an FcPattern or embedded font */
+    status = csi_name_new_static (ctx, &key, "pattern");
+    if (csi_dictionary_has (font, key.datum.name)) {
+	csi_object_t obj;
+
+	status = csi_dictionary_get (ctx, font, key.datum.name, &obj);
+	if (_csi_unlikely (status))
+	    return status;
+
+	switch ((int) csi_object_get_type (&obj)) {
+	case CSI_OBJECT_TYPE_FILE:
+	    status = _csi_file_as_string (ctx, obj.datum.file, &obj);
+	    if (_csi_unlikely (status))
+		return status;
+	    break;
+	case CSI_OBJECT_TYPE_STRING:
+	    obj.datum.object->ref++;
+	    break;
+	default:
+	    return  _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	}
+
+	return _ft_create_for_pattern (ctx,
+				       obj.datum.string,
+				       font_face_out);
+    }
+
+    status = csi_name_new_static (ctx, &key, "source");
+    if (_csi_unlikely (status))
+	return status;
+
+    if (csi_dictionary_has (font, key.datum.name)) {
+	csi_object_t obj;
+	long index, flags;
+
+	index = 0;
+	status = _csi_dictionary_get_integer (ctx, font, "index", TRUE, &index);
+	if (_csi_unlikely (status))
+	    return status;
+
+	flags = 0;
+	status = _csi_dictionary_get_integer (ctx, font, "flags", TRUE, &flags);
+	if (_csi_unlikely (status))
+	    return status;
+
+	status = csi_name_new_static (ctx, &key, "source");
+	if (_csi_unlikely (status))
+	    return status;
+	status = csi_dictionary_get (ctx, font, key.datum.name, &obj);
+	if (_csi_unlikely (status))
+	    return status;
+	switch ((int) csi_object_get_type (&obj)) {
+	case CSI_OBJECT_TYPE_FILE:
+	    status = _csi_file_as_string (ctx, obj.datum.file, &obj);
+	    if (_csi_unlikely (status))
+		return status;
+	    break;
+	case CSI_OBJECT_TYPE_STRING:
+	    obj.datum.object->ref++;
+	    break;
+	default:
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	}
+
+	return _ft_create_for_source (ctx, obj.datum.string,
+				      index, flags,
+				      font_face_out);
+    }
+
+    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+}
+#else
+#define _ft_type1_create(font, face_out) CSI_INT_STATUS_UNSUPPORTED
+#define _ft_type42_create(font, face_out) CSI_INT_STATUS_UNSUPPORTED
+#endif
+
+static csi_status_t
+_font_type42 (csi_t *ctx, csi_dictionary_t *font, cairo_font_face_t **font_face)
+{
+    csi_status_t status;
+
+    status = _ft_type42_create (ctx, font, font_face);
+    if (_csi_likely (status != CSI_INT_STATUS_UNSUPPORTED))
+	return status;
+
+    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+}
+
+static csi_status_t
+_font (csi_t *ctx)
+{
+    csi_dictionary_t *font;
+    csi_status_t status;
+    cairo_font_face_t *font_face;
+    csi_proxy_t *proxy;
+    csi_object_t obj;
+    long type;
+
+    check (1);
+
+    status = _csi_ostack_get_dictionary (ctx, 0, &font);
+    if (_csi_unlikely (status))
+	return status;
+
+    status = _csi_dictionary_get_integer (ctx, font, "type", FALSE, &type);
+    if (_csi_unlikely (status))
+	return status;
+
+    switch (type) {
+    case 3:
+	status = _font_type3 (ctx, font, &font_face);
+	break;
+    case 42:
+	status = _font_type42 (ctx, font, &font_face);
+	break;
+    default:
+	status = _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	break;
+    }
+
+    if (_csi_unlikely (status))
+	return status;
+
+    /* transfer ownership of dictionary to cairo_font_face_t */
+    proxy = _csi_proxy_create (ctx, font_face, font, NULL, NULL);
+    if (_csi_likely (proxy == NULL)) {
+	cairo_font_face_destroy (font_face);
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+    }
+
+    status = cairo_font_face_set_user_data (font_face,
+					    &_csi_proxy_key,
+					    proxy, _csi_proxy_destroy);
+    if (_csi_unlikely (status)) {
+	_csi_proxy_destroy (proxy);
+	cairo_font_face_destroy (font_face);
+	return status;
+    }
+
+    obj.type = CSI_OBJECT_TYPE_FONT;
+    obj.datum.font_face = font_face;
+
+    pop (1);
+    status = push (&obj);
+    if (_csi_unlikely (status)) {
+	cairo_font_face_destroy (font_face);
+	return status;
+    }
+
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_for (csi_t *ctx)
+{
+    csi_array_t *proc;
+    csi_status_t status;
+    long i, inc, limit;
+
+    check (4);
+
+    status = _csi_ostack_get_procedure (ctx, 0, &proc);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_integer (ctx, 1, &limit);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_integer (ctx, 2, &inc);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_integer (ctx, 3, &i);
+    if (_csi_unlikely (status))
+	return status;
+
+    proc->base.ref++;
+    pop (4);
+
+    for (; i <= limit; i += inc) {
+	status = _csi_push_ostack_integer (ctx, i);
+	if (_csi_unlikely (status))
+	    break;
+
+	status = _csi_array_execute (ctx, proc);
+	if (_csi_unlikely (status))
+	    break;
+    }
+
+    if (--proc->base.ref == 0)
+	csi_array_free (ctx, proc);
+    return status;
+}
+
+static csi_status_t
+_ge (csi_t *ctx)
+{
+    csi_object_t *a, *b;
+    csi_boolean_t v;
+
+    check (2);
+
+    b = _csi_peek_ostack (ctx, 0);
+    a = _csi_peek_ostack (ctx, 1);
+
+    if (csi_object_get_type (a) != csi_object_get_type (b)) {
+	if (csi_object_is_number (a) && csi_object_is_number (b)) {
+	    double ia, ib;
+	    ia = csi_number_get_value (a);
+	    ib = csi_number_get_value (b);
+	    v = ia >= ib;
+	} else {
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	}
+    } else switch ((int) csi_object_get_type (a)) {
+    case CSI_OBJECT_TYPE_BOOLEAN:
+	v = a->datum.boolean >= b->datum.boolean;
+	break;
+    case CSI_OBJECT_TYPE_INTEGER:
+	v = a->datum.integer >= b->datum.integer;
+	break;
+    case CSI_OBJECT_TYPE_REAL:
+	v = a->datum.real >= b->datum.real;
+	break;
+    case CSI_OBJECT_TYPE_STRING:
+	v = strcmp (a->datum.string->string, b->datum.string->string) >= 0;
+	break;
+    case CSI_OBJECT_TYPE_NAME:
+	v = strcmp ((char *) a->datum.name, (char *) b->datum.name) >= 0;
+	break;
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    pop (2);
+    return _csi_push_ostack_boolean (ctx, v);
+}
+
+static csi_status_t
+_proxy_get (csi_proxy_t *proxy,
+	    csi_name_t key)
+{
+    csi_object_t obj;
+    csi_status_t status;
+
+    if (_csi_unlikely (proxy == NULL || proxy->dictionary == NULL))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    status = csi_dictionary_get (proxy->ctx, proxy->dictionary, key, &obj);
+    if (_csi_unlikely (status))
+	return status;
+
+    return _csi_push_ostack_copy (proxy->ctx, &obj);
+}
+
+static csi_status_t
+_context_get (csi_t *ctx,
+	      cairo_t *cr,
+	      csi_name_t key)
+{
+    csi_status_t status;
+
+    if (strcmp ((char *) key, "current-point") == 0) {
+	double x, y;
+
+	cairo_get_current_point (cr, &x, &y);
+
+	status = _csi_push_ostack_real (ctx, x);
+	if (_csi_unlikely (status))
+	    return status;
+	status = _csi_push_ostack_real (ctx, y);
+	if (_csi_unlikely (status))
+	    return status;
+
+	return CSI_STATUS_SUCCESS;
+    }
+
+    if (strcmp ((char *) key, "source") == 0) {
+	csi_object_t obj;
+
+	obj.type = CSI_OBJECT_TYPE_PATTERN;
+	obj.datum.pattern = cairo_pattern_reference (cairo_get_source (cr));
+	return push (&obj);
+    }
+
+    if (strcmp ((char *) key, "target") == 0) {
+	csi_object_t obj;
+
+	obj.type = CSI_OBJECT_TYPE_SURFACE;
+	obj.datum.surface = cairo_surface_reference (cairo_get_target (cr));
+	return push (&obj);
+    }
+
+    if (strcmp ((char *) key, "group-target") == 0) {
+	csi_object_t obj;
+
+	obj.type = CSI_OBJECT_TYPE_SURFACE;
+	obj.datum.surface = cairo_surface_reference (cairo_get_group_target (cr));
+	return push (&obj);
+    }
+
+    if (strcmp ((char *) key, "scaled-font") == 0) {
+	csi_object_t obj;
+
+	obj.type = CSI_OBJECT_TYPE_SCALED_FONT;
+	obj.datum.scaled_font = cairo_scaled_font_reference (cairo_get_scaled_font (cr));
+	return push (&obj);
+    }
+
+    if (strcmp ((char *) key, "font-face") == 0) {
+	csi_object_t obj;
+
+	obj.type = CSI_OBJECT_TYPE_FONT;
+	obj.datum.font_face = cairo_font_face_reference (cairo_get_font_face (cr));
+	return push (&obj);
+    }
+
+    return _proxy_get (cairo_get_user_data (cr, &_csi_proxy_key), key);
+}
+
+static csi_status_t
+_font_get (csi_t *ctx,
+	   cairo_font_face_t *font_face,
+	   csi_name_t key)
+{
+    return _proxy_get (cairo_font_face_get_user_data (font_face,
+						      &_csi_proxy_key),
+		       key);
+}
+
+static csi_status_t
+_pattern_get (csi_t *ctx,
+	      cairo_pattern_t *pattern,
+	      csi_name_t key)
+{
+    csi_status_t status;
+
+    if (strcmp ((char *) key, "type") == 0)
+	return _csi_push_ostack_integer (ctx, cairo_pattern_get_type (pattern));
+
+    if (strcmp ((char *) key, "filter") == 0)
+	return _csi_push_ostack_integer (ctx, cairo_pattern_get_filter (pattern));
+
+    if (strcmp ((char *) key, "extend") == 0)
+	return _csi_push_ostack_integer (ctx, cairo_pattern_get_extend (pattern));
+
+    if (strcmp ((char *) key, "matrix") == 0) {
+	csi_object_t obj;
+	cairo_matrix_t m;
+
+	cairo_pattern_get_matrix (pattern, &m);
+	status = csi_matrix_new_from_matrix (ctx, &obj, &m);
+	if (_csi_unlikely (status))
+	    return status;
+
+	return push (&obj);
+    }
+
+    return _proxy_get (cairo_pattern_get_user_data (pattern, &_csi_proxy_key),
+		       key);
+}
+
+static csi_status_t
+_scaled_font_get (csi_t *ctx,
+		  cairo_scaled_font_t *font,
+		  csi_name_t key)
+{
+    return _proxy_get (cairo_scaled_font_get_user_data (font, &_csi_proxy_key),
+		       key);
+}
+
+static csi_status_t
+_surface_get (csi_t *ctx,
+	      cairo_surface_t *surface,
+	      csi_name_t key)
+{
+    if (strcmp ((char *) key, "type") == 0) {
+	return _csi_push_ostack_integer (ctx, cairo_surface_get_type (surface));
+    }
+
+    if (strcmp ((char *) key, "content") == 0) {
+	return _csi_push_ostack_integer (ctx,
+					 cairo_surface_get_content (surface));
+    }
+
+    return _proxy_get (cairo_surface_get_user_data (surface, &_csi_proxy_key),
+		       key);
+}
+
+static csi_status_t
+_get (csi_t *ctx)
+{
+    csi_object_t *key, *src, obj;
+    csi_status_t status;
+
+    check (2);
+
+    key = _csi_peek_ostack (ctx, 0);
+    src = _csi_peek_ostack (ctx, 1);
+    pop (1);
+    switch ((int) csi_object_get_type (src)) {
+    case CSI_OBJECT_TYPE_DICTIONARY:
+	if (_csi_unlikely (csi_object_get_type (key) !=
+			     CSI_OBJECT_TYPE_NAME))
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+	status = csi_dictionary_get (ctx,
+				     src->datum.dictionary,
+				     key->datum.name,
+				     &obj);
+	break;
+    case CSI_OBJECT_TYPE_ARRAY:
+	if (_csi_unlikely (csi_object_get_type (key) !=
+			     CSI_OBJECT_TYPE_INTEGER))
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+	status = csi_array_get (ctx,
+				src->datum.array,
+				key->datum.integer,
+				&obj);
+	break;
+#if 0
+    case CSI_OBJECT_TYPE_STRING:
+	status = csi_string_get (src, key, &obj);
+	break;
+#endif
+
+    case CSI_OBJECT_TYPE_CONTEXT:
+	if (_csi_unlikely (csi_object_get_type (key) !=
+			     CSI_OBJECT_TYPE_NAME))
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	return _context_get (ctx, src->datum.cr, key->datum.name);
+
+    case CSI_OBJECT_TYPE_FONT:
+	if (_csi_unlikely (csi_object_get_type (key) !=
+			     CSI_OBJECT_TYPE_NAME))
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	return _font_get (ctx, src->datum.font_face, key->datum.name);
+
+    case CSI_OBJECT_TYPE_PATTERN:
+	if (_csi_unlikely (csi_object_get_type (key) !=
+			     CSI_OBJECT_TYPE_NAME))
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	return _pattern_get (ctx, src->datum.pattern, key->datum.name);
+
+    case CSI_OBJECT_TYPE_SCALED_FONT:
+	if (_csi_unlikely (csi_object_get_type (key) !=
+			     CSI_OBJECT_TYPE_NAME))
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	return _scaled_font_get (ctx, src->datum.scaled_font, key->datum.name);
+
+    case CSI_OBJECT_TYPE_SURFACE:
+	if (_csi_unlikely (csi_object_get_type (key) !=
+			     CSI_OBJECT_TYPE_NAME))
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	return _surface_get (ctx, src->datum.surface, key->datum.name);
+
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    if (_csi_unlikely (status))
+	return status;
+
+    return _csi_push_ostack_copy (ctx, &obj);
+}
+
+static csi_status_t
+_glyph_path (csi_t *ctx)
+{
+    csi_object_t *obj;
+    csi_array_t *array;
+    csi_array_t *glyph_array;
+    csi_string_t *glyph_string;
+    csi_status_t status;
+    cairo_t *cr;
+    cairo_scaled_font_t *scaled_font;
+    cairo_glyph_t stack_glyphs[256], *glyphs;
+    double x,y;
+    csi_integer_t nglyphs, i, j;
+    double glyph_advance[256][2];
+    int have_glyph_advance[256];
+
+    check (2);
+
+    status = _csi_ostack_get_array (ctx, 0, &array);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    /* count glyphs */
+    nglyphs = 0;
+    for (i = 0; i < array->stack.len; i++) {
+	obj = &array->stack.objects[i];
+	switch ((int) csi_object_get_type (obj)) {
+	case CSI_OBJECT_TYPE_ARRAY:
+	    nglyphs += obj->datum.array->stack.len;
+	    break;
+	case CSI_OBJECT_TYPE_STRING:
+	    nglyphs += obj->datum.string->len;
+	    break;
+	}
+    }
+    if (nglyphs == 0) {
+	pop (1);
+	return CSI_STATUS_SUCCESS;
+    }
+
+    if (nglyphs > ARRAY_LENGTH (stack_glyphs)) {
+	if (_csi_unlikely ((unsigned) nglyphs >= INT32_MAX / sizeof (cairo_glyph_t)))
+	    return _csi_error (CSI_STATUS_NO_MEMORY);
+	glyphs = _csi_alloc (ctx, sizeof (cairo_glyph_t) * nglyphs);
+	if (_csi_unlikely (glyphs == NULL))
+	    return _csi_error (CSI_STATUS_NO_MEMORY);
+    } else
+	glyphs = stack_glyphs;
+
+    scaled_font = cairo_get_scaled_font (cr);
+
+    nglyphs = 0;
+    memset (have_glyph_advance, 0, sizeof (have_glyph_advance));
+    x = y = 0;
+    for (i = 0; i < array->stack.len; i++) {
+	obj = &array->stack.objects[i];
+	switch ((int) csi_object_get_type (obj)) {
+	case CSI_OBJECT_TYPE_ARRAY: /* glyphs */
+	    glyph_array = obj->datum.array;
+	    for (j = 0; j < glyph_array->stack.len; j++) {
+		unsigned long g;
+		cairo_bool_t have_advance;
+
+		obj = &glyph_array->stack.objects[j];
+		if (csi_object_get_type (obj) != CSI_OBJECT_TYPE_INTEGER)
+		    break;
+		g = obj->datum.integer;
+
+		glyphs[nglyphs].index = g;
+		glyphs[nglyphs].x = x;
+		glyphs[nglyphs].y = y;
+
+		if (g < ARRAY_LENGTH (have_glyph_advance)) {
+		    if (! have_glyph_advance[g]) {
+			cairo_text_extents_t extents;
+
+			cairo_scaled_font_glyph_extents (scaled_font,
+							 &glyphs[nglyphs], 1,
+							 &extents);
+
+			glyph_advance[g][0] = extents.x_advance;
+			glyph_advance[g][1] = extents.y_advance;
+			have_glyph_advance[g] = TRUE;
+
+		    }
+
+		    have_advance = glyph_advance[g][0] != 0.0;
+		    x += glyph_advance[g][0];
+		    y += glyph_advance[g][1];
+		} else {
+		    cairo_text_extents_t extents;
+
+		    cairo_scaled_font_glyph_extents (scaled_font,
+						     &glyphs[nglyphs], 1,
+						     &extents);
+
+		    have_advance = extents.x_advance != 0.0;
+		    x += extents.x_advance;
+		    y += extents.y_advance;
+		}
+
+		nglyphs += have_advance;
+	    }
+	    break;
+
+	case CSI_OBJECT_TYPE_STRING: /* glyphs */
+	    glyph_string = obj->datum.string;
+	    for (j = 0; j < glyph_string->len; j++) {
+		uint8_t g;
+		cairo_bool_t have_advance;
+
+		g = glyph_string->string[j];
+		glyphs[nglyphs].index = g;
+		glyphs[nglyphs].x = x;
+		glyphs[nglyphs].y = y;
+
+		if (! have_glyph_advance[g]) {
+		    cairo_text_extents_t extents;
+
+		    cairo_scaled_font_glyph_extents (scaled_font,
+						     &glyphs[nglyphs], 1,
+						     &extents);
+
+		    glyph_advance[g][0] = extents.x_advance;
+		    glyph_advance[g][1] = extents.y_advance;
+		    have_glyph_advance[g] = TRUE;
+		}
+
+		have_advance = glyph_advance[g][0] != 0.0;
+		x += glyph_advance[g][0];
+		y += glyph_advance[g][1];
+
+		nglyphs += have_advance;
+	    }
+	    break;
+
+	case CSI_OBJECT_TYPE_INTEGER:
+	case CSI_OBJECT_TYPE_REAL: /* dx */
+	    x = csi_number_get_value (obj);
+	    if (++i == array->stack.len)
+		break;
+	    y = csi_number_get_value (&array->stack.objects[i]);
+	    break;
+	}
+    }
+
+    cairo_glyph_path (cr, glyphs, nglyphs);
+
+    if (glyphs != stack_glyphs)
+	_csi_free (ctx, glyphs);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_gray (csi_t *ctx)
+{
+    csi_object_t obj;
+    csi_status_t status;
+    double g;
+
+    check (1);
+
+    status = _csi_ostack_get_number (ctx, 0, &g);
+    if (_csi_unlikely (status))
+	return status;
+
+    pop (1);
+
+    obj.type = CSI_OBJECT_TYPE_PATTERN;
+    obj.datum.pattern = cairo_pattern_create_rgba (g, g, g, 1);
+    return push (&obj);
+}
+
+static csi_status_t
+_gt (csi_t *ctx)
+{
+    csi_object_t *a, *b;
+    csi_boolean_t v;
+
+    check (2);
+
+    b = _csi_peek_ostack (ctx, 0);
+    a = _csi_peek_ostack (ctx, 1);
+
+    if (_csi_unlikely (csi_object_get_type (a) != csi_object_get_type (b))) {
+	if (_csi_likely (csi_object_is_number (a) && csi_object_is_number (b))){
+	    double ia, ib;
+	    ia = csi_number_get_value (a);
+	    ib = csi_number_get_value (b);
+	    v = ia > ib;
+	} else {
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	}
+    } else switch ((int) csi_object_get_type (a)) {
+    case CSI_OBJECT_TYPE_BOOLEAN:
+	v = a->datum.boolean > b->datum.boolean;
+	break;
+    case CSI_OBJECT_TYPE_INTEGER:
+	v = a->datum.integer > b->datum.integer;
+	break;
+    case CSI_OBJECT_TYPE_REAL:
+	v = a->datum.real > b->datum.real;
+	break;
+    case CSI_OBJECT_TYPE_STRING:
+	v = strcmp (a->datum.string->string, b->datum.string->string) > 0;
+	break;
+    case CSI_OBJECT_TYPE_NAME:
+	v = strcmp ((char *) a->datum.name, (char *) b->datum.name) > 0;
+	break;
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    pop (2);
+    return _csi_push_ostack_boolean (ctx, v);
+}
+
+static csi_status_t
+_identity (csi_t *ctx)
+{
+    csi_object_t obj;
+    csi_status_t status;
+
+    status = csi_matrix_new (ctx, &obj);
+    if (_csi_unlikely (status))
+	return status;
+
+    return push (&obj);
+}
+
+static csi_status_t
+_if (csi_t *ctx)
+{
+    csi_array_t *proc;
+    csi_boolean_t predicate;
+    csi_status_t status;
+
+    check (2);
+
+    status = _csi_ostack_get_procedure (ctx, 0, &proc);
+    if (_csi_unlikely (status))
+	return status;
+
+    status = _csi_ostack_get_boolean (ctx, 1, &predicate);
+    if (_csi_unlikely (status))
+	return status;
+
+    proc->base.ref++;
+    pop (2);
+
+    if (predicate)
+	status = _csi_array_execute (ctx, proc);
+
+    if (--proc->base.ref == 0)
+	csi_array_free (ctx, proc);
+
+    return status;
+}
+
+static csi_status_t
+_ifelse (csi_t *ctx)
+{
+    csi_array_t *true_proc, *false_proc;
+    csi_boolean_t predicate;
+    csi_status_t status;
+
+    check (3);
+
+    status = _csi_ostack_get_procedure (ctx, 0, &false_proc);
+    if (_csi_unlikely (status))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    status = _csi_ostack_get_procedure (ctx, 1, &true_proc);
+    if (_csi_unlikely (status))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    status = _csi_ostack_get_boolean (ctx, 2, &predicate);
+    if (_csi_unlikely (status))
+	return status;
+
+    true_proc->base.ref++;
+    false_proc->base.ref++;
+    pop (3);
+
+    if (predicate)
+	status = _csi_array_execute (ctx, true_proc);
+    else
+	status = _csi_array_execute (ctx, false_proc);
+
+    if (--true_proc->base.ref == 0)
+	csi_array_free (ctx, true_proc);
+    if (--false_proc->base.ref == 0)
+	csi_array_free (ctx, false_proc);
+
+    return status;
+}
+
+static csi_status_t
+_image_read_raw (csi_file_t *src,
+		 cairo_format_t format,
+		 int width, int height,
+		 cairo_surface_t **image_out)
+{
+    cairo_surface_t *image;
+    uint8_t *bp, *data;
+    int rem, len, ret, x, stride;
+    cairo_status_t status;
+
+    image = cairo_image_surface_create (format, width, height);
+    status = cairo_surface_status (image);
+    if (status)
+	return status;
+
+    switch (format) {
+    case CAIRO_FORMAT_A1:
+	len = (width+7)/8 * height;
+	break;
+    case CAIRO_FORMAT_A8:
+	len = width * height;
+	break;
+    case CAIRO_FORMAT_RGB24:
+	len = 3 * width * height;
+	break;
+    case CAIRO_FORMAT_ARGB32:
+	len = 4 * width * height;
+	break;
+    default:
+	break;
+    }
+
+    stride = cairo_image_surface_get_stride (image);
+    data = cairo_image_surface_get_data (image);
+    bp = data;
+    rem = len;
+    while (rem) {
+	ret = csi_file_read (src, bp, rem);
+	if (_csi_unlikely (ret == 0)) {
+	    cairo_surface_destroy (image);
+	    return _csi_error (CSI_STATUS_READ_ERROR);
+	}
+	rem -= ret;
+	bp += ret;
+    }
+
+    if (len != height * stride) {
+	while (--height) {
+	    uint8_t *row = data + height * stride;
+
+	    /* XXX pixel conversion */
+	    switch (format) {
+	    case CAIRO_FORMAT_A1:
+		for (x = (width+7)/8; x--; ) {
+		    uint8_t byte = *--bp;
+		    row[x] = CSI_BITSWAP8_IF_LITTLE_ENDIAN (byte);
+		}
+		break;
+	    case CAIRO_FORMAT_A8:
+		for (x = width; x--; )
+		    row[x] = *--bp;
+		break;
+	    case CAIRO_FORMAT_RGB24:
+		for (x = width; x--; ) {
+#ifdef WORDS_BIGENDIAN
+		    row[4*x + 3] = *--bp;
+		    row[4*x + 2] = *--bp;
+		    row[4*x + 1] = *--bp;
+		    row[4*x + 0] = 0;
+#else
+		    row[4*x + 0] = *--bp;
+		    row[4*x + 1] = *--bp;
+		    row[4*x + 2] = *--bp;
+		    row[4*x + 3] = 0;
+#endif
+		}
+		break;
+	    case CAIRO_FORMAT_ARGB32:
+		/* stride == width */
+		break;
+	    }
+	}
+
+	/* need to treat last row carefully */
+	switch (format) {
+	case CAIRO_FORMAT_A1:
+	    for (x = (width+7)/8; x--; ) {
+		uint8_t byte = *--bp;
+		data[x] = CSI_BITSWAP8_IF_LITTLE_ENDIAN (byte);
+	    }
+	    break;
+	case CAIRO_FORMAT_A8:
+	    for (x = width; x--; )
+		data[x] = *--bp;
+	    break;
+	case CAIRO_FORMAT_RGB24:
+	    for (x = width; --x>1; ) {
+#ifdef WORDS_BIGENDIAN
+		data[4*x + 3] = *--bp;
+		data[4*x + 2] = *--bp;
+		data[4*x + 1] = *--bp;
+		data[4*x + 0] = 0;
+#else
+		data[4*x + 0] = *--bp;
+		data[4*x + 1] = *--bp;
+		data[4*x + 2] = *--bp;
+		data[4*x + 3] = 0;
+#endif
+	    }
+	    if (width > 1) {
+		uint8_t rgb[2][3];
+		/* shuffle the last couple of overlapping pixels */
+		rgb[1][0] = data[5];
+		rgb[1][1] = data[4];
+		rgb[1][2] = data[3];
+		rgb[0][0] = data[2];
+		rgb[0][1] = data[1];
+		rgb[0][2] = data[0];
+#ifdef WORDS_BIGENDIAN
+		data[4] = 0;
+		data[5] = rgb[1][2];
+		data[6] = rgb[1][1];
+		data[7] = rgb[1][0];
+		data[0] = 0;
+		data[1] = rgb[0][2];
+		data[2] = rgb[0][1];
+		data[3] = rgb[0][0];
+#else
+		data[7] = 0;
+		data[6] = rgb[1][2];
+		data[5] = rgb[1][1];
+		data[4] = rgb[1][0];
+		data[3] = 0;
+		data[2] = rgb[0][2];
+		data[1] = rgb[0][1];
+		data[0] = rgb[0][0];
+#endif
+	    } else {
+#ifdef WORDS_BIGENDIAN
+		data[0] = 0;
+		data[1] = data[0];
+		data[2] = data[1];
+		data[3] = data[2];
+#else
+		data[3] = data[0];
+		data[0] = data[2];
+		data[2] = data[3];
+		data[3] = 0;
+#endif
+	    }
+	    break;
+	case CAIRO_FORMAT_ARGB32:
+	    /* stride == width */
+	    break;
+	}
+    } else {
+#ifndef WORDS_BIGENDIAN
+	switch (format) {
+	case CAIRO_FORMAT_A1:
+	    for (x = 0; x < len; x++) {
+		uint8_t byte = data[x];
+		data[x] = CSI_BITSWAP8_IF_LITTLE_ENDIAN (byte);
+	    }
+	    break;
+	case CAIRO_FORMAT_ARGB32:
+	    {
+		uint32_t *rgba = (uint32_t *) data;
+		for (x = len/4; x--; rgba++) {
+		    *rgba = bswap_32 (*rgba);
+		}
+	    }
+	    break;
+
+	case CAIRO_FORMAT_A8:
+	    break;
+
+	case CAIRO_FORMAT_RGB24:
+	default:
+	    break;
+	}
+#endif
+    }
+
+    *image_out = image;
+    return CSI_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+png_read_func (void *closure, uint8_t *data, unsigned int len)
+{
+    int ret;
+
+    ret = csi_file_read (closure, data, len);
+    if ((unsigned int) ret != len)
+	return CAIRO_STATUS_READ_ERROR;
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_image_read_png (csi_file_t *src, cairo_surface_t **out)
+{
+    *out = cairo_image_surface_create_from_png_stream (png_read_func, src);
+    return cairo_surface_status (*out);
+}
+
+struct _image_tag {
+    csi_t *ctx;
+    csi_blob_t blob;
+    cairo_surface_t *surface;
+};
+
+static void
+_image_tag_done (void *closure)
+{
+    struct _image_tag *tag = closure;
+    csi_t *ctx = tag->ctx;
+
+    ctx->_images = _csi_list_unlink (ctx->_images, &tag->blob.list);
+    _csi_slab_free (ctx, tag, sizeof (*tag));
+    cairo_script_interpreter_destroy (ctx);
+}
+
+static cairo_surface_t *
+_image_cached (csi_t *ctx, cairo_surface_t *surface)
+{
+    csi_blob_t tmpl;
+    csi_list_t *link;
+    uint8_t *data;
+    int stride, height;
+    struct _image_tag *tag;
+
+    /* check for an existing image  */
+
+    data = cairo_image_surface_get_data (surface);
+    stride = cairo_image_surface_get_stride (surface);
+    height = cairo_image_surface_get_height (surface);
+    _csi_blob_init (&tmpl, data, stride * height);
+    link = _csi_list_find (ctx->_images, _csi_blob_equal, &tmpl);
+    if (link) {
+	cairo_surface_destroy (surface);
+	tag = csi_container_of (link, struct _image_tag, blob.list);
+	return cairo_surface_reference (tag->surface);
+    }
+
+    /* none found, insert a tag for this one */
+
+    tag = _csi_slab_alloc (ctx, sizeof (struct _image_tag));
+    if (tag == NULL)
+	return surface;
+
+    ctx->_images = _csi_list_prepend (ctx->_images, &tag->blob.list);
+    tag->ctx = cairo_script_interpreter_reference (ctx);
+    tag->blob.hash = tmpl.hash;
+    tag->blob.bytes = tmpl.bytes;
+    tag->blob.len = tmpl.len;
+    tag->surface = surface;
+
+    if (cairo_surface_set_user_data (surface, &_csi_blob_key,
+				     tag, _image_tag_done))
+    {
+	_image_tag_done (tag);
+    }
+
+    return surface;
+}
+
+static csi_status_t
+_image_load_from_dictionary (csi_t *ctx,
+			     csi_dictionary_t *dict,
+			     cairo_surface_t **image_out)
+{
+    csi_object_t obj, key;
+    long width;
+    long height;
+    long format;
+    cairo_surface_t *image;
+    csi_status_t status;
+
+    /* check for "status? */
+
+    status = _csi_dictionary_get_integer (ctx, dict, "width", FALSE, &width);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_dictionary_get_integer (ctx, dict, "height", FALSE, &height);
+    if (_csi_unlikely (status))
+	return status;
+
+    format = CAIRO_FORMAT_ARGB32;
+    status = _csi_dictionary_get_integer (ctx, dict, "format", TRUE, &format);
+    if (_csi_unlikely (status))
+	return status;
+
+    status = csi_name_new_static (ctx, &key, "source");
+    if (csi_dictionary_has (dict, key.datum.name)) {
+	enum mime_type type;
+	csi_object_t file;
+
+	status = csi_dictionary_get (ctx, dict, key.datum.name, &obj);
+	if (_csi_unlikely (status))
+	    return status;
+
+	status = csi_name_new_static (ctx, &key, "mime-type");
+	if (_csi_unlikely (status))
+	    return status;
+
+	type = MIME_TYPE_NONE;
+	if (csi_dictionary_has (dict, key.datum.name)) {
+	    csi_object_t type_obj;
+	    const char *type_str;
+
+	    status = csi_dictionary_get (ctx, dict, key.datum.name, &type_obj);
+	    if (_csi_unlikely (status))
+		return status;
+
+	    switch ((int) csi_object_get_type (&type_obj)){
+	    case CSI_OBJECT_TYPE_STRING:
+		type_str = type_obj.datum.string->string;
+		break;
+	    case CSI_OBJECT_TYPE_NAME:
+		type_str = (char *) type_obj.datum.name;
+		break;
+	    default:
+		return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	    }
+
+	    if (strcmp (type_str, CAIRO_MIME_TYPE_PNG) == 0)
+		type = MIME_TYPE_PNG;
+	}
+
+	status = csi_object_as_file (ctx, &obj, &file);
+	if (_csi_unlikely (status))
+	    return status;
+
+	/* XXX hook for general mime-type decoder */
+
+	switch (type) {
+	case MIME_TYPE_NONE:
+	    status = _image_read_raw (file.datum.file,
+				      format, width, height, &image);
+	    break;
+	case MIME_TYPE_PNG:
+	    status = _image_read_png (file.datum.file, &image);
+	    break;
+	}
+	csi_object_free (ctx, &file);
+	if (_csi_unlikely (status))
+	    return status;
+
+	image = _image_cached (ctx, image);
+    } else
+	image = cairo_image_surface_create (format, width, height);
+
+    *image_out = image;
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_image (csi_t *ctx)
+{
+    csi_dictionary_t *dict;
+    cairo_surface_t *image;
+    csi_status_t status;
+    csi_object_t obj;
+
+    check (1);
+
+    status = _csi_ostack_get_dictionary (ctx, 0, &dict);
+    if (_csi_unlikely (status))
+	return status;
+
+    status = _image_load_from_dictionary (ctx, dict, &image);
+    if (_csi_unlikely (status))
+	return status;
+
+    pop (1);
+    obj.type = CSI_OBJECT_TYPE_SURFACE;
+    obj.datum.surface = image;
+    return push (&obj);
+}
+
+static csi_status_t
+_index (csi_t *ctx)
+{
+    csi_status_t status;
+    long n;
+
+    check (1);
+
+    status = _csi_ostack_get_integer (ctx, 0,  &n);
+    if (_csi_unlikely (status))
+	return status;
+
+    pop (1);
+
+    check (n);
+    return _csi_push_ostack_copy (ctx, _csi_peek_ostack (ctx, n));
+}
+
+static csi_status_t
+_le (csi_t *ctx)
+{
+    csi_object_t *a, *b;
+    csi_boolean_t v;
+
+    check (2);
+
+    b = _csi_peek_ostack (ctx, 0);
+    a = _csi_peek_ostack (ctx, 1);
+
+    if (_csi_unlikely (csi_object_get_type (a) != csi_object_get_type (b))) {
+	if (_csi_likely (csi_object_is_number (a) && csi_object_is_number (b))) {
+	    double ia, ib;
+	    ia = csi_number_get_value (a);
+	    ib = csi_number_get_value (b);
+	    v = ia <= ib;
+	} else {
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	}
+    } else switch ((int) csi_object_get_type (a)) {
+    case CSI_OBJECT_TYPE_BOOLEAN:
+	v = a->datum.boolean <= b->datum.boolean;
+	break;
+    case CSI_OBJECT_TYPE_INTEGER:
+	v = a->datum.integer <= b->datum.integer;
+	break;
+    case CSI_OBJECT_TYPE_REAL:
+	v = a->datum.real <= b->datum.real;
+	break;
+    case CSI_OBJECT_TYPE_STRING:
+	v = strcmp (a->datum.string->string, b->datum.string->string) <= 0;
+	break;
+    case CSI_OBJECT_TYPE_NAME:
+	v = strcmp ((char *) a->datum.name, (char *) b->datum.name) <= 0;
+	break;
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    pop (2);
+    return _csi_push_ostack_boolean (ctx, v);
+}
+
+static csi_status_t
+_linear (csi_t *ctx)
+{
+    csi_object_t obj;
+    csi_status_t status;
+    double x1, y1, x2, y2;
+
+    check (4);
+
+    status = _csi_ostack_get_number (ctx, 0, &y2);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &x2);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 2, &y1);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 3, &x1);
+    if (_csi_unlikely (status))
+	return status;
+
+    pop (4);
+
+    obj.type = CSI_OBJECT_TYPE_PATTERN;
+    obj.datum.pattern = cairo_pattern_create_linear (x1, y1, x2, y2);
+    return push (&obj);
+}
+
+static csi_status_t
+_line_to (csi_t *ctx)
+{
+    csi_status_t status;
+    double x, y;
+    cairo_t *cr;
+
+    check (3);
+
+    status = _csi_ostack_get_number (ctx, 0, &y);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &x);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 2, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    /* XXX path object */
+
+    pop (2);
+    cairo_line_to (cr, x, y);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_lt (csi_t *ctx)
+{
+    csi_object_t *a, *b;
+    csi_boolean_t v;
+
+    check (2);
+
+    b = _csi_peek_ostack (ctx, 0);
+    a = _csi_peek_ostack (ctx, 1);
+
+    if (_csi_unlikely (csi_object_get_type (a) != csi_object_get_type (b))) {
+	if (_csi_likely (csi_object_is_number (a) && csi_object_is_number (b))) {
+	    double ia, ib;
+	    ia = csi_number_get_value (a);
+	    ib = csi_number_get_value (b);
+	    v = ia < ib;
+	} else {
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	}
+    } else switch ((int) csi_object_get_type (a)) {
+    case CSI_OBJECT_TYPE_BOOLEAN:
+	v = a->datum.boolean < b->datum.boolean;
+	break;
+    case CSI_OBJECT_TYPE_INTEGER:
+	v = a->datum.integer < b->datum.integer;
+	break;
+    case CSI_OBJECT_TYPE_REAL:
+	v = a->datum.real < b->datum.real;
+	break;
+    case CSI_OBJECT_TYPE_STRING:
+	v = strcmp (a->datum.string->string, b->datum.string->string) < 0;
+	break;
+    case CSI_OBJECT_TYPE_NAME:
+	v = strcmp ((char *) a->datum.name, (char *) b->datum.name) < 0;
+	break;
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    pop (2);
+    return _csi_push_ostack_boolean (ctx, v);
+}
+
+static csi_status_t
+_mark (csi_t *ctx)
+{
+    return _csi_push_ostack_mark (ctx);
+}
+
+static csi_status_t
+_neg (csi_t *ctx)
+{
+    csi_object_t *obj;
+
+    check (1);
+
+    obj = _csi_peek_ostack (ctx, 0);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_INTEGER:
+	obj->datum.integer = -obj->datum.integer;
+	break;
+    case CSI_OBJECT_TYPE_REAL:
+	obj->datum.real = -obj->datum.real;
+	break;
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_not (csi_t *ctx)
+{
+    csi_object_t *obj;
+
+    check (1);
+
+    obj = _csi_peek_ostack (ctx, 0);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_BOOLEAN:
+	obj->datum.boolean = ! obj->datum.boolean;
+	break;
+    case CSI_OBJECT_TYPE_INTEGER:
+	obj->type = CSI_OBJECT_TYPE_BOOLEAN;
+	obj->datum.boolean = ! obj->datum.integer;
+	break;
+    case CSI_OBJECT_TYPE_REAL:
+	obj->type = CSI_OBJECT_TYPE_BOOLEAN;
+	obj->datum.boolean = obj->datum.real == 0.0;
+	break;
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_new_path (csi_t *ctx)
+{
+    /* XXX handle path object */
+    return _do_cairo_op (ctx, cairo_new_path);
+}
+
+static csi_status_t
+_new_sub_path (csi_t *ctx)
+{
+    /* XXX handle path object */
+    return _do_cairo_op (ctx, cairo_new_sub_path);
+}
+
+static csi_status_t
+_mask (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_t *cr;
+    cairo_pattern_t *pattern;
+
+    check (2);
+
+    status = _csi_ostack_get_pattern (ctx, 0, &pattern);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_mask (cr, pattern);
+    pop (1);
+
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_matrix (csi_t *ctx)
+{
+    csi_object_t *obj, matrix;
+    double v[6];
+    csi_status_t status;
+    int n;
+
+    check (1);
+
+    obj = _csi_peek_ostack (ctx, 0);
+    if (csi_object_is_number (obj)) {
+	check (6);
+
+	for (n = 6; n--; ) {
+	    status = _csi_ostack_get_number (ctx, 5-n, &v[n]);
+	    if (_csi_unlikely (status))
+		return status;
+	}
+	status = csi_matrix_new_from_values (ctx, &matrix, v);
+	if (_csi_unlikely (status))
+	    return status;
+
+	pop (6);
+    } else {
+	csi_array_t *array;
+
+	status = _csi_ostack_get_array (ctx, 0, &array);
+	if (_csi_unlikely (status))
+	    return status;
+
+	status = csi_matrix_new_from_array (ctx, &matrix, array);
+	if (_csi_unlikely (status))
+	    return status;
+
+	pop (1);
+    }
+
+    return push (&matrix);
+}
+
+static csi_status_t
+_move_to (csi_t *ctx)
+{
+    csi_status_t status;
+    double x, y;
+    cairo_t *cr;
+
+    check (3);
+
+    status = _csi_ostack_get_number (ctx, 0, &y);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &x);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 2, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    /* XXX path object */
+
+    pop (2);
+    cairo_move_to (cr, x, y);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_mul (csi_t *ctx)
+{
+    csi_object_t *A;
+    csi_object_t *B;
+    csi_object_type_t type_a, type_b;
+
+    check (2);
+
+    B = _csi_peek_ostack (ctx, 0);
+    A = _csi_peek_ostack (ctx, 1);
+
+    type_a = csi_object_get_type (A);
+    if (_csi_unlikely (! (type_a == CSI_OBJECT_TYPE_INTEGER ||
+			    type_a == CSI_OBJECT_TYPE_REAL)))
+    {
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+    type_b = csi_object_get_type (B);
+    if (_csi_unlikely (! (type_b == CSI_OBJECT_TYPE_INTEGER ||
+			    type_b == CSI_OBJECT_TYPE_REAL)))
+    {
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    pop (2);
+
+    if (type_a == CSI_OBJECT_TYPE_REAL &&
+	type_b == CSI_OBJECT_TYPE_REAL)
+    {
+	return _csi_push_ostack_real (ctx, A->datum.real * B->datum.real);
+
+    }
+    else if (type_a == CSI_OBJECT_TYPE_INTEGER &&
+	     type_b == CSI_OBJECT_TYPE_INTEGER)
+    {
+	return _csi_push_ostack_integer (ctx,
+					 A->datum.integer * B->datum.integer);
+    }
+    else
+    {
+	double v;
+
+	if (type_a == CSI_OBJECT_TYPE_REAL)
+	    v = A->datum.real;
+	else
+	    v = A->datum.integer;
+
+	if (type_b == CSI_OBJECT_TYPE_REAL)
+	    v *= B->datum.real;
+	else
+	    v *= B->datum.integer;
+
+	return _csi_push_ostack_real (ctx, v);
+    }
+}
+
+static csi_status_t
+_or (csi_t *ctx)
+{
+    csi_object_t *a, *b;
+
+    check (2);
+
+    a = _csi_peek_ostack (ctx, 0);
+    b = _csi_peek_ostack (ctx, 1);
+    if (_csi_unlikely (csi_object_get_type (a) != csi_object_get_type (b)))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    pop (2);
+    switch ((int) csi_object_get_type (a)) {
+    case CSI_OBJECT_TYPE_INTEGER:
+	return _csi_push_ostack_integer (ctx,
+					 a->datum.integer | b->datum.integer);
+    case CSI_OBJECT_TYPE_BOOLEAN:
+	return _csi_push_ostack_boolean (ctx,
+					 a->datum.boolean | b->datum.boolean);
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+}
+
+static csi_status_t
+_paint (csi_t *ctx)
+{
+    return _do_cairo_op (ctx, cairo_paint);
+}
+
+static csi_status_t
+_paint_with_alpha (csi_t *ctx)
+{
+    cairo_t *cr;
+    csi_status_t status;
+    double alpha;
+
+    check (2);
+
+    status = _csi_ostack_get_number (ctx, 0, &alpha);
+    if (_csi_unlikely (status))
+	return status;
+
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_paint_with_alpha (cr, alpha);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_pattern (csi_t *ctx)
+{
+    csi_object_t obj;
+    csi_status_t status;
+    cairo_surface_t *surface;
+
+    check (1);
+
+    status = _csi_ostack_get_surface (ctx, 0, &surface);
+    if (_csi_unlikely (status))
+	return status;
+
+    obj.type = CSI_OBJECT_TYPE_PATTERN;
+    obj.datum.pattern = cairo_pattern_create_for_surface (surface);
+
+    pop (1);
+    return push (&obj);
+}
+
+static csi_status_t
+_pop (csi_t *ctx)
+{
+    check (1);
+    pop (1);
+
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_pop_group (csi_t *ctx)
+{
+    csi_object_t obj;
+    csi_status_t status;
+    cairo_t *cr;
+
+    check (1);
+
+    status = _csi_ostack_get_context (ctx, 0, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    obj.type = CSI_OBJECT_TYPE_PATTERN;
+    obj.datum.pattern = cairo_pop_group (cr);
+
+    return push (&obj);
+}
+
+static csi_status_t
+_push_group (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_t *cr;
+    long content;
+
+    check (2);
+
+    status = _csi_ostack_get_integer (ctx, 0, &content);
+    if (_csi_unlikely (status))
+	return status;
+
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_push_group_with_content (cr, content);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_radial (csi_t *ctx)
+{
+    csi_object_t obj;
+    csi_status_t status;
+    double x1, y1, r1, x2, y2, r2;
+
+    check (6);
+
+    status = _csi_ostack_get_number (ctx, 0, &r2);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &y2);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 2, &x2);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 3, &r1);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 4, &y1);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 5, &x1);
+    if (_csi_unlikely (status))
+	return status;
+
+    obj.type = CSI_OBJECT_TYPE_PATTERN;
+    obj.datum.pattern = cairo_pattern_create_radial (x1, y1, r1, x2, y2, r2);
+    pop (6);
+    return push (&obj);
+}
+
+static csi_status_t
+_rectangle (csi_t *ctx)
+{
+    csi_status_t status;
+    double x, y;
+    double w, h;
+    cairo_t *cr;
+
+    check (5);
+
+    status = _csi_ostack_get_number (ctx, 0, &h);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &w);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 2, &y);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 3, &x);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 4, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    /* XXX path object */
+
+    cairo_rectangle (cr, x, y, w, h);
+    pop(4);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_rel_curve_to (csi_t *ctx)
+{
+    csi_status_t status;
+    double x1, y1;
+    double x2, y2;
+    double x3, y3;
+    cairo_t *cr;
+
+    check (7);
+
+    status = _csi_ostack_get_number (ctx, 0, &y3);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &x3);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 2, &y2);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 3, &x2);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 4, &y1);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 5, &x1);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 6, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    /* XXX path object */
+
+    cairo_rel_curve_to (cr, x1, y1, x2, y2, x3, y3);
+    pop (6);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_rel_line_to (csi_t *ctx)
+{
+    csi_status_t status;
+    double x, y;
+    cairo_t *cr;
+
+    check (3);
+
+    status = _csi_ostack_get_number (ctx, 0, &y);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &x);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 2, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    /* XXX path object */
+
+    cairo_rel_line_to (cr, x, y);
+    pop (2);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_rel_move_to (csi_t *ctx)
+{
+    csi_status_t status;
+    double x, y;
+    cairo_t *cr;
+
+    check (3);
+
+    status = _csi_ostack_get_number (ctx, 0, &y);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &x);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 2, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    /* XXX path object */
+    cairo_rel_move_to (cr, x, y);
+    pop (2);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_reset_clip (csi_t *ctx)
+{
+    return _do_cairo_op (ctx, cairo_reset_clip);
+}
+
+static csi_status_t
+_restore (csi_t *ctx)
+{
+    return _do_cairo_op (ctx, cairo_restore);
+}
+
+static csi_status_t
+_rgb (csi_t *ctx)
+{
+    csi_object_t obj;
+    csi_status_t status;
+    double r,g,b;
+
+    check (3);
+
+    status = _csi_ostack_get_number (ctx, 0, &b);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &g);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 2, &r);
+    if (_csi_unlikely (status))
+	return status;
+
+    obj.type = CSI_OBJECT_TYPE_PATTERN;
+    obj.datum.pattern = cairo_pattern_create_rgb (r, g, b);
+    pop (3);
+    return push (&obj);
+}
+
+static csi_status_t
+_rgba (csi_t *ctx)
+{
+    csi_object_t obj;
+    csi_status_t status;
+    double r,g,b,a;
+
+    check (4);
+
+    status = _csi_ostack_get_number (ctx, 0, &a);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &b);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 2, &g);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 3, &r);
+    if (_csi_unlikely (status))
+	return status;
+
+    obj.type = CSI_OBJECT_TYPE_PATTERN;
+    obj.datum.pattern = cairo_pattern_create_rgba (r, g, b, a);
+    pop (4);
+    return push (&obj);
+}
+
+static csi_status_t
+_roll (csi_t *ctx)
+{
+    csi_status_t status;
+    long j, n;
+
+    check (2);
+
+    status = _csi_ostack_get_integer (ctx, 0, &j);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_integer (ctx, 1, &n);
+    if (_csi_unlikely (status))
+	return status;
+
+    pop (2);
+    check (n);
+    return _csi_stack_roll (ctx, &ctx->ostack, j, n);
+}
+
+static csi_status_t
+_rotate (csi_t *ctx)
+{
+    csi_object_t *obj;
+    csi_status_t status;
+    double theta;
+
+    check (2);
+
+    status = _csi_ostack_get_number (ctx, 0, &theta);
+    if (_csi_unlikely (status))
+	return status;
+
+    obj = _csi_peek_ostack (ctx, 1);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_CONTEXT:
+	cairo_rotate (obj->datum.cr, theta);
+	break;
+
+    case CSI_OBJECT_TYPE_PATTERN:
+	{
+	    cairo_matrix_t ctm;
+	    cairo_pattern_get_matrix (obj->datum.pattern, &ctm);
+	    cairo_matrix_rotate (&ctm, theta);
+	    cairo_pattern_set_matrix (obj->datum.pattern, &ctm);
+	}
+	break;
+
+
+    case CSI_OBJECT_TYPE_MATRIX:
+	cairo_matrix_rotate (&obj->datum.matrix->matrix, theta);
+	break;
+
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_save (csi_t *ctx)
+{
+    return _do_cairo_op (ctx, cairo_save);
+}
+
+static csi_status_t
+_scale (csi_t *ctx)
+{
+    csi_object_t *obj;
+    csi_status_t status;
+    double x, y;
+
+    check (3);
+
+    status = _csi_ostack_get_number (ctx, 0, &y);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &x);
+    if (_csi_unlikely (status))
+	return status;
+
+    obj = _csi_peek_ostack (ctx, 2);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_CONTEXT:
+	cairo_scale (obj->datum.cr, x, y);
+	break;
+
+    case CSI_OBJECT_TYPE_PATTERN:
+	{
+	    cairo_matrix_t ctm;
+	    cairo_pattern_get_matrix (obj->datum.pattern, &ctm);
+	    cairo_matrix_scale (&ctm, x, y);
+	    cairo_pattern_set_matrix (obj->datum.pattern, &ctm);
+	}
+	break;
+
+
+    case CSI_OBJECT_TYPE_MATRIX:
+	cairo_matrix_scale (&obj->datum.matrix->matrix, x, y);
+	break;
+
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    pop (2);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_font_options_load_from_dictionary (csi_t *ctx,
+				    csi_dictionary_t *dict,
+				    cairo_font_options_t *options)
+{
+    const struct {
+	const char *key;
+	void (*setter) (cairo_font_options_t *, int val);
+    } properties[] = {
+	{ "antialias",
+	    (void (*)(cairo_font_options_t *, int val))
+		cairo_font_options_set_antialias },
+	{ "subpixel-order",
+	    (void (*)(cairo_font_options_t *, int val))
+		cairo_font_options_set_subpixel_order },
+	{ "hint-style",
+	    (void (*)(cairo_font_options_t *, int val))
+		cairo_font_options_set_hint_style },
+	{ "hint-metrics",
+	    (void (*)(cairo_font_options_t *, int val))
+		cairo_font_options_set_hint_metrics },
+	{ NULL, NULL },
+    }, *prop = properties;
+
+    while (prop->key != NULL) {
+	csi_object_t key, value;
+	csi_status_t status;
+
+	status = csi_name_new_static (ctx, &key, prop->key);
+	if (_csi_unlikely (status))
+	    return status;
+
+	if (csi_dictionary_has (dict, key.datum.name)) {
+	    status = csi_dictionary_get (ctx, dict, key.datum.name, &value);
+	    if (_csi_unlikely (status))
+		return status;
+
+	    if (_csi_unlikely (csi_object_get_type (&value) !=
+				 CSI_OBJECT_TYPE_INTEGER))
+	    {
+		csi_object_free (ctx, &value);
+		return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	    }
+
+	    prop->setter (options, value.datum.integer);
+	}
+
+	prop++;
+    }
+
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_scaled_font (csi_t *ctx)
+{
+    csi_object_t obj;
+    csi_dictionary_t *dict;
+    csi_status_t status;
+    cairo_font_face_t *font_face;
+    cairo_matrix_t font_matrix, ctm;
+    cairo_font_options_t *options;
+
+    check (4);
+
+    status = _csi_ostack_get_dictionary (ctx, 0, &dict);
+    if (_csi_unlikely (status))
+	return status;
+    options = cairo_font_options_create ();
+    status = _font_options_load_from_dictionary (ctx, dict, options);
+    if (_csi_unlikely (status)) {
+	cairo_font_options_destroy (options);
+	return status;
+    }
+
+    status = _csi_ostack_get_matrix (ctx, 1, &ctm);
+    if (_csi_unlikely (status)) {
+	cairo_font_options_destroy (options);
+	return status;
+    }
+
+    status = _csi_ostack_get_matrix (ctx, 2, &font_matrix);
+    if (_csi_unlikely (status)) {
+	cairo_font_options_destroy (options);
+	return status;
+    }
+
+    status = _csi_ostack_get_font_face (ctx, 3, &font_face);
+    if (_csi_unlikely (status)) {
+	cairo_font_options_destroy (options);
+	return status;
+    }
+
+    obj.type = CSI_OBJECT_TYPE_SCALED_FONT;
+    obj.datum.scaled_font = cairo_scaled_font_create (font_face,
+						      &font_matrix,
+						      &ctm,
+						      options);
+    cairo_font_options_destroy (options);
+    pop (4);
+    return push (&obj);
+}
+
+static csi_status_t
+_select_font_face (csi_t *ctx)
+{
+    cairo_t *cr;
+    long weight;
+    long slant;
+    csi_string_t *family;
+    csi_status_t status;
+
+    check (4);
+
+    status = _csi_ostack_get_integer (ctx, 0,  &weight);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_integer (ctx, 1, &slant);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_string (ctx, 2, &family);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 3, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_select_font_face (cr, family->string, slant, weight);
+    pop (3);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_context_set (csi_t *ctx,
+	      cairo_t *cr,
+	      csi_name_t key,
+	      csi_object_t *obj)
+{
+    if (strcmp ((char *) key, "source") == 0) {
+	if (_csi_unlikely (csi_object_get_type (obj) !=
+			     CSI_OBJECT_TYPE_PATTERN))
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+	cairo_set_source (cr, obj->datum.pattern);
+	return CSI_STATUS_SUCCESS;
+    }
+
+    if (strcmp ((char *) key, "scaled-font") == 0) {
+	if (_csi_unlikely (csi_object_get_type (obj) !=
+			     CSI_OBJECT_TYPE_SCALED_FONT))
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+	cairo_set_scaled_font (cr, obj->datum.scaled_font);
+	return CSI_STATUS_SUCCESS;
+    }
+
+    if (strcmp ((char *) key, "font-face") == 0) {
+	if (_csi_unlikely (csi_object_get_type (obj) !=
+			     CSI_OBJECT_TYPE_FONT))
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+	cairo_set_font_face (cr, obj->datum.font_face);
+	return CSI_STATUS_SUCCESS;
+    }
+
+    /* return _proxy_set()? */
+    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+}
+
+static csi_status_t
+_set (csi_t *ctx)
+{
+    csi_object_t *key, *value, *dst;
+    csi_status_t status;
+
+    check (3);
+
+    value = _csi_peek_ostack (ctx, 0);
+    key = _csi_peek_ostack (ctx, 1);
+    dst = _csi_peek_ostack (ctx, 2);
+
+    switch ((int) csi_object_get_type (dst)) {
+    case CSI_OBJECT_TYPE_DICTIONARY:
+	if (_csi_unlikely (csi_object_get_type (key) !=
+			     CSI_OBJECT_TYPE_NAME))
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+	status = csi_dictionary_put (ctx,
+				     dst->datum.dictionary,
+				     key->datum.name,
+				     value);
+	break;
+    case CSI_OBJECT_TYPE_ARRAY:
+	if (_csi_unlikely (csi_object_get_type (key) !=
+			     CSI_OBJECT_TYPE_INTEGER))
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+	status = csi_array_put (ctx,
+				dst->datum.array,
+				key->datum.integer,
+				value);
+	break;
+
+    case CSI_OBJECT_TYPE_CONTEXT:
+	if (_csi_unlikely (csi_object_get_type (key) !=
+			     CSI_OBJECT_TYPE_NAME))
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+	status = _context_set (ctx,
+			       dst->datum.cr,
+			       key->datum.name,
+			       value);
+	break;
+
+    case CSI_OBJECT_TYPE_STRING:
+#if 0
+	status = csi_string_put (dst, key, value);
+	break;
+#endif
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    pop (2);
+    return status;
+}
+
+static csi_status_t
+_set_antialias (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_t *cr;
+    long antialias;
+
+    check (2);
+
+    status = _csi_ostack_get_integer (ctx, 0, &antialias);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_set_antialias (cr, antialias);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_dash (csi_t *ctx)
+{
+    csi_array_t *array;
+    csi_status_t status;
+    cairo_t *cr;
+    double offset;
+
+    check (3);
+
+    status = _csi_ostack_get_number (ctx, 0, &offset);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_array (ctx, 1, &array);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 2, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    if (array->stack.len == 0) {
+	cairo_set_dash (cr, NULL, 0., 0.);
+    } else {
+	double stack_dashes[8];
+	double *dashes;
+	csi_integer_t n;
+
+	if (_csi_likely (array->stack.len < ARRAY_LENGTH (stack_dashes))) {
+	    dashes = stack_dashes;
+	} else {
+	if (_csi_unlikely ((unsigned) array->stack.len >= INT32_MAX / sizeof (double)))
+	    return _csi_error (CSI_STATUS_NO_MEMORY);
+	    dashes = _csi_alloc (ctx, sizeof (double) * array->stack.len);
+	    if (_csi_unlikely (dashes == NULL))
+		return _csi_error (CSI_STATUS_NO_MEMORY);
+	}
+
+	for (n = 0; n < array->stack.len; n++) {
+	    if (_csi_unlikely (! csi_object_is_number
+				 (&array->stack.objects[n])))
+	    {
+		if (dashes != stack_dashes)
+		    _csi_free (ctx, dashes);
+		return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	    }
+
+	    dashes[n] = csi_number_get_value (&array->stack.objects[n]);
+	}
+
+	cairo_set_dash (cr, dashes, n, offset);
+
+	if (dashes != stack_dashes)
+	    _csi_free (ctx, dashes);
+    }
+
+    pop (2);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_device_offset (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_surface_t *surface;
+    double x, y;
+
+    check (3);
+
+    status = _csi_ostack_get_number (ctx, 0,  &y);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &x);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_surface (ctx, 2, &surface);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_surface_set_device_offset (surface, x, y);
+    pop (2);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_extend (csi_t *ctx)
+{
+    csi_status_t status;
+    csi_object_t *obj;
+    long extend;
+
+    check (2);
+
+    status = _csi_ostack_get_integer (ctx, 0, &extend);
+    if (_csi_unlikely (status))
+	return status;
+
+    obj = _csi_peek_ostack (ctx, 1);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_CONTEXT:
+	cairo_pattern_set_extend (cairo_get_source (obj->datum.cr),
+				  extend);
+	break;
+    case CSI_OBJECT_TYPE_PATTERN:
+	cairo_pattern_set_extend (obj->datum.pattern, extend);
+	break;
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_fallback_resolution (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_surface_t *surface;
+    double dpi_x, dpi_y;
+
+    check (3);
+
+    status = _csi_ostack_get_number (ctx, 0, &dpi_y);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &dpi_x);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_surface (ctx, 2, &surface);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_surface_set_fallback_resolution (surface, dpi_x, dpi_y);
+    pop (2);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_fill_rule (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_t *cr;
+    long fill_rule;
+
+    check (2);
+
+    status = _csi_ostack_get_integer (ctx, 0, &fill_rule);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_set_fill_rule (cr, fill_rule);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_filter (csi_t *ctx)
+{
+    csi_status_t status;
+    csi_object_t *obj;
+    long filter;
+
+    check (2);
+
+    status = _csi_ostack_get_integer (ctx, 0, &filter);
+    if (_csi_unlikely (status))
+	return status;
+
+    obj = _csi_peek_ostack (ctx, 1);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_CONTEXT:
+	cairo_pattern_set_filter (cairo_get_source (obj->datum.cr),
+				  filter);
+	break;
+    case CSI_OBJECT_TYPE_PATTERN:
+	cairo_pattern_set_filter (obj->datum.pattern, filter);
+	break;
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_font_face (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_font_face_t *font;
+    cairo_t *cr;
+
+    check (2);
+
+    status = _csi_ostack_get_font_face (ctx, 0, &font);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_set_font_face (cr, font);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_font_options (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_t *cr;
+    csi_dictionary_t *dict;
+    cairo_font_options_t *options;
+
+    check (2);
+
+    status = _csi_ostack_get_dictionary (ctx, 0, &dict);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    options = cairo_font_options_create ();
+    status = _font_options_load_from_dictionary (ctx, dict, options);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_set_font_options (cr, options);
+    cairo_font_options_destroy (options);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_font_matrix (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_t *cr;
+    cairo_matrix_t m;
+
+    check (2);
+
+    status = _csi_ostack_get_matrix (ctx, 0, &m);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_set_font_matrix (cr, &m);
+    pop(1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_font_size (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_t *cr;
+    double size;
+
+    check (2);
+
+    status = _csi_ostack_get_number (ctx, 0, &size);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_set_font_size (cr, size);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_line_cap (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_t *cr;
+    long line_cap;
+
+    check (2);
+
+    status = _csi_ostack_get_integer (ctx, 0, &line_cap);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_set_line_cap (cr, line_cap);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_line_join (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_t *cr;
+    long line_join;
+
+    status = _csi_ostack_get_integer (ctx, 0, &line_join);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_set_line_join (cr, line_join);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_line_width (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_t *cr;
+    double line_width;
+
+    check (2);
+
+    status = _csi_ostack_get_number (ctx, 0, &line_width);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_set_line_width (cr, line_width);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_matrix (csi_t *ctx)
+{
+    csi_object_t *obj;
+    csi_status_t status;
+    cairo_matrix_t m;
+
+    check (2);
+
+    status = _csi_ostack_get_matrix (ctx, 0, &m);
+    if (_csi_unlikely (status))
+	return status;
+
+    obj = _csi_peek_ostack (ctx, 1);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_CONTEXT:
+	cairo_set_matrix (obj->datum.cr, &m);
+	break;
+    case CSI_OBJECT_TYPE_PATTERN:
+	cairo_pattern_set_matrix (obj->datum.pattern, &m);
+	break;
+    case CSI_OBJECT_TYPE_MATRIX:
+	obj->datum.matrix->matrix = m;
+	break;
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+struct _mime_tag {
+    csi_t *ctx;
+    csi_string_t *source;
+};
+static void
+_mime_tag_destroy (void *closure)
+{
+    struct _mime_tag *tag = closure;
+
+    if (--tag->source->base.ref)
+	csi_string_free (tag->ctx, tag->source);
+
+    _csi_slab_free (tag->ctx, tag, sizeof (struct _mime_tag));
+}
+
+static csi_status_t
+_set_mime_data (csi_t *ctx)
+{
+    csi_status_t status;
+    csi_object_t *obj;
+    const char *mime;
+    csi_object_t source;
+    cairo_surface_t *surface;
+    struct _mime_tag *tag;
+
+    check (3);
+
+    obj = _csi_peek_ostack (ctx, 0);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_FILE:
+	status = _csi_file_as_string (ctx, obj->datum.file, &source);
+	if (_csi_unlikely (status))
+	    return status;
+
+	break;
+
+    case CSI_OBJECT_TYPE_STRING:
+	source = *csi_object_reference (obj);
+	break;
+
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    status = _csi_ostack_get_string_constant (ctx, 1, &mime);
+    if (_csi_unlikely (status))
+	return status;
+
+    status = _csi_ostack_get_surface (ctx, 2, &surface);
+    if (_csi_unlikely (status))
+	return status;
+
+
+    /* XXX free source */
+    tag = _csi_slab_alloc (ctx, sizeof (struct _mime_tag));
+    if (_csi_unlikely (tag == NULL))
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+    tag->ctx = cairo_script_interpreter_reference (ctx);
+    tag->source = source.datum.string;
+    tag->source->base.ref++;
+
+    status = cairo_surface_set_mime_data (surface,
+					  mime,
+					  (uint8_t *)
+					  source.datum.string->string,
+					  source.datum.string->len,
+					  _mime_tag_destroy, tag);
+    if (_csi_unlikely (status)) {
+	_mime_tag_destroy (tag);
+	return status;
+    }
+
+    pop (2);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_miter_limit (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_t *cr;
+    double miter_limit;
+
+    check (2);
+
+    status = _csi_ostack_get_number (ctx, 0, &miter_limit);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_set_miter_limit (cr, miter_limit);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_operator (csi_t *ctx)
+{
+    cairo_t *cr;
+    long val;
+    csi_status_t status;
+
+    check (2);
+
+    status = _csi_ostack_get_integer (ctx, 0, &val);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_set_operator (cr, val);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_scaled_font (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_scaled_font_t *font;
+    cairo_t *cr;
+
+    check (2);
+
+    status = _csi_ostack_get_scaled_font (ctx, 0, &font);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_set_scaled_font (cr, font);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_source (csi_t *ctx)
+{
+    cairo_t *cr;
+    cairo_pattern_t *pattern;
+    csi_status_t status;
+
+    check (2);
+
+    status = _csi_ostack_get_pattern (ctx, 0, &pattern);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_set_source (cr, pattern);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_source_image (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_surface_t *surface;
+    cairo_surface_t *source;
+    cairo_t *cr;
+
+    check (2);
+
+    status = _csi_ostack_get_surface (ctx, 0, &source);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_surface (ctx, 1, &surface);
+    if (_csi_unlikely (status))
+	return status;
+
+    cr = cairo_create (surface);
+    cairo_set_source_surface (cr, source, 0, 0);
+    cairo_paint (cr);
+    cairo_destroy (cr);
+
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_set_tolerance (csi_t *ctx)
+{
+    csi_status_t status;
+    cairo_t *cr;
+    double tolerance;
+
+    check (2);
+
+    status = _csi_ostack_get_number (ctx, 0, &tolerance);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_set_tolerance (cr, tolerance);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_transform (csi_t *ctx)
+{
+    csi_object_t *obj;
+    csi_status_t status;
+    cairo_matrix_t m;
+
+    check (2);
+
+    status = _csi_ostack_get_matrix (ctx, 0, &m);
+    if (_csi_unlikely (status))
+	return status;
+
+    obj = _csi_peek_ostack (ctx, 1);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_CONTEXT:
+	cairo_transform (obj->datum.cr, &m);
+	break;
+    case CSI_OBJECT_TYPE_PATTERN:
+	{
+	    cairo_matrix_t ctm;
+	    cairo_pattern_get_matrix (obj->datum.pattern, &ctm);
+	    cairo_matrix_multiply (&ctm, &m, &ctm);
+	    cairo_pattern_set_matrix (obj->datum.pattern, &ctm);
+	}
+	break;
+    case CSI_OBJECT_TYPE_MATRIX:
+	    cairo_matrix_multiply (&obj->datum.matrix->matrix,
+				   &m,
+				   &obj->datum.matrix->matrix);
+	break;
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_translate (csi_t *ctx)
+{
+    csi_object_t *obj;
+    csi_status_t status;
+    double x, y;
+
+    check (3);
+
+    status = _csi_ostack_get_number (ctx, 0, &y);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &x);
+    if (_csi_unlikely (status))
+	return status;
+
+    obj = _csi_peek_ostack (ctx, 2);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_CONTEXT:
+	cairo_translate (obj->datum.cr, x, y);
+	break;
+
+    case CSI_OBJECT_TYPE_PATTERN:
+	{
+	    cairo_matrix_t ctm;
+	    cairo_pattern_get_matrix (obj->datum.pattern, &ctm);
+	    cairo_matrix_translate (&ctm, x, y);
+	    cairo_pattern_set_matrix (obj->datum.pattern, &ctm);
+	}
+	break;
+
+
+    case CSI_OBJECT_TYPE_MATRIX:
+	cairo_matrix_translate (&obj->datum.matrix->matrix, x, y);
+	break;
+
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    pop (2);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_true (csi_t *ctx)
+{
+    return _csi_push_ostack_boolean (ctx, TRUE);
+}
+
+static csi_status_t
+_show_page (csi_t *ctx)
+{
+    csi_object_t *obj;
+
+    check (1);
+
+    obj = _csi_peek_ostack (ctx, 0);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_CONTEXT:
+	cairo_show_page (obj->datum.cr);
+	if (ctx->hooks.copy_page != NULL)
+	    ctx->hooks.copy_page (ctx->hooks.closure, obj->datum.cr);
+	break;
+    case CSI_OBJECT_TYPE_SURFACE:
+	cairo_surface_show_page (obj->datum.surface);
+	/* XXX hook? */
+	break;
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_similar (csi_t *ctx)
+{
+    csi_object_t obj;
+    long content;
+    double width, height;
+    cairo_surface_t *other;
+    csi_status_t status;
+
+    check (4);
+
+    status = _csi_ostack_get_integer (ctx, 0, &content);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 1, &height);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_number (ctx, 2, &width);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_surface (ctx, 3, &other);
+    if (_csi_unlikely (status))
+	return status;
+
+    /* silently fix-up a common bug when writing CS */
+    if ((content & CAIRO_CONTENT_COLOR_ALPHA) == 0) {
+	if (_csi_unlikely (content & ~CAIRO_CONTENT_COLOR_ALPHA))
+	    return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+	switch ((int) content) {
+	default:
+	case CAIRO_FORMAT_ARGB32:
+	    content = CAIRO_CONTENT_COLOR_ALPHA;
+	    break;
+	case CAIRO_FORMAT_RGB24:
+	    content = CAIRO_CONTENT_COLOR;
+	    break;
+	case CAIRO_FORMAT_A8:
+	case CAIRO_FORMAT_A1:
+	    content = CAIRO_CONTENT_ALPHA;
+	    break;
+	}
+    }
+
+    obj.type = CSI_OBJECT_TYPE_SURFACE;
+    obj.datum.surface = cairo_surface_create_similar (other,
+						      content, width, height);
+    pop (4);
+    return push (&obj);
+}
+
+static csi_status_t
+_show_text (csi_t *ctx)
+{
+    csi_status_t status;
+    csi_string_t *text;
+    cairo_t *cr;
+
+    check (2);
+
+    status = _csi_ostack_get_string (ctx, 0, &text);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_show_text (cr, text->string);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_show_glyphs (csi_t *ctx)
+{
+    csi_object_t *obj;
+    csi_array_t *array;
+    csi_array_t *glyph_array;
+    csi_string_t *glyph_string;
+    csi_status_t status;
+    cairo_t *cr;
+    cairo_scaled_font_t *scaled_font;
+    cairo_glyph_t stack_glyphs[256], *glyphs;
+    double x,y;
+    csi_integer_t nglyphs, i, j;
+    double glyph_advance[256][2];
+    int have_glyph_advance[256];
+
+    check (2);
+
+    status = _csi_ostack_get_array (ctx, 0, &array);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    /* count glyphs */
+    nglyphs = 0;
+    for (i = 0; i < array->stack.len; i++) {
+	obj = &array->stack.objects[i];
+	switch ((int) csi_object_get_type (obj)) {
+	case CSI_OBJECT_TYPE_ARRAY:
+	    nglyphs += obj->datum.array->stack.len;
+	    break;
+	case CSI_OBJECT_TYPE_STRING:
+	    nglyphs += obj->datum.string->len;
+	    break;
+	}
+    }
+    if (nglyphs == 0) {
+	pop (1);
+	return CSI_STATUS_SUCCESS;
+    }
+
+    if (nglyphs > ARRAY_LENGTH (stack_glyphs)) {
+	if (_csi_unlikely ((unsigned) nglyphs >= INT32_MAX / sizeof (cairo_glyph_t)))
+	    return _csi_error (CSI_STATUS_NO_MEMORY);
+	glyphs = _csi_alloc (ctx, sizeof (cairo_glyph_t) * nglyphs);
+	if (_csi_unlikely (glyphs == NULL))
+	    return _csi_error (CSI_STATUS_NO_MEMORY);
+    } else
+	glyphs = stack_glyphs;
+
+    scaled_font = cairo_get_scaled_font (cr);
+
+    nglyphs = 0;
+    memset (have_glyph_advance, 0, sizeof (have_glyph_advance));
+    x = y = 0;
+    for (i = 0; i < array->stack.len; i++) {
+	obj = &array->stack.objects[i];
+	switch ((int) csi_object_get_type (obj)) {
+	case CSI_OBJECT_TYPE_ARRAY: /* glyphs */
+	    glyph_array = obj->datum.array;
+	    for (j = 0; j < glyph_array->stack.len; j++) {
+		unsigned long g;
+		cairo_bool_t have_advance;
+
+		obj = &glyph_array->stack.objects[j];
+		if (csi_object_get_type (obj) != CSI_OBJECT_TYPE_INTEGER)
+		    break;
+		g = obj->datum.integer;
+
+		glyphs[nglyphs].index = g;
+		glyphs[nglyphs].x = x;
+		glyphs[nglyphs].y = y;
+
+		if (g < ARRAY_LENGTH (have_glyph_advance)) {
+		    if (! have_glyph_advance[g]) {
+			cairo_text_extents_t extents;
+
+			cairo_scaled_font_glyph_extents (scaled_font,
+							 &glyphs[nglyphs], 1,
+							 &extents);
+
+			glyph_advance[g][0] = extents.x_advance;
+			glyph_advance[g][1] = extents.y_advance;
+			have_glyph_advance[g] = TRUE;
+
+		    }
+
+		    have_advance = glyph_advance[g][0] != 0.0;
+		    x += glyph_advance[g][0];
+		    y += glyph_advance[g][1];
+		} else {
+		    cairo_text_extents_t extents;
+
+		    cairo_scaled_font_glyph_extents (scaled_font,
+						     &glyphs[nglyphs], 1,
+						     &extents);
+
+		    have_advance = extents.x_advance != 0.0;
+		    x += extents.x_advance;
+		    y += extents.y_advance;
+		}
+
+		nglyphs += have_advance;
+	    }
+	    break;
+
+	case CSI_OBJECT_TYPE_STRING: /* glyphs */
+	    glyph_string = obj->datum.string;
+	    for (j = 0; j < glyph_string->len; j++) {
+		uint8_t g;
+		cairo_bool_t have_advance;
+
+		g = glyph_string->string[j];
+		glyphs[nglyphs].index = g;
+		glyphs[nglyphs].x = x;
+		glyphs[nglyphs].y = y;
+
+		if (! have_glyph_advance[g]) {
+		    cairo_text_extents_t extents;
+
+		    cairo_scaled_font_glyph_extents (scaled_font,
+						     &glyphs[nglyphs], 1,
+						     &extents);
+
+		    glyph_advance[g][0] = extents.x_advance;
+		    glyph_advance[g][1] = extents.y_advance;
+		    have_glyph_advance[g] = TRUE;
+		}
+
+		have_advance = glyph_advance[g][0] != 0.0;
+		x += glyph_advance[g][0];
+		y += glyph_advance[g][1];
+
+		nglyphs += have_advance;
+	    }
+	    break;
+
+	case CSI_OBJECT_TYPE_INTEGER:
+	case CSI_OBJECT_TYPE_REAL: /* dx */
+	    x = csi_number_get_value (obj);
+	    if (++i == array->stack.len)
+		break;
+	    y = csi_number_get_value (&array->stack.objects[i]);
+	    break;
+	}
+    }
+
+    cairo_show_glyphs (cr, glyphs, nglyphs);
+    cairo_move_to (cr, x, y);
+
+    if (glyphs != stack_glyphs)
+	_csi_free (ctx, glyphs);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_show_text_glyphs (csi_t *ctx)
+{
+    csi_object_t *obj;
+    csi_array_t *array;
+    csi_string_t *string;
+    csi_string_t *utf8_string;
+    csi_string_t *glyph_string;
+    csi_array_t *glyph_array;
+    csi_status_t status;
+    cairo_t *cr;
+    cairo_scaled_font_t *scaled_font;
+    cairo_text_cluster_t stack_clusters[256], *clusters;
+    cairo_glyph_t stack_glyphs[256], *glyphs;
+    double x,y;
+    csi_integer_t nglyphs, nclusters, i, j;
+    double glyph_advance[256][2];
+    int have_glyph_advance[256];
+    long direction;
+
+    check (5);
+
+    status = _csi_ostack_get_integer (ctx, 0, &direction);
+    if (_csi_unlikely (status))
+	return status;
+
+    obj = _csi_peek_ostack (ctx, 1);
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_ARRAY:
+	array = obj->datum.array;
+	nclusters = array->stack.len / 2;
+	if (nclusters > ARRAY_LENGTH (stack_clusters)) {
+	    if (_csi_unlikely ((unsigned) nclusters >= INT32_MAX / sizeof (cairo_text_cluster_t)))
+		return _csi_error (CSI_STATUS_NO_MEMORY);
+	    clusters = _csi_alloc (ctx, sizeof (cairo_text_cluster_t) * nclusters);
+	    if (_csi_unlikely (clusters == NULL))
+		return _csi_error (CSI_STATUS_NO_MEMORY);
+	} else
+	    clusters = stack_clusters;
+
+	for (i = 0; i < nclusters; i++) {
+	    clusters[i].num_bytes = csi_number_get_value (&array->stack.objects[2*i+0]);
+	    clusters[i].num_glyphs = csi_number_get_value (&array->stack.objects[2*i+1]);
+	}
+	break;
+
+    case CSI_OBJECT_TYPE_STRING:
+	string = obj->datum.string;
+	nclusters = string->len / 2;
+	if (nclusters > ARRAY_LENGTH (stack_clusters)) {
+	    if (_csi_unlikely ((unsigned) nclusters >= INT32_MAX / sizeof (cairo_text_cluster_t)))
+		return _csi_error (CSI_STATUS_NO_MEMORY);
+	    clusters = _csi_alloc (ctx, sizeof (cairo_text_cluster_t) * nclusters);
+	    if (_csi_unlikely (clusters == NULL))
+		return _csi_error (CSI_STATUS_NO_MEMORY);
+	} else
+	    clusters = stack_clusters;
+
+	for (i = 0; i < nclusters; i++) {
+	    clusters[i].num_bytes = string->string[2*i+0];
+	    clusters[i].num_glyphs = string->string[2*i+1];
+	}
+	break;
+
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    status = _csi_ostack_get_array (ctx, 2, &array);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_string (ctx, 3, &utf8_string);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 4, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    /* count glyphs */
+    nglyphs = 0;
+    for (i = 0; i < array->stack.len; i++) {
+	obj = &array->stack.objects[i];
+	switch ((int) csi_object_get_type (obj)) {
+	case CSI_OBJECT_TYPE_ARRAY:
+	    nglyphs += obj->datum.array->stack.len;
+	    break;
+	case CSI_OBJECT_TYPE_STRING:
+	    nglyphs += obj->datum.string->len;
+	    break;
+	}
+    }
+    if (nglyphs == 0)
+	return CSI_STATUS_SUCCESS;
+
+    if (nglyphs > ARRAY_LENGTH (stack_glyphs)) {
+	    if (_csi_unlikely ((unsigned) nglyphs >= INT32_MAX / sizeof (cairo_glyph_t)))
+		return _csi_error (CSI_STATUS_NO_MEMORY);
+	glyphs = _csi_alloc (ctx, sizeof (cairo_glyph_t) * nglyphs);
+	if (_csi_unlikely (glyphs == NULL)) {
+	    return _csi_error (CSI_STATUS_NO_MEMORY);
+	}
+    } else
+	glyphs = stack_glyphs;
+
+    /* amalgamate glyph strings */
+    scaled_font = cairo_get_scaled_font (cr);
+
+    nglyphs = 0;
+    memset (have_glyph_advance, 0, sizeof (have_glyph_advance));
+    x = y = 0;
+    for (i = 0; i < array->stack.len; i++) {
+	obj = &array->stack.objects[i];
+	switch ((int) csi_object_get_type (obj)) {
+	case CSI_OBJECT_TYPE_ARRAY: /* glyphs */
+	    glyph_array = obj->datum.array;
+	    for (j = 0; j < glyph_array->stack.len; j++) {
+		unsigned long g;
+		cairo_bool_t have_advance;
+
+		obj = &glyph_array->stack.objects[j];
+		if (csi_object_get_type (obj) != CSI_OBJECT_TYPE_INTEGER)
+		    break;
+		g = obj->datum.integer;
+
+		glyphs[nglyphs].index = g;
+		glyphs[nglyphs].x = x;
+		glyphs[nglyphs].y = y;
+
+		if (g < ARRAY_LENGTH (have_glyph_advance)) {
+		    if (! have_glyph_advance[g]) {
+			cairo_text_extents_t extents;
+
+			cairo_scaled_font_glyph_extents (scaled_font,
+							 &glyphs[nglyphs], 1,
+							 &extents);
+
+			glyph_advance[g][0] = extents.x_advance;
+			glyph_advance[g][1] = extents.y_advance;
+			have_glyph_advance[g] = TRUE;
+
+		    }
+
+		    have_advance = glyph_advance[g][0] != 0.0;
+		    x += glyph_advance[g][0];
+		    y += glyph_advance[g][1];
+		} else {
+		    cairo_text_extents_t extents;
+
+		    cairo_scaled_font_glyph_extents (scaled_font,
+						     &glyphs[nglyphs], 1,
+						     &extents);
+
+		    have_advance = extents.x_advance != 0.0;
+		    x += extents.x_advance;
+		    y += extents.y_advance;
+		}
+
+		nglyphs += have_advance;
+	    }
+	    break;
+
+	case CSI_OBJECT_TYPE_STRING: /* glyphs */
+	    glyph_string = obj->datum.string;
+	    for (j = 0; j < glyph_string->len; j++) {
+		uint8_t g;
+		cairo_bool_t have_advance;
+
+		g = glyph_string->string[j];
+		glyphs[nglyphs].index = g;
+		glyphs[nglyphs].x = x;
+		glyphs[nglyphs].y = y;
+
+		if (! have_glyph_advance[g]) {
+		    cairo_text_extents_t extents;
+
+		    cairo_scaled_font_glyph_extents (scaled_font,
+						     &glyphs[nglyphs], 1,
+						     &extents);
+
+		    glyph_advance[g][0] = extents.x_advance;
+		    glyph_advance[g][1] = extents.y_advance;
+		    have_glyph_advance[g] = TRUE;
+		}
+
+		have_advance = glyph_advance[g][0] != 0.0;
+		x += glyph_advance[g][0];
+		y += glyph_advance[g][1];
+
+		nglyphs += have_advance;
+	    }
+	    break;
+
+	case CSI_OBJECT_TYPE_INTEGER:
+	case CSI_OBJECT_TYPE_REAL: /* dx */
+	    x = csi_number_get_value (obj);
+	    if (++i == array->stack.len)
+		break;
+	    y = csi_number_get_value (&array->stack.objects[i]);
+	    break;
+	}
+    }
+
+    cairo_show_text_glyphs (cr,
+			    utf8_string->string, utf8_string->len,
+			    glyphs, nglyphs,
+			    clusters, nclusters,
+			    direction);
+    cairo_move_to (cr, x, y);
+
+    if (clusters != stack_clusters)
+	_csi_free (ctx, clusters);
+    if (glyphs != stack_glyphs)
+	_csi_free (ctx, glyphs);
+
+    pop (4);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_stroke (csi_t *ctx)
+{
+    return _do_cairo_op (ctx, cairo_stroke);
+}
+
+static csi_status_t
+_stroke_preserve (csi_t *ctx)
+{
+    return _do_cairo_op (ctx, cairo_stroke_preserve);
+}
+
+static csi_status_t
+_sub (csi_t *ctx)
+{
+    csi_object_t *A;
+    csi_object_t *B;
+    csi_object_type_t type_a, type_b;
+
+    check (2);
+
+    B = _csi_peek_ostack (ctx, 0);
+    A = _csi_peek_ostack (ctx, 1);
+
+    type_a = csi_object_get_type (A);
+    if (_csi_unlikely (! (type_a == CSI_OBJECT_TYPE_INTEGER ||
+			    type_a == CSI_OBJECT_TYPE_REAL)))
+    {
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    type_b = csi_object_get_type (B);
+    if (_csi_unlikely (! (type_b == CSI_OBJECT_TYPE_INTEGER ||
+			    type_b == CSI_OBJECT_TYPE_REAL)))
+    {
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    pop (2);
+
+    if (type_a == CSI_OBJECT_TYPE_REAL &&
+	type_b == CSI_OBJECT_TYPE_REAL)
+    {
+	return _csi_push_ostack_real (ctx, A->datum.real - B->datum.real);
+
+    }
+    else if (type_a == CSI_OBJECT_TYPE_INTEGER &&
+	     type_b == CSI_OBJECT_TYPE_INTEGER)
+    {
+	return _csi_push_ostack_integer (ctx,
+					 A->datum.integer - B->datum.integer);
+    }
+    else
+    {
+	double v;
+
+	if (type_a == CSI_OBJECT_TYPE_REAL)
+	    v = A->datum.real;
+	else
+	    v = A->datum.integer;
+
+	if (type_b == CSI_OBJECT_TYPE_REAL)
+	    v -= B->datum.real;
+	else
+	    v -= B->datum.integer;
+
+	return _csi_push_ostack_real (ctx, v);
+    }
+}
+
+static csi_status_t
+_surface (csi_t *ctx)
+{
+    csi_object_t obj;
+    csi_dictionary_t *dict;
+    csi_proxy_t *proxy;
+    csi_object_t key;
+    double width, height;
+    csi_surface_create_func_t hook;
+    cairo_surface_t *surface;
+    csi_status_t status;
+
+    check (1);
+
+    status = _csi_ostack_get_dictionary (ctx, 0, &dict);
+    if (_csi_unlikely (status))
+	return status;
+
+    status = _csi_dictionary_get_number (ctx, dict, "width", FALSE, &width);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_dictionary_get_number (ctx, dict, "height", FALSE, &height);
+    if (_csi_unlikely (status))
+	return status;
+
+    hook = ctx->hooks.surface_create;
+    assert (hook != NULL);
+
+    surface = hook (ctx->hooks.closure, width, height);
+    if (_csi_unlikely (surface == NULL)) {
+	return _csi_error (CSI_STATUS_NULL_POINTER);
+    }
+
+    proxy = _csi_proxy_create (ctx, surface, dict,
+			       ctx->hooks.surface_destroy,
+			       ctx->hooks.closure);
+    if (_csi_unlikely (proxy == NULL)) {
+	cairo_surface_destroy (surface);
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+    }
+
+    status = cairo_surface_set_user_data (surface,
+					  &_csi_proxy_key,
+					  proxy, _csi_proxy_destroy);
+    if (_csi_unlikely (status)) {
+	_csi_proxy_destroy (proxy);
+	cairo_surface_destroy (surface);
+	return status;
+    }
+
+    status = csi_name_new_static (ctx, &key, "fallback-resolution");
+    if (_csi_unlikely (status)) {
+	cairo_surface_destroy (surface);
+	return status;
+    }
+    if (csi_dictionary_has (dict, key.datum.name)) {
+	status = csi_dictionary_get (ctx, dict, key.datum.name, &obj);
+	if (csi_object_get_type (&obj) == CSI_OBJECT_TYPE_ARRAY) {
+	    csi_array_t *array = obj.datum.array;
+	    if (array->stack.len == 2) {
+		cairo_surface_set_fallback_resolution (surface,
+						       csi_number_get_value
+						       (&array->stack.objects[0]),
+						       csi_number_get_value
+						       (&array->stack.objects[1]));
+	    }
+	}
+    }
+    /* initialise surface to source */
+    status = csi_name_new_static (ctx, &key, "source");
+    if (_csi_unlikely (status)) {
+	cairo_surface_destroy (surface);
+	return status;
+    }
+    if (csi_dictionary_has (dict, key.datum.name)) {
+	cairo_surface_t *image;
+	cairo_t *cr;
+
+	status = _image_load_from_dictionary (ctx, dict, &image);
+	if (_csi_unlikely (status)) {
+	    cairo_surface_destroy (surface);
+	    return status;
+	}
+
+	cr = cairo_create (surface);
+	cairo_set_source_surface (cr, image, 0, 0);
+	cairo_surface_destroy (image);
+	cairo_paint (cr);
+	status = cairo_status (cr);
+	cairo_destroy (cr);
+
+	if (_csi_unlikely (status))
+	    return status;
+    }
+
+    status = csi_name_new_static (ctx, &key, "device-offset");
+    if (_csi_unlikely (status)) {
+	cairo_surface_destroy (surface);
+	return status;
+    }
+    if (csi_dictionary_has (dict, key.datum.name)) {
+	status = csi_dictionary_get (ctx, dict, key.datum.name, &obj);
+	if (_csi_unlikely (status))
+	    return status;
+
+	if (csi_object_get_type (&obj) == CSI_OBJECT_TYPE_ARRAY) {
+	    csi_array_t *array = obj.datum.array;
+
+	    if (array->stack.len == 2) {
+		cairo_surface_set_device_offset (surface,
+						 csi_number_get_value
+						 (&array->stack.objects[0]),
+						 csi_number_get_value
+						 (&array->stack.objects[1]));
+	    }
+	}
+    }
+
+    obj.type = CSI_OBJECT_TYPE_SURFACE;
+    obj.datum.surface = surface;
+    pop (1);
+    return push (&obj);
+}
+
+static csi_status_t
+_text_path (csi_t *ctx)
+{
+    csi_status_t status;
+    csi_string_t *text;
+    cairo_t *cr;
+
+    check (2);
+
+    status = _csi_ostack_get_string (ctx, 0, &text);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_context (ctx, 1, &cr);
+    if (_csi_unlikely (status))
+	return status;
+
+    cairo_text_path (cr, text->string);
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_undef (csi_t *ctx)
+{
+    csi_name_t name;
+    csi_status_t status;
+
+    check (1);
+
+    status = _csi_ostack_get_name (ctx, 0, &name);
+    if (_csi_unlikely (status))
+	return status;
+
+    status = _csi_name_undefine (ctx, name);
+    if (_csi_unlikely (status))
+	return status;
+
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_unset (csi_t *ctx)
+{
+    csi_object_t *dst;
+    csi_name_t name;
+    csi_status_t status;
+
+    check (2);
+
+    status = _csi_ostack_get_name (ctx, 0, &name);
+    if (_csi_unlikely (status))
+	return status;
+
+    dst = _csi_peek_ostack (ctx, 1);
+    switch ((int) csi_object_get_type (dst)) {
+    case CSI_OBJECT_TYPE_DICTIONARY:
+	csi_dictionary_remove (ctx, dst->datum.dictionary, name);
+	break;
+    case CSI_OBJECT_TYPE_STRING:
+    case CSI_OBJECT_TYPE_ARRAY:
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_write_to_png (csi_t *ctx)
+{
+    csi_status_t status;
+    csi_string_t *filename;
+    cairo_surface_t *surface;
+
+    check (2);
+
+    status = _csi_ostack_get_string (ctx, 0, &filename);
+    if (_csi_unlikely (status))
+	return status;
+    status = _csi_ostack_get_surface (ctx, 1, &surface);
+    if (_csi_unlikely (status))
+	return status;
+
+    status = cairo_surface_write_to_png (surface, filename->string);
+    if (_csi_unlikely (status))
+	return status;
+
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static csi_status_t
+_xor (csi_t *ctx)
+{
+    csi_object_t *a, *b;
+
+    check (2);
+
+    a = _csi_peek_ostack (ctx, 0);
+    b = _csi_peek_ostack (ctx, 1);
+    if (_csi_unlikely (csi_object_get_type (a) != csi_object_get_type (b)))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    pop (2);
+    switch ((int) csi_object_get_type (a)) {
+    case CSI_OBJECT_TYPE_INTEGER:
+	return _csi_push_ostack_integer (ctx,
+					 a->datum.integer ^ b->datum.integer);
+    case CSI_OBJECT_TYPE_BOOLEAN:
+	return _csi_push_ostack_boolean (ctx,
+					 a->datum.boolean ^ b->datum.boolean);
+    default:
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    }
+}
+
+static csi_status_t
+_debug_print (csi_t *ctx)
+{
+    csi_object_t *obj;
+
+    check (1);
+    obj = _csi_peek_ostack (ctx, 0);
+    switch (csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_NULL:
+	fprintf (stderr, "NULL\n");
+	break;
+
+	/* atomics */
+    case CSI_OBJECT_TYPE_BOOLEAN:
+	fprintf (stderr, "boolean: %s\n",
+		 obj->datum.boolean ? "true" : "false");
+	break;
+    case CSI_OBJECT_TYPE_INTEGER:
+	fprintf (stderr, "integer: %ld\n", obj->datum.integer);
+	break;
+    case CSI_OBJECT_TYPE_MARK:
+	fprintf (stderr, "mark\n");
+	break;
+    case CSI_OBJECT_TYPE_NAME:
+	fprintf (stderr, "name: %s\n", (char *) obj->datum.name);
+	break;
+    case CSI_OBJECT_TYPE_OPERATOR:
+	fprintf (stderr, "operator: %p\n", obj->datum.op);
+	break;
+    case CSI_OBJECT_TYPE_REAL:
+	fprintf (stderr, "real: %g\n", obj->datum.real);
+	break;
+
+	/* compound */
+    case CSI_OBJECT_TYPE_ARRAY:
+	fprintf (stderr, "array\n");
+	break;
+    case CSI_OBJECT_TYPE_DICTIONARY:
+	fprintf (stderr, "dictionary\n");
+	break;
+    case CSI_OBJECT_TYPE_FILE:
+	fprintf (stderr, "file\n");
+	break;
+    case CSI_OBJECT_TYPE_MATRIX:
+	fprintf (stderr, "matrix: [%g %g %g %g %g %g]\n",
+		 obj->datum.matrix->matrix.xx,
+		 obj->datum.matrix->matrix.yx,
+		 obj->datum.matrix->matrix.xy,
+		 obj->datum.matrix->matrix.yy,
+		 obj->datum.matrix->matrix.x0,
+		 obj->datum.matrix->matrix.y0);
+	break;
+    case CSI_OBJECT_TYPE_STRING:
+	fprintf (stderr, "string: %s\n", obj->datum.string->string);
+	break;
+
+	/* cairo */
+    case CSI_OBJECT_TYPE_CONTEXT:
+	fprintf (stderr, "context\n");
+	break;
+    case CSI_OBJECT_TYPE_FONT:
+	fprintf (stderr, "font\n");
+	break;
+    case CSI_OBJECT_TYPE_PATTERN:
+	fprintf (stderr, "pattern\n");
+	break;
+    case CSI_OBJECT_TYPE_SCALED_FONT:
+	fprintf (stderr, "scaled_font\n");
+	break;
+    case CSI_OBJECT_TYPE_SURFACE:
+	fprintf (stderr, "surface\n");
+	break;
+    }
+    pop (1);
+    return CSI_STATUS_SUCCESS;
+}
+
+static const csi_operator_def_t
+_defs[] = {
+    { "<<", _mark },
+    { ">>", end_dict_construction },
+    { "[", _mark },
+    { "]", end_array_construction },
+    { "a", _alpha },
+    { "abs", NULL },
+    { "add", _add },
+    { "add_color_stop", _add_color_stop },
+    { "and", _and },
+    { "arc", _arc },
+    { "arc_negative", _arc_negative },
+    { "arc-", _arc_negative },
+    //{ "arc_to", NULL },
+    { "array", _array },
+    { "astore", NULL },
+    { "atan", NULL },
+    { "bind", _bind },
+    { "bitshift", _bitshift },
+    { "c", _curve_to },
+    { "C", _rel_curve_to },
+    { "ceiling", NULL },
+    { "clear", NULL },
+    { "clear_to_mark", NULL },
+    { "clip", _clip },
+    { "clip_extents", NULL },
+    { "clip_preserve", _clip_preserve },
+    { "clip+", _clip_preserve },
+    { "close_path", _close_path },
+    { "context", _context },
+    { "copy", _copy },
+    { "copy_page", _copy_page },
+    { "cos", NULL },
+    { "count", NULL },
+    { "count_to_mark", NULL },
+    { "curve_to", _curve_to },
+    { "def", _def },
+    { "device_to_user", NULL },
+    { "device_to_user_distance", NULL },
+    { "dict", _dict },
+    { "div", _div },
+    { "dup", _dup },
+    { "eq", _eq },
+    { "exch", _exch },
+    { "exec", NULL },
+    { "exp", NULL },
+    { "false", _false },
+    { "fill", _fill },
+    { "fill_extents", NULL },
+    { "fill_preserve", _fill_preserve },
+    { "fill+", _fill_preserve },
+    { "filter", _filter },
+    { "floor", NULL },
+    { "font", _font },
+    { "for", _for },
+    { "forall", NULL },
+    { "g", _gray },
+    { "ge", _ge },
+    { "get", _get },
+    { "glyph_path", _glyph_path },
+    { "gt", _gt },
+    { "h", _close_path },
+    { "identity", _identity },
+    { "if", _if },
+    { "ifelse", _ifelse },
+    { "image", _image },
+    { "index", _index },
+    { "invert", NULL },
+    { "in_stroke", NULL },
+    { "in_fill", NULL },
+    { "known", NULL },
+    { "l", _line_to },
+    { "L", _rel_line_to },
+    { "languagelevel", NULL },
+    { "le", _le },
+    { "length", NULL },
+    { "linear", _linear },
+    { "line_to", _line_to },
+    { "ln", NULL },
+    { "load", NULL },
+    { "log", NULL },
+    { "loop", NULL },
+    { "lt", _lt },
+    { "m", _move_to },
+    { "M", _rel_move_to },
+    { "mark", _mark },
+    { "mask", _mask },
+    { "matrix", _matrix },
+    { "mod", NULL },
+    { "move_to", _move_to },
+    { "mul", _mul },
+    { "multiply", NULL },
+    { "n", _new_path },
+    { "N", _new_sub_path },
+    { "ne", NULL },
+    { "neg", _neg },
+    { "new_path", _new_path },
+    { "new_sub_path", _new_sub_path },
+    { "not", _not },
+    { "null", NULL },
+    { "or", _or },
+    { "paint", _paint },
+    { "paint_with_alpha", _paint_with_alpha },
+    { "pattern", _pattern },
+    { "pop", _pop },
+    { "pop_group", _pop_group },
+    { "push_group", _push_group },
+    { "radial", _radial },
+    { "rand", NULL },
+    { "rectangle", _rectangle },
+    { "repeat", NULL },
+    { "restore", _restore },
+    { "rel_curve_to", _rel_curve_to },
+    { "rel_line_to", _rel_line_to },
+    { "rel_move_to", _rel_move_to },
+    { "reset_clip", _reset_clip },
+    { "rgb", _rgb },
+    { "rgba", _rgba },
+    { "roll", _roll },
+    { "rotate", _rotate },
+    { "round", NULL },
+    { "run", NULL },
+    { "save", _save },
+    { "scale", _scale },
+    { "scaled_font", _scaled_font },
+    { "select_font_face", _select_font_face },
+    { "set", _set },
+    { "set_antialias", _set_antialias },
+    { "set_dash", _set_dash },
+    { "set_device_offset", _set_device_offset },
+    { "set_extend", _set_extend },
+    { "set_fallback_resolution", _set_fallback_resolution },
+    { "set_fill_rule", _set_fill_rule },
+    { "set_filter", _set_filter },
+    { "set_font_face", _set_font_face },
+    { "set_font_options", _set_font_options },
+    { "set_font_matrix", _set_font_matrix },
+    { "set_font_size", _set_font_size },
+    { "set_line_cap", _set_line_cap },
+    { "set_line_join", _set_line_join },
+    { "set_line_width", _set_line_width },
+    { "set_matrix", _set_matrix },
+    { "set_miter_limit", _set_miter_limit },
+    { "set_mime_data", _set_mime_data },
+    { "set_operator", _set_operator },
+    { "set_scaled_font", _set_scaled_font },
+    { "set_source", _set_source },
+    { "set_source_image", _set_source_image },
+    { "set_tolerance", _set_tolerance },
+    { "show_glyphs", _show_glyphs },
+    { "show_text", _show_text },
+    { "show_text_glyphs", _show_text_glyphs },
+    { "show_page", _show_page },
+    { "similar", _similar },
+    { "sin", NULL },
+    { "sqrt", NULL },
+    { "sub", _sub },
+    { "surface", _surface },
+    { "string", NULL },
+    { "stroke", _stroke },
+    { "stroke_extents", NULL },
+    { "stroke_preserve", _stroke_preserve },
+    { "stroke+", _stroke_preserve },
+    { "text_path", _text_path },
+    { "transform", _transform },
+    { "transform_distance", NULL },
+    { "transform_point", NULL },
+    { "translate", _translate },
+    { "true", _true },
+    { "type", NULL },
+    { "undef", _undef },
+    { "unset", _unset },
+    { "user_to_device", NULL },
+    { "user_to_device_distance", NULL },
+    { "where", NULL },
+    { "write_to_png", _write_to_png },
+    { "xor", _xor },
+
+    { "=", _debug_print },
+
+    { NULL, NULL },
+};
+
+const csi_operator_def_t *
+_csi_operators (void)
+{
+    return _defs;
+}
+
+static const csi_integer_constant_def_t
+_integer_constants[] = {
+    { "CLEAR",		CAIRO_OPERATOR_CLEAR },
+    { "SOURCE",		CAIRO_OPERATOR_SOURCE },
+    { "OVER",		CAIRO_OPERATOR_OVER },
+    { "IN",		CAIRO_OPERATOR_IN },
+    { "OUT",		CAIRO_OPERATOR_OUT },
+    { "ATOP",		CAIRO_OPERATOR_ATOP },
+    { "DEST",		CAIRO_OPERATOR_DEST },
+    { "DEST_OVER",	CAIRO_OPERATOR_DEST_OVER },
+    { "DEST_IN",	CAIRO_OPERATOR_DEST_IN },
+    { "DEST_OUT",	CAIRO_OPERATOR_DEST_OUT },
+    { "DEST_ATOP",	CAIRO_OPERATOR_DEST_ATOP },
+    { "XOR",		CAIRO_OPERATOR_XOR },
+    { "ADD",		CAIRO_OPERATOR_ADD },
+    { "SATURATE",	CAIRO_OPERATOR_SATURATE },
+
+    { "WINDING",	CAIRO_FILL_RULE_WINDING },
+    { "EVEN_ODD",	CAIRO_FILL_RULE_EVEN_ODD },
+
+    { "ANTIALIAS_DEFAULT",	CAIRO_ANTIALIAS_DEFAULT },
+    { "ANTIALIAS_NONE",		CAIRO_ANTIALIAS_NONE },
+    { "ANTIALIAS_GRAY",		CAIRO_ANTIALIAS_GRAY },
+    { "ANTIALIAS_SUBPIXEL",	CAIRO_ANTIALIAS_SUBPIXEL },
+
+    { "LINE_CAP_BUTT",		CAIRO_LINE_CAP_BUTT },
+    { "LINE_CAP_ROUND",		CAIRO_LINE_CAP_ROUND },
+    { "LINE_CAP_SQUARE",	CAIRO_LINE_CAP_SQUARE },
+
+    { "LINE_JOIN_MITER",	CAIRO_LINE_JOIN_MITER },
+    { "LINE_JOIN_ROUND",	CAIRO_LINE_JOIN_ROUND },
+    { "LINE_JOIN_BEVEL",	CAIRO_LINE_JOIN_BEVEL },
+
+    { "EXTEND_NONE",		CAIRO_EXTEND_NONE },
+    { "EXTEND_REPEAT",		CAIRO_EXTEND_REPEAT },
+    { "EXTEND_REFLECT",		CAIRO_EXTEND_REFLECT },
+    { "EXTEND_PAD",		CAIRO_EXTEND_PAD },
+
+    { "FILTER_FAST",		CAIRO_FILTER_FAST },
+    { "FILTER_GOOD",		CAIRO_FILTER_GOOD },
+    { "FILTER_BEST",		CAIRO_FILTER_BEST },
+    { "FILTER_BILINEAR",	CAIRO_FILTER_BILINEAR },
+    { "FILTER_NEAREST",		CAIRO_FILTER_NEAREST },
+    { "FILTER_GAUSSIAN",	CAIRO_FILTER_GAUSSIAN },
+
+    { "SLANT_NORMAL",		CAIRO_FONT_SLANT_NORMAL },
+    { "SLANT_ITALIC",		CAIRO_FONT_SLANT_ITALIC },
+    { "SLANT_OBLIQUE",		CAIRO_FONT_SLANT_OBLIQUE },
+
+    { "WEIGHT_NORMAL",		CAIRO_FONT_WEIGHT_NORMAL },
+    { "WEIGHT_BOLD",		CAIRO_FONT_WEIGHT_BOLD },
+
+    { "SUBPIXEL_ORDER_DEFAULT",	CAIRO_SUBPIXEL_ORDER_DEFAULT },
+    { "SUBPIXEL_ORDER_RGB",	CAIRO_SUBPIXEL_ORDER_RGB },
+    { "SUBPIXEL_ORDER_BGR",	CAIRO_SUBPIXEL_ORDER_BGR },
+    { "SUBPIXEL_ORDER_VRGB",	CAIRO_SUBPIXEL_ORDER_VRGB },
+    { "SUBPIXEL_ORDER_VBGR",	CAIRO_SUBPIXEL_ORDER_VBGR },
+
+    { "HINT_STYLE_DEFAULT",	CAIRO_HINT_STYLE_DEFAULT },
+    { "HINT_STYLE_NONE",	CAIRO_HINT_STYLE_NONE },
+    { "HINT_STYLE_SLIGHT",	CAIRO_HINT_STYLE_SLIGHT },
+    { "HINT_STYLE_MEDIUM",	CAIRO_HINT_STYLE_MEDIUM },
+    { "HINT_STYLE_FULL",	CAIRO_HINT_STYLE_FULL },
+
+    { "HINT_METRICS_DEFAULT",	CAIRO_HINT_METRICS_DEFAULT },
+    { "HINT_METRICS_OFF",	CAIRO_HINT_METRICS_OFF },
+    { "HINT_METRICS_ON",	CAIRO_HINT_METRICS_ON },
+
+    { "FORWARD",		0 },
+    { "BACKWARD",		1 },
+
+    { "COLOR",			CAIRO_CONTENT_COLOR },
+    { "ALPHA",			CAIRO_CONTENT_ALPHA },
+    { "COLOR_ALPHA",		CAIRO_CONTENT_COLOR_ALPHA },
+
+    { "A1",			CAIRO_FORMAT_A1 },
+    { "A8",			CAIRO_FORMAT_A8 },
+    { "RGB24",			CAIRO_FORMAT_RGB24 },
+    { "ARGB32",			CAIRO_FORMAT_ARGB32 },
+
+    { NULL, 0 }
+};
+
+const csi_integer_constant_def_t *
+_csi_integer_constants (void)
+{
+    return _integer_constants;
+}
diff --git a/util/cairo-script/cairo-script-private.h b/util/cairo-script/cairo-script-private.h
new file mode 100644
index 0000000..b700977
--- /dev/null
+++ b/util/cairo-script/cairo-script-private.h
@@ -0,0 +1,853 @@
+/*
+ * Copyright © 2008 Chris Wilson <chris at chris-wilson.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Chris Wilson.
+ *
+ * Contributor(s):
+ *	Chris Wilson <chris at chris-wilson.co.uk>
+ */
+
+#ifndef CAIRO_SCRIPT_PRIVATE_H
+#define CAIRO_SCRIPT_PRIVATE_H
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "cairo-script-interpreter.h"
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef TRUE
+#define TRUE (!FALSE)
+#endif
+
+#ifndef NULL
+#define NULL (void *) 0
+#endif
+
+#if   HAVE_STDINT_H
+# include <stdint.h>
+#elif HAVE_INTTYPES_H
+# include <inttypes.h>
+#elif HAVE_SYS_INT_TYPES_H
+# include <sys/int_types.h>
+#elif defined(_MSC_VER)
+  typedef __int8 int8_t;
+  typedef unsigned __int8 uint8_t;
+  typedef __int16 int16_t;
+  typedef unsigned __int16 uint16_t;
+  typedef __int32 int32_t;
+  typedef unsigned __int32 uint32_t;
+  typedef __int64 int64_t;
+  typedef unsigned __int64 uint64_t;
+# ifndef HAVE_UINT64_T
+#  define HAVE_UINT64_T 1
+# endif
+#else
+#error Cannot find definitions for fixed-width integral types (uint8_t, uint32_t, etc.)
+#endif
+
+#if __GNUC__ >= 3 && defined(__ELF__) && !defined(__sun)
+# define slim_hidden_proto(name)		slim_hidden_proto1(name, slim_hidden_int_name(name)) csi_private
+# define slim_hidden_proto_no_warn(name)	slim_hidden_proto1(name, slim_hidden_int_name(name)) csi_private_no_warn
+# define slim_hidden_def(name)			slim_hidden_def1(name, slim_hidden_int_name(name))
+# define slim_hidden_int_name(name) INT_##name
+# define slim_hidden_proto1(name, internal)				\
+  extern __typeof (name) name						\
+	__asm__ (slim_hidden_asmname (internal))
+# define slim_hidden_def1(name, internal)				\
+  extern __typeof (name) EXT_##name __asm__(slim_hidden_asmname(name))	\
+	__attribute__((__alias__(slim_hidden_asmname(internal))))
+# define slim_hidden_ulp		slim_hidden_ulp1(__USER_LABEL_PREFIX__)
+# define slim_hidden_ulp1(x)		slim_hidden_ulp2(x)
+# define slim_hidden_ulp2(x)		#x
+# define slim_hidden_asmname(name)	slim_hidden_asmname1(name)
+# define slim_hidden_asmname1(name)	slim_hidden_ulp #name
+#else
+# define slim_hidden_proto(name)		int _csi_dummy_prototype(void)
+# define slim_hidden_proto_no_warn(name)	int _csi_dummy_prototype(void)
+# define slim_hidden_def(name)			int _csi_dummy_prototype(void)
+#endif
+
+#if __GNUC__ >= 3
+#define csi_pure __attribute__((pure))
+#define csi_const __attribute__((const))
+#else
+#define csi_pure
+#define csi_const
+#endif
+
+#if defined(__GNUC__) && (__GNUC__ > 2) && defined(__OPTIMIZE__)
+#define _CSI_BOOLEAN_EXPR(expr)                   \
+ __extension__ ({                               \
+   int _csi_boolean_var_;                         \
+   if (expr)                                    \
+      _csi_boolean_var_ = 1;                      \
+   else                                         \
+      _csi_boolean_var_ = 0;                      \
+   _csi_boolean_var_;                             \
+})
+#define _csi_likely(expr) (__builtin_expect (_CSI_BOOLEAN_EXPR(expr), 1))
+#define _csi_unlikely(expr) (__builtin_expect (_CSI_BOOLEAN_EXPR(expr), 0))
+#else
+#define _csi_likely(expr) (expr)
+#define _csi_unlikely(expr) (expr)
+#endif
+
+#ifdef __GNUC__
+#ifndef offsetof
+#define offsetof(type, member) \
+    ((char *) &((type *) 0)->member - (char *) 0)
+#endif
+#define csi_container_of(ptr, type, member) ({ \
+    const typeof(((type *) 0)->member) *mptr__ = (ptr); \
+    (type *) ((char *) mptr__ - offsetof (type, member)); \
+})
+#else
+#define csi_container_of(ptr, type, member) \
+    (type *)((char *) (ptr) - (char *) &((type *)0)->member)
+#endif
+
+/* slim_internal.h */
+#if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3)) && defined(__ELF__) && !defined(__sun)
+#define csi_private_no_warn	__attribute__((__visibility__("hidden")))
+#elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x550)
+#define csi_private_no_warn	__hidden
+#else /* not gcc >= 3.3 and not Sun Studio >= 8 */
+#define csi_private_no_warn
+#endif
+
+#undef  ARRAY_LENGTH
+#define ARRAY_LENGTH(__array) ((int) (sizeof (__array) / sizeof (__array[0])))
+
+#ifndef WARN_UNUSED_RESULT
+#define WARN_UNUSED_RESULT
+#endif
+/* Add attribute(warn_unused_result) if supported */
+#define csi_warn	    WARN_UNUSED_RESULT
+#define csi_private	    csi_private_no_warn csi_warn
+
+#define CSI_BITSWAP8(c) ((((c) * 0x0802LU & 0x22110LU) | ((c) * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16)
+#ifdef WORDS_BIGENDIAN
+#define CSI_BITSWAP8_IF_LITTLE_ENDIAN(c) (c)
+#else
+#define CSI_BITSWAP8_IF_LITTLE_ENDIAN(c) CSI_BITSWAP8(c)
+#endif
+
+typedef enum _csi_status {
+    CSI_STATUS_SUCCESS = CAIRO_STATUS_SUCCESS,
+    CSI_STATUS_NO_MEMORY = CAIRO_STATUS_NO_MEMORY,
+    CSI_STATUS_INVALID_RESTORE = CAIRO_STATUS_INVALID_RESTORE,
+    CSI_STATUS_INVALID_POP_GROUP = CAIRO_STATUS_INVALID_POP_GROUP,
+    CSI_STATUS_NO_CURRENT_POINT = CAIRO_STATUS_NO_CURRENT_POINT,
+    CSI_STATUS_INVALID_MATRIX = CAIRO_STATUS_INVALID_MATRIX,
+    CSI_STATUS_INVALID_STATUS = CAIRO_STATUS_INVALID_STATUS,
+    CSI_STATUS_NULL_POINTER = CAIRO_STATUS_NULL_POINTER,
+    CSI_STATUS_INVALID_STRING = CAIRO_STATUS_INVALID_STRING,
+    CSI_STATUS_INVALID_PATH_DATA = CAIRO_STATUS_INVALID_PATH_DATA,
+    CSI_STATUS_READ_ERROR = CAIRO_STATUS_READ_ERROR,
+    CSI_STATUS_WRITE_ERROR = CAIRO_STATUS_WRITE_ERROR,
+    CSI_STATUS_SURFACE_FINISHED = CAIRO_STATUS_SURFACE_FINISHED,
+    CSI_STATUS_SURFACE_TYPE_MISMATCH = CAIRO_STATUS_SURFACE_TYPE_MISMATCH,
+    CSI_STATUS_PATTERN_TYPE_MISMATCH = CAIRO_STATUS_PATTERN_TYPE_MISMATCH,
+    CSI_STATUS_INVALID_CONTENT = CAIRO_STATUS_INVALID_CONTENT,
+    CSI_STATUS_INVALID_FORMAT = CAIRO_STATUS_INVALID_FORMAT,
+    CSI_STATUS_INVALID_VISUAL = CAIRO_STATUS_INVALID_VISUAL,
+    CSI_STATUS_FILE_NOT_FOUND = CAIRO_STATUS_FILE_NOT_FOUND,
+    CSI_STATUS_INVALID_DASH = CAIRO_STATUS_INVALID_DASH,
+    CSI_STATUS_INVALID_DSC_COMMENT = CAIRO_STATUS_INVALID_DSC_COMMENT,
+    CSI_STATUS_INVALID_INDEX = CAIRO_STATUS_INVALID_INDEX,
+    CSI_STATUS_CLIP_NOT_REPRESENTABLE = CAIRO_STATUS_CLIP_NOT_REPRESENTABLE,
+    CSI_STATUS_TEMP_FILE_ERROR = CAIRO_STATUS_TEMP_FILE_ERROR,
+    CSI_STATUS_INVALID_STRIDE = CAIRO_STATUS_INVALID_STRIDE,
+    CSI_STATUS_FONT_TYPE_MISMATCH = CAIRO_STATUS_FONT_TYPE_MISMATCH,
+    CSI_STATUS_USER_FONT_IMMUTABLE = CAIRO_STATUS_USER_FONT_IMMUTABLE,
+    CSI_STATUS_USER_FONT_ERROR = CAIRO_STATUS_USER_FONT_ERROR,
+    CSI_STATUS_NEGATIVE_COUNT = CAIRO_STATUS_NEGATIVE_COUNT,
+    CSI_STATUS_INVALID_CLUSTERS = CAIRO_STATUS_INVALID_CLUSTERS,
+    CSI_STATUS_INVALID_SLANT = CAIRO_STATUS_INVALID_SLANT,
+    CSI_STATUS_INVALID_WEIGHT = CAIRO_STATUS_INVALID_WEIGHT,
+
+    /* cairo-script-interpreter specific errors */
+
+    CSI_STATUS_INVALID_SCRIPT,
+    CSI_STATUS_SCRIPT_INVALID_TYPE,
+    CSI_STATUS_SCRIPT_INVALID_INDEX,
+    CSI_STATUS_SCRIPT_UNDEFINED_NAME,
+
+    _CSI_STATUS_SCRIPT_LAST_ERROR,
+    CSI_INT_STATUS_UNSUPPORTED
+} csi_status_t;
+
+typedef enum {
+    CSI_OBJECT_TYPE_NULL = 0,
+
+    /* atomics */
+    CSI_OBJECT_TYPE_BOOLEAN,
+    CSI_OBJECT_TYPE_INTEGER,
+    CSI_OBJECT_TYPE_MARK,
+    CSI_OBJECT_TYPE_NAME,
+    CSI_OBJECT_TYPE_OPERATOR,
+    CSI_OBJECT_TYPE_REAL,
+
+    /* compound */
+    CSI_OBJECT_TYPE_ARRAY = 0x8,
+    CSI_OBJECT_TYPE_DICTIONARY,
+    CSI_OBJECT_TYPE_FILE,
+    CSI_OBJECT_TYPE_MATRIX,
+    CSI_OBJECT_TYPE_STRING,
+
+    /* cairo */
+    CSI_OBJECT_TYPE_CONTEXT = 0x10,
+    CSI_OBJECT_TYPE_FONT,
+    CSI_OBJECT_TYPE_PATTERN,
+    CSI_OBJECT_TYPE_SCALED_FONT,
+    CSI_OBJECT_TYPE_SURFACE,
+} csi_object_type_t;
+
+#define CSI_OBJECT_IS_ATOM(OBJ) (((OBJ)->type & CSI_OBJECT_TYPE_MASK) < 0x08)
+#define CSI_OBJECT_IS_COMPOUND(OBJ) ((OBJ)->type & 0x08)
+#define CSI_OBJECT_IS_CAIRO(OBJ) ((OBJ)->type & 0x10)
+
+enum { /* attributes */
+    CSI_OBJECT_ATTR_EXECUTABLE = 1 << 6,
+    CSI_OBJECT_ATTR_WRITABLE   = 1 << 7,
+};
+#define CSI_OBJECT_ATTR_MASK (CSI_OBJECT_ATTR_EXECUTABLE | \
+			      CSI_OBJECT_ATTR_WRITABLE)
+#define CSI_OBJECT_TYPE_MASK (~CSI_OBJECT_ATTR_MASK)
+
+typedef struct _cairo_script_interpreter csi_t;
+
+typedef cairo_bool_t csi_boolean_t;
+typedef csi_status_t (*csi_operator_t) (csi_t *);
+typedef float csi_real_t;
+typedef long csi_integer_t;
+typedef long csi_name_t;
+typedef struct _csi_array csi_array_t;
+typedef struct _csi_buffer csi_buffer_t;
+typedef struct _csi_compound_object csi_compound_object_t;
+typedef struct _csi_dictionary csi_dictionary_t;
+typedef struct _csi_file csi_file_t;
+typedef struct _csi_hash_entry csi_hash_entry_t;
+typedef struct _csi_hash_table csi_hash_table_t;
+typedef struct _csi_hash_table_arrangement csi_hash_table_arrangement_t;
+typedef struct _csi_list csi_list_t;
+typedef struct _csi_matrix csi_matrix_t;
+typedef struct _csi_object csi_object_t;
+typedef struct _csi_scanner csi_scanner_t;
+typedef struct _csi_stack csi_stack_t;
+typedef struct _csi_string csi_string_t;
+
+typedef cairo_bool_t
+(*csi_hash_predicate_func_t) (void *entry);
+
+typedef void
+(*csi_hash_callback_func_t) (void *entry,
+			     void *closure);
+
+typedef cairo_bool_t
+(*csi_hash_keys_equal_func_t) (const void *key_a, const void *key_b);
+
+struct _csi_object {
+    csi_object_type_t type;
+    union {
+	cairo_t *cr;
+	cairo_font_face_t *font_face;
+	cairo_pattern_t *pattern;
+	cairo_scaled_font_t *scaled_font;
+	cairo_surface_t *surface;
+	csi_array_t *array;
+	csi_boolean_t boolean;
+	csi_compound_object_t *object;
+	csi_dictionary_t *dictionary;
+	csi_file_t *file;
+	csi_integer_t integer;
+	csi_matrix_t *matrix;
+	csi_operator_t op;
+	csi_name_t name;
+	csi_real_t real;
+	csi_string_t *string;
+	void *ptr;
+    } datum;
+};
+
+struct _csi_compound_object {
+    csi_object_type_t type;
+    unsigned int ref;
+};
+
+struct _csi_hash_entry {
+    unsigned long hash;
+};
+
+struct _csi_hash_table_arrangement {
+    unsigned long high_water_mark;
+    unsigned long size;
+    unsigned long rehash;
+};
+
+struct _csi_hash_table {
+    csi_hash_keys_equal_func_t keys_equal;
+
+    const csi_hash_table_arrangement_t *arrangement;
+    csi_hash_entry_t **entries;
+
+    unsigned long live_entries;
+    unsigned long iterating;   /* Iterating, no insert, no resize */
+};
+
+
+/* simple, embedded doubly-linked links */
+struct _csi_list {
+    struct _csi_list *next, *prev;
+};
+
+struct _csi_buffer {
+    csi_status_t status;
+
+    char *base, *ptr, *end;
+    unsigned int size;
+};
+
+struct _csi_stack {
+    csi_object_t *objects;
+    csi_integer_t len;
+    csi_integer_t size;
+};
+
+struct _csi_array {
+    csi_compound_object_t base;
+    csi_stack_t stack;
+};
+
+typedef struct _csi_dictionary_entry {
+    csi_hash_entry_t hash_entry;
+    csi_object_t value;
+} csi_dictionary_entry_t;
+
+struct _csi_dictionary {
+    csi_compound_object_t base;
+    csi_hash_table_t hash_table;
+};
+
+struct _csi_matrix {
+    csi_compound_object_t base;
+    cairo_matrix_t matrix;
+};
+
+struct _csi_string {
+    csi_compound_object_t base;
+    csi_integer_t len;
+    char *string;
+};
+
+typedef struct _csi_filter_funcs {
+    int (*filter_getc) (csi_file_t *);
+    void (*filter_putc) (csi_file_t *, int);
+    int (*filter_read) (csi_file_t *, uint8_t *, int);
+    void (*filter_destroy) (csi_t *, void *);
+} csi_filter_funcs_t;
+
+struct _csi_file {
+    csi_compound_object_t base;
+    enum {
+	STDIO,
+	BYTES,
+	PROCEDURE,
+	FILTER,
+    } type;
+    void *src;
+    void *data;
+    uint8_t *bp;
+    int rem;
+    const csi_filter_funcs_t *filter;
+};
+
+struct _csi_scanner {
+    csi_status_t status;
+
+    enum {
+	NONE,
+	TOKEN,
+	COMMENT,
+	STRING,
+	HEX,
+	BASE85,
+    } state;
+
+    csi_buffer_t buffer;
+    csi_stack_t procedure_stack;
+    csi_object_t build_procedure;
+
+    int string_p;
+    unsigned int accumulator;
+    unsigned int accumulator_count;
+};
+
+typedef cairo_script_interpreter_hooks_t csi_hooks_t;
+
+struct _cairo_script_interpreter {
+    int ref_count;
+    csi_status_t status;
+
+    csi_hooks_t hooks;
+
+    csi_hash_table_t strings;
+
+    csi_stack_t ostack;
+    csi_stack_t dstack;
+
+    csi_scanner_t scanner;
+
+    /* caches of live data */
+    csi_list_t *_images;
+    csi_list_t *_faces;
+};
+
+typedef struct _csi_operator_def {
+    const char *name;
+    csi_operator_t op;
+} csi_operator_def_t;
+
+typedef struct _csi_integer_constant_def {
+    const char *name;
+    csi_integer_t value;
+} csi_integer_constant_def_t;
+
+/* cairo-script-file.c */
+
+csi_private csi_status_t
+csi_file_new (csi_t *ctx,
+	      csi_object_t *obj,
+	      const char *path, const char *mode);
+
+csi_private csi_status_t
+csi_file_new_for_bytes (csi_t *ctx,
+			csi_object_t *obj,
+			const char *bytes,
+			unsigned int length);
+
+csi_private csi_status_t
+csi_file_new_from_string (csi_t *ctx,
+			  csi_object_t *obj,
+			  csi_string_t *src);
+
+csi_private csi_status_t
+csi_file_new_ascii85_decode (csi_t *ctx,
+			     csi_object_t *obj,
+			     csi_dictionary_t *dict,
+			     csi_object_t *src);
+
+csi_private csi_status_t
+csi_file_new_deflate_decode (csi_t *ctx,
+			     csi_object_t *obj,
+			     csi_dictionary_t *dict,
+			     csi_object_t *src);
+
+csi_private csi_status_t
+_csi_file_execute (csi_t *ctx, csi_file_t *obj);
+
+csi_private int
+csi_file_getc (csi_file_t *obj);
+
+csi_private int
+csi_file_read (csi_file_t *obj, void *buf, int len);
+
+csi_private void
+csi_file_putc (csi_file_t *obj, int c);
+
+csi_private void
+csi_file_flush (csi_file_t *obj);
+
+csi_private void
+csi_file_close (csi_t *ctx, csi_file_t *obj);
+
+csi_private void
+_csi_file_free (csi_t *ctx, csi_file_t *obj);
+
+csi_private csi_status_t
+_csi_file_as_string (csi_t *ctx,
+		     csi_file_t *file,
+		     csi_object_t *obj);
+
+/* cairo-script-hash.c */
+
+csi_private csi_status_t
+_csi_hash_table_init (csi_hash_table_t *hash_table,
+		      csi_hash_keys_equal_func_t keys_equal);
+
+csi_private void
+_csi_hash_table_fini (csi_hash_table_t *hash_table);
+
+csi_private void *
+_csi_hash_table_lookup (csi_hash_table_t  *hash_table,
+			csi_hash_entry_t  *key);
+
+csi_private csi_status_t
+_csi_hash_table_insert (csi_hash_table_t *hash_table,
+			csi_hash_entry_t *entry);
+
+csi_private void
+_csi_hash_table_remove (csi_hash_table_t *hash_table,
+			csi_hash_entry_t *key);
+
+csi_private void
+_csi_hash_table_foreach (csi_hash_table_t	      *hash_table,
+			 csi_hash_callback_func_t  hash_callback,
+			 void			      *closure);
+
+/* cairo-script-interpreter.c */
+
+csi_private void *
+_csi_alloc (csi_t *ctx, int size);
+
+csi_private void *
+_csi_alloc0 (csi_t *ctx, int size);
+
+csi_private void *
+_csi_realloc (csi_t *ctx, void *ptr, int size);
+
+csi_private void
+_csi_free (csi_t *ctx, void *ptr);
+
+csi_private void *
+_csi_slab_alloc (csi_t *ctx, int size);
+
+csi_private void
+_csi_slab_free (csi_t *ctx, void *ptr, int size);
+
+csi_private csi_status_t
+csi_push_ostack (csi_t *ctx, csi_object_t *obj);
+
+csi_private csi_status_t
+_csi_name_define (csi_t *ctx, csi_name_t name, csi_object_t *obj);
+
+csi_private csi_status_t
+_csi_name_lookup (csi_t *ctx, csi_name_t name, csi_object_t *obj);
+
+csi_private csi_status_t
+_csi_name_undefine (csi_t *ctx, csi_name_t name);
+
+csi_private csi_status_t
+_csi_intern_string (csi_t *ctx, const char **str_inout, int len);
+
+csi_private csi_status_t
+_csi_error (csi_status_t status);
+
+/* cairo-script-objects.c */
+
+csi_private csi_status_t
+csi_array_new (csi_t *ctx,
+	       csi_object_t *obj);
+
+csi_private csi_status_t
+_csi_array_execute (csi_t *ctx, csi_array_t *array);
+
+csi_private csi_status_t
+csi_array_get (csi_t *ctx,
+	       csi_array_t *array,
+	       long elem,
+	       csi_object_t *value);
+
+csi_private csi_status_t
+csi_array_put (csi_t *ctx,
+	       csi_array_t *array,
+	       csi_integer_t elem,
+	       csi_object_t *value);
+
+csi_private csi_status_t
+csi_array_append (csi_t *ctx,
+		  csi_array_t *array,
+		  csi_object_t *obj);
+
+csi_private void
+csi_array_free (csi_t *ctx, csi_array_t *array);
+
+csi_private csi_status_t
+csi_boolean_new (csi_t *ctx,
+		 csi_object_t *obj,
+		 csi_boolean_t v);
+
+csi_private csi_status_t
+csi_dictionary_new (csi_t *ctx,
+		    csi_object_t *obj);
+
+csi_private csi_status_t
+csi_dictionary_put (csi_t *ctx,
+		    csi_dictionary_t *dict,
+		    csi_name_t name,
+		    csi_object_t *value);
+
+csi_private csi_status_t
+csi_dictionary_get (csi_t *ctx,
+		    csi_dictionary_t *dict,
+		    csi_name_t name,
+		    csi_object_t *value);
+
+csi_private csi_boolean_t
+csi_dictionary_has (csi_dictionary_t *dict,
+		    csi_name_t name);
+
+csi_private void
+csi_dictionary_remove (csi_t *ctx,
+		       csi_dictionary_t *dict,
+		       csi_name_t name);
+
+csi_private void
+csi_dictionary_free (csi_t *ctx,
+		     csi_dictionary_t *dict);
+
+csi_private csi_status_t
+csi_integer_new (csi_t *ctx,
+		 csi_object_t *obj,
+		 csi_integer_t v);
+
+csi_private csi_status_t
+csi_matrix_new (csi_t *ctx,
+		csi_object_t *obj);
+
+csi_private csi_status_t
+csi_matrix_new_from_array (csi_t *ctx,
+			   csi_object_t *obj,
+			   csi_array_t *array);
+
+csi_private csi_status_t
+csi_matrix_new_from_matrix (csi_t *ctx,
+			    csi_object_t *obj,
+			    const cairo_matrix_t *m);
+
+csi_private csi_status_t
+csi_matrix_new_from_values (csi_t *ctx,
+			    csi_object_t *obj,
+			    double v[6]);
+
+csi_private void
+csi_matrix_free (csi_t *ctx,
+		 csi_matrix_t *obj);
+
+csi_private csi_status_t
+csi_name_new (csi_t *ctx,
+	      csi_object_t *obj,
+	      const char *str,
+	      int len);
+
+csi_private csi_status_t
+csi_name_new_static (csi_t *ctx,
+		     csi_object_t *obj,
+		     const char *str);
+
+csi_private csi_status_t
+csi_operator_new (csi_t *ctx,
+		  csi_object_t *obj,
+		  csi_operator_t op);
+
+csi_private csi_status_t
+csi_real_new (csi_t *ctx,
+	      csi_object_t *obj,
+	      csi_real_t v);
+
+csi_private csi_status_t
+csi_string_new (csi_t *ctx,
+		csi_object_t *obj,
+		const char *str,
+		int len);
+
+csi_private void
+csi_string_free (csi_t *ctx, csi_string_t *string);
+
+csi_private csi_status_t
+csi_object_execute (csi_t *ctx, csi_object_t *obj);
+
+csi_private csi_object_t *
+csi_object_reference (csi_object_t *obj);
+
+csi_private void
+csi_object_free (csi_t *ctx,
+		 csi_object_t *obj);
+
+csi_private csi_status_t
+csi_object_as_file (csi_t *ctx,
+		    csi_object_t *src,
+		    csi_object_t *file);
+
+/* cairo-script-operators.c */
+
+csi_private const csi_operator_def_t *
+_csi_operators (void);
+
+csi_private const csi_integer_constant_def_t *
+_csi_integer_constants (void);
+
+/* cairo-script-scanner.c */
+
+csi_private csi_status_t
+_csi_scanner_init (csi_t *ctx, csi_scanner_t *scanner);
+
+csi_private csi_status_t
+_csi_scan_file (csi_t *ctx, csi_scanner_t *scan, csi_file_t *src);
+
+csi_private void
+_csi_scanner_fini (csi_t *ctx, csi_scanner_t *scanner);
+
+/* cairo-script-stack.c */
+
+csi_private csi_status_t
+_csi_stack_init (csi_t *ctx, csi_stack_t *stack, csi_integer_t size);
+
+csi_private void
+_csi_stack_fini (csi_t *ctx, csi_stack_t *stack);
+
+csi_private csi_status_t
+_csi_stack_roll (csi_t *ctx,
+		 csi_stack_t *stack,
+		 csi_integer_t mod,
+		 csi_integer_t n);
+
+csi_private csi_status_t
+_csi_stack_grow (csi_t *ctx, csi_stack_t *stack, csi_integer_t cnt);
+
+csi_private csi_status_t
+_csi_stack_push (csi_t *ctx, csi_stack_t *stack, const csi_object_t *obj);
+
+csi_private csi_object_t *
+_csi_stack_peek (csi_stack_t *stack, csi_integer_t i);
+
+csi_private void
+_csi_stack_pop (csi_t *ctx, csi_stack_t *stack, csi_integer_t count);
+
+csi_private csi_status_t
+_csi_stack_exch (csi_stack_t *stack);
+
+static inline csi_object_type_t
+csi_object_get_type (const csi_object_t *obj)
+{
+    return obj->type & CSI_OBJECT_TYPE_MASK;
+}
+
+static inline csi_boolean_t
+csi_object_is_procedure (const csi_object_t *obj)
+{
+    return obj->type == (CSI_OBJECT_TYPE_ARRAY | CSI_OBJECT_ATTR_EXECUTABLE);
+}
+
+static inline csi_boolean_t
+csi_object_is_number (const csi_object_t *obj)
+{
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_BOOLEAN:
+    case CSI_OBJECT_TYPE_INTEGER:
+    case CSI_OBJECT_TYPE_REAL:
+	return 1;
+    default:
+	return 0;
+    }
+}
+
+static inline double
+csi_number_get_value (const csi_object_t *obj)
+{
+    switch ((int) csi_object_get_type (obj)) {
+    case CSI_OBJECT_TYPE_BOOLEAN: return obj->datum.boolean;
+    case CSI_OBJECT_TYPE_INTEGER: return obj->datum.integer;
+    case CSI_OBJECT_TYPE_REAL: return obj->datum.real;
+    default: return 0.;
+    }
+}
+
+static inline csi_boolean_t
+_csi_check_ostack (csi_t *ctx, csi_integer_t count)
+{
+    return ctx->ostack.len >= count;
+}
+
+static inline csi_object_t *
+_csi_peek_ostack (csi_t *ctx, csi_integer_t i)
+{
+    return &ctx->ostack.objects[ctx->ostack.len - i -1];
+}
+
+static inline void
+_csi_pop_ostack (csi_t *ctx, csi_integer_t count)
+{
+    do
+	csi_object_free (ctx, &ctx->ostack.objects[--ctx->ostack.len]);
+    while (--count);
+}
+
+static inline csi_status_t
+_csi_push_ostack_copy (csi_t *ctx, csi_object_t *obj)
+{
+    return _csi_stack_push (ctx, &ctx->ostack, csi_object_reference (obj));
+}
+
+static inline csi_status_t
+_csi_push_ostack (csi_t *ctx, csi_object_t *obj)
+{
+    return _csi_stack_push (ctx, &ctx->ostack, obj);
+}
+
+static inline csi_status_t
+_csi_push_ostack_boolean (csi_t *ctx, csi_boolean_t v)
+{
+    csi_object_t obj;
+    obj.type = CSI_OBJECT_TYPE_BOOLEAN;
+    obj.datum.boolean = v;
+    return _csi_stack_push (ctx, &ctx->ostack, &obj);
+}
+static inline csi_status_t
+_csi_push_ostack_integer (csi_t *ctx, csi_integer_t v)
+{
+    csi_object_t obj;
+    obj.type = CSI_OBJECT_TYPE_INTEGER;
+    obj.datum.integer = v;
+    return _csi_stack_push (ctx, &ctx->ostack, &obj);
+}
+static inline csi_status_t
+_csi_push_ostack_mark (csi_t *ctx)
+{
+    csi_object_t obj;
+    obj.type = CSI_OBJECT_TYPE_MARK;
+    return _csi_stack_push (ctx, &ctx->ostack, &obj);
+}
+static inline csi_status_t
+_csi_push_ostack_real (csi_t *ctx, csi_real_t v)
+{
+    csi_object_t obj;
+    obj.type = CSI_OBJECT_TYPE_REAL;
+    obj.datum.real = v;
+    return _csi_stack_push (ctx, &ctx->ostack, &obj);
+}
+
+slim_hidden_proto_no_warn (cairo_script_interpreter_destroy);
+slim_hidden_proto_no_warn (cairo_script_interpreter_reference);
+
+#endif /* CAIRO_SCRIPT_PRIVATE_H */
diff --git a/util/cairo-script/cairo-script-scanner.c b/util/cairo-script/cairo-script-scanner.c
new file mode 100644
index 0000000..bc3ff2c
--- /dev/null
+++ b/util/cairo-script/cairo-script-scanner.c
@@ -0,0 +1,1180 @@
+/*
+ * Copyright © 2008 Chris Wilson <chris at chris-wilson.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Chris Wilson.
+ *
+ * Contributor(s):
+ *	Chris Wilson <chris at chris-wilson.co.uk>
+ */
+
+#include "cairo-script-private.h"
+
+#include <stdio.h> /* EOF */
+#include <string.h> /* memset */
+#include <math.h> /* pow */
+#include <byteswap.h> /* bswap_32 */
+
+/*
+ * whitespace:
+ * 0 - nul
+ * 9 - tab
+ * A - LF
+ * C - FF
+ * D - CR
+ *
+ * syntax delimiters
+ * ( = 28, ) = 29 - literal strings
+ * < = 3C, > = 3E - hex/base85 strings, dictionary name
+ * [ = 5B, ] = 5D - array
+ * { = 7B, } = 7C - procedure
+ * / = 5C - literal marker
+ * % = 25 - comment
+ */
+
+static cairo_status_t
+_csi_buffer_init (csi_t *ctx, csi_buffer_t *buffer)
+{
+    buffer->status = CSI_STATUS_SUCCESS;
+    buffer->size = 16384;
+    buffer->base = _csi_alloc (ctx, buffer->size);
+    if (_csi_unlikely (buffer->base == NULL)) {
+	buffer->status = _csi_error (CSI_STATUS_NO_MEMORY);
+	buffer->size = 0;
+    }
+
+    buffer->ptr = buffer->base;
+    buffer->end = buffer->base + buffer->size;
+
+    return buffer->status;
+}
+
+static void
+_csi_buffer_fini (csi_t *ctx, csi_buffer_t *buffer)
+{
+    _csi_free (ctx, buffer->base);
+}
+
+static cairo_status_t
+_csi_buffer_grow (csi_t *ctx, csi_buffer_t *buffer)
+{
+    int newsize;
+    int offset;
+    char *base;
+
+    if (_csi_unlikely (buffer->status))
+	return buffer->status;
+
+    if (_csi_unlikely (buffer->size > INT32_MAX / 2))
+	return buffer->status = _csi_error (CSI_STATUS_NO_MEMORY);
+
+    offset = buffer->ptr - buffer->base;
+    newsize = buffer->size * 2;
+    base = _csi_realloc (ctx, buffer->base, newsize);
+    if (_csi_unlikely (base == NULL))
+	return buffer->status = _csi_error (CSI_STATUS_NO_MEMORY);
+
+    buffer->base = base;
+    buffer->ptr  = base + offset;
+    buffer->end  = base + newsize;
+    buffer->size = newsize;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+static inline csi_boolean_t
+_csi_buffer_check (csi_t *ctx, csi_buffer_t *buffer, int count)
+{
+    if (_csi_unlikely (buffer->ptr + count > buffer->end)) {
+	if (_csi_buffer_grow (ctx, buffer))
+	    return FALSE;
+    }
+
+    return TRUE;
+}
+
+static inline void
+_csi_buffer_add (csi_buffer_t *buffer, int c)
+{
+    *buffer->ptr++ = c;
+}
+
+static inline void
+_csi_buffer_reset (csi_buffer_t *buffer)
+{
+    buffer->ptr = buffer->base;
+}
+
+static inline int
+scan_getc (csi_scanner_t *scan, csi_file_t *src)
+{
+    if (_csi_unlikely (scan->status))
+	return EOF;
+
+    return csi_file_getc (src);
+}
+
+static inline int
+scan_read (csi_scanner_t *scan, csi_file_t *src, void *buf, int len)
+{
+    return csi_file_read (src, buf, len);
+}
+
+static inline void
+scan_putc (csi_scanner_t *scan, csi_file_t *src, int c)
+{
+    csi_file_putc (src, c);
+}
+
+static inline void
+reset (csi_scanner_t *scan)
+{
+    scan->state = NONE;
+}
+
+static void
+token_start (csi_scanner_t *scan)
+{
+    scan->state = TOKEN;
+    _csi_buffer_reset (&scan->buffer);
+}
+
+static void
+token_add (csi_t *ctx, csi_scanner_t *scan, int c)
+{
+    if (_csi_likely (_csi_buffer_check (ctx, &scan->buffer, 1)))
+	_csi_buffer_add (&scan->buffer, c);
+}
+
+static void
+token_add_unchecked (csi_scanner_t *scan, int c)
+{
+    _csi_buffer_add (&scan->buffer, c);
+}
+
+static csi_boolean_t
+parse_number (csi_object_t *obj, const char *s, int len)
+{
+    int radix = 0;
+    long long mantissa = 0;
+    int exponent = 0;
+    int sign = 1;
+    int decimal = -1;
+    int exponent_sign = 0;
+    const char * const end = s + len;
+
+    switch (*s) {
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+	mantissa = *s - '0';
+    case '+':
+	break;
+    case '-':
+	sign = -1;
+	break;
+    case '.':
+	decimal = 0;
+	break;
+    default:
+	return FALSE;
+    }
+
+    while (++s < end) {
+	if (*s < '0') {
+	    if (*s == '.') {
+		if (radix)
+		    return FALSE;
+		if (decimal != -1)
+		    return FALSE;
+		if (exponent_sign)
+		    return FALSE;
+
+		decimal = 0;
+	    } else if (*s == '!') {
+		if (radix)
+		    return FALSE;
+		if (decimal != -1)
+		    return FALSE;
+		if (exponent_sign)
+		    return FALSE;
+
+		radix = mantissa;
+		mantissa = 0;
+
+		if (radix < 2 || radix > 36)
+		    return FALSE;
+	    } else
+		return FALSE;
+	} else if (*s <= '9') {
+	    int v = *s - '0';
+	    if (radix && v >= radix)
+		return FALSE;
+
+	    if (exponent_sign) {
+		exponent = 10 * exponent + v;
+	    } else {
+		if (radix)
+		    mantissa = radix * mantissa + v;
+		else
+		    mantissa = 10 * mantissa + v;
+		if (decimal != -1)
+		    decimal++;
+	    }
+	} else if (*s == 'E' || * s== 'e') {
+	    if (radix == 0) {
+		if (s + 1 == end)
+		    return FALSE;
+
+		exponent_sign = 1;
+		if (s[1] == '-') {
+		    exponent_sign = -1;
+		    s++;
+		} else if (s[1] == '+')
+		    s++;
+	    } else {
+		int v = 0xe;
+
+		if (v >= radix)
+		    return FALSE;
+
+		mantissa = radix * mantissa + v;
+	    }
+	} else if (*s < 'A') {
+	    return FALSE;
+	} else if (*s <= 'Z') {
+	    int v = *s - 'A' + 0xA;
+
+	    if (v >= radix)
+		return FALSE;
+
+	    mantissa = radix * mantissa + v;
+	} else if (*s < 'a') {
+	    return FALSE;
+	} else if (*s <= 'z') {
+	    int v = *s - 'a' + 0xa;
+
+	    if (v >= radix)
+		return FALSE;
+
+	    mantissa = radix * mantissa + v;
+	} else
+	    return FALSE;
+    }
+
+    if (exponent_sign || decimal != -1) {
+	if (mantissa == 0) {
+	    obj->type = CSI_OBJECT_TYPE_REAL;
+	    obj->datum.real = 0.;
+	    return TRUE;
+	} else {
+	    int e;
+	    double v;
+
+	    v = mantissa;
+	    e = exponent * exponent_sign;
+	    if (decimal != -1)
+		e -= decimal;
+	    if (e != 0)
+		v *= pow (10, e); /* XXX */
+
+	    obj->type = CSI_OBJECT_TYPE_REAL;
+	    obj->datum.real = sign * v;
+	    return TRUE;
+	}
+    } else {
+	obj->type = CSI_OBJECT_TYPE_INTEGER;
+	obj->datum.integer = sign * mantissa;
+	return TRUE;
+    }
+}
+
+static void
+token_end (csi_t *ctx, csi_scanner_t *scan, csi_file_t *src)
+{
+    char *s;
+    csi_object_t obj;
+    int len;
+
+    /*
+     * Any token that consists entirely of regular characters and
+     * cannot be interpreted as a number is treated as a name object
+     * (more precisely, an executable name). All characters except
+     * delimiters and white-space characters can appear in names,
+     * including characters ordinarily considered to be punctuation.
+     */
+
+    if (_csi_unlikely (scan->buffer.ptr == scan->buffer.base))
+	return;
+
+    scan->status = scan->buffer.status;
+    if (_csi_unlikely (scan->status))
+	return;
+
+    s = scan->buffer.base;
+    len = scan->buffer.ptr - scan->buffer.base;
+
+    if (s[0] == '{') { /* special case procedures */
+	if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL)
+	    scan->status = _csi_stack_push (ctx,
+					    &scan->procedure_stack,
+					    &scan->build_procedure);
+
+	scan->status = csi_array_new (ctx, &scan->build_procedure);
+	scan->build_procedure.type |= CSI_OBJECT_ATTR_EXECUTABLE;
+	reset (scan);
+	return;
+    } else if (s[0] == '}') {
+	csi_object_t *next;
+
+	if (_csi_unlikely
+	    (scan->build_procedure.type == CSI_OBJECT_TYPE_NULL))
+	{
+	    scan->status = _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	    return;
+	}
+
+	if (scan->procedure_stack.len) {
+	    next = _csi_stack_peek (&scan->procedure_stack, 0);
+	    scan->status = csi_array_append (ctx, next->datum.array,
+					     &scan->build_procedure);
+	    scan->build_procedure = *next;
+	    scan->procedure_stack.len--;
+	} else {
+	    scan->status = _csi_push_ostack (ctx, &scan->build_procedure);
+	    scan->build_procedure.type = CSI_OBJECT_TYPE_NULL;
+	}
+
+	reset (scan);
+	return;
+    }
+
+    if (s[0] == '/') {
+	if (len >= 2 && s[1] == '/') { /* substituted name */
+	    scan->status = csi_name_new (ctx, &obj, s + 2, len - 2);
+	    if (_csi_unlikely (scan->status))
+		return;
+
+	    scan->status = _csi_name_lookup (ctx, obj.datum.name, &obj);
+	} else { /* literal name */
+	    scan->status = csi_name_new (ctx, &obj, s + 1, len - 1);
+	}
+    } else {
+	if (! parse_number (&obj, s, len)) {
+	    scan->status = csi_name_new (ctx, &obj, s, len);
+	    obj.type |= CSI_OBJECT_ATTR_EXECUTABLE;
+	}
+    }
+    if (_csi_unlikely (scan->status))
+	return;
+
+    /* consume whitespace after token, before calling the interpreter */
+    reset (scan);
+
+    if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL) {
+	scan->status = csi_array_append (ctx,
+					 scan->build_procedure.datum.array,
+					 &obj);
+    } else if (obj.type & CSI_OBJECT_ATTR_EXECUTABLE) {
+	scan->status = csi_object_execute (ctx, csi_object_reference (&obj));
+	csi_object_free (ctx, &obj);
+    } else
+	scan->status = _csi_push_ostack (ctx, &obj);
+}
+
+static void
+comment_start (csi_scanner_t *scan)
+{
+    /* XXX check for '!' interpreter mode?, '%' dsc setup? */
+    scan->state = COMMENT;
+}
+
+static void
+comment_end (csi_scanner_t *scan)
+{
+    reset (scan);
+}
+
+static void
+string_start (csi_scanner_t *scan)
+{
+    scan->state = STRING;
+    scan->string_p = 1;
+    _csi_buffer_reset (&scan->buffer);
+}
+
+static void
+string_inc_p (csi_scanner_t *scan)
+{
+    scan->string_p++;
+}
+
+static int
+string_dec_p (csi_scanner_t *scan)
+{
+    return --scan->string_p == 0;
+}
+
+static void
+string_add (csi_t *ctx, csi_scanner_t *scan, int c)
+{
+    if (_csi_likely (_csi_buffer_check (ctx, &scan->buffer, 1)))
+	_csi_buffer_add (&scan->buffer, c);
+}
+
+static void
+string_end (csi_t *ctx, csi_scanner_t *scan)
+{
+    csi_object_t obj;
+
+    scan->status = scan->buffer.status;
+    if (_csi_unlikely (scan->status))
+	return;
+
+    scan->status = csi_string_new (ctx,
+				   &obj,
+				   scan->buffer.base,
+				   scan->buffer.ptr - scan->buffer.base);
+    if (_csi_unlikely (scan->status))
+	return;
+
+    if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL)
+	scan->status = csi_array_append (ctx,
+					 scan->build_procedure.datum.array,
+					 &obj);
+    else
+	scan->status = _csi_push_ostack (ctx, &obj);
+
+    reset (scan);
+}
+
+static void
+hex_start (csi_scanner_t *scan)
+{
+    scan->state = HEX;
+    scan->accumulator_count = 0;
+    scan->accumulator = 0;
+
+    _csi_buffer_reset (&scan->buffer);
+}
+
+static int
+hex_value (int c)
+{
+    if (c < '0')
+	return EOF;
+    if (c <= '9')
+	return c - '0';
+    c |= 32;
+    if (c < 'a')
+	return EOF;
+    if (c <= 'f')
+	return c - 'a' + 0xa;
+    return EOF;
+}
+
+static void
+hex_add (csi_t *ctx, csi_scanner_t *scan, int c)
+{
+    if (scan->accumulator_count == 0) {
+	scan->accumulator |= hex_value (c) << 4;
+	scan->accumulator_count = 1;
+    } else {
+	scan->accumulator |= hex_value (c) << 0;
+	if (_csi_likely (_csi_buffer_check (ctx, &scan->buffer, 1)))
+	    _csi_buffer_add (&scan->buffer, scan->accumulator);
+	scan->accumulator = 0;
+	scan->accumulator_count = 0;
+    }
+}
+
+static void
+hex_end (csi_t *ctx, csi_scanner_t *scan)
+{
+    csi_object_t obj;
+
+    if (scan->accumulator_count)
+	hex_add (ctx, scan, '0');
+
+    scan->status = scan->buffer.status;
+    if (_csi_unlikely (scan->status))
+	return;
+
+    scan->status = csi_string_new (ctx,
+				   &obj,
+				   scan->buffer.base,
+				   scan->buffer.ptr - scan->buffer.base);
+    if (_csi_unlikely (scan->status))
+	return;
+
+    if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL)
+	scan->status = csi_array_append (ctx,
+					 scan->build_procedure.datum.array,
+					 &obj);
+    else
+	scan->status = _csi_push_ostack (ctx, &obj);
+
+    reset (scan);
+}
+
+static void
+base85_start (csi_scanner_t *scan)
+{
+    scan->state = BASE85;
+    scan->accumulator = 0;
+    scan->accumulator_count = 0;
+
+    _csi_buffer_reset (&scan->buffer);
+}
+
+static void
+base85_add (csi_t *ctx, csi_scanner_t *scan, int c)
+{
+    if (c == 'z') {
+	if (_csi_unlikely (scan->accumulator_count != 0)) {
+	    scan->status = _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	    return;
+	}
+	if (_csi_likely (_csi_buffer_check (ctx, &scan->buffer, 4))) {
+	    _csi_buffer_add (&scan->buffer, 0);
+	    _csi_buffer_add (&scan->buffer, 0);
+	    _csi_buffer_add (&scan->buffer, 0);
+	    _csi_buffer_add (&scan->buffer, 0);
+	}
+    } else if (_csi_unlikely (c < '!' || c > 'u')) {
+	scan->status = _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	return;
+    } else {
+	scan->accumulator = scan->accumulator*85 + c - '!';
+	if (++scan->accumulator_count == 5) {
+	    if (_csi_likely (_csi_buffer_check (ctx, &scan->buffer, 4))) {
+		_csi_buffer_add (&scan->buffer,
+				 (scan->accumulator >> 24) & 0xff);
+		_csi_buffer_add (&scan->buffer,
+				 (scan->accumulator >> 16) & 0xff);
+		_csi_buffer_add (&scan->buffer,
+				 (scan->accumulator >>  8) & 0xff);
+		_csi_buffer_add (&scan->buffer,
+				 (scan->accumulator >>  0) & 0xff);
+	    }
+
+	    scan->accumulator = 0;
+	    scan->accumulator_count = 0;
+	}
+    }
+}
+
+static void
+base85_end (csi_t *ctx, csi_scanner_t *scan)
+{
+    csi_object_t obj;
+
+    if (_csi_unlikely (! _csi_buffer_check (ctx, &scan->buffer, 4))) {
+	scan->status = scan->buffer.status;
+	return;
+    }
+
+    switch (scan->accumulator_count) {
+    case 0:
+	break;
+    case 1:
+	scan->status = _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	break;
+
+    case 2:
+	scan->accumulator = scan->accumulator * (85*85*85) + 85*85*85 -1;
+	_csi_buffer_add (&scan->buffer, (scan->accumulator >> 24) & 0xff);
+	break;
+    case 3:
+	scan->accumulator = scan->accumulator * (85*85) + 85*85 -1;
+	_csi_buffer_add (&scan->buffer, (scan->accumulator >> 24) & 0xff);
+	_csi_buffer_add (&scan->buffer, (scan->accumulator >> 16) & 0xff);
+	break;
+    case 4:
+	scan->accumulator = scan->accumulator * 85 + 84;
+	_csi_buffer_add (&scan->buffer, (scan->accumulator >> 24) & 0xff);
+	_csi_buffer_add (&scan->buffer, (scan->accumulator >> 16) & 0xff);
+	_csi_buffer_add (&scan->buffer, (scan->accumulator >>  8) & 0xff);
+	break;
+    }
+
+    scan->status = csi_string_new (ctx,
+				   &obj,
+				   scan->buffer.base,
+				   scan->buffer.ptr - scan->buffer.base);
+    if (_csi_unlikely (scan->status))
+	return;
+
+    if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL)
+	scan->status = csi_array_append (ctx,
+					 scan->build_procedure.datum.array,
+					 &obj);
+    else
+	scan->status = _csi_push_ostack (ctx, &obj);
+
+    reset (scan);
+}
+
+static int
+scan_none (csi_t *ctx,
+	   csi_scanner_t *scan,
+	   csi_file_t *src)
+{
+    int c, next;
+    union {
+	int i;
+	float f;
+    } u;
+
+    while ((c = scan_getc (scan, src)) != EOF) {
+	csi_object_t obj = { CSI_OBJECT_TYPE_NULL };
+
+	switch (c) {
+	case 0x0:
+	case 0x9:
+	case 0xa:
+	case 0xc:
+	case 0xd:
+	case 0x20: /* ignore whitespace */
+	    break;
+
+	case '%':
+	    comment_start (scan);
+	    return 1;
+
+	case '(':
+	    string_start (scan);
+	    return 1;
+
+	case '[': /* needs special case */
+	case ']':
+	case '{':
+	case '}':
+	    token_start (scan);
+	    token_add_unchecked (scan, c);
+	    token_end (ctx, scan, src);
+	    return 1;
+
+	case '<':
+	    next = scan_getc (scan, src);
+	    switch (next) {
+	    case EOF:
+		scan_putc (scan, src, '<');
+		return 0;
+	    case '<':
+		/* dictionary name */
+		token_start (scan);
+		token_add_unchecked (scan, '<');
+		token_add_unchecked (scan, '<');
+		token_end (ctx, scan, src);
+		return 1;
+	    case '~':
+		base85_start (scan);
+		return 1;
+	    default:
+		scan_putc (scan, src, next);
+		hex_start (scan);
+		return 1;
+	    }
+	    break;
+
+	    /* binary token */
+	case 128:
+	case 129:
+	case 130:
+	case 131:
+	    /* binary object sequence */
+	    break;
+	case 132: /* 32-bit integer, MSB */
+	    break;
+	case 133: /* 32-bit integer, LSB */
+	    break;
+	case 134: /* 16-bit integer, MSB */
+	    break;
+	case 135: /* 16-bit integer, LSB */
+	    break;
+	case 136: /* 8-bit integer */
+	    break;
+	case 137: /* 16/32-bit fixed point */
+	    break;
+	case 138: /* 32-bit real, MSB */
+	    scan_read (scan, src, &u.i, 4);
+#if ! WORDS_BIGENDIAN
+	    u.i = bswap_32 (u.i);
+#endif
+	    scan->status = csi_real_new (ctx, &obj, u.f);
+	    break;
+	case 139: /* 32-bit real, LSB */
+	    scan_read (scan, src, &u.f, 4);
+#if WORDS_BIGENDIAN
+	    u.i = bswap_32 (u.i);
+#endif
+	    scan->status = csi_real_new (ctx, &obj, u.f);
+	    break;
+	case 140: /* 32-bit real, native */
+	    scan_read (scan, src, &u.f, 4);
+	    scan->status = csi_real_new (ctx, &obj, u.f);
+	    break;
+	case 141: /* boolean */
+	    break;
+	case 142: /* string of length 1n */
+	    break;
+	case 143: /* string of length 2n (MSB) */
+	    break;
+	case 144: /* string of length 2n (LSB) */
+	    break;
+	case 145: /* literal system name */
+	    break;
+	case 146: /* executable system name */
+	    break;
+	case 147: /* reserved */
+	    break;
+	case 148: /* reserved */
+	    break;
+	case 149: /* homogeneous array */
+	    break;
+
+	    /* unassigned */
+	case 150:
+	case 151:
+	case 152:
+	case 153:
+	case 154:
+	case 155:
+	case 156:
+	case 157:
+	case 158:
+	case 159:
+	    scan->status = _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	    return 0;
+
+	case '#': /* PDF 1.2 escape code */
+	    {
+		int c_hi = scan_getc (scan, src);
+		int c_lo = scan_getc (scan, src);
+		c = (hex_value (c_hi) << 4) | hex_value (c_lo);
+	    }
+	    /* fall-through */
+	default:
+	    token_start (scan);
+	    token_add_unchecked (scan, c);
+	    return 1;
+	}
+
+	if (obj.type != CSI_OBJECT_TYPE_NULL) {
+	    if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL)
+		scan->status = csi_array_append (ctx,
+						 scan->build_procedure.datum.array,
+						 &obj);
+	    else
+		scan->status = csi_object_execute (ctx, &obj);
+	}
+    }
+
+    return 0;
+}
+
+static int
+scan_token (csi_t *ctx, csi_scanner_t *scan, csi_file_t *src)
+{
+    int c;
+
+    while ((c = scan_getc (scan, src)) != EOF) {
+	switch (c) {
+	case 0x0:
+	case 0x9:
+	case 0xa:
+	case 0xc:
+	case 0xd:
+	case 0x20:
+	    token_end (ctx, scan, src);
+	    return 1;
+
+	    /* syntax delimiters */
+	case '%':
+	    token_end (ctx, scan, src);
+	    comment_start (scan);
+	    return 1;
+	    /* syntax error? */
+	case '(':
+	    token_end (ctx, scan, src);
+	    string_start (scan);
+	    return 1;
+	    /* XXX syntax error? */
+	case ')':
+	    token_end (ctx, scan, src);
+	    return 1;
+	case '/':
+	    /* need to special case '^//?' */
+	    if (scan->buffer.ptr > scan->buffer.base+1 ||
+		scan->buffer.base[0] != '/')
+	    {
+		token_end (ctx, scan, src);
+		token_start (scan);
+	    }
+	    token_add_unchecked (scan, '/');
+	    return 1;
+
+	case '{':
+	case '}':
+	case ']':
+	    token_end (ctx, scan, src);
+	    token_start (scan);
+	    token_add_unchecked (scan, c);
+	    token_end (ctx, scan, src);
+	    return 1;
+
+	case '<':
+	    scan_putc (scan, src, '<');
+	    token_end (ctx, scan, src);
+	    return 1;
+
+	case '#': /* PDF 1.2 escape code */
+	    {
+		int c_hi = scan_getc (scan, src);
+		int c_lo = scan_getc (scan, src);
+		c = (hex_value (c_hi) << 4) | hex_value (c_lo);
+	    }
+	    /* fall-through */
+	default:
+	    token_add (ctx, scan, c);
+	    break;
+	}
+    }
+    token_end (ctx, scan, src);
+
+    return 0;
+}
+
+static int
+scan_hex (csi_t *ctx, csi_scanner_t *scan, csi_file_t *src)
+{
+    int c;
+
+    while ((c = scan_getc (scan, src)) != EOF) {
+	switch (c) {
+	case 0x0:
+	case 0x9:
+	case 0xa:
+	case 0xc:
+	case 0xd:
+	case 0x20: /* ignore whitespace */
+	    break;
+
+	case '>':
+	    hex_end (ctx, scan); /* fixup odd digit with '0' */
+	    return 1;
+
+	case '0':
+	case '1':
+	case '2':
+	case '3':
+	case '4':
+	case '5':
+	case '6':
+	case '7':
+	case '8':
+	case '9':
+	case 'a':
+	case 'b':
+	case 'c':
+	case 'd':
+	case 'e':
+	case 'f':
+	case 'A':
+	case 'B':
+	case 'C':
+	case 'D':
+	case 'E':
+	case 'F':
+	    hex_add (ctx, scan, c);
+	    break;
+
+	default:
+	    scan->status = _csi_error (CSI_STATUS_INVALID_SCRIPT);
+	    return 0;
+	}
+    }
+
+    scan->status = _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    return 0;
+}
+
+static int
+scan_base85 (csi_t *ctx, csi_scanner_t *scan, csi_file_t *src)
+{
+    int c, next;
+
+    while ((c = scan_getc (scan, src)) != EOF) {
+	switch (c) {
+	case '~':
+	    next = scan_getc (scan, src);
+	    switch (next) {
+	    case EOF:
+		return 0;
+
+	    case '>':
+		base85_end (ctx, scan);
+		return 1;
+	    }
+	    scan_putc (scan, src, next);
+
+	    /* fall-through */
+	default:
+	    base85_add (ctx, scan, c);
+	    break;
+	}
+    }
+
+    scan->status = _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    return 0;
+}
+
+static int
+scan_string (csi_t *ctx, csi_scanner_t *scan, csi_file_t *src)
+{
+    int c, next;
+
+    while ((c = scan_getc (scan, src)) != EOF) {
+	switch (c) {
+	case '\\': /* escape */
+	    next = scan_getc (scan, src);
+	    switch (next) {
+	    case EOF:
+		return 0;
+
+	    case 'n':
+		string_add (ctx, scan, '\n');
+		break;
+	    case 'r':
+		string_add (ctx, scan, '\r');
+		break;
+	    case 't':
+		string_add (ctx, scan, '\r');
+		break;
+	    case 'b':
+		string_add (ctx, scan, '\b');
+		break;
+	    case 'f':
+		string_add (ctx, scan, '\f');
+		break;
+	    case '\\':
+		string_add (ctx, scan, '\\');
+		break;
+	    case '(':
+		string_add (ctx, scan, '(');
+		break;
+	    case ')':
+		string_add (ctx, scan, ')');
+		break;
+
+	    case '0': case '1': case '2': case '3':
+	    case '4': case '5': case '6': case '7':
+		{ /* octal code: \d{1,3} */
+		    int i;
+
+		    c = next - '0';
+
+		    for (i = 0; i < 2; i++) {
+			next = scan_getc (scan, src);
+			switch (next) {
+			case EOF:
+			    return 0;
+
+			case '0': case '1': case '2': case '3':
+			case '4': case '5': case '6': case '7':
+			    c = 8*c + next-'0';
+			    break;
+
+			default:
+			    scan_putc (scan, src, next);
+			    goto octal_code_done;
+			}
+		    }
+  octal_code_done:
+		    string_add (ctx, scan, c);
+		}
+		break;
+
+	    case 0xa:
+		/* skip the newline */
+		next = scan_getc (scan, src); /* might be compound LFCR */
+		switch (next) {
+		case EOF:
+		    return 0;
+		case 0xc:
+		    break;
+		default:
+		    scan_putc (scan, src, next);
+		    break;
+		}
+		break;
+	    case 0xc:
+		break;
+
+	    default:
+		/* ignore the '\' */
+		break;
+	    }
+	    break;
+
+	case '(':
+	    string_inc_p (scan);
+	    string_add (ctx, scan, c);
+	    break;
+
+	case ')':
+	    if (string_dec_p (scan)) {
+		string_end (ctx, scan);
+		return 1;
+	    } else
+		string_add (ctx, scan, c);
+	    break;
+
+	default:
+	    string_add (ctx, scan, c);
+	    break;
+	}
+    }
+
+    scan->status = _csi_error (CSI_STATUS_INVALID_SCRIPT);
+    return 0;
+}
+
+static int
+scan_comment (csi_t *ctx, csi_scanner_t *scan, csi_file_t *src)
+{
+    int c;
+
+    /* discard until newline */
+    while ((c = scan_getc (scan, src)) != EOF) {
+	switch (c) {
+	case 0xa:
+	case 0xc:
+	    comment_end (scan);
+	    return 1;
+	}
+    }
+
+    return 0;
+}
+
+csi_status_t
+_csi_scan_file (csi_t *ctx, csi_scanner_t *scan, csi_file_t *src)
+{
+    static int (* const func[]) (csi_t *, csi_scanner_t *, csi_file_t *) = {
+	scan_none,
+	scan_token,
+	scan_comment,
+	scan_string,
+	scan_hex,
+	scan_base85,
+    };
+
+    while (func[scan->state] (ctx, scan, src))
+	;
+
+    return scan->status;
+}
+
+#if 0
+cairo_status_t
+_csi_tokenize_string (csi_t *ctx,
+		      const char *code, int len,
+		      csi_object_t **array_out)
+{
+    csi_scanner_t scan;
+    csi_object_t *src;
+    cairo_status_t status;
+
+    status = _csi_scanner_init (&scan, ctx);
+    if (status)
+	return status;
+
+    scan.build_procedure = csi_array_new (ctx, 0);
+    if (scan.build_procedure == NULL)
+	goto CLEANUP_SCAN;
+    csi_object_set_literal (scan.build_procedure, FALSE);
+
+    src = csi_file_new_for_string (ctx, (const uint8_t *) code, len);
+    if (src == NULL) {
+	status = _csi_error (CSI_STATUS_NO_MEMORY);
+	goto CLEANUP_SCAN;
+    }
+
+    status = _csi_scan_object (&scan, src);
+    if (status)
+	goto CLEANUP_SRC;
+
+    *array_out = scan.build_procedure;
+    scan.build_procedure = NULL;
+
+CLEANUP_SRC:
+    csi_object_free (src);
+
+CLEANUP_SCAN:
+    _csi_scanner_fini (&scan);
+
+    return status;
+}
+#endif
+
+csi_status_t
+_csi_scanner_init (csi_t *ctx, csi_scanner_t *scanner)
+{
+    csi_status_t status;
+
+    memset (scanner, 0, sizeof (csi_scanner_t));
+
+    status = _csi_buffer_init (ctx, &scanner->buffer);
+    if (status)
+	return status;
+
+    status = _csi_stack_init (ctx, &scanner->procedure_stack, 4);
+    if (status)
+	return status;
+
+    reset (scanner);
+
+    return CSI_STATUS_SUCCESS;
+}
+
+void
+_csi_scanner_fini (csi_t *ctx, csi_scanner_t *scanner)
+{
+    _csi_buffer_fini (ctx, &scanner->buffer);
+    _csi_stack_fini (ctx, &scanner->procedure_stack);
+    if (scanner->build_procedure.type != CSI_OBJECT_TYPE_NULL)
+	csi_object_free (ctx, &scanner->build_procedure);
+}
diff --git a/util/cairo-script/cairo-script-stack.c b/util/cairo-script/cairo-script-stack.c
new file mode 100644
index 0000000..bc1d889
--- /dev/null
+++ b/util/cairo-script/cairo-script-stack.c
@@ -0,0 +1,196 @@
+/*
+ * Copyright © 2008 Chris Wilson <chris at chris-wilson.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Chris Wilson.
+ *
+ * Contributor(s):
+ *	Chris Wilson <chris at chris-wilson.co.uk>
+ */
+
+#include "cairo-script-private.h"
+
+#include <string.h>
+
+csi_status_t
+_csi_stack_init (csi_t *ctx, csi_stack_t *stack, csi_integer_t size)
+{
+    csi_status_t status = CSI_STATUS_SUCCESS;
+
+    stack->len = 0;
+    stack->size = size;
+    /* assert ((unsigned) size < INT32_MAX / sizeof (csi_object_t)); */
+    stack->objects = _csi_alloc (ctx, stack->size * sizeof (csi_object_t));
+    if (_csi_unlikely (stack->objects == NULL))
+	status = _csi_error (CSI_STATUS_NO_MEMORY);
+
+    return status;
+}
+
+void
+_csi_stack_fini (csi_t *ctx, csi_stack_t *stack)
+{
+    csi_integer_t n;
+
+    for (n = 0; n < stack->len; n++)
+	csi_object_free (ctx, &stack->objects[n]);
+
+    _csi_free (ctx, stack->objects);
+}
+
+csi_status_t
+_csi_stack_roll (csi_t *ctx,
+		 csi_stack_t *stack,
+		 csi_integer_t mod, csi_integer_t n)
+{
+    csi_object_t stack_copy[128];
+    csi_object_t *copy;
+    csi_integer_t last, i, len;
+
+    switch (mod) { /* special cases */
+    case 1:
+	last = stack->len - 1;
+	stack_copy[0] = stack->objects[last];
+	for (i = last; --n; i--)
+	    stack->objects[i] = stack->objects[i-1];
+	stack->objects[i] = stack_copy[0];
+	return CSI_STATUS_SUCCESS;
+    case -1:
+	last = stack->len - 1;
+	stack_copy[0] = stack->objects[i = last - n + 1];
+	for (; --n; i++)
+	    stack->objects[i] = stack->objects[i+1];
+	stack->objects[i] = stack_copy[0];
+	return CSI_STATUS_SUCCESS;
+    }
+
+    /* fall back to a copy */
+    if (n > ARRAY_LENGTH (stack_copy)) {
+	if (_csi_unlikely ((unsigned) n > INT32_MAX / sizeof (csi_object_t)))
+	    return _csi_error (CSI_STATUS_NO_MEMORY);
+	copy = _csi_alloc (ctx, n * sizeof (csi_object_t));
+	if (copy == NULL)
+	    return _csi_error (CSI_STATUS_NO_MEMORY);
+    } else
+	copy = stack_copy;
+
+    i = stack->len - n;
+    memcpy (copy, stack->objects + i, n * sizeof (csi_object_t));
+    mod = -mod;
+    if (mod < 0)
+	mod += n;
+    last = mod;
+    for (len = n; n--; i++) {
+	stack->objects[i] = copy[last];
+	if (++last == len)
+	    last = 0;
+    }
+
+    if (copy != stack_copy)
+	_csi_free (ctx, copy);
+
+    return CSI_STATUS_SUCCESS;
+}
+
+csi_status_t
+_csi_stack_grow (csi_t *ctx, csi_stack_t *stack, csi_integer_t cnt)
+{
+    csi_integer_t newsize;
+    csi_object_t *newstack;
+
+    if (_csi_likely (cnt <= stack->size))
+	return CSI_STATUS_SUCCESS;
+    if (_csi_unlikely ((unsigned) cnt >= INT32_MAX / sizeof (csi_object_t)))
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+
+    newsize = stack->size;
+    do {
+	newsize *= 2;
+    } while (newsize <= cnt);
+
+    newstack = _csi_realloc (ctx,
+			     stack->objects,
+			     newsize * sizeof (csi_object_t));
+    if (_csi_unlikely (newstack == NULL))
+	return _csi_error (CSI_STATUS_NO_MEMORY);
+
+    stack->objects = newstack;
+    stack->size  = newsize;
+
+    return CSI_STATUS_SUCCESS;
+}
+
+csi_status_t
+_csi_stack_push (csi_t *ctx, csi_stack_t *stack, const csi_object_t *obj)
+{
+    if (_csi_unlikely (stack->len == stack->size)) {
+	csi_status_t status;
+
+	status = _csi_stack_grow (ctx, stack, stack->size + 1);
+	if (status)
+	    return status;
+    }
+
+    stack->objects[stack->len++] = *obj;
+    return CSI_STATUS_SUCCESS;
+}
+
+csi_object_t *
+_csi_stack_peek (csi_stack_t *stack, csi_integer_t i)
+{
+    if (_csi_unlikely (stack->len < i))
+	return NULL;
+
+    return &stack->objects[stack->len - i -1];
+}
+
+void
+_csi_stack_pop (csi_t *ctx, csi_stack_t *stack, csi_integer_t count)
+{
+    if (_csi_unlikely (stack->len < count))
+	count = stack->len;
+
+    while (count--)
+	csi_object_free (ctx, &stack->objects[--stack->len]);
+}
+
+csi_status_t
+_csi_stack_exch (csi_stack_t *stack)
+{
+    csi_object_t tmp;
+    csi_integer_t n;
+
+    if (_csi_unlikely (stack->len < 2))
+	return _csi_error (CSI_STATUS_INVALID_SCRIPT);
+
+    n = stack->len - 1;
+    tmp = stack->objects[n];
+    stack->objects[n] = stack->objects[n - 1];
+    stack->objects[n - 1] = tmp;
+
+    return CSI_STATUS_SUCCESS;
+}
commit a856371bef496da0e84226f4fd2fc3cb72e955ac
Author: Chris Wilson <chris at chris-wilson.co.uk>
Date:   Tue Nov 4 10:45:34 2008 +0000

    Add CairoScript backend.
    
    A new meta-surface backend for serialising drawing operations to a
    CairoScript file. The principal use (as currently envisaged) is to provide
    a round-trip testing mechanism for CairoScript - i.e. we can generate
    script files for every test in the suite and check that we can replay them
    with perfect fidelity. (Obviously this does not provide complete coverage
    of CairoScript's syntax, but should give reasonable coverage over the
    operators.)

diff --git a/boilerplate/Makefile.sources b/boilerplate/Makefile.sources
index 44eeb39..7be4467 100644
--- a/boilerplate/Makefile.sources
+++ b/boilerplate/Makefile.sources
@@ -36,6 +36,9 @@ cairo_boilerplate_ps_sources = cairo-boilerplate-ps.c
 cairo_boilerplate_quartz_private = cairo-boilerplate-quartz-private.h
 cairo_boilerplate_quartz_sources = cairo-boilerplate-quartz.c
 
+cairo_boilerplate_script_private = cairo-boilerplate-script-private.h
+cairo_boilerplate_script_sources = cairo-boilerplate-script.c
+
 cairo_boilerplate_sdl_private = cairo-boilerplate-sdl-private.h
 cairo_boilerplate_sdl_sources = cairo-boilerplate-sdl.c
 
diff --git a/boilerplate/cairo-boilerplate-script-private.h b/boilerplate/cairo-boilerplate-script-private.h
new file mode 100644
index 0000000..480e422
--- /dev/null
+++ b/boilerplate/cairo-boilerplate-script-private.h
@@ -0,0 +1,57 @@
+/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
+/*
+ * Copyright © 2008 Chris Wilson
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of
+ * Red Hat, Inc. not be used in advertising or publicity pertaining to
+ * distribution of the software without specific, written prior
+ * permission. Red Hat, Inc. makes no representations about the
+ * suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Author: Chris Wilson <chris at chris-wilson.co.uk>
+ */
+
+#ifndef _CAIRO_BOILERPLATE_SCRIPT_PRIVATE_H_
+#define _CAIRO_BOILERPLATE_SCRIPT_PRIVATE_H_
+
+cairo_surface_t *
+_cairo_boilerplate_script_create_surface (const char		 *name,
+				       cairo_content_t		  content,
+				       int			  width,
+				       int			  height,
+				       int			  max_width,
+				       int			  max_height,
+				       cairo_boilerplate_mode_t	  mode,
+				       int                        id,
+				       void			**closure);
+
+cairo_status_t
+_cairo_boilerplate_script_finish_surface (cairo_surface_t *surface);
+
+cairo_status_t
+_cairo_boilerplate_script_surface_write_to_png (cairo_surface_t *surface,
+						const char *filename);
+
+cairo_surface_t *
+_cairo_boilerplate_script_get_image_surface (cairo_surface_t *surface,
+					     int page,
+					     int width,
+					     int height);
+
+void
+_cairo_boilerplate_script_cleanup (void *closure);
+
+#endif
diff --git a/boilerplate/cairo-boilerplate-script.c b/boilerplate/cairo-boilerplate-script.c
new file mode 100644
index 0000000..ae08cbc
--- /dev/null
+++ b/boilerplate/cairo-boilerplate-script.c
@@ -0,0 +1,125 @@
+/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
+/*
+ * Copyright © Chris Wilson
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of
+ * Chris Wilson not be used in advertising or publicity pertaining to
+ * distribution of the software without specific, written prior
+ * permission. Chris Wilson makes no representations about the
+ * suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * CHRIS WILSON DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL CHRIS WILSON BE LIABLE FOR ANY SPECIAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Author: Chris Wilson <chris at chris-wilson.co.uk>
+ */
+
+#include "cairo-boilerplate.h"
+#include "cairo-boilerplate-script-private.h"
+
+#include "cairo-script.h"
+
+cairo_user_data_key_t script_closure_key;
+
+typedef struct _script_target_closure {
+    char		*filename;
+    int			 width;
+    int			 height;
+} script_target_closure_t;
+
+cairo_surface_t *
+_cairo_boilerplate_script_create_surface (const char		 *name,
+					  cairo_content_t	  content,
+					  int			  width,
+					  int			  height,
+					  int			  max_width,
+					  int			  max_height,
+					  cairo_boilerplate_mode_t	  mode,
+					  int                        id,
+					  void			**closure)
+{
+    script_target_closure_t *ptc;
+    cairo_surface_t *surface;
+    cairo_status_t status;
+
+    *closure = ptc = xmalloc (sizeof (script_target_closure_t));
+
+    ptc->width = width;
+    ptc->height = height;
+
+    xasprintf (&ptc->filename, "%s.out.cs", name);
+    xunlink (ptc->filename);
+
+    surface = cairo_script_surface_create (ptc->filename, width, height);
+
+    status = cairo_surface_set_user_data (surface,
+					  &script_closure_key, ptc, NULL);
+    if (status == CAIRO_STATUS_SUCCESS)
+	return surface;
+
+    cairo_surface_destroy (surface);
+    surface = cairo_boilerplate_surface_create_in_error (status);
+
+    free (ptc->filename);
+    free (ptc);
+    return surface;
+}
+
+cairo_status_t
+_cairo_boilerplate_script_finish_surface (cairo_surface_t		*surface)
+{
+    cairo_surface_finish (surface);
+    return cairo_surface_status (surface);
+}
+
+cairo_status_t
+_cairo_boilerplate_script_surface_write_to_png (cairo_surface_t *surface,
+						const char *filename)
+{
+    return CAIRO_STATUS_WRITE_ERROR;
+}
+
+static cairo_surface_t *
+_cairo_boilerplate_script_convert_to_image (cairo_surface_t *surface,
+					    int page)
+{
+    script_target_closure_t *ptc = cairo_surface_get_user_data (surface,
+								&script_closure_key);
+    return cairo_boilerplate_convert_to_image (ptc->filename, page);
+}
+
+cairo_surface_t *
+_cairo_boilerplate_script_get_image_surface (cairo_surface_t *surface,
+					     int page,
+					     int width,
+					     int height)
+{
+    cairo_surface_t *image;
+
+    image = _cairo_boilerplate_script_convert_to_image (surface, page);
+    cairo_surface_set_device_offset (image,
+				     cairo_image_surface_get_width (image) - width,
+				     cairo_image_surface_get_height (image) - height);
+    surface = _cairo_boilerplate_get_image_surface (image, 0, width, height);
+    cairo_surface_destroy (image);
+
+    return surface;
+}
+
+void
+_cairo_boilerplate_script_cleanup (void *closure)
+{
+    script_target_closure_t *ptc = closure;
+    free (ptc->filename);
+    free (ptc);
+}
diff --git a/boilerplate/cairo-boilerplate.c b/boilerplate/cairo-boilerplate.c
index b12b6d6..f6f68f9 100644
--- a/boilerplate/cairo-boilerplate.c
+++ b/boilerplate/cairo-boilerplate.c
@@ -47,6 +47,9 @@
 #if CAIRO_HAS_QUARTZ_SURFACE
 #include "cairo-boilerplate-quartz-private.h"
 #endif
+#if CAIRO_HAS_SCRIPT_SURFACE
+#include "cairo-boilerplate-script-private.h"
+#endif
 #if CAIRO_HAS_SDL_SURFACE
 #include "cairo-boilerplate-sdl-private.h"
 #endif
@@ -603,6 +606,19 @@ static cairo_boilerplate_target_t targets[] =
 	NULL, TRUE, TRUE
     },
 #endif
+#if CAIRO_HAS_SCRIPT_SURFACE
+    {
+	"script", "script", ".cs",
+	CAIRO_SURFACE_TYPE_SCRIPT, CAIRO_CONTENT_COLOR_ALPHA, 0,
+	_cairo_boilerplate_script_create_surface,
+	NULL,
+	_cairo_boilerplate_script_finish_surface,
+	_cairo_boilerplate_script_get_image_surface,
+	_cairo_boilerplate_script_surface_write_to_png,
+	_cairo_boilerplate_script_cleanup,
+	NULL, FALSE
+    },
+#endif
 #if CAIRO_HAS_SVG_SURFACE && CAIRO_CAN_TEST_SVG_SURFACE
     /* It seems we should be able to round-trip SVG content perfectly
      * through librsvg and cairo, but for some mysterious reason, some
diff --git a/build/configure.ac.features b/build/configure.ac.features
index 9fa7086..9970fde 100644
--- a/build/configure.ac.features
+++ b/build/configure.ac.features
@@ -367,6 +367,7 @@ AC_DEFUN([CAIRO_REPORT],
 	echo "  XCB:           $use_xcb"
 	echo "  Win32:         $use_win32"
 	echo "  OS2:           $use_os2"
+	echo "  CairoScript:   $use_script"
 	echo "  PostScript:    $use_ps"
 	echo "  PDF:           $use_pdf"
 	echo "  SVG:           $use_svg"
@@ -389,6 +390,7 @@ AC_DEFUN([CAIRO_REPORT],
 	echo "  test surfaces: $use_test_surfaces"
 	echo "  ps testing:    $test_ps"
 	echo "  pdf testing:   $test_pdf"
+	echo "  cs testing:    $test_script"
 	echo "  svg testing:   $test_svg"
 	if test x"$use_win32" = "xyes"; then
 		echo "  win32 printing testing:    $test_win32_printing"
diff --git a/configure.ac b/configure.ac
index b13f34f..a307493 100644
--- a/configure.ac
+++ b/configure.ac
@@ -236,6 +236,22 @@ CAIRO_ENABLE_SURFACE_BACKEND(directfb, directfb, no, [
 
 dnl ===========================================================================
 
+CAIRO_ENABLE_SURFACE_BACKEND(script, script, no, [
+  test_script="yes"
+  csi_CFLAGS=
+  csi_LIBS=-lcairo-script-interpreter
+  if test "x$test_script" = "xyes"; then
+    AC_DEFINE([CAIRO_CAN_TEST_SCRIPT_SURFACE], 1,
+	      [Define to 1 if the CairoScript backend can be tested])
+  else
+    AC_MSG_WARN([CairoScript backend will not be tested])
+  fi
+  AC_SUBST(csi_CFLAGS)
+  AC_SUBST(csi_LIBS)
+])
+
+dnl ===========================================================================
+
 # We use pkg-config to look for freetype2, but fall back to
 # freetype-config if it fails.  We prefer pkg-config, since we can
 # then just put freetype2 >= $FREETYPE_MIN_VERSION in
diff --git a/doc/public/tmpl/cairo-surface.sgml b/doc/public/tmpl/cairo-surface.sgml
index 3a6cd8d..93bfe08 100644
--- a/doc/public/tmpl/cairo-surface.sgml
+++ b/doc/public/tmpl/cairo-surface.sgml
@@ -194,6 +194,7 @@ cairo_<emphasis>backend</emphasis>_surface_create().
 @CAIRO_SURFACE_TYPE_WIN32_PRINTING: 
 @CAIRO_SURFACE_TYPE_QUARTZ_IMAGE: 
 @CAIRO_SURFACE_TYPE_SDL:
+ at CAIRO_SURFACE_TYPE_SCRIPT:
 
 <!-- ##### FUNCTION cairo_surface_get_type ##### -->
 <para>
diff --git a/src/Makefile.sources b/src/Makefile.sources
index 0b44bbf..58781dd 100644
--- a/src/Makefile.sources
+++ b/src/Makefile.sources
@@ -249,3 +249,6 @@ cairo_directfb_sources = cairo-directfb-surface.c
 
 cairo_sdl_headers = cairo-sdl.h
 cairo_sdl_sources = cairo-sdl-surface.c
+
+cairo_script_headers = cairo-script.h
+cairo_script_sources = cairo-script-surface.c
diff --git a/src/cairo-base85-stream.c b/src/cairo-base85-stream.c
index 8897403..4d5a465 100644
--- a/src/cairo-base85-stream.c
+++ b/src/cairo-base85-stream.c
@@ -124,6 +124,7 @@ _cairo_base85_stream_create (cairo_output_stream_t *output)
 
     _cairo_output_stream_init (&stream->base,
 			       _cairo_base85_stream_write,
+			       NULL,
 			       _cairo_base85_stream_close);
     stream->output = output;
     stream->pending = 0;
diff --git a/src/cairo-cache-private.h b/src/cairo-cache-private.h
index 6a9b8b8..5ac8cc8 100644
--- a/src/cairo-cache-private.h
+++ b/src/cairo-cache-private.h
@@ -118,7 +118,7 @@ _cairo_cache_insert (cairo_cache_t	 *cache,
 		     cairo_cache_entry_t *entry);
 
 cairo_private void
-_cairo_cache_foreach (cairo_cache_t 	      	 *cache,
+_cairo_cache_foreach (cairo_cache_t		 *cache,
 		      cairo_cache_callback_func_t cache_callback,
 		      void			 *closure);
 
diff --git a/src/cairo-cache.c b/src/cairo-cache.c
index 01e5713..f5caba4 100644
--- a/src/cairo-cache.c
+++ b/src/cairo-cache.c
@@ -44,7 +44,7 @@ _cairo_cache_remove (cairo_cache_t	 *cache,
 
 static void
 _cairo_cache_shrink_to_accommodate (cairo_cache_t *cache,
-				   unsigned long  additional);
+				    unsigned long  additional);
 
 static cairo_status_t
 _cairo_cache_init (cairo_cache_t		*cache,
@@ -67,24 +67,19 @@ _cairo_cache_init (cairo_cache_t		*cache,
 }
 
 static void
-_cairo_cache_fini (cairo_cache_t *cache)
+_cairo_cache_pluck (void *entry, void *closure)
 {
-    cairo_cache_entry_t *entry;
-
-    /* We have to manually remove all entries from the cache ourselves
-     * rather than relying on _cairo_hash_table_destroy() to do that
-     * since otherwise the cache->entry_destroy callback would not get
-     * called on each entry. */
-
-    while (1) {
-	entry = _cairo_hash_table_random_entry (cache->hash_table, NULL);
-	if (entry == NULL)
-	    break;
-	_cairo_cache_remove (cache, entry);
-    }
+    _cairo_cache_remove (closure, entry);
+}
 
+static void
+_cairo_cache_fini (cairo_cache_t *cache)
+{
+    _cairo_hash_table_foreach (cache->hash_table,
+			       _cairo_cache_pluck,
+			       cache);
+    assert (cache->size == 0);
     _cairo_hash_table_destroy (cache->hash_table);
-    cache->size = 0;
 }
 
 /**
@@ -354,8 +349,20 @@ unsigned long
 _cairo_hash_string (const char *c)
 {
     /* This is the djb2 hash. */
-    unsigned long hash = 5381;
+    unsigned long hash = _CAIRO_HASH_INIT_VALUE;
     while (c && *c)
 	hash = ((hash << 5) + hash) + *c++;
     return hash;
 }
+
+unsigned long
+_cairo_hash_bytes (unsigned long hash,
+		   const void *ptr,
+		   unsigned int length)
+{
+    const uint8_t *bytes = ptr;
+    /* This is the djb2 hash. */
+    while (length--)
+	hash = ((hash << 5) + hash) + *bytes++;
+    return hash;
+}
diff --git a/src/cairo-deflate-stream.c b/src/cairo-deflate-stream.c
index bf2784a..3bb884c 100644
--- a/src/cairo-deflate-stream.c
+++ b/src/cairo-deflate-stream.c
@@ -128,6 +128,7 @@ _cairo_deflate_stream_create (cairo_output_stream_t *output)
 
     _cairo_output_stream_init (&stream->base,
 			       _cairo_deflate_stream_write,
+			       NULL,
 			       _cairo_deflate_stream_close);
     stream->output = output;
 
diff --git a/src/cairo-ft-font.c b/src/cairo-ft-font.c
index 7cae046..4245448 100644
--- a/src/cairo-ft-font.c
+++ b/src/cairo-ft-font.c
@@ -2739,6 +2739,18 @@ _cairo_ft_scaled_font_is_vertical (cairo_scaled_font_t *scaled_font)
     return FALSE;
 }
 
+unsigned int
+_cairo_ft_scaled_font_get_load_flags (cairo_scaled_font_t *scaled_font)
+{
+    cairo_ft_scaled_font_t *ft_scaled_font;
+
+    if (! _cairo_scaled_font_is_ft (scaled_font))
+	return 0;
+
+    ft_scaled_font = (cairo_ft_scaled_font_t *) scaled_font;
+    return ft_scaled_font->ft_options.load_flags;
+}
+
 void
 _cairo_ft_font_reset_static_data (void)
 {
diff --git a/src/cairo-ft-private.h b/src/cairo-ft-private.h
index 3e7d3de..00f7f77 100644
--- a/src/cairo-ft-private.h
+++ b/src/cairo-ft-private.h
@@ -64,6 +64,9 @@ _cairo_ft_unscaled_font_unlock_face (cairo_ft_unscaled_font_t *unscaled);
 cairo_private cairo_bool_t
 _cairo_ft_scaled_font_is_vertical (cairo_scaled_font_t *scaled_font);
 
+cairo_private unsigned int
+_cairo_ft_scaled_font_get_load_flags (cairo_scaled_font_t *scaled_font);
+
 CAIRO_END_DECLS
 
 #endif /* CAIRO_HAS_FT_FONT */
diff --git a/src/cairo-gstate.c b/src/cairo-gstate.c
index 1d532c7..33880d8 100644
--- a/src/cairo-gstate.c
+++ b/src/cairo-gstate.c
@@ -1056,7 +1056,8 @@ _cairo_gstate_fill (cairo_gstate_t *gstate, cairo_path_fixed_t *path)
 				  path,
 				  gstate->fill_rule,
 				  gstate->tolerance,
-				  gstate->antialias, NULL);
+				  gstate->antialias,
+				  NULL);
 
     if (pattern == &pattern_stack.base)
 	_cairo_pattern_fini (&pattern_stack.base);
diff --git a/src/cairo-output-stream-private.h b/src/cairo-output-stream-private.h
index 6f10483..2b3d584 100644
--- a/src/cairo-output-stream-private.h
+++ b/src/cairo-output-stream-private.h
@@ -43,14 +43,20 @@
 #include <stdio.h>
 #include <stdarg.h>
 
-typedef cairo_status_t (*cairo_output_stream_write_func_t) (cairo_output_stream_t *output_stream,
-							    const unsigned char   *data,
-							    unsigned int           length);
+typedef cairo_status_t
+(*cairo_output_stream_write_func_t) (cairo_output_stream_t *output_stream,
+				     const unsigned char   *data,
+				     unsigned int           length);
 
-typedef cairo_status_t (*cairo_output_stream_close_func_t) (cairo_output_stream_t *output_stream);
+typedef cairo_status_t
+(*cairo_output_stream_flush_func_t) (cairo_output_stream_t *output_stream);
+
+typedef cairo_status_t
+(*cairo_output_stream_close_func_t) (cairo_output_stream_t *output_stream);
 
 struct _cairo_output_stream {
     cairo_output_stream_write_func_t write_func;
+    cairo_output_stream_flush_func_t flush_func;
     cairo_output_stream_close_func_t close_func;
     unsigned long		     position;
     cairo_status_t		     status;
@@ -62,6 +68,7 @@ extern const cairo_private cairo_output_stream_t _cairo_output_stream_nil;
 cairo_private void
 _cairo_output_stream_init (cairo_output_stream_t            *stream,
 			   cairo_output_stream_write_func_t  write_func,
+			   cairo_output_stream_flush_func_t  flush_func,
 			   cairo_output_stream_close_func_t  close_func);
 
 cairo_private cairo_status_t
@@ -93,6 +100,10 @@ _cairo_output_stream_create (cairo_write_func_t		write_func,
 cairo_private cairo_output_stream_t *
 _cairo_output_stream_create_in_error (cairo_status_t status);
 
+/* Tries to flush any buffer maintained by the stream or its delegates. */
+cairo_private cairo_status_t
+_cairo_output_stream_flush (cairo_output_stream_t *stream);
+
 /* Returns the final status value associated with this object, just
  * before its last gasp. This final status value will capture any
  * status failure returned by the stream's close_func as well. */
diff --git a/src/cairo-output-stream.c b/src/cairo-output-stream.c
index c31c2da..9a58aac 100644
--- a/src/cairo-output-stream.c
+++ b/src/cairo-output-stream.c
@@ -70,9 +70,11 @@
 void
 _cairo_output_stream_init (cairo_output_stream_t            *stream,
 			   cairo_output_stream_write_func_t  write_func,
+			   cairo_output_stream_flush_func_t  flush_func,
 			   cairo_output_stream_close_func_t  close_func)
 {
     stream->write_func = write_func;
+    stream->flush_func = flush_func;
     stream->close_func = close_func;
     stream->position = 0;
     stream->status = CAIRO_STATUS_SUCCESS;
@@ -87,6 +89,7 @@ _cairo_output_stream_fini (cairo_output_stream_t *stream)
 
 const cairo_output_stream_t _cairo_output_stream_nil = {
     NULL, /* write_func */
+    NULL, /* flush_func */
     NULL, /* close_func */
     0,    /* position */
     CAIRO_STATUS_NO_MEMORY,
@@ -95,6 +98,7 @@ const cairo_output_stream_t _cairo_output_stream_nil = {
 
 static const cairo_output_stream_t _cairo_output_stream_nil_write_error = {
     NULL, /* write_func */
+    NULL, /* flush_func */
     NULL, /* close_func */
     0,    /* position */
     CAIRO_STATUS_WRITE_ERROR,
@@ -148,7 +152,8 @@ _cairo_output_stream_create (cairo_write_func_t		write_func,
 	return (cairo_output_stream_t *) &_cairo_output_stream_nil;
     }
 
-    _cairo_output_stream_init (&stream->base, closure_write, closure_close);
+    _cairo_output_stream_init (&stream->base,
+			       closure_write, NULL, closure_close);
     stream->write_func = write_func;
     stream->close_func = close_func;
     stream->closure = closure;
@@ -173,13 +178,37 @@ _cairo_output_stream_create_in_error (cairo_status_t status)
 	return (cairo_output_stream_t *) &_cairo_output_stream_nil;
     }
 
-    _cairo_output_stream_init (stream, NULL, NULL);
+    _cairo_output_stream_init (stream, NULL, NULL, NULL);
     stream->status = status;
 
     return stream;
 }
 
 cairo_status_t
+_cairo_output_stream_flush (cairo_output_stream_t *stream)
+{
+    cairo_status_t status;
+
+    if (stream->closed)
+	return stream->status;
+
+    if (stream == &_cairo_output_stream_nil ||
+	stream == &_cairo_output_stream_nil_write_error)
+    {
+	return stream->status;
+    }
+
+    if (stream->flush_func) {
+	status = stream->flush_func (stream);
+	/* Don't overwrite a pre-existing status failure. */
+	if (stream->status == CAIRO_STATUS_SUCCESS)
+	    stream->status = status;
+    }
+
+    return stream->status;
+}
+
+cairo_status_t
 _cairo_output_stream_close (cairo_output_stream_t *stream)
 {
     cairo_status_t status;
@@ -574,7 +603,8 @@ _cairo_output_stream_create_for_file (FILE *file)
 	return (cairo_output_stream_t *) &_cairo_output_stream_nil;
     }
 
-    _cairo_output_stream_init (&stream->base, stdio_write, stdio_flush);
+    _cairo_output_stream_init (&stream->base,
+			       stdio_write, stdio_flush, stdio_flush);
     stream->file = file;
 
     return &stream->base;
@@ -608,7 +638,8 @@ _cairo_output_stream_create_for_filename (const char *filename)
 	return (cairo_output_stream_t *) &_cairo_output_stream_nil;
     }
 
-    _cairo_output_stream_init (&stream->base, stdio_write, stdio_close);
+    _cairo_output_stream_init (&stream->base,
+			       stdio_write, stdio_flush, stdio_close);
     stream->file = file;
 
     return &stream->base;
@@ -650,7 +681,7 @@ _cairo_memory_stream_create (void)
 	return (cairo_output_stream_t *) &_cairo_output_stream_nil;
     }
 
-    _cairo_output_stream_init (&stream->base, memory_write, memory_close);
+    _cairo_output_stream_init (&stream->base, memory_write, NULL, memory_close);
     _cairo_array_init (&stream->array, 1);
 
     return &stream->base;
@@ -727,7 +758,7 @@ _cairo_null_stream_create (void)
 	return (cairo_output_stream_t *) &_cairo_output_stream_nil;
     }
 
-    _cairo_output_stream_init (stream, null_write, NULL);
+    _cairo_output_stream_init (stream, null_write, NULL, NULL);
 
     return stream;
 }
diff --git a/src/cairo-path-fixed-private.h b/src/cairo-path-fixed-private.h
index 4a5990d..43c33c1 100644
--- a/src/cairo-path-fixed-private.h
+++ b/src/cairo-path-fixed-private.h
@@ -37,6 +37,7 @@
 #define CAIRO_PATH_FIXED_PRIVATE_H
 
 #include "cairo-types-private.h"
+#include "cairo-compiler-private.h"
 
 enum cairo_path_op {
     CAIRO_PATH_OP_MOVE_TO = 0,
@@ -77,4 +78,14 @@ struct _cairo_path_fixed {
     cairo_path_buf_fixed_t  buf_head;
 };
 
+cairo_private unsigned long
+_cairo_path_fixed_hash (const cairo_path_fixed_t *path);
+
+cairo_private unsigned long
+_cairo_path_fixed_size (const cairo_path_fixed_t *path);
+
+cairo_private cairo_bool_t
+_cairo_path_fixed_equal (const cairo_path_fixed_t *a,
+			 const cairo_path_fixed_t *b);
+
 #endif /* CAIRO_PATH_FIXED_PRIVATE_H */
diff --git a/src/cairo-path-fixed.c b/src/cairo-path-fixed.c
index 9086119..027ebed 100644
--- a/src/cairo-path-fixed.c
+++ b/src/cairo-path-fixed.c
@@ -144,6 +144,170 @@ _cairo_path_fixed_init_copy (cairo_path_fixed_t *path,
     return CAIRO_STATUS_SUCCESS;
 }
 
+unsigned long
+_cairo_path_fixed_hash (const cairo_path_fixed_t *path)
+{
+    unsigned long hash = 0;
+    const cairo_path_buf_t *buf;
+    int num_points, num_ops;
+
+    hash = _cairo_hash_bytes (hash,
+			 &path->current_point,
+			 sizeof (path->current_point));
+    hash = _cairo_hash_bytes (hash,
+			 &path->last_move_point,
+			 sizeof (path->last_move_point));
+
+    num_ops = path->buf_head.base.num_ops;
+    num_points = path->buf_head.base.num_points;
+    for (buf = path->buf_head.base.next;
+	 buf != NULL;
+	 buf = buf->next)
+    {
+	hash = _cairo_hash_bytes (hash, buf->op,
+			     buf->num_ops * sizeof (buf->op[0]));
+	hash = _cairo_hash_bytes (hash, buf->points,
+			     buf->num_points * sizeof (buf->points[0]));
+
+	num_ops    += buf->num_ops;
+	num_points += buf->num_points;
+    }
+
+    hash = _cairo_hash_bytes (hash, &num_ops, sizeof (num_ops));
+    hash = _cairo_hash_bytes (hash, &num_points, sizeof (num_points));
+
+    return hash;
+}
+
+unsigned long
+_cairo_path_fixed_size (const cairo_path_fixed_t *path)
+{
+    const cairo_path_buf_t *buf;
+    int num_points, num_ops;
+
+    num_ops = path->buf_head.base.num_ops;
+    num_points = path->buf_head.base.num_points;
+    for (buf = path->buf_head.base.next;
+	 buf != NULL;
+	 buf = buf->next)
+    {
+	num_ops    += buf->num_ops;
+	num_points += buf->num_points;
+    }
+
+    return num_ops * sizeof (buf->op[0]) +
+	   num_points * sizeof (buf->points[0]);
+}
+
+cairo_bool_t
+_cairo_path_fixed_equal (const cairo_path_fixed_t *a,
+			 const cairo_path_fixed_t *b)
+{
+    const cairo_path_buf_t *buf_a, *buf_b;
+    const cairo_path_op_t *ops_a, *ops_b;
+    const cairo_point_t *points_a, *points_b;
+    int num_points_a, num_ops_a;
+    int num_points_b, num_ops_b;
+
+    if (a == b)
+	return TRUE;
+
+    if (a != NULL) {
+	num_ops_a = a->buf_head.base.num_ops;
+	num_points_a = a->buf_head.base.num_points;
+	for (buf_a = a->buf_head.base.next;
+	     buf_a != NULL;
+	     buf_a = buf_a->next)
+	{
+	    num_ops_a    += buf_a->num_ops;
+	    num_points_a += buf_a->num_points;
+	}
+    } else
+	num_ops_a = num_points_a = 0;
+
+    if (b != NULL) {
+	num_ops_b = b->buf_head.base.num_ops;
+	num_points_b = b->buf_head.base.num_points;
+	for (buf_b = b->buf_head.base.next;
+	     buf_b != NULL;
+	     buf_b = buf_b->next)
+	{
+	    num_ops_b    += buf_b->num_ops;
+	    num_points_b += buf_b->num_points;
+	}
+    } else
+	num_ops_b = num_points_b = 0;
+
+    if (num_ops_a == 0 && num_ops_b == 0)
+	return TRUE;
+
+    if (num_ops_a != num_ops_b || num_points_a != num_points_b)
+	return FALSE;
+
+    assert (a != NULL && b != NULL);
+
+    buf_a = &a->buf_head.base;
+    num_points_a = buf_a->num_points;
+    num_ops_a = buf_a->num_ops;
+    ops_a = buf_a->op;
+    points_a = buf_a->points;
+
+    buf_b = &b->buf_head.base;
+    num_points_b = buf_b->num_points;
+    num_ops_b = buf_b->num_ops;
+    ops_b = buf_b->op;
+    points_b = buf_b->points;
+
+    while (TRUE) {
+	int num_ops = MIN (num_ops_a, num_ops_b);
+	int num_points = MIN (num_points_a, num_points_b);
+
+	if (memcmp (ops_a, ops_b, num_ops * sizeof (cairo_path_op_t)))
+	    return FALSE;
+	if (memcmp (points_a, points_b, num_points * sizeof (cairo_point_t)))
+	    return FALSE;
+
+	num_ops_a -= num_ops;
+	ops_a += num_ops;
+	num_points_a -= num_points;
+	points_a += num_points;
+	if (num_ops_a == 0 || num_points_a == 0) {
+	    if (num_ops_a || num_points_a)
+		return FALSE;
+
+	    buf_a = buf_a->next;
+	    if (buf_a == NULL)
+		break;
+
+	    num_points_a = buf_a->num_points;
+	    num_ops_a = buf_a->num_ops;
+	    ops_a = buf_a->op;
+	    points_a = buf_a->points;
+	}
+
+	num_ops_b -= num_ops;
+	ops_b += num_ops;
+	num_points_b -= num_points;
+	points_b += num_points;
+	if (num_ops_b == 0 || num_points_b == 0) {
+	    if (num_ops_b || num_points_b)
+		return FALSE;
+
+	    buf_b = buf_b->next;
+	    if (buf_b == NULL)
+		break;
+
+	    num_points_b = buf_b->num_points;
+	    num_ops_b = buf_b->num_ops;
+	    ops_b = buf_b->op;
+	    points_b = buf_b->points;
+	}
+    }
+
+    return TRUE;
+}
+
+
 cairo_path_fixed_t *
 _cairo_path_fixed_create (void)
 {
diff --git a/src/cairo-pattern.c b/src/cairo-pattern.c
index 610ba61..c78317e 100644
--- a/src/cairo-pattern.c
+++ b/src/cairo-pattern.c
@@ -2293,6 +2293,261 @@ _cairo_pattern_get_extents (const cairo_pattern_t         *pattern,
     return CAIRO_STATUS_SUCCESS;
 }
 
+
+static unsigned long
+_cairo_solid_pattern_hash (unsigned long hash,
+			   const cairo_pattern_t *pattern)
+{
+    const cairo_solid_pattern_t *solid = (cairo_solid_pattern_t *) pattern;
+
+    hash = _cairo_hash_bytes (hash, &solid->content, sizeof (solid->content));
+    hash = _cairo_hash_bytes (hash, &solid->color, sizeof (solid->color));
+
+    return hash;
+}
+
+static unsigned long
+_cairo_gradient_color_stops_hash (unsigned long hash,
+				  const cairo_gradient_pattern_t *gradient)
+{
+    unsigned int n;
+
+    hash = _cairo_hash_bytes (hash,
+			      &gradient->n_stops,
+			      sizeof (gradient->n_stops));
+
+    for (n = 0; n < gradient->n_stops; n++) {
+	hash = _cairo_hash_bytes (hash,
+				  &gradient->stops[n].offset,
+				  sizeof (double));
+	hash = _cairo_hash_bytes (hash,
+				  &gradient->stops[n].color,
+				  sizeof (cairo_color_t));
+    }
+
+    return hash;
+}
+
+static unsigned long
+_cairo_linear_pattern_hash (unsigned long hash,
+			    const cairo_pattern_t *pattern)
+{
+    const cairo_linear_pattern_t *linear = (cairo_linear_pattern_t *) pattern;
+
+    hash = _cairo_hash_bytes (hash, &linear->p1, sizeof (linear->p1));
+    hash = _cairo_hash_bytes (hash, &linear->p2, sizeof (linear->p2));
+
+    return _cairo_gradient_color_stops_hash (hash, &linear->base);
+}
+
+static unsigned long
+_cairo_radial_pattern_hash (unsigned long hash, const cairo_pattern_t *pattern)
+{
+    const cairo_radial_pattern_t *radial = (cairo_radial_pattern_t *) pattern;
+
+    hash = _cairo_hash_bytes (hash, &radial->c1, sizeof (radial->c1));
+    hash = _cairo_hash_bytes (hash, &radial->r1, sizeof (radial->r1));
+    hash = _cairo_hash_bytes (hash, &radial->c2, sizeof (radial->c2));
+    hash = _cairo_hash_bytes (hash, &radial->r2, sizeof (radial->r2));
+
+    return _cairo_gradient_color_stops_hash (hash, &radial->base);
+}
+
+static unsigned long
+_cairo_surface_pattern_hash (unsigned long hash,
+			     const cairo_pattern_t *pattern)
+{
+    /* XXX requires cow-snapshots */
+    return hash;
+}
+
+unsigned long
+_cairo_pattern_hash (const cairo_pattern_t *pattern)
+{
+    unsigned long hash = _CAIRO_HASH_INIT_VALUE;
+
+    if (pattern->status)
+	return 0;
+
+    hash = _cairo_hash_bytes (hash, &pattern->type, sizeof (pattern->type));
+    hash = _cairo_hash_bytes (hash, &pattern->matrix, sizeof (pattern->matrix));
+    hash = _cairo_hash_bytes (hash, &pattern->filter, sizeof (pattern->filter));
+    hash = _cairo_hash_bytes (hash, &pattern->extend, sizeof (pattern->extend));
+
+    switch (pattern->type) {
+    case CAIRO_PATTERN_TYPE_SOLID:
+	return _cairo_solid_pattern_hash (hash, pattern);
+    case CAIRO_PATTERN_TYPE_LINEAR:
+	return _cairo_linear_pattern_hash (hash, pattern);
+    case CAIRO_PATTERN_TYPE_RADIAL:
+	return _cairo_radial_pattern_hash (hash, pattern);
+    case CAIRO_PATTERN_TYPE_SURFACE:
+	return _cairo_surface_pattern_hash (hash, pattern);
+    default:
+	ASSERT_NOT_REACHED;
+	return FALSE;
+    }
+}
+
+static unsigned long
+_cairo_gradient_pattern_color_stops_size (const cairo_pattern_t *pattern)
+{
+    cairo_gradient_pattern_t *gradient = (cairo_gradient_pattern_t *) pattern;
+
+    return gradient->n_stops * (sizeof (double) + sizeof (cairo_color_t));
+}
+
+unsigned long
+_cairo_pattern_size (const cairo_pattern_t *pattern)
+{
+    if (pattern->status)
+	return 0;
+
+    /* XXX */
+    switch (pattern->type) {
+    case CAIRO_PATTERN_TYPE_SOLID:
+	return sizeof (cairo_solid_pattern_t);
+	break;
+    case CAIRO_PATTERN_TYPE_SURFACE:
+	return sizeof (cairo_surface_pattern_t);
+	break;
+    case CAIRO_PATTERN_TYPE_LINEAR:
+	return sizeof (cairo_linear_pattern_t) +
+	    _cairo_gradient_pattern_color_stops_size (pattern);
+	break;
+    case CAIRO_PATTERN_TYPE_RADIAL:
+	return sizeof (cairo_radial_pattern_t) +
+	    _cairo_gradient_pattern_color_stops_size (pattern);
+    default:
+	ASSERT_NOT_REACHED;
+	return 0;
+    }
+}
+
+
+static cairo_bool_t
+_cairo_solid_pattern_equal (const cairo_pattern_t *A,
+			    const cairo_pattern_t *B)
+{
+    const cairo_solid_pattern_t *a = (cairo_solid_pattern_t *) A;
+    const cairo_solid_pattern_t *b = (cairo_solid_pattern_t *) B;
+
+    if (a->content != b->content)
+	return FALSE;
+
+    return _cairo_color_equal (&a->color, &b->color);
+}
+
+static cairo_bool_t
+_cairo_gradient_color_stops_equal (const cairo_gradient_pattern_t *a,
+				   const cairo_gradient_pattern_t *b)
+{
+    unsigned int n;
+
+    if (a->n_stops != b->n_stops)
+	return FALSE;
+
+    for (n = 0; n < a->n_stops; n++) {
+	if (a->stops[n].offset != b->stops[n].offset)
+	    return FALSE;
+	if (! _cairo_color_equal (&a->stops[n].color, &b->stops[n].color))
+	    return FALSE;
+    }
+
+    return TRUE;
+}
+
+static cairo_bool_t
+_cairo_linear_pattern_equal (const cairo_pattern_t *A,
+			     const cairo_pattern_t *B)
+{
+    const cairo_linear_pattern_t *a = (cairo_linear_pattern_t *) A;
+    const cairo_linear_pattern_t *b = (cairo_linear_pattern_t *) B;
+
+    if (a->p1.x != b->p1.x)
+	return FALSE;
+
+    if (a->p1.y != b->p1.y)
+	return FALSE;
+
+    if (a->p2.x != b->p2.x)
+	return FALSE;
+
+    if (a->p2.y != b->p2.y)
+	return FALSE;
+
+    return _cairo_gradient_color_stops_equal (&a->base, &b->base);
+}
+
+static cairo_bool_t
+_cairo_radial_pattern_equal (const cairo_pattern_t *A,
+			     const cairo_pattern_t *B)
+{
+    const cairo_radial_pattern_t *a = (cairo_radial_pattern_t *) A;
+    const cairo_radial_pattern_t *b = (cairo_radial_pattern_t *) B;
+
+    if (a->c1.x != b->c1.x)
+	return FALSE;
+
+    if (a->c1.y != b->c1.y)
+	return FALSE;
+
+    if (a->r1 != b->r1)
+	return FALSE;
+
+    if (a->c2.x != b->c2.x)
+	return FALSE;
+
+    if (a->c2.y != b->c2.y)
+	return FALSE;
+
+    if (a->r2 != b->r2)
+	return FALSE;
+
+    return _cairo_gradient_color_stops_equal (&a->base, &b->base);
+}
+
+static cairo_bool_t
+_cairo_surface_pattern_equal (const cairo_pattern_t *A,
+			      const cairo_pattern_t *B)
+{
+    /* XXX requires cow-snapshots */
+    return FALSE;
+}
+
+cairo_bool_t
+_cairo_pattern_equal (const cairo_pattern_t *a, const cairo_pattern_t *b)
+{
+    if (a->status || b->status)
+	return FALSE;
+
+    if (a->type != b->type)
+	return FALSE;
+
+    if (memcmp (&a->matrix, &b->matrix, sizeof (cairo_matrix_t)))
+	return FALSE;
+
+    if (a->filter != b->filter)
+	return FALSE;
+
+    if (a->extend != b->extend)
+	return FALSE;
+
+    switch (a->type) {
+    case CAIRO_PATTERN_TYPE_SOLID:
+	return _cairo_solid_pattern_equal (a, b);
+    case CAIRO_PATTERN_TYPE_LINEAR:
+	return _cairo_linear_pattern_equal (a, b);
+    case CAIRO_PATTERN_TYPE_RADIAL:
+	return _cairo_radial_pattern_equal (a, b);
+    case CAIRO_PATTERN_TYPE_SURFACE:
+	return _cairo_surface_pattern_equal (a, b);
+    default:
+	ASSERT_NOT_REACHED;
+	return FALSE;
+    }
+}
+
 /**
  * cairo_pattern_get_rgba
  * @pattern: a #cairo_pattern_t
diff --git a/src/cairo-pdf-operators.c b/src/cairo-pdf-operators.c
index 819318a..f335a37 100644
--- a/src/cairo-pdf-operators.c
+++ b/src/cairo-pdf-operators.c
@@ -300,6 +300,7 @@ _word_wrap_stream_create (cairo_output_stream_t *output, int max_column)
 
     _cairo_output_stream_init (&stream->base,
 			       _word_wrap_stream_write,
+			       NULL,
 			       _word_wrap_stream_close);
     stream->output = output;
     stream->max_column = max_column;
diff --git a/src/cairo-ps-surface.c b/src/cairo-ps-surface.c
index 9916651..f4431fa 100644
--- a/src/cairo-ps-surface.c
+++ b/src/cairo-ps-surface.c
@@ -1740,6 +1740,7 @@ _string_array_stream_create (cairo_output_stream_t *output)
 
     _cairo_output_stream_init (&stream->base,
 			       _string_array_stream_write,
+			       NULL,
 			       _string_array_stream_close);
     stream->output = output;
     stream->column = 0;
@@ -1766,6 +1767,7 @@ _base85_array_stream_create (cairo_output_stream_t *output)
 
     _cairo_output_stream_init (&stream->base,
 			       _string_array_stream_write,
+			       NULL,
 			       _string_array_stream_close);
     stream->output = output;
     stream->column = 0;
diff --git a/src/cairo-scaled-font-private.h b/src/cairo-scaled-font-private.h
index c1d87ae..86a50bb 100644
--- a/src/cairo-scaled-font-private.h
+++ b/src/cairo-scaled-font-private.h
@@ -94,10 +94,11 @@ struct _cairo_scaled_font {
     cairo_bool_t finished;
 
     /* "live" scaled_font members */
-    cairo_matrix_t scale;	  /* font space => device space */
-    cairo_matrix_t scale_inverse; /* device space => font space */
-    double max_scale;		  /* maximum x/y expansion of scale */
-    cairo_font_extents_t extents; /* user space */
+    cairo_matrix_t scale;	     /* font space => device space */
+    cairo_matrix_t scale_inverse;    /* device space => font space */
+    double max_scale;		     /* maximum x/y expansion of scale */
+    cairo_font_extents_t extents;    /* user space */
+    cairo_font_extents_t fs_extents; /* font space */
 
     /* The mutex protects modification to all subsequent fields. */
     cairo_mutex_t mutex;
diff --git a/src/cairo-scaled-font.c b/src/cairo-scaled-font.c
index aa1a2c8..8b74994 100644
--- a/src/cairo-scaled-font.c
+++ b/src/cairo-scaled-font.c
@@ -201,6 +201,7 @@ static const cairo_scaled_font_t _cairo_scaled_font_nil = {
     { 1., 0., 0., 1., 0, 0},	/* scale_inverse */
     1.,				/* max_scale */
     { 0., 0., 0., 0., 0. },	/* extents */
+    { 0., 0., 0., 0., 0. },	/* fs_extents */
     CAIRO_MUTEX_NIL_INITIALIZER,/* mutex */
     NULL,			/* glyphs */
     NULL,			/* surface_backend */
@@ -691,6 +692,8 @@ _cairo_scaled_font_set_metrics (cairo_scaled_font_t	    *scaled_font,
     cairo_status_t status;
     double  font_scale_x, font_scale_y;
 
+    scaled_font->fs_extents = *fs_metrics;
+
     status = _cairo_matrix_compute_basis_scale_factors (&scaled_font->font_matrix,
 						  &font_scale_x, &font_scale_y,
 						  1);
@@ -2210,6 +2213,8 @@ _cairo_scaled_glyph_set_metrics (cairo_scaled_glyph_t *scaled_glyph,
     double min_device_x = 0.0, max_device_x = 0.0, min_device_y = 0.0, max_device_y = 0.0;
     double device_x_advance, device_y_advance;
 
+    scaled_glyph->fs_metrics = *fs_metrics;
+
     for (hm = 0.0; hm <= 1.0; hm += 1.0)
 	for (wm = 0.0; wm <= 1.0; wm += 1.0) {
 	    double x, y;
diff --git a/src/cairo-script-surface.c b/src/cairo-script-surface.c
new file mode 100644
index 0000000..9118d66
--- /dev/null
+++ b/src/cairo-script-surface.c
@@ -0,0 +1,2598 @@
+/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */
+/* cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2008 Chris Wilson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Chris Wilson.
+ *
+ * Contributor(s):
+ *      Chris Wilson <chris at chris-wilson.co.uk>
+ */
+
+/* The script surface is one that records all operations performed on
+ * it in the form of a procedural script, similar in fashion to
+ * PostScript but using Cairo's imaging model. In essence, this is
+ * equivalent to the meta-surface, but as there is no impedance mismatch
+ * between Cairo and CairoScript, we can generate output immediately
+ * without having to copy and hold the data in memory.
+ */
+
+#include "cairoint.h"
+
+#include "cairo-script.h"
+
+#include "cairo-analysis-surface-private.h"
+#include "cairo-ft-private.h"
+#include "cairo-meta-surface-private.h"
+#include "cairo-output-stream-private.h"
+
+#define _cairo_output_stream_puts(S, STR) \
+    _cairo_output_stream_write ((S), (STR), strlen (STR))
+
+#define static cairo_warn static
+
+typedef struct _cairo_script_vmcontext cairo_script_vmcontext_t;
+typedef struct _cairo_script_surface cairo_script_surface_t;
+typedef struct _cairo_script_implicit_context cairo_script_implicit_context_t;
+typedef struct _cairo_script_surface_font_private cairo_script_surface_font_private_t;
+
+struct _cairo_script_vmcontext {
+    int ref;
+
+    cairo_output_stream_t *stream;
+    cairo_script_mode_t mode;
+
+    struct _bitmap {
+	unsigned long min;
+	unsigned long count;
+	unsigned int map[64];
+	struct _bitmap *next;
+    } surface_id, font_id;
+
+    cairo_script_surface_t *current_target;
+
+    cairo_script_surface_font_private_t *fonts;
+};
+
+struct _cairo_script_surface_font_private {
+    cairo_script_vmcontext_t *ctx;
+    cairo_bool_t has_sfnt;
+    unsigned long id;
+    unsigned long subset_glyph_index;
+    cairo_script_surface_font_private_t *prev, *next;
+    cairo_scaled_font_t *parent;
+};
+
+struct _cairo_script_implicit_context {
+    cairo_operator_t current_operator;
+    cairo_fill_rule_t current_fill_rule;
+    double current_tolerance;
+    cairo_antialias_t current_antialias;
+    cairo_stroke_style_t current_style;
+    cairo_pattern_t *current_source;
+    cairo_matrix_t current_ctm;
+    cairo_matrix_t current_font_matrix;
+    cairo_font_options_t current_font_options;
+    cairo_scaled_font_t *current_scaled_font;
+    cairo_path_fixed_t current_path;
+};
+
+struct _cairo_script_surface {
+    cairo_surface_t base;
+
+    cairo_script_vmcontext_t *ctx;
+
+    unsigned long id;
+
+    double width, height;
+
+    /* implicit flattened context */
+    cairo_script_implicit_context_t cr;
+};
+
+static const cairo_surface_backend_t _cairo_script_surface_backend;
+
+static cairo_script_surface_t *
+_cairo_script_surface_create_internal (cairo_script_vmcontext_t *ctx,
+				       double width,
+				       double height);
+
+static void
+_cairo_script_surface_scaled_font_fini (cairo_scaled_font_t *scaled_font);
+
+static void
+_cairo_script_implicit_context_init (cairo_script_implicit_context_t *cr);
+
+static void
+_bitmap_release_id (struct _bitmap *b, unsigned long token)
+{
+    struct _bitmap **prev = NULL;
+
+    do {
+	if (token < b->min + sizeof (b->map) * CHAR_BIT) {
+	    unsigned int bit, elem;
+
+	    token -= b->min;
+	    elem = token / (sizeof (b->map[0]) * CHAR_BIT);
+	    bit  = token % (sizeof (b->map[0]) * CHAR_BIT);
+	    b->map[elem] &= ~(1 << bit);
+	    if (! --b->count && prev) {
+		*prev = b->next;
+		free (b);
+	    }
+	    return;
+	}
+	prev = &b->next;
+	b = b->next;
+    } while (b != NULL);
+}
+
+static cairo_status_t
+_bitmap_next_id (struct _bitmap *b,
+		 unsigned long *id)
+{
+    struct _bitmap *bb, **prev = NULL;
+    unsigned long min = 0;
+
+    do {
+	if (b->min != min)
+	    break;
+
+	if (b->count < sizeof (b->map) * CHAR_BIT) {
+	    unsigned int n, m, bit;
+	    for (n = 0; n < ARRAY_LENGTH (b->map); n++) {
+		if (b->map[n] == (unsigned int) -1)
+		    continue;
+
+		for (m=0, bit=1; m<sizeof (b->map[0])*CHAR_BIT; m++, bit<<=1) {
+		    if ((b->map[n] & bit) == 0) {
+			b->map[n] |= bit;
+			b->count++;
+			*id = n * sizeof (b->map[0])*CHAR_BIT + m + b->min;
+			return CAIRO_STATUS_SUCCESS;
+		    }
+		}
+	    }
+	}
+	min += sizeof (b->map) * CHAR_BIT;
+
+	prev = &b->next;
+	b = b->next;
+    } while (b != NULL);
+
+    bb = malloc (sizeof (struct _bitmap));
+    if (bb == NULL)
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    *prev = bb;
+    bb->next = b;
+    bb->min = min;
+    bb->count = 1;
+    bb->map[0] = 0x1;
+    memset (bb->map + 1, 0, sizeof (bb->map) - sizeof (bb->map[0]));
+    *id = min;
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static const char *
+_direction_to_string (cairo_bool_t backward)
+{
+    static const char *names[] = {
+	"FORWARD",
+	"BACKWARD"
+    };
+    assert (backward < ARRAY_LENGTH (names));
+    return names[backward];
+}
+
+static const char *
+_operator_to_string (cairo_operator_t op)
+{
+    static const char *names[] = {
+	"CLEAR",	/* CAIRO_OPERATOR_CLEAR */
+
+	"SOURCE",	/* CAIRO_OPERATOR_SOURCE */
+	"OVER",		/* CAIRO_OPERATOR_OVER */
+	"IN",		/* CAIRO_OPERATOR_IN */
+	"OUT",		/* CAIRO_OPERATOR_OUT */
+	"ATOP",		/* CAIRO_OPERATOR_ATOP */
+
+	"DEST",		/* CAIRO_OPERATOR_DEST */
+	"DEST_OVER",	/* CAIRO_OPERATOR_DEST_OVER */
+	"DEST_IN",	/* CAIRO_OPERATOR_DEST_IN */
+	"DEST_OUT",	/* CAIRO_OPERATOR_DEST_OUT */
+	"DEST_ATOP",	/* CAIRO_OPERATOR_DEST_ATOP */
+
+	"XOR",		/* CAIRO_OPERATOR_XOR */
+	"ADD",		/* CAIRO_OPERATOR_ADD */
+	"SATURATE"	/* CAIRO_OPERATOR_SATURATE */
+    };
+    assert (op < ARRAY_LENGTH (names));
+    return names[op];
+}
+
+static const char *
+_extend_to_string (cairo_extend_t extend)
+{
+    static const char *names[] = {
+	"EXTEND_NONE",		/* CAIRO_EXTEND_NONE */
+	"EXTEND_REPEAT",	/* CAIRO_EXTEND_REPEAT */
+	"EXTEND_REFLECT",	/* CAIRO_EXTEND_REFLECT */
+	"EXTEND_PAD"		/* CAIRO_EXTEND_PAD */
+    };
+    assert (extend < ARRAY_LENGTH (names));
+    return names[extend];
+}
+
+static const char *
+_filter_to_string (cairo_filter_t filter)
+{
+    static const char *names[] = {
+	"FILTER_FAST",		/* CAIRO_FILTER_FAST */
+	"FILTER_GOOD",		/* CAIRO_FILTER_GOOD */
+	"FILTER_BEST",		/* CAIRO_FILTER_BEST */
+	"FILTER_NEAREST",	/* CAIRO_FILTER_NEAREST */
+	"FILTER_BILINEAR",	/* CAIRO_FILTER_BILINEAR */
+	"FILTER_GAUSSIAN",	/* CAIRO_FILTER_GAUSSIAN */
+    };
+    assert (filter < ARRAY_LENGTH (names));
+    return names[filter];
+}
+
+static const char *
+_fill_rule_to_string (cairo_fill_rule_t rule)
+{
+    static const char *names[] = {
+	"WINDING",	/* CAIRO_FILL_RULE_WINDING */
+	"EVEN_ODD"	/* CAIRO_FILL_RILE_EVEN_ODD */
+    };
+    assert (rule < ARRAY_LENGTH (names));
+    return names[rule];
+}
+
+static const char *
+_antialias_to_string (cairo_antialias_t antialias)
+{
+    static const char *names[] = {
+	"ANTIALIAS_DEFAULT",	/* CAIRO_ANTIALIAS_DEFAULT */
+	"ANTIALIAS_NONE",	/* CAIRO_ANTIALIAS_NONE */
+	"ANTIALIAS_GRAY",	/* CAIRO_ANTIALIAS_GRAY */
+	"ANTIALIAS_SUBPIXEL"	/* CAIRO_ANTIALIAS_SUBPIXEL */
+    };
+    assert (antialias < ARRAY_LENGTH (names));
+    return names[antialias];
+}
+
+static const char *
+_line_cap_to_string (cairo_line_cap_t line_cap)
+{
+    static const char *names[] = {
+	"LINE_CAP_BUTT",	/* CAIRO_LINE_CAP_BUTT */
+	"LINE_CAP_ROUND",	/* CAIRO_LINE_CAP_ROUND */
+	"LINE_CAP_SQUARE"	/* CAIRO_LINE_CAP_SQUARE */
+    };
+    assert (line_cap < ARRAY_LENGTH (names));
+    return names[line_cap];
+}
+
+static const char *
+_line_join_to_string (cairo_line_join_t line_join)
+{
+    static const char *names[] = {
+	"LINE_JOIN_MITER",	/* CAIRO_LINE_JOIN_MITER */
+	"LINE_JOIN_ROUND",	/* CAIRO_LINE_JOIN_ROUND */
+	"LINE_JOIN_BEVEL",	/* CAIRO_LINE_JOIN_BEVEL */
+    };
+    assert (line_join < ARRAY_LENGTH (names));
+    return names[line_join];
+}
+
+static cairo_bool_t
+_cairo_script_surface_owns_context (cairo_script_surface_t *surface)
+{
+    return surface->ctx->current_target == surface;
+}
+
+static cairo_status_t
+_emit_context (cairo_script_surface_t *surface)
+{
+    if (_cairo_script_surface_owns_context (surface))
+	return CAIRO_STATUS_SUCCESS;
+
+    if (surface->ctx->current_target != NULL)
+	_cairo_output_stream_puts (surface->ctx->stream, "pop\n");
+
+    surface->ctx->current_target = surface;
+
+    if (surface->id == (unsigned long) -1) {
+	cairo_status_t status;
+
+	status = _bitmap_next_id (&surface->ctx->surface_id,
+				  &surface->id);
+	if (status)
+	    return status;
+
+	_cairo_output_stream_printf (surface->ctx->stream,
+				     "dict\n"
+				     "  /width %f set\n"
+				     "  /height %f set\n",
+				     surface->width,
+				     surface->height);
+	if (surface->base.x_fallback_resolution !=
+	    CAIRO_SURFACE_FALLBACK_RESOLUTION_DEFAULT ||
+	    surface->base.y_fallback_resolution !=
+	    CAIRO_SURFACE_FALLBACK_RESOLUTION_DEFAULT)
+	{
+	    _cairo_output_stream_printf (surface->ctx->stream,
+					 "  /fallback-resolution [%f %f] set\n",
+					 surface->base.x_fallback_resolution,
+					 surface->base.y_fallback_resolution);
+	}
+	if (surface->base.device_transform.x0 != 0. ||
+	    surface->base.device_transform.y0 != 0.)
+	{
+	    /* XXX device offset is encoded into the pattern matrices etc. */
+	    _cairo_output_stream_printf (surface->ctx->stream,
+					 "  %%/device-offset [%f %f] set\n",
+					 surface->base.device_transform.x0,
+					 surface->base.device_transform.y0);
+	}
+	_cairo_output_stream_printf (surface->ctx->stream,
+				     "  surface dup /s%lu exch def\n"
+				     "context dup /c%lu exch def\n",
+				     surface->id,
+				     surface->id);
+    } else {
+	_cairo_output_stream_printf (surface->ctx->stream,
+				     "c%lu\n",
+				     surface->id);
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_operator (cairo_script_surface_t *surface,
+		cairo_operator_t op)
+{
+    assert (_cairo_script_surface_owns_context (surface));
+
+    if (surface->cr.current_operator == op)
+	return CAIRO_STATUS_SUCCESS;
+
+    surface->cr.current_operator = op;
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "//%s set_operator\n",
+				 _operator_to_string (op));
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_fill_rule (cairo_script_surface_t *surface,
+		 cairo_fill_rule_t fill_rule)
+{
+    assert (_cairo_script_surface_owns_context (surface));
+
+    if (surface->cr.current_fill_rule == fill_rule)
+	return CAIRO_STATUS_SUCCESS;
+
+    surface->cr.current_fill_rule = fill_rule;
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "//%s set_fill_rule\n",
+				 _fill_rule_to_string (fill_rule));
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_tolerance (cairo_script_surface_t *surface,
+		 double tolerance,
+		 cairo_bool_t force)
+{
+    assert (_cairo_script_surface_owns_context (surface));
+
+    if (! force && surface->cr.current_tolerance == tolerance)
+	return CAIRO_STATUS_SUCCESS;
+
+    surface->cr.current_tolerance = tolerance;
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "%f set_tolerance\n",
+				 tolerance);
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_antialias (cairo_script_surface_t *surface,
+		 cairo_antialias_t antialias)
+{
+    assert (_cairo_script_surface_owns_context (surface));
+
+    if (surface->cr.current_antialias == antialias)
+	return CAIRO_STATUS_SUCCESS;
+
+    surface->cr.current_antialias = antialias;
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "//%s set_antialias\n",
+				 _antialias_to_string (antialias));
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_line_width (cairo_script_surface_t *surface,
+		 double line_width,
+		 cairo_bool_t force)
+{
+    assert (_cairo_script_surface_owns_context (surface));
+
+    if (! force && surface->cr.current_style.line_width == line_width)
+	return CAIRO_STATUS_SUCCESS;
+
+    surface->cr.current_style.line_width = line_width;
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "%f set_line_width\n",
+				 line_width);
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_line_cap (cairo_script_surface_t *surface,
+		cairo_line_cap_t line_cap)
+{
+    assert (_cairo_script_surface_owns_context (surface));
+
+    if (surface->cr.current_style.line_cap == line_cap)
+	return CAIRO_STATUS_SUCCESS;
+
+    surface->cr.current_style.line_cap = line_cap;
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "//%s set_line_cap\n",
+				 _line_cap_to_string (line_cap));
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_line_join (cairo_script_surface_t *surface,
+		 cairo_line_join_t line_join)
+{
+    assert (_cairo_script_surface_owns_context (surface));
+
+    if (surface->cr.current_style.line_join == line_join)
+	return CAIRO_STATUS_SUCCESS;
+
+    surface->cr.current_style.line_join = line_join;
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "//%s set_line_join\n",
+				 _line_join_to_string (line_join));
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_miter_limit (cairo_script_surface_t *surface,
+		   double miter_limit,
+		   cairo_bool_t force)
+{
+    assert (_cairo_script_surface_owns_context (surface));
+
+    if (! force && surface->cr.current_style.miter_limit == miter_limit)
+	return CAIRO_STATUS_SUCCESS;
+
+    surface->cr.current_style.miter_limit = miter_limit;
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "%f set_miter_limit\n",
+				 miter_limit);
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_dash (cairo_script_surface_t *surface,
+	    const double *dash,
+	    unsigned int num_dashes,
+	    double offset,
+	    cairo_bool_t force)
+{
+    unsigned int n;
+
+    assert (_cairo_script_surface_owns_context (surface));
+
+    if (force &&
+	num_dashes == 0 &&
+	surface->cr.current_style.num_dashes == 0)
+    {
+	return CAIRO_STATUS_SUCCESS;
+    }
+
+    if (! force &&
+	(surface->cr.current_style.num_dashes == num_dashes &&
+	 (num_dashes == 0 ||
+	  (surface->cr.current_style.dash_offset == offset &&
+	   memcmp (surface->cr.current_style.dash, dash,
+		   sizeof (double) * num_dashes)))))
+    {
+	return CAIRO_STATUS_SUCCESS;
+    }
+
+
+    if (num_dashes) {
+	surface->cr.current_style.dash = _cairo_realloc_ab
+	    (surface->cr.current_style.dash,
+	     num_dashes,
+	     sizeof (double));
+	memcpy (surface->cr.current_style.dash, dash,
+		sizeof (double) * num_dashes);
+    } else {
+	if (surface->cr.current_style.dash != NULL) {
+	    free (surface->cr.current_style.dash);
+	    surface->cr.current_style.dash = NULL;
+	}
+    }
+
+    surface->cr.current_style.num_dashes = num_dashes;
+    surface->cr.current_style.dash_offset = offset;
+
+    _cairo_output_stream_printf (surface->ctx->stream, "[");
+    for (n = 0; n < num_dashes; n++) {
+	_cairo_output_stream_printf (surface->ctx->stream, "%f", dash[n]);
+	if (n < num_dashes-1)
+	    _cairo_output_stream_puts (surface->ctx->stream, " ");
+    }
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "] %f set_dash\n",
+				 offset);
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_stroke_style (cairo_script_surface_t *surface,
+		    const cairo_stroke_style_t *style,
+		    cairo_bool_t force)
+{
+    cairo_status_t status;
+
+    assert (_cairo_script_surface_owns_context (surface));
+
+    status = _emit_line_width (surface, style->line_width, force);
+    if (status)
+	return status;
+
+    status = _emit_line_cap (surface, style->line_cap);
+    if (status)
+	return status;
+
+    status = _emit_line_join (surface, style->line_join);
+    if (status)
+	return status;
+
+    status = _emit_miter_limit (surface, style->miter_limit, force);
+    if (status)
+	return status;
+
+    status = _emit_dash (surface,
+			 style->dash, style->num_dashes, style->dash_offset,
+			 force);
+    if (status)
+	return status;
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static const char *
+_format_to_string (cairo_format_t format)
+{
+    static const char *names[] = {
+	"ARGB32",	/* CAIRO_FORMAT_ARGB32 */
+	"RGB24",	/* CAIRO_FORMAT_RGB24 */
+	"A8",		/* CAIRO_FORMAT_A8 */
+	"A1"		/* CAIRO_FORMAT_A1 */
+    };
+    assert (format < ARRAY_LENGTH (names));
+    return names[format];
+}
+
+static cairo_status_t
+_emit_solid_pattern (cairo_script_surface_t *surface,
+		     const cairo_pattern_t *pattern)
+{
+    cairo_solid_pattern_t *solid = (cairo_solid_pattern_t *) pattern;
+
+    if (solid->content & CAIRO_CONTENT_ALPHA &&
+	! CAIRO_COLOR_IS_OPAQUE (&solid->color))
+    {
+	if (! (solid->content & CAIRO_CONTENT_COLOR) ||
+	    (solid->color.red_short   == 0 &&
+	     solid->color.green_short == 0 &&
+	     solid->color.blue_short  == 0))
+	{
+	    _cairo_output_stream_printf (surface->ctx->stream,
+					 "%f a",
+					 solid->color.alpha);
+	}
+	else
+	{
+	    _cairo_output_stream_printf (surface->ctx->stream,
+					 "%f %f %f %f rgba",
+					 solid->color.red,
+					 solid->color.green,
+					 solid->color.blue,
+					 solid->color.alpha);
+	}
+    }
+    else
+    {
+	if (solid->color.red_short == solid->color.green_short &&
+	    solid->color.red_short == solid->color.blue_short)
+	{
+	    _cairo_output_stream_printf (surface->ctx->stream,
+					 "%f g",
+					 solid->color.red);
+	}
+	else
+	{
+	    _cairo_output_stream_printf (surface->ctx->stream,
+					 "%f %f %f rgb",
+					 solid->color.red,
+					 solid->color.green,
+					 solid->color.blue);
+	}
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+
+static cairo_status_t
+_emit_gradient_color_stops (cairo_gradient_pattern_t *gradient,
+			    cairo_output_stream_t *output)
+{
+    unsigned int n;
+
+    for (n = 0; n < gradient->n_stops; n++) {
+	_cairo_output_stream_printf (output,
+				     " %f %f %f %f %f add_color_stop\n ",
+				     gradient->stops[n].offset,
+				     gradient->stops[n].color.red,
+				     gradient->stops[n].color.green,
+				     gradient->stops[n].color.blue,
+				     gradient->stops[n].color.alpha);
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_linear_pattern (cairo_script_surface_t *surface,
+		      const cairo_pattern_t *pattern)
+{
+    cairo_linear_pattern_t *linear;
+
+    linear = (cairo_linear_pattern_t *) pattern;
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "%f %f %f %f linear\n ",
+				 _cairo_fixed_to_double (linear->p1.x),
+				 _cairo_fixed_to_double (linear->p1.y),
+				 _cairo_fixed_to_double (linear->p2.x),
+				 _cairo_fixed_to_double (linear->p2.y));
+    return _emit_gradient_color_stops (&linear->base, surface->ctx->stream);
+}
+
+static cairo_status_t
+_emit_radial_pattern (cairo_script_surface_t *surface,
+		      const cairo_pattern_t *pattern)
+{
+    cairo_radial_pattern_t *radial;
+
+    radial = (cairo_radial_pattern_t *) pattern;
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "%f %f %f %f %f %f radial\n ",
+				 _cairo_fixed_to_double (radial->c1.x),
+				 _cairo_fixed_to_double (radial->c1.y),
+				 _cairo_fixed_to_double (radial->r1),
+				 _cairo_fixed_to_double (radial->c2.x),
+				 _cairo_fixed_to_double (radial->c2.y),
+				 _cairo_fixed_to_double (radial->r2));
+    return _emit_gradient_color_stops (&radial->base, surface->ctx->stream);
+}
+
+static cairo_status_t
+_emit_meta_surface_pattern (cairo_script_surface_t *surface,
+			    const cairo_pattern_t *pattern)
+{
+    cairo_surface_pattern_t *surface_pattern;
+    cairo_surface_t *source;
+    cairo_surface_t *null_surface;
+    cairo_surface_t *analysis_surface;
+    cairo_surface_t *similar;
+    cairo_status_t status;
+    cairo_box_t bbox;
+
+    surface_pattern = (cairo_surface_pattern_t *) pattern;
+    source = surface_pattern->surface;
+
+    /* first measure the extents */
+    null_surface = _cairo_null_surface_create (source->content);
+    analysis_surface = _cairo_analysis_surface_create (null_surface, -1, -1);
+    cairo_surface_destroy (null_surface);
+
+    status = analysis_surface->status;
+    if (status)
+	return status;
+
+    status = _cairo_meta_surface_replay (source, analysis_surface);
+    _cairo_analysis_surface_get_bounding_box (analysis_surface, &bbox);
+    cairo_surface_destroy (analysis_surface);
+    if (status)
+	return status;
+
+    similar = cairo_surface_create_similar (&surface->base,
+					    source->content,
+					    _cairo_fixed_to_double (bbox.p2.x-bbox.p1.x),
+					    _cairo_fixed_to_double (bbox.p2.y-bbox.p1.y));
+    if (similar->status)
+	return similar->status;
+
+    status = _cairo_meta_surface_replay (source, similar);
+    if (status) {
+	cairo_surface_destroy (similar);
+	return status;
+    }
+
+    status = _emit_context (surface);
+    if (status) {
+	cairo_surface_destroy (similar);
+	return status;
+    }
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "s%lu pattern\n ",
+				 ((cairo_script_surface_t *) similar)->id);
+    cairo_surface_destroy (similar);
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_script_surface_pattern (cairo_script_surface_t *surface,
+			      const cairo_pattern_t *pattern)
+{
+    cairo_surface_pattern_t *surface_pattern;
+    cairo_script_surface_t *source;
+
+    surface_pattern = (cairo_surface_pattern_t *) pattern;
+    source = (cairo_script_surface_t *) surface_pattern->surface;
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "s%lu pattern\n ", source->id);
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_write_image_surface (cairo_output_stream_t *output,
+		      const cairo_image_surface_t *image)
+{
+    int stride, row, width;
+    uint8_t row_stack[CAIRO_STACK_BUFFER_SIZE];
+    uint8_t *rowdata;
+    uint8_t *data;
+
+    stride = image->stride;
+    width = image->width;
+    data = image->data;
+#if WORDS_BIGENDIAN
+    switch (image->format) {
+    case CAIRO_FORMAT_A1:
+	for (row = image->height; row--; ) {
+	    _cairo_output_stream_write (output, data, (width+7)/8);
+	    data += stride;
+	}
+	break;
+    case CAIRO_FORMAT_A8:
+	for (row = image->height; row--; ) {
+	    _cairo_output_stream_write (output, data, width);
+	    data += stride;
+	}
+	break;
+    case CAIRO_FORMAT_RGB24:
+	for (row = image->height; row--; ) {
+	    int col;
+	    rowdata = data;
+	    for (col = width; col--; ) {
+		_cairo_output_stream_write (output, rowdata, 3);
+		rowdata+=4;
+	    }
+	    data += stride;
+	}
+	break;
+    case CAIRO_FORMAT_ARGB32:
+	for (row = image->height; row--; ) {
+	    _cairo_output_stream_write (output, data, 4*width);
+	    data += stride;
+	}
+	break;
+    default:
+	ASSERT_NOT_REACHED;
+	break;
+    }
+#else
+    if (stride > ARRAY_LENGTH (row_stack)) {
+	rowdata = malloc (stride);
+	if (rowdata == NULL)
+	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+    } else
+	rowdata = row_stack;
+
+    switch (image->format) {
+    case CAIRO_FORMAT_A1:
+	for (row = image->height; row--; ) {
+	    int col;
+	    for (col = 0; col < (width + 7)/8; col++)
+		rowdata[col] = CAIRO_BITSWAP8 (data[col]);
+	    _cairo_output_stream_write (output, rowdata, (width+7)/8);
+	    data += stride;
+	}
+	break;
+    case CAIRO_FORMAT_A8:
+	for (row = image->height; row--; ) {
+	    _cairo_output_stream_write (output, data, width);
+	    data += stride;
+	}
+	break;
+    case CAIRO_FORMAT_RGB24:
+	for (row = image->height; row--; ) {
+	    uint8_t *src = data;
+	    int col;
+	    for (col = 0; col < width; col++) {
+		rowdata[3*col+2] = *src++;
+		rowdata[3*col+1] = *src++;
+		rowdata[3*col+0] = *src++;
+		src++;
+	    }
+	    _cairo_output_stream_write (output, rowdata, 3*width);
+	    data += stride;
+	}
+	break;
+    case CAIRO_FORMAT_ARGB32:
+	for (row = image->height; row--; ) {
+	    uint32_t *src = (uint32_t *) data;
+	    uint32_t *dst = (uint32_t *) rowdata;
+	    int col;
+	    for (col = 0; col < width; col++)
+		dst[col] = bswap_32 (src[col]);
+	    _cairo_output_stream_write (output, rowdata, 4*width);
+	    data += stride;
+	}
+	break;
+    default:
+	ASSERT_NOT_REACHED;
+	break;
+    }
+    if (rowdata != row_stack)
+	free (rowdata);
+#endif
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+_emit_png_surface (cairo_script_surface_t *surface,
+		     cairo_image_surface_t *image)
+{
+    cairo_output_stream_t *base85_stream;
+    cairo_status_t status;
+    const uint8_t *mime_data;
+    unsigned int mime_data_length;
+
+    cairo_surface_get_mime_data (&image->base, CAIRO_MIME_TYPE_PNG,
+				 &mime_data, &mime_data_length);
+    if (mime_data == NULL)
+	return CAIRO_INT_STATUS_UNSUPPORTED;
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "dict\n"
+				 "  /width %d set\n"
+				 "  /height %d set\n"
+				 "  /format //%s set\n"
+				 "  /mime-type (image/png) set\n"
+				 "  /source <~",
+				 image->width, image->height,
+				 _format_to_string (image->format));
+
+    base85_stream = _cairo_base85_stream_create (surface->ctx->stream);
+    _cairo_output_stream_write (base85_stream, mime_data, mime_data_length);
+    status = _cairo_output_stream_destroy (base85_stream);
+    if (status)
+	return status;
+
+    _cairo_output_stream_puts (surface->ctx->stream,
+			       " set\n  image");
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_image_surface (cairo_script_surface_t *surface,
+		     cairo_image_surface_t *image)
+{
+    cairo_output_stream_t *base85_stream;
+    cairo_output_stream_t *zlib_stream;
+    cairo_status_t status, status2;
+    const uint8_t *mime_data;
+    unsigned int mime_data_length;
+
+    status = _emit_png_surface (surface, image);
+    if (_cairo_status_is_error (status)) {
+	return status;
+    } else if (status == CAIRO_INT_STATUS_UNSUPPORTED) {
+	_cairo_output_stream_printf (surface->ctx->stream,
+				     "dict\n"
+				     "  /width %d set\n"
+				     "  /height %d set\n"
+				     "  /format //%s set\n"
+				     "  /source <~",
+				     image->width, image->height,
+				     _format_to_string (image->format));
+
+	if (image->width * image->height > 8) {
+	    base85_stream = _cairo_base85_stream_create (surface->ctx->stream);
+	    zlib_stream = _cairo_deflate_stream_create (base85_stream);
+
+	    status = _write_image_surface (zlib_stream, image);
+
+	    status2 = _cairo_output_stream_destroy (zlib_stream);
+	    if (status == CAIRO_STATUS_SUCCESS)
+		status = status2;
+	    status2 = _cairo_output_stream_destroy (base85_stream);
+	    if (status == CAIRO_STATUS_SUCCESS)
+		status = status2;
+	    if (status)
+		return status;
+
+	    _cairo_output_stream_puts (surface->ctx->stream,
+				       " /deflate filter set\n  image");
+	} else {
+	    base85_stream = _cairo_base85_stream_create (surface->ctx->stream);
+	    status = _write_image_surface (base85_stream, image);
+	    status2 = _cairo_output_stream_destroy (base85_stream);
+	    if (status == CAIRO_STATUS_SUCCESS)
+		status = status2;
+
+	    _cairo_output_stream_puts (surface->ctx->stream,
+				       " set\n  image");
+	}
+    }
+
+    cairo_surface_get_mime_data (&image->base, CAIRO_MIME_TYPE_JPEG,
+				 &mime_data, &mime_data_length);
+    if (mime_data != NULL) {
+	_cairo_output_stream_printf (surface->ctx->stream,
+				     "\n (%s) <~",
+				     CAIRO_MIME_TYPE_JPEG);
+
+	base85_stream = _cairo_base85_stream_create (surface->ctx->stream);
+	_cairo_output_stream_write (base85_stream, mime_data, mime_data_length);
+	status = _cairo_output_stream_destroy (base85_stream);
+	if (status)
+	    return status;
+
+	_cairo_output_stream_puts (surface->ctx->stream,
+				   " set_mime_data\n ");
+    }
+
+    _cairo_output_stream_puts (surface->ctx->stream,
+			       " pattern\n ");
+
+    return status;
+}
+
+static cairo_status_t
+_emit_image_surface_pattern (cairo_script_surface_t *surface,
+			     const cairo_pattern_t *pattern)
+{
+    cairo_surface_pattern_t *surface_pattern;
+    cairo_surface_t *source;
+    cairo_image_surface_t *image;
+    void *image_extra;
+    cairo_status_t status;
+
+    surface_pattern = (cairo_surface_pattern_t *) pattern;
+    source = surface_pattern->surface;
+
+    /* XXX snapshot-cow */
+    status = _cairo_surface_acquire_source_image (source, &image, &image_extra);
+    if (status)
+	return status;
+
+    status = _emit_image_surface (surface, image);
+
+    _cairo_surface_release_source_image (source, image, image_extra);
+
+    return status;
+}
+
+static cairo_status_t
+_emit_surface_pattern (cairo_script_surface_t *surface,
+		       const cairo_pattern_t *pattern)
+{
+    cairo_surface_pattern_t *surface_pattern;
+    cairo_surface_t *source;
+
+    surface_pattern = (cairo_surface_pattern_t *) pattern;
+    source = surface_pattern->surface;
+
+    switch ((int) source->type) {
+    case CAIRO_INTERNAL_SURFACE_TYPE_META:
+	return _emit_meta_surface_pattern (surface, pattern);
+    case CAIRO_SURFACE_TYPE_SCRIPT:
+	return _emit_script_surface_pattern (surface, pattern);
+    default:
+	return _emit_image_surface_pattern (surface, pattern);
+    }
+}
+
+static cairo_status_t
+_emit_pattern (cairo_script_surface_t *surface,
+	       const cairo_pattern_t *pattern)
+{
+    cairo_status_t status;
+
+    switch (pattern->type) {
+    case CAIRO_PATTERN_TYPE_SOLID:
+	/* solid colors do not need filter/extend/matrix */
+	return _emit_solid_pattern (surface, pattern);
+
+    case CAIRO_PATTERN_TYPE_LINEAR:
+	status = _emit_linear_pattern (surface, pattern);
+	break;
+    case CAIRO_PATTERN_TYPE_RADIAL:
+	status = _emit_radial_pattern (surface, pattern);
+	break;
+    case CAIRO_PATTERN_TYPE_SURFACE:
+	status = _emit_surface_pattern (surface, pattern);
+	break;
+
+    default:
+	ASSERT_NOT_REACHED;
+	status = CAIRO_INT_STATUS_UNSUPPORTED;
+    }
+    if (status)
+	return status;
+
+    if (! _cairo_matrix_is_identity (&pattern->matrix)) {
+	_cairo_output_stream_printf (surface->ctx->stream,
+				     " [%f %f %f %f %f %f] set_matrix\n ",
+				     pattern->matrix.xx, pattern->matrix.yx,
+				     pattern->matrix.xy, pattern->matrix.yy,
+				     pattern->matrix.x0, pattern->matrix.y0);
+    }
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 " //%s set_extend\n "
+				 " //%s set_filter\n ",
+				 _extend_to_string (pattern->extend),
+				 _filter_to_string (pattern->filter));
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_identity (cairo_script_surface_t *surface,
+		cairo_bool_t *matrix_updated)
+{
+    assert (_cairo_script_surface_owns_context (surface));
+
+    if (_cairo_matrix_is_identity (&surface->cr.current_ctm))
+	return CAIRO_STATUS_SUCCESS;
+
+    _cairo_output_stream_puts (surface->ctx->stream,
+			       "identity set_matrix\n");
+
+    *matrix_updated = TRUE;
+    cairo_matrix_init_identity (&surface->cr.current_ctm);
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_source (cairo_script_surface_t *surface,
+	      cairo_operator_t op,
+	      const cairo_pattern_t *source)
+{
+    cairo_bool_t matrix_updated = FALSE;
+    cairo_status_t status;
+
+    assert (_cairo_script_surface_owns_context (surface));
+
+    if (op == CAIRO_OPERATOR_CLEAR) {
+	/* the source is ignored, so don't change it */
+	return CAIRO_STATUS_SUCCESS;
+    }
+
+    if (surface->cr.current_source == source)
+	return CAIRO_STATUS_SUCCESS;
+
+    if (_cairo_pattern_equal (surface->cr.current_source, source))
+	return CAIRO_STATUS_SUCCESS;
+
+    cairo_pattern_destroy (surface->cr.current_source);
+    status = _cairo_pattern_create_copy (&surface->cr.current_source,
+					 source);
+    if (status)
+	return status;
+
+    status = _emit_identity (surface, &matrix_updated);
+    if (status)
+	return status;
+
+    status = _emit_pattern (surface, source);
+    if (status)
+	return status;
+
+    _cairo_output_stream_puts (surface->ctx->stream,
+			       " set_source\n");
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_path_move_to (void *closure, cairo_point_t *point)
+{
+    _cairo_output_stream_printf (closure,
+				 " %f %f m",
+				 _cairo_fixed_to_double (point->x),
+				 _cairo_fixed_to_double (point->y));
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_path_line_to (void *closure, cairo_point_t *point)
+{
+    _cairo_output_stream_printf (closure,
+				 " %f %f l",
+				 _cairo_fixed_to_double (point->x),
+				 _cairo_fixed_to_double (point->y));
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_path_curve_to (void *closure,
+		cairo_point_t *p1,
+		cairo_point_t *p2,
+		cairo_point_t *p3)
+{
+    _cairo_output_stream_printf (closure,
+				 " %f %f %f %f %f %f c",
+				 _cairo_fixed_to_double (p1->x),
+				 _cairo_fixed_to_double (p1->y),
+				 _cairo_fixed_to_double (p2->x),
+				 _cairo_fixed_to_double (p2->y),
+				 _cairo_fixed_to_double (p3->x),
+				 _cairo_fixed_to_double (p3->y));
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_path_close (void *closure)
+{
+    _cairo_output_stream_printf (closure,
+				 " h");
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_path (cairo_script_surface_t *surface,
+	    cairo_path_fixed_t *path)
+{
+    cairo_box_t box;
+    cairo_status_t status;
+
+    assert (_cairo_script_surface_owns_context (surface));
+    assert (_cairo_matrix_is_identity (&surface->cr.current_ctm));
+
+    if (_cairo_path_fixed_equal (&surface->cr.current_path, path))
+	return CAIRO_STATUS_SUCCESS;
+
+    _cairo_path_fixed_fini (&surface->cr.current_path);
+
+    _cairo_output_stream_puts (surface->ctx->stream, "n");
+
+    if (path == NULL) {
+	_cairo_path_fixed_init (&surface->cr.current_path);
+    } else if (_cairo_path_fixed_is_rectangle (path, &box)) {
+	double x1 = _cairo_fixed_to_double (box.p1.x);
+	double y1 = _cairo_fixed_to_double (box.p1.y);
+	double x2 = _cairo_fixed_to_double (box.p2.x);
+	double y2 = _cairo_fixed_to_double (box.p2.y);
+
+	status = _cairo_path_fixed_init_copy (&surface->cr.current_path, path);
+	if (status)
+	    return status;
+
+	_cairo_output_stream_printf (surface->ctx->stream,
+				     " %f %f %f %f rectangle",
+				     x1, y1, x2 - x1, y2 - y1);
+    } else {
+	cairo_status_t status;
+
+	status = _cairo_path_fixed_init_copy (&surface->cr.current_path, path);
+	if (status)
+	    return status;
+
+	status = _cairo_path_fixed_interpret (path,
+					      CAIRO_DIRECTION_FORWARD,
+					      _path_move_to,
+					      _path_line_to,
+					      _path_curve_to,
+					      _path_close,
+					      surface->ctx->stream);
+	if (status)
+	    return status;
+    }
+
+    _cairo_output_stream_puts (surface->ctx->stream, "\n");
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_matrix (cairo_script_surface_t *surface,
+	      const cairo_matrix_t *ctm,
+	      cairo_bool_t *matrix_updated)
+{
+    assert (_cairo_script_surface_owns_context (surface));
+
+    if (memcmp (&surface->cr.current_ctm, ctm, sizeof (cairo_matrix_t)) == 0)
+	return CAIRO_STATUS_SUCCESS;
+
+    *matrix_updated = TRUE;
+    surface->cr.current_ctm = *ctm;
+
+    if (_cairo_matrix_is_identity (ctm)) {
+	_cairo_output_stream_puts (surface->ctx->stream,
+				   "identity set_matrix\n");
+    } else {
+	_cairo_output_stream_printf (surface->ctx->stream,
+				   "[%f %f %f %f %f %f] set_matrix\n",
+				   ctm->xx, ctm->yx,
+				   ctm->xy, ctm->yy,
+				   ctm->x0, ctm->y0);
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_font_matrix (cairo_script_surface_t *surface,
+		   const cairo_matrix_t *font_matrix)
+{
+    assert (_cairo_script_surface_owns_context (surface));
+
+    if (memcmp (&surface->cr.current_font_matrix,
+		font_matrix,
+		sizeof (cairo_matrix_t)) == 0)
+    {
+	return CAIRO_STATUS_SUCCESS;
+    }
+
+    surface->cr.current_font_matrix = *font_matrix;
+
+    if (_cairo_matrix_is_identity (font_matrix)) {
+	_cairo_output_stream_puts (surface->ctx->stream,
+				   "identity set_font_matrix\n");
+    } else {
+	_cairo_output_stream_printf (surface->ctx->stream,
+				   "[%f %f %f %f %f %f] set_font_matrix\n",
+				   font_matrix->xx, font_matrix->yx,
+				   font_matrix->xy, font_matrix->yy,
+				   font_matrix->x0, font_matrix->y0);
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static const char *
+_content_to_string (cairo_content_t content)
+{
+    switch (content) {
+    case CAIRO_CONTENT_ALPHA: return "ALPHA";
+    case CAIRO_CONTENT_COLOR: return "COLOR";
+    default:
+    case CAIRO_CONTENT_COLOR_ALPHA: return "COLOR_ALPHA";
+    }
+}
+
+static cairo_surface_t *
+_cairo_script_surface_create_similar (void	       *abstract_surface,
+				      cairo_content_t	content,
+				      int		width,
+				      int		height)
+{
+    cairo_script_surface_t *surface, *other;
+    cairo_script_vmcontext_t *ctx;
+    cairo_status_t status;
+
+    other = abstract_surface;
+    ctx = other->ctx;
+
+    if (other->id == (unsigned long) -1) {
+	cairo_status_t status;
+
+	status = _bitmap_next_id (&ctx->surface_id,
+				  &other->id);
+	if (status)
+	    return _cairo_surface_create_in_error (status);
+
+	_cairo_output_stream_printf (ctx->stream,
+				     "dict\n"
+				     "  /width %f set\n"
+				     "  /height %f set\n"
+				     "  surface dup /s%lu exch def\n"
+				     "context /c%lu exch def\n",
+				     other->width,
+				     other->height,
+				     other->id,
+				     other->id);
+    }
+
+
+    surface = _cairo_script_surface_create_internal (ctx, width, height);
+    if (surface->base.status)
+	return &surface->base;
+
+    status = _bitmap_next_id (&ctx->surface_id,
+			      &surface->id);
+    if (status) {
+	cairo_surface_destroy (&surface->base);
+	return _cairo_surface_create_in_error (status);
+    }
+
+    if (ctx->current_target != NULL)
+	_cairo_output_stream_printf (ctx->stream, "pop\n");
+
+    _cairo_output_stream_printf (ctx->stream,
+				 "s%lu %u %u //%s similar dup /s%lu exch def\n"
+				 "context dup /c%lu exch def\n",
+				 other->id, width, height,
+				 _content_to_string (content),
+				 surface->id,
+				 surface->id);
+
+    ctx->current_target = surface;
+
+    return &surface->base;
+}
+
+static cairo_status_t
+_vmcontext_destroy (cairo_script_vmcontext_t *ctx)
+{
+    cairo_status_t status;
+
+    if (--ctx->ref)
+	return _cairo_output_stream_flush (ctx->stream);
+
+    while (ctx->fonts != NULL ){
+	cairo_script_surface_font_private_t *font = ctx->fonts;
+	ctx->fonts = font->next;
+	_cairo_script_surface_scaled_font_fini (font->parent);
+    }
+
+    status = _cairo_output_stream_destroy (ctx->stream);
+
+    free (ctx);
+
+    return status;
+}
+
+static cairo_status_t
+_cairo_script_surface_finish (void *abstract_surface)
+{
+    cairo_script_surface_t *surface = abstract_surface;
+    cairo_status_t status;
+
+    cairo_pattern_destroy (surface->cr.current_source);
+    _cairo_path_fixed_fini (&surface->cr.current_path);
+
+    if (surface->ctx->current_target == surface) {
+	_cairo_output_stream_printf (surface->ctx->stream,
+				     "pop\n");
+	surface->ctx->current_target = NULL;
+    }
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "/c%lu undef\n"
+				 "/s%lu undef\n",
+				 surface->id,
+				 surface->id);
+
+    _bitmap_release_id (&surface->ctx->surface_id, surface->id);
+
+    status = _vmcontext_destroy (surface->ctx);
+
+    return status;
+}
+
+static cairo_int_status_t
+_cairo_script_surface_copy_page (void *abstract_surface)
+{
+    cairo_script_surface_t *surface = abstract_surface;
+    cairo_status_t status;
+
+    status = _emit_context (surface);
+    if (status)
+	return status;
+
+    _cairo_output_stream_puts (surface->ctx->stream, "copy_page\n");
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+_cairo_script_surface_show_page (void *abstract_surface)
+{
+    cairo_script_surface_t *surface = abstract_surface;
+    cairo_status_t status;
+
+    status = _emit_context (surface);
+    if (status)
+	return status;
+
+    _cairo_output_stream_puts (surface->ctx->stream, "show_page\n");
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+_cairo_script_surface_intersect_clip_path (void			*abstract_surface,
+					   cairo_path_fixed_t	*path,
+					   cairo_fill_rule_t	 fill_rule,
+					   double		 tolerance,
+					   cairo_antialias_t	 antialias)
+{
+    cairo_script_surface_t *surface = abstract_surface;
+    cairo_bool_t matrix_updated = FALSE;
+    cairo_status_t status;
+
+    status = _emit_context (surface);
+    if (status)
+	return status;
+
+    if (path == NULL) {
+	_cairo_output_stream_puts (surface->ctx->stream, "reset_clip\n");
+	return CAIRO_STATUS_SUCCESS;
+    }
+
+    status = _emit_identity (surface, &matrix_updated);
+    if (status)
+	return status;
+
+    status = _emit_fill_rule (surface, fill_rule);
+    if (status)
+	return status;
+
+    status = _emit_tolerance (surface, tolerance, matrix_updated);
+    if (status)
+	return status;
+
+    status = _emit_antialias (surface, antialias);
+    if (status)
+	return status;
+
+    status = _emit_path (surface, path);
+    if (status)
+	return status;
+
+    _cairo_output_stream_puts (surface->ctx->stream, "clip+\n");
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+_cairo_script_surface_paint (void			*abstract_surface,
+			     cairo_operator_t		 op,
+			     const cairo_pattern_t	*source,
+			     cairo_rectangle_int_t	*extents)
+{
+    cairo_script_surface_t *surface = abstract_surface;
+    cairo_status_t status;
+
+    status = _emit_context (surface);
+    if (status)
+	return status;
+
+    status = _emit_operator (surface, op);
+    if (status)
+	return status;
+
+    status = _emit_source (surface, op, source);
+    if (status)
+	return status;
+
+    _cairo_output_stream_puts (surface->ctx->stream,
+			       "paint\n");
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+_cairo_script_surface_mask (void			*abstract_surface,
+			    cairo_operator_t		 op,
+			    const cairo_pattern_t	*source,
+			    const cairo_pattern_t	*mask,
+			    cairo_rectangle_int_t	*extents)
+{
+    cairo_script_surface_t *surface = abstract_surface;
+    cairo_status_t status;
+
+    status = _emit_context (surface);
+    if (status)
+	return status;
+
+    status = _emit_operator (surface, op);
+    if (status)
+	return status;
+
+    status = _emit_source (surface, op, source);
+    if (status)
+	return status;
+
+    status = _emit_pattern (surface, mask);
+    if (status)
+	return status;
+
+    _cairo_output_stream_puts (surface->ctx->stream,
+			       " mask\n");
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+_cairo_script_surface_stroke (void				*abstract_surface,
+			      cairo_operator_t			 op,
+			      const cairo_pattern_t		*source,
+			      cairo_path_fixed_t		*path,
+			      cairo_stroke_style_t		*style,
+			      cairo_matrix_t			*ctm,
+			      cairo_matrix_t			*ctm_inverse,
+			      double				 tolerance,
+			      cairo_antialias_t			 antialias,
+			      cairo_rectangle_int_t		*extents)
+{
+    cairo_script_surface_t *surface = abstract_surface;
+    cairo_bool_t matrix_updated = FALSE;
+    cairo_status_t status;
+
+    status = _emit_context (surface);
+    if (status)
+	return status;
+
+    status = _emit_identity (surface, &matrix_updated);
+    if (status)
+	return status;
+
+    status = _emit_path (surface, path);
+    if (status)
+	return status;
+
+    status = _emit_source (surface, op, source);
+    if (status)
+	return status;
+
+    status = _emit_matrix (surface, ctm, &matrix_updated);
+    if (status)
+	return status;
+
+    status = _emit_operator (surface, op);
+    if (status)
+	return status;
+
+    status = _emit_stroke_style (surface, style, matrix_updated);
+    if (status)
+	return status;
+
+    status = _emit_tolerance (surface, tolerance, matrix_updated);
+    if (status)
+	return status;
+
+    status = _emit_antialias (surface, antialias);
+    if (status)
+	return status;
+
+    _cairo_output_stream_puts (surface->ctx->stream, "stroke+\n");
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+_cairo_script_surface_fill (void			*abstract_surface,
+			    cairo_operator_t		 op,
+			    const cairo_pattern_t	*source,
+			    cairo_path_fixed_t		*path,
+			    cairo_fill_rule_t		 fill_rule,
+			    double			 tolerance,
+			    cairo_antialias_t		 antialias,
+			    cairo_rectangle_int_t	*extents)
+{
+    cairo_script_surface_t *surface = abstract_surface;
+    cairo_bool_t matrix_updated = FALSE;
+    cairo_status_t status;
+
+    status = _emit_context (surface);
+    if (status)
+	return status;
+
+    status = _emit_operator (surface, op);
+    if (status)
+	return status;
+
+    status = _emit_identity (surface, &matrix_updated);
+    if (status)
+	return status;
+
+    status = _emit_source (surface, op, source);
+    if (status)
+	return status;
+
+    status = _emit_fill_rule (surface, fill_rule);
+    if (status)
+	return status;
+
+    status = _emit_tolerance (surface, tolerance, matrix_updated);
+    if (status)
+	return status;
+
+    status = _emit_antialias (surface, antialias);
+    if (status)
+	return status;
+
+    status = _emit_path (surface, path);
+    if (status)
+	return status;
+
+    _cairo_output_stream_puts (surface->ctx->stream, "fill+\n");
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_bool_t
+_cairo_script_surface_has_show_text_glyphs (void *abstract_surface)
+{
+    return TRUE;
+}
+
+static const char *
+_subpixel_order_to_string (cairo_subpixel_order_t subpixel_order)
+{
+    static const char *names[] = {
+	"SUBPIXEL_ORDER_DEFAULT",	/* CAIRO_SUBPIXEL_ORDER_DEFAULT */
+	"SUBPIXEL_ORDER_RGB",		/* CAIRO_SUBPIXEL_ORDER_RGB */
+	"SUBPIXEL_ORDER_BGR",		/* CAIRO_SUBPIXEL_ORDER_BGR */
+	"SUBPIXEL_ORDER_VRGB",		/* CAIRO_SUBPIXEL_ORDER_VRGB */
+	"SUBPIXEL_ORDER_VBGR"		/* CAIRO_SUBPIXEL_ORDER_VBGR */
+    };
+    return names[subpixel_order];
+}
+static const char *
+_hint_style_to_string (cairo_hint_style_t hint_style)
+{
+    static const char *names[] = {
+	"HINT_STYLE_DEFAULT",	/* CAIRO_HINT_STYLE_DEFAULT */
+	"HINT_STYLE_NONE",	/* CAIRO_HINT_STYLE_NONE */
+	"HINT_STYLE_SLIGHT",	/* CAIRO_HINT_STYLE_SLIGHT */
+	"HINT_STYLE_MEDIUM",	/* CAIRO_HINT_STYLE_MEDIUM */
+	"HINT_STYLE_FULL"	/* CAIRO_HINT_STYLE_FULL */
+    };
+    return names[hint_style];
+}
+static const char *
+_hint_metrics_to_string (cairo_hint_metrics_t hint_metrics)
+{
+    static const char *names[] = {
+	 "HINT_METRICS_DEFAULT",	/* CAIRO_HINT_METRICS_DEFAULT */
+	 "HINT_METRICS_OFF",		/* CAIRO_HINT_METRICS_OFF */
+	 "HINT_METRICS_ON"		/* CAIRO_HINT_METRICS_ON */
+    };
+    return names[hint_metrics];
+}
+
+static cairo_status_t
+_emit_font_options (cairo_script_surface_t *surface,
+		    cairo_font_options_t *font_options)
+{
+    if (cairo_font_options_equal (&surface->cr.current_font_options,
+				  font_options))
+    {
+	return CAIRO_STATUS_SUCCESS;
+    }
+
+    _cairo_output_stream_printf (surface->ctx->stream, "dict\n");
+
+    if (font_options->antialias != surface->cr.current_font_options.antialias) {
+	_cairo_output_stream_printf (surface->ctx->stream,
+				     "  /antialias //%s set\n",
+				     _antialias_to_string (font_options->antialias));
+    }
+
+    if (font_options->subpixel_order !=
+	surface->cr.current_font_options.subpixel_order)
+    {
+	_cairo_output_stream_printf (surface->ctx->stream,
+				     "  /subpixel-order //%s set\n",
+				     _subpixel_order_to_string (font_options->subpixel_order));
+    }
+
+    if (font_options->hint_style !=
+	surface->cr.current_font_options.hint_style)
+    {
+	_cairo_output_stream_printf (surface->ctx->stream,
+				     "  /hint-style //%s set\n",
+				     _hint_style_to_string (font_options->hint_style));
+    }
+
+    if (font_options->hint_metrics !=
+	surface->cr.current_font_options.hint_metrics)
+    {
+	_cairo_output_stream_printf (surface->ctx->stream,
+				     "  /hint-metrics //%s set\n",
+				     _hint_metrics_to_string (font_options->hint_metrics));
+    }
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "  set_font_options\n");
+
+    surface->cr.current_font_options = *font_options;
+
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static void
+_cairo_script_surface_scaled_font_fini (cairo_scaled_font_t *scaled_font)
+{
+    cairo_script_surface_font_private_t *font_private;
+
+    font_private = scaled_font->surface_private;
+    if (font_private != NULL) {
+	_cairo_output_stream_printf (font_private->ctx->stream,
+				     "/f%lu undef\n",
+				     font_private->id);
+
+	_bitmap_release_id (&font_private->ctx->font_id, font_private->id);
+
+	if (font_private->prev != NULL)
+	    font_private->prev = font_private->next;
+	else
+	    font_private->ctx->fonts = font_private->next;
+
+	if (font_private->next != NULL)
+	    font_private->next = font_private->prev;
+
+	free (font_private);
+
+	scaled_font->surface_private = NULL;
+    }
+}
+
+static cairo_status_t
+_emit_type42_font (cairo_script_surface_t *surface,
+		   cairo_scaled_font_t *scaled_font)
+{
+    const cairo_scaled_font_backend_t *backend;
+    cairo_script_surface_font_private_t *font_private;
+    cairo_output_stream_t *base85_stream;
+    cairo_output_stream_t *zlib_stream;
+    cairo_status_t status, status2;
+    unsigned long size;
+    unsigned int load_flags;
+    uint8_t *buf;
+
+    backend = scaled_font->backend;
+    if (backend->load_truetype_table == NULL)
+	return CAIRO_INT_STATUS_UNSUPPORTED;
+
+    size = 0;
+    status = backend->load_truetype_table (scaled_font, 0, 0, NULL, &size);
+    if (status)
+	return status;
+
+    buf = malloc (size);
+    if (buf == NULL)
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    status = backend->load_truetype_table (scaled_font, 0, 0, buf, NULL);
+    if (status) {
+	free (buf);
+	return status;
+    }
+
+    load_flags = _cairo_ft_scaled_font_get_load_flags (scaled_font);
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "dict\n"
+				 "  /type 42 set\n"
+				 "  /size %lu set\n"
+				 "  /index 0 set\n"
+				 "  /flags %d set\n"
+				 "  /source <~",
+				 size, load_flags);
+
+    base85_stream = _cairo_base85_stream_create (surface->ctx->stream);
+    zlib_stream = _cairo_deflate_stream_create (base85_stream);
+
+    _cairo_output_stream_write (zlib_stream, buf, size);
+    free (buf);
+
+    status2 = _cairo_output_stream_destroy (zlib_stream);
+    if (status == CAIRO_STATUS_SUCCESS)
+	status = status2;
+
+    status2 = _cairo_output_stream_destroy (base85_stream);
+    if (status == CAIRO_STATUS_SUCCESS)
+	status = status2;
+
+    font_private = scaled_font->surface_private;
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 " /deflate filter set\n"
+				 "  font dup /f%lu exch def set_font_face\n",
+				 font_private->id);
+
+    return status;
+}
+
+static cairo_status_t
+_emit_scaled_font_init (cairo_script_surface_t *surface,
+			cairo_scaled_font_t *scaled_font)
+{
+    cairo_script_surface_font_private_t *font_private;
+    cairo_status_t status;
+
+    font_private = malloc (sizeof (cairo_script_surface_font_private_t));
+    if (font_private == NULL)
+	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+    font_private->ctx = surface->ctx;
+    font_private->parent = scaled_font;
+    font_private->subset_glyph_index = 0;
+    font_private->has_sfnt = TRUE;
+
+    font_private->next = font_private->ctx->fonts;
+    font_private->prev = NULL;
+    if (font_private->ctx->fonts != NULL)
+	font_private->ctx->fonts->prev = font_private;
+    font_private->ctx->fonts = font_private;
+
+    status = _bitmap_next_id (&surface->ctx->font_id,
+			      &font_private->id);
+    if (status) {
+	free (font_private);
+	return status;
+    }
+
+    scaled_font->surface_private = font_private;
+    scaled_font->surface_backend = &_cairo_script_surface_backend;
+
+    status = _emit_context (surface);
+    if (status)
+	return status;
+
+    status = _emit_type42_font (surface, scaled_font);
+    if (status != CAIRO_INT_STATUS_UNSUPPORTED)
+	return status;
+
+    font_private->has_sfnt = FALSE;
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "dict\n"
+				 "  /type 3 set\n"
+				 "  /metrics [%f %f %f %f %f] set\n"
+				 "  /glyphs array set\n"
+				 "  font dup /f%lu exch def set_font_face\n",
+				 scaled_font->fs_extents.ascent,
+				 scaled_font->fs_extents.descent,
+				 scaled_font->fs_extents.height,
+				 scaled_font->fs_extents.max_x_advance,
+				 scaled_font->fs_extents.max_y_advance,
+				 font_private->id);
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_scaled_font (cairo_script_surface_t *surface,
+		   cairo_scaled_font_t *scaled_font)
+{
+    cairo_matrix_t matrix;
+    cairo_font_options_t options;
+    cairo_bool_t matrix_updated = FALSE;
+    cairo_status_t status;
+    cairo_script_surface_font_private_t *font_private;
+
+    cairo_scaled_font_get_ctm (scaled_font, &matrix);
+    status = _emit_matrix (surface, &matrix, &matrix_updated);
+    if (status)
+	return status;
+
+    if (! matrix_updated && surface->cr.current_scaled_font == scaled_font)
+	return CAIRO_STATUS_SUCCESS;
+
+    cairo_scaled_font_get_font_matrix (scaled_font, &matrix);
+    status = _emit_font_matrix (surface, &matrix);
+    if (status)
+	return status;
+
+    cairo_scaled_font_get_font_options (scaled_font, &options);
+    status = _emit_font_options (surface, &options);
+    if (status)
+	return status;
+
+    surface->cr.current_scaled_font = scaled_font;
+
+    assert (scaled_font->surface_backend == NULL ||
+	    scaled_font->surface_backend == &_cairo_script_surface_backend);
+
+    font_private = scaled_font->surface_private;
+    if (font_private == NULL) {
+	status = _emit_scaled_font_init (surface, scaled_font);
+	if (status)
+	    return status;
+    } else {
+	status = _emit_context (surface);
+	if (status)
+	    return status;
+
+	_cairo_output_stream_printf (surface->ctx->stream,
+				     "f%lu set_font_face\n",
+				     font_private->id);
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_scaled_glyph_vector (cairo_script_surface_t *surface,
+			   cairo_scaled_font_t *scaled_font,
+			   cairo_scaled_glyph_t *scaled_glyph)
+{
+    cairo_script_surface_font_private_t *font_private;
+    cairo_script_implicit_context_t old_cr;
+    cairo_status_t status;
+    unsigned long index;
+
+    font_private = scaled_font->surface_private;
+    index = ++font_private->subset_glyph_index;
+    scaled_glyph->surface_private = (void *) index;
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "%lu dict\n"
+				 "  /metrics [%f %f %f %f %f %f] set\n"
+				 "  /render {\n",
+				 index,
+				 scaled_glyph->fs_metrics.x_bearing,
+				 scaled_glyph->fs_metrics.y_bearing,
+				 scaled_glyph->fs_metrics.width,
+				 scaled_glyph->fs_metrics.height,
+				 scaled_glyph->fs_metrics.x_advance,
+				 scaled_glyph->fs_metrics.y_advance);
+
+    if (! _cairo_matrix_is_identity (&scaled_font->scale_inverse)) {
+	_cairo_output_stream_printf (surface->ctx->stream,
+				     "[%f %f %f %f %f %f] transform\n",
+				     scaled_font->scale_inverse.xx,
+				     scaled_font->scale_inverse.yx,
+				     scaled_font->scale_inverse.xy,
+				     scaled_font->scale_inverse.yy,
+				     scaled_font->scale_inverse.x0,
+				     scaled_font->scale_inverse.y0);
+    }
+
+    old_cr = surface->cr;
+    _cairo_script_implicit_context_init (&surface->cr);
+    status = _cairo_meta_surface_replay (scaled_glyph->meta_surface,
+					 &surface->base);
+    surface->cr = old_cr;
+
+    _cairo_output_stream_puts (surface->ctx->stream, "} set set\n");
+
+    return status;
+}
+
+static cairo_status_t
+_emit_scaled_glyph_bitmap (cairo_script_surface_t *surface,
+			   cairo_scaled_font_t *scaled_font,
+			   cairo_scaled_glyph_t *scaled_glyph)
+{
+    cairo_script_surface_font_private_t *font_private;
+    cairo_status_t status;
+    unsigned long index;
+
+    font_private = scaled_font->surface_private;
+    index = ++font_private->subset_glyph_index;
+    scaled_glyph->surface_private = (void *) index;
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "%lu dict\n"
+				 "  /metrics [%f %f %f %f %f %f] set\n"
+				 "  /render {\n"
+				 "%f %f translate\n",
+				 index,
+				 scaled_glyph->fs_metrics.x_bearing,
+				 scaled_glyph->fs_metrics.y_bearing,
+				 scaled_glyph->fs_metrics.width,
+				 scaled_glyph->fs_metrics.height,
+				 scaled_glyph->fs_metrics.x_advance,
+				 scaled_glyph->fs_metrics.y_advance,
+				 scaled_glyph->fs_metrics.x_bearing,
+				 scaled_glyph->fs_metrics.y_bearing);
+
+    status = _emit_image_surface (surface, scaled_glyph->surface);
+    if (status)
+	return status;
+
+    if (! _cairo_matrix_is_identity (&scaled_font->font_matrix)) {
+	_cairo_output_stream_printf (surface->ctx->stream,
+				     "  [%f %f %f %f %f %f] set_matrix\n",
+				     scaled_font->font_matrix.xx,
+				     scaled_font->font_matrix.yx,
+				     scaled_font->font_matrix.xy,
+				     scaled_font->font_matrix.yy,
+				     scaled_font->font_matrix.x0,
+				     scaled_font->font_matrix.y0);
+    }
+    _cairo_output_stream_puts (surface->ctx->stream,
+				 "mask\n} set set\n");
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_scaled_glyph_prologue (cairo_script_surface_t *surface,
+			     cairo_scaled_font_t *scaled_font)
+{
+    cairo_script_surface_font_private_t *font_private;
+
+    assert (scaled_font->surface_backend == &_cairo_script_surface_backend);
+
+    font_private = scaled_font->surface_private;
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "f%lu /glyphs get\n",
+				 font_private->id);
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_emit_scaled_glyphs (cairo_script_surface_t *surface,
+		     cairo_scaled_font_t *scaled_font,
+		     cairo_glyph_t *glyphs,
+		     unsigned int num_glyphs)
+{
+    cairo_script_surface_font_private_t *font_private;
+    cairo_status_t status;
+    unsigned int n;
+    cairo_bool_t have_glyph_prologue = FALSE;
+
+    if (num_glyphs == 0)
+	return CAIRO_STATUS_SUCCESS;
+
+    font_private = scaled_font->surface_private;
+    if (font_private->has_sfnt)
+	return CAIRO_STATUS_SUCCESS;
+
+    _cairo_scaled_font_freeze_cache (scaled_font);
+    for (n = 0; n < num_glyphs; n++) {
+	cairo_scaled_glyph_t *scaled_glyph;
+
+	status = _cairo_scaled_glyph_lookup (scaled_font,
+					     glyphs[n].index,
+					     CAIRO_SCALED_GLYPH_INFO_METRICS,
+					     &scaled_glyph);
+	if (status)
+	    break;
+
+	if (scaled_glyph->surface_private != NULL)
+	    continue;
+
+	status = _cairo_scaled_glyph_lookup (scaled_font,
+					     glyphs[n].index,
+					     CAIRO_SCALED_GLYPH_INFO_META_SURFACE,
+					     &scaled_glyph);
+	if (_cairo_status_is_error (status))
+	    break;
+
+	if (status == CAIRO_STATUS_SUCCESS) {
+	    if (! have_glyph_prologue) {
+		status = _emit_scaled_glyph_prologue (surface, scaled_font);
+		if (status)
+		    break;
+
+		have_glyph_prologue = TRUE;
+	    }
+
+	    status = _emit_scaled_glyph_vector (surface,
+						scaled_font,
+						scaled_glyph);
+	    if (status)
+		break;
+
+	    continue;
+	}
+
+	status = _cairo_scaled_glyph_lookup (scaled_font,
+					     glyphs[n].index,
+					     CAIRO_SCALED_GLYPH_INFO_SURFACE,
+					     &scaled_glyph);
+	if (_cairo_status_is_error (status))
+	    break;
+
+	if (status == CAIRO_STATUS_SUCCESS) {
+	    if (! have_glyph_prologue) {
+		status = _emit_scaled_glyph_prologue (surface, scaled_font);
+		if (status)
+		    break;
+
+		have_glyph_prologue = TRUE;
+	    }
+
+	    status = _emit_scaled_glyph_bitmap (surface,
+						scaled_font,
+						scaled_glyph);
+	    if (status)
+		break;
+
+	    continue;
+	}
+    }
+    _cairo_scaled_font_thaw_cache (scaled_font);
+
+    if (have_glyph_prologue) {
+	_cairo_output_stream_puts (surface->ctx->stream, "pop pop\n");
+    }
+
+    return status;
+}
+
+static cairo_int_status_t
+_cairo_script_surface_show_text_glyphs (void			    *abstract_surface,
+					cairo_operator_t	     op,
+					const cairo_pattern_t	    *source,
+					const char		    *utf8,
+					int			     utf8_len,
+					cairo_glyph_t		    *glyphs,
+					int			     num_glyphs,
+					const cairo_text_cluster_t  *clusters,
+					int			     num_clusters,
+					cairo_text_cluster_flags_t   backward,
+					cairo_scaled_font_t	    *scaled_font,
+					cairo_rectangle_int_t	    *extents)
+{
+    cairo_script_surface_t *surface = abstract_surface;
+    cairo_script_surface_font_private_t *font_private;
+    cairo_scaled_glyph_t *scaled_glyph;
+    cairo_matrix_t matrix;
+    cairo_status_t status;
+    double x, y, ix, iy;
+    int n;
+    cairo_output_stream_t *base85_stream = NULL;
+
+    status = _emit_context (surface);
+    if (status)
+	return status;
+
+    status = _emit_operator (surface, op);
+    if (status)
+	return status;
+
+    status = _emit_source (surface, op, source);
+    if (status)
+	return status;
+
+    status = _emit_scaled_font (surface, scaled_font);
+    if (status)
+	return status;
+
+    status = _emit_scaled_glyphs (surface, scaled_font, glyphs, num_glyphs);
+    if (status)
+	return status;
+
+    /* (utf8) [cx cy [glyphs]] [clusters] backward show_text_glyphs */
+    /* [cx cy [glyphs]] show_glyphs */
+
+    if (utf8 != NULL && clusters != NULL) {
+	_cairo_output_stream_printf (surface->ctx->stream,
+				     "(%s) ",
+				     utf8);
+    }
+
+    matrix = surface->cr.current_ctm;
+    status = cairo_matrix_invert (&matrix);
+    assert (status == CAIRO_STATUS_SUCCESS);
+
+    ix = x = glyphs[0].x;
+    iy = y = glyphs[0].y;
+    cairo_matrix_transform_point (&matrix, &ix, &iy);
+    ix -= scaled_font->font_matrix.x0;
+    iy -= scaled_font->font_matrix.y0;
+
+    _cairo_scaled_font_freeze_cache (scaled_font);
+    font_private = scaled_font->surface_private;
+
+    _cairo_output_stream_printf (surface->ctx->stream,
+				 "[%f %f ",
+				 ix, iy);
+
+    for (n = 0; n < num_glyphs; n++) {
+	if (font_private->has_sfnt) {
+	    if (glyphs[n].index > 256)
+		break;
+	} else {
+	    status = _cairo_scaled_glyph_lookup (scaled_font,
+						 glyphs[n].index,
+						 CAIRO_SCALED_GLYPH_INFO_METRICS,
+						 &scaled_glyph);
+	    if (status) {
+		_cairo_scaled_font_thaw_cache (scaled_font);
+		return status;
+	    }
+	}
+    }
+
+    if (n == num_glyphs) {
+	_cairo_output_stream_puts (surface->ctx->stream, "<~");
+	base85_stream = _cairo_base85_stream_create (surface->ctx->stream);
+    } else
+	_cairo_output_stream_puts (surface->ctx->stream, "[");
+
+    for (n = 0; n < num_glyphs; n++) {
+	double dx, dy;
+
+	status = _cairo_scaled_glyph_lookup (scaled_font,
+					     glyphs[n].index,
+					     CAIRO_SCALED_GLYPH_INFO_METRICS,
+					     &scaled_glyph);
+	if (status)
+	    break;
+
+	if (fabs (glyphs[n].x - x) > 1e-5 || fabs (glyphs[n].y - y) > 1e-5) {
+	    ix = x = glyphs[n].x;
+	    iy = y = glyphs[n].y;
+	    cairo_matrix_transform_point (&matrix, &ix, &iy);
+	    ix -= scaled_font->font_matrix.x0;
+	    iy -= scaled_font->font_matrix.y0;
+	    if (base85_stream != NULL) {
+		status = _cairo_output_stream_destroy (base85_stream);
+		if (status) {
+		    base85_stream = NULL;
+		    break;
+		}
+
+		_cairo_output_stream_printf (surface->ctx->stream,
+					     " %f %f <~",
+					     ix, iy);
+		base85_stream = _cairo_base85_stream_create (surface->ctx->stream);
+	    } else {
+		_cairo_output_stream_printf (surface->ctx->stream,
+					     " ] %f %f [ ",
+					     ix, iy);
+	    }
+	}
+	if (base85_stream != NULL) {
+	    uint8_t c;
+
+	    if (font_private->has_sfnt)
+		c = glyphs[n].index;
+	    else
+		c = (uint8_t) (long unsigned) scaled_glyph->surface_private;
+
+	    _cairo_output_stream_write (base85_stream, &c, 1);
+	} else {
+	    if (font_private->has_sfnt)
+		_cairo_output_stream_printf (surface->ctx->stream, " %lu",
+					     glyphs[n].index);
+	    else
+		_cairo_output_stream_printf (surface->ctx->stream, " %lu",
+					     (long unsigned) scaled_glyph->surface_private);
+	}
+
+        dx = scaled_glyph->metrics.x_advance;
+        dy = scaled_glyph->metrics.y_advance;
+	cairo_matrix_transform_distance (&scaled_font->ctm, &dx, &dy);
+	x += dx;
+	y += dy;
+    }
+    _cairo_scaled_font_thaw_cache (scaled_font);
+
+    if (base85_stream != NULL) {
+	cairo_status_t status2;
+
+	status2 = _cairo_output_stream_destroy (base85_stream);
+	if (status == CAIRO_STATUS_SUCCESS)
+	    status = status2;
+    } else {
+	_cairo_output_stream_puts (surface->ctx->stream, " ]");
+    }
+    if (status)
+	return status;
+
+    if (utf8 != NULL && clusters != NULL) {
+	for (n = 0; n < num_clusters; n++) {
+	    if (clusters[n].num_bytes > UCHAR_MAX ||
+		clusters[n].num_glyphs > UCHAR_MAX)
+	    {
+		break;
+	    }
+	}
+
+	if (n < num_clusters) {
+	    _cairo_output_stream_puts (surface->ctx->stream, "] [ ");
+	    for (n = 0; n < num_clusters; n++) {
+		_cairo_output_stream_printf (surface->ctx->stream,
+					     "%d %d ",
+					     clusters[n].num_bytes,
+					     clusters[n].num_glyphs);
+	    }
+	    _cairo_output_stream_puts (surface->ctx->stream, "]");
+	}
+	else
+	{
+	    _cairo_output_stream_puts (surface->ctx->stream, "] <~");
+	    base85_stream = _cairo_base85_stream_create (surface->ctx->stream);
+	    for (n = 0; n < num_clusters; n++) {
+		uint8_t c[2];
+		c[0] = clusters[n].num_bytes;
+		c[1] = clusters[n].num_glyphs;
+		_cairo_output_stream_write (base85_stream, c, 2);
+	    }
+	    status = _cairo_output_stream_destroy (base85_stream);
+	    if (status)
+		return status;
+	}
+
+	_cairo_output_stream_printf (surface->ctx->stream,
+				     " //%s show_text_glyphs\n",
+				     _direction_to_string (backward));
+    } else {
+	_cairo_output_stream_puts (surface->ctx->stream,
+				   "] show_glyphs\n");
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+_cairo_script_surface_get_extents (void *abstract_surface,
+				   cairo_rectangle_int_t *rectangle)
+{
+    cairo_script_surface_t *surface = abstract_surface;
+
+    if (surface->width < 0 || surface->height < 0)
+	return CAIRO_INT_STATUS_UNSUPPORTED;
+
+    rectangle->x = 0;
+    rectangle->y = 0;
+    rectangle->width = surface->width;
+    rectangle->height = surface->height;
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static const cairo_surface_backend_t
+_cairo_script_surface_backend = {
+    CAIRO_SURFACE_TYPE_SCRIPT,
+    _cairo_script_surface_create_similar,
+    _cairo_script_surface_finish,
+    NULL, //_cairo_script_surface_acquire_source_image,
+    NULL, //_cairo_script_surface_release_source_image,
+    NULL, /* acquire_dest_image */
+    NULL, /* release_dest_image */
+    NULL, /* clone_similar */
+    NULL, /* composite */
+    NULL, /* fill_rectangles */
+    NULL, /* composite_trapezoids */
+    _cairo_script_surface_copy_page,
+    _cairo_script_surface_show_page,
+    NULL, /* set_clip_region */
+    _cairo_script_surface_intersect_clip_path,
+    _cairo_script_surface_get_extents,
+    NULL, /* old_show_glyphs */
+    NULL, /* get_font_options */
+    NULL, /* flush */
+    NULL, /* mark_dirty_rectangle */
+    _cairo_script_surface_scaled_font_fini,
+    NULL, /* scaled_glyph_fini */
+
+    /* The 5 high level operations */
+    _cairo_script_surface_paint,
+    _cairo_script_surface_mask,
+    _cairo_script_surface_stroke,
+    _cairo_script_surface_fill,
+    NULL,
+
+    NULL, //_cairo_script_surface_snapshot,
+
+    NULL, /* is_similar */
+    NULL, /* reset */
+    NULL, /* fill_stroke */
+    NULL, /* create_solid_pattern_surface */
+
+    /* The alternate high-level text operation */
+    _cairo_script_surface_has_show_text_glyphs,
+    _cairo_script_surface_show_text_glyphs
+};
+
+static cairo_bool_t
+_cairo_surface_is_script (cairo_surface_t *surface)
+{
+    return surface->backend == &_cairo_script_surface_backend;
+}
+
+static cairo_script_vmcontext_t *
+_cairo_script_vmcontext_create (cairo_output_stream_t *stream)
+{
+    cairo_script_vmcontext_t *ctx;
+
+    ctx = malloc (sizeof (cairo_script_vmcontext_t));
+    if (ctx == NULL)
+	return NULL;
+
+    memset (ctx, 0, sizeof (cairo_script_vmcontext_t));
+
+    ctx->stream = stream;
+    ctx->mode = CAIRO_SCRIPT_MODE_ASCII;
+
+    return ctx;
+}
+
+static void
+_cairo_script_implicit_context_init (cairo_script_implicit_context_t *cr)
+{
+    cr->current_operator = CAIRO_GSTATE_OPERATOR_DEFAULT;
+    cr->current_fill_rule = CAIRO_GSTATE_FILL_RULE_DEFAULT;
+    cr->current_tolerance = CAIRO_GSTATE_TOLERANCE_DEFAULT;
+    cr->current_antialias = CAIRO_ANTIALIAS_DEFAULT;
+    _cairo_stroke_style_init (&cr->current_style);
+    cr->current_source = (cairo_pattern_t *) &_cairo_pattern_black.base;
+    _cairo_path_fixed_init (&cr->current_path);
+    cairo_matrix_init_identity (&cr->current_ctm);
+    cairo_matrix_init_identity (&cr->current_font_matrix);
+    _cairo_font_options_init_default (&cr->current_font_options);
+    cr->current_scaled_font = NULL;
+}
+
+static cairo_script_surface_t *
+_cairo_script_surface_create_internal (cairo_script_vmcontext_t *ctx,
+				       double width,
+				       double height)
+{
+    cairo_script_surface_t *surface;
+
+    if (ctx == NULL)
+	return (cairo_script_surface_t *) _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
+
+    surface = malloc (sizeof (cairo_script_surface_t));
+    if (surface == NULL)
+	return (cairo_script_surface_t *) _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
+
+    _cairo_surface_init (&surface->base,
+			 &_cairo_script_surface_backend,
+			 CAIRO_CONTENT_COLOR_ALPHA);
+
+    surface->ctx = ctx;
+    ctx->ref++;
+
+    surface->width = width;
+    surface->height = height;
+
+    surface->id = (unsigned long) -1;
+
+    _cairo_script_implicit_context_init (&surface->cr);
+
+    return surface;
+}
+
+cairo_surface_t *
+cairo_script_surface_create (const char		*filename,
+			     double width,
+			     double height)
+{
+    cairo_output_stream_t *stream;
+    cairo_script_surface_t *surface;
+
+    stream = _cairo_output_stream_create_for_filename (filename);
+    if (_cairo_output_stream_get_status (stream))
+	return _cairo_surface_create_in_error (_cairo_output_stream_destroy (stream));
+
+
+    surface = _cairo_script_surface_create_internal
+	(_cairo_script_vmcontext_create (stream), width, height);
+    if (surface->base.status)
+	return &surface->base;
+
+    _cairo_output_stream_puts (surface->ctx->stream, "%!CairoScript\n");
+    return &surface->base;
+}
+
+cairo_surface_t *
+cairo_script_surface_create_for_stream (cairo_write_func_t	 write_func,
+					void			*closure,
+					double width,
+					double height)
+{
+    cairo_output_stream_t *stream;
+
+    stream = _cairo_output_stream_create (write_func, NULL, closure);
+    if (_cairo_output_stream_get_status (stream))
+	return _cairo_surface_create_in_error (_cairo_output_stream_destroy (stream));
+
+    return &_cairo_script_surface_create_internal
+	(_cairo_script_vmcontext_create (stream), width, height)->base;
+}
+
+void
+cairo_script_surface_set_mode (cairo_surface_t *abstract_surface,
+			       cairo_script_mode_t mode)
+{
+    cairo_script_surface_t *surface;
+    cairo_status_t status_ignored;
+
+    if (! _cairo_surface_is_script (abstract_surface)) {
+	status_ignored = _cairo_surface_set_error (abstract_surface,
+				  CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
+	return;
+    }
+
+    if (abstract_surface->status)
+	return;
+
+    surface = (cairo_script_surface_t *) abstract_surface;
+    surface->ctx->mode = mode;
+}
+
+cairo_script_mode_t
+cairo_script_surface_get_mode (cairo_surface_t *abstract_surface)
+{
+    cairo_script_surface_t *surface;
+
+    if (! _cairo_surface_is_script (abstract_surface) ||
+	abstract_surface->status)
+    {
+	return CAIRO_SCRIPT_MODE_ASCII;
+    }
+
+    surface = (cairo_script_surface_t *) abstract_surface;
+    return surface->ctx->mode;
+}
diff --git a/src/cairo-script.h b/src/cairo-script.h
new file mode 100644
index 0000000..9c428e3
--- /dev/null
+++ b/src/cairo-script.h
@@ -0,0 +1,74 @@
+/* cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2008 Chris Wilson
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is Chris Wilson
+ *
+ * Contributor(s):
+ *	Chris Wilson <chris at chris-wilson.co.uk>
+ */
+
+#ifndef CAIRO_SCRIPT_H
+#define CAIRO_SCRIPT_H
+
+#include "cairo.h"
+
+#if CAIRO_HAS_SCRIPT_SURFACE
+
+CAIRO_BEGIN_DECLS
+
+cairo_public cairo_surface_t *
+cairo_script_surface_create (const char		*filename,
+			     double width,
+			     double height);
+
+cairo_public cairo_surface_t *
+cairo_script_surface_create_for_stream (cairo_write_func_t	 write_func,
+					void			*closure,
+					double width,
+					double height);
+
+typedef enum {
+    CAIRO_SCRIPT_MODE_BINARY,
+    CAIRO_SCRIPT_MODE_ASCII
+} cairo_script_mode_t;
+
+cairo_public void
+cairo_script_surface_set_mode (cairo_surface_t *surface,
+			       cairo_script_mode_t mode);
+
+cairo_public cairo_script_mode_t
+cairo_script_surface_get_mode (cairo_surface_t *surface);
+
+CAIRO_END_DECLS
+
+#else  /*CAIRO_HAS_SCRIPT_SURFACE*/
+# error Cairo was not compiled with support for the CairoScript backend
+#endif /*CAIRO_HAS_SCRIPT_SURFACE*/
+
+#endif /*CAIRO_SCRIPT_H*/
diff --git a/src/cairo-types-private.h b/src/cairo-types-private.h
index 77f8184..905bc40 100644
--- a/src/cairo-types-private.h
+++ b/src/cairo-types-private.h
@@ -44,6 +44,7 @@
 #include "cairo-reference-count-private.h"
 
 typedef struct _cairo_array cairo_array_t;
+typedef struct _cairo_backend cairo_backend_t;
 typedef struct _cairo_cache cairo_cache_t;
 typedef struct _cairo_clip cairo_clip_t;
 typedef struct _cairo_clip_path cairo_clip_path_t;
diff --git a/src/cairo.h b/src/cairo.h
index 27d56f5..3365523 100644
--- a/src/cairo.h
+++ b/src/cairo.h
@@ -1876,6 +1876,7 @@ cairo_surface_status (cairo_surface_t *surface);
  * @CAIRO_SURFACE_TYPE_WIN32_PRINTING: The surface is a win32 printing surface
  * @CAIRO_SURFACE_TYPE_QUARTZ_IMAGE: The surface is of type quartz_image
  * @CAIRO_SURFACE_TYPE_SDL: The surface is of type SDL, since 1.10
+ * @CAIRO_SURFACE_TYPE_SCRIPT: The surface is of type script, since 1.10
  *
  * #cairo_surface_type_t is used to describe the type of a given
  * surface. The surface types are also known as "backends" or "surface
@@ -1915,7 +1916,8 @@ typedef enum _cairo_surface_type {
     CAIRO_SURFACE_TYPE_OS2,
     CAIRO_SURFACE_TYPE_WIN32_PRINTING,
     CAIRO_SURFACE_TYPE_QUARTZ_IMAGE,
-    CAIRO_SURFACE_TYPE_SDL
+    CAIRO_SURFACE_TYPE_SDL,
+    CAIRO_SURFACE_TYPE_SCRIPT
 } cairo_surface_type_t;
 
 cairo_public cairo_surface_type_t
diff --git a/src/cairoint.h b/src/cairoint.h
index 6101ea5..940e33c 100644
--- a/src/cairoint.h
+++ b/src/cairoint.h
@@ -322,9 +322,16 @@ _cairo_user_data_array_set_data (cairo_user_data_array_t     *array,
 				 void			     *user_data,
 				 cairo_destroy_func_t	      destroy);
 
+#define _CAIRO_HASH_INIT_VALUE 5381
+
 cairo_private unsigned long
 _cairo_hash_string (const char *c);
 
+cairo_private unsigned long
+_cairo_hash_bytes (unsigned long hash,
+		   const void *bytes,
+		   unsigned int length);
+
 /*
  * A #cairo_unscaled_font_t is just an opaque handle we use in the
  * glyph cache.
@@ -339,6 +346,7 @@ typedef struct _cairo_scaled_glyph {
     cairo_cache_entry_t	    cache_entry;	/* hash is glyph index */
     cairo_scaled_font_t	    *scaled_font;	/* font the glyph lives in */
     cairo_text_extents_t    metrics;		/* user-space metrics */
+    cairo_text_extents_t    fs_metrics;		/* font-space metrics */
     cairo_box_t		    bbox;		/* device-space bounds */
     int16_t                 x_advance;		/* device-space rounded X advance */
     int16_t                 y_advance;		/* device-space rounded Y advance */
@@ -2386,6 +2394,16 @@ cairo_private cairo_status_t
 _cairo_pattern_get_extents (const cairo_pattern_t	    *pattern,
 			    cairo_rectangle_int_t           *extents);
 
+cairo_private unsigned long
+_cairo_pattern_hash (const cairo_pattern_t *pattern);
+
+cairo_private unsigned long
+_cairo_pattern_size (const cairo_pattern_t *pattern);
+
+cairo_private cairo_bool_t
+_cairo_pattern_equal (const cairo_pattern_t *a,
+		      const cairo_pattern_t *b);
+
 cairo_private void
 _cairo_pattern_reset_static_data (void);
 
diff --git a/test/Makefile.am b/test/Makefile.am
index 131ed60..498f616 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1170,10 +1170,10 @@ png_flatten_LDADD = $(top_builddir)/src/libcairo.la $(CAIRO_LDADD)
 
 if BUILD_ANY2PPM
 check_PROGRAMS += any2ppm
-any2ppm_CFLAGS = $(POPPLER_CFLAGS) $(LIBRSVG_CFLAGS) $(LIBSPECTRE_CFLAGS)
+any2ppm_CFLAGS = $(POPPLER_CFLAGS) $(LIBRSVG_CFLAGS) $(LIBSPECTRE_CFLAGS) $(csi_CFLAGS)
 # add LDADD, so poppler/librsvg uses "our" cairo
 any2ppm_LDFLAGS = $(CAIRO_TEST_UNDEFINED_LDFLAGS)
-any2ppm_LDADD  = $(top_builddir)/src/libcairo.la $(CAIRO_LDADD) $(POPPLER_LIBS) $(LIBRSVG_LIBS) $(LIBSPECTRE_LIBS)
+any2ppm_LDADD  = $(top_builddir)/src/libcairo.la $(CAIRO_LDADD) $(POPPLER_LIBS) $(LIBRSVG_LIBS) $(LIBSPECTRE_LIBS) $(csi_LIBS)
 endif
 
 if CAIRO_CAN_TEST_PDF_SURFACE
diff --git a/test/any2ppm.c b/test/any2ppm.c
index 85c1bdb..1bd4285 100644
--- a/test/any2ppm.c
+++ b/test/any2ppm.c
@@ -63,6 +63,10 @@
 
 #include <cairo.h>
 
+#if CAIRO_CAN_TEST_SCRIPT_SURFACE
+#include <cairo-script-interpreter.h>
+#endif
+
 #if CAIRO_CAN_TEST_PDF_SURFACE
 #include <poppler.h>
 #endif
@@ -160,7 +164,21 @@ write_ppm (cairo_surface_t *surface, int fd)
     int width, height, stride;
     int i, j;
 
+    data = cairo_image_surface_get_data (surface);
+    height = cairo_image_surface_get_height (surface);
+    width = cairo_image_surface_get_width (surface);
+    stride = cairo_image_surface_get_stride (surface);
     format = cairo_image_surface_get_format (surface);
+    if (format == CAIRO_FORMAT_ARGB32) {
+	/* see if we can convert to a standard ppm type and trim a few bytes */
+	cairo_bool_t uses_alpha = FALSE;
+	const unsigned char *alpha = data;
+	for (j = height * stride; j-- && uses_alpha == FALSE; alpha += 4)
+	    uses_alpha = *alpha != 0xff;
+	if (! uses_alpha)
+	    format = CAIRO_FORMAT_RGB24;
+    }
+
     switch (format) {
     case CAIRO_FORMAT_ARGB32:
 	/* XXX need true alpha for svg */
@@ -177,11 +195,6 @@ write_ppm (cairo_surface_t *surface, int fd)
 	return "unhandled image format";
     }
 
-    data = cairo_image_surface_get_data (surface);
-    height = cairo_image_surface_get_height (surface);
-    width = cairo_image_surface_get_width (surface);
-    stride = cairo_image_surface_get_stride (surface);
-
     len = sprintf (buf, "%s %d %d 255\n", format_str, width, height);
     for (j = 0; j < height; j++) {
 	const unsigned int *row = (unsigned int *) (data + stride * j);
@@ -220,6 +233,69 @@ write_ppm (cairo_surface_t *surface, int fd)
     return NULL;
 }
 
+#if CAIRO_CAN_TEST_SCRIPT_SURFACE
+static cairo_surface_t *
+_create_image (void *closure,
+	       double width, double height,
+	       csi_object_t *dictionary)
+{
+    cairo_surface_t **out = closure;
+    cairo_surface_t *surface;
+
+    surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+    return *out = cairo_surface_reference (surface);
+}
+
+static const char *
+_cairo_script_render_page (const char *filename,
+			   cairo_surface_t **surface_out)
+{
+    cairo_script_interpreter_t *csi;
+    cairo_surface_t *surface = NULL;
+    cairo_status_t status;
+
+    csi = csi_create ();
+    csi_set_surface_create_function (csi, _create_image, &surface);
+    csi_run (csi, filename);
+    status = csi_destroy (csi);
+    if (status || surface == NULL) {
+	cairo_surface_destroy (surface);
+	return "cairo-script interpreter failed";
+    }
+
+    status = cairo_surface_status (surface);
+    if (status) {
+	cairo_surface_destroy (surface);
+	return cairo_status_to_string (status);
+    }
+
+    *surface_out = surface;
+    return NULL;
+}
+
+static const char *
+cs_convert (char **argv, int fd)
+{
+    const char *err;
+    cairo_surface_t *surface = NULL; /* silence compiler warning */
+
+    err = _cairo_script_render_page (argv[0], &surface);
+    if (err != NULL)
+	return err;
+
+    err = write_ppm (surface, fd);
+    cairo_surface_destroy (surface);
+
+    return err;
+}
+#else
+static const char *
+cs_convert (char **argv, int fd)
+{
+    return "compiled without CairoScript support.";
+}
+#endif
+
 #if CAIRO_CAN_TEST_PDF_SURFACE
 /* adapted from pdf2png.c */
 static const char *
@@ -453,6 +529,7 @@ convert (char **argv, int fd)
 	const char *type;
 	const char *(*func) (char **, int);
     } converters[] = {
+	{ "cs", cs_convert },
 	{ "pdf", pdf_convert },
 	{ "ps", ps_convert },
 	{ "svg", svg_convert },
diff --git a/test/mime-data.script.ref.png b/test/mime-data.script.ref.png
new file mode 100644
index 0000000..a236b04
Binary files /dev/null and b/test/mime-data.script.ref.png differ


More information about the cairo-commit mailing list