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 | #ifndef _WIN32 |
15 | #include <unistd.h> |
16 | #endif |
17 | |
18 | #include <sys/stat.h> |
19 | |
20 | #include "render.h" |
21 | #include "gvio.h" |
22 | |
23 | static int N_EPSF_files; |
24 | static Dict_t *EPSF_contents; |
25 | |
26 | static void ps_image_free(Dict_t * dict, usershape_t * p, Dtdisc_t * disc) |
27 | { |
28 | free(p->data); |
29 | } |
30 | |
31 | static Dtdisc_t ImageDictDisc = { |
32 | offsetof(usershape_t, name),/* key */ |
33 | -1, /* size */ |
34 | 0, /* link offset */ |
35 | NIL(Dtmake_f), |
36 | (Dtfree_f) ps_image_free, |
37 | NIL(Dtcompar_f), |
38 | NIL(Dthash_f), |
39 | NIL(Dtmemory_f), |
40 | NIL(Dtevent_f) |
41 | }; |
42 | |
43 | static usershape_t *user_init(const char *str) |
44 | { |
45 | char *contents; |
46 | char line[BUFSIZ]; |
47 | FILE *fp; |
48 | struct stat statbuf; |
49 | int saw_bb, must_inline, rc; |
50 | int lx, ly, ux, uy; |
51 | usershape_t *us; |
52 | |
53 | if (!EPSF_contents) |
54 | EPSF_contents = dtopen(&ImageDictDisc, Dtoset); |
55 | |
56 | us = dtmatch(EPSF_contents, str); |
57 | if (us) |
58 | return us; |
59 | |
60 | if (!(fp = fopen(str, "r" ))) { |
61 | agerr(AGWARN, "couldn't open epsf file %s\n" , str); |
62 | return NULL; |
63 | } |
64 | /* try to find size */ |
65 | saw_bb = must_inline = FALSE; |
66 | while (fgets(line, sizeof(line), fp)) { |
67 | if (sscanf |
68 | (line, "%%%%BoundingBox: %d %d %d %d" , &lx, &ly, &ux, &uy) == 4) { |
69 | saw_bb = TRUE; |
70 | } |
71 | if ((line[0] != '%') && strstr(line,"read" )) must_inline = TRUE; |
72 | if (saw_bb && must_inline) break; |
73 | } |
74 | |
75 | if (saw_bb) { |
76 | us = GNEW(usershape_t); |
77 | us->x = lx; |
78 | us->y = ly; |
79 | us->w = ux - lx; |
80 | us->y = uy - ly; |
81 | us->name = str; |
82 | us->macro_id = N_EPSF_files++; |
83 | fstat(fileno(fp), &statbuf); |
84 | contents = us->data = N_GNEW(statbuf.st_size + 1, char); |
85 | fseek(fp, 0, SEEK_SET); |
86 | rc = fread(contents, statbuf.st_size, 1, fp); |
87 | contents[statbuf.st_size] = '\0'; |
88 | dtinsert(EPSF_contents, us); |
89 | us->must_inline = must_inline; |
90 | } else { |
91 | agerr(AGWARN, "BoundingBox not found in epsf file %s\n" , str); |
92 | us = NULL; |
93 | } |
94 | fclose(fp); |
95 | return us; |
96 | } |
97 | |
98 | void epsf_init(node_t * n) |
99 | { |
100 | epsf_t *desc; |
101 | const char *str; |
102 | usershape_t *us; |
103 | int dx, dy; |
104 | |
105 | if ((str = safefile(agget(n, "shapefile" )))) { |
106 | us = user_init(str); |
107 | if (!us) |
108 | return; |
109 | dx = us->w; |
110 | dy = us->h; |
111 | ND_width(n) = PS2INCH(dx); |
112 | ND_height(n) = PS2INCH(dy); |
113 | ND_shape_info(n) = desc = NEW(epsf_t); |
114 | desc->macro_id = us->macro_id; |
115 | desc->offset.x = -us->x - (dx) / 2; |
116 | desc->offset.y = -us->y - (dy) / 2; |
117 | } else |
118 | agerr(AGWARN, "shapefile not set or not found for epsf node %s\n" , agnameof(n)); |
119 | } |
120 | |
121 | void epsf_free(node_t * n) |
122 | { |
123 | |
124 | if (ND_shape_info(n)) |
125 | free(ND_shape_info(n)); |
126 | } |
127 | |
128 | |
129 | /* cat_libfile: |
130 | * Write library files onto the given file pointer. |
131 | * arglib is an NULL-terminated array of char* |
132 | * Each non-trivial entry should be the name of a file to be included. |
133 | * stdlib is an NULL-terminated array of char* |
134 | * Each of these is a line of a standard library to be included. |
135 | * If any item in arglib is the empty string, the stdlib is not used. |
136 | * The stdlib is printed first, if used, followed by the user libraries. |
137 | * We check that for web-safe file usage. |
138 | */ |
139 | void cat_libfile(GVJ_t * job, const char **arglib, const char **stdlib) |
140 | { |
141 | FILE *fp; |
142 | const char **s, *bp, *p, *path; |
143 | int i; |
144 | boolean use_stdlib = TRUE; |
145 | |
146 | /* check for empty string to turn off stdlib */ |
147 | if (arglib) { |
148 | for (i = 0; use_stdlib && ((p = arglib[i])); i++) { |
149 | if (*p == '\0') |
150 | use_stdlib = FALSE; |
151 | } |
152 | } |
153 | if (use_stdlib) |
154 | for (s = stdlib; *s; s++) { |
155 | gvputs(job, *s); |
156 | gvputs(job, "\n" ); |
157 | } |
158 | if (arglib) { |
159 | for (i = 0; (p = arglib[i]) != 0; i++) { |
160 | if (*p == '\0') |
161 | continue; /* ignore empty string */ |
162 | path = safefile(p); /* make sure filename is okay */ |
163 | if (!path) { |
164 | agerr(AGWARN, "can't find library file %s\n" , p); |
165 | } |
166 | else if ((fp = fopen(path, "r" ))) { |
167 | while ((bp = Fgets(fp))) |
168 | gvputs(job, bp); |
169 | gvputs(job, "\n" ); /* append a newline just in case */ |
170 | fclose (fp); |
171 | } else |
172 | agerr(AGWARN, "can't open library file %s\n" , path); |
173 | } |
174 | } |
175 | } |
176 | |
177 | #define FILTER_EPSF 1 |
178 | #ifdef FILTER_EPSF |
179 | /* this removes EPSF DSC comments that, when nested in another |
180 | * document, cause errors in Ghostview and other Postscript |
181 | * processors (although legal according to the Adobe EPSF spec). |
182 | * |
183 | * N.B. PostScript lines can end with \n, \r or \r\n. |
184 | */ |
185 | void epsf_emit_body(GVJ_t *job, usershape_t *us) |
186 | { |
187 | char *p; |
188 | char c; |
189 | p = us->data; |
190 | while (*p) { |
191 | /* skip %%EOF lines */ |
192 | if ((p[0] == '%') && (p[1] == '%') |
193 | && (!strncasecmp(&p[2], "EOF" , 3) |
194 | || !strncasecmp(&p[2], "BEGIN" , 5) |
195 | || !strncasecmp(&p[2], "END" , 3) |
196 | || !strncasecmp(&p[2], "TRAILER" , 7) |
197 | )) { |
198 | /* check for *p since last line might not end in '\n' */ |
199 | while ((c = *p) && (c != '\r') && (c != '\n')) p++; |
200 | if ((*p == '\r') && (*(p+1) == '\n')) p += 2; |
201 | else if (*p) p++; |
202 | continue; |
203 | } |
204 | /* output line */ |
205 | while ((c = *p) && (c != '\r') && (c != '\n')) { |
206 | gvputc(job, c); |
207 | p++; |
208 | } |
209 | if ((*p == '\r') && (*(p+1) == '\n')) p += 2; |
210 | else if (*p) p++; |
211 | gvputc(job, '\n'); |
212 | } |
213 | } |
214 | #else |
215 | void epsf_emit_body(GVJ_t *job, usershape_t *us) |
216 | { |
217 | gvputs(job, us->data); |
218 | } |
219 | #endif |
220 | |
221 | void epsf_define(GVJ_t *job) |
222 | { |
223 | usershape_t *us; |
224 | |
225 | if (!EPSF_contents) |
226 | return; |
227 | for (us = dtfirst(EPSF_contents); us; us = dtnext(EPSF_contents, us)) { |
228 | if (us->must_inline) |
229 | continue; |
230 | gvprintf(job, "/user_shape_%d {\n" , us->macro_id); |
231 | gvputs(job, "%%BeginDocument:\n" ); |
232 | epsf_emit_body(job, us); |
233 | gvputs(job, "%%EndDocument\n" ); |
234 | gvputs(job, "} bind def\n" ); |
235 | } |
236 | } |
237 | |
238 | enum {ASCII, LATIN1, NONLATIN}; |
239 | |
240 | /* charsetOf: |
241 | * Assuming legal utf-8 input, determine if |
242 | * the character value range is ascii, latin-1 or otherwise. |
243 | */ |
244 | static int |
245 | charsetOf (char* s) |
246 | { |
247 | int r = ASCII; |
248 | unsigned char c; |
249 | |
250 | while ((c = *(unsigned char*)s++)) { |
251 | if (c < 0x7F) |
252 | continue; |
253 | else if ((c & 0xFC) == 0xC0) { |
254 | r = LATIN1; |
255 | s++; /* eat second byte */ |
256 | } |
257 | else return NONLATIN; |
258 | } |
259 | return r; |
260 | } |
261 | |
262 | /* ps_string: |
263 | * internally, strings are always utf8. If chset is CHAR_LATIN1, we know |
264 | * all of the values can be represented by latin-1; if chset is |
265 | * CHAR_UTF8, we use the string as is; otherwise, we test to see if the |
266 | * string is ascii, latin-1 or non-latin, and translate to latin-l if |
267 | * possible. |
268 | */ |
269 | char *ps_string(char *ins, int chset) |
270 | { |
271 | char *s; |
272 | char *base; |
273 | static agxbuf xb; |
274 | static int warned; |
275 | |
276 | switch (chset) { |
277 | case CHAR_UTF8 : |
278 | base = ins; |
279 | break; |
280 | case CHAR_LATIN1 : |
281 | base = utf8ToLatin1 (ins); |
282 | break; |
283 | default : |
284 | switch (charsetOf (ins)) { |
285 | case ASCII : |
286 | base = ins; |
287 | break; |
288 | case LATIN1 : |
289 | base = utf8ToLatin1 (ins); |
290 | break; |
291 | case NONLATIN : |
292 | if (!warned) { |
293 | agerr (AGWARN, "UTF-8 input uses non-Latin1 characters which cannot be handled by this PostScript driver\n" ); |
294 | warned = 1; |
295 | } |
296 | base = ins; |
297 | break; |
298 | default: |
299 | base = ins; |
300 | break; |
301 | } |
302 | } |
303 | |
304 | if (xb.buf == NULL) |
305 | agxbinit (&xb, 0, NULL); |
306 | |
307 | agxbputc (&xb, LPAREN); |
308 | s = base; |
309 | while (*s) { |
310 | if ((*s == LPAREN) || (*s == RPAREN) || (*s == '\\')) |
311 | agxbputc (&xb, '\\'); |
312 | agxbputc (&xb, *s++); |
313 | } |
314 | agxbputc (&xb, RPAREN); |
315 | if (base != ins) free (base); |
316 | s = agxbuse(&xb); |
317 | return s; |
318 | } |
319 | |