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>
73 #include <librsvg/rsvg-cairo.h>
77 #include <libspectre/spectre.h>
82 #if HAVE_UNISTD_H && HAVE_FCNTL_H && HAVE_SIGNAL_H && HAVE_SYS_STAT_H && HAVE_SYS_SOCKET_H && HAVE_SYS_POLL_H && HAVE_SYS_UN_H
86 #include <sys/socket.h>
90 #define SOCKET_PATH "./.any2ppm"
91 #define TIMEOUT 60000 /* 60 seconds */
93 #define CAN_RUN_AS_DAEMON 1
96 #define ARRAY_LENGTH(A) (sizeof (A) / sizeof (A[0]))
99 _cairo_writen (int fd, char *buf, int len)
104 ret = write (fd, buf, len);
123 _cairo_write (int fd,
124 char *buf, int maxlen, int buflen,
125 const unsigned char *src, int srclen)
133 len = buflen + srclen;
138 memcpy (buf + buflen, src, len);
143 if (buflen == maxlen) {
144 if (! _cairo_writen (fd, buf, buflen))
155 write_ppm (cairo_surface_t *surface, int fd)
158 cairo_format_t format;
159 const char *format_str;
160 const unsigned char *data;
162 int width, height, stride;
165 data = cairo_image_surface_get_data (surface);
166 height = cairo_image_surface_get_height (surface);
167 width = cairo_image_surface_get_width (surface);
168 stride = cairo_image_surface_get_stride (surface);
169 format = cairo_image_surface_get_format (surface);
170 if (format == CAIRO_FORMAT_ARGB32) {
171 /* see if we can convert to a standard ppm type and trim a few bytes */
172 const unsigned char *alpha = data;
173 for (j = height; j--; alpha += stride) {
174 for (i = 0; i < width; i++) {
175 if ((*(unsigned int *) (alpha+4*i) & 0xff000000) != 0xff000000)
179 format = CAIRO_FORMAT_RGB24;
184 case CAIRO_FORMAT_ARGB32:
185 /* XXX need true alpha for svg */
188 case CAIRO_FORMAT_RGB24:
191 case CAIRO_FORMAT_A8:
194 case CAIRO_FORMAT_A1:
195 case CAIRO_FORMAT_RGB16_565:
196 case CAIRO_FORMAT_RGB30:
197 case CAIRO_FORMAT_INVALID:
199 return "unhandled image format";
202 len = sprintf (buf, "%s %d %d 255\n", format_str, width, height);
203 for (j = 0; j < height; j++) {
204 const unsigned int *row = (unsigned int *) (data + stride * j);
206 switch ((int) format) {
207 case CAIRO_FORMAT_ARGB32:
208 len = _cairo_write (fd,
209 buf, sizeof (buf), len,
210 (unsigned char *) row, 4 * width);
212 case CAIRO_FORMAT_RGB24:
213 for (i = 0; i < width; i++) {
214 unsigned char rgb[3];
215 unsigned int p = *row++;
216 rgb[0] = (p & 0xff0000) >> 16;
217 rgb[1] = (p & 0x00ff00) >> 8;
218 rgb[2] = (p & 0x0000ff) >> 0;
219 len = _cairo_write (fd,
220 buf, sizeof (buf), len,
224 case CAIRO_FORMAT_A8:
225 len = _cairo_write (fd,
226 buf, sizeof (buf), len,
227 (unsigned char *) row, width);
231 return "write failed";
234 if (len && ! _cairo_writen (fd, buf, len))
235 return "write failed";
240 static cairo_surface_t *
241 _create_image (void *closure,
242 cairo_content_t content,
243 double width, double height,
246 cairo_surface_t **out = closure;
247 cairo_format_t format;
249 case CAIRO_CONTENT_ALPHA:
250 format = CAIRO_FORMAT_A8;
252 case CAIRO_CONTENT_COLOR:
253 format = CAIRO_FORMAT_RGB24;
256 case CAIRO_CONTENT_COLOR_ALPHA:
257 format = CAIRO_FORMAT_ARGB32;
260 *out = cairo_image_surface_create (format, width, height);
261 return cairo_surface_reference (*out);
264 #if CAIRO_HAS_INTERPRETER
266 _cairo_script_render_page (const char *filename,
267 cairo_surface_t **surface_out)
269 cairo_script_interpreter_t *csi;
270 cairo_surface_t *surface = NULL;
271 cairo_status_t status;
272 const cairo_script_interpreter_hooks_t hooks = {
275 NULL, /* surface_destroy */
276 NULL, /* context_create */
277 NULL, /* context_destroy */
278 NULL, /* show_page */
282 csi = cairo_script_interpreter_create ();
283 cairo_script_interpreter_install_hooks (csi, &hooks);
284 status = cairo_script_interpreter_run (csi, filename);
286 cairo_surface_destroy (surface);
289 status = cairo_script_interpreter_destroy (csi);
291 return "cairo-script interpreter failed";
293 if (status == CAIRO_STATUS_SUCCESS)
294 status = cairo_surface_status (surface);
296 cairo_surface_destroy (surface);
297 return cairo_status_to_string (status);
300 *surface_out = surface;
305 cs_convert (char **argv, int fd)
308 cairo_surface_t *surface = NULL; /* silence compiler warning */
310 err = _cairo_script_render_page (argv[0], &surface);
314 err = write_ppm (surface, fd);
315 cairo_surface_destroy (surface);
321 cs_convert (char **argv, int fd)
323 return "compiled without CairoScript support.";
327 #if CAIRO_CAN_TEST_PDF_SURFACE
328 /* adapted from pdf2png.c */
330 _poppler_render_page (const char *filename,
331 const char *page_label,
332 cairo_surface_t **surface_out)
334 PopplerDocument *document;
336 double width, height;
337 GError *error = NULL;
338 gchar *absolute, *uri;
339 cairo_surface_t *surface;
341 cairo_status_t status;
343 if (g_path_is_absolute (filename)) {
344 absolute = g_strdup (filename);
346 gchar *dir = g_get_current_dir ();
347 absolute = g_build_filename (dir, filename, (gchar *) 0);
351 uri = g_filename_to_uri (absolute, NULL, &error);
354 return error->message; /* XXX g_error_free (error) */
356 document = poppler_document_new_from_file (uri, NULL, &error);
358 if (document == NULL)
359 return error->message; /* XXX g_error_free (error) */
361 page = poppler_document_get_page_by_label (document, page_label);
362 g_object_unref (document);
364 return "page not found";
366 poppler_page_get_size (page, &width, &height);
368 surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
369 cr = cairo_create (surface);
371 cairo_set_source_rgb (cr, 1., 1., 1.);
373 cairo_push_group_with_content (cr, CAIRO_CONTENT_COLOR_ALPHA);
375 poppler_page_render (page, cr);
376 g_object_unref (page);
378 cairo_pop_group_to_source (cr);
381 status = cairo_status (cr);
385 cairo_surface_destroy (surface);
386 return cairo_status_to_string (status);
389 *surface_out = surface;
394 pdf_convert (char **argv, int fd)
397 cairo_surface_t *surface = NULL; /* silence compiler warning */
399 err = _poppler_render_page (argv[0], argv[1], &surface);
403 err = write_ppm (surface, fd);
404 cairo_surface_destroy (surface);
410 pdf_convert (char **argv, int fd)
412 return "compiled without PDF support.";
416 #if CAIRO_CAN_TEST_SVG_SURFACE
418 _rsvg_render_page (const char *filename,
419 cairo_surface_t **surface_out)
422 RsvgDimensionData dimensions;
423 GError *error = NULL;
424 cairo_surface_t *surface;
426 cairo_status_t status;
428 handle = rsvg_handle_new_from_file (filename, &error);
430 return error->message; /* XXX g_error_free */
432 rsvg_handle_get_dimensions (handle, &dimensions);
433 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
436 cr = cairo_create (surface);
438 rsvg_handle_render_cairo (handle, cr);
439 g_object_unref (handle);
441 status = cairo_status (cr);
445 cairo_surface_destroy (surface);
446 return cairo_status_to_string (status);
449 *surface_out = surface;
454 svg_convert (char **argv, int fd)
457 cairo_surface_t *surface = NULL; /* silence compiler warning */
459 err = _rsvg_render_page (argv[0], &surface);
463 err = write_ppm (surface, fd);
464 cairo_surface_destroy (surface);
470 svg_convert (char **argv, int fd)
472 return "compiled without SVG support.";
476 #if CAIRO_HAS_SPECTRE
478 _spectre_render_page (const char *filename,
479 const char *page_label,
480 cairo_surface_t **surface_out)
482 static const cairo_user_data_key_t key;
484 SpectreDocument *document;
485 SpectreStatus status;
486 int width, height, stride;
487 unsigned char *pixels;
488 cairo_surface_t *surface;
490 document = spectre_document_new ();
491 spectre_document_load (document, filename);
492 status = spectre_document_status (document);
494 spectre_document_free (document);
495 return spectre_status_to_string (status);
500 SpectreRenderContext *rc;
502 page = spectre_document_get_page_by_label (document, page_label);
503 spectre_document_free (document);
505 return "page not found";
507 spectre_page_get_size (page, &width, &height);
508 rc = spectre_render_context_new ();
509 spectre_render_context_set_page_size (rc, width, height);
510 spectre_page_render (page, rc, &pixels, &stride);
511 spectre_render_context_free (rc);
512 status = spectre_page_status (page);
513 spectre_page_free (page);
516 return spectre_status_to_string (status);
519 spectre_document_get_page_size (document, &width, &height);
520 spectre_document_render (document, &pixels, &stride);
521 spectre_document_free (document);
524 surface = cairo_image_surface_create_for_data (pixels,
528 cairo_surface_set_user_data (surface, &key,
529 pixels, (cairo_destroy_func_t) free);
530 *surface_out = surface;
535 ps_convert (char **argv, int fd)
538 cairo_surface_t *surface = NULL; /* silence compiler warning */
540 err = _spectre_render_page (argv[0], argv[1], &surface);
544 err = write_ppm (surface, fd);
545 cairo_surface_destroy (surface);
551 ps_convert (char **argv, int fd)
553 return "compiled without PostScript support.";
558 convert (char **argv, int fd)
560 static const struct converter {
562 const char *(*func) (char **, int);
564 { "cs", cs_convert },
565 { "pdf", pdf_convert },
566 { "ps", ps_convert },
567 { "svg", svg_convert },
570 const struct converter *converter = converters;
573 type = strrchr (argv[0], '.');
575 return "no file extension";
578 while (converter->type) {
579 if (strcmp (type, converter->type) == 0)
580 return converter->func (argv, fd);
583 return "no converter";
586 #if CAN_RUN_AS_DAEMON
588 _getline (int fd, char **linep, size_t *lenp)
596 line = malloc (1024);
604 /* XXX simple, but ugly! */
610 nline = realloc (line, len + 1024);
618 ret = read (fd, line + i, 1);
619 if (ret == -1 || ret == 0)
621 } while (line[i++] != '\n');
631 split_line (char *line, char *argv[], int max_argc)
635 max_argc--; /* leave one spare for the trailing NULL */
638 while (i < max_argc && (line = strchr (line, ' ')) != NULL) {
643 /* chomp the newline */
644 line = strchr (argv[i-1], '\n');
654 any2ppm_daemon_exists (void)
662 if (stat (SOCKET_PATH, &st) < 0)
665 fd = open (SOCKET_PATH ".pid", O_RDONLY);
670 ret = read (fd, buf, sizeof (buf) - 1);
677 return pid > 0 && kill (pid, 0) == 0;
681 write_pid_file (void)
687 fd = open (SOCKET_PATH ".pid", O_CREAT | O_TRUNC | O_WRONLY, 0666);
691 ret = sprintf (buf, "%d\n", getpid ());
692 ret = write (fd, buf, ret) == ret;
699 open_devnull_to_fd (int want_fd, int flags)
706 got_fd = open("/dev/null", flags | O_CREAT, 0700);
710 error = dup2 (got_fd, want_fd);
719 void (*oldhup) (int);
721 /* Let the parent go. */
728 /* Become session leader. */
732 /* Refork to yield session leadership. */
733 oldhup = signal (SIGHUP, SIG_IGN);
735 switch (fork ()) { /* refork to yield session leadership. */
741 signal (SIGHUP, oldhup);
743 /* Establish stdio. */
744 if (open_devnull_to_fd (0, O_RDONLY) == -1)
746 if (open_devnull_to_fd (1, O_WRONLY | O_APPEND) == -1)
748 if (dup2 (1, 2) == -1)
755 any2ppm_daemon (void)
757 int timeout = TIMEOUT;
761 struct sockaddr_un addr;
766 signal (SIGPIPE, SIG_IGN);
770 if (getenv ("ANY2PPM_FORCE") == NULL && any2ppm_daemon_exists ())
771 return "any2ppm daemon already running";
773 unlink (SOCKET_PATH);
775 sk = socket (PF_UNIX, SOCK_STREAM, 0);
777 return "unable to create socket";
779 memset (&addr, 0, sizeof (addr));
780 addr.sun_family = AF_UNIX;
781 strcpy (addr.sun_path, SOCKET_PATH);
782 if (bind (sk, (struct sockaddr *) &addr, sizeof (addr)) == -1) {
784 return "unable to bind socket";
787 flags = fcntl (sk, F_GETFL);
788 if (flags == -1 || fcntl (sk, F_SETFL, flags | O_NONBLOCK) == -1) {
790 return "unable to set socket to non-blocking";
793 if (listen (sk, 5) == -1) {
795 return "unable to listen on socket";
798 /* ready for client connection - detach from parent/terminal */
799 if (getenv ("ANY2PPM_NODAEMON") == NULL && daemonize () == -1) {
801 return "unable to detach from parent";
804 if (! write_pid_file ()) {
806 return "unable to write pid file";
809 if (getenv ("ANY2PPM_TIMEOUT") != NULL) {
810 timeout = atoi (getenv ("ANY2PPM_TIMEOUT"));
814 timeout *= 1000; /* convert env (in seconds) to milliseconds */
819 pfd.revents = 0; /* valgrind */
820 while (poll (&pfd, 1, timeout) > 0) {
821 while ((fd = accept (sk, NULL, NULL)) != -1) {
822 if (_getline (fd, &line, &len) != -1) {
825 if (split_line (line, argv, ARRAY_LENGTH (argv)) > 0) {
828 err = convert (argv, fd);
830 FILE *file = fopen (".any2ppm.errors", "a");
833 "Failed to convert '%s': %s\n",
844 unlink (SOCKET_PATH);
845 unlink (SOCKET_PATH ".pid");
852 any2ppm_daemon (void)
854 return "daemon not compiled in.";
859 main (int argc, char **argv)
863 #if CAIRO_CAN_TEST_PDF_SURFACE || CAIRO_CAN_TEST_SVG_SURFACE
867 #if CAIRO_CAN_TEST_SVG_SURFACE
869 rsvg_set_default_dpi (72.0);
873 err = any2ppm_daemon ();
875 err = convert (argv + 1, 1);
877 fprintf (stderr, "Failed to run converter: %s\n", err);