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