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 <ctype.h> |
15 | #include "render.h" |
16 | #include "htmltable.h" |
17 | #include "gvc.h" |
18 | #include "xdot.h" |
19 | #include "agxbuf.h" |
20 | |
21 | static char *usageFmt = |
22 | "Usage: %s [-Vv?] [-(GNE)name=val] [-(KTlso)<val>] <dot files>\n" ; |
23 | |
24 | static char *genericItems = "\n\ |
25 | -V - Print version and exit\n\ |
26 | -v - Enable verbose mode \n\ |
27 | -Gname=val - Set graph attribute 'name' to 'val'\n\ |
28 | -Nname=val - Set node attribute 'name' to 'val'\n\ |
29 | -Ename=val - Set edge attribute 'name' to 'val'\n\ |
30 | -Tv - Set output format to 'v'\n\ |
31 | -Kv - Set layout engine to 'v' (overrides default based on command name)\n\ |
32 | -lv - Use external library 'v'\n\ |
33 | -ofile - Write output to 'file'\n\ |
34 | -O - Automatically generate an output filename based on the input filename with a .'format' appended. (Causes all -ofile options to be ignored.) \n\ |
35 | -P - Internally generate a graph of the current plugins. \n\ |
36 | -q[l] - Set level of message suppression (=1)\n\ |
37 | -s[v] - Scale input by 'v' (=72)\n\ |
38 | -y - Invert y coordinate in output\n" ; |
39 | |
40 | static char *neatoFlags = |
41 | "(additional options for neato) [-x] [-n<v>]\n" ; |
42 | static char *neatoItems = "\n\ |
43 | -n[v] - No layout mode 'v' (=1)\n\ |
44 | -x - Reduce graph\n" ; |
45 | |
46 | static char *fdpFlags = |
47 | "(additional options for fdp) [-L(gO)] [-L(nUCT)<val>]\n" ; |
48 | static char *fdpItems = "\n\ |
49 | -Lg - Don't use grid\n\ |
50 | -LO - Use old attractive force\n\ |
51 | -Ln<i> - Set number of iterations to i\n\ |
52 | -LU<i> - Set unscaled factor to i\n\ |
53 | -LC<v> - Set overlap expansion factor to v\n\ |
54 | -LT[*]<v> - Set temperature (temperature factor) to v\n" ; |
55 | |
56 | static char *memtestFlags = "(additional options for memtest) [-m<v>]\n" ; |
57 | static char *memtestItems = "\n\ |
58 | -m - Memory test (Observe no growth with top. Kill when done.)\n\ |
59 | -m[v] - Memory test - v iterations.\n" ; |
60 | |
61 | static char *configFlags = "(additional options for config) [-cv]\n" ; |
62 | static char *configItems = "\n\ |
63 | -c - Configure plugins (Writes $prefix/lib/graphviz/config \n\ |
64 | with available plugin information. Needs write privilege.)\n\ |
65 | -? - Print usage and exit\n" ; |
66 | |
67 | /* dotneato_usage: |
68 | * Print usage information. If GvExitOnUsage is set, exit with |
69 | * given exval, else return exval+1. |
70 | */ |
71 | int dotneato_usage(int exval) |
72 | { |
73 | FILE *outs; |
74 | |
75 | if (exval > 0) |
76 | outs = stderr; |
77 | else |
78 | outs = stdout; |
79 | |
80 | fprintf(outs, usageFmt, CmdName); |
81 | fputs(neatoFlags, outs); |
82 | fputs(fdpFlags, outs); |
83 | fputs(memtestFlags, outs); |
84 | fputs(configFlags, outs); |
85 | fputs(genericItems, outs); |
86 | fputs(neatoItems, outs); |
87 | fputs(fdpItems, outs); |
88 | fputs(memtestItems, outs); |
89 | fputs(configItems, outs); |
90 | |
91 | if (GvExitOnUsage && (exval >= 0)) |
92 | exit(exval); |
93 | return (exval+1); |
94 | |
95 | } |
96 | |
97 | /* getFlagOpt: |
98 | * Look for flag parameter. idx is index of current argument. |
99 | * We assume argv[*idx] has the form "-x..." If there are characters |
100 | * after the x, return |
101 | * these, else if there are more arguments, return the next one, |
102 | * else return NULL. |
103 | */ |
104 | static char *getFlagOpt(int argc, char **argv, int *idx) |
105 | { |
106 | int i = *idx; |
107 | char *arg = argv[i]; |
108 | |
109 | if (arg[2]) |
110 | return arg + 2; |
111 | if (i < argc - 1) { |
112 | i++; |
113 | arg = argv[i]; |
114 | if (*arg && (*arg != '-')) { |
115 | *idx = i; |
116 | return arg; |
117 | } |
118 | } |
119 | return 0; |
120 | } |
121 | |
122 | /* dotneato_basename: |
123 | * Partial implementation of real basename. |
124 | * Skip over any trailing slashes or backslashes; then |
125 | * find next (back)slash moving left; return string to the right. |
126 | * If no next slash is found, return the whole string. |
127 | */ |
128 | static char* dotneato_basename (char* path) |
129 | { |
130 | char* ret; |
131 | char* s = path; |
132 | if (*s == '\0') return path; /* empty string */ |
133 | #ifdef _WIN32 |
134 | /* On Windows, executables, by convention, end in ".exe". Thus, |
135 | * this may be part of the path name and must be removed for |
136 | * matching to work. |
137 | */ |
138 | { |
139 | char* dotp = strrchr (s, '.'); |
140 | if (dotp && !strcasecmp(dotp+1,"exe" )) *dotp = '\0'; |
141 | } |
142 | #endif |
143 | while (*s) s++; |
144 | s--; |
145 | /* skip over trailing slashes, nulling out as we go */ |
146 | while ((s > path) && ((*s == '/') || (*s == '\\'))) |
147 | *s-- = '\0'; |
148 | if (s == path) ret = path; |
149 | else { |
150 | while ((s > path) && ((*s != '/') && (*s != '\\'))) s--; |
151 | if ((*s == '/') || (*s == '\\')) ret = s+1; |
152 | else ret = path; |
153 | } |
154 | #ifdef _WIN32 |
155 | /* On Windows, names are case-insensitive, so make name lower-case |
156 | */ |
157 | { |
158 | char c; |
159 | for (s = ret; (c = *s); s++) |
160 | *s = tolower(c); |
161 | } |
162 | #endif |
163 | return ret; |
164 | } |
165 | |
166 | static void use_library(GVC_t *gvc, const char *name) |
167 | { |
168 | static int cnt = 0; |
169 | if (name) { |
170 | Lib = ALLOC(cnt + 2, Lib, const char *); |
171 | Lib[cnt++] = name; |
172 | Lib[cnt] = NULL; |
173 | } |
174 | gvc->common.lib = Lib; |
175 | } |
176 | |
177 | static void global_def(agxbuf* xb, char *dcl, int kind, |
178 | attrsym_t * ((*dclfun) (Agraph_t *, int kind, char *, char *)) ) |
179 | { |
180 | char *p; |
181 | char *rhs = "true" ; |
182 | |
183 | attrsym_t *sym; |
184 | if ((p = strchr(dcl, '='))) { |
185 | agxbput_n (xb, dcl, p-dcl); |
186 | rhs = p+1; |
187 | } |
188 | else |
189 | agxbput (xb, dcl); |
190 | sym = dclfun(NULL, kind, agxbuse (xb), rhs); |
191 | sym->fixed = 1; |
192 | } |
193 | |
194 | static int gvg_init(GVC_t *gvc, graph_t *g, char *fn, int gidx) |
195 | { |
196 | GVG_t *gvg; |
197 | |
198 | gvg = zmalloc(sizeof(GVG_t)); |
199 | if (!gvc->gvgs) |
200 | gvc->gvgs = gvg; |
201 | else |
202 | gvc->gvg->next = gvg; |
203 | gvc->gvg = gvg; |
204 | gvg->gvc = gvc; |
205 | gvg->g = g; |
206 | gvg->input_filename = fn; |
207 | gvg->graph_index = gidx; |
208 | return 0; |
209 | } |
210 | |
211 | static graph_t *P_graph; |
212 | |
213 | graph_t *gvPluginsGraph(GVC_t *gvc) |
214 | { |
215 | gvg_init(gvc, P_graph, "<internal>" , 0); |
216 | return P_graph; |
217 | } |
218 | |
219 | /* dotneato_args_initialize" |
220 | * Scan argv[] for allowed flags. |
221 | * Return 0 on success; v+1 if calling function should call exit(v). |
222 | * If -c is set, config file is created and we exit. |
223 | */ |
224 | int dotneato_args_initialize(GVC_t * gvc, int argc, char **argv) |
225 | { |
226 | char c, *rest, *layout; |
227 | const char *val; |
228 | int i, v, nfiles; |
229 | unsigned char buf[SMALLBUF]; |
230 | agxbuf xb; |
231 | int Kflag = 0; |
232 | |
233 | /* establish if we are running in a CGI environment */ |
234 | HTTPServerEnVar = getenv("SERVER_NAME" ); |
235 | |
236 | /* establish Gvfilepath, if any */ |
237 | Gvfilepath = getenv("GV_FILE_PATH" ); |
238 | |
239 | gvc->common.cmdname = dotneato_basename(argv[0]); |
240 | if (gvc->common.verbose) { |
241 | fprintf(stderr, "%s - %s version %s (%s)\n" , |
242 | gvc->common.cmdname, gvc->common.info[0], |
243 | gvc->common.info[1], gvc->common.info[2]); |
244 | } |
245 | |
246 | /* configure for available plugins */ |
247 | /* needs to know if "dot -c" is set (gvc->common.config) */ |
248 | /* must happen before trying to select any plugins */ |
249 | if (gvc->common.config) { |
250 | gvconfig(gvc, gvc->common.config); |
251 | exit (0); |
252 | } |
253 | |
254 | /* feed the globals */ |
255 | Verbose = gvc->common.verbose; |
256 | CmdName = gvc->common.cmdname; |
257 | |
258 | nfiles = 0; |
259 | for (i = 1; i < argc; i++) |
260 | if (argv[i] && argv[i][0] != '-') |
261 | nfiles++; |
262 | gvc->input_filenames = N_NEW(nfiles + 1, char *); |
263 | nfiles = 0; |
264 | agxbinit(&xb, SMALLBUF, buf); |
265 | for (i = 1; i < argc; i++) { |
266 | if (argv[i] && argv[i][0] == '-') { |
267 | rest = &(argv[i][2]); |
268 | switch (c = argv[i][1]) { |
269 | case 'G': |
270 | if (*rest) |
271 | global_def(&xb, rest, AGRAPH, agattr); |
272 | else { |
273 | fprintf(stderr, "Missing argument for -G flag\n" ); |
274 | return (dotneato_usage(1)); |
275 | } |
276 | break; |
277 | case 'N': |
278 | if (*rest) |
279 | global_def(&xb, rest, AGNODE,agattr); |
280 | else { |
281 | fprintf(stderr, "Missing argument for -N flag\n" ); |
282 | return (dotneato_usage(1)); |
283 | } |
284 | break; |
285 | case 'E': |
286 | if (*rest) |
287 | global_def(&xb, rest, AGEDGE,agattr); |
288 | else { |
289 | fprintf(stderr, "Missing argument for -E flag\n" ); |
290 | return (dotneato_usage(1)); |
291 | } |
292 | break; |
293 | case 'T': |
294 | val = getFlagOpt(argc, argv, &i); |
295 | if (!val) { |
296 | fprintf(stderr, "Missing argument for -T flag\n" ); |
297 | return (dotneato_usage(1)); |
298 | } |
299 | v = gvjobs_output_langname(gvc, val); |
300 | if (!v) { |
301 | fprintf(stderr, "Format: \"%s\" not recognized. Use one of:%s\n" , |
302 | val, gvplugin_list(gvc, API_device, val)); |
303 | if (GvExitOnUsage) exit(1); |
304 | return(2); |
305 | } |
306 | break; |
307 | case 'K': |
308 | val = getFlagOpt(argc, argv, &i); |
309 | if (!val) { |
310 | fprintf(stderr, "Missing argument for -K flag\n" ); |
311 | return (dotneato_usage(1)); |
312 | } |
313 | v = gvlayout_select(gvc, val); |
314 | if (v == NO_SUPPORT) { |
315 | fprintf(stderr, "There is no layout engine support for \"%s\"\n" , val); |
316 | if (streq(val, "dot" )) { |
317 | fprintf(stderr, "Perhaps \"dot -c\" needs to be run (with installer's privileges) to register the plugins?\n" ); |
318 | } |
319 | else { |
320 | fprintf(stderr, "Use one of:%s\n" , |
321 | gvplugin_list(gvc, API_layout, val)); |
322 | } |
323 | if (GvExitOnUsage) exit(1); |
324 | return(2); |
325 | } |
326 | Kflag = 1; |
327 | break; |
328 | case 'P': |
329 | P_graph = gvplugin_graph(gvc); |
330 | break; |
331 | case 'V': |
332 | fprintf(stderr, "%s - %s version %s (%s)\n" , |
333 | gvc->common.cmdname, gvc->common.info[0], |
334 | gvc->common.info[1], gvc->common.info[2]); |
335 | if (GvExitOnUsage) exit(0); |
336 | return (1); |
337 | break; |
338 | case 'l': |
339 | val = getFlagOpt(argc, argv, &i); |
340 | if (!val) { |
341 | fprintf(stderr, "Missing argument for -l flag\n" ); |
342 | return (dotneato_usage(1)); |
343 | } |
344 | use_library(gvc, val); |
345 | break; |
346 | case 'o': |
347 | val = getFlagOpt(argc, argv, &i); |
348 | if (!val) { |
349 | fprintf(stderr, "Missing argument for -o flag\n" ); |
350 | return (dotneato_usage(1)); |
351 | } |
352 | if (! gvc->common.auto_outfile_names) |
353 | gvjobs_output_filename(gvc, val); |
354 | break; |
355 | case 'q': |
356 | if (*rest) { |
357 | v = atoi(rest); |
358 | if (v <= 0) { |
359 | fprintf(stderr, |
360 | "Invalid parameter \"%s\" for -q flag - ignored\n" , |
361 | rest); |
362 | } else if (v == 1) |
363 | agseterr(AGERR); |
364 | else |
365 | agseterr(AGMAX); |
366 | } else |
367 | agseterr(AGERR); |
368 | break; |
369 | case 's': |
370 | if (*rest) { |
371 | PSinputscale = atof(rest); |
372 | if (PSinputscale < 0) { |
373 | fprintf(stderr, |
374 | "Invalid parameter \"%s\" for -s flag\n" , |
375 | rest); |
376 | return (dotneato_usage(1)); |
377 | } |
378 | else if (PSinputscale == 0) |
379 | PSinputscale = POINTS_PER_INCH; |
380 | } else |
381 | PSinputscale = POINTS_PER_INCH; |
382 | break; |
383 | case 'x': |
384 | Reduce = TRUE; |
385 | break; |
386 | case 'y': |
387 | Y_invert = TRUE; |
388 | break; |
389 | case '?': |
390 | return (dotneato_usage(0)); |
391 | break; |
392 | default: |
393 | agerr(AGERR, "%s: option -%c unrecognized\n\n" , gvc->common.cmdname, |
394 | c); |
395 | return (dotneato_usage(1)); |
396 | } |
397 | } else if (argv[i]) |
398 | gvc->input_filenames[nfiles++] = argv[i]; |
399 | } |
400 | agxbfree (&xb); |
401 | |
402 | /* if no -K, use cmd name to set layout type */ |
403 | if (!Kflag) { |
404 | layout = gvc->common.cmdname; |
405 | if (streq(layout, "dot_static" ) |
406 | || streq(layout, "dot_builtins" ) |
407 | || streq(layout, "lt-dot" ) |
408 | || streq(layout, "lt-dot_builtins" ) |
409 | || streq(layout, "" ) /* when run as a process from Gvedit on Windows */ |
410 | ) |
411 | layout = "dot" ; |
412 | i = gvlayout_select(gvc, layout); |
413 | if (i == NO_SUPPORT) { |
414 | fprintf(stderr, "There is no layout engine support for \"%s\"\n" , layout); |
415 | if (streq(layout, "dot" )) |
416 | fprintf(stderr, "Perhaps \"dot -c\" needs to be run (with installer's privileges) to register the plugins?\n" ); |
417 | else |
418 | fprintf(stderr, "Use one of:%s\n" , gvplugin_list(gvc, API_layout, "" )); |
419 | |
420 | if (GvExitOnUsage) exit(1); |
421 | return(2); |
422 | } |
423 | } |
424 | |
425 | /* if no -Txxx, then set default format */ |
426 | if (!gvc->jobs || !gvc->jobs->output_langname) { |
427 | v = gvjobs_output_langname(gvc, "dot" ); |
428 | if (!v) { |
429 | // assert(v); /* "dot" should always be available as an output format */ |
430 | fprintf(stderr, |
431 | "Unable to find even the default \"-Tdot\" renderer. Has the config\nfile been generated by running \"dot -c\" with installer's privileges?\n" ); |
432 | return(2); |
433 | } |
434 | } |
435 | |
436 | /* set persistent attributes here (if not already set from command line options) */ |
437 | if (!agattr(NULL, AGNODE, "label" , 0)) |
438 | agattr(NULL, AGNODE, "label" , NODENAME_ESC); |
439 | return 0; |
440 | } |
441 | |
442 | /* getdoubles2ptf: |
443 | * converts a graph attribute in inches to a pointf in points. |
444 | * If only one number is given, it is used for both x and y. |
445 | * Returns true if the attribute ends in '!'. |
446 | */ |
447 | static boolean getdoubles2ptf(graph_t * g, char *name, pointf * result) |
448 | { |
449 | char *p; |
450 | int i; |
451 | double xf, yf; |
452 | char c = '\0'; |
453 | boolean rv = FALSE; |
454 | |
455 | if ((p = agget(g, name))) { |
456 | i = sscanf(p, "%lf,%lf%c" , &xf, &yf, &c); |
457 | if ((i > 1) && (xf > 0) && (yf > 0)) { |
458 | result->x = POINTS(xf); |
459 | result->y = POINTS(yf); |
460 | if (c == '!') |
461 | rv = TRUE; |
462 | } |
463 | else { |
464 | c = '\0'; |
465 | i = sscanf(p, "%lf%c" , &xf, &c); |
466 | if ((i > 0) && (xf > 0)) { |
467 | result->y = result->x = POINTS(xf); |
468 | if (c == '!') rv = TRUE; |
469 | } |
470 | } |
471 | } |
472 | return rv; |
473 | } |
474 | |
475 | void getdouble(graph_t * g, char *name, double *result) |
476 | { |
477 | char *p; |
478 | double f; |
479 | |
480 | if ((p = agget(g, name))) { |
481 | if (sscanf(p, "%lf" , &f) >= 1) |
482 | *result = f; |
483 | } |
484 | } |
485 | |
486 | #ifdef EXPERIMENTAL_MYFGETS |
487 | /* |
488 | * Potential input filter - e.g. for iconv |
489 | */ |
490 | |
491 | /* |
492 | * myfgets - same api as fgets |
493 | * |
494 | * gets n chars at a time |
495 | * |
496 | * returns pointer to user buffer, |
497 | * or returns NULL on eof or error. |
498 | */ |
499 | static char *myfgets(char * ubuf, int n, FILE * fp) |
500 | { |
501 | static char *buf; |
502 | static int bufsz, pos, len; |
503 | int cnt; |
504 | |
505 | if (!n) { /* a call with n==0 (from aglexinit) resets */ |
506 | ubuf[0] = '\0'; |
507 | pos = len = 0; |
508 | return NULL; |
509 | } |
510 | |
511 | if (!len) { |
512 | if (n > bufsz) { |
513 | bufsz = n; |
514 | buf = realloc(buf, bufsz); |
515 | } |
516 | if (!(fgets(buf, bufsz, fp))) { |
517 | ubuf[0] = '\0'; |
518 | return NULL; |
519 | } |
520 | len = strlen(buf); |
521 | pos = 0; |
522 | } |
523 | |
524 | cnt = n - 1; |
525 | if (len < cnt) |
526 | cnt = len; |
527 | |
528 | memcpy(ubuf, buf + pos, cnt); |
529 | pos += cnt; |
530 | len -= cnt; |
531 | ubuf[cnt] = '\0'; |
532 | |
533 | return ubuf; |
534 | } |
535 | #endif |
536 | |
537 | graph_t *gvNextInputGraph(GVC_t *gvc) |
538 | { |
539 | graph_t *g = NULL; |
540 | static char *fn; |
541 | static FILE *fp; |
542 | static FILE *oldfp; |
543 | static int fidx, gidx; |
544 | |
545 | while (!g) { |
546 | if (!fp) { |
547 | if (!(fn = gvc->input_filenames[0])) { |
548 | if (fidx++ == 0) |
549 | fp = stdin; |
550 | } |
551 | else { |
552 | while ((fn = gvc->input_filenames[fidx++]) && !(fp = fopen(fn, "r" ))) { |
553 | agerr(AGERR, "%s: can't open %s\n" , gvc->common.cmdname, fn); |
554 | graphviz_errors++; |
555 | } |
556 | } |
557 | } |
558 | if (fp == NULL) |
559 | break; |
560 | if (oldfp != fp) { |
561 | agsetfile(fn ? fn : "<stdin>" ); |
562 | oldfp = fp; |
563 | } |
564 | #ifdef EXPERIMENTAL_MYFGETS |
565 | g = agread_usergets(fp, myfgets); |
566 | #else |
567 | g = agread(fp,NIL(Agdisc_t*)); |
568 | #endif |
569 | if (g) { |
570 | gvg_init(gvc, g, fn, gidx++); |
571 | break; |
572 | } |
573 | if (fp != stdin) |
574 | fclose (fp); |
575 | oldfp = fp = NULL; |
576 | gidx = 0; |
577 | } |
578 | return g; |
579 | } |
580 | |
581 | /* findCharset: |
582 | * Check if the charset attribute is defined for the graph and, if |
583 | * so, return the corresponding internal value. If undefined, return |
584 | * CHAR_UTF8 |
585 | */ |
586 | static int findCharset (graph_t * g) |
587 | { |
588 | int enc; |
589 | char* p; |
590 | |
591 | p = late_nnstring(g,agfindgraphattr(g,"charset" ),"utf-8" ); |
592 | if (!strcasecmp(p,"latin-1" ) |
593 | || !strcasecmp(p,"latin1" ) |
594 | || !strcasecmp(p,"l1" ) |
595 | || !strcasecmp(p,"ISO-8859-1" ) |
596 | || !strcasecmp(p,"ISO_8859-1" ) |
597 | || !strcasecmp(p,"ISO8859-1" ) |
598 | || !strcasecmp(p,"ISO-IR-100" )) |
599 | enc = CHAR_LATIN1; |
600 | else if (!strcasecmp(p,"big-5" ) |
601 | || !strcasecmp(p,"big5" )) |
602 | enc = CHAR_BIG5; |
603 | else if (!strcasecmp(p,"utf-8" ) |
604 | || !strcasecmp(p,"utf8" )) |
605 | enc = CHAR_UTF8; |
606 | else { |
607 | agerr(AGWARN, "Unsupported charset \"%s\" - assuming utf-8\n" , p); |
608 | enc = CHAR_UTF8; |
609 | } |
610 | return enc; |
611 | } |
612 | |
613 | /* setRatio: |
614 | * Checks "ratio" attribute, if any, and sets enum type. |
615 | */ |
616 | static void setRatio(graph_t * g) |
617 | { |
618 | char *p, c; |
619 | double ratio; |
620 | |
621 | if ((p = agget(g, "ratio" )) && ((c = p[0]))) { |
622 | switch (c) { |
623 | case 'a': |
624 | if (streq(p, "auto" )) |
625 | GD_drawing(g)->ratio_kind = R_AUTO; |
626 | break; |
627 | case 'c': |
628 | if (streq(p, "compress" )) |
629 | GD_drawing(g)->ratio_kind = R_COMPRESS; |
630 | break; |
631 | case 'e': |
632 | if (streq(p, "expand" )) |
633 | GD_drawing(g)->ratio_kind = R_EXPAND; |
634 | break; |
635 | case 'f': |
636 | if (streq(p, "fill" )) |
637 | GD_drawing(g)->ratio_kind = R_FILL; |
638 | break; |
639 | default: |
640 | ratio = atof(p); |
641 | if (ratio > 0.0) { |
642 | GD_drawing(g)->ratio_kind = R_VALUE; |
643 | GD_drawing(g)->ratio = ratio; |
644 | } |
645 | break; |
646 | } |
647 | } |
648 | } |
649 | |
650 | /* |
651 | cgraph requires |
652 | |
653 | */ |
654 | void graph_init(graph_t * g, boolean use_rankdir) |
655 | { |
656 | char *p; |
657 | double xf; |
658 | static char *rankname[] = { "local" , "global" , "none" , NULL }; |
659 | static int rankcode[] = { LOCAL, GLOBAL, NOCLUST, LOCAL }; |
660 | static char *fontnamenames[] = {"gd" ,"ps" ,"svg" , NULL}; |
661 | static int fontnamecodes[] = {NATIVEFONTS,PSFONTS,SVGFONTS,-1}; |
662 | int rankdir; |
663 | GD_drawing(g) = NEW(layout_t); |
664 | |
665 | /* set this up fairly early in case any string sizes are needed */ |
666 | if ((p = agget(g, "fontpath" )) || (p = getenv("DOTFONTPATH" ))) { |
667 | /* overide GDFONTPATH in local environment if dot |
668 | * wants its own */ |
669 | #ifdef HAVE_SETENV |
670 | setenv("GDFONTPATH" , p, 1); |
671 | #else |
672 | static char *buf = 0; |
673 | |
674 | buf = grealloc(buf, strlen("GDFONTPATH=" ) + strlen(p) + 1); |
675 | strcpy(buf, "GDFONTPATH=" ); |
676 | strcat(buf, p); |
677 | putenv(buf); |
678 | #endif |
679 | } |
680 | |
681 | GD_charset(g) = findCharset (g); |
682 | |
683 | if (!HTTPServerEnVar) { |
684 | Gvimagepath = agget (g, "imagepath" ); |
685 | if (!Gvimagepath) |
686 | Gvimagepath = Gvfilepath; |
687 | } |
688 | |
689 | GD_drawing(g)->quantum = |
690 | late_double(g, agfindgraphattr(g, "quantum" ), 0.0, 0.0); |
691 | |
692 | /* setting rankdir=LR is only defined in dot, |
693 | * but having it set causes shape code and others to use it. |
694 | * The result is confused output, so we turn it off unless requested. |
695 | * This effective rankdir is stored in the bottom 2 bits of g->u.rankdir. |
696 | * Sometimes, the code really needs the graph's rankdir, e.g., neato -n |
697 | * with record shapes, so we store the real rankdir in the next 2 bits. |
698 | */ |
699 | rankdir = RANKDIR_TB; |
700 | if ((p = agget(g, "rankdir" ))) { |
701 | if (streq(p, "LR" )) |
702 | rankdir = RANKDIR_LR; |
703 | else if (streq(p, "BT" )) |
704 | rankdir = RANKDIR_BT; |
705 | else if (streq(p, "RL" )) |
706 | rankdir = RANKDIR_RL; |
707 | } |
708 | if (use_rankdir) |
709 | SET_RANKDIR (g, (rankdir << 2) | rankdir); |
710 | else |
711 | SET_RANKDIR (g, (rankdir << 2)); |
712 | |
713 | xf = late_double(g, agfindgraphattr(g, "nodesep" ), |
714 | DEFAULT_NODESEP, MIN_NODESEP); |
715 | GD_nodesep(g) = POINTS(xf); |
716 | |
717 | p = late_string(g, agfindgraphattr(g, "ranksep" ), NULL); |
718 | if (p) { |
719 | if (sscanf(p, "%lf" , &xf) == 0) |
720 | xf = DEFAULT_RANKSEP; |
721 | else { |
722 | if (xf < MIN_RANKSEP) |
723 | xf = MIN_RANKSEP; |
724 | } |
725 | if (strstr(p, "equally" )) |
726 | GD_exact_ranksep(g) = TRUE; |
727 | } else |
728 | xf = DEFAULT_RANKSEP; |
729 | GD_ranksep(g) = POINTS(xf); |
730 | |
731 | GD_showboxes(g) = late_int(g, agfindgraphattr(g, "showboxes" ), 0, 0); |
732 | p = late_string(g, agfindgraphattr(g, "fontnames" ), NULL); |
733 | GD_fontnames(g) = maptoken(p, fontnamenames, fontnamecodes); |
734 | |
735 | setRatio(g); |
736 | GD_drawing(g)->filled = |
737 | getdoubles2ptf(g, "size" , &(GD_drawing(g)->size)); |
738 | getdoubles2ptf(g, "page" , &(GD_drawing(g)->page)); |
739 | |
740 | GD_drawing(g)->centered = mapbool(agget(g, "center" )); |
741 | |
742 | if ((p = agget(g, "rotate" ))) |
743 | GD_drawing(g)->landscape = (atoi(p) == 90); |
744 | else if ((p = agget(g, "orientation" ))) |
745 | GD_drawing(g)->landscape = ((p[0] == 'l') || (p[0] == 'L')); |
746 | else if ((p = agget(g, "landscape" ))) |
747 | GD_drawing(g)->landscape = mapbool(p); |
748 | |
749 | p = agget(g, "clusterrank" ); |
750 | CL_type = maptoken(p, rankname, rankcode); |
751 | p = agget(g, "concentrate" ); |
752 | Concentrate = mapbool(p); |
753 | State = GVBEGIN; |
754 | EdgeLabelsDone = 0; |
755 | |
756 | GD_drawing(g)->dpi = 0.0; |
757 | if (((p = agget(g, "dpi" )) && p[0]) |
758 | || ((p = agget(g, "resolution" )) && p[0])) |
759 | GD_drawing(g)->dpi = atof(p); |
760 | |
761 | do_graph_label(g); |
762 | |
763 | Initial_dist = MYHUGE; |
764 | |
765 | G_ordering = agfindgraphattr(g, "ordering" ); |
766 | G_gradientangle = agfindgraphattr(g,"gradientangle" ); |
767 | G_margin = agfindgraphattr(g, "margin" ); |
768 | |
769 | /* initialize nodes */ |
770 | N_height = agfindnodeattr(g, "height" ); |
771 | N_width = agfindnodeattr(g, "width" ); |
772 | N_shape = agfindnodeattr(g, "shape" ); |
773 | N_color = agfindnodeattr(g, "color" ); |
774 | N_fillcolor = agfindnodeattr(g, "fillcolor" ); |
775 | N_style = agfindnodeattr(g, "style" ); |
776 | N_fontsize = agfindnodeattr(g, "fontsize" ); |
777 | N_fontname = agfindnodeattr(g, "fontname" ); |
778 | N_fontcolor = agfindnodeattr(g, "fontcolor" ); |
779 | N_label = agfindnodeattr(g, "label" ); |
780 | if (!N_label) |
781 | N_label = agattr(g, AGNODE, "label" , NODENAME_ESC); |
782 | N_xlabel = agfindnodeattr(g, "xlabel" ); |
783 | N_showboxes = agfindnodeattr(g, "showboxes" ); |
784 | N_penwidth = agfindnodeattr(g, "penwidth" ); |
785 | N_ordering = agfindnodeattr(g, "ordering" ); |
786 | N_margin = agfindnodeattr(g, "margin" ); |
787 | /* attribs for polygon shapes */ |
788 | N_sides = agfindnodeattr(g, "sides" ); |
789 | N_peripheries = agfindnodeattr(g, "peripheries" ); |
790 | N_skew = agfindnodeattr(g, "skew" ); |
791 | N_orientation = agfindnodeattr(g, "orientation" ); |
792 | N_distortion = agfindnodeattr(g, "distortion" ); |
793 | N_fixed = agfindnodeattr(g, "fixedsize" ); |
794 | N_imagescale = agfindnodeattr(g, "imagescale" ); |
795 | N_imagepos = agfindnodeattr(g, "imagepos" ); |
796 | N_nojustify = agfindnodeattr(g, "nojustify" ); |
797 | N_layer = agfindnodeattr(g, "layer" ); |
798 | N_group = agfindnodeattr(g, "group" ); |
799 | N_comment = agfindnodeattr(g, "comment" ); |
800 | N_vertices = agfindnodeattr(g, "vertices" ); |
801 | N_z = agfindnodeattr(g, "z" ); |
802 | N_gradientangle = agfindnodeattr(g,"gradientangle" ); |
803 | |
804 | /* initialize edges */ |
805 | E_weight = agfindedgeattr(g, "weight" ); |
806 | E_color = agfindedgeattr(g, "color" ); |
807 | E_fillcolor = agfindedgeattr(g, "fillcolor" ); |
808 | E_fontsize = agfindedgeattr(g, "fontsize" ); |
809 | E_fontname = agfindedgeattr(g, "fontname" ); |
810 | E_fontcolor = agfindedgeattr(g, "fontcolor" ); |
811 | E_label = agfindedgeattr(g, "label" ); |
812 | E_xlabel = agfindedgeattr(g, "xlabel" ); |
813 | E_label_float = agfindedgeattr(g, "labelfloat" ); |
814 | /* vladimir */ |
815 | E_dir = agfindedgeattr(g, "dir" ); |
816 | E_arrowhead = agfindedgeattr(g, "arrowhead" ); |
817 | E_arrowtail = agfindedgeattr(g, "arrowtail" ); |
818 | E_headlabel = agfindedgeattr(g, "headlabel" ); |
819 | E_taillabel = agfindedgeattr(g, "taillabel" ); |
820 | E_labelfontsize = agfindedgeattr(g, "labelfontsize" ); |
821 | E_labelfontname = agfindedgeattr(g, "labelfontname" ); |
822 | E_labelfontcolor = agfindedgeattr(g, "labelfontcolor" ); |
823 | E_labeldistance = agfindedgeattr(g, "labeldistance" ); |
824 | E_labelangle = agfindedgeattr(g, "labelangle" ); |
825 | /* end vladimir */ |
826 | E_minlen = agfindedgeattr(g, "minlen" ); |
827 | E_showboxes = agfindedgeattr(g, "showboxes" ); |
828 | E_style = agfindedgeattr(g, "style" ); |
829 | E_decorate = agfindedgeattr(g, "decorate" ); |
830 | E_arrowsz = agfindedgeattr(g, "arrowsize" ); |
831 | E_constr = agfindedgeattr(g, "constraint" ); |
832 | E_layer = agfindedgeattr(g, "layer" ); |
833 | E_comment = agfindedgeattr(g, "comment" ); |
834 | E_tailclip = agfindedgeattr(g, "tailclip" ); |
835 | E_headclip = agfindedgeattr(g, "headclip" ); |
836 | E_penwidth = agfindedgeattr(g, "penwidth" ); |
837 | |
838 | /* background */ |
839 | GD_drawing(g)->xdots = init_xdot (g); |
840 | |
841 | /* initialize id, if any */ |
842 | |
843 | if ((p = agget(g, "id" )) && *p) |
844 | GD_drawing(g)->id = strdup_and_subst_obj(p, g); |
845 | } |
846 | |
847 | void graph_cleanup(graph_t *g) |
848 | { |
849 | if (GD_drawing(g) && GD_drawing(g)->xdots) |
850 | freeXDot ((xdot*)GD_drawing(g)->xdots); |
851 | if (GD_drawing(g) && GD_drawing(g)->id) |
852 | free (GD_drawing(g)->id); |
853 | free(GD_drawing(g)); |
854 | GD_drawing(g) = NULL; |
855 | free_label(GD_label(g)); |
856 | //FIX HERE , STILL SHALLOW |
857 | //memset(&(g->u), 0, sizeof(Agraphinfo_t)); |
858 | agclean(g, AGRAPH,"Agraphinfo_t" ); |
859 | } |
860 | |
861 | /* charsetToStr: |
862 | * Given an internal charset value, return a canonical string |
863 | * representation. |
864 | */ |
865 | char* |
866 | charsetToStr (int c) |
867 | { |
868 | char* s; |
869 | |
870 | switch (c) { |
871 | case CHAR_UTF8 : |
872 | s = "UTF-8" ; |
873 | break; |
874 | case CHAR_LATIN1 : |
875 | s = "ISO-8859-1" ; |
876 | break; |
877 | case CHAR_BIG5 : |
878 | s = "BIG-5" ; |
879 | break; |
880 | default : |
881 | agerr(AGERR, "Unsupported charset value %d\n" , c); |
882 | s = "UTF-8" ; |
883 | break; |
884 | } |
885 | return s; |
886 | } |
887 | |
888 | /* do_graph_label: |
889 | * Set characteristics of graph label if it exists. |
890 | * |
891 | */ |
892 | void do_graph_label(graph_t * sg) |
893 | { |
894 | char *str, *pos, *just; |
895 | int pos_ix; |
896 | |
897 | /* it would be nice to allow multiple graph labels in the future */ |
898 | if ((str = agget(sg, "label" )) && (*str != '\0')) { |
899 | char pos_flag; |
900 | pointf dimen; |
901 | |
902 | GD_has_labels(sg->root) |= GRAPH_LABEL; |
903 | |
904 | GD_label(sg) = make_label((void*)sg, str, (aghtmlstr(str) ? LT_HTML : LT_NONE), |
905 | late_double(sg, agfindgraphattr(sg, "fontsize" ), |
906 | DEFAULT_FONTSIZE, MIN_FONTSIZE), |
907 | late_nnstring(sg, agfindgraphattr(sg, "fontname" ), |
908 | DEFAULT_FONTNAME), |
909 | late_nnstring(sg, agfindgraphattr(sg, "fontcolor" ), |
910 | DEFAULT_COLOR)); |
911 | |
912 | /* set label position */ |
913 | pos = agget(sg, "labelloc" ); |
914 | if (sg != agroot(sg)) { |
915 | if (pos && (pos[0] == 'b')) |
916 | pos_flag = LABEL_AT_BOTTOM; |
917 | else |
918 | pos_flag = LABEL_AT_TOP; |
919 | } else { |
920 | if (pos && (pos[0] == 't')) |
921 | pos_flag = LABEL_AT_TOP; |
922 | else |
923 | pos_flag = LABEL_AT_BOTTOM; |
924 | } |
925 | just = agget(sg, "labeljust" ); |
926 | if (just) { |
927 | if (just[0] == 'l') |
928 | pos_flag |= LABEL_AT_LEFT; |
929 | else if (just[0] == 'r') |
930 | pos_flag |= LABEL_AT_RIGHT; |
931 | } |
932 | GD_label_pos(sg) = pos_flag; |
933 | |
934 | if (sg == agroot(sg)) |
935 | return; |
936 | |
937 | /* Set border information for cluster labels to allow space |
938 | */ |
939 | dimen = GD_label(sg)->dimen; |
940 | PAD(dimen); |
941 | if (!GD_flip(agroot(sg))) { |
942 | if (GD_label_pos(sg) & LABEL_AT_TOP) |
943 | pos_ix = TOP_IX; |
944 | else |
945 | pos_ix = BOTTOM_IX; |
946 | GD_border(sg)[pos_ix] = dimen; |
947 | } else { |
948 | /* when rotated, the labels will be restored to TOP or BOTTOM */ |
949 | if (GD_label_pos(sg) & LABEL_AT_TOP) |
950 | pos_ix = RIGHT_IX; |
951 | else |
952 | pos_ix = LEFT_IX; |
953 | GD_border(sg)[pos_ix].x = dimen.y; |
954 | GD_border(sg)[pos_ix].y = dimen.x; |
955 | } |
956 | } |
957 | } |
958 | |