2 * Copyright © 2008 Chris Wilson
4 * Permission to use, copy, modify, distribute, and sell this software
5 * and its documentation for any purpose is hereby granted without
6 * fee, provided that the above copyright notice appear in all copies
7 * and that both that copyright notice and this permission notice
8 * appear in supporting documentation, and that the name of
9 * Chris Wilson not be used in advertising or publicity pertaining to
10 * distribution of the software without specific, written prior
11 * permission. Chris Wilson makes no representations about the
12 * suitability of this software for any purpose. It is provided "as
13 * is" without express or implied warranty.
15 * CHRIS WILSON DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
16 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
17 * FITNESS, IN NO EVENT SHALL CHRIS WILSON BE LIABLE FOR ANY SPECIAL,
18 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
19 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
21 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 * Author: Chris Wilson <chris@chris-wilson.co.uk>
26 * Carlos Garcia Campos <carlosgc@gnome.org>
28 * Adapted from pdf2png.c:
29 * Copyright © 2005 Red Hat, Inc.
31 * Permission to use, copy, modify, distribute, and sell this software
32 * and its documentation for any purpose is hereby granted without
33 * fee, provided that the above copyright notice appear in all copies
34 * and that both that copyright notice and this permission notice
35 * appear in supporting documentation, and that the name of
36 * Red Hat, Inc. not be used in advertising or publicity pertaining to
37 * distribution of the software without specific, written prior
38 * permission. Red Hat, Inc. makes no representations about the
39 * suitability of this software for any purpose. It is provided "as
40 * is" without express or implied warranty.
42 * RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
43 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
44 * FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL,
45 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
46 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
47 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
48 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
50 * Author: Kristian Høgsberg <krh@redhat.com>
65 #include <cairo-script-interpreter.h>
67 #if CAIRO_CAN_TEST_PDF_SURFACE
71 #if CAIRO_CAN_TEST_SVG_SURFACE
72 #include <librsvg/rsvg.h>
74 #include <librsvg/rsvg-cairo.h>
79 #include <libspectre/spectre.h>
88 #if HAVE_UNISTD_H && HAVE_SIGNAL_H && HAVE_SYS_STAT_H && HAVE_SYS_SOCKET_H && HAVE_SYS_POLL_H && HAVE_SYS_UN_H
91 #include <sys/socket.h>
95 #define SOCKET_PATH "./.any2ppm"
96 #define TIMEOUT 60000 /* 60 seconds */
99 #define CAN_RUN_AS_DAEMON 1
103 #define ARRAY_LENGTH(__array) ((int) (sizeof (__array) / sizeof (__array[0])))
106 _cairo_writen (int fd, char *buf, int len)
111 ret = write (fd, buf, len);
130 _cairo_write (int fd,
131 char *buf, int maxlen, int buflen,
132 const unsigned char *src, int srclen)
140 len = buflen + srclen;
145 memcpy (buf + buflen, src, len);
150 if (buflen == maxlen) {
151 if (! _cairo_writen (fd, buf, buflen))
162 write_ppm (cairo_surface_t *surface, int fd)
165 cairo_format_t format;
166 const char *format_str;
167 const unsigned char *data;
169 int width, height, stride;
172 data = cairo_image_surface_get_data (surface);
173 height = cairo_image_surface_get_height (surface);
174 width = cairo_image_surface_get_width (surface);
175 stride = cairo_image_surface_get_stride (surface);
176 format = cairo_image_surface_get_format (surface);
177 if (format == CAIRO_FORMAT_ARGB32) {
178 /* see if we can convert to a standard ppm type and trim a few bytes */
179 const unsigned char *alpha = data;
180 for (j = height; j--; alpha += stride) {
181 for (i = 0; i < width; i++) {
182 if ((*(unsigned int *) (alpha+4*i) & 0xff000000) != 0xff000000)
186 format = CAIRO_FORMAT_RGB24;
191 case CAIRO_FORMAT_ARGB32:
192 /* XXX need true alpha for svg */
195 case CAIRO_FORMAT_RGB24:
198 case CAIRO_FORMAT_A8:
201 case CAIRO_FORMAT_A1:
202 case CAIRO_FORMAT_RGB16_565:
203 case CAIRO_FORMAT_RGB30:
204 case CAIRO_FORMAT_INVALID:
206 return "unhandled image format";
209 len = sprintf (buf, "%s %d %d 255\n", format_str, width, height);
210 for (j = 0; j < height; j++) {
211 const unsigned int *row = (unsigned int *) (data + stride * j);
213 switch ((int) format) {
214 case CAIRO_FORMAT_ARGB32:
215 len = _cairo_write (fd,
216 buf, sizeof (buf), len,
217 (unsigned char *) row, 4 * width);
219 case CAIRO_FORMAT_RGB24:
220 for (i = 0; i < width; i++) {
221 unsigned char rgb[3];
222 unsigned int p = *row++;
223 rgb[0] = (p & 0xff0000) >> 16;
224 rgb[1] = (p & 0x00ff00) >> 8;
225 rgb[2] = (p & 0x0000ff) >> 0;
226 len = _cairo_write (fd,
227 buf, sizeof (buf), len,
231 case CAIRO_FORMAT_A8:
232 len = _cairo_write (fd,
233 buf, sizeof (buf), len,
234 (unsigned char *) row, width);
238 return "write failed";
241 if (len && ! _cairo_writen (fd, buf, len))
242 return "write failed";
247 static cairo_surface_t *
248 _create_image (void *closure,
249 cairo_content_t content,
250 double width, double height,
253 cairo_surface_t **out = closure;
254 cairo_format_t format;
256 case CAIRO_CONTENT_ALPHA:
257 format = CAIRO_FORMAT_A8;
259 case CAIRO_CONTENT_COLOR:
260 format = CAIRO_FORMAT_RGB24;
263 case CAIRO_CONTENT_COLOR_ALPHA:
264 format = CAIRO_FORMAT_ARGB32;
267 *out = cairo_image_surface_create (format, width, height);
268 return cairo_surface_reference (*out);
271 #if CAIRO_HAS_INTERPRETER
273 _cairo_script_render_page (const char *filename,
274 cairo_surface_t **surface_out)
276 cairo_script_interpreter_t *csi;
277 cairo_surface_t *surface = NULL;
278 cairo_status_t status;
279 const cairo_script_interpreter_hooks_t hooks = {
282 NULL, /* surface_destroy */
283 NULL, /* context_create */
284 NULL, /* context_destroy */
285 NULL, /* show_page */
289 csi = cairo_script_interpreter_create ();
290 cairo_script_interpreter_install_hooks (csi, &hooks);
291 status = cairo_script_interpreter_run (csi, filename);
293 cairo_surface_destroy (surface);
296 status = cairo_script_interpreter_destroy (csi);
298 return "cairo-script interpreter failed";
300 if (status == CAIRO_STATUS_SUCCESS)
301 status = cairo_surface_status (surface);
303 cairo_surface_destroy (surface);
304 return cairo_status_to_string (status);
307 *surface_out = surface;
312 cs_convert (char **argv, int fd)
315 cairo_surface_t *surface = NULL; /* silence compiler warning */
317 err = _cairo_script_render_page (argv[0], &surface);
321 err = write_ppm (surface, fd);
322 cairo_surface_destroy (surface);
328 cs_convert (char **argv, int fd)
330 return "compiled without CairoScript support.";
334 #if CAIRO_CAN_TEST_PDF_SURFACE
335 /* adapted from pdf2png.c */
337 _poppler_render_page (const char *filename,
338 const char *page_label,
339 cairo_surface_t **surface_out)
341 PopplerDocument *document;
343 double width, height;
344 GError *error = NULL;
345 gchar *absolute, *uri;
346 cairo_surface_t *surface;
348 cairo_status_t status;
350 if (g_path_is_absolute (filename)) {
351 absolute = g_strdup (filename);
353 gchar *dir = g_get_current_dir ();
354 absolute = g_build_filename (dir, filename, (gchar *) 0);
358 uri = g_filename_to_uri (absolute, NULL, &error);
361 return error->message; /* XXX g_error_free (error) */
363 document = poppler_document_new_from_file (uri, NULL, &error);
365 if (document == NULL)
366 return error->message; /* XXX g_error_free (error) */
368 page = poppler_document_get_page_by_label (document, page_label);
369 g_object_unref (document);
371 return "page not found";
373 poppler_page_get_size (page, &width, &height);
375 surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
376 cr = cairo_create (surface);
378 cairo_set_source_rgb (cr, 1., 1., 1.);
380 cairo_push_group_with_content (cr, CAIRO_CONTENT_COLOR_ALPHA);
382 poppler_page_render (page, cr);
383 g_object_unref (page);
385 cairo_pop_group_to_source (cr);
388 status = cairo_status (cr);
392 cairo_surface_destroy (surface);
393 return cairo_status_to_string (status);
396 *surface_out = surface;
401 pdf_convert (char **argv, int fd)
404 cairo_surface_t *surface = NULL; /* silence compiler warning */
406 err = _poppler_render_page (argv[0], argv[1], &surface);
410 err = write_ppm (surface, fd);
411 cairo_surface_destroy (surface);
417 pdf_convert (char **argv, int fd)
419 return "compiled without PDF support.";
423 #if CAIRO_CAN_TEST_SVG_SURFACE
425 _rsvg_render_page (const char *filename,
426 cairo_surface_t **surface_out)
429 RsvgDimensionData dimensions;
430 GError *error = NULL;
431 cairo_surface_t *surface;
433 cairo_status_t status;
435 handle = rsvg_handle_new_from_file (filename, &error);
437 return error->message; /* XXX g_error_free */
439 rsvg_handle_get_dimensions (handle, &dimensions);
440 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
443 cr = cairo_create (surface);
445 rsvg_handle_render_cairo (handle, cr);
446 g_object_unref (handle);
448 status = cairo_status (cr);
452 cairo_surface_destroy (surface);
453 return cairo_status_to_string (status);
456 *surface_out = surface;
461 svg_convert (char **argv, int fd)
464 cairo_surface_t *surface = NULL; /* silence compiler warning */
466 err = _rsvg_render_page (argv[0], &surface);
470 err = write_ppm (surface, fd);
471 cairo_surface_destroy (surface);
477 svg_convert (char **argv, int fd)
479 return "compiled without SVG support.";
483 #if CAIRO_HAS_SPECTRE
485 _spectre_render_page (const char *filename,
486 const char *page_label,
487 cairo_surface_t **surface_out)
489 static const cairo_user_data_key_t key;
491 SpectreDocument *document;
492 SpectreStatus status;
493 int width, height, stride;
494 unsigned char *pixels;
495 cairo_surface_t *surface;
497 document = spectre_document_new ();
498 spectre_document_load (document, filename);
499 status = spectre_document_status (document);
501 spectre_document_free (document);
502 return spectre_status_to_string (status);
507 SpectreRenderContext *rc;
509 page = spectre_document_get_page_by_label (document, page_label);
510 spectre_document_free (document);
512 return "page not found";
514 spectre_page_get_size (page, &width, &height);
515 rc = spectre_render_context_new ();
516 spectre_render_context_set_page_size (rc, width, height);
517 spectre_page_render (page, rc, &pixels, &stride);
518 spectre_render_context_free (rc);
519 status = spectre_page_status (page);
520 spectre_page_free (page);
523 return spectre_status_to_string (status);
526 spectre_document_get_page_size (document, &width, &height);
527 spectre_document_render (document, &pixels, &stride);
528 spectre_document_free (document);
531 surface = cairo_image_surface_create_for_data (pixels,
535 cairo_surface_set_user_data (surface, &key,
536 pixels, (cairo_destroy_func_t) free);
537 *surface_out = surface;
542 ps_convert (char **argv, int fd)
545 cairo_surface_t *surface = NULL; /* silence compiler warning */
547 err = _spectre_render_page (argv[0], argv[1], &surface);
551 err = write_ppm (surface, fd);
552 cairo_surface_destroy (surface);
558 ps_convert (char **argv, int fd)
560 return "compiled without PostScript support.";
565 convert (char **argv, int fd)
567 static const struct converter {
569 const char *(*func) (char **, int);
571 { "cs", cs_convert },
572 { "pdf", pdf_convert },
573 { "ps", ps_convert },
574 { "svg", svg_convert },
577 const struct converter *converter = converters;
580 type = strrchr (argv[0], '.');
582 return "no file extension";
585 while (converter->type) {
586 if (strcmp (type, converter->type) == 0)
587 return converter->func (argv, fd);
590 return "no converter";
593 #if CAN_RUN_AS_DAEMON
595 _getline (int fd, char **linep, size_t *lenp)
603 line = malloc (1024);
611 /* XXX simple, but ugly! */
617 nline = realloc (line, len + 1024);
625 ret = read (fd, line + i, 1);
626 if (ret == -1 || ret == 0)
628 } while (line[i++] != '\n');
638 split_line (char *line, char *argv[], int max_argc)
642 max_argc--; /* leave one spare for the trailing NULL */
645 while (i < max_argc && (line = strchr (line, ' ')) != NULL) {
650 /* chomp the newline */
651 line = strchr (argv[i-1], '\n');
661 any2ppm_daemon_exists (void)
669 if (stat (SOCKET_PATH, &st) < 0)
672 fd = open (SOCKET_PATH ".pid", O_RDONLY);
677 ret = read (fd, buf, sizeof (buf) - 1);
684 return pid > 0 && kill (pid, 0) == 0;
688 write_pid_file (void)
694 fd = open (SOCKET_PATH ".pid", O_CREAT | O_TRUNC | O_WRONLY, 0666);
698 ret = sprintf (buf, "%d\n", getpid ());
699 ret = write (fd, buf, ret) == ret;
706 open_devnull_to_fd (int want_fd, int flags)
713 got_fd = open("/dev/null", flags | O_CREAT, 0700);
717 error = dup2 (got_fd, want_fd);
726 void (*oldhup) (int);
728 /* Let the parent go. */
735 /* Become session leader. */
739 /* Refork to yield session leadership. */
740 oldhup = signal (SIGHUP, SIG_IGN);
742 switch (fork ()) { /* refork to yield session leadership. */
748 signal (SIGHUP, oldhup);
750 /* Establish stdio. */
751 if (open_devnull_to_fd (0, O_RDONLY) == -1)
753 if (open_devnull_to_fd (1, O_WRONLY | O_APPEND) == -1)
755 if (dup2 (1, 2) == -1)
762 any2ppm_daemon (void)
764 int timeout = TIMEOUT;
768 struct sockaddr_un addr;
773 signal (SIGPIPE, SIG_IGN);
777 if (getenv ("ANY2PPM_FORCE") == NULL && any2ppm_daemon_exists ())
778 return "any2ppm daemon already running";
780 unlink (SOCKET_PATH);
782 sk = socket (PF_UNIX, SOCK_STREAM, 0);
784 return "unable to create socket";
786 memset (&addr, 0, sizeof (addr));
787 addr.sun_family = AF_UNIX;
788 strcpy (addr.sun_path, SOCKET_PATH);
789 if (bind (sk, (struct sockaddr *) &addr, sizeof (addr)) == -1) {
791 return "unable to bind socket";
794 flags = fcntl (sk, F_GETFL);
795 if (flags == -1 || fcntl (sk, F_SETFL, flags | O_NONBLOCK) == -1) {
797 return "unable to set socket to non-blocking";
800 if (listen (sk, 5) == -1) {
802 return "unable to listen on socket";
805 /* ready for client connection - detach from parent/terminal */
806 if (getenv ("ANY2PPM_NODAEMON") == NULL && daemonize () == -1) {
808 return "unable to detach from parent";
811 if (! write_pid_file ()) {
813 return "unable to write pid file";
816 if (getenv ("ANY2PPM_TIMEOUT") != NULL) {
817 timeout = atoi (getenv ("ANY2PPM_TIMEOUT"));
821 timeout *= 1000; /* convert env (in seconds) to milliseconds */
826 pfd.revents = 0; /* valgrind */
827 while (poll (&pfd, 1, timeout) > 0) {
828 while ((fd = accept (sk, NULL, NULL)) != -1) {
829 if (_getline (fd, &line, &len) != -1) {
832 if (split_line (line, argv, ARRAY_LENGTH (argv)) > 0) {
835 err = convert (argv, fd);
837 FILE *file = fopen (".any2ppm.errors", "a");
840 "Failed to convert '%s': %s\n",
851 unlink (SOCKET_PATH);
852 unlink (SOCKET_PATH ".pid");
859 any2ppm_daemon (void)
861 return "daemon not compiled in.";
866 main (int argc, char **argv)
870 #if CAIRO_CAN_TEST_PDF_SURFACE || CAIRO_CAN_TEST_SVG_SURFACE
871 #if GLIB_MAJOR_VERSION <= 2 && GLIB_MINOR_VERSION <= 34
876 #if CAIRO_CAN_TEST_SVG_SURFACE
877 rsvg_set_default_dpi (72.0);
880 #if defined(_WIN32) && !defined (__CYGWIN__)
881 _setmode (1, _O_BINARY);
885 err = any2ppm_daemon ();
887 err = convert (argv + 1, 1);
889 fprintf (stderr, "Failed to run converter: %s\n", err);