1 /* $Id$ $Revision$ */
2 /* vim:set shiftwidth=4 ts=8: */
3 
4 /*************************************************************************
5  * Copyright (c) 2011 AT&T Intellectual Property
6  * All rights reserved. This program and the accompanying materials
7  * are made available under the terms of the Eclipse Public License v1.0
8  * which accompanies this distribution, and is available at
9  * http://www.eclipse.org/legal/epl-v10.html
10  *
11  * Contributors: See CVS logs. Details at http://www.graphviz.org/
12  *************************************************************************/
13 
14 #include "config.h"
15 
16 #include <stdlib.h>
17 #include <stdint.h>
18 #include <sys/stat.h>
19 
20 #include "gvplugin_loadimage.h"
21 
22 #ifdef HAVE_GS
23 #ifdef HAVE_PANGOCAIRO
24 #include <ghostscript/iapi.h>
25 #include <ghostscript/ierrors.h>
26 #include <cairo/cairo.h>
27 
28 
29 /**
30  * Ensure compatibility with Ghostscipt versions newer than 9.18
31  * while maintaining compatibility with the older versions.
32  **/
33 
34 #ifndef e_VMerror
35 #define e_VMerror gs_error_VMerror
36 #endif
37 
38 #ifndef e_unregistered
39 #define e_unregistered gs_error_unregistered
40 #endif
41 
42 #ifndef e_invalidid
43 #define e_invalidid gs_error_invalidid
44 #endif
45 
46 #ifdef _WIN32
47 #define NUL_FILE "nul"
48 #else
49 #define NUL_FILE "/dev/null"
50 #endif
51 
52 typedef enum {
53     FORMAT_PS_CAIRO, FORMAT_EPS_CAIRO,
54 } format_type;
55 
56 typedef struct gs_s {
57     cairo_t* cr;
58     cairo_surface_t* surface;
59     cairo_pattern_t* pattern;
60 } gs_t;
61 
gvloadimage_gs_free(usershape_t * us)62 static void gvloadimage_gs_free(usershape_t *us)
63 {
64     gs_t *gs = (gs_t*)us->data;
65 
66     if (gs->pattern) cairo_pattern_destroy(gs->pattern);
67     if (gs->surface) cairo_surface_destroy(gs->surface);
68     free(gs);
69 }
70 
gs_writer(void * caller_handle,const char * str,int len)71 static int gs_writer(void *caller_handle, const char *str, int len)
72 {
73     GVJ_t *job = (GVJ_t*)caller_handle;
74 
75     if (job->common->verbose)
76     	return fwrite(str, 1, len, stderr);
77     return len;
78 }
79 
gs_error(GVJ_t * job,const char * name,const char * funstr,int err)80 static void gs_error(GVJ_t * job, const char *name, const char *funstr, int err)
81 {
82     const char *errsrc;
83 
84     assert (err < 0);
85 
86     if (err >= e_VMerror)
87 	errsrc = "PostScript Level 1";
88     else if (err >= e_unregistered)
89 	errsrc = "PostScript Level 2";
90     else if (err >= e_invalidid)
91 	errsrc = "DPS error";
92     else
93 	errsrc = "Ghostscript internal error";
94 
95     job->common->errorfn("%s: %s() returned: %d \"%s\" (%s)\n",
96 		name, funstr, err, gs_error_names[-err - 1], errsrc);
97 }
98 
gvloadimage_process_file(GVJ_t * job,usershape_t * us,void * instance)99 static int gvloadimage_process_file(GVJ_t *job, usershape_t *us, void *instance)
100 {
101     int rc = 0, exit_code;
102 
103     if (! gvusershape_file_access(us)) {
104 	job->common->errorfn("Failure to read shape file\n");
105 	return -1;
106     }
107     rc = gsapi_run_file(instance, us->name, -1, &exit_code);
108     if (rc) {
109 	gs_error(job, us->name, "gsapi_run_file", rc);
110     }
111     gvusershape_file_release(us);
112     return rc;
113 }
114 
gvloadimage_process_surface(GVJ_t * job,usershape_t * us,gs_t * gs,void * instance)115 static int gvloadimage_process_surface(GVJ_t *job, usershape_t *us, gs_t *gs, void *instance)
116 {
117     cairo_t *cr; /* temp cr for gs */
118     int rc, rc2;
119     char width_height[20], dpi[10], cairo_context[30];
120     char *gs_args[] = {
121 	"dot",      /* actual value of argv[0] doesn't matter */
122 	"-dQUIET",
123 	"-dNOPAUSE",
124 	"-sDEVICE=cairo",
125 	cairo_context,
126 	width_height,
127 	dpi,
128     };
129 #define GS_ARGC sizeof(gs_args)/sizeof(gs_args[0])
130 
131     gs->surface = cairo_surface_create_similar(
132 	cairo_get_target(gs->cr),
133 	CAIRO_CONTENT_COLOR_ALPHA,
134 	us->x + us->w,
135 	us->y + us->h);
136 
137     cr = cairo_create(gs->surface);  /* temp context for gs */
138 
139     sprintf(width_height, "-g%dx%d", us->x + us->w, us->y + us->h);
140     sprintf(dpi, "-r%d", us->dpi);
141     sprintf(cairo_context, "-sCairoContext=%p", cr);
142 
143     rc = gsapi_init_with_args(instance, GS_ARGC, gs_args);
144 
145     cairo_destroy(cr); /* finished with temp context */
146 
147     if (rc)
148 	gs_error(job, us->name, "gsapi_init_with_args", rc);
149     else
150 	rc = gvloadimage_process_file(job, us, instance);
151 
152     if (rc) {
153 	cairo_surface_destroy(gs->surface);
154 	gs->surface = NULL;
155     }
156 
157     rc2 = gsapi_exit(instance);
158     if (rc2) {
159 	gs_error(job, us->name, "gsapi_exit", rc2);
160 	return rc2;
161     }
162 
163     if (!rc)
164         gs->pattern = cairo_pattern_create_for_surface (gs->surface);
165 
166     return rc;
167 }
168 
gvloadimage_gs_load(GVJ_t * job,usershape_t * us)169 static cairo_pattern_t* gvloadimage_gs_load(GVJ_t * job, usershape_t *us)
170 {
171     gs_t *gs = NULL;
172     gsapi_revision_t gsapi_revision_info;
173     void *instance;
174     int rc;
175 
176     assert(job);
177     assert(us);
178     assert(us->name);
179 
180     if (us->data) {
181         if (us->datafree == gvloadimage_gs_free
182 	&& ((gs_t*)(us->data))->cr == (cairo_t *)job->context)
183 	    gs = (gs_t*)(us->data); /* use cached data */
184 	else {
185 	    us->datafree(us);        /* free incompatible cache data */
186 	    us->data = NULL;
187 	}
188     }
189     if (!gs) {
190 	gs = (gs_t *)malloc(sizeof(gs_t));
191 	if (!gs) {
192 	    job->common->errorfn("malloc() failure\n");
193 	    return NULL;
194 	}
195 	gs->cr = (cairo_t *)job->context;
196 	gs->surface = NULL;
197 	gs->pattern = NULL;
198 
199 	/* cache this - even if things go bad below - avoids repeats */
200 	us->data = (void*)gs;
201 	us->datafree = gvloadimage_gs_free;
202 
203 #define GSAPI_REVISION_REQUIRED 863
204 	rc = gsapi_revision(&gsapi_revision_info, sizeof(gsapi_revision_t));
205         if (rc && rc < sizeof(gsapi_revision_t)) {
206     	    job->common->errorfn("gs revision - struct too short %d\n", rc);
207 	    return NULL;
208         }
209 	if (gsapi_revision_info.revision < GSAPI_REVISION_REQUIRED) {
210     	    job->common->errorfn("gs revision - too old %d\n",
211 		gsapi_revision_info.revision);
212 	    return NULL;
213 	}
214 
215 	rc = gsapi_new_instance(&instance, (void*)job);
216 	if (rc)
217 	    gs_error(job, us->name, "gsapi_new_instance", rc);
218 	else {
219 	    rc = gsapi_set_stdio(instance, NULL, gs_writer, gs_writer);
220 	    if (rc)
221 	        gs_error(job, us->name, "gsapi_set_stdio", rc);
222 	    else
223                 rc = gvloadimage_process_surface(job, us, gs, instance);
224 	    gsapi_delete_instance(instance);
225 	}
226     }
227     return gs->pattern;
228 }
229 
gvloadimage_gs_cairo(GVJ_t * job,usershape_t * us,boxf b,boolean filled)230 static void gvloadimage_gs_cairo(GVJ_t * job, usershape_t *us, boxf b, boolean filled)
231 {
232     cairo_t *cr = (cairo_t *) job->context; /* target context */
233     cairo_pattern_t *pattern = gvloadimage_gs_load(job, us);
234 
235     if (pattern) {
236         cairo_save(cr);
237 	cairo_translate(cr, b.LL.x - us->x, -b.UR.y);
238 	cairo_scale(cr, (b.UR.x - b.LL.x) / us->w, (b.UR.y - b.LL.y) / us->h);
239         cairo_set_source(cr, pattern);
240 	cairo_paint(cr);
241         cairo_restore(cr);
242     }
243 }
244 
245 static gvloadimage_engine_t engine_cairo = {
246     gvloadimage_gs_cairo
247 };
248 #endif
249 #endif
250 
251 gvplugin_installed_t gvloadimage_gs_types[] = {
252 #ifdef HAVE_GS
253 #ifdef HAVE_PANGOCAIRO
254     {FORMAT_PS_CAIRO,   "ps:cairo", 1, &engine_cairo, NULL},
255     {FORMAT_EPS_CAIRO, "eps:cairo", 1, &engine_cairo, NULL},
256 #endif
257 #endif
258     {0, NULL, 0, NULL, NULL}
259 };
260