gvplugin.c 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. /*************************************************************************
  2. * Copyright (c) 2011 AT&T Intellectual Property
  3. * All rights reserved. This program and the accompanying materials
  4. * are made available under the terms of the Eclipse Public License v1.0
  5. * which accompanies this distribution, and is available at
  6. * https://www.eclipse.org/legal/epl-v10.html
  7. *
  8. * Contributors: Details at https://graphviz.org
  9. *************************************************************************/
  10. #include "config.h"
  11. #include <stdbool.h>
  12. #include <stddef.h>
  13. #include <stdio.h>
  14. #include <string.h>
  15. #include <unistd.h>
  16. #ifdef ENABLE_LTDL
  17. #include <ltdl.h>
  18. #endif
  19. #include <common/types.h>
  20. #include <gvc/gvc.h>
  21. #include <gvc/gvplugin.h>
  22. #include <gvc/gvcjob.h>
  23. #include <gvc/gvcint.h>
  24. #include <gvc/gvcproc.h>
  25. #include <gvc/gvio.h>
  26. #include <common/const.h>
  27. #include <cgraph/list.h>
  28. #include <cgraph/strview.h>
  29. #include <util/agxbuf.h>
  30. #include <util/alloc.h>
  31. #include <util/startswith.h>
  32. #include <util/strcasecmp.h>
  33. /*
  34. * Define an apis array of name strings using an enumerated api_t as index.
  35. * The enumerated type is defined gvplugin.h. The apis array is
  36. * initialized here by redefining ELEM and reinvoking APIS.
  37. */
  38. #define ELEM(x) #x,
  39. static char *api_names[] = { APIS }; /* "render", "layout", ... */
  40. #undef ELEM
  41. /* translate a string api name to its type, or -1 on error */
  42. api_t gvplugin_api(const char *str)
  43. {
  44. for (size_t api = 0; api < ARRAY_SIZE(api_names); api++) {
  45. if (strcmp(str, api_names[api]) == 0)
  46. return (api_t) api;
  47. }
  48. return -1; /* invalid api */
  49. }
  50. /* translate api_t into string name, or NULL */
  51. char *gvplugin_api_name(api_t api)
  52. {
  53. if (api >= ARRAY_SIZE(api_names))
  54. return NULL;
  55. return api_names[api];
  56. }
  57. /* install a plugin description into the list of available plugins
  58. * list is alpha sorted by type (not including :dependency), then
  59. * quality sorted within the type, then, if qualities are the same,
  60. * last install wins.
  61. */
  62. bool gvplugin_install(GVC_t *gvc, api_t api, const char *typestr, int quality,
  63. gvplugin_package_t *package,
  64. gvplugin_installed_t *typeptr)
  65. {
  66. gvplugin_available_t *plugin, **pnext;
  67. char *t;
  68. /* duplicate typestr to later save in the plugin list */
  69. t = strdup(typestr);
  70. if (t == NULL)
  71. return false;
  72. // find the current plugin
  73. const strview_t type = strview(typestr, ':');
  74. /* point to the beginning of the linked list of plugins for this api */
  75. pnext = &gvc->apis[api];
  76. /* keep alpha-sorted and insert new duplicates ahead of old */
  77. while (*pnext) {
  78. // find the next plugin
  79. const strview_t next_type = strview((*pnext)->typestr, ':');
  80. if (strview_cmp(type, next_type) <= 0)
  81. break;
  82. pnext = &(*pnext)->next;
  83. }
  84. /* keep quality sorted within type and insert new duplicates ahead of old */
  85. while (*pnext) {
  86. // find the next plugin
  87. const strview_t next_type = strview((*pnext)->typestr, ':');
  88. if (!strview_eq(type, next_type))
  89. break;
  90. if (quality >= (*pnext)->quality)
  91. break;
  92. pnext = &(*pnext)->next;
  93. }
  94. plugin = gv_alloc(sizeof(gvplugin_available_t));
  95. plugin->next = *pnext;
  96. *pnext = plugin;
  97. plugin->typestr = t;
  98. plugin->quality = quality;
  99. plugin->package = package;
  100. plugin->typeptr = typeptr; /* null if not loaded */
  101. return true;
  102. }
  103. /* Activate a plugin description in the list of available plugins.
  104. * This is used when a plugin-library loaded because of demand for
  105. * one of its plugins. It updates the available plugin data with
  106. * pointers into the loaded library.
  107. * NB the quality value is not replaced as it might have been
  108. * manually changed in the config file.
  109. */
  110. static void gvplugin_activate(GVC_t * gvc, api_t api, const char *typestr,
  111. const char *name, const char *plugin_path,
  112. gvplugin_installed_t * typeptr)
  113. {
  114. gvplugin_available_t *pnext;
  115. /* point to the beginning of the linked list of plugins for this api */
  116. pnext = gvc->apis[api];
  117. while (pnext) {
  118. if (strcasecmp(typestr, pnext->typestr) == 0
  119. && strcasecmp(name, pnext->package->name) == 0
  120. && pnext->package->path != 0
  121. && strcasecmp(plugin_path, pnext->package->path) == 0) {
  122. pnext->typeptr = typeptr;
  123. return;
  124. }
  125. pnext = pnext->next;
  126. }
  127. }
  128. gvplugin_library_t *gvplugin_library_load(GVC_t *gvc, const char *pathname) {
  129. #ifdef ENABLE_LTDL
  130. lt_dlhandle hndl;
  131. lt_ptr ptr;
  132. char *s;
  133. size_t len;
  134. char *libdir;
  135. char *suffix = "_LTX_library";
  136. if (!gvc->common.demand_loading)
  137. return NULL;
  138. libdir = gvconfig_libdir(gvc);
  139. agxbuf fullpath = {0};
  140. #ifdef _WIN32
  141. if (pathname[1] == ':') {
  142. #else
  143. if (pathname[0] == '/') {
  144. #endif
  145. agxbput(&fullpath, pathname);
  146. } else {
  147. agxbprint(&fullpath, "%s%s%s", libdir, DIRSEP, pathname);
  148. }
  149. if (lt_dlinit()) {
  150. agerrorf("failed to init libltdl\n");
  151. agxbfree(&fullpath);
  152. return NULL;
  153. }
  154. char *p = agxbuse(&fullpath);
  155. hndl = lt_dlopen(p);
  156. if (!hndl) {
  157. if (access(p, R_OK) == 0) {
  158. agwarningf("Could not load \"%s\" - %s\n", p, "It was found, so perhaps one of its dependents was not. Try ldd.");
  159. }
  160. else {
  161. agwarningf("Could not load \"%s\" - %s\n", p, lt_dlerror());
  162. }
  163. agxbfree(&fullpath);
  164. return NULL;
  165. }
  166. if (gvc->common.verbose >= 2)
  167. fprintf(stderr, "Loading %s\n", p);
  168. s = strrchr(p, DIRSEP[0]);
  169. len = strlen(s);
  170. #if defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)
  171. if (len < strlen("/gvplugin_x")) {
  172. #else
  173. if (len < strlen("/libgvplugin_x")) {
  174. #endif
  175. agerrorf("invalid plugin path \"%s\"\n", p);
  176. agxbfree(&fullpath);
  177. return NULL;
  178. }
  179. char *sym = gv_alloc(len + strlen(suffix) + 1);
  180. #if defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)
  181. strcpy(sym, s + 1); /* strip leading "/" */
  182. #else
  183. strcpy(sym, s + 4); /* strip leading "/lib" or "/cyg" */
  184. #endif
  185. #if defined(__CYGWIN__) || defined(__MINGW32__)
  186. s = strchr(sym, '-'); /* strip trailing "-1.dll" */
  187. #else
  188. s = strchr(sym, '.'); /* strip trailing ".so.0" or ".dll" or ".sl" */
  189. #endif
  190. strcpy(s, suffix); /* append "_LTX_library" */
  191. ptr = lt_dlsym(hndl, sym);
  192. if (!ptr) {
  193. agerrorf("failed to resolve %s in %s\n", sym, p);
  194. free(sym);
  195. agxbfree(&fullpath);
  196. return NULL;
  197. }
  198. free(sym);
  199. agxbfree(&fullpath);
  200. return (gvplugin_library_t *)ptr;
  201. #else
  202. agerrorf("dynamic loading not available\n");
  203. return NULL;
  204. #endif
  205. }
  206. /* load a plugin of type=str
  207. the str can optionally contain one or more ":dependencies"
  208. examples:
  209. png
  210. png:cairo
  211. fully qualified:
  212. png:cairo:cairo
  213. png:cairo:gd
  214. png:gd:gd
  215. */
  216. gvplugin_available_t *gvplugin_load(GVC_t *gvc, api_t api, const char *str,
  217. FILE *debug) {
  218. gvplugin_available_t *pnext, *rv;
  219. gvplugin_library_t *library;
  220. gvplugin_api_t *apis;
  221. gvplugin_installed_t *types;
  222. int i;
  223. api_t apidep;
  224. if (api == API_device || api == API_loadimage)
  225. /* api dependencies - FIXME - find better way to code these *s */
  226. apidep = API_render;
  227. else
  228. apidep = api;
  229. const strview_t reqtyp = strview(str, ':');
  230. strview_t reqdep = {0};
  231. strview_t reqpkg = {0};
  232. if (reqtyp.data[reqtyp.size] == ':') {
  233. reqdep = strview(reqtyp.data + reqtyp.size + strlen(":"), ':');
  234. if (reqdep.data[reqdep.size] == ':') {
  235. reqpkg = strview(reqdep.data + reqdep.size + strlen(":"), '\0');
  236. }
  237. }
  238. agxbuf diag = {0}; // diagnostic messages
  239. /* iterate the linked list of plugins for this api */
  240. for (pnext = gvc->apis[api]; pnext; pnext = pnext->next) {
  241. const strview_t typ = strview(pnext->typestr, ':');
  242. strview_t dep = {0};
  243. if (typ.data[typ.size] == ':') {
  244. dep = strview(typ.data + typ.size + strlen(":"), '\0');
  245. }
  246. if (!strview_eq(typ, reqtyp)) {
  247. agxbprint(&diag, "# type \"%.*s\" did not match \"%.*s\"\n",
  248. (int)typ.size, typ.data, (int)reqtyp.size, reqtyp.data);
  249. continue; /* types empty or mismatched */
  250. }
  251. if (dep.data && reqdep.data) {
  252. if (!strview_eq(dep, reqdep)) {
  253. agxbprint(&diag,
  254. "# dependencies \"%.*s\" did not match \"%.*s\"\n",
  255. (int)dep.size, dep.data, (int)reqdep.size,
  256. reqdep.data);
  257. continue; /* dependencies not empty, but mismatched */
  258. }
  259. }
  260. if (!reqpkg.data || strview_str_eq(reqpkg, pnext->package->name)) {
  261. // found with no packagename constraints, or with required matching packagename
  262. if (dep.data && apidep != api) // load dependency if needed, continue if can't find
  263. if (!gvplugin_load(gvc, apidep, dep.data, debug)) {
  264. agxbprint(&diag,
  265. "# plugin loading of dependency \"%.*s\" failed\n",
  266. (int)dep.size, dep.data);
  267. continue;
  268. }
  269. break;
  270. }
  271. }
  272. rv = pnext;
  273. if (rv && rv->typeptr == NULL) {
  274. library = gvplugin_library_load(gvc, rv->package->path);
  275. if (library) {
  276. /* Now activate the library with real type ptrs */
  277. for (apis = library->apis; (types = apis->types); apis++) {
  278. for (i = 0; types[i].type; i++) {
  279. /* NB. quality is not checked or replaced
  280. * in case user has manually edited quality in config */
  281. gvplugin_activate(gvc, apis->api, types[i].type, library->packagename, rv->package->path, &types[i]);
  282. }
  283. }
  284. if (gvc->common.verbose >= 1)
  285. fprintf(stderr, "Activated plugin library: %s\n", rv->package->path ? rv->package->path : "<builtin>");
  286. }
  287. }
  288. /* one last check for successful load */
  289. if (rv && rv->typeptr == NULL) {
  290. agxbprint(&diag, "# unsuccessful plugin load\n");
  291. rv = NULL;
  292. }
  293. if (rv && gvc->common.verbose >= 1)
  294. fprintf(stderr, "Using %s: %s:%s\n", api_names[api], rv->typestr, rv->package->name);
  295. if (debug != NULL) {
  296. fputs(agxbuse(&diag), debug);
  297. }
  298. agxbfree(&diag);
  299. gvc->api[api] = rv;
  300. return rv;
  301. }
  302. /* assemble a string list of available plugins
  303. * non-re-entrant as character store is shared
  304. */
  305. char *gvplugin_list(GVC_t * gvc, api_t api, const char *str)
  306. {
  307. const gvplugin_available_t *pnext, *plugin;
  308. char *bp;
  309. bool new = true;
  310. static agxbuf xb;
  311. /* check for valid str */
  312. if (!str)
  313. return NULL;
  314. /* does str have a :path modifier? */
  315. const strview_t strv = strview(str, ':');
  316. /* point to the beginning of the linked list of plugins for this api */
  317. plugin = gvc->apis[api];
  318. if (strv.data[strv.size] == ':') { /* if str contains a ':', and if we find a match for the type,
  319. then just list the alternative paths for the plugin */
  320. for (pnext = plugin; pnext; pnext = pnext->next) {
  321. const strview_t type = strview(pnext->typestr, ':');
  322. // skip duplicates
  323. bool already_seen = false;
  324. for (const gvplugin_available_t *p = plugin; p != pnext;
  325. p = p->next) {
  326. already_seen |= strcasecmp(pnext->typestr, p->typestr) == 0 &&
  327. strcasecmp(pnext->package->name, p->package->name) == 0;
  328. }
  329. if (already_seen) {
  330. continue;
  331. }
  332. // list only the matching type, or all types if str is an empty
  333. // string or starts with ":"
  334. if (strv.size == 0 || strview_case_eq(strv, type)) {
  335. /* list each member of the matching type as "type:path" */
  336. agxbprint(&xb, " %s:%s", pnext->typestr, pnext->package->name);
  337. new = false;
  338. }
  339. }
  340. }
  341. if (new) { /* if the type was not found, or if str without ':',
  342. then just list available types */
  343. strview_t type_last = {0};
  344. for (pnext = plugin; pnext; pnext = pnext->next) {
  345. /* list only one instance of type */
  346. const strview_t type = strview(pnext->typestr, ':');
  347. if (!type_last.data || !strview_case_eq(type_last, type)) {
  348. /* list it as "type" i.e. w/o ":path" */
  349. agxbprint(&xb, " %.*s", (int)type.size, type.data);
  350. new = false;
  351. }
  352. type_last = type;
  353. }
  354. }
  355. if (new)
  356. bp = "";
  357. else
  358. bp = agxbuse(&xb);
  359. return bp;
  360. }
  361. DEFINE_LIST(strs, char*)
  362. /* gvPluginList:
  363. * Return list of plugins of type kind.
  364. * The size of the list is stored in sz.
  365. * The caller is responsible for freeing the storage. This involves
  366. * freeing each item, then the list.
  367. * Returns NULL on error, or if there are no plugins.
  368. * In the former case, sz is unchanged; in the latter, sz = 0.
  369. */
  370. char **gvPluginList(GVC_t *gvc, const char *kind, int *sz) {
  371. size_t api;
  372. const gvplugin_available_t *pnext, *plugin;
  373. strs_t list = {0};
  374. if (!kind)
  375. return NULL;
  376. for (api = 0; api < ARRAY_SIZE(api_names); api++) {
  377. if (!strcasecmp(kind, api_names[api]))
  378. break;
  379. }
  380. if (api == ARRAY_SIZE(api_names)) {
  381. agerrorf("unrecognized api name \"%s\"\n", kind);
  382. return NULL;
  383. }
  384. /* point to the beginning of the linked list of plugins for this api */
  385. plugin = gvc->apis[api];
  386. strview_t typestr_last = {0};
  387. for (pnext = plugin; pnext; pnext = pnext->next) {
  388. /* list only one instance of type */
  389. strview_t q = strview(pnext->typestr, ':');
  390. if (!typestr_last.data || !strview_case_eq(typestr_last, q)) {
  391. strs_append(&list, strview_str(q));
  392. }
  393. typestr_last = q;
  394. }
  395. *sz = (int)strs_size(&list);
  396. return strs_detach(&list);
  397. }
  398. void gvplugin_write_status(GVC_t * gvc)
  399. {
  400. int api;
  401. #ifdef ENABLE_LTDL
  402. if (gvc->common.demand_loading) {
  403. fprintf(stderr, "The plugin configuration file:\n\t%s\n", gvc->config_path);
  404. if (gvc->config_found)
  405. fprintf(stderr, "\t\twas successfully loaded.\n");
  406. else
  407. fprintf(stderr, "\t\twas not found or not usable. No on-demand plugins.\n");
  408. } else {
  409. fprintf(stderr, "Demand loading of plugins is disabled.\n");
  410. }
  411. #endif
  412. for (api = 0; api < (int)ARRAY_SIZE(api_names); api++) {
  413. if (gvc->common.verbose >= 2)
  414. fprintf(stderr, " %s\t: %s\n", api_names[api], gvplugin_list(gvc, api, ":"));
  415. else
  416. fprintf(stderr, " %s\t: %s\n", api_names[api], gvplugin_list(gvc, api, "?"));
  417. }
  418. }
  419. Agraph_t *gvplugin_graph(GVC_t * gvc)
  420. {
  421. Agraph_t *g, *sg, *ssg;
  422. Agnode_t *n, *m, *loadimage_n, *renderer_n, *device_n, *textlayout_n, *layout_n;
  423. Agedge_t *e;
  424. Agsym_t *a;
  425. gvplugin_package_t *package;
  426. const gvplugin_available_t *pnext;
  427. char *p, *q, *lq, *t;
  428. int neededge_loadimage, neededge_device;
  429. g = agopen("G", Agdirected, NULL);
  430. agattr(g, AGRAPH, "label", "");
  431. agattr(g, AGRAPH, "rankdir", "");
  432. agattr(g, AGRAPH, "rank", "");
  433. agattr(g, AGRAPH, "ranksep", "");
  434. agattr(g, AGNODE, "label", NODENAME_ESC);
  435. agattr(g, AGNODE, "shape", "");
  436. agattr(g, AGNODE, "style", "");
  437. agattr(g, AGNODE, "width", "");
  438. agattr(g, AGEDGE, "style", "");
  439. a = agfindgraphattr(g, "rankdir");
  440. agxset(g, a, "LR");
  441. a = agfindgraphattr(g, "ranksep");
  442. agxset(g, a, "2.5");
  443. a = agfindgraphattr(g, "label");
  444. agxset(g, a, "Plugins");
  445. agxbuf buf = {0};
  446. for (package = gvc->packages; package; package = package->next) {
  447. loadimage_n = renderer_n = device_n = textlayout_n = layout_n = NULL;
  448. neededge_loadimage = neededge_device = 0;
  449. agxbprint(&buf, "cluster_%s", package->name);
  450. sg = agsubg(g, agxbuse(&buf), 1);
  451. a = agfindgraphattr(sg, "label");
  452. agxset(sg, a, package->name);
  453. for (size_t api = 0; api < ARRAY_SIZE(api_names); api++) {
  454. agxbprint(&buf, "%s_%s", package->name, api_names[api]);
  455. ssg = agsubg(sg, agxbuse(&buf), 1);
  456. a = agfindgraphattr(ssg, "rank");
  457. agxset(ssg, a, "same");
  458. for (pnext = gvc->apis[api]; pnext; pnext = pnext->next) {
  459. if (pnext->package == package) {
  460. t = q = gv_strdup(pnext->typestr);
  461. if ((p = strchr(q, ':')))
  462. *p++ = '\0';
  463. /* Now p = renderer, e.g. "gd"
  464. * and q = device, e.g. "png"
  465. * or q = loadimage, e.g. "png" */
  466. switch (api) {
  467. case API_device:
  468. case API_loadimage:
  469. /* draw device as box - record last device in plugin (if any) in device_n */
  470. /* draw loadimage as box - record last loadimage in plugin (if any) in loadimage_n */
  471. /* hack for aliases */
  472. lq = q;
  473. if (startswith(q, "jp")) {
  474. q = "jpg"; /* canonical - for node name */
  475. lq = "jpeg\\njpe\\njpg"; /* list - for label */
  476. }
  477. else if (startswith(q, "tif")) {
  478. q = "tif";
  479. lq = "tiff\\ntif";
  480. }
  481. else if (!strcmp(q, "x11") || !strcmp(q, "xlib")) {
  482. q = "x11";
  483. lq = "x11\\nxlib";
  484. }
  485. else if (!strcmp(q, "dot") || !strcmp(q, "gv")) {
  486. q = "gv";
  487. lq = "gv\\ndot";
  488. }
  489. agxbprint(&buf, "%s_%s_%s", package->name,
  490. api_names[api], q);
  491. n = agnode(ssg, agxbuse(&buf), 1);
  492. a = agfindnodeattr(g, "label");
  493. agxset(n, a, lq);
  494. a = agfindnodeattr(g, "width");
  495. agxset(n, a, "1.0");
  496. a = agfindnodeattr(g, "shape");
  497. if (api == API_device) {
  498. agxset(n, a, "box");
  499. device_n = n;
  500. }
  501. else {
  502. agxset(n, a, "box");
  503. loadimage_n = n;
  504. }
  505. if (!(p && *p)) {
  506. m = agfindnode(sg, "render_cg");
  507. if (!m) {
  508. m = agnode(sg, "render_cg", 1);
  509. a = agfindgraphattr(g, "label");
  510. agxset(m, a, "cg");
  511. }
  512. agedge(sg, m, n, NULL, 1);
  513. }
  514. break;
  515. case API_render:
  516. /* draw renderers as ellipses - record last renderer in plugin (if any) in renderer_n */
  517. agxbprint(&buf, "%s_%s", api_names[api], q);
  518. renderer_n = n = agnode(ssg, agxbuse(&buf), 1);
  519. a = agfindnodeattr(g, "label");
  520. agxset(n, a, q);
  521. break;
  522. case API_textlayout:
  523. /* draw textlayout as invtriangle - record last textlayout in plugin (if any) in textlayout_n */
  524. /* FIXME? only one textlayout is loaded. Why? */
  525. agxbprint(&buf, "%s_%s", api_names[api], q);
  526. textlayout_n = n = agnode(ssg, agxbuse(&buf), 1);
  527. a = agfindnodeattr(g, "shape");
  528. agxset(n, a, "invtriangle");
  529. a = agfindnodeattr(g, "label");
  530. agxset(n, a, "T");
  531. break;
  532. case API_layout:
  533. /* draw textlayout as hexagon - record last layout in plugin (if any) in layout_n */
  534. agxbprint(&buf, "%s_%s", api_names[api], q);
  535. layout_n = n = agnode(ssg, agxbuse(&buf), 1);
  536. a = agfindnodeattr(g, "shape");
  537. agxset(n, a, "hexagon");
  538. a = agfindnodeattr(g, "label");
  539. agxset(n, a, q);
  540. break;
  541. default:
  542. break;
  543. }
  544. free(t);
  545. }
  546. }
  547. // add some invisible nodes (if needed) and invisible edges to
  548. // improve layout of cluster
  549. if (api == API_loadimage && !loadimage_n) {
  550. neededge_loadimage = 1;
  551. agxbprint(&buf, "%s_%s_invis", package->name, api_names[api]);
  552. loadimage_n = n = agnode(ssg, agxbuse(&buf), 1);
  553. a = agfindnodeattr(g, "style");
  554. agxset(n, a, "invis");
  555. a = agfindnodeattr(g, "label");
  556. agxset(n, a, "");
  557. a = agfindnodeattr(g, "width");
  558. agxset(n, a, "1.0");
  559. agxbprint(&buf, "%s_%s_invis_src", package->name,
  560. api_names[api]);
  561. n = agnode(g, agxbuse(&buf), 1);
  562. a = agfindnodeattr(g, "style");
  563. agxset(n, a, "invis");
  564. a = agfindnodeattr(g, "label");
  565. agxset(n, a, "");
  566. e = agedge(g, n, loadimage_n, NULL, 1);
  567. a = agfindedgeattr(g, "style");
  568. agxset(e, a, "invis");
  569. }
  570. if (api == API_render && !renderer_n) {
  571. neededge_loadimage = 1;
  572. neededge_device = 1;
  573. agxbprint(&buf, "%s_%s_invis", package->name, api_names[api]);
  574. renderer_n = n = agnode(ssg, agxbuse(&buf), 1);
  575. a = agfindnodeattr(g, "style");
  576. agxset(n, a, "invis");
  577. a = agfindnodeattr(g, "label");
  578. agxset(n, a, "");
  579. }
  580. if (api == API_device && !device_n) {
  581. neededge_device = 1;
  582. agxbprint(&buf, "%s_%s_invis", package->name, api_names[api]);
  583. device_n = n = agnode(ssg, agxbuse(&buf), 1);
  584. a = agfindnodeattr(g, "style");
  585. agxset(n, a, "invis");
  586. a = agfindnodeattr(g, "label");
  587. agxset(n, a, "");
  588. a = agfindnodeattr(g, "width");
  589. agxset(n, a, "1.0");
  590. }
  591. }
  592. if (neededge_loadimage) {
  593. e = agedge(sg, loadimage_n, renderer_n, NULL, 1);
  594. a = agfindedgeattr(g, "style");
  595. agxset(e, a, "invis");
  596. }
  597. if (neededge_device) {
  598. e = agedge(sg, renderer_n, device_n, NULL, 1);
  599. a = agfindedgeattr(g, "style");
  600. agxset(e, a, "invis");
  601. }
  602. if (textlayout_n) {
  603. e = agedge(sg, loadimage_n, textlayout_n, NULL, 1);
  604. a = agfindedgeattr(g, "style");
  605. agxset(e, a, "invis");
  606. }
  607. if (layout_n) {
  608. e = agedge(sg, loadimage_n, layout_n, NULL, 1);
  609. a = agfindedgeattr(g, "style");
  610. agxset(e, a, "invis");
  611. }
  612. }
  613. ssg = agsubg(g, "output_formats", 1);
  614. a = agfindgraphattr(ssg, "rank");
  615. agxset(ssg, a, "same");
  616. for (package = gvc->packages; package; package = package->next) {
  617. for (size_t api = 0; api < ARRAY_SIZE(api_names); api++) {
  618. for (pnext = gvc->apis[api]; pnext; pnext = pnext->next) {
  619. if (pnext->package == package) {
  620. t = q = gv_strdup(pnext->typestr);
  621. if ((p = strchr(q, ':')))
  622. *p++ = '\0';
  623. /* Now p = renderer, e.g. "gd"
  624. * and q = device, e.g. "png"
  625. * or q = imageloader, e.g. "png" */
  626. /* hack for aliases */
  627. lq = q;
  628. if (startswith(q, "jp")) {
  629. q = "jpg"; /* canonical - for node name */
  630. lq = "jpeg\\njpe\\njpg"; /* list - for label */
  631. }
  632. else if (startswith(q, "tif")) {
  633. q = "tif";
  634. lq = "tiff\\ntif";
  635. }
  636. else if (!strcmp(q, "x11") || !strcmp(q, "xlib")) {
  637. q = "x11";
  638. lq = "x11\\nxlib";
  639. }
  640. else if (!strcmp(q, "dot") || !strcmp(q, "gv")) {
  641. q = "gv";
  642. lq = "gv\\ndot";
  643. }
  644. switch (api) {
  645. case API_device: {
  646. agxbprint(&buf, "%s_%s_%s", package->name,
  647. api_names[api], q);
  648. n = agnode(g, agxbuse(&buf), 1);
  649. agxbprint(&buf, "output_%s", q);
  650. char *const output = agxbuse(&buf);
  651. m = agfindnode(ssg, output);
  652. if (!m) {
  653. m = agnode(ssg, output, 1);
  654. a = agfindnodeattr(g, "label");
  655. agxset(m, a, lq);
  656. a = agfindnodeattr(g, "shape");
  657. agxset(m, a, "note");
  658. }
  659. e = agfindedge(g, n, m);
  660. if (!e)
  661. e = agedge(g, n, m, NULL, 1);
  662. if (p && *p) {
  663. agxbprint(&buf, "render_%s", p);
  664. char *const render = agxbuse(&buf);
  665. m = agfindnode(ssg, render);
  666. if (!m)
  667. m = agnode(g, render, 1);
  668. e = agfindedge(g, m, n);
  669. if (!e)
  670. e = agedge(g, m, n, NULL, 1);
  671. }
  672. break;
  673. }
  674. case API_loadimage: {
  675. agxbprint(&buf, "%s_%s_%s", package->name,
  676. api_names[api], q);
  677. n = agnode(g, agxbuse(&buf), 1);
  678. agxbprint(&buf, "input_%s", q);
  679. char *const input = agxbuse(&buf);
  680. m = agfindnode(g, input);
  681. if (!m) {
  682. m = agnode(g, input, 1);
  683. a = agfindnodeattr(g, "label");
  684. agxset(m, a, lq);
  685. a = agfindnodeattr(g, "shape");
  686. agxset(m, a, "note");
  687. }
  688. e = agfindedge(g, m, n);
  689. if (!e)
  690. e = agedge(g, m, n, NULL, 1);
  691. agxbprint(&buf, "render_%s", p);
  692. char *const render = agxbuse(&buf);
  693. m = agfindnode(g, render);
  694. if (!m)
  695. m = agnode(g, render, 1);
  696. e = agfindedge(g, n, m);
  697. if (!e)
  698. e = agedge(g, n, m, NULL, 1);
  699. break;
  700. }
  701. default:
  702. break;
  703. }
  704. free(t);
  705. }
  706. }
  707. }
  708. }
  709. agxbfree(&buf);
  710. return g;
  711. }