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