| 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 | |