/* * 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 "cairo-script-private.h" #include /* INT_MAX */ #include /* pow */ #include /* EOF */ #include /* for {INT,UINT}*_{MIN,MAX} */ #include /* malloc/free */ #include /* memset */ #include #include #if HAVE_LZO #include #endif #define DEBUG_SCAN 0 #if WORDS_BIGENDIAN #define le16(x) bswap_16 (x) #define le32(x) bswap_32 (x) #define be16(x) x #define be32(x) x #define to_be32(x) x #else #define le16(x) x #define le32(x) x #define be16(x) bswap_16 (x) #define be32(x) bswap_32 (x) #define to_be32(x) bswap_32 (x) #endif /* * whitespace: * 0 - nul * 9 - tab * A - LF * C - FF * D - CR * * syntax delimiters * ( = 28, ) = 29 - literal strings * < = 3C, > = 3E - hex/base85 strings, dictionary name * [ = 5B, ] = 5D - array * { = 7B, } = 7C - procedure * / = 5C - literal marker * % = 25 - comment */ static void fprintf_obj (FILE *stream, csi_t *ctx, const csi_object_t *obj) { switch (csi_object_get_type (obj)) { case CSI_OBJECT_TYPE_NULL: fprintf (stream, "NULL\n"); break; /* atomics */ case CSI_OBJECT_TYPE_BOOLEAN: fprintf (stream, "boolean: %s\n", obj->datum.boolean ? "true" : "false"); break; case CSI_OBJECT_TYPE_INTEGER: fprintf (stream, "integer: %ld\n", obj->datum.integer); break; case CSI_OBJECT_TYPE_MARK: fprintf (stream, "mark\n"); break; case CSI_OBJECT_TYPE_NAME: fprintf (stream, "name: %s\n", (char *) obj->datum.name); break; case CSI_OBJECT_TYPE_OPERATOR: fprintf (stream, "operator: %p\n", obj->datum.ptr); break; case CSI_OBJECT_TYPE_REAL: fprintf (stream, "real: %g\n", obj->datum.real); break; /* compound */ case CSI_OBJECT_TYPE_ARRAY: fprintf (stream, "array\n"); break; case CSI_OBJECT_TYPE_DICTIONARY: fprintf (stream, "dictionary\n"); break; case CSI_OBJECT_TYPE_FILE: fprintf (stream, "file\n"); break; case CSI_OBJECT_TYPE_MATRIX: fprintf (stream, "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 (stream, "string: len=%ld, defate=%ld, method=%d\n", obj->datum.string->len, obj->datum.string->deflate, obj->datum.string->method); break; /* cairo */ case CSI_OBJECT_TYPE_CONTEXT: fprintf (stream, "context\n"); break; case CSI_OBJECT_TYPE_FONT: fprintf (stream, "font\n"); break; case CSI_OBJECT_TYPE_PATTERN: fprintf (stream, "pattern\n"); break; case CSI_OBJECT_TYPE_SCALED_FONT: fprintf (stream, "scaled-font\n"); break; case CSI_OBJECT_TYPE_SURFACE: fprintf (stream, "surface\n"); break; } } /* takes ownership of obj */ static inline csi_status_t scan_push (csi_t *ctx, csi_object_t *obj) { return ctx->scanner.push (ctx, obj); } static inline csi_status_t scan_execute (csi_t *ctx, csi_object_t *obj) { return ctx->scanner.execute (ctx, obj); } static cairo_status_t buffer_init (csi_t *ctx, csi_buffer_t *buffer) { cairo_status_t status = CSI_STATUS_SUCCESS; buffer->size = 16384; buffer->base = _csi_alloc (ctx, buffer->size); if (_csi_unlikely (buffer->base == NULL)) { status = _csi_error (CSI_STATUS_NO_MEMORY); buffer->size = 0; } buffer->ptr = buffer->base; buffer->end = buffer->base + buffer->size; return status; } static void buffer_fini (csi_t *ctx, csi_buffer_t *buffer) { _csi_free (ctx, buffer->base); } static void _buffer_grow (csi_t *ctx, csi_scanner_t *scan) { int newsize; int offset; char *base; if (_csi_unlikely (scan->buffer.size > INT_MAX / 2)) longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_NO_MEMORY)); offset = scan->buffer.ptr - scan->buffer.base; newsize = scan->buffer.size * 2; base = _csi_realloc (ctx, scan->buffer.base, newsize); if (_csi_unlikely (base == NULL)) longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_NO_MEMORY)); scan->buffer.base = base; scan->buffer.ptr = base + offset; scan->buffer.end = base + newsize; scan->buffer.size = newsize; } static inline void buffer_check (csi_t *ctx, csi_scanner_t *scan, int count) { if (_csi_unlikely (scan->buffer.ptr + count > scan->buffer.end)) _buffer_grow (ctx, scan); } static inline void buffer_add (csi_buffer_t *buffer, int c) { *buffer->ptr++ = c; } static inline void buffer_reset (csi_buffer_t *buffer) { buffer->ptr = buffer->base; } static void token_start (csi_scanner_t *scan) { buffer_reset (&scan->buffer); } static void token_add (csi_t *ctx, csi_scanner_t *scan, int c) { buffer_check (ctx, scan, 1); buffer_add (&scan->buffer, c); } static void token_add_unchecked (csi_scanner_t *scan, int c) { buffer_add (&scan->buffer, c); } csi_boolean_t _csi_parse_number (csi_object_t *obj, const char *s, int len) { int radix = 0; long long mantissa = 0; int exponent = 0; int sign = 1; int decimal = -1; int exponent_sign = 0; const char * const end = s + len; switch (*s) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': mantissa = *s - '0'; case '+': break; case '-': sign = -1; break; case '.': decimal = 0; break; default: return FALSE; } while (++s < end) { if (*s < '0') { if (*s == '.') { if (_csi_unlikely (radix)) return FALSE; if (_csi_unlikely (decimal != -1)) return FALSE; if (_csi_unlikely (exponent_sign)) return FALSE; decimal = 0; } else if (*s == '!') { if (_csi_unlikely (radix)) return FALSE; if (_csi_unlikely (decimal != -1)) return FALSE; if (_csi_unlikely (exponent_sign)) return FALSE; radix = mantissa; mantissa = 0; if (_csi_unlikely (radix < 2 || radix > 36)) return FALSE; } else return FALSE; } else if (*s <= '9') { int v = *s - '0'; if (_csi_unlikely (radix && v >= radix)) return FALSE; if (exponent_sign) { exponent = 10 * exponent + v; } else { if (radix) mantissa = radix * mantissa + v; else mantissa = 10 * mantissa + v; if (decimal != -1) decimal++; } } else if (*s == 'E' || * s== 'e') { if (radix == 0) { if (_csi_unlikely (s + 1 == end)) return FALSE; exponent_sign = 1; if (s[1] == '-') { exponent_sign = -1; s++; } else if (s[1] == '+') s++; } else { int v = 0xe; if (_csi_unlikely (v >= radix)) return FALSE; mantissa = radix * mantissa + v; } } else if (*s < 'A') { return FALSE; } else if (*s <= 'Z') { int v = *s - 'A' + 0xA; if (_csi_unlikely (v >= radix)) return FALSE; mantissa = radix * mantissa + v; } else if (*s < 'a') { return FALSE; } else if (*s <= 'z') { int v = *s - 'a' + 0xa; if (_csi_unlikely (v >= radix)) return FALSE; mantissa = radix * mantissa + v; } else return FALSE; } if (exponent_sign || decimal != -1) { if (mantissa == 0) { obj->type = CSI_OBJECT_TYPE_REAL; obj->datum.real = 0.; return TRUE; } else { int e; double v; v = mantissa; e = exponent * exponent_sign; if (decimal != -1) e -= decimal; switch (e) { case -7: v *= 0.0000001; break; case -6: v *= 0.000001; break; case -5: v *= 0.00001; break; case -4: v *= 0.0001; break; case -3: v *= 0.001; break; case -2: v *= 0.01; break; case -1: v *= 0.1; break; case 0: break; case 1: v *= 10; break; case 2: v *= 100; break; case 3: v *= 1000; break; case 4: v *= 10000; break; case 5: v *= 100000; break; case 6: v *= 1000000; break; default: v *= pow (10, e); /* XXX */ break; } obj->type = CSI_OBJECT_TYPE_REAL; obj->datum.real = sign * v; return TRUE; } } else { obj->type = CSI_OBJECT_TYPE_INTEGER; obj->datum.integer = sign * mantissa; return TRUE; } } static void token_end (csi_t *ctx, csi_scanner_t *scan, csi_file_t *src) { cairo_status_t status; char *s; csi_object_t obj; int len; /* * Any token that consists entirely of regular characters and * cannot be interpreted as a number is treated as a name object * (more precisely, an executable name). All characters except * delimiters and white-space characters can appear in names, * including characters ordinarily considered to be punctuation. */ if (_csi_unlikely (scan->buffer.ptr == scan->buffer.base)) return; s = scan->buffer.base; len = scan->buffer.ptr - scan->buffer.base; if (_csi_likely (! scan->bind)) { if (s[0] == '{') { /* special case procedures */ if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL) { status = _csi_stack_push (ctx, &scan->procedure_stack, &scan->build_procedure); if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); } status = csi_array_new (ctx, 0, &scan->build_procedure); if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); scan->build_procedure.type |= CSI_OBJECT_ATTR_EXECUTABLE; return; } else if (s[0] == '}') { if (_csi_unlikely (scan->build_procedure.type == CSI_OBJECT_TYPE_NULL)) { longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT)); } if (scan->procedure_stack.len) { csi_object_t *next; next = _csi_stack_peek (&scan->procedure_stack, 0); if (next == NULL) { longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_NULL_POINTER)); return; } status = csi_array_append (ctx, next->datum.array, &scan->build_procedure); scan->build_procedure = *next; scan->procedure_stack.len--; } else { status = scan_push (ctx, &scan->build_procedure); scan->build_procedure.type = CSI_OBJECT_TYPE_NULL; } if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); return; } } if (s[0] == '/') { if (len >= 2 && s[1] == '/') { /* substituted name */ status = csi_name_new (ctx, &obj, s + 2, len - 2); if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); status = _csi_name_lookup (ctx, obj.datum.name, &obj); } else { /* literal name */ status = csi_name_new (ctx, &obj, s + 1, len - 1); } if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); } else { if (! _csi_parse_number (&obj, s, len)) { status = csi_name_new (ctx, &obj, s, len); if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); obj.type |= CSI_OBJECT_ATTR_EXECUTABLE; } } /* consume whitespace after token, before calling the interpreter */ if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL) { status = csi_array_append (ctx, scan->build_procedure.datum.array, &obj); } else if (obj.type & CSI_OBJECT_ATTR_EXECUTABLE) { status = scan_execute (ctx, &obj); csi_object_free (ctx, &obj); } else { status = scan_push (ctx, &obj); } if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); } static void string_add (csi_t *ctx, csi_scanner_t *scan, int c) { buffer_check (ctx, scan, 1); buffer_add (&scan->buffer, c); } static void string_end (csi_t *ctx, csi_scanner_t *scan) { csi_object_t obj; cairo_status_t status; status = csi_string_new (ctx, &obj, scan->buffer.base, scan->buffer.ptr - scan->buffer.base); if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL) status = csi_array_append (ctx, scan->build_procedure.datum.array, &obj); else status = scan_push (ctx, &obj); if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); } static int hex_value (int c) { if (c < '0') return EOF; if (c <= '9') return c - '0'; c |= 32; if (c < 'a') return EOF; if (c <= 'f') return c - 'a' + 0xa; return EOF; } static void hex_add (csi_t *ctx, csi_scanner_t *scan, int c) { if (scan->accumulator_count == 0) { scan->accumulator |= hex_value (c) << 4; scan->accumulator_count = 1; } else { scan->accumulator |= hex_value (c) << 0; buffer_check (ctx, scan, 1); buffer_add (&scan->buffer, scan->accumulator); scan->accumulator = 0; scan->accumulator_count = 0; } } static void hex_end (csi_t *ctx, csi_scanner_t *scan) { csi_object_t obj; cairo_status_t status; if (scan->accumulator_count) hex_add (ctx, scan, '0'); status = csi_string_new (ctx, &obj, scan->buffer.base, scan->buffer.ptr - scan->buffer.base); if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL) status = csi_array_append (ctx, scan->build_procedure.datum.array, &obj); else status = scan_push (ctx, &obj); if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); } static void base85_add (csi_t *ctx, csi_scanner_t *scan, int c) { if (c == 'z') { if (_csi_unlikely (scan->accumulator_count != 0)) longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT)); buffer_check (ctx, scan, 4); buffer_add (&scan->buffer, 0); buffer_add (&scan->buffer, 0); buffer_add (&scan->buffer, 0); buffer_add (&scan->buffer, 0); } else if (_csi_unlikely (c < '!' || c > 'u')) { longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT)); } else { scan->accumulator = scan->accumulator*85 + c - '!'; if (++scan->accumulator_count == 5) { buffer_check (ctx, scan, 4); buffer_add (&scan->buffer, (scan->accumulator >> 24) & 0xff); buffer_add (&scan->buffer, (scan->accumulator >> 16) & 0xff); buffer_add (&scan->buffer, (scan->accumulator >> 8) & 0xff); buffer_add (&scan->buffer, (scan->accumulator >> 0) & 0xff); scan->accumulator = 0; scan->accumulator_count = 0; } } } static void base85_end (csi_t *ctx, csi_scanner_t *scan, cairo_bool_t deflate) { csi_object_t obj; cairo_status_t status; buffer_check (ctx, scan, 4); switch (scan->accumulator_count) { case 0: break; case 1: longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT)); break; case 2: scan->accumulator = scan->accumulator * (85*85*85) + 85*85*85 -1; buffer_add (&scan->buffer, (scan->accumulator >> 24) & 0xff); break; case 3: scan->accumulator = scan->accumulator * (85*85) + 85*85 -1; buffer_add (&scan->buffer, (scan->accumulator >> 24) & 0xff); buffer_add (&scan->buffer, (scan->accumulator >> 16) & 0xff); break; case 4: scan->accumulator = scan->accumulator * 85 + 84; buffer_add (&scan->buffer, (scan->accumulator >> 24) & 0xff); buffer_add (&scan->buffer, (scan->accumulator >> 16) & 0xff); buffer_add (&scan->buffer, (scan->accumulator >> 8) & 0xff); break; } if (deflate) { uLongf len = be32 (*(uint32_t *) scan->buffer.base); Bytef *source = (Bytef *) (scan->buffer.base + sizeof (uint32_t)); status = csi_string_deflate_new (ctx, &obj, source, (Bytef *) scan->buffer.ptr - source, len); if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); } else { status = csi_string_new (ctx, &obj, scan->buffer.base, scan->buffer.ptr - scan->buffer.base); if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); } if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL) status = csi_array_append (ctx, scan->build_procedure.datum.array, &obj); else status = scan_push (ctx, &obj); if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); } static void base64_add (csi_t *ctx, csi_scanner_t *scan, int c) { int val; /* Convert Base64 character to its 6 bit nibble */ val = scan->accumulator; if (c =='/') { val = (val << 6) | 63; } else if (c =='+') { val = (val << 6) | 62; } else if (c >='A' && c <='Z') { val = (val << 6) | (c -'A'); } else if (c >='a' && c <='z') { val = (val << 6) | (c -'a' + 26); } else if (c >='0' && c <='9') { val = (val << 6) | (c -'0' + 52); } buffer_check (ctx, scan, 1); switch (scan->accumulator_count++) { case 0: break; case 1: buffer_add (&scan->buffer, (val >> 4) & 0xFF); val &= 0xF; break; case 2: buffer_add (&scan->buffer, (val >> 2) & 0xFF); val &= 0x3; break; case 3: buffer_add (&scan->buffer, (val >> 0) & 0xFF); scan->accumulator_count = 0; val = 0; break; } if (c == '=') { scan->accumulator_count = 0; scan->accumulator = 0; } else { scan->accumulator = val; } } static void base64_end (csi_t *ctx, csi_scanner_t *scan) { csi_object_t obj; cairo_status_t status; switch (scan->accumulator_count) { case 0: break; case 2: base64_add (ctx, scan, (scan->accumulator << 2) & 0x3f); base64_add (ctx, scan, '='); break; case 1: base64_add (ctx, scan, (scan->accumulator << 4) & 0x3f); base64_add (ctx, scan, '='); base64_add (ctx, scan, '='); break; } status = csi_string_new (ctx, &obj, scan->buffer.base, scan->buffer.ptr - scan->buffer.base); if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL) status = csi_array_append (ctx, scan->build_procedure.datum.array, &obj); else status = scan_push (ctx, &obj); if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); } static void scan_read (csi_scanner_t *scan, csi_file_t *src, void *ptr, int len) { uint8_t *data = ptr; do { int ret = csi_file_read (src, data, len); if (_csi_unlikely (ret == 0)) longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_READ_ERROR)); data += ret; len -= ret; } while (_csi_unlikely (len)); } static void string_read (csi_t *ctx, csi_scanner_t *scan, csi_file_t *src, int len, int compressed, csi_object_t *obj) { csi_status_t status; status = csi_string_new (ctx, obj, NULL, len); if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); if (compressed) { uint32_t u32; scan_read (scan, src, &u32, 4); obj->datum.string->deflate = be32 (u32); obj->datum.string->method = compressed; } if (_csi_likely (len)) scan_read (scan, src, obj->datum.string->string, len); obj->datum.string->string[len] = '\0'; } static void _scan_file (csi_t *ctx, csi_file_t *src) { csi_scanner_t *scan = &ctx->scanner; int c, next; union { int8_t i8; uint8_t u8; int16_t i16; uint16_t u16; int32_t i32; uint32_t u32; float f; } u; int deflate = 0; int string_p; scan_none: while ((c = csi_file_getc (src)) != EOF) { csi_object_t obj = { CSI_OBJECT_TYPE_NULL }; switch (c) { case 0xa: scan->line_number++; case 0x0: case 0x9: case 0xc: case 0xd: case 0x20: /* ignore whitespace */ break; case '%': goto scan_comment; case '(': goto scan_string; case '[': /* needs special case */ case ']': case '{': case '}': token_start (scan); token_add_unchecked (scan, c); token_end (ctx, scan, src); goto scan_none; case '<': next = csi_file_getc (src); switch (next) { case EOF: csi_file_putc (src, '<'); return; case '<': /* dictionary name */ token_start (scan); token_add_unchecked (scan, '<'); token_add_unchecked (scan, '<'); token_end (ctx, scan, src); goto scan_none; case '|': deflate = 1; case '~': goto scan_base85; case '{': goto scan_base64; default: csi_file_putc (src, next); goto scan_hex; } break; /* binary token */ #define MSB_INT8 128 #define MSB_UINT8 129 #define MSB_INT16 130 #define MSB_UINT16 131 #define MSB_INT32 132 #define LSB_INT8 MSB_INT8 #define LSB_UINT8 MSB_UINT8 #define LSB_INT16 133 #define LSB_UINT16 134 #define LSB_INT32 135 #define MSB_FLOAT32 140 #define LSB_FLOAT32 141 case MSB_INT8: scan_read (scan, src, &u.i8, 1); csi_integer_new (&obj, u.i8); break; case MSB_UINT8: scan_read (scan, src, &u.u8, 1); csi_integer_new (&obj, u.u8); break; case MSB_INT16: scan_read (scan, src, &u.i16, 2); csi_integer_new (&obj, be16 (u.i16)); break; case LSB_INT16: scan_read (scan, src, &u.i16, 2); csi_integer_new (&obj, le16 (u.i16)); break; case MSB_UINT16: scan_read (scan, src, &u.u16, 2); csi_integer_new (&obj, be16 (u.u16)); break; case LSB_UINT16: scan_read (scan, src, &u.u16, 2); csi_integer_new (&obj, le16 (u.u16)); break; case MSB_INT32: scan_read (scan, src, &u.i32, 4); csi_integer_new (&obj, be32 (u.i32)); break; case LSB_INT32: scan_read (scan, src, &u.i32, 4); csi_integer_new (&obj, le32 (u.i32)); break; case 136: /* 16.16 msb */ scan_read (scan, src, &u.i32, 4); csi_real_new (&obj, be32 (u.i32) / 65536.); break; case 137: /* 16.16 lsb */ scan_read (scan, src, &u.i32, 4); csi_real_new (&obj, le32 (u.i32) / 65536.); break; case 138: /* 24.8 msb */ scan_read (scan, src, &u.i32, 4); csi_real_new (&obj, be32 (u.i32) / 256.); break; case 139: /* 24.8 lsb */ scan_read (scan, src, &u.i32, 4); csi_real_new (&obj, le32 (u.i32) / 256.); break; case MSB_FLOAT32: scan_read (scan, src, &u.i32, 4); u.i32 = be32 (u.i32); csi_real_new (&obj, u.f); break; case LSB_FLOAT32: scan_read (scan, src, &u.i32, 4); u.i32 = le32 (u.i32); csi_real_new (&obj, u.f); break; #define STRING_1 142 #define STRING_2_MSB 144 #define STRING_2_LSB 146 #define STRING_4_MSB 148 #define STRING_4_LSB 150 #define STRING_DEFLATE 1 case STRING_1: case STRING_1 | STRING_DEFLATE: scan_read (scan, src, &u.u8, 1); string_read (ctx, scan, src, u.u8, c & STRING_DEFLATE, &obj); break; case STRING_2_MSB: case STRING_2_MSB | STRING_DEFLATE: scan_read (scan, src, &u.u16, 2); string_read (ctx, scan, src, be16 (u.u16), c & STRING_DEFLATE, &obj); break; case STRING_2_LSB: case STRING_2_LSB | STRING_DEFLATE: scan_read (scan, src, &u.u16, 2); string_read (ctx, scan, src, le16 (u.u16), c & STRING_DEFLATE, &obj); break; case STRING_4_MSB: case STRING_4_MSB | STRING_DEFLATE: scan_read (scan, src, &u.u32, 4); string_read (ctx, scan, src, be32 (u.u32), c & STRING_DEFLATE, &obj); break; case STRING_4_LSB: case STRING_4_LSB | STRING_DEFLATE: scan_read (scan, src, &u.u32, 4); string_read (ctx, scan, src, le32 (u.u32), c & STRING_DEFLATE, &obj); break; #define OPCODE 152 case OPCODE: scan_read (scan, src, &u.u8, 1); csi_operator_new (&obj, ctx->opcode[u.u8]); break; case OPCODE | 1: scan_read (scan, src, &u.u8, 1); csi_operator_new (&obj, ctx->opcode[u.u8]); obj.type &= ~CSI_OBJECT_ATTR_EXECUTABLE; break; #define STRING_LZO 154 case STRING_LZO: scan_read (scan, src, &u.u32, 4); string_read (ctx, scan, src, be32 (u.u32), LZO, &obj); break; /* unassigned */ case 155: case 156: case 157: case 158: case 159: longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT)); case '#': /* PDF 1.2 escape code */ { int c_hi = csi_file_getc (src); int c_lo = csi_file_getc (src); c = (hex_value (c_hi) << 4) | hex_value (c_lo); } /* fall-through */ default: token_start (scan); token_add_unchecked (scan, c); goto scan_token; } if (obj.type != CSI_OBJECT_TYPE_NULL) { cairo_status_t status; if (scan->build_procedure.type != CSI_OBJECT_TYPE_NULL) { status = csi_array_append (ctx, scan->build_procedure.datum.array, &obj); } else if (obj.type & CSI_OBJECT_ATTR_EXECUTABLE) { status = scan_execute (ctx, &obj); csi_object_free (ctx, &obj); } else { status = scan_push (ctx, &obj); } if (_csi_unlikely (status)) longjmp (scan->jmpbuf, status); } } return; scan_token: while ((c = csi_file_getc (src)) != EOF) { switch (c) { case 0xa: scan->line_number++; case 0x0: case 0x9: case 0xc: case 0xd: case 0x20: token_end (ctx, scan, src); goto scan_none; /* syntax delimiters */ case '%': token_end (ctx, scan, src); goto scan_comment; /* syntax error? */ case '(': token_end (ctx, scan, src); goto scan_string; /* XXX syntax error? */ case ')': token_end (ctx, scan, src); goto scan_none; case '/': /* need to special case '^//?' */ if (scan->buffer.ptr > scan->buffer.base+1 || scan->buffer.base[0] != '/') { token_end (ctx, scan, src); token_start (scan); } token_add_unchecked (scan, '/'); goto scan_token; case '{': case '}': case ']': token_end (ctx, scan, src); token_start (scan); token_add_unchecked (scan, c); token_end (ctx, scan, src); goto scan_none; case '<': csi_file_putc (src, '<'); token_end (ctx, scan, src); goto scan_none; case '#': /* PDF 1.2 escape code */ { int c_hi = csi_file_getc (src); int c_lo = csi_file_getc (src); c = (hex_value (c_hi) << 4) | hex_value (c_lo); } /* fall-through */ default: token_add (ctx, scan, c); break; } } token_end (ctx, scan, src); return; scan_comment: /* discard until newline */ while ((c = csi_file_getc (src)) != EOF) { switch (c) { case 0xa: scan->line_number++; case 0xc: goto scan_none; } } return; scan_string: buffer_reset (&scan->buffer); string_p = 1; while ((c = csi_file_getc (src)) != EOF) { switch (c) { case '\\': /* escape */ next = csi_file_getc (src); switch (next) { case EOF: longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT)); case 'n': string_add (ctx, scan, '\n'); break; case 'r': string_add (ctx, scan, '\r'); break; case 't': string_add (ctx, scan, '\t'); break; case 'b': string_add (ctx, scan, '\b'); break; case 'f': string_add (ctx, scan, '\f'); break; case '\\': string_add (ctx, scan, '\\'); break; case '(': string_add (ctx, scan, '('); break; case ')': string_add (ctx, scan, ')'); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { /* octal code: \d{1,3} */ int i; c = next - '0'; for (i = 0; i < 2; i++) { next = csi_file_getc (src); switch (next) { case EOF: return; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': c = 8*c + next-'0'; break; default: csi_file_putc (src, next); goto octal_code_done; } } octal_code_done: string_add (ctx, scan, c); } break; case 0xa: /* skip the newline */ next = csi_file_getc (src); /* might be compound LFCR */ switch (next) { case EOF: return; case 0xc: break; default: csi_file_putc (src, next); break; } scan->line_number++; break; case 0xc: break; default: /* ignore the '\' */ break; } break; case '(': string_p++; string_add (ctx, scan, c); break; case ')': if (--string_p == 0) { string_end (ctx, scan); goto scan_none; } /* fall through */ default: string_add (ctx, scan, c); break; } } longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT)); scan_hex: buffer_reset (&scan->buffer); scan->accumulator_count = 0; scan->accumulator = 0; while ((c = csi_file_getc (src)) != EOF) { switch (c) { case 0xa: scan->line_number++; case 0x0: case 0x9: case 0xc: case 0xd: case 0x20: /* ignore whitespace */ break; case '>': hex_end (ctx, scan); /* fixup odd digit with '0' */ goto scan_none; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': hex_add (ctx, scan, c); break; default: longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT)); } } longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT)); scan_base85: buffer_reset (&scan->buffer); scan->accumulator = 0; scan->accumulator_count = 0; while ((c = csi_file_getc (src)) != EOF) { switch (c) { case '~': next = csi_file_getc (src); switch (next) { case EOF: return; case '>': base85_end (ctx, scan, deflate); deflate = 0; goto scan_none; } csi_file_putc (src, next); /* fall-through */ default: base85_add (ctx, scan, c); break; } } longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT)); scan_base64: buffer_reset (&scan->buffer); scan->accumulator = 0; scan->accumulator_count = 0; while ((c = csi_file_getc (src)) != EOF) { switch (c) { case '}': next = csi_file_getc (src); switch (next) { case EOF: return; case '>': base64_end (ctx, scan); goto scan_none; } longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT)); break; default: base64_add (ctx, scan, c); break; } } longjmp (scan->jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT)); } static csi_status_t _scan_push (csi_t *ctx, csi_object_t *obj) { if (DEBUG_SCAN) { fprintf (stderr, "push "); fprintf_obj (stderr, ctx, obj); } return _csi_push_ostack (ctx, obj); } static csi_status_t _scan_execute (csi_t *ctx, csi_object_t *obj) { if (DEBUG_SCAN) { fprintf (stderr, "exec "); fprintf_obj (stderr, ctx, obj); } return csi_object_execute (ctx, obj); } csi_status_t _csi_scanner_init (csi_t *ctx, csi_scanner_t *scanner) { csi_status_t status; memset (scanner, 0, sizeof (csi_scanner_t)); status = buffer_init (ctx, &scanner->buffer); if (status) return status; status = _csi_stack_init (ctx, &scanner->procedure_stack, 4); if (status) return status; scanner->bind = 0; scanner->push = _scan_push; scanner->execute = _scan_execute; return CSI_STATUS_SUCCESS; } void _csi_scanner_fini (csi_t *ctx, csi_scanner_t *scanner) { buffer_fini (ctx, &scanner->buffer); _csi_stack_fini (ctx, &scanner->procedure_stack); if (scanner->build_procedure.type != CSI_OBJECT_TYPE_NULL) csi_object_free (ctx, &scanner->build_procedure); } csi_status_t _csi_scan_file (csi_t *ctx, csi_file_t *src) { csi_status_t status; int old_line_number; /* This function needs to be reentrant to handle recursive scanners. * i.e. one script executes a second. */ if (ctx->scanner.depth++ == 0) { if ((status = setjmp (ctx->scanner.jmpbuf))) { ctx->scanner.depth = 0; return status; } } old_line_number = ctx->scanner.line_number; ctx->scanner.line_number = 0; _scan_file (ctx, src); ctx->scanner.line_number = old_line_number; --ctx->scanner.depth; return CSI_STATUS_SUCCESS; } struct _translate_closure { csi_dictionary_t *opcodes; cairo_write_func_t write_func; void *closure; }; static csi_status_t _translate_name (csi_t *ctx, csi_name_t name, csi_boolean_t executable, struct _translate_closure *closure) { if (executable) { csi_dictionary_entry_t *entry; uint16_t u16; /* Bind executable names. * XXX This may break some scripts that overload system operators. */ entry = _csi_hash_table_lookup (&closure->opcodes->hash_table, (csi_hash_entry_t *) &name); if (entry == NULL) goto STRING; u16 = entry->value.datum.integer; u16 = be16 (u16); closure->write_func (closure->closure, (unsigned char *) &u16, 2); } else { closure->write_func (closure->closure, (unsigned char *) "/", 1); STRING: closure->write_func (closure->closure, (unsigned char *) name, strlen ((char *) name)); closure->write_func (closure->closure, (unsigned char *) "\n", 1); } return CSI_STATUS_SUCCESS; } static csi_status_t _translate_operator (csi_t *ctx, csi_operator_t op, csi_boolean_t executable, struct _translate_closure *closure) { csi_dictionary_entry_t *entry; uint16_t u16; entry = _csi_hash_table_lookup (&closure->opcodes->hash_table, (csi_hash_entry_t *) &op); if (entry == NULL) return _csi_error (CSI_STATUS_INVALID_SCRIPT); u16 = entry->value.datum.integer; if (! executable) u16 += 1 << 8; u16 = be16 (u16); closure->write_func (closure->closure, (unsigned char *) &u16, 2); return CSI_STATUS_SUCCESS; } static csi_status_t _translate_integer (csi_t *ctx, csi_integer_t i, struct _translate_closure *closure) { uint8_t hdr; union { int8_t i8; uint8_t u8; int16_t i16; uint16_t u16; int32_t i32; uint32_t u32; } u; int len; #if WORDS_BIGENDIAN if (i < INT16_MIN) { hdr = MSB_INT32; len = 4; u.i32 = i; } else if (i < INT8_MIN) { hdr = MSB_INT16; len = 2; u.i16 = i; } else if (i < 0) { hdr = MSB_INT8; len = 1; u.i8 = i; } else if (i <= UINT8_MAX) { hdr = MSB_UINT8; len = 1; u.u8 = i; } else if (i <= UINT16_MAX) { hdr = MSB_UINT16; len = 2; u.u16 = i; } else { hdr = MSB_INT32; len = 4; u.u32 = i; } #else if (i < INT16_MIN) { hdr = LSB_INT32; len = 4; u.i32 = i; } else if (i < INT8_MIN) { hdr = LSB_INT16; len = 2; u.i16 = i; } else if (i < 0) { hdr = LSB_INT8; len = 1; u.i8 = i; } else if (i <= UINT8_MAX) { hdr = LSB_UINT8; len = 1; u.u8 = i; } else if (i <= UINT16_MAX) { hdr = LSB_UINT16; len = 2; u.u16 = i; } else { hdr = LSB_INT32; len = 4; u.u32 = i; } #endif closure->write_func (closure->closure, (unsigned char *) &hdr, 1); closure->write_func (closure->closure, (unsigned char *) &u, len); return CSI_STATUS_SUCCESS; } static csi_status_t _translate_real (csi_t *ctx, csi_real_t real, struct _translate_closure *closure) { uint8_t hdr; if (real >= INT32_MIN && real <= INT32_MAX && (int) real == real) return _translate_integer (ctx, real, closure); #if WORDS_BIGENDIAN hdr = MSB_FLOAT32; #else hdr = LSB_FLOAT32; #endif closure->write_func (closure->closure, (unsigned char *) &hdr, 1); closure->write_func (closure->closure, (unsigned char *) &real, 4); return CSI_STATUS_SUCCESS; } static csi_status_t _translate_string (csi_t *ctx, csi_string_t *string, struct _translate_closure *closure) { uint8_t hdr; union { uint8_t u8; uint16_t u16; uint32_t u32; } u; void *buf; unsigned long hdr_len, buf_len, deflate; int method; buf = string->string; buf_len = string->len; deflate = string->deflate; method = string->method; #if HAVE_LZO if (method == NONE && buf_len > 16) { unsigned long mem_len = 2*string->len > LZO2A_999_MEM_COMPRESS ? 2*string->len : LZO2A_999_MEM_COMPRESS; void *mem = malloc (mem_len); void *work = malloc(LZO2A_999_MEM_COMPRESS); if (work == NULL) { free (mem); return CSI_STATUS_NO_MEMORY; } if (lzo2a_999_compress ((lzo_bytep) buf, buf_len, (lzo_bytep) mem, &mem_len, work) == 0 && 8+2*mem_len < buf_len) { method = LZO; deflate = buf_len; buf_len = mem_len; buf = mem; } else { free (mem); } free (work); } #if HAVE_ZLIB if (method == ZLIB) { buf_len = string->deflate; buf = malloc (string->deflate); if (uncompress ((Bytef *) buf, &buf_len, (Bytef *) string->string, string->len) == Z_OK) { if (buf_len <= 8 + 2*string->len) { method = NONE; deflate = 0; } else { unsigned long mem_len = 2*string->deflate; void *mem = malloc (mem_len); void *work = malloc(LZO2A_999_MEM_COMPRESS); if (work == NULL) { free (mem); return CSI_STATUS_NO_MEMORY; } if (lzo2a_999_compress ((lzo_bytep) buf, buf_len, (lzo_bytep) mem, &mem_len, work) == 0) { if (8 + mem_len > buf_len) { method = NONE; deflate = 0; free (mem); } else { free (buf); method = LZO; deflate = buf_len; buf_len = mem_len; buf = mem; assert(deflate); } } else { free (buf); free (mem); buf = string->string; buf_len = string->len; } free (work); } } else { free (buf); buf = string->string; buf_len = string->len; } } #endif #endif if (method == LZO) { hdr = STRING_LZO; u.u32 = to_be32 (buf_len); hdr_len = 4; } else { #if WORDS_BIGENDIAN if (buf_len <= UINT8_MAX) { hdr = STRING_1; u.u8 = buf_len; hdr_len = 1; } else if (buf_len <= UINT16_MAX) { hdr = STRING_2_MSB; u.u16 = buf_len; hdr_len = 2; } else { hdr = STRING_4_MSB; u.u32 = buf_len; hdr_len = 4; } #else if (buf_len <= UINT8_MAX) { hdr = STRING_1; u.u8 = buf_len; hdr_len = 1; } else if (buf_len <= UINT16_MAX) { hdr = STRING_2_LSB; u.u16 = buf_len; hdr_len = 2; } else { hdr = STRING_4_LSB; u.u32 = buf_len; hdr_len = 4; } #endif if (deflate) { assert (method == ZLIB); hdr |= STRING_DEFLATE; } } closure->write_func (closure->closure, (unsigned char *) &hdr, 1); closure->write_func (closure->closure, (unsigned char *) &u, hdr_len); if (deflate) { uint32_t u32 = to_be32 (deflate); closure->write_func (closure->closure, (unsigned char *) &u32, 4); } closure->write_func (closure->closure, (unsigned char *) buf, buf_len); if (buf != string->string) free (buf); return CSI_STATUS_SUCCESS; } static csi_status_t _translate_push (csi_t *ctx, csi_object_t *obj) { struct _translate_closure *closure = ctx->scanner.closure; if (0) { fprintf (stderr, "push "); fprintf_obj (stderr, ctx, obj); } switch (csi_object_get_type (obj)) { case CSI_OBJECT_TYPE_NAME: return _translate_name (ctx, obj->datum.name, FALSE, closure); case CSI_OBJECT_TYPE_OPERATOR: return _translate_operator (ctx, obj->datum.op, FALSE, closure); case CSI_OBJECT_TYPE_INTEGER: return _translate_integer (ctx, obj->datum.integer, closure); case CSI_OBJECT_TYPE_REAL: return _translate_real (ctx, obj->datum.real, closure); case CSI_OBJECT_TYPE_STRING: return _translate_string (ctx, obj->datum.string, closure); case CSI_OBJECT_TYPE_NULL: case CSI_OBJECT_TYPE_BOOLEAN: case CSI_OBJECT_TYPE_MARK: case CSI_OBJECT_TYPE_ARRAY: case CSI_OBJECT_TYPE_DICTIONARY: case CSI_OBJECT_TYPE_FILE: case CSI_OBJECT_TYPE_MATRIX: case CSI_OBJECT_TYPE_CONTEXT: case CSI_OBJECT_TYPE_FONT: case CSI_OBJECT_TYPE_PATTERN: case CSI_OBJECT_TYPE_SCALED_FONT: case CSI_OBJECT_TYPE_SURFACE: longjmp (ctx->scanner.jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT)); break; } csi_object_free (ctx, obj); return CSI_STATUS_SUCCESS; } static csi_status_t _translate_execute (csi_t *ctx, csi_object_t *obj) { struct _translate_closure *closure = ctx->scanner.closure; if (0) { fprintf (stderr, "exec "); fprintf_obj (stderr, ctx, obj); } switch (csi_object_get_type (obj)) { case CSI_OBJECT_TYPE_NAME: return _translate_name (ctx, obj->datum.name, TRUE, closure); case CSI_OBJECT_TYPE_OPERATOR: return _translate_operator (ctx, obj->datum.op, TRUE, closure); case CSI_OBJECT_TYPE_INTEGER: return _translate_integer (ctx, obj->datum.integer, closure); case CSI_OBJECT_TYPE_REAL: return _translate_real (ctx, obj->datum.real, closure); case CSI_OBJECT_TYPE_STRING: return _translate_string (ctx, obj->datum.string, closure); case CSI_OBJECT_TYPE_NULL: case CSI_OBJECT_TYPE_BOOLEAN: case CSI_OBJECT_TYPE_MARK: case CSI_OBJECT_TYPE_ARRAY: case CSI_OBJECT_TYPE_DICTIONARY: case CSI_OBJECT_TYPE_FILE: case CSI_OBJECT_TYPE_MATRIX: case CSI_OBJECT_TYPE_CONTEXT: case CSI_OBJECT_TYPE_FONT: case CSI_OBJECT_TYPE_PATTERN: case CSI_OBJECT_TYPE_SCALED_FONT: case CSI_OBJECT_TYPE_SURFACE: longjmp (ctx->scanner.jmpbuf, _csi_error (CSI_STATUS_INVALID_SCRIPT)); break; } return CSI_STATUS_SUCCESS; } static csi_status_t build_opcodes (csi_t *ctx, csi_dictionary_t **out) { csi_object_t obj; csi_dictionary_t *dict; const csi_operator_def_t *def; csi_status_t status; int opcode = OPCODE << 8; status = csi_dictionary_new (ctx, &obj); if (_csi_unlikely (status)) return status; dict = obj.datum.dictionary; csi_integer_new (&obj, opcode++); status = csi_dictionary_put (ctx, dict, 0, &obj); if (_csi_unlikely (status)) goto FAIL; for (def = _csi_operators (); def->name != NULL; def++) { csi_object_t name; csi_dictionary_entry_t *entry; int code; entry = _csi_hash_table_lookup (&dict->hash_table, (csi_hash_entry_t *) &def->op); if (entry == NULL) { code = opcode++; csi_integer_new (&obj, code); status = csi_dictionary_put (ctx, dict, (csi_name_t) def->op, &obj); if (_csi_unlikely (status)) goto FAIL; } else { code = entry->value.datum.integer; csi_integer_new (&obj, code); } assert (ctx->opcode[code & 0xff] == def->op); status = csi_name_new_static (ctx, &name, def->name); if (_csi_unlikely (status)) goto FAIL; status = csi_dictionary_put (ctx, dict, name.datum.name, &obj); if (_csi_unlikely (status)) goto FAIL; } *out = dict; return CSI_STATUS_SUCCESS; FAIL: csi_dictionary_free (ctx, dict); return status; } csi_status_t _csi_translate_file (csi_t *ctx, csi_file_t *file, cairo_write_func_t write_func, void *closure) { csi_status_t status; struct _translate_closure translator; if ((status = setjmp (ctx->scanner.jmpbuf))) return status; status = build_opcodes (ctx, &translator.opcodes); if (_csi_unlikely (status)) return status; translator.write_func = write_func; translator.closure = closure; ctx->scanner.closure = &translator; ctx->scanner.bind = 1; ctx->scanner.push = _translate_push; ctx->scanner.execute = _translate_execute; _scan_file (ctx, file); ctx->scanner.bind = 0; ctx->scanner.push = _scan_push; ctx->scanner.execute = _scan_execute; csi_dictionary_free (ctx, translator.opcodes); return CSI_STATUS_SUCCESS; }