diff --git a/configure.ac b/configure.ac index a307493ce..9bc22e1d7 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 498f61616..551797786 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 1bd428587..c4cf085e3 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 42c831c91..f272e4c40 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 000000000..66ad7840f --- /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 000000000..348519f67 --- /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 000000000..c21ef6303 --- /dev/null +++ b/util/cairo-script/cairo-script-file.c @@ -0,0 +1,1018 @@ +/* + * 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 + */ + +#include "cairo-script-private.h" + +#include +#include +#include + +#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 000000000..c1e6bc28a --- /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 + * Graydon Hoare + * Carl Worth + */ + +#include "cairo-script-private.h" + +#include + +/* + * 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 000000000..216304659 --- /dev/null +++ b/util/cairo-script/cairo-script-interpreter.c @@ -0,0 +1,473 @@ +/* + * 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 + */ + +#include + +#include "cairo-script-private.h" + +#include +#include +#include +#include +#include + +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 000000000..21a3a15b2 --- /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 + */ + +#ifndef CAIRO_SCRIPT_INTERPRETER_H +#define CAIRO_SCRIPT_INTERPRETER_H + +#include + +#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 000000000..f4726cefb --- /dev/null +++ b/util/cairo-script/cairo-script-objects.c @@ -0,0 +1,666 @@ +/* + * 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 + */ + +#include "cairo-script-private.h" + +#include + +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 000000000..dc1c208f0 --- /dev/null +++ b/util/cairo-script/cairo-script-operators.c @@ -0,0 +1,5791 @@ +/* + * 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 + */ + +/* TODO real matrix and path types */ + +#include "cairo-script-private.h" + +#include /* snprintf */ +#include +#include +#include +#include + +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 +#include +#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 000000000..b70097708 --- /dev/null +++ b/util/cairo-script/cairo-script-private.h @@ -0,0 +1,853 @@ +/* + * 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 + */ + +#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 +#elif HAVE_INTTYPES_H +# include +#elif HAVE_SYS_INT_TYPES_H +# include +#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 000000000..bc3ff2cfa --- /dev/null +++ b/util/cairo-script/cairo-script-scanner.c @@ -0,0 +1,1180 @@ +/* + * 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 + */ + +#include "cairo-script-private.h" + +#include /* EOF */ +#include /* memset */ +#include /* pow */ +#include /* 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 000000000..bc1d889e6 --- /dev/null +++ b/util/cairo-script/cairo-script-stack.c @@ -0,0 +1,196 @@ +/* + * 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 + */ + +#include "cairo-script-private.h" + +#include + +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; +}