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
23static int N_EPSF_files;
24static Dict_t *EPSF_contents;
25
26static void ps_image_free(Dict_t * dict, usershape_t * p, Dtdisc_t * disc)
27{
28 free(p->data);
29}
30
31static 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
43static 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
98void 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
121void 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 */
139void 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 */
185void 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
215void epsf_emit_body(GVJ_t *job, usershape_t *us)
216{
217 gvputs(job, us->data);
218}
219#endif
220
221void 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
238enum {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 */
244static int
245charsetOf (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 */
269char *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