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