/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */ /* * Copyright © 2004,2006 Red Hat, Inc. * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without * fee, provided that the above copyright notice appear in all copies * and that both that copyright notice and this permission notice * appear in supporting documentation, and that the name of * Red Hat, Inc. not be used in advertising or publicity pertaining to * distribution of the software without specific, written prior * permission. Red Hat, Inc. makes no representations about the * suitability of this software for any purpose. It is provided "as * is" without express or implied warranty. * * RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL, * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Author: Carl D. Worth */ #define CAIRO_VERSION_H 1 #include "cairo-boilerplate-private.h" #include "cairo-boilerplate-scaled-font.h" #include #include #include #if CAIRO_HAS_SCRIPT_SURFACE #include #endif /* get the "real" version info instead of dummy cairo-version.h */ #undef CAIRO_VERSION_H #include "../cairo-version.h" #include #include #include #include #if HAVE_DLFCN_H #include #endif #if HAVE_UNISTD_H && HAVE_FCNTL_H && HAVE_SIGNAL_H && HAVE_SYS_STAT_H && HAVE_SYS_SOCKET_H && HAVE_SYS_UN_H #include #include #include #include #include #include #define HAS_DAEMON 1 #define SOCKET_PATH "./.any2ppm" #endif cairo_content_t cairo_boilerplate_content (cairo_content_t content) { if (content == CAIRO_TEST_CONTENT_COLOR_ALPHA_FLATTENED) content = CAIRO_CONTENT_COLOR_ALPHA; return content; } const char * cairo_boilerplate_content_name (cairo_content_t content) { /* For the purpose of the content name, we don't distinguish the * flattened content value. */ switch (cairo_boilerplate_content (content)) { case CAIRO_CONTENT_COLOR: return "rgb24"; case CAIRO_CONTENT_COLOR_ALPHA: return "argb32"; case CAIRO_CONTENT_ALPHA: default: assert (0); /* not reached */ return "---"; } } static const char * _cairo_boilerplate_content_visible_name (cairo_content_t content) { switch (cairo_boilerplate_content (content)) { case CAIRO_CONTENT_COLOR: return "rgb"; case CAIRO_CONTENT_COLOR_ALPHA: return "rgba"; case CAIRO_CONTENT_ALPHA: return "a"; default: assert (0); /* not reached */ return "---"; } } cairo_format_t cairo_boilerplate_format_from_content (cairo_content_t content) { cairo_format_t format; switch (content) { case CAIRO_CONTENT_COLOR: format = CAIRO_FORMAT_RGB24; break; case CAIRO_CONTENT_COLOR_ALPHA: format = CAIRO_FORMAT_ARGB32; break; case CAIRO_CONTENT_ALPHA: format = CAIRO_FORMAT_A8; break; default: assert (0); /* not reached */ format = CAIRO_FORMAT_INVALID; break; } return format; } static cairo_surface_t * _cairo_boilerplate_image_create_surface (const char *name, cairo_content_t content, double width, double height, double max_width, double max_height, cairo_boilerplate_mode_t mode, void **closure) { cairo_format_t format; *closure = NULL; if (content == CAIRO_CONTENT_COLOR_ALPHA) { format = CAIRO_FORMAT_ARGB32; } else if (content == CAIRO_CONTENT_COLOR) { format = CAIRO_FORMAT_RGB24; } else { assert (0); /* not reached */ return NULL; } return cairo_image_surface_create (format, ceil (width), ceil (height)); } static const cairo_user_data_key_t key; static cairo_surface_t * _cairo_boilerplate_image_create_similar (cairo_surface_t *other, cairo_content_t content, int width, int height) { cairo_format_t format; cairo_surface_t *surface; int stride; void *ptr; switch (content) { case CAIRO_CONTENT_ALPHA: format = CAIRO_FORMAT_A8; break; case CAIRO_CONTENT_COLOR: format = CAIRO_FORMAT_RGB24; break; default: case CAIRO_CONTENT_COLOR_ALPHA: format = CAIRO_FORMAT_ARGB32; break; } stride = cairo_format_stride_for_width(format, width); ptr = malloc (stride* height); surface = cairo_image_surface_create_for_data (ptr, format, width, height, stride); cairo_surface_set_user_data (surface, &key, ptr, free); return surface; } static cairo_surface_t * _cairo_boilerplate_image16_create_surface (const char *name, cairo_content_t content, double width, double height, double max_width, double max_height, cairo_boilerplate_mode_t mode, void **closure) { *closure = NULL; /* XXX force CAIRO_CONTENT_COLOR */ return cairo_image_surface_create (CAIRO_FORMAT_RGB16_565, ceil (width), ceil (height)); } static cairo_surface_t * _cairo_boilerplate_image16_create_similar (cairo_surface_t *other, cairo_content_t content, int width, int height) { cairo_format_t format; cairo_surface_t *surface; int stride; void *ptr; switch (content) { case CAIRO_CONTENT_ALPHA: format = CAIRO_FORMAT_A8; break; case CAIRO_CONTENT_COLOR: format = CAIRO_FORMAT_RGB16_565; break; default: case CAIRO_CONTENT_COLOR_ALPHA: format = CAIRO_FORMAT_ARGB32; break; } stride = cairo_format_stride_for_width(format, width); ptr = malloc (stride* height); surface = cairo_image_surface_create_for_data (ptr, format, width, height, stride); cairo_surface_set_user_data (surface, &key, ptr, free); return surface; } static char * _cairo_boilerplate_image_describe (void *closure) { char *s; xasprintf (&s, "pixman %s", pixman_version_string ()); return s; } #if CAIRO_HAS_RECORDING_SURFACE static cairo_surface_t * _cairo_boilerplate_recording_create_surface (const char *name, cairo_content_t content, double width, double height, double max_width, double max_height, cairo_boilerplate_mode_t mode, void **closure) { cairo_rectangle_t extents; extents.x = 0; extents.y = 0; extents.width = width; extents.height = height; return *closure = cairo_surface_reference (cairo_recording_surface_create (content, &extents)); } static void _cairo_boilerplate_recording_surface_cleanup (void *closure) { cairo_surface_finish (closure); cairo_surface_destroy (closure); } #endif const cairo_user_data_key_t cairo_boilerplate_output_basename_key; cairo_surface_t * _cairo_boilerplate_get_image_surface (cairo_surface_t *src, int page, int width, int height) { cairo_surface_t *surface, *image; cairo_t *cr; cairo_status_t status; cairo_format_t format; if (cairo_surface_status (src)) return cairo_surface_reference (src); if (page != 0) return cairo_boilerplate_surface_create_in_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH); /* extract sub-surface */ switch (cairo_surface_get_content (src)) { case CAIRO_CONTENT_ALPHA: format = CAIRO_FORMAT_A8; break; case CAIRO_CONTENT_COLOR: format = CAIRO_FORMAT_RGB24; break; default: case CAIRO_CONTENT_COLOR_ALPHA: format = CAIRO_FORMAT_ARGB32; break; } surface = cairo_image_surface_create (format, width, height); assert (cairo_surface_get_content (surface) == cairo_surface_get_content (src)); image = cairo_surface_reference (surface); /* open a logging channel (only interesting for recording surfaces) */ #if CAIRO_HAS_SCRIPT_SURFACE && CAIRO_HAS_RECORDING_SURFACE if (cairo_surface_get_type (src) == CAIRO_SURFACE_TYPE_RECORDING) { const char *test_name; test_name = cairo_surface_get_user_data (src, &cairo_boilerplate_output_basename_key); if (test_name != NULL) { cairo_device_t *ctx; char *filename; cairo_surface_destroy (surface); xasprintf (&filename, "%s.out.trace", test_name); ctx = cairo_script_create (filename); surface = cairo_script_surface_create_for_target (ctx, image); cairo_device_destroy (ctx); free (filename); } } #endif cr = cairo_create (surface); cairo_surface_destroy (surface); cairo_set_source_surface (cr, src, 0, 0); cairo_paint (cr); status = cairo_status (cr); if (status) { cairo_surface_destroy (image); image = cairo_surface_reference (cairo_get_target (cr)); } cairo_destroy (cr); return image; } cairo_surface_t * cairo_boilerplate_get_image_surface_from_png (const char *filename, int width, int height, cairo_bool_t flatten) { cairo_surface_t *surface; surface = cairo_image_surface_create_from_png (filename); if (cairo_surface_status (surface)) return surface; if (flatten) { cairo_t *cr; cairo_surface_t *flattened; flattened = cairo_image_surface_create (cairo_image_surface_get_format (surface), width, height); cr = cairo_create (flattened); cairo_surface_destroy (flattened); cairo_set_source_rgb (cr, 1, 1, 1); cairo_paint (cr); cairo_set_source_surface (cr, surface, width - cairo_image_surface_get_width (surface), height - cairo_image_surface_get_height (surface)); cairo_paint (cr); cairo_surface_destroy (surface); surface = cairo_surface_reference (cairo_get_target (cr)); cairo_destroy (cr); } else if (cairo_image_surface_get_width (surface) != width || cairo_image_surface_get_height (surface) != height) { cairo_t *cr; cairo_surface_t *sub; sub = cairo_image_surface_create (cairo_image_surface_get_format (surface), width, height); cr = cairo_create (sub); cairo_surface_destroy (sub); cairo_set_source_surface (cr, surface, width - cairo_image_surface_get_width (surface), height - cairo_image_surface_get_height (surface)); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_paint (cr); cairo_surface_destroy (surface); surface = cairo_surface_reference (cairo_get_target (cr)); cairo_destroy (cr); } return surface; } static const cairo_boilerplate_target_t builtin_targets[] = { /* I'm uncompromising about leaving the image backend as 0 * for tolerance. There shouldn't ever be anything that is out of * our control here. */ { "image", "image", NULL, NULL, CAIRO_SURFACE_TYPE_IMAGE, CAIRO_CONTENT_COLOR_ALPHA, 0, NULL, _cairo_boilerplate_image_create_surface, _cairo_boilerplate_image_create_similar, NULL, NULL, _cairo_boilerplate_get_image_surface, cairo_surface_write_to_png, NULL, NULL, _cairo_boilerplate_image_describe, TRUE, FALSE, FALSE }, { "image", "image", NULL, NULL, CAIRO_SURFACE_TYPE_IMAGE, CAIRO_CONTENT_COLOR, 0, NULL, _cairo_boilerplate_image_create_surface, _cairo_boilerplate_image_create_similar, NULL, NULL, _cairo_boilerplate_get_image_surface, cairo_surface_write_to_png, NULL, NULL, _cairo_boilerplate_image_describe, FALSE, FALSE, FALSE }, { "image16", "image", NULL, NULL, CAIRO_SURFACE_TYPE_IMAGE, CAIRO_CONTENT_COLOR, 0, NULL, _cairo_boilerplate_image16_create_surface, _cairo_boilerplate_image16_create_similar, NULL, NULL, _cairo_boilerplate_get_image_surface, cairo_surface_write_to_png, NULL, NULL, _cairo_boilerplate_image_describe, TRUE, FALSE, FALSE }, #if CAIRO_HAS_RECORDING_SURFACE { "recording", "image", NULL, NULL, CAIRO_SURFACE_TYPE_RECORDING, CAIRO_CONTENT_COLOR_ALPHA, 0, "cairo_recording_surface_create", _cairo_boilerplate_recording_create_surface, cairo_surface_create_similar, NULL, NULL, _cairo_boilerplate_get_image_surface, cairo_surface_write_to_png, _cairo_boilerplate_recording_surface_cleanup, NULL, NULL, FALSE, FALSE, TRUE }, { "recording", "image", NULL, NULL, CAIRO_SURFACE_TYPE_RECORDING, CAIRO_CONTENT_COLOR, 0, "cairo_recording_surface_create", _cairo_boilerplate_recording_create_surface, cairo_surface_create_similar, NULL, NULL, _cairo_boilerplate_get_image_surface, cairo_surface_write_to_png, _cairo_boilerplate_recording_surface_cleanup, NULL, NULL, FALSE, FALSE, TRUE }, #endif }; CAIRO_BOILERPLATE (builtin, builtin_targets) static struct cairo_boilerplate_target_list { struct cairo_boilerplate_target_list *next; const cairo_boilerplate_target_t *target; } *cairo_boilerplate_targets; static cairo_bool_t probe_target (const cairo_boilerplate_target_t *target) { if (target->probe == NULL) return TRUE; #if HAVE_DLSYM return dlsym (NULL, target->probe) != NULL; #else return TRUE; #endif } void _cairo_boilerplate_register_backend (const cairo_boilerplate_target_t *targets, unsigned int count) { targets += count; while (count--) { struct cairo_boilerplate_target_list *list; --targets; if (! probe_target (targets)) continue; list = xmalloc (sizeof (*list)); list->next = cairo_boilerplate_targets; list->target = targets; cairo_boilerplate_targets = list; } } static cairo_bool_t _cairo_boilerplate_target_matches_name (const cairo_boilerplate_target_t *target, const char *tname, const char *end) { char const *content_name; const char *content_start = strpbrk (tname, "."); const char *content_end = end; size_t name_len; size_t content_len; if (content_start != NULL) end = content_start++; name_len = end - tname; /* Check name. */ if (! (name_len == 1 && 0 == strncmp (tname, "?", 1))) { /* wildcard? */ if (0 != strncmp (target->name, tname, name_len)) /* exact match? */ return FALSE; if (isalnum (target->name[name_len])) return FALSE; } /* Check optional content. */ if (content_start == NULL) /* none given? */ return TRUE; /* Exact content match? */ content_name = _cairo_boilerplate_content_visible_name (target->content); content_len = content_end - content_start; if (strlen(content_name) != content_len) return FALSE; if (0 == strncmp (content_name, content_start, content_len)) return TRUE; return FALSE; } const cairo_boilerplate_target_t ** cairo_boilerplate_get_targets (int *pnum_targets, cairo_bool_t *plimited_targets) { size_t i, num_targets; cairo_bool_t limited_targets = FALSE; const char *tname; const cairo_boilerplate_target_t **targets_to_test; struct cairo_boilerplate_target_list *list; if (cairo_boilerplate_targets == NULL) _cairo_boilerplate_register_all (); if ((tname = getenv ("CAIRO_TEST_TARGET")) != NULL && *tname) { /* check the list of targets specified by the user */ limited_targets = TRUE; num_targets = 0; targets_to_test = NULL; while (*tname) { int found = 0; const char *end = strpbrk (tname, " \t\r\n;:,"); if (!end) end = tname + strlen (tname); if (end == tname) { tname = end + 1; continue; } for (list = cairo_boilerplate_targets; list != NULL; list = list->next) { const cairo_boilerplate_target_t *target = list->target; if (_cairo_boilerplate_target_matches_name (target, tname, end)) { /* realloc isn't exactly the best thing here, but meh. */ targets_to_test = xrealloc (targets_to_test, sizeof(cairo_boilerplate_target_t *) * (num_targets+1)); targets_to_test[num_targets++] = target; found = 1; } } if (!found) { const char *last_name = NULL; fprintf (stderr, "Cannot find target '%.*s'.\n", (int)(end - tname), tname); fprintf (stderr, "Known targets:"); for (list = cairo_boilerplate_targets; list != NULL; list = list->next) { const cairo_boilerplate_target_t *target = list->target; if (last_name != NULL) { if (strcmp (target->name, last_name) == 0) { /* filter out repeats that differ in content */ continue; } fprintf (stderr, ","); } fprintf (stderr, " %s", target->name); last_name = target->name; } fprintf (stderr, "\n"); exit(-1); } if (*end) end++; tname = end; } } else { /* check all compiled in targets */ num_targets = 0; for (list = cairo_boilerplate_targets; list != NULL; list = list->next) num_targets++; targets_to_test = xmalloc (sizeof(cairo_boilerplate_target_t*) * num_targets); num_targets = 0; for (list = cairo_boilerplate_targets; list != NULL; list = list->next) { const cairo_boilerplate_target_t *target = list->target; targets_to_test[num_targets++] = target; } } /* exclude targets as specified by the user */ if ((tname = getenv ("CAIRO_TEST_TARGET_EXCLUDE")) != NULL && *tname) { limited_targets = TRUE; while (*tname) { int j; const char *end = strpbrk (tname, " \t\r\n;:,"); if (!end) end = tname + strlen (tname); if (end == tname) { tname = end + 1; continue; } for (i = j = 0; i < num_targets; i++) { const cairo_boilerplate_target_t *target = targets_to_test[i]; if (! _cairo_boilerplate_target_matches_name (target, tname, end)) { targets_to_test[j++] = targets_to_test[i]; } } num_targets = j; if (*end) end++; tname = end; } } if (pnum_targets) *pnum_targets = num_targets; if (plimited_targets) *plimited_targets = limited_targets; return targets_to_test; } const cairo_boilerplate_target_t * cairo_boilerplate_get_image_target (cairo_content_t content) { if (cairo_boilerplate_targets == NULL) _cairo_boilerplate_register_all (); switch (content) { default: case CAIRO_CONTENT_ALPHA: return NULL; case CAIRO_CONTENT_COLOR: return &builtin_targets[1]; case CAIRO_CONTENT_COLOR_ALPHA: return &builtin_targets[0]; } } const cairo_boilerplate_target_t * cairo_boilerplate_get_target_by_name (const char *name, cairo_content_t content) { struct cairo_boilerplate_target_list *list; if (cairo_boilerplate_targets == NULL) _cairo_boilerplate_register_all (); /* first return an exact match */ for (list = cairo_boilerplate_targets; list != NULL; list = list->next) { const cairo_boilerplate_target_t *target = list->target; if (strcmp (target->name, name) == 0 && target->content == content) { return target; } } /* otherwise just return a match that may differ in content */ for (list = cairo_boilerplate_targets; list != NULL; list = list->next) { const cairo_boilerplate_target_t *target = list->target; if (strcmp (target->name, name) == 0) return target; } return NULL; } void cairo_boilerplate_free_targets (const cairo_boilerplate_target_t **targets) { free (targets); } cairo_surface_t * cairo_boilerplate_surface_create_in_error (cairo_status_t status) { cairo_surface_t *surface = NULL; int loop = 5; do { cairo_surface_t *intermediate; cairo_t *cr; cairo_path_t path; intermediate = cairo_image_surface_create (CAIRO_FORMAT_A8, 0, 0); cr = cairo_create (intermediate); cairo_surface_destroy (intermediate); path.status = status; cairo_append_path (cr, &path); cairo_surface_destroy (surface); surface = cairo_surface_reference (cairo_get_target (cr)); cairo_destroy (cr); } while (cairo_surface_status (surface) != status && --loop); return surface; } void cairo_boilerplate_scaled_font_set_max_glyphs_cached (cairo_scaled_font_t *scaled_font, int max_glyphs) { /* XXX CAIRO_DEBUG */ } #if HAS_DAEMON static int any2ppm_daemon_exists (void) { struct stat st; int fd; char buf[80]; int pid; int ret; if (stat (SOCKET_PATH, &st) < 0) return 0; fd = open (SOCKET_PATH ".pid", O_RDONLY); if (fd < 0) return 0; pid = 0; ret = read (fd, buf, sizeof (buf) - 1); if (ret > 0) { buf[ret] = '\0'; pid = atoi (buf); } close (fd); return pid > 0 && kill (pid, 0) != -1; } #endif FILE * cairo_boilerplate_open_any2ppm (const char *filename, int page, unsigned int flags, int (**close_cb) (FILE *)) { char command[4096]; const char *any2ppm; #if HAS_DAEMON int sk; struct sockaddr_un addr; int len; #endif any2ppm = getenv ("ANY2PPM"); if (any2ppm == NULL) any2ppm = "./any2ppm"; #if HAS_DAEMON if (flags & CAIRO_BOILERPLATE_OPEN_NO_DAEMON) goto POPEN; if (! any2ppm_daemon_exists ()) { if (system (any2ppm) != 0) goto POPEN; } sk = socket (PF_UNIX, SOCK_STREAM, 0); if (sk == -1) goto POPEN; memset (&addr, 0, sizeof (addr)); addr.sun_family = AF_UNIX; strcpy (addr.sun_path, SOCKET_PATH); if (connect (sk, (struct sockaddr *) &addr, sizeof (addr)) == -1) { close (sk); goto POPEN; } len = sprintf (command, "%s %d\n", filename, page); if (write (sk, command, len) != len) { close (sk); goto POPEN; } *close_cb = fclose; return fdopen (sk, "r"); POPEN: #endif *close_cb = pclose; sprintf (command, "%s %s %d", any2ppm, filename, page); return popen (command, "r"); } static cairo_bool_t freadn (unsigned char *buf, int len, FILE *file) { int ret; while (len) { ret = fread (buf, 1, len, file); if (ret != len) { if (ferror (file) || feof (file)) return FALSE; } len -= ret; buf += len; } return TRUE; } cairo_surface_t * cairo_boilerplate_image_surface_create_from_ppm_stream (FILE *file) { char format; int width, height, stride; int x, y; unsigned char *data; cairo_surface_t *image = NULL; if (fscanf (file, "P%c %d %d 255\n", &format, &width, &height) != 3) goto FAIL; switch (format) { case '7': /* XXX */ image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); break; case '6': image = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height); break; case '5': image = cairo_image_surface_create (CAIRO_FORMAT_A8, width, height); break; default: goto FAIL; } if (cairo_surface_status (image)) return image; data = cairo_image_surface_get_data (image); stride = cairo_image_surface_get_stride (image); for (y = 0; y < height; y++) { unsigned char *buf = data + y*stride; switch (format) { case '7': if (! freadn (buf, 4 * width, file)) goto FAIL; break; case '6': if (! freadn (buf, 3*width, file)) goto FAIL; buf += 3*width; for (x = width; x--; ) { buf -= 3; ((uint32_t *) (data + y*stride))[x] = (buf[0] << 16) | (buf[1] << 8) | (buf[2] << 0); } break; case '5': if (! freadn (buf, width, file)) goto FAIL; break; } } cairo_surface_mark_dirty (image); return image; FAIL: cairo_surface_destroy (image); return cairo_boilerplate_surface_create_in_error (CAIRO_STATUS_READ_ERROR); } cairo_surface_t * cairo_boilerplate_convert_to_image (const char *filename, int page) { FILE *file; unsigned int flags = 0; cairo_surface_t *image; int (*close_cb) (FILE *); int ret; RETRY: file = cairo_boilerplate_open_any2ppm (filename, page, flags, &close_cb); if (file == NULL) { switch (errno) { case ENOMEM: return cairo_boilerplate_surface_create_in_error (CAIRO_STATUS_NO_MEMORY); default: return cairo_boilerplate_surface_create_in_error (CAIRO_STATUS_READ_ERROR); } } image = cairo_boilerplate_image_surface_create_from_ppm_stream (file); ret = close_cb (file); /* check for fatal errors from the interpreter */ if (ret) { /* any2pmm should never die... */ cairo_surface_destroy (image); return cairo_boilerplate_surface_create_in_error (CAIRO_STATUS_INVALID_STATUS); } if (ret == 0 && cairo_surface_status (image) == CAIRO_STATUS_READ_ERROR) { if (flags == 0) { /* Try again in a standalone process. */ cairo_surface_destroy (image); flags = CAIRO_BOILERPLATE_OPEN_NO_DAEMON; goto RETRY; } } return image; } int cairo_boilerplate_version (void) { return CAIRO_VERSION; } const char* cairo_boilerplate_version_string (void) { return CAIRO_VERSION_STRING; } void cairo_boilerplate_fini (void) { while (cairo_boilerplate_targets != NULL) { struct cairo_boilerplate_target_list *next; next = cairo_boilerplate_targets->next; free (cairo_boilerplate_targets); cairo_boilerplate_targets = next; } }