summaryrefslogtreecommitdiff
path: root/test/any2ppm.c
diff options
context:
space:
mode:
authorChris Wilson <chris@chris-wilson.co.uk>2008-08-18 18:50:00 +0100
committerChris Wilson <chris@chris-wilson.co.uk>2008-08-19 11:15:12 +0100
commit776844eb9e4b0eb70621242212d732dfefcb6d8e (patch)
tree0c7087e65b8b27e9467b89be8098ee398c312a94 /test/any2ppm.c
parent00bc650455219e41fa20d3ec99321f4cbe97cbf1 (diff)
downloadcairo-776844eb9e4b0eb70621242212d732dfefcb6d8e.tar.gz
[boilerplate] Daemonic conversion utility.
In order to achieve substantial speed improvements the external conversion utilities are rewritten as a daemon that communicates with the test suite over a local socket. This is faster as it avoids the libtool and dynamic linker overhead for each invocation, the caches persist between tests and we no longer require a round trip through libpng. The daemon is started automatically by the test suite and if communication cannot be established then it falls back to using a pipe to a normal conversion utility. The daemon will then persist for 60 seconds waiting for further connections. Of course any memory leak (stares at poppler) is exacerbated.
Diffstat (limited to 'test/any2ppm.c')
-rw-r--r--test/any2ppm.c594
1 files changed, 594 insertions, 0 deletions
diff --git a/test/any2ppm.c b/test/any2ppm.c
new file mode 100644
index 000000000..5fe8b9efd
--- /dev/null
+++ b/test/any2ppm.c
@@ -0,0 +1,594 @@
+/*
+ * Copyright © 2008 Chris Wilson
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of
+ * Chris Wilson not be used in advertising or publicity pertaining to
+ * distribution of the software without specific, written prior
+ * permission. Chris Wilson makes no representations about the
+ * suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * CHRIS WILSON DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL CHRIS WILSON BE LIABLE FOR ANY SPECIAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Author: Chris Wilson <chris@chris-wilson.co.uk>
+ *
+ * Adapted from pdf2png.c:
+ * Copyright © 2005 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: Kristian Høgsberg <krh@redhat.com>
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cairo.h>
+
+#if CAIRO_CAN_TEST_PDF_SURFACE
+#include <poppler.h>
+#endif
+
+#if CAIRO_CAN_TEST_SVG_SURFACE
+#include <librsvg/rsvg.h>
+#include <librsvg/rsvg-cairo.h>
+#endif
+
+#if CAIRO_CAN_TEST_PS_SURFACE
+#endif
+
+#if HAVE_FCNTL_H && HAVE_SIGNAL_H && HAVE_SYS_STAT_H && HAVE_SYS_SOCKET_H && HAVE_SYS_POLL_H && HAVE_SYS_UN_H
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <sys/un.h>
+
+#define SOCKET_PATH "./.any2ppm"
+#define TIMEOUT 60000 /* 60 seconds */
+
+#define CAN_RUN_AS_DAEMON 1
+#endif
+
+#define ARRAY_LENGTH(A) (sizeof (A) / sizeof (A[0]))
+
+static int
+_write (int fd,
+ char *buf, int maxlen, int buflen,
+ const unsigned char *src, int srclen)
+{
+ if (buflen < 0)
+ return buflen;
+
+ while (srclen) {
+ int len;
+
+ len = buflen + srclen;
+ if (len > maxlen)
+ len = maxlen;
+ len -= buflen;
+
+ memcpy (buf + buflen, src, len);
+ buflen += len;
+ srclen -= len;
+ src += len;
+
+ if (buflen == maxlen) {
+ if (write (fd, buf, maxlen) != maxlen)
+ return -1;
+ buflen = 0;
+ }
+ }
+
+ return buflen;
+}
+
+static const char *
+write_ppm (cairo_surface_t *surface, int fd)
+{
+ char buf[4096];
+ cairo_format_t format;
+ const char *format_str;
+ const unsigned char *data;
+ int len;
+ int width, height, stride;
+ int i, j;
+
+ format = cairo_image_surface_get_format (surface);
+ switch (format) {
+ case CAIRO_FORMAT_ARGB32:
+ /* XXX need true alpha for svg */
+ format_str = "P7";
+ break;
+ case CAIRO_FORMAT_RGB24:
+ format_str = "P6";
+ break;
+ case CAIRO_FORMAT_A8:
+ format_str = "P5";
+ break;
+ case CAIRO_FORMAT_A1:
+ default:
+ return "unhandled image format";
+ }
+
+ data = cairo_image_surface_get_data (surface);
+ height = cairo_image_surface_get_height (surface);
+ width = cairo_image_surface_get_width (surface);
+ stride = cairo_image_surface_get_stride (surface);
+
+ len = sprintf (buf, "%s %d %d 255\n", format_str, width, height);
+ for (j = 0; j < height; j++) {
+ const unsigned char *row = data + stride * j;
+
+ switch ((int) format) {
+ case CAIRO_FORMAT_ARGB32:
+ len = _write (fd,
+ buf, sizeof (buf), len,
+ row, 4 * width);
+ break;
+ case CAIRO_FORMAT_RGB24:
+ for (i = 0; i < width; i++) {
+ len = _write (fd,
+ buf, sizeof (buf), len,
+ row, 3);
+ row += 4;
+ }
+ break;
+ case CAIRO_FORMAT_A8:
+ len = _write (fd,
+ buf, sizeof (buf), len,
+ row, width);
+ break;
+ }
+ if (len < 0)
+ return "write failed";
+ }
+
+ if (len) {
+ if (write (fd, buf, len) != len)
+ return "write failed";
+ }
+
+ return NULL;
+}
+
+#if CAIRO_CAN_TEST_PDF_SURFACE
+/* adapted from pdf2png.c */
+static const char *
+_poppler_render_page (const char *filename,
+ const char *page_label,
+ cairo_surface_t **surface_out)
+{
+ PopplerDocument *document;
+ PopplerPage *page;
+ double width, height;
+ GError *error = NULL;
+ gchar *absolute, *uri;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ cairo_status_t status;
+
+ if (g_path_is_absolute (filename)) {
+ absolute = g_strdup (filename);
+ } else {
+ gchar *dir = g_get_current_dir ();
+ absolute = g_build_filename (dir, filename, (gchar *) 0);
+ g_free (dir);
+ }
+
+ uri = g_filename_to_uri (absolute, NULL, &error);
+ g_free (absolute);
+ if (uri == NULL)
+ return error->message; /* XXX g_error_free (error) */
+
+ document = poppler_document_new_from_file (uri, NULL, &error);
+ g_free (uri);
+ if (document == NULL)
+ return error->message; /* XXX g_error_free (error) */
+
+ page = poppler_document_get_page_by_label (document, page_label);
+ g_object_unref (document);
+ if (page == NULL)
+ return "page not found";
+
+ poppler_page_get_size (page, &width, &height);
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
+ cr = cairo_create (surface);
+
+ cairo_set_source_rgb (cr, 1., 1., 1.);
+ cairo_paint (cr);
+
+ poppler_page_render (page, cr);
+ g_object_unref (page);
+
+ status = cairo_status (cr);
+ cairo_destroy (cr);
+
+ if (status) {
+ cairo_surface_destroy (surface);
+ return cairo_status_to_string (status);
+ }
+
+ *surface_out = surface;
+ return NULL;
+}
+
+static const char *
+pdf_convert (char **argv, int fd)
+{
+ const char *err;
+ cairo_surface_t *surface = NULL; /* silence compiler warning */
+
+ err = _poppler_render_page (argv[0], argv[1], &surface);
+ if (err != NULL)
+ return err;
+
+ err = write_ppm (surface, fd);
+ cairo_surface_destroy (surface);
+
+ return err;
+}
+#endif
+
+#if CAIRO_CAN_TEST_SVG_SURFACE
+static const char *
+_rsvg_render_page (const char *filename,
+ cairo_surface_t **surface_out)
+{
+ RsvgHandle *handle;
+ RsvgDimensionData dimensions;
+ GError *error = NULL;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ cairo_status_t status;
+
+ handle = rsvg_handle_new_from_file (filename, &error);
+ if (handle == NULL)
+ return error->message; /* XXX g_error_free */
+
+ rsvg_handle_get_dimensions (handle, &dimensions);
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ dimensions.width,
+ dimensions.height);
+ cr = cairo_create (surface);
+
+ rsvg_handle_render_cairo (handle, cr);
+ g_object_unref (handle);
+
+ status = cairo_status (cr);
+ cairo_destroy (cr);
+
+ if (status) {
+ cairo_surface_destroy (surface);
+ return cairo_status_to_string (status);
+ }
+
+ *surface_out = surface;
+ return NULL;
+}
+
+static const char *
+svg_convert (char **argv, int fd)
+{
+ const char *err;
+ cairo_surface_t *surface = NULL; /* silence compiler warning */
+
+ err = _rsvg_render_page (argv[0], &surface);
+ if (err != NULL)
+ return err;
+
+ err = write_ppm (surface, fd);
+ cairo_surface_destroy (surface);
+
+ return err;
+}
+#endif
+
+#if CAIRO_CAN_TEST_PS_SURFACE
+static const char *
+ps_convert (char **argv, int fd)
+{
+ /* XXX libspectre */
+
+ return "no method to convert PS";
+}
+#endif
+
+static const char *
+convert (char **argv, int fd)
+{
+ static const struct converter {
+ const char *type;
+ const char *(*func) (char **, int);
+ } converters[] = {
+#if CAIRO_CAN_TEST_PDF_SURFACE
+ { "pdf", pdf_convert },
+#endif
+#if CAIRO_CAN_TEST_PS_SURFACE
+ { "ps", ps_convert },
+#endif
+#if CAIRO_CAN_TEST_SVG_SURFACE
+ { "svg", svg_convert },
+#endif
+ { NULL, NULL }
+ };
+ const struct converter *converter = converters;
+ char *type;
+
+ type = strchr (argv[0], '.');
+ if (type == NULL)
+ return "no file extension";
+ type++;
+
+ while (converter->type) {
+ if (strcmp (type, converter->type) == 0)
+ return converter->func (argv, fd);
+ converter++;
+ }
+ return "no converter";
+}
+
+#if CAN_RUN_AS_DAEMON
+static int
+_getline (int fd, char **linep, size_t *lenp)
+{
+ char *line;
+ size_t len, i;
+ ssize_t ret;
+
+ line = *linep;
+ if (line == NULL) {
+ line = malloc (1024);
+ if (line == NULL)
+ return -1;
+ line[0] = '\0';
+ len = 1024;
+ } else
+ len = *lenp;
+
+ /* XXX simple, but ugly! */
+ i = 0;
+ do {
+ if (i == len - 1) {
+ char *nline;
+
+ nline = realloc (line, len + 1024);
+ if (nline == NULL)
+ goto out;
+
+ line = nline;
+ len += 1024;
+ }
+
+ ret = read (fd, line + i, 1);
+ if (ret == -1 || ret == 0)
+ goto out;
+ } while (line[i++] != '\n');
+
+out:
+ line[i] = '\0';
+ *linep = line;
+ *lenp = len;
+ return i-1;
+}
+
+static int
+split_line (char *line, char *argv[], int max_argc)
+{
+ int i = 0;
+
+ max_argc--; /* leave one spare for the trailing NULL */
+
+ argv[i++] = line;
+ while (i < max_argc && (line = strchr (line, ' ')) != NULL) {
+ *line++ = '\0';
+ argv[i++] = line;
+ }
+
+ /* chomp the newline */
+ line = strchr (argv[i-1], '\n');
+ if (line != NULL)
+ *line = '\0';
+
+ argv[i] = NULL;
+
+ return i;
+}
+
+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) == 0;
+}
+
+static int
+write_pid_file (void)
+{
+ int fd;
+ char buf[80];
+ int ret;
+
+ fd = open (SOCKET_PATH ".pid", O_CREAT | O_TRUNC | O_WRONLY, 0666);
+ if (fd < 0)
+ return 0;
+
+ ret = sprintf (buf, "%d\n", getpid ());
+ ret = write (fd, buf, ret) == ret;
+ close (fd);
+
+ return ret;
+}
+
+static const char *
+any2ppm_daemon (void)
+{
+ int timeout = TIMEOUT;
+ struct pollfd pfd;
+ int sk, fd;
+ long flags;
+ struct sockaddr_un addr;
+ char *line = NULL;
+ size_t len = 0;
+
+#ifdef SIGPIPE
+ signal (SIGPIPE, SIG_IGN);
+#endif
+
+ /* XXX racy! */
+ if (getenv ("ANY2PPM_FORCE") == NULL && any2ppm_daemon_exists ())
+ return "any2ppm daemon already running";
+
+ unlink (SOCKET_PATH);
+
+ sk = socket (PF_UNIX, SOCK_STREAM, 0);
+ if (sk == -1)
+ return "unable to create socket";
+
+ memset (&addr, 0, sizeof (addr));
+ addr.sun_family = AF_UNIX;
+ strcpy (addr.sun_path, SOCKET_PATH);
+ if (bind (sk, (struct sockaddr *) &addr, sizeof (addr)) == -1) {
+ close (sk);
+ return "unable to bind socket";
+ }
+
+ flags = fcntl (sk, F_GETFL);
+ if (flags == -1 || fcntl (sk, F_SETFL, flags | O_NONBLOCK) == -1) {
+ close (sk);
+ return "unable to set socket to non-blocking";
+ }
+
+ if (listen (sk, 5) == -1) {
+ close (sk);
+ return "unable to listen on socket";
+ }
+
+ /* ready for client connection - detach from parent/terminal */
+ if (getenv ("ANY2PPM_NODAEMON") == NULL && daemon (1, 0) == -1) {
+ close (sk);
+ return "unable to detach from parent";
+ }
+
+ if (! write_pid_file ()) {
+ close (sk);
+ return "unable to write pid file";
+ }
+
+ if (getenv ("ANY2PPM_TIMEOUT") != NULL) {
+ timeout = atoi (getenv ("ANY2PPM_TIMEOUT"));
+ if (timeout == 0)
+ timeout = -1;
+ if (timeout > 0)
+ timeout *= 1000; /* convert env (in seconds) to milliseconds */
+ }
+
+ pfd.fd = sk;
+ pfd.events = POLLIN;
+ pfd.revents = 0; /* valgrind */
+ while (poll (&pfd, 1, timeout) > 0) {
+ while ((fd = accept (sk, NULL, NULL)) != -1) {
+ if (_getline (fd, &line, &len) != -1) {
+ char *argv[10];
+
+ if (split_line (line, argv, ARRAY_LENGTH (argv)) > 0)
+ convert (argv, fd);
+ }
+ close (fd);
+ }
+ }
+ close (sk);
+ unlink (SOCKET_PATH);
+ unlink (SOCKET_PATH ".pid");
+
+ free (line);
+ return NULL;
+}
+#else
+static const char *
+any2ppm_daemon (void)
+{
+ return "daemon not compiled in.";
+}
+#endif
+
+int
+main (int argc, char **argv)
+{
+ const char *err;
+
+ g_type_init ();
+
+#if CAIRO_CAN_TEST_SVG_SURFACE
+ rsvg_init ();
+ rsvg_set_default_dpi (72.0);
+#endif
+
+ if (argc == 1)
+ err = any2ppm_daemon ();
+ else
+ err = convert (argv + 1, 1);
+ if (err != NULL) {
+ fprintf (stderr, "Failed to run converter: %s\n", err);
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}