gvpack.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. /**
  2. * @file
  3. * @brief merge and pack disjoint graphs
  4. */
  5. /*************************************************************************
  6. * Copyright (c) 2011 AT&T Intellectual Property
  7. * All rights reserved. This program and the accompanying materials
  8. * are made available under the terms of the Eclipse Public License v1.0
  9. * which accompanies this distribution, and is available at
  10. * https://www.eclipse.org/legal/epl-v10.html
  11. *
  12. * Contributors: Details at https://graphviz.org
  13. *************************************************************************/
  14. /*
  15. * Written by Emden R. Gansner
  16. */
  17. #include "config.h"
  18. #include <getopt.h>
  19. #include <algorithm>
  20. #include <cassert>
  21. #include <gvc/gvc.h>
  22. #include <cgraph/ingraphs.h>
  23. #include <common/render.h>
  24. #include <common/utils.h>
  25. #include <neatogen/neatoprocs.h>
  26. #include <iostream>
  27. #include <limits>
  28. #include <map>
  29. #include <optional>
  30. #include <pack/pack.h>
  31. #include <set>
  32. #include <stddef.h>
  33. #include <string>
  34. #include <utility>
  35. #include <util/alloc.h>
  36. #include <util/exit.h>
  37. #include <vector>
  38. #include "openFile.h"
  39. extern "C" {
  40. #ifdef GVDLL
  41. __declspec(dllimport)
  42. #endif
  43. extern gvplugin_library_t gvplugin_neato_layout_LTX_library;
  44. }
  45. lt_symlist_t lt_preloaded_symbols[] = {
  46. #ifdef GVDLL
  47. { "gvplugin_neato_layout_LTX_library", 0 },
  48. #else
  49. { "gvplugin_neato_layout_LTX_library", &gvplugin_neato_layout_LTX_library },
  50. #endif
  51. { 0, 0 }
  52. };
  53. /* gvpack:
  54. * Input consists of graphs in dot format.
  55. * The graphs should have pos, width and height set for nodes,
  56. * bb set for clusters, and, optionally, spline info for edges.
  57. * The graphs are packed geometrically and combined
  58. * into a single output graph, ready to be sent to neato -s -n2.
  59. * -m <i> specifies the margin, in points, about each graph.
  60. */
  61. typedef struct {
  62. char *name;
  63. char *value;
  64. } attr_t;
  65. static int verbose = 0;
  66. static char **myFiles = 0;
  67. static FILE *outfp; /* output; stdout by default */
  68. static std::vector<attr_t> G_args; // Storage for -G arguments
  69. static bool doPack; /* Do packing if true */
  70. static char* gname = const_cast<char*>("root");
  71. #define NEWNODE(n) ((node_t*)ND_alg(n))
  72. static const char useString[] =
  73. "Usage: gvpack [-gnuv?] [-m<margin>] {-array[_rc][n]] [-o<outf>] <files>\n\
  74. -n - use node granularity\n\
  75. -g - use graph granularity\n\
  76. -array* - pack as array of graphs\n\
  77. -G<n>=<v> - attach name/value attribute to output graph\n\
  78. -m<n> - set margin to <n> points\n\
  79. -s<gname> - use <gname> for name of root graph\n\
  80. -o<outfile> - write output to <outfile>\n\
  81. -u - no packing; just combine graphs\n\
  82. -v - verbose\n\
  83. -? - print usage\n\
  84. If no files are specified, stdin is used\n";
  85. static void usage(int v)
  86. {
  87. std::cout << useString;
  88. graphviz_exit(v);
  89. }
  90. /* setNameValue:
  91. * If arg is a name-value pair, add it to the list
  92. * and return 0; otherwise, return 1.
  93. */
  94. static int setNameValue(char *arg)
  95. {
  96. char *rhs = const_cast<char*>("true");
  97. if (char *p = strchr(arg, '=')) {
  98. *p++ = '\0';
  99. rhs = p;
  100. }
  101. G_args.push_back(attr_t{arg, rhs});
  102. return 0;
  103. }
  104. /* setUInt:
  105. * If arg is an integer, value is stored in v
  106. * and function returns 0; otherwise, returns 1.
  107. */
  108. static int setUInt(unsigned int *v, char *arg)
  109. {
  110. char *p;
  111. unsigned int i;
  112. i = (unsigned int) strtol(arg, &p, 10);
  113. if (p == arg) {
  114. std::cerr << "Error: bad value in flag -" << (arg - 1) << " - ignored\n";
  115. return 1;
  116. }
  117. *v = i;
  118. return 0;
  119. }
  120. static Agsym_t *agraphattr(Agraph_t *g, char *name, const char *value) {
  121. return agattr(g, AGRAPH, name, value);
  122. }
  123. static Agsym_t *agnodeattr(Agraph_t *g, char *name, const char *value) {
  124. return agattr(g, AGNODE, name, value);
  125. }
  126. static Agsym_t *agedgeattr(Agraph_t *g, char *name, const char *value) {
  127. return agattr(g, AGEDGE, name, value);
  128. }
  129. static void init(int argc, char *argv[], pack_info* pinfo)
  130. {
  131. int c;
  132. agnodeattr(nullptr, const_cast<char*>("label"), NODENAME_ESC);
  133. pinfo->mode = l_clust;
  134. pinfo->margin = CL_OFFSET;
  135. pinfo->doSplines = true; // Use edges in packing
  136. pinfo->fixed = nullptr;
  137. pinfo->sz = 0;
  138. opterr = 0;
  139. while ((c = getopt(argc, argv, ":na:gvum:s:o:G:?")) != -1) {
  140. switch (c) {
  141. case 'a': {
  142. auto buf = std::string("a") + optarg + "\n";
  143. parsePackModeInfo(buf.c_str(), pinfo->mode, pinfo);
  144. break;
  145. }
  146. case 'n':
  147. parsePackModeInfo ("node", pinfo->mode, pinfo);
  148. break;
  149. case 's':
  150. gname = optarg;
  151. break;
  152. case 'g':
  153. parsePackModeInfo ("graph", pinfo->mode, pinfo);
  154. break;
  155. case 'm':
  156. setUInt(&pinfo->margin, optarg);
  157. break;
  158. case 'o':
  159. if (outfp != nullptr)
  160. fclose(outfp);
  161. outfp = openFile("gvpack", optarg, "w");
  162. break;
  163. case 'u':
  164. pinfo->mode = l_undef;
  165. break;
  166. case 'G':
  167. if (*optarg)
  168. setNameValue(optarg);
  169. else
  170. std::cerr << "gvpack: option -G missing argument - ignored\n";
  171. break;
  172. case 'v':
  173. verbose = 1;
  174. Verbose = 1;
  175. break;
  176. case ':':
  177. std::cerr << "gvpack: option -" << (char)optopt
  178. << " missing argument - ignored\n";
  179. break;
  180. case '?':
  181. if (optopt == '\0' || optopt == '?')
  182. usage(0);
  183. else {
  184. std::cerr << "gvpack: option -" << (char)optopt << " unrecognized\n";
  185. usage(1);
  186. }
  187. break;
  188. }
  189. }
  190. argv += optind;
  191. argc -= optind;
  192. if (argc > 0) {
  193. myFiles = argv;
  194. }
  195. if (!outfp)
  196. outfp = stdout; /* stdout the default */
  197. if (verbose)
  198. std::cerr << " margin " << pinfo->margin << '\n';
  199. }
  200. /* init_node_edge:
  201. * initialize node and edge attributes
  202. */
  203. static void init_node_edge(Agraph_t * g)
  204. {
  205. node_t *n;
  206. edge_t *e;
  207. int nG = agnnodes(g);
  208. attrsym_t *N_pos = agfindnodeattr(g, const_cast<char*>("pos"));
  209. attrsym_t *N_pin = agfindnodeattr(g, const_cast<char*>("pin"));
  210. for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
  211. neato_init_node(n);
  212. user_pos(N_pos, N_pin, n, nG); /* set user position if given */
  213. }
  214. for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
  215. for (e = agfstout(g, n); e; e = agnxtout(g, e))
  216. common_init_edge(e);
  217. }
  218. }
  219. /* init_graph:
  220. * Initialize attributes. We always do the minimum required by
  221. * libcommon. If fill is true, we use init_nop (neato -n) to
  222. * read in attributes relevant to the layout.
  223. */
  224. static void init_graph(Agraph_t *g, bool fill, GVC_t *gvc) {
  225. int d;
  226. node_t *n;
  227. edge_t *e;
  228. aginit (g, AGRAPH, "Agraphinfo_t", sizeof(Agraphinfo_t), true);
  229. aginit (g, AGNODE, "Agnodeinfo_t", sizeof(Agnodeinfo_t), true);
  230. aginit (g, AGEDGE, "Agedgeinfo_t", sizeof(Agedgeinfo_t), true);
  231. GD_gvc(g) = gvc;
  232. graph_init(g, false);
  233. d = late_int(g, agfindgraphattr(g, const_cast<char*>("dim")), 2, 2);
  234. if (d != 2) {
  235. std::cerr << "Error: graph " << agnameof(g) << " has dim = " << d
  236. << " (!= 2)\n";
  237. graphviz_exit(1);
  238. }
  239. Ndim = GD_ndim(g) = 2;
  240. init_node_edge(g);
  241. if (fill) {
  242. if (int ret = init_nop(g, 0)) {
  243. if (ret < 0)
  244. std::cerr << "Error loading layout info from graph " << agnameof(g) << '\n';
  245. else if (ret > 0)
  246. std::cerr << "gvpack does not support backgrounds as found in graph "
  247. << agnameof(g) << '\n';
  248. graphviz_exit(1);
  249. }
  250. if (Concentrate) { /* check for edges without pos info */
  251. for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
  252. for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
  253. if (ED_spl(e) == nullptr) ED_edge_type(e) = IGNORED;
  254. }
  255. }
  256. }
  257. }
  258. }
  259. /* cloneAttrs:
  260. * Copy all attributes from old object to new. Assume
  261. * attributes have been initialized.
  262. */
  263. static void cloneDfltAttrs(Agraph_t *old, Agraph_t *new_graph, int attr_kind) {
  264. Agsym_t *a;
  265. for (a = agnxtattr(old, attr_kind, 0); a; a = agnxtattr(old, attr_kind, a)) {
  266. if (aghtmlstr(a->defval)) {
  267. char *s = agstrdup_html(new_graph, a->defval);
  268. agattr(new_graph, attr_kind, a->name, s);
  269. agstrfree(new_graph, s); // drop the extra reference count we bumped for s
  270. } else {
  271. agattr(new_graph, attr_kind, a->name, a->defval);
  272. }
  273. }
  274. }
  275. static void cloneAttrs(void *old, void *new_graph) {
  276. int attr_kind = AGTYPE(old);
  277. Agsym_t *a;
  278. char* s;
  279. Agraph_t *g = agroot(old);
  280. Agraph_t *ng = agroot(new_graph);
  281. for (a = agnxtattr(g, attr_kind, 0); a; a = agnxtattr(g, attr_kind, a)) {
  282. s = agxget (old, a);
  283. if (aghtmlstr(s)) {
  284. char *scopy = agstrdup_html(ng, s);
  285. agset(new_graph, a->name, scopy);
  286. agstrfree(ng, scopy); // drop the extra reference count we bumped for scopy
  287. } else {
  288. agset(new_graph, a->name, s);
  289. }
  290. }
  291. }
  292. /* cloneEdge:
  293. * Note that here, and in cloneNode and cloneCluster,
  294. * we do a shallow copy. We thus assume that attributes
  295. * are not disturbed. In particular, we assume they are
  296. * not deallocated.
  297. */
  298. static void cloneEdge(Agedge_t *old, Agedge_t *new_edge) {
  299. cloneAttrs(old, new_edge);
  300. ED_spl(new_edge) = ED_spl(old);
  301. ED_edge_type(new_edge) = ED_edge_type(old);
  302. ED_label(new_edge) = ED_label(old);
  303. ED_head_label(new_edge) = ED_head_label(old);
  304. ED_tail_label(new_edge) = ED_tail_label(old);
  305. ED_xlabel(new_edge) = ED_xlabel(old);
  306. }
  307. static void cloneNode(Agnode_t *old, Agnode_t *new_node) {
  308. cloneAttrs(old, new_node);
  309. ND_coord(new_node).x = POINTS(ND_pos(old)[0]);
  310. ND_coord(new_node).y = POINTS(ND_pos(old)[1]);
  311. ND_height(new_node) = ND_height(old);
  312. ND_ht(new_node) = ND_ht(old);
  313. ND_width(new_node) = ND_width(old);
  314. ND_lw(new_node) = ND_lw(old);
  315. ND_rw(new_node) = ND_rw(old);
  316. ND_shape(new_node) = ND_shape(old);
  317. ND_shape_info(new_node) = ND_shape_info(old);
  318. ND_xlabel(new_node) = ND_xlabel(old);
  319. }
  320. static void cloneCluster(Agraph_t *old, Agraph_t *new_cluster) {
  321. // string attributes were cloned as subgraphs
  322. GD_label(new_cluster) = GD_label(old);
  323. GD_bb(new_cluster) = GD_bb(old);
  324. }
  325. namespace {
  326. /// a value of a graph attribute, possibly set multiple times
  327. struct AttributeValue {
  328. std::string value; ///< text of the value
  329. size_t instances; ///< number of times this attribute was seen
  330. };
  331. } // namespace
  332. /// attribute name → value collection of those we have seen
  333. using attr_map_t = std::map<std::string, AttributeValue>;
  334. /* fillDict:
  335. * Fill newdict with all the name-value attributes of
  336. * objp. If the attribute has already been defined and
  337. * has a different default, set default to "".
  338. */
  339. static void fillDict(attr_map_t &newdict, Agraph_t *g, int attr_kind) {
  340. for (Agsym_t *a = agnxtattr(g, attr_kind, 0); a; a = agnxtattr(g, attr_kind, a)) {
  341. char *name = a->name;
  342. char *value = a->defval;
  343. auto it = newdict.find(name);
  344. if (it == newdict.end()) {
  345. newdict.insert({name, AttributeValue{value, 1}});
  346. } else if (it->second.value == value)
  347. ++it->second.instances;
  348. }
  349. }
  350. /* fillGraph:
  351. * Use all the name-value entries in the dictionary d to define
  352. * to define universal node/edge/graph attributes for g.
  353. * For a non-empty default value, the attribute must be defined and the
  354. * same in all graphs.
  355. */
  356. static void fillGraph(Agraph_t *g, const attr_map_t &d,
  357. Agsym_t *(*setf)(Agraph_t *, char *, const char *),
  358. size_t cnt) {
  359. for (const auto &kv : d) {
  360. const std::string &name = kv.first;
  361. const std::string &value = kv.second.value;
  362. const size_t &attr_cnt = kv.second.instances;
  363. if (cnt == attr_cnt)
  364. setf(g, const_cast<char *>(name.c_str()), value.c_str());
  365. else
  366. setf(g, const_cast<char *>(name.c_str()), "");
  367. }
  368. }
  369. /* initAttrs:
  370. * Initialize the attributes of root as the union of the
  371. * attributes in the graphs gs.
  372. */
  373. static void initAttrs(Agraph_t *root, std::vector<Agraph_t*> &gs) {
  374. attr_map_t n_attrs;
  375. attr_map_t e_attrs;
  376. attr_map_t g_attrs;
  377. for (Agraph_t *g : gs) {
  378. fillDict(g_attrs, g, AGRAPH);
  379. fillDict(n_attrs, g, AGNODE);
  380. fillDict(e_attrs, g, AGEDGE);
  381. }
  382. fillGraph(root, g_attrs, agraphattr, gs.size());
  383. fillGraph(root, n_attrs, agnodeattr, gs.size());
  384. fillGraph(root, e_attrs, agedgeattr, gs.size());
  385. }
  386. static void cloneGraphAttr(Agraph_t * g, Agraph_t * ng)
  387. {
  388. cloneAttrs(g, ng);
  389. cloneDfltAttrs(g, ng, AGNODE);
  390. cloneDfltAttrs(g, ng, AGEDGE);
  391. }
  392. /// names that have already been used during generation
  393. using used_t = std::multiset<std::string>;
  394. /* xName:
  395. * Create a name for an object in the new graph using the
  396. * dictionary names and the old name. If the old name has not
  397. * been used, use it and add it to names. If it has been used,
  398. * create a new name using the old name and a number.
  399. * Note that returned string will immediately made into an agstring.
  400. */
  401. static std::string xName(used_t &names, char *oldname) {
  402. size_t previous_instances = names.count(oldname);
  403. names.insert(oldname);
  404. if (previous_instances > 0) {
  405. return std::string(oldname) + "_gv" + std::to_string(previous_instances);
  406. }
  407. return oldname;
  408. }
  409. #define MARK(e) (ED_alg(e) = e)
  410. #define MARKED(e) (ED_alg(e))
  411. #define SETCLUST(g,h) (GD_alg(g) = h)
  412. #define GETCLUST(g) ((Agraph_t*)GD_alg(g))
  413. /* cloneSubg:
  414. * Create a copy of g in ng, copying attributes, inserting nodes
  415. * and adding edges.
  416. */
  417. static void
  418. cloneSubg(Agraph_t *g, Agraph_t *ng, Agsym_t *G_bb, used_t &gnames) {
  419. node_t *n;
  420. node_t *nn;
  421. edge_t *e;
  422. edge_t *ne;
  423. node_t *nt;
  424. node_t *nh;
  425. Agraph_t *subg;
  426. Agraph_t *nsubg;
  427. cloneGraphAttr(g, ng);
  428. if (doPack)
  429. agxset(ng, G_bb, ""); /* Unset all subgraph bb */
  430. /* clone subgraphs */
  431. for (subg = agfstsubg (g); subg; subg = agnxtsubg (subg)) {
  432. nsubg = agsubg(ng, xName(gnames, agnameof(subg)).data(), 1);
  433. agbindrec (nsubg, "Agraphinfo_t", sizeof(Agraphinfo_t), true);
  434. cloneSubg(subg, nsubg, G_bb, gnames);
  435. /* if subgraphs are clusters, point to the new
  436. * one so we can find it later.
  437. */
  438. if (is_a_cluster(subg))
  439. SETCLUST(subg, nsubg);
  440. }
  441. /* add remaining nodes */
  442. for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
  443. nn = NEWNODE(n);
  444. agsubnode(ng, nn, 1);
  445. }
  446. /* add remaining edges. libgraph doesn't provide a way to find
  447. * multiedges, so we add edges bottom up, marking edges when added.
  448. */
  449. for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
  450. for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
  451. if (MARKED(e))
  452. continue;
  453. nt = NEWNODE(agtail(e));
  454. nh = NEWNODE(aghead(e));
  455. ne = agedge(ng, nt, nh, nullptr, 1);
  456. agbindrec (ne, "Agedgeinfo_t", sizeof(Agedgeinfo_t), true);
  457. cloneEdge(e, ne);
  458. MARK(e);
  459. }
  460. }
  461. }
  462. /* cloneClusterTree:
  463. * Given old and new subgraphs which are corresponding
  464. * clusters, recursively create the subtree of clusters
  465. * under ng using the subtree of clusters under g.
  466. */
  467. static void cloneClusterTree(Agraph_t * g, Agraph_t * ng)
  468. {
  469. int i;
  470. cloneCluster(g, ng);
  471. if (GD_n_cluster(g)) {
  472. GD_n_cluster(ng) = GD_n_cluster(g);
  473. GD_clust(ng) = reinterpret_cast<Agraph_t**>(gv_calloc(1 + GD_n_cluster(g), sizeof(Agraph_t*)));
  474. for (i = 1; i <= GD_n_cluster(g); i++) {
  475. Agraph_t *c = GETCLUST(GD_clust(g)[i]);
  476. GD_clust(ng)[i] = c;
  477. cloneClusterTree(GD_clust(g)[i], c);
  478. }
  479. }
  480. }
  481. /* cloneGraph:
  482. * Create and return a new graph which is the logical union
  483. * of the graphs gs.
  484. */
  485. static Agraph_t *cloneGraph(std::vector<Agraph_t *> &gs, GVC_t *gvc,
  486. Agdesc_t kind) {
  487. Agraph_t *root;
  488. Agraph_t *subg;
  489. Agnode_t *n;
  490. Agnode_t *np;
  491. Agsym_t *G_bb;
  492. bool doWarn = true;
  493. if (verbose)
  494. std::cerr << "Creating clone graph\n";
  495. root = agopen(gname, kind, &AgDefaultDisc);
  496. initAttrs(root, gs);
  497. G_bb = agfindgraphattr(root, const_cast<char*>("bb"));
  498. if (doPack) assert(G_bb);
  499. /* add command-line attributes */
  500. for (attr_t &a : G_args) {
  501. if (Agsym_t *rv = agfindgraphattr(root, a.name))
  502. agxset(root, rv, a.value);
  503. else
  504. agattr(root, AGRAPH, a.name, a.value);
  505. }
  506. /* do common initialization. This will handle root's label. */
  507. init_graph(root, false, gvc);
  508. State = GVSPLINES;
  509. used_t gnames; // dict of used subgraph names
  510. used_t nnames; // dict of used node names
  511. for (size_t i = 0; i < gs.size(); i++) {
  512. Agraph_t *g = gs[i];
  513. if (verbose)
  514. std::cerr << "Cloning graph " << agnameof(g) << '\n';
  515. GD_n_cluster(root) += GD_n_cluster(g);
  516. GD_has_labels(root) |= GD_has_labels(g);
  517. /* Clone nodes, checking for node name conflicts */
  518. for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
  519. if (doWarn && agfindnode(root, agnameof(n))) {
  520. std::cerr << "Warning: node " << agnameof(n) << " in graph[" << i << "] "
  521. << agnameof(g) << " already defined\n"
  522. << "Some nodes will be renamed.\n";
  523. doWarn = false;
  524. }
  525. np = agnode(root, xName(nnames, agnameof(n)).data(), 1);
  526. agbindrec (np, "Agnodeinfo_t", sizeof(Agnodeinfo_t), true);
  527. ND_alg(n) = np;
  528. cloneNode(n, np);
  529. }
  530. /* wrap the clone of g in a subgraph of root */
  531. subg = agsubg(root, xName(gnames, agnameof(g)).data(), 1);
  532. agbindrec (subg, "Agraphinfo_t", sizeof(Agraphinfo_t), true);
  533. cloneSubg(g, subg, G_bb, gnames);
  534. }
  535. /* set up cluster tree */
  536. if (GD_n_cluster(root)) {
  537. int j, idx;
  538. GD_clust(root) = reinterpret_cast<graph_t**>(gv_calloc(1 + GD_n_cluster(root), sizeof(graph_t*)));
  539. idx = 1;
  540. for (Agraph_t *g : gs) {
  541. for (j = 1; j <= GD_n_cluster(g); j++) {
  542. Agraph_t *c = GETCLUST(GD_clust(g)[j]);
  543. GD_clust(root)[idx++] = c;
  544. cloneClusterTree(GD_clust(g)[j], c);
  545. }
  546. }
  547. }
  548. return root;
  549. }
  550. /* readGraphs:
  551. * Read input, parse the graphs, use init_nop (neato -n) to
  552. * read in all attributes need for layout.
  553. * Return the list of graphs. If cp != nullptr, set it to the number
  554. * of graphs read.
  555. * We keep track of the types of graphs read. They all must be
  556. * either directed or undirected. If all graphs are strict, the
  557. * combined graph will be strict; other, the combined graph will
  558. * be non-strict.
  559. *
  560. * @param kind [out] The type to use for the combined graph
  561. */
  562. static std::vector<Agraph_t *> readGraphs(GVC_t *gvc,
  563. std::optional<Agdesc_t> &kind) {
  564. std::vector<Agraph_t*> gs;
  565. ingraph_state ig;
  566. /* set various state values */
  567. PSinputscale = POINTS_PER_INCH;
  568. Nop = 2;
  569. newIngraph(&ig, myFiles);
  570. while (Agraph_t *g = nextGraph(&ig)) {
  571. if (verbose)
  572. std::cerr << "Reading graph " << agnameof(g) << '\n';
  573. if (agnnodes(g) == 0) {
  574. std::cerr << "Graph " << agnameof(g) << " is empty - ignoring\n";
  575. continue;
  576. }
  577. if (!kind.has_value()) {
  578. kind = g->desc;
  579. }
  580. else if (kind->directed != g->desc.directed) {
  581. std::cerr << "Error: all graphs must be directed or undirected\n";
  582. graphviz_exit(1);
  583. } else if (!agisstrict(g))
  584. kind = g->desc;
  585. init_graph(g, doPack, gvc);
  586. gs.push_back(g);
  587. }
  588. return gs;
  589. }
  590. /* compBB:
  591. * Compute the bounding box containing the graphs.
  592. * We can just use the bounding boxes of the graphs.
  593. */
  594. static boxf compBB(std::vector<Agraph_t*> &gs) {
  595. boxf bb, bb2;
  596. bb = GD_bb(gs[0]);
  597. for (size_t i = 1; i < gs.size(); i++) {
  598. bb2 = GD_bb(gs[i]);
  599. bb.LL.x = std::min(bb.LL.x, bb2.LL.x);
  600. bb.LL.y = std::min(bb.LL.y, bb2.LL.y);
  601. bb.UR.x = std::max(bb.UR.x, bb2.UR.x);
  602. bb.UR.y = std::max(bb.UR.y, bb2.UR.y);
  603. }
  604. return bb;
  605. }
  606. #ifdef DEBUG
  607. void dump(Agraph_t * g)
  608. {
  609. node_t *v;
  610. edge_t *e;
  611. for (v = agfstnode(g); v; v = agnxtnode(g, v)) {
  612. std::cerr << agnameof(v) << '\n';
  613. for (e = agfstout(g, v); e; e = agnxtout(g, e)) {
  614. std::cerr << " " << agnameof(agtail(e)) << " -- " << agnameof(aghead(e))
  615. << '\n';
  616. }
  617. }
  618. }
  619. void dumps(Agraph_t * g)
  620. {
  621. graph_t *subg;
  622. for (subg = agfstsubg(g); subg; subg = agnxtsubg(subg)) {
  623. dump(subg);
  624. std::cerr << "====\n";
  625. }
  626. }
  627. #endif
  628. int main(int argc, char *argv[])
  629. {
  630. Agraph_t *g;
  631. pack_info pinfo;
  632. GVC_t * gvc;
  633. init(argc, argv, &pinfo);
  634. doPack = (pinfo.mode != l_undef);
  635. #if defined(_WIN32)
  636. lt_preloaded_symbols[0].address = &gvplugin_neato_layout_LTX_library;
  637. #endif
  638. gvc = gvContextPlugins(lt_preloaded_symbols, DEMAND_LOADING);
  639. std::optional<Agdesc_t> kind; // type of graph
  640. std::vector<Agraph_t*> gs = readGraphs(gvc, kind);
  641. if (gs.empty())
  642. graphviz_exit(0);
  643. /* pack graphs */
  644. if (doPack) {
  645. if (packGraphs(gs.size(), gs.data(), 0, &pinfo)) {
  646. std::cerr << "gvpack: packing of graphs failed.\n";
  647. graphviz_exit(1);
  648. }
  649. }
  650. /* create union graph and copy attributes */
  651. assert(kind.has_value());
  652. g = cloneGraph(gs, gvc, *kind);
  653. /* compute new top-level bb and set */
  654. if (doPack) {
  655. GD_bb(g) = compBB(gs);
  656. dotneato_postprocess(g);
  657. attach_attrs(g);
  658. }
  659. agwrite(g, outfp);
  660. graphviz_exit(0);
  661. }