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
15/*
16 * Written by Emden R. Gansner
17 */
18
19
20
21#include "config.h"
22
23#include <getopt.h>
24
25#include <assert.h>
26#include "gvc.h"
27#include "render.h"
28#include "neatoprocs.h"
29#include "ingraphs.h"
30#include "pack.h"
31
32/*visual studio*/
33#if defined(_WIN32)
34#define extern __declspec(dllimport)
35#endif
36/*end visual studio*/
37extern gvplugin_library_t gvplugin_neato_layout_LTX_library;
38#undef extern
39
40lt_symlist_t lt_preloaded_symbols[] = {
41#if defined(_WIN32)
42 { "gvplugin_neato_layout_LTX_library", 0 },
43#else
44 { "gvplugin_neato_layout_LTX_library", (void*)(&gvplugin_neato_layout_LTX_library) },
45#endif
46 { 0, 0 }
47};
48
49/* gvpack:
50 * Input consists of graphs in dot format.
51 * The graphs should have pos, width and height set for nodes,
52 * bb set for clusters, and, optionally, spline info for edges.
53 * The graphs are packed geometrically and combined
54 * into a single output graph, ready to be sent to neato -s -n2.
55 * -m <i> specifies the margin, in points, about each graph.
56 */
57
58typedef struct {
59 Dtlink_t link;
60 char *name;
61 char *value;
62 int cnt;
63} attr_t;
64
65typedef struct {
66 Dtlink_t link;
67 char *name;
68 int cnt;
69} pair_t;
70
71static int verbose = 0;
72static char **myFiles = 0;
73static int nGraphs = 0; /* Guess as to no. of graphs */
74static FILE *outfp; /* output; stdout by default */
75static Agdesc_t kind; /* type of graph */
76static int G_cnt; /* No. of -G arguments */
77static int G_sz; /* Storage size for -G arguments */
78static attr_t *G_args; /* Storage for -G arguments */
79static int doPack; /* Do packing if true */
80static char* gname = "root";
81
82#define NEWNODE(n) ((node_t*)ND_alg(n))
83
84static char *useString =
85 "Usage: gvpack [-gnuv?] [-m<margin>] {-array[_rc][n]] [-o<outf>] <files>\n\
86 -n - use node granularity\n\
87 -g - use graph granularity\n\
88 -array* - pack as array of graphs\n\
89 -G<n>=<v> - attach name/value attribute to output graph\n\
90 -m<n> - set margin to <n> points\n\
91 -s<gname> - use <gname> for name of root graph\n\
92 -o<outfile> - write output to <outfile>\n\
93 -u - no packing; just combine graphs\n\
94 -v - verbose\n\
95 -? - print usage\n\
96If no files are specified, stdin is used\n";
97
98static void usage(int v)
99{
100 printf("%s",useString);
101 exit(v);
102}
103
104static FILE *openFile(char *name, char *mode)
105{
106 FILE *fp;
107 char *modestr;
108
109 fp = fopen(name, mode);
110 if (!fp) {
111 if (*mode == 'r')
112 modestr = "reading";
113 else
114 modestr = "writing";
115 fprintf(stderr, "gvpack: could not open file %s for %s\n",
116 name, modestr);
117 exit(1);
118 }
119 return (fp);
120}
121
122#define G_CHUNK 10
123
124/* setNameValue:
125 * If arg is a name-value pair, add it to the list
126 * and return 0; otherwise, return 1.
127 */
128static int setNameValue(char *arg)
129{
130 char *p;
131 char *rhs = "true";
132
133 if ((p = strchr(arg, '='))) {
134 *p++ = '\0';
135 rhs = p;
136 }
137 if (G_cnt >= G_sz) {
138 G_sz += G_CHUNK;
139 G_args = RALLOC(G_sz, G_args, attr_t);
140 }
141 G_args[G_cnt].name = arg;
142 G_args[G_cnt].value = rhs;
143 G_cnt++;
144
145 return 0;
146}
147
148/* setUInt:
149 * If arg is an integer, value is stored in v
150 * and function returns 0; otherwise, returns 1.
151 */
152static int setUInt(unsigned int *v, char *arg)
153{
154 char *p;
155 unsigned int i;
156
157 i = (unsigned int) strtol(arg, &p, 10);
158 if (p == arg) {
159 fprintf(stderr, "Error: bad value in flag -%s - ignored\n",
160 arg - 1);
161 return 1;
162 }
163 *v = i;
164 return 0;
165}
166
167static Agsym_t *agraphattr(Agraph_t *g, char *name, char *value)
168{
169 return agattr(g, AGRAPH, name, value);
170}
171
172static Agsym_t *agnodeattr(Agraph_t *g, char *name, char *value)
173{
174 return agattr(g, AGNODE, name, value);
175}
176
177static Agsym_t *agedgeattr(Agraph_t *g, char *name, char *value)
178{
179 return agattr(g, AGEDGE, name, value);
180}
181
182/* init:
183 */
184static void init(int argc, char *argv[], pack_info* pinfo)
185{
186 int c, len;
187 char buf[BUFSIZ];
188 char* bp;
189
190 agnodeattr(NULL, "label", NODENAME_ESC);
191 pinfo->mode = l_clust;
192 pinfo->margin = CL_OFFSET;
193 pinfo->doSplines = TRUE; /* Use edges in packing */
194 pinfo->fixed = 0;
195 pinfo->sz = 0;
196
197 opterr = 0;
198 while ((c = getopt(argc, argv, ":na:gvum:s:o:G:")) != -1) {
199 switch (c) {
200 case 'a':
201 len = strlen(optarg) + 2;
202 if (len > BUFSIZ)
203 bp = N_GNEW(len, char);
204 else
205 bp = buf;
206 sprintf (bp, "a%s\n", optarg);
207 parsePackModeInfo (bp, pinfo->mode, pinfo);
208 if (bp != buf)
209 free (bp);
210 break;
211 case 'n':
212 parsePackModeInfo ("node", pinfo->mode, pinfo);
213 break;
214 case 's':
215 gname = optarg;
216 break;
217 case 'g':
218 parsePackModeInfo ("graph", pinfo->mode, pinfo);
219 break;
220 case 'm':
221 setUInt(&pinfo->margin, optarg);
222 break;
223 case 'o':
224 outfp = openFile(optarg, "w");
225 break;
226 case 'u':
227 pinfo->mode = l_undef;
228 break;
229 case 'G':
230 if (*optarg)
231 setNameValue(optarg);
232 else
233 fprintf(stderr,
234 "gvpack: option -G missing argument - ignored\n");
235 break;
236 case 'v':
237 verbose = 1;
238 Verbose = 1;
239 break;
240 case ':':
241 fprintf(stderr, "gvpack: option -%c missing argument - ignored\n", optopt);
242 break;
243 case '?':
244 if (optopt == '?')
245 usage(0);
246 else
247 fprintf(stderr,
248 "gvpack: option -%c unrecognized - ignored\n", optopt);
249 break;
250 }
251 }
252 argv += optind;
253 argc -= optind;
254
255 if (argc > 0) {
256 myFiles = argv;
257 nGraphs = argc; /* guess one graph per file */
258 } else
259 nGraphs = 10; /* initial guess as to no. of graphs */
260 if (!outfp)
261 outfp = stdout; /* stdout the default */
262 if (verbose)
263 fprintf (stderr, " margin %d\n", pinfo->margin);
264}
265
266/* init_node_edge:
267 * initialize node and edge attributes
268 */
269static void init_node_edge(Agraph_t * g)
270{
271 node_t *n;
272 edge_t *e;
273 int nG = agnnodes(g);
274 attrsym_t *N_pos = agfindnodeattr(g, "pos");
275 attrsym_t *N_pin = agfindnodeattr(g, "pin");
276
277 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
278 neato_init_node(n);
279 user_pos(N_pos, N_pin, n, nG); /* set user position if given */
280 }
281 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
282 for (e = agfstout(g, n); e; e = agnxtout(g, e))
283 common_init_edge(e);
284 }
285}
286
287/* init_graph:
288 * Initialize attributes. We always do the minimum required by
289 * libcommon. If fill is true, we use init_nop (neato -n) to
290 * read in attributes relevant to the layout.
291 */
292static void init_graph(Agraph_t * g, boolean fill, GVC_t* gvc)
293{
294 int d;
295 node_t *n;
296 edge_t *e;
297
298 aginit (g, AGRAPH, "Agraphinfo_t", sizeof(Agraphinfo_t), TRUE);
299 aginit (g, AGNODE, "Agnodeinfo_t", sizeof(Agnodeinfo_t), TRUE);
300 aginit (g, AGEDGE, "Agedgeinfo_t", sizeof(Agedgeinfo_t), TRUE);
301 GD_gvc(g) = gvc;
302 graph_init(g, FALSE);
303 d = late_int(g, agfindgraphattr(g, "dim"), 2, 2);
304 if (d != 2) {
305 fprintf(stderr, "Error: graph %s has dim = %d (!= 2)\n", agnameof(g),
306 d);
307 exit(1);
308 }
309 Ndim = GD_ndim(g) = 2;
310 init_node_edge(g);
311 if (fill) {
312 int ret = init_nop(g,0);
313 if (ret) {
314 if (ret < 0)
315 fprintf(stderr, "Error loading layout info from graph %s\n",
316 agnameof(g));
317 else if (ret > 0)
318 fprintf(stderr, "gvpack does not support backgrounds as found in graph %s\n",
319 agnameof(g));
320 exit(1);
321 }
322 if (Concentrate) { /* check for edges without pos info */
323 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
324 for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
325 if (ED_spl(e) == NULL) ED_edge_type(e) = IGNORED;
326 }
327 }
328 }
329 }
330}
331
332/* cloneAttrs:
333 * Copy all attributes from old object to new. Assume
334 * attributes have been initialized.
335 */
336static void cloneDfltAttrs(Agraph_t *old, Agraph_t *new, int kind)
337{
338 Agsym_t *a;
339
340 for (a = agnxtattr(old, kind, 0); a; a = agnxtattr(old, kind, a)) {
341 if (aghtmlstr(a->defval))
342 agattr (new, kind, a->name, agstrdup_html(new, a->defval));
343 else
344 agattr (new, kind, a->name, a->defval);
345 }
346}
347static void cloneAttrs(void *old, void *new)
348{
349 int kind = AGTYPE(old);
350 Agsym_t *a;
351 char* s;
352 Agraph_t *g = agroot(old);
353 Agraph_t *ng = agroot(new);
354
355 for (a = agnxtattr(g, kind, 0); a; a = agnxtattr(g, kind, a)) {
356 s = agxget (old, a);
357 if (aghtmlstr(s))
358 agset(new, a->name, agstrdup_html(ng, s));
359 else
360 agset(new, a->name, s);
361 }
362}
363
364/* cloneEdge:
365 * Note that here, and in cloneNode and cloneCluster,
366 * we do a shallow copy. We thus assume that attributes
367 * are not disturbed. In particular, we assume they are
368 * not deallocated.
369 */
370static void cloneEdge(Agedge_t * old, Agedge_t * new)
371{
372 cloneAttrs(old, new);
373 ED_spl(new) = ED_spl(old);
374 ED_edge_type(new) = ED_edge_type(old);
375 ED_label(new) = ED_label(old);
376 ED_head_label(new) = ED_head_label(old);
377 ED_tail_label(new) = ED_tail_label(old);
378 ED_xlabel(new) = ED_xlabel(old);
379}
380
381/* cloneNode:
382 */
383static void cloneNode(Agnode_t * old, Agnode_t * new)
384{
385 cloneAttrs(old, new);
386 ND_coord(new).x = POINTS(ND_pos(old)[0]);
387 ND_coord(new).y = POINTS(ND_pos(old)[1]);
388 ND_height(new) = ND_height(old);
389 ND_ht(new) = ND_ht(old);
390 ND_width(new) = ND_width(old);
391 ND_lw(new) = ND_lw(old);
392 ND_rw(new) = ND_rw(old);
393 ND_shape(new) = ND_shape(old);
394 ND_shape_info(new) = ND_shape_info(old);
395 ND_xlabel(new) = ND_xlabel(old);
396}
397
398/* cloneCluster:
399 */
400static void cloneCluster(Agraph_t * old, Agraph_t * new)
401{
402 /* string attributes were cloned as subgraphs */
403 GD_label(new) = GD_label(old);
404 GD_bb(new) = GD_bb(old);
405}
406
407/* freef:
408 * Generic free function for dictionaries.
409 */
410static void freef(Dt_t * dt, void * obj, Dtdisc_t * disc)
411{
412 free(obj);
413}
414
415static Dtdisc_t attrdisc = {
416 offsetof(attr_t, name), /* key */
417 -1, /* size */
418 offsetof(attr_t, link), /* link */
419 (Dtmake_f) 0,
420 (Dtfree_f) freef,
421 (Dtcompar_f) 0, /* use strcmp */
422 (Dthash_f) 0,
423 (Dtmemory_f) 0,
424 (Dtevent_f) 0
425};
426
427/* fillDict:
428 * Fill newdict with all the name-value attributes of
429 * objp. If the attribute has already been defined and
430 * has a different default, set default to "".
431 */
432static void fillDict(Dt_t * newdict, Agraph_t* g, int kind)
433{
434 Agsym_t *a;
435 char *name;
436 char *value;
437 attr_t *rv;
438
439 for (a = agnxtattr(g,kind,0); a; a = agnxtattr(g,kind,a)) {
440 name = a->name;
441 value = a->defval;
442 rv = (attr_t *) dtmatch(newdict, name);
443 if (!rv) {
444 rv = NEW(attr_t);
445 rv->name = name;
446 rv->value = value;
447 rv->cnt = 1;
448 dtinsert(newdict, rv);
449 } else if (!strcmp(value, rv->value))
450 rv->cnt++;
451 }
452}
453
454/* fillGraph:
455 * Use all the name-value entries in the dictionary d to define
456 * to define universal node/edge/graph attributes for g.
457 * For a non-empty default value, the attribute must be defined and the
458 * same in all graphs.
459 */
460static void
461fillGraph(Agraph_t * g, Dt_t * d,
462 Agsym_t * (*setf) (Agraph_t *, char *, char *), int cnt)
463{
464 attr_t *av;
465 for (av = (attr_t *) dtflatten(d); av;
466 av = (attr_t *) dtlink(d, (Dtlink_t *) av)) {
467 if (cnt == av->cnt)
468 setf(g, av->name, av->value);
469 else
470 setf(g, av->name, "");
471 }
472}
473
474/* initAttrs:
475 * Initialize the attributes of root as the union of the
476 * attributes in the graphs gs.
477 */
478static void initAttrs(Agraph_t * root, Agraph_t ** gs, int cnt)
479{
480 Agraph_t *g;
481 Dt_t *n_attrs;
482 Dt_t *e_attrs;
483 Dt_t *g_attrs;
484 int i;
485
486 n_attrs = dtopen(&attrdisc, Dtoset);
487 e_attrs = dtopen(&attrdisc, Dtoset);
488 g_attrs = dtopen(&attrdisc, Dtoset);
489
490 for (i = 0; i < cnt; i++) {
491 g = gs[i];
492 fillDict(g_attrs, g, AGRAPH);
493 fillDict(n_attrs, g, AGNODE);
494 fillDict(e_attrs, g, AGEDGE);
495 }
496
497 fillGraph(root, g_attrs, agraphattr, cnt);
498 fillGraph(root, n_attrs, agnodeattr, cnt);
499 fillGraph(root, e_attrs, agedgeattr, cnt);
500
501 dtclose(n_attrs);
502 dtclose(e_attrs);
503 dtclose(g_attrs);
504}
505
506/* cloneGraphAttr:
507 */
508static void cloneGraphAttr(Agraph_t * g, Agraph_t * ng)
509{
510 cloneAttrs(g, ng);
511 cloneDfltAttrs(g, ng, AGNODE);
512 cloneDfltAttrs(g, ng, AGEDGE);
513}
514
515#ifdef UNIMPL
516/* redoBB:
517 * If "bb" attribute is valid, translate to reflect graph's
518 * new packed position.
519 */
520static void redoBB(Agraph_t * g, char *s, Agsym_t * G_bb, point delta)
521{
522 box bb;
523 char buf[100];
524
525 if (sscanf(s, "%d,%d,%d,%d", &bb.LL.x, &bb.LL.y, &bb.UR.x, &bb.UR.y) ==
526 4) {
527 bb.LL.x += delta.x;
528 bb.LL.y += delta.y;
529 bb.UR.x += delta.x;
530 bb.UR.y += delta.y;
531 sprintf(buf, "%d,%d,%d,%d", bb.LL.x, bb.LL.y, bb.UR.x, bb.UR.y);
532 agxset(g, G_bb->index, buf);
533 }
534}
535#endif
536
537/* xName:
538 * Create a name for an object in the new graph using the
539 * dictionary names and the old name. If the old name has not
540 * been used, use it and add it to names. If it has been used,
541 * create a new name using the old name and a number.
542 * Note that returned string will immediately made into an agstring.
543 */
544static char *xName(Dt_t * names, char *oldname)
545{
546 static char* name = NULL;
547 static int namelen = 0;
548 char *namep;
549 pair_t *p;
550 int len;
551
552 p = (pair_t *) dtmatch(names, oldname);
553 if (p) {
554 p->cnt++;
555 len = strlen(oldname) + 100; /* 100 for "_gv" and decimal no. */
556 if (namelen < len) {
557 if (name) free (name);
558 name = N_NEW(len, char);
559 namelen = len;
560 }
561 sprintf(name, "%s_gv%d", oldname, p->cnt);
562 namep = name;
563 } else {
564 p = NEW(pair_t);
565 p->name = oldname;
566 dtinsert(names, p);
567 namep = oldname;
568 }
569 return namep;
570}
571
572#define MARK(e) (ED_alg(e) = e)
573#define MARKED(e) (ED_alg(e))
574#define ISCLUSTER(g) (!strncmp(agnameof(g),"cluster",7))
575#define SETCLUST(g,h) (GD_alg(g) = h)
576#define GETCLUST(g) ((Agraph_t*)GD_alg(g))
577
578/* cloneSubg:
579 * Create a copy of g in ng, copying attributes, inserting nodes
580 * and adding edges.
581 */
582static void
583cloneSubg(Agraph_t * g, Agraph_t * ng, Agsym_t * G_bb, Dt_t * gnames)
584{
585 node_t *n;
586 node_t *nn;
587 edge_t *e;
588 edge_t *ne;
589 node_t *nt;
590 node_t *nh;
591 Agraph_t *subg;
592 Agraph_t *nsubg;
593
594 cloneGraphAttr(g, ng);
595 if (doPack)
596 agxset(ng, G_bb, ""); /* Unset all subgraph bb */
597
598 /* clone subgraphs */
599 for (subg = agfstsubg (g); subg; subg = agnxtsubg (subg)) {
600 nsubg = agsubg(ng, xName(gnames, agnameof(subg)), 1);
601 agbindrec (nsubg, "Agraphinfo_t", sizeof(Agraphinfo_t), TRUE);
602 cloneSubg(subg, nsubg, G_bb, gnames);
603 /* if subgraphs are clusters, point to the new
604 * one so we can find it later.
605 */
606 if (ISCLUSTER(subg))
607 SETCLUST(subg, nsubg);
608 }
609
610 /* add remaining nodes */
611 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
612 nn = NEWNODE(n);
613 agsubnode(ng, nn, 1);
614 }
615
616 /* add remaining edges. libgraph doesn't provide a way to find
617 * multiedges, so we add edges bottom up, marking edges when added.
618 */
619 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
620 for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
621 if (MARKED(e))
622 continue;
623 nt = NEWNODE(agtail(e));
624 nh = NEWNODE(aghead(e));
625 ne = agedge(ng, nt, nh, NULL, 1);
626 agbindrec (ne, "Agedgeinfo_t", sizeof(Agedgeinfo_t), TRUE);
627 cloneEdge(e, ne);
628 MARK(e);
629 }
630 }
631}
632
633/* cloneClusterTree:
634 * Given old and new subgraphs which are corresponding
635 * clusters, recursively create the subtree of clusters
636 * under ng using the subtree of clusters under g.
637 */
638static void cloneClusterTree(Agraph_t * g, Agraph_t * ng)
639{
640 int i;
641
642 cloneCluster(g, ng);
643
644 if (GD_n_cluster(g)) {
645 GD_n_cluster(ng) = GD_n_cluster(g);
646 GD_clust(ng) = N_NEW(1 + GD_n_cluster(g), Agraph_t *);
647 for (i = 1; i <= GD_n_cluster(g); i++) {
648 Agraph_t *c = GETCLUST(GD_clust(g)[i]);
649 GD_clust(ng)[i] = c;
650 cloneClusterTree(GD_clust(g)[i], c);
651 }
652 }
653}
654
655static Dtdisc_t pairdisc = {
656 offsetof(pair_t, name), /* key */
657 -1, /* size */
658 offsetof(attr_t, link), /* link */
659 (Dtmake_f) 0,
660 (Dtfree_f) freef,
661 (Dtcompar_f) 0, /* use strcmp */
662 (Dthash_f) 0,
663 (Dtmemory_f) 0,
664 (Dtevent_f) 0
665};
666
667/* cloneGraph:
668 * Create and return a new graph which is the logical union
669 * of the graphs gs.
670 */
671static Agraph_t *cloneGraph(Agraph_t ** gs, int cnt, GVC_t * gvc)
672{
673 Agraph_t *root;
674 Agraph_t *g;
675 Agraph_t *subg;
676 Agnode_t *n;
677 Agnode_t *np;
678 int i;
679 Dt_t *gnames; /* dict of used subgraph names */
680 Dt_t *nnames; /* dict of used node names */
681 Agsym_t *G_bb;
682 Agsym_t *rv;
683 boolean doWarn = TRUE;
684
685 if (verbose)
686 fprintf(stderr, "Creating clone graph\n");
687 root = agopen(gname, kind, &AgDefaultDisc);
688 initAttrs(root, gs, cnt);
689 G_bb = agfindgraphattr(root, "bb");
690 if (doPack) assert(G_bb);
691
692 /* add command-line attributes */
693 for (i = 0; i < G_cnt; i++) {
694 rv = agfindgraphattr(root, G_args[i].name);
695 if (rv)
696 agxset(root, rv, G_args[i].value);
697 else
698 agattr(root, AGRAPH, G_args[i].name, G_args[i].value);
699 }
700
701 /* do common initialization. This will handle root's label. */
702 init_graph(root, FALSE, gvc);
703 State = GVSPLINES;
704
705 gnames = dtopen(&pairdisc, Dtoset);
706 nnames = dtopen(&pairdisc, Dtoset);
707 for (i = 0; i < cnt; i++) {
708 g = gs[i];
709 if (verbose)
710 fprintf(stderr, "Cloning graph %s\n", agnameof(g));
711 GD_n_cluster(root) += GD_n_cluster(g);
712 GD_has_labels(root) |= GD_has_labels(g);
713
714 /* Clone nodes, checking for node name conflicts */
715 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
716 if (doWarn && agfindnode(root, agnameof(n))) {
717 fprintf(stderr,
718 "Warning: node %s in graph[%d] %s already defined\n",
719 agnameof(n), i, agnameof(g));
720 fprintf(stderr, "Some nodes will be renamed.\n");
721 doWarn = FALSE;
722 }
723 np = agnode(root, xName(nnames, agnameof(n)), 1);
724 agbindrec (np, "Agnodeinfo_t", sizeof(Agnodeinfo_t), TRUE);
725 ND_alg(n) = np;
726 cloneNode(n, np);
727 }
728
729 /* wrap the clone of g in a subgraph of root */
730 subg = agsubg(root, xName(gnames, agnameof(g)), 1);
731 agbindrec (subg, "Agraphinfo_t", sizeof(Agraphinfo_t), TRUE);
732 cloneSubg(g, subg, G_bb, gnames);
733 }
734 dtclose(gnames);
735 dtclose(nnames);
736
737 /* set up cluster tree */
738 if (GD_n_cluster(root)) {
739 int j, idx;
740 GD_clust(root) = N_NEW(1 + GD_n_cluster(root), graph_t *);
741
742 idx = 1;
743 for (i = 0; i < cnt; i++) {
744 g = gs[i];
745 for (j = 1; j <= GD_n_cluster(g); j++) {
746 Agraph_t *c = GETCLUST(GD_clust(g)[j]);
747 GD_clust(root)[idx++] = c;
748 cloneClusterTree(GD_clust(g)[j], c);
749 }
750 }
751 }
752
753 return root;
754}
755
756static Agraph_t *gread(FILE * fp)
757{
758 return agread(fp, (Agdisc_t *) 0);
759}
760
761/* readGraphs:
762 * Read input, parse the graphs, use init_nop (neato -n) to
763 * read in all attributes need for layout.
764 * Return the list of graphs. If cp != NULL, set it to the number
765 * of graphs read.
766 * We keep track of the types of graphs read. They all must be
767 * either directed or undirected. If all graphs are strict, the
768 * combined graph will be strict; other, the combined graph will
769 * be non-strict.
770 */
771static Agraph_t **readGraphs(int *cp, GVC_t* gvc)
772{
773 Agraph_t *g;
774 Agraph_t **gs = 0;
775 ingraph_state ig;
776 int cnt = 0;
777 int sz = 0;
778 int kindUnset = 1;
779
780 /* set various state values */
781 PSinputscale = POINTS_PER_INCH;
782 Nop = 2;
783
784 newIngraph(&ig, myFiles, gread);
785 while ((g = nextGraph(&ig)) != 0) {
786 if (verbose)
787 fprintf(stderr, "Reading graph %s\n", agnameof(g));
788 if (agnnodes(g) == 0) {
789 fprintf(stderr, "Graph %s is empty - ignoring\n", agnameof(g));
790 continue;
791 }
792 if (cnt >= sz) {
793 sz += nGraphs;
794 gs = ALLOC(sz, gs, Agraph_t *);
795 }
796 if (kindUnset) {
797 kindUnset = 0;
798 kind = g->desc;
799 }
800 else if (kind.directed != g->desc.directed) {
801 fprintf(stderr,
802 "Error: all graphs must be directed or undirected\n");
803 exit(1);
804 } else if (!agisstrict(g))
805 kind = g->desc;
806 init_graph(g, doPack, gvc);
807 gs[cnt++] = g;
808 }
809
810 gs = RALLOC(cnt, gs, Agraph_t *);
811 if (cp)
812 *cp = cnt;
813 return gs;
814}
815
816/* compBB:
817 * Compute the bounding box containing the graphs.
818 * We can just use the bounding boxes of the graphs.
819 */
820boxf compBB(Agraph_t ** gs, int cnt)
821{
822 boxf bb, bb2;
823 int i;
824
825 bb = GD_bb(gs[0]);
826
827 for (i = 1; i < cnt; i++) {
828 bb2 = GD_bb(gs[i]);
829 bb.LL.x = MIN(bb.LL.x, bb2.LL.x);
830 bb.LL.y = MIN(bb.LL.y, bb2.LL.y);
831 bb.UR.x = MAX(bb.UR.x, bb2.UR.x);
832 bb.UR.y = MAX(bb.UR.y, bb2.UR.y);
833 }
834
835 return bb;
836}
837
838#ifdef DEBUG
839void dump(Agraph_t * g)
840{
841 node_t *v;
842 edge_t *e;
843
844 for (v = agfstnode(g); v; v = agnxtnode(g, v)) {
845 fprintf(stderr, "%s\n", agnameof(v));
846 for (e = agfstout(g, v); e; e = agnxtout(g, e)) {
847 fprintf(stderr, " %s -- %s\n", agnameof(agtail(e)), agnameof(aghead(e)));
848 }
849 }
850}
851
852void dumps(Agraph_t * g)
853{
854 graph_t *subg;
855
856 for (subg = agfstsubg(g); subg; subg = agnxtsubg(subg)) {
857 dump(subg);
858 fprintf(stderr, "====\n");
859 }
860}
861#endif
862
863int main(int argc, char *argv[])
864{
865 Agraph_t **gs;
866 Agraph_t *g;
867 int cnt;
868 pack_info pinfo;
869 GVC_t * gvc;
870
871 init(argc, argv, &pinfo);
872
873 doPack = (pinfo.mode != l_undef);
874
875#if defined(_WIN32)
876 lt_preloaded_symbols[0].address = (void*)(&gvplugin_neato_layout_LTX_library);
877#endif
878 gvc = gvContextPlugins(lt_preloaded_symbols, DEMAND_LOADING);
879 gs = readGraphs(&cnt, gvc);
880 if (cnt == 0)
881 exit(0);
882
883 /* pack graphs */
884 if (doPack) {
885 if (packGraphs(cnt, gs, 0, &pinfo)) {
886 fprintf(stderr, "gvpack: packing of graphs failed.\n");
887 exit(1);
888 }
889 }
890
891 /* create union graph and copy attributes */
892 g = cloneGraph(gs, cnt, gvc);
893
894 /* compute new top-level bb and set */
895 if (doPack) {
896 GD_bb(g) = compBB(gs, cnt);
897 dotneato_postprocess(g);
898 attach_attrs(g);
899 }
900 agwrite(g, outfp);
901 exit(0);
902}
903