/* * 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, 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 "config.h" /* TODO real path type */ #include "cairo-script-private.h" #if CAIRO_HAS_SCRIPT_SURFACE #include "cairo-script.h" #endif #include /* snprintf */ #include /* mkstemp */ #include #ifdef _MSC_VER #define _USE_MATH_DEFINES /* for M_LN2, M_PI and M_SQRT2 on win32 */ #define snprintf _snprintf #endif #include #include /* INT_MAX */ #include #if HAVE_ZLIB #include #endif #if HAVE_LZO #include #endif #ifdef HAVE_MMAP # ifdef HAVE_UNISTD_H # include # include # else # undef HAVE_MMAP # endif #endif 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 void _csi_blob_hash (csi_blob_t *blob, const uint32_t *data, int len) { unsigned long hash = blob->hash; /* very simple! */ while (len--) { unsigned long c = *data++; hash *= 33; hash ^= c; } blob->hash = 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 != 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 = 5381; 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; int type; obj = _csi_peek_ostack (ctx, i); type = csi_object_get_type (obj); switch (type) { 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; int type; obj = _csi_peek_ostack (ctx, i); type = csi_object_get_type (obj); switch (type) { 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; int type; obj = _csi_peek_ostack (ctx, i); type = csi_object_get_type (obj); switch (type) { 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 double _csi_object_as_real (csi_object_t *obj) { int type; type = csi_object_get_type (obj); switch (type) { 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 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; int type; obj = _csi_peek_ostack (ctx, i); type = csi_object_get_type (obj); switch (type) { case CSI_OBJECT_TYPE_CONTEXT: *out = cairo_get_target (obj->datum.cr); break; case CSI_OBJECT_TYPE_SURFACE: *out = obj->datum.surface; break; default: return _csi_error (CSI_STATUS_INVALID_SCRIPT); } 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; int type; obj = _csi_peek_ostack (ctx, i); type = csi_object_get_type (obj); switch (type) { 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; int type; 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; type = csi_object_get_type (&obj); switch (type) { 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; int type; obj = _csi_peek_ostack (ctx, i); type = csi_object_get_type (obj); switch (type) { 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 (2); name = _csi_peek_ostack (ctx, 1); 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_status_t status; int len = 0; do { check (len + 1); if (csi_object_get_type (_csi_peek_ostack (ctx, len)) == CSI_OBJECT_TYPE_MARK) { break; } len++; } while (TRUE); status = csi_array_new (ctx, len, &obj); if (_csi_unlikely (status)) return status; if (len != 0) { csi_array_t *array; array = obj.datum.array; memcpy (array->stack.objects, _csi_peek_ostack (ctx, len - 1), sizeof (csi_object_t) * len); array->stack.len = len; } ctx->ostack.len -= len + 1; 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 = NULL; /* silence the compiler */ 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; int type; 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); type = csi_object_get_type (a); switch (type) { 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, 0, &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_context_create_func_t hook; csi_proxy_t *proxy; check (1); status = _csi_ostack_get_surface (ctx, 0, &surface); if (_csi_unlikely (status)) return status; hook = ctx->hooks.context_create; if (hook != NULL) cr = hook (ctx->hooks.closure, surface); else 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; int type; check (1); obj = csi_object_reference (_csi_peek_ostack (ctx, 0)); pop (1); type = csi_object_get_type (obj); switch (type) { /*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; int type; check (1); obj = _csi_peek_ostack (ctx, 0); type = csi_object_get_type (obj); switch (type) { 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; csi_object_t *obj; int type; double x1, y1; double x2, y2; double x3, y3; 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; obj = _csi_peek_ostack (ctx, 6); type = csi_object_get_type (obj); switch (type) { case CSI_OBJECT_TYPE_CONTEXT: cairo_curve_to (obj->datum.cr, x1, y1, x2, y2, x3, y3); break; case CSI_OBJECT_TYPE_PATTERN: cairo_mesh_pattern_curve_to (obj->datum.pattern, x1, y1, x2, y2, x3, y3); break; /* XXX handle path object */ } pop (6); return CSI_STATUS_SUCCESS; } static csi_status_t _cvi (csi_t *ctx) { csi_object_t *val, obj; int type; check (1); val = _csi_peek_ostack (ctx, 0); type = csi_object_get_type (val); switch (type) { case CSI_OBJECT_TYPE_INTEGER: return CSI_STATUS_SUCCESS; case CSI_OBJECT_TYPE_REAL: pop (1); return _csi_push_ostack_integer (ctx, val->datum.real); case CSI_OBJECT_TYPE_STRING: if (! _csi_parse_number (&obj, val->datum.string->string, val->datum.string->len)) { return _csi_error (CSI_STATUS_INVALID_SCRIPT); } pop (1); if (csi_object_get_type (&obj) == CSI_OBJECT_TYPE_INTEGER) return push (&obj); else return _csi_push_ostack_integer (ctx, obj.datum.real); default: return _csi_error (CSI_STATUS_INVALID_SCRIPT); } } static csi_status_t _cvr (csi_t *ctx) { csi_object_t *val, obj; int type; check (1); val = _csi_peek_ostack (ctx, 0); type = csi_object_get_type (val); switch (type) { case CSI_OBJECT_TYPE_REAL: return CSI_STATUS_SUCCESS; case CSI_OBJECT_TYPE_INTEGER: pop (1); return _csi_push_ostack_real (ctx, val->datum.integer); case CSI_OBJECT_TYPE_STRING: if (! _csi_parse_number (&obj, val->datum.string->string, val->datum.string->len)) { return _csi_error (CSI_STATUS_INVALID_SCRIPT); } pop (1); if (csi_object_get_type (&obj) == CSI_OBJECT_TYPE_REAL) return push (&obj); else return _csi_push_ostack_real (ctx, obj.datum.integer); default: return _csi_error (CSI_STATUS_INVALID_SCRIPT); } } static csi_status_t _def (csi_t *ctx) { csi_name_t name = 0; /* silence the compiler */ 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 _duplicate (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); v = csi_object_eq (a, b); 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 = NULL; /* silence the compiler */ 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 }, #if HAVE_ZLIB { "deflate", csi_file_new_deflate_decode }, #endif #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 status ? CAIRO_STATUS_USER_FONT_ERROR : CAIRO_STATUS_SUCCESS; } 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; void *bytes; 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 != NULL) { if (--data->source->base.ref == 0) csi_string_free (ctx, data->source); } else { #ifdef HAVE_MMAP munmap (data->blob.bytes, data->blob.len); #endif } if (data->bytes != NULL) _csi_free (ctx, data->bytes); _csi_slab_free (ctx, data, sizeof (*data)); cairo_script_interpreter_destroy (ctx); } struct mmap_vec { const uint8_t *bytes; size_t num_bytes; }; #ifdef HAVE_MMAP /* manual form of swapping for swapless systems like tiny */ static void * _mmap_bytes (const struct mmap_vec *vec, int count) { char template[] = "/tmp/csi-font.XXXXXX"; void *ptr; int fd; int num_bytes; fd = mkstemp (template); if (fd == -1) return MAP_FAILED; unlink (template); num_bytes = 0; while (count--) { const uint8_t *bytes = vec->bytes; size_t len = vec->num_bytes; while (len) { int ret = write (fd, bytes, len); if (ret < 0) { close (fd); return MAP_FAILED; } len -= ret; bytes += ret; } num_bytes += vec->num_bytes; vec++; } ptr = mmap (NULL, num_bytes, PROT_READ, MAP_SHARED, fd, 0); close (fd); return ptr; } #endif static void * inflate_string (csi_t *ctx, csi_string_t *src) { uLongf len; uint8_t *bytes; len = src->deflate; bytes = _csi_alloc (ctx, len + 1); if (bytes == NULL) return NULL; switch (src->method) { default: case NONE: free (bytes); return NULL; case ZLIB: #if HAVE_ZLIB if (uncompress ((Bytef *) bytes, &len, (Bytef *) src->string, src->len) != Z_OK) #endif { _csi_free (ctx, bytes); return NULL; } break; case LZO: #if HAVE_LZO if (lzo2a_decompress ((Bytef *) src->string, src->len, (Bytef *) bytes, &len, NULL)) #endif { _csi_free (ctx, bytes); return NULL; } break; } bytes[len] = '\0'; return bytes; } 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_Error err; cairo_font_face_t *font_face; csi_status_t status; struct mmap_vec vec[2]; int vec_count; void *bytes; int len; /* 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); _csi_blob_hash (&tmpl, (uint32_t *) source->string, source->len / sizeof (uint32_t)); 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); } data = _csi_slab_alloc (ctx, sizeof (*data)); data->bytes = NULL; data->source = source; vec[0].bytes = tmpl.bytes; vec[0].num_bytes = tmpl.len; if (source->deflate) { len = source->deflate; bytes = inflate_string (ctx, source); if (_csi_unlikely (bytes == NULL)) return _csi_error (CSI_STATUS_NO_MEMORY); vec[1].bytes = bytes; vec[1].num_bytes = len; data->bytes = bytes; vec_count = 2; } else { bytes = tmpl.bytes; len = tmpl.len; vec_count = 1; } data->face = NULL; ctx->_faces = _csi_list_prepend (ctx->_faces, &data->blob.list); data->ctx = cairo_script_interpreter_reference (ctx); data->blob.hash = tmpl.hash; data->blob.len = tmpl.len; #ifdef HAVE_MMAP data->blob.bytes = _mmap_bytes (vec, vec_count); if (data->blob.bytes != MAP_FAILED) { if (--source->base.ref == 0) csi_string_free (ctx, source); if (source->deflate) { _csi_free (ctx, bytes); bytes = data->blob.bytes + vec[0].num_bytes; } else bytes = data->blob.bytes; data->source = NULL; data->bytes = NULL; } else { data->blob.bytes = tmpl.bytes; } #else data->blob.bytes = tmpl.bytes; #endif err = FT_New_Memory_Face (_ft_lib, bytes, len, index, &data->face); if (_csi_unlikely (err != FT_Err_Ok)) { _ft_done_face (data); if (err == FT_Err_Out_Of_Memory) return _csi_error (CSI_STATUS_NO_MEMORY); return _csi_error (CSI_STATUS_INVALID_SCRIPT); } font_face = cairo_ft_font_face_create_for_ft_face (data->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) { #if CAIRO_HAS_FC_FONT csi_blob_t tmpl; struct _ft_face_data *data; csi_list_t *link; cairo_font_face_t *font_face; FcPattern *pattern, *resolved; csi_status_t status; struct mmap_vec vec; void *bytes; _csi_blob_init (&tmpl, (uint8_t *) string->string, string->len); _csi_blob_hash (&tmpl, (uint32_t *) string->string, string->len / sizeof (uint32_t)); 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; } if (string->deflate) { bytes = inflate_string (ctx, string); if (_csi_unlikely (bytes == NULL)) return _csi_error (CSI_STATUS_NO_MEMORY); } else { bytes = tmpl.bytes; } pattern = FcNameParse (bytes); if (!pattern) { /* Fontconfig's representation of charset changed mid 2014; * We used to record charset before that. Remove everything * after charset if that's present, and try again. */ char *s = strstr ((char *) bytes, ":charset="); if (s) { *s = '\0'; pattern = FcNameParse (bytes); } } if (bytes != tmpl.bytes) _csi_free (ctx, bytes); retry: resolved = pattern; if (cairo_version () < CAIRO_VERSION_ENCODE (1, 9, 0)) { /* prior to 1.9, you needed to pass a resolved pattern */ resolved = FcFontMatch (NULL, pattern, NULL); if (_csi_unlikely (resolved == NULL)) { FcPatternDestroy (pattern); return _csi_error (CSI_STATUS_NO_MEMORY); } } font_face = cairo_ft_font_face_create_for_pattern (resolved); if (resolved != pattern) FcPatternDestroy (resolved); if (cairo_font_face_status (font_face)) { char *filename = NULL; /* Try a manual fallback process by eliminating specific requests */ if (FcPatternGetString (pattern, FC_FILE, 0, (FcChar8 **) &filename) == FcResultMatch) { FcPatternDel (pattern, FC_FILE); goto retry; } } 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.len = tmpl.len; data->bytes = NULL; data->face = NULL; #ifdef HAVE_MMAP vec.bytes = tmpl.bytes; vec.num_bytes = tmpl.len; data->blob.bytes = _mmap_bytes (&vec, 1); if (data->blob.bytes != MAP_FAILED) { data->source = NULL; if (--string->base.ref == 0) csi_string_free (ctx, string); } else { data->blob.bytes = tmpl.bytes; data->source = string; } #else data->blob.bytes = tmpl.bytes; data->source = string; #endif 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; #else if (--string->base.ref == 0) csi_string_free (ctx, string); return CSI_INT_STATUS_UNSUPPORTED; #endif } 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_unlikely (status)) return status; if (csi_dictionary_has (font, key.datum.name)) { csi_object_t obj; int type; status = csi_dictionary_get (ctx, font, key.datum.name, &obj); if (_csi_unlikely (status)) return status; type = csi_object_get_type (&obj); switch (type) { 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; int type; 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; type = csi_object_get_type (&obj); switch (type) { 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_type42_create(ctx, font, face_out) CSI_INT_STATUS_UNSUPPORTED #endif static char * _fc_strcpy (csi_t *ctx, const char *str) { char *ret; int len; ret = strchr (str, ':'); if (ret != NULL) len = ret - str; else len = strlen (str); ret = _csi_alloc (ctx, len+1); if (_csi_unlikely (ret == NULL)) return NULL; memcpy (ret, str, len); ret[len] = '\0'; return ret; } static cairo_font_face_t * _select_font (const char *name) { cairo_surface_t *surface; cairo_font_face_t *face; cairo_t *cr; /* create a dummy context to choose a font */ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 0, 0); cr = cairo_create (surface); cairo_surface_destroy (surface); cairo_select_font_face (cr, name, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); face = cairo_font_face_reference (cairo_get_font_face (cr)); cairo_destroy (cr); return face; } static csi_status_t _ft_fallback_create_for_pattern (csi_t *ctx, csi_string_t *string, cairo_font_face_t **font_face_out) { char *str, *name; str = string->string; #if 0 name = strstr (str, "fullname="); if (name != NULL) str = name + 9; #endif name = _fc_strcpy (ctx, str); if (_csi_unlikely (name == NULL)) return _csi_error (CSI_STATUS_NO_MEMORY); *font_face_out = _select_font (name); _csi_free (ctx, name); return CSI_STATUS_SUCCESS; } static csi_status_t _ft_type42_fallback_create (csi_t *ctx, csi_dictionary_t *font, cairo_font_face_t **font_face_out) { csi_object_t key; csi_status_t status; /* attempt to select a similar font */ /* two basic sub-types, either an FcPattern or embedded font */ status = csi_name_new_static (ctx, &key, "pattern"); if (_csi_unlikely (status)) return status; if (csi_dictionary_has (font, key.datum.name)) { csi_object_t obj; int type; status = csi_dictionary_get (ctx, font, key.datum.name, &obj); if (_csi_unlikely (status)) return status; type = csi_object_get_type (&obj); switch (type) { 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_fallback_create_for_pattern (ctx, obj.datum.string, font_face_out); } /* XXX: enable the trace to run */ *font_face_out = _select_font ("Sans"); return CSI_STATUS_SUCCESS; } 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 _ft_type42_fallback_create (ctx, font, font_face); } static csi_status_t _font (csi_t *ctx) { csi_dictionary_t *font; csi_status_t status; cairo_font_face_t *font_face = NULL; /* silence the compiler */ 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_unlikely (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_status_t status; csi_object_t *a, *b; int cmp; check (2); b = _csi_peek_ostack (ctx, 0); a = _csi_peek_ostack (ctx, 1); status = csi_object_compare (a, b, &cmp); if (_csi_unlikely (status)) return status; pop (2); return _csi_push_ostack_boolean (ctx, cmp >= 0); } 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; csi_object_t obj; 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; } else if (strcmp ((char *) key, "source") == 0) { obj.type = CSI_OBJECT_TYPE_PATTERN; obj.datum.pattern = cairo_pattern_reference (cairo_get_source (cr)); } else if (strcmp ((char *) key, "target") == 0) { obj.type = CSI_OBJECT_TYPE_SURFACE; obj.datum.surface = cairo_surface_reference (cairo_get_target (cr)); } else if (strcmp ((char *) key, "group-target") == 0) { obj.type = CSI_OBJECT_TYPE_SURFACE; obj.datum.surface = cairo_surface_reference (cairo_get_group_target (cr)); } else if (strcmp ((char *) key, "scaled-font") == 0) { obj.type = CSI_OBJECT_TYPE_SCALED_FONT; obj.datum.scaled_font = cairo_scaled_font_reference (cairo_get_scaled_font (cr)); } else if (strcmp ((char *) key, "font-face") == 0) { obj.type = CSI_OBJECT_TYPE_FONT; obj.datum.font_face = cairo_font_face_reference (cairo_get_font_face (cr)); } else return _proxy_get (cairo_get_user_data (cr, &_csi_proxy_key), key); return push (&obj); } 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; int type; check (2); key = _csi_peek_ostack (ctx, 0); src = _csi_peek_ostack (ctx, 1); pop (1); type = csi_object_get_type (src); switch (type) { 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); } struct glyph_advance_cache { csi_t *ctx; double glyph_advance[256][2]; unsigned long have_glyph_advance[256]; }; static void glyph_advance_cache_destroy (void *closure) { struct glyph_advance_cache *cache = closure; _csi_free (cache->ctx, cache); } static int _glyph_string (csi_t *ctx, csi_array_t *array, cairo_scaled_font_t *scaled_font, cairo_glyph_t *glyphs) { struct glyph_advance_cache uncached; struct glyph_advance_cache *cache; csi_integer_t nglyphs, i, j; double x, y, dx; cairo_status_t status; if (cairo_scaled_font_status (scaled_font)) return 0; cache = cairo_scaled_font_get_user_data (scaled_font, (cairo_user_data_key_t *) ctx); if (cache == NULL) { cache = _csi_alloc (ctx, sizeof (*cache)); if (_csi_likely (cache != NULL)) { cache->ctx = ctx; memset (cache->have_glyph_advance, 0xff, sizeof (cache->have_glyph_advance)); status = cairo_scaled_font_set_user_data (scaled_font, (cairo_user_data_key_t *) ctx, cache, glyph_advance_cache_destroy); if (_csi_unlikely (status)) { _csi_free (ctx, cache); cache = NULL; } } } if (_csi_unlikely (cache == NULL)) { cache = &uncached; cache->ctx = ctx; memset (cache->have_glyph_advance, 0xff, sizeof (cache->have_glyph_advance)); } nglyphs = 0; x = y = 0; for (i = 0; i < array->stack.len; i++) { const csi_object_t *obj = &array->stack.objects[i]; int type = csi_object_get_type (obj); switch (type) { case CSI_OBJECT_TYPE_ARRAY: { const csi_array_t *glyph_array = obj->datum.array; for (j = 0; j < glyph_array->stack.len; j++) { unsigned long g; int gi; 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; gi = g % ARRAY_LENGTH (cache->have_glyph_advance); if (cache->have_glyph_advance[gi] != g) { cairo_text_extents_t extents; cairo_scaled_font_glyph_extents (scaled_font, &glyphs[nglyphs], 1, &extents); cache->glyph_advance[gi][0] = extents.x_advance; cache->glyph_advance[gi][1] = extents.y_advance; cache->have_glyph_advance[gi] = g; } x += cache->glyph_advance[gi][0]; y += cache->glyph_advance[gi][1]; nglyphs++; } break; } case CSI_OBJECT_TYPE_STRING: { const csi_string_t *glyph_string = obj->datum.string; for (j = 0; j < glyph_string->len; j++) { uint8_t g; g = glyph_string->string[j]; glyphs[nglyphs].index = g; glyphs[nglyphs].x = x; glyphs[nglyphs].y = y; if (cache->have_glyph_advance[g] != g) { cairo_text_extents_t extents; cairo_scaled_font_glyph_extents (scaled_font, &glyphs[nglyphs], 1, &extents); cache->glyph_advance[g][0] = extents.x_advance; cache->glyph_advance[g][1] = extents.y_advance; cache->have_glyph_advance[g] = g; } x += cache->glyph_advance[g][0]; y += cache->glyph_advance[g][1]; nglyphs++; } break; } case CSI_OBJECT_TYPE_INTEGER: case CSI_OBJECT_TYPE_REAL: /* dx or x*/ dx = csi_number_get_value (obj); if (i+1 == array->stack.len) break; type = csi_object_get_type (&array->stack.objects[i+1]); switch (type) { case CSI_OBJECT_TYPE_INTEGER: case CSI_OBJECT_TYPE_REAL: /* y */ y = csi_number_get_value (&array->stack.objects[i+1]); x = dx; i++; break; default: x += dx; } } } return nglyphs; } static csi_status_t _glyph_path (csi_t *ctx) { csi_array_t *array; csi_status_t status; cairo_t *cr; cairo_glyph_t stack_glyphs[256], *glyphs; csi_integer_t nglyphs, i; 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++) { csi_object_t *obj = &array->stack.objects[i]; int type = csi_object_get_type (obj); switch (type) { 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 >= INT_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; nglyphs = _glyph_string (ctx, array, cairo_get_scaled_font (cr), glyphs); 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_status_t status; csi_object_t *a, *b; int cmp; check (2); b = _csi_peek_ostack (ctx, 0); a = _csi_peek_ostack (ctx, 1); status = csi_object_compare (a, b, &cmp); if (_csi_unlikely (status)) return status; pop (2); return _csi_push_ostack_boolean (ctx, cmp > 0); } 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 = FALSE; /* silence the compiler */ 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 = FALSE; /* silence the compiler */ 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_t *ctx, csi_object_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, rowlen, instride, stride; cairo_status_t status; if (width == 0 || height == 0) { *image_out = cairo_image_surface_create (format, 0, 0); return CSI_STATUS_SUCCESS; } if (ctx->hooks.create_source_image != NULL) { image = ctx->hooks.create_source_image (ctx->hooks.closure, format, width, height, 0); stride = cairo_image_surface_get_stride (image); data = cairo_image_surface_get_data (image); } else { stride = cairo_format_stride_for_width (format, width); data = malloc (stride * height); if (data == NULL) return CAIRO_STATUS_NO_MEMORY; image = cairo_image_surface_create_for_data (data, format, width, height, stride); status = cairo_surface_set_user_data (image, (const cairo_user_data_key_t *) image, data, free); if (status) { cairo_surface_destroy (image); free (image); return status; } } switch (format) { case CAIRO_FORMAT_A1: instride = rowlen = (width+7)/8; break; case CAIRO_FORMAT_A8: instride = rowlen = width; break; case CAIRO_FORMAT_RGB16_565: instride = rowlen = 2 * width; break; case CAIRO_FORMAT_RGB24: rowlen = 3 * width; instride = 4 *width; break; default: case CAIRO_FORMAT_RGB30: case CAIRO_FORMAT_INVALID: case CAIRO_FORMAT_ARGB32: instride = rowlen = 4 * width; break; } len = rowlen * height; if (rowlen == instride && src->type == CSI_OBJECT_TYPE_STRING && len == src->datum.string->deflate) { csi_string_t *s = src->datum.string; unsigned long out = s->deflate; switch (s->method) { default: case NONE: err_decompress: cairo_surface_destroy (image); return _csi_error (CSI_STATUS_READ_ERROR); case ZLIB: #if HAVE_ZLIB if (uncompress ((Bytef *) data, &out, (Bytef *) s->string, s->len) != Z_OK) #endif goto err_decompress; break; case LZO: #if HAVE_LZO if (lzo2a_decompress ((Bytef *) s->string, s->len, (Bytef *) data, &out, NULL)) #endif goto err_decompress; break; } } else { csi_object_t file; status = csi_object_as_file (ctx, src, &file); if (_csi_unlikely (status)) { cairo_surface_destroy (image); return status; } bp = data; rem = len; while (rem) { ret = csi_file_read (file.datum.file, 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 = rowlen; 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_RGB16_565: for (x = width; x--; ) { #ifdef WORDS_BIGENDIAN row[2*x + 1] = *--bp; row[2*x + 0] = *--bp; #else row[2*x + 0] = *--bp; row[2*x + 1] = *--bp; #endif } 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] = 0xff; #else row[4*x + 0] = *--bp; row[4*x + 1] = *--bp; row[4*x + 2] = *--bp; row[4*x + 3] = 0xff; #endif } break; case CAIRO_FORMAT_RGB30: case CAIRO_FORMAT_INVALID: case CAIRO_FORMAT_ARGB32: /* stride == width */ break; } memset (row + instride, 0, stride - instride); } /* need to treat last row carefully */ switch (format) { case CAIRO_FORMAT_A1: for (x = rowlen; 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_RGB16_565: for (x = width; x--; ) { #ifdef WORDS_BIGENDIAN data[2*x + 1] = *--bp; data[2*x + 0] = *--bp; #else data[2*x + 0] = *--bp; data[2*x + 1] = *--bp; #endif } 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] = 0xff; #else data[4*x + 0] = *--bp; data[4*x + 1] = *--bp; data[4*x + 2] = *--bp; data[4*x + 3] = 0xff; #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] = 0xff; data[5] = rgb[1][2]; data[6] = rgb[1][1]; data[7] = rgb[1][0]; data[0] = 0xff; data[1] = rgb[0][2]; data[2] = rgb[0][1]; data[3] = rgb[0][0]; #else data[7] = 0xff; data[6] = rgb[1][2]; data[5] = rgb[1][1]; data[4] = rgb[1][0]; data[3] = 0xff; data[2] = rgb[0][2]; data[1] = rgb[0][1]; data[0] = rgb[0][0]; #endif } else { #ifdef WORDS_BIGENDIAN data[0] = 0xff; 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] = 0xff; #endif } break; case CAIRO_FORMAT_RGB30: case CAIRO_FORMAT_INVALID: case CAIRO_FORMAT_ARGB32: /* stride == width */ break; } memset (data + instride, 0, stride - instride); } 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_RGB16_565: { uint32_t *rgba = (uint32_t *) data; for (x = len/2; x--; rgba++) { *rgba = bswap_16 (*rgba); } } 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_RGB30: case CAIRO_FORMAT_RGB24: case CAIRO_FORMAT_INVALID: default: break; } #endif } csi_object_free (ctx, &file); } cairo_surface_mark_dirty (image); *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) { #if CAIRO_HAS_PNG_FUNCTIONS *out = cairo_image_surface_create_from_png_stream (png_read_func, src); return cairo_surface_status (*out); #else return CAIRO_STATUS_READ_ERROR; #endif } 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 void _image_hash (csi_blob_t *blob, cairo_surface_t *surface) { uint32_t value; value = cairo_image_surface_get_width (surface); _csi_blob_hash (blob, &value, 1); value = cairo_image_surface_get_height (surface); _csi_blob_hash (blob, &value, 1); value = cairo_image_surface_get_format (surface); _csi_blob_hash (blob, &value, 1); } 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); _image_hash (&tmpl, surface); 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 = NULL; /* silence the compiler */ 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_unlikely (status)) return status; if (csi_dictionary_has (dict, key.datum.name)) { enum mime_type mime_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; mime_type = MIME_TYPE_NONE; if (csi_dictionary_has (dict, key.datum.name)) { csi_object_t type_obj; const char *type_str; int type; status = csi_dictionary_get (ctx, dict, key.datum.name, &type_obj); if (_csi_unlikely (status)) return status; type = csi_object_get_type (&type_obj); switch (type) { 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) mime_type = MIME_TYPE_PNG; } /* XXX hook for general mime-type decoder */ switch (mime_type) { case MIME_TYPE_NONE: status = _image_read_raw (ctx, &obj, format, width, height, &image); break; case MIME_TYPE_PNG: status = csi_object_as_file (ctx, &obj, &file); if (_csi_unlikely (status)) return status; status = _image_read_png (file.datum.file, &image); csi_object_free (ctx, &file); break; } 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 _integer (csi_t *ctx) { csi_object_t *obj; int type; check (1); obj = _csi_peek_ostack (ctx, 0); type = csi_object_get_type (obj); switch (type) { case CSI_OBJECT_TYPE_INTEGER: break; case CSI_OBJECT_TYPE_REAL: obj->datum.integer = obj->datum.real; break; default: return _csi_error (CSI_STATUS_INVALID_SCRIPT); } obj->type = CSI_OBJECT_TYPE_INTEGER; return CSI_STATUS_SUCCESS; } static csi_status_t _invert (csi_t *ctx) { csi_object_t obj; csi_status_t status; cairo_matrix_t m; check (1); status = _csi_ostack_get_matrix (ctx, 0, &m); if (_csi_unlikely (status)) return status; cairo_matrix_invert (&m); status = csi_matrix_new_from_matrix (ctx, &obj, &m); if (_csi_unlikely (status)) return status; pop (1); return push (&obj); } static csi_status_t _le (csi_t *ctx) { csi_status_t status; csi_object_t *a, *b; int cmp; check (2); b = _csi_peek_ostack (ctx, 0); a = _csi_peek_ostack (ctx, 1); status = csi_object_compare (a, b, &cmp); if (_csi_unlikely (status)) return status; pop (2); return _csi_push_ostack_boolean (ctx, cmp <= 0); } 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; csi_object_t *obj; int type; 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; /* XXX path object */ obj = _csi_peek_ostack (ctx, 2); type = csi_object_get_type (obj); switch (type) { case CSI_OBJECT_TYPE_CONTEXT: cairo_line_to (obj->datum.cr, x, y); break; case CSI_OBJECT_TYPE_PATTERN: cairo_mesh_pattern_line_to (obj->datum.pattern, x, y); break; } pop (2); return CSI_STATUS_SUCCESS; } static csi_status_t _lt (csi_t *ctx) { csi_status_t status; csi_object_t *a, *b; int cmp; check (2); b = _csi_peek_ostack (ctx, 0); a = _csi_peek_ostack (ctx, 1); status = csi_object_compare (a, b, &cmp); if (_csi_unlikely (status)) return status; pop (2); return _csi_push_ostack_boolean (ctx, cmp < 0); } static csi_status_t _mark (csi_t *ctx) { return _csi_push_ostack_mark (ctx); } static csi_status_t _ne (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); v = ! csi_object_eq (a, b); pop (2); return _csi_push_ostack_boolean (ctx, v); } static csi_status_t _neg (csi_t *ctx) { csi_object_t *obj; int type; check (1); obj = _csi_peek_ostack (ctx, 0); type = csi_object_get_type (obj); switch (type) { 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; int type; check (1); obj = _csi_peek_ostack (ctx, 0); type = csi_object_get_type (obj); switch (type) { 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 _null (csi_t *ctx) { return _csi_push_ostack_null (ctx); } static csi_status_t _mask (csi_t *ctx) { cairo_t *cr; cairo_pattern_t *pattern = NULL; /* silence the compiler */ 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_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 _map_to_image (csi_t *ctx) { csi_object_t obj; csi_array_t *array; csi_status_t status; cairo_rectangle_int_t extents, *r; cairo_surface_t *surface; check (2); status = _csi_ostack_get_array (ctx, 0, &array); if (_csi_unlikely (status)) return status; status = _csi_ostack_get_surface (ctx, 1, &surface); if (_csi_unlikely (status)) return status; switch (array->stack.len) { case 0: r = NULL; break; case 4: extents.x = floor (_csi_object_as_real (&array->stack.objects[0])); extents.y = floor (_csi_object_as_real (&array->stack.objects[1])); extents.width = ceil (_csi_object_as_real (&array->stack.objects[2])); extents.height = ceil (_csi_object_as_real (&array->stack.objects[3])); r = &extents; break; default: return _csi_error (CSI_STATUS_INVALID_SCRIPT); } obj.type = CSI_OBJECT_TYPE_SURFACE; obj.datum.surface = cairo_surface_reference (cairo_surface_map_to_image (surface, r)); pop (1); return push (&obj); } static csi_status_t _unmap_image (csi_t *ctx) { cairo_surface_t *surface, *image; csi_status_t status; check (2); status = _csi_ostack_get_surface (ctx, 0, &image); if (_csi_unlikely (status)) return status; status = _csi_ostack_get_surface (ctx, 1, &surface); if (_csi_unlikely (status)) return status; cairo_surface_unmap_image (surface, image); pop (1); return CSI_STATUS_SUCCESS; } static csi_status_t _mesh (csi_t *ctx) { csi_object_t obj; obj.type = CSI_OBJECT_TYPE_PATTERN; obj.datum.pattern = cairo_pattern_create_mesh (); return push (&obj); } static csi_status_t _mesh_begin_patch (csi_t *ctx) { csi_status_t status; cairo_pattern_t *pattern = NULL; /* silence the compiler */ check (1); status = _csi_ostack_get_pattern (ctx, 0, &pattern); if (_csi_unlikely (status)) return status; cairo_mesh_pattern_begin_patch (pattern); return CSI_STATUS_SUCCESS; } static csi_status_t _mesh_end_patch (csi_t *ctx) { csi_status_t status; cairo_pattern_t *pattern = NULL; /* silence the compiler */ check (1); status = _csi_ostack_get_pattern (ctx, 0, &pattern); if (_csi_unlikely (status)) return status; cairo_mesh_pattern_end_patch (pattern); return CSI_STATUS_SUCCESS; } static csi_status_t _mesh_set_control_point (csi_t *ctx) { csi_status_t status; double x, y; csi_integer_t point; cairo_pattern_t *pattern = NULL; /* silence the compiler */ check (4); 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_integer (ctx, 2, &point); if (_csi_unlikely (status)) return status; status = _csi_ostack_get_pattern (ctx, 3, &pattern); if (_csi_unlikely (status)) return status; cairo_mesh_pattern_set_control_point (pattern, point, x, y); pop (3); return CSI_STATUS_SUCCESS; } static csi_status_t _mesh_set_corner_color (csi_t *ctx) { csi_status_t status; double r, g, b, a; csi_integer_t corner; cairo_pattern_t *pattern = NULL; /* silence the compiler */ 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_integer (ctx, 4, &corner); if (_csi_unlikely (status)) return status; status = _csi_ostack_get_pattern (ctx, 5, &pattern); if (_csi_unlikely (status)) return status; cairo_mesh_pattern_set_corner_color_rgba (pattern, corner, r, g, b, a); pop (5); return CSI_STATUS_SUCCESS; } static csi_status_t _mod (csi_t *ctx) { csi_integer_t x, y; csi_status_t status; check (2); status = _csi_ostack_get_integer (ctx, 0, &y); if (_csi_unlikely (status)) return status; status = _csi_ostack_get_integer (ctx, 1, &x); if (_csi_unlikely (status)) return status; pop (2); return _csi_push_ostack_integer (ctx, x % y); } static csi_status_t _move_to (csi_t *ctx) { csi_status_t status; csi_object_t *obj; int type; 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); type = csi_object_get_type (obj); switch (type) { case CSI_OBJECT_TYPE_CONTEXT: cairo_move_to (obj->datum.cr, x, y); break; case CSI_OBJECT_TYPE_PATTERN: cairo_mesh_pattern_move_to (obj->datum.pattern, x, y); break; /* XXX path object */ } pop (2); 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; int type; 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); type = csi_object_get_type (a); switch (type) { 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 _repeat (csi_t *ctx) { csi_array_t *proc; csi_integer_t count; csi_status_t status; check (2); status = _csi_ostack_get_procedure (ctx, 0, &proc); if (_csi_unlikely (status)) return status; status = _csi_ostack_get_integer (ctx, 1, &count); if (_csi_unlikely (status)) return status; if (_csi_unlikely (count < 0)) return _csi_error (CSI_STATUS_INVALID_SCRIPT); proc->base.ref++; pop (2); while (count--) { 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 _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; int type; check (2); status = _csi_ostack_get_number (ctx, 0, &theta); if (_csi_unlikely (status)) return status; obj = _csi_peek_ostack (ctx, 1); type = csi_object_get_type (obj); switch (type) { 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; int type; 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); type = csi_object_get_type (obj); switch (type) { 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; cairo_font_face_t *font_face = NULL; /* silence the compiler */ cairo_matrix_t font_matrix, ctm; cairo_font_options_t *options; csi_status_t status; 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; int type; check (3); value = _csi_peek_ostack (ctx, 0); key = _csi_peek_ostack (ctx, 1); dst = _csi_peek_ostack (ctx, 2); type = csi_object_get_type (dst); switch (type) { 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 >= INT_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_device_scale (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_scale (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; int type; check (2); status = _csi_ostack_get_integer (ctx, 0, &extend); if (_csi_unlikely (status)) return status; obj = _csi_peek_ostack (ctx, 1); type = csi_object_get_type (obj); switch (type) { 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; int type; check (2); status = _csi_ostack_get_integer (ctx, 0, &filter); if (_csi_unlikely (status)) return status; obj = _csi_peek_ostack (ctx, 1); type = csi_object_get_type (obj); switch (type) { 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) { cairo_t *cr; cairo_font_face_t *font = NULL; /* silence the compiler */ csi_status_t status; 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; int type; check (2); status = _csi_ostack_get_matrix (ctx, 0, &m); if (_csi_unlikely (status)) return status; obj = _csi_peek_ostack (ctx, 1); type = csi_object_get_type (obj); switch (type) { 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 = NULL; /* silence the compiler */ csi_object_t source; cairo_surface_t *surface; struct _mime_tag *tag; int type; check (3); obj = _csi_peek_ostack (ctx, 0); type = csi_object_get_type (obj); switch (type) { 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) { cairo_t *cr; cairo_scaled_font_t *font = NULL; /* silence the compiler */ csi_status_t status; 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 = NULL; /* silence the compiler */ 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_boolean_t _matching_images (cairo_surface_t *a, cairo_surface_t *b) { cairo_format_t format_a, format_b; if (cairo_surface_get_type (a) != CAIRO_SURFACE_TYPE_IMAGE) return FALSE; if (cairo_surface_get_type (b) != CAIRO_SURFACE_TYPE_IMAGE) return FALSE; if (cairo_image_surface_get_height (a) != cairo_image_surface_get_height (b)) return FALSE; if (cairo_image_surface_get_width (a) != cairo_image_surface_get_width (b)) return FALSE; format_a = cairo_image_surface_get_format (a); if (format_a == CAIRO_FORMAT_RGB24) format_a = CAIRO_FORMAT_ARGB32; format_b = cairo_image_surface_get_format (b); if (format_b == CAIRO_FORMAT_RGB24) format_b = CAIRO_FORMAT_ARGB32; if (format_a != format_b) return FALSE; return TRUE; } static csi_status_t _set_source_image (csi_t *ctx) { csi_status_t status; cairo_surface_t *surface; cairo_surface_t *source; 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; /* Catch the most frequent use of simply uploading pixel data, * principally to remove the pixman ops from the profiles. */ if (_csi_likely (_matching_images (surface, source))) { if (cairo_surface_get_reference_count (surface) == 1 && cairo_surface_get_reference_count (source) == 1) { _csi_peek_ostack (ctx, 0)->datum.surface = surface; _csi_peek_ostack (ctx, 1)->datum.surface = source; } else { cairo_surface_flush (surface); memcpy (cairo_image_surface_get_data (surface), cairo_image_surface_get_data (source), cairo_image_surface_get_height (source) * cairo_image_surface_get_stride (source)); cairo_surface_mark_dirty (surface); } } else { cairo_t *cr; 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_source_rgb (csi_t *ctx) { csi_status_t status; double r,g,b; cairo_t *cr; check (4); 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; status = _csi_ostack_get_context (ctx, 3, &cr); if (_csi_unlikely (status)) return status; cairo_set_source_rgb (cr, r, g, b); pop (3); return CSI_STATUS_SUCCESS; } static csi_status_t _set_source_rgba (csi_t *ctx) { csi_status_t status; double r,g,b,a; cairo_t *cr; check (5); 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_context (ctx, 4, &cr); if (_csi_unlikely (status)) return status; cairo_set_source_rgba (cr, r, g, b, a); pop (4); 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; int type; check (2); status = _csi_ostack_get_matrix (ctx, 0, &m); if (_csi_unlikely (status)) return status; obj = _csi_peek_ostack (ctx, 1); type = csi_object_get_type (obj); switch (type) { 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; int type; 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); type = csi_object_get_type (obj); switch (type) { 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; int type; check (1); obj = _csi_peek_ostack (ctx, 0); type = csi_object_get_type (obj); switch (type) { 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_RGB16_565: 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 _similar_image (csi_t *ctx) { csi_object_t obj; long format; double width, height; cairo_surface_t *other; csi_status_t status; check (4); status = _csi_ostack_get_number (ctx, 0, &height); if (_csi_unlikely (status)) return status; status = _csi_ostack_get_number (ctx, 1, &width); if (_csi_unlikely (status)) return status; status = _csi_ostack_get_integer (ctx, 2, &format); if (_csi_unlikely (status)) return status; status = _csi_ostack_get_surface (ctx, 3, &other); if (_csi_unlikely (status)) return status; obj.type = CSI_OBJECT_TYPE_SURFACE; obj.datum.surface = cairo_surface_create_similar_image (other, format, width, height); pop (4); return push (&obj); } static csi_status_t _subsurface (csi_t *ctx) { csi_object_t obj; double x, y, width, height; cairo_surface_t *target; csi_status_t status; check (5); status = _csi_ostack_get_number (ctx, 0, &height); if (_csi_unlikely (status)) return status; status = _csi_ostack_get_number (ctx, 1, &width); 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_surface (ctx, 4, &target); if (_csi_unlikely (status)) return status; obj.type = CSI_OBJECT_TYPE_SURFACE; obj.datum.surface = cairo_surface_create_for_rectangle (target, x, y, width, height); pop (5); 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_array_t *array; csi_status_t status; cairo_t *cr; cairo_glyph_t stack_glyphs[256], *glyphs; csi_integer_t nglyphs, i; 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++) { csi_object_t *obj = &array->stack.objects[i]; int type = csi_object_get_type (obj); switch (type) { 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 >= INT_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; nglyphs = _glyph_string (ctx, array, cairo_get_scaled_font (cr), glyphs); cairo_show_glyphs (cr, glyphs, nglyphs); 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_status_t status; cairo_t *cr; cairo_text_cluster_t stack_clusters[256], *clusters; cairo_glyph_t stack_glyphs[256], *glyphs; csi_integer_t nglyphs, nclusters, i; long direction; int type; check (5); status = _csi_ostack_get_integer (ctx, 0, &direction); if (_csi_unlikely (status)) return status; obj = _csi_peek_ostack (ctx, 1); type = csi_object_get_type (obj); switch (type) { 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 >= INT_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 >= INT_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]; type = csi_object_get_type (obj); switch (type) { 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 (4); return CSI_STATUS_SUCCESS; } if (nglyphs > ARRAY_LENGTH (stack_glyphs)) { if (_csi_unlikely ((unsigned) nglyphs >= INT_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; nglyphs = _glyph_string (ctx, array, cairo_get_scaled_font (cr), glyphs); cairo_show_text_glyphs (cr, utf8_string->string, utf8_string->len, glyphs, nglyphs, clusters, nclusters, direction); 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; long content; cairo_surface_t *surface; long uid; 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; content = CAIRO_CONTENT_COLOR_ALPHA; status = _csi_dictionary_get_integer (ctx, dict, "content", TRUE, &content); if (_csi_unlikely (status)) return status; uid = 0; status = _csi_dictionary_get_integer (ctx, dict, "uid", TRUE, &uid); if (_csi_unlikely (status)) return status; if (uid == 0) { status = _csi_dictionary_get_integer (ctx, dict, "drawable", TRUE, &uid); if (_csi_unlikely (status)) return status; } hook = ctx->hooks.surface_create; assert (hook != NULL); surface = hook (ctx->hooks.closure, content, width, height, uid); 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_unlikely (status)) { cairo_surface_destroy (surface); 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_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])); } } } status = csi_name_new_static (ctx, &key, "device-scale"); 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_scale (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 _record (csi_t *ctx) { csi_object_t obj; long content; csi_array_t *array; csi_status_t status; cairo_rectangle_t extents; cairo_rectangle_t *r; check (2); status = _csi_ostack_get_array (ctx, 0, &array); if (_csi_unlikely (status)) return status; status = _csi_ostack_get_integer (ctx, 1, &content); if (_csi_unlikely (status)) return status; switch (array->stack.len) { case 0: r = NULL; break; case 2: extents.x = extents.y = 0; extents.width = _csi_object_as_real (&array->stack.objects[0]); extents.height = _csi_object_as_real (&array->stack.objects[1]); r = &extents; break; case 4: extents.x = _csi_object_as_real (&array->stack.objects[0]); extents.y = _csi_object_as_real (&array->stack.objects[1]); extents.width = _csi_object_as_real (&array->stack.objects[2]); extents.height = _csi_object_as_real (&array->stack.objects[3]); r = &extents; break; default: return _csi_error (CSI_STATUS_INVALID_SCRIPT); } obj.type = CSI_OBJECT_TYPE_SURFACE; obj.datum.surface = cairo_recording_surface_create (content, r); pop (2); 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 = 0; /* silence the compiler */ 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 = 0; /* silence the compiler */ csi_status_t status; int type; check (2); status = _csi_ostack_get_name (ctx, 0, &name); if (_csi_unlikely (status)) return status; dst = _csi_peek_ostack (ctx, 1); type = csi_object_get_type (dst); switch (type) { 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; #if CAIRO_HAS_PNG_FUNCTIONS status = cairo_surface_write_to_png (surface, filename->string); if (_csi_unlikely (status)) return status; #else return CAIRO_STATUS_WRITE_ERROR; #endif pop (1); return CSI_STATUS_SUCCESS; } static csi_status_t _write_to_script (csi_t *ctx) { csi_status_t status; csi_string_t *filename; cairo_surface_t *record; check (2); status = _csi_ostack_get_string (ctx, 0, &filename); if (_csi_unlikely (status)) return status; status = _csi_ostack_get_surface (ctx, 1, &record); if (_csi_unlikely (status)) return status; if (cairo_surface_get_type (record) != CAIRO_SURFACE_TYPE_RECORDING) return CAIRO_STATUS_SURFACE_TYPE_MISMATCH; #if CAIRO_HAS_SCRIPT_SURFACE { cairo_device_t *script; script = cairo_script_create (filename->string); status = cairo_script_from_recording_surface (script, record); cairo_device_destroy (script); if (_csi_unlikely (status)) return status; } #else return CAIRO_STATUS_WRITE_ERROR; #endif pop (1); return CSI_STATUS_SUCCESS; } static csi_status_t _xor (csi_t *ctx) { csi_object_t *a, *b; int type; 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); type = csi_object_get_type (a); switch (type) { 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.ptr); 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 }, { "cvi", _cvi }, { "cvr", _cvr }, { "def", _def }, { "device-to-user", NULL }, { "device-to-user-distance", NULL }, { "dict", _dict }, { "div", _div }, { "dup", _duplicate }, { "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 }, { "integer", _integer }, { "invert", _invert }, { "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 }, { "map-to-image", _map_to_image }, { "mark", _mark }, { "mask", _mask }, { "matrix", _matrix }, { "mesh", _mesh }, { "begin-patch", _mesh_begin_patch }, { "end-patch", _mesh_end_patch }, { "set-control-point", _mesh_set_control_point }, { "set-corner-color", _mesh_set_corner_color }, { "mod", _mod }, { "move-to", _move_to }, { "mul", _mul }, { "multiply", NULL }, { "n", _new_path }, { "N", _new_sub_path }, { "ne", _ne }, { "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 }, { "record", _record }, { "rectangle", _rectangle }, { "repeat", _repeat }, { "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-device-scale", _set_device_scale }, { "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-source-rgb", _set_source_rgb }, { "set-source-rgba", _set_source_rgba }, { "set-tolerance", _set_tolerance }, { "show-glyphs", _show_glyphs }, { "show-text", _show_text }, { "show-text-glyphs", _show_text_glyphs }, { "show-page", _show_page }, { "similar", _similar }, { "similar-image", _similar_image }, { "sin", NULL }, { "sqrt", NULL }, { "sub", _sub }, { "subsurface", _subsurface }, { "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 }, { "unmap-image", _unmap_image }, { "unset", _unset }, { "user-to-device", NULL }, { "user-to-device-distance", NULL }, { "where", NULL }, { "write-to-png", _write_to_png }, { "write-to-script", _write_to_script }, { "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 }, { "MULTIPLY", CAIRO_OPERATOR_MULTIPLY }, { "SCREEN", CAIRO_OPERATOR_SCREEN }, { "OVERLAY", CAIRO_OPERATOR_OVERLAY }, { "DARKEN", CAIRO_OPERATOR_DARKEN }, { "LIGHTEN", CAIRO_OPERATOR_LIGHTEN }, { "DODGE", CAIRO_OPERATOR_COLOR_DODGE }, { "BURN", CAIRO_OPERATOR_COLOR_BURN }, { "HARD_LIGHT", CAIRO_OPERATOR_HARD_LIGHT }, { "SOFT_LIGHT", CAIRO_OPERATOR_SOFT_LIGHT }, { "DIFFERENCE", CAIRO_OPERATOR_DIFFERENCE }, { "EXCLUSION", CAIRO_OPERATOR_EXCLUSION }, { "HSL_HUE", CAIRO_OPERATOR_HSL_HUE }, { "HSL_SATURATION", CAIRO_OPERATOR_HSL_SATURATION }, { "HSL_COLOR", CAIRO_OPERATOR_HSL_COLOR }, { "HSL_LUMINOSITY", CAIRO_OPERATOR_HSL_LUMINOSITY }, { "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 }, { "ANTIALIAS_FAST", CAIRO_ANTIALIAS_FAST }, { "ANTIALIAS_GOOD", CAIRO_ANTIALIAS_GOOD }, { "ANTIALIAS_BEST", CAIRO_ANTIALIAS_BEST }, { "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 }, { "RGB16_565", CAIRO_FORMAT_RGB16_565 }, { "RGB24", CAIRO_FORMAT_RGB24 }, { "ARGB32", CAIRO_FORMAT_ARGB32 }, { "INVALID", CAIRO_FORMAT_INVALID }, { NULL, 0 } }; const csi_integer_constant_def_t * _csi_integer_constants (void) { return _integer_constants; } static const csi_real_constant_def_t _real_constants[] = { { "math.pi", M_PI }, { "math.2pi", 2 * M_PI }, { "math.sqrt2", M_SQRT2 }, { "math.ln2", M_LN2 }, { NULL, 0 } }; const csi_real_constant_def_t * _csi_real_constants (void) { return _real_constants; }