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