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