Graphviz 14.1.1~dev.20251213.0445
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 v1.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors: Details at https://graphviz.org
9 *************************************************************************/
10
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 lt_ptr ptr;
155 char *s;
156 size_t len;
157 char *libdir;
158 char *suffix = "_LTX_library";
159
161 return NULL;
162
163 libdir = gvconfig_libdir(gvc);
164 agxbuf fullpath = {0};
165#ifdef _WIN32
166 if (pathname[1] == ':') {
167#else
168 if (pathname[0] == '/') {
169#endif
170 agxbput(&fullpath, pathname);
171 } else {
172 agxbprint(&fullpath, "%s%c%s", libdir, PATH_SEPARATOR, pathname);
173 }
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 char *sym = gv_alloc(len + strlen(suffix) + 1);
207#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)
208 strcpy(sym, s + 1); /* strip leading "/" */
209#else
210 strcpy(sym, s + 4); /* strip leading "/lib" or "/cyg" */
211#endif
212#if defined(__CYGWIN__) || defined(__MINGW32__)
213 s = strchr(sym, '-'); /* strip trailing "-1.dll" */
214#else
215 s = strchr(sym, '.'); /* strip trailing ".so.0" or ".dll" or ".sl" */
216#endif
217 strcpy(s, suffix); /* append "_LTX_library" */
218
219 ptr = lt_dlsym(hndl, sym);
220 if (!ptr) {
221 agerrorf("failed to resolve %s in %s\n", sym, p);
222 free(sym);
223 agxbfree(&fullpath);
224 return NULL;
225 }
226 free(sym);
227 agxbfree(&fullpath);
228 return (gvplugin_library_t *)ptr;
229#else
230 (void)gvc;
231 (void)pathname;
232
233 agerrorf("dynamic loading not available\n");
234 return NULL;
235#endif
236}
237
238
239/* load a plugin of type=str
240 the str can optionally contain one or more ":dependencies"
241
242 examples:
243 png
244 png:cairo
245 fully qualified:
246 png:cairo:cairo
247 png:cairo:gd
248 png:gd:gd
249
250*/
252 FILE *debug) {
253 gvplugin_available_t *pnext, *rv;
254 gvplugin_library_t *library;
257 int i;
258 api_t apidep;
259
260 if (api == API_device || api == API_loadimage)
261 /* api dependencies - FIXME - find better way to code these *s */
262 apidep = API_render;
263 else
264 apidep = api;
265
266 const strview_t reqtyp = strview(str, ':');
267
268 strview_t reqdep = {0};
269
270 strview_t reqpkg = {0};
271
272 if (reqtyp.data[reqtyp.size] == ':') {
273 reqdep = strview(reqtyp.data + reqtyp.size + strlen(":"), ':');
274 if (reqdep.data[reqdep.size] == ':') {
275 reqpkg = strview(reqdep.data + reqdep.size + strlen(":"), '\0');
276 }
277 }
278
279 agxbuf diag = {0}; // diagnostic messages
280
281 /* iterate the linked list of plugins for this api */
282 for (pnext = gvc->apis[api]; pnext; pnext = pnext->next) {
283 const strview_t typ = strview(pnext->typestr, ':');
284
285 strview_t dep = {0};
286 if (typ.data[typ.size] == ':') {
287 dep = strview(typ.data + typ.size + strlen(":"), '\0');
288 }
289
290 if (!strview_eq(typ, reqtyp)) {
291 agxbprint(&diag, "# type \"%.*s\" did not match \"%.*s\"\n",
292 (int)typ.size, typ.data, (int)reqtyp.size, reqtyp.data);
293 continue; /* types empty or mismatched */
294 }
295 if (dep.data && reqdep.data) {
296 if (!strview_eq(dep, reqdep)) {
297 agxbprint(&diag,
298 "# dependencies \"%.*s\" did not match \"%.*s\"\n",
299 (int)dep.size, dep.data, (int)reqdep.size,
300 reqdep.data);
301 continue; /* dependencies not empty, but mismatched */
302 }
303 }
304 if (!reqpkg.data || strview_str_eq(reqpkg, pnext->package->name)) {
305 // found with no packagename constraints, or with required matching packagename
306
307 if (dep.data && apidep != api) // load dependency if needed, continue if can't find
308 if (!gvplugin_load(gvc, apidep, dep.data, debug)) {
309 agxbprint(&diag,
310 "# plugin loading of dependency \"%.*s\" failed\n",
311 (int)dep.size, dep.data);
312 continue;
313 }
314 break;
315 }
316 }
317 rv = pnext;
318
319 if (rv && rv->typeptr == NULL) {
320 library = gvplugin_library_load(gvc, rv->package->path);
321 if (library) {
322
323 /* Now activate the library with real type ptrs */
324 for (apis = library->apis; (types = apis->types); apis++) {
325 for (i = 0; types[i].type; i++) {
326 /* NB. quality is not checked or replaced
327 * in case user has manually edited quality in config */
328 gvplugin_activate(gvc, apis->api, types[i].type, library->packagename, rv->package->path, &types[i]);
329 }
330 }
331 if (gvc->common.verbose >= 1)
332 fprintf(stderr, "Activated plugin library: %s\n", rv->package->path ? rv->package->path : "<builtin>");
333 }
334 }
335
336 /* one last check for successful load */
337 if (rv && rv->typeptr == NULL) {
338 agxbprint(&diag, "# unsuccessful plugin load\n");
339 rv = NULL;
340 }
341
342 if (rv && gvc->common.verbose >= 1)
343 fprintf(stderr, "Using %s: %s:%s\n", api_names[api], rv->typestr, rv->package->name);
344
345 if (debug != NULL) {
346 fputs(agxbuse(&diag), debug);
347 }
348 agxbfree(&diag);
349
350 gvc->api[api] = rv;
351 return rv;
352}
353
354/* assemble a string list of available plugins
355 * non-re-entrant as character store is shared
356 */
357char *gvplugin_list(GVC_t * gvc, api_t api, const char *str)
358{
359 const gvplugin_available_t *pnext, *plugin;
360 char *bp;
361 bool new = true;
362 static agxbuf xb;
363
364 /* check for valid str */
365 if (!str)
366 return NULL;
367
368 /* does str have a :path modifier? */
369 const strview_t strv = strview(str, ':');
370
371 /* point to the beginning of the linked list of plugins for this api */
372 plugin = gvc->apis[api];
373
374 if (strv.data[strv.size] == ':') { /* 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 const strview_t type = strview(pnext->typestr, ':');
378 // skip duplicates
379 bool already_seen = false;
380 for (const gvplugin_available_t *p = plugin; p != pnext;
381 p = p->next) {
382 already_seen |= strcasecmp(pnext->typestr, p->typestr) == 0 &&
383 strcasecmp(pnext->package->name, p->package->name) == 0;
384 }
385 if (already_seen) {
386 continue;
387 }
388 // list only the matching type, or all types if str is an empty
389 // string or starts with ":"
390 if (strv.size == 0 || strview_case_eq(strv, type)) {
391 /* list each member of the matching type as "type:path" */
392 agxbprint(&xb, " %s:%s", pnext->typestr, pnext->package->name);
393 new = false;
394 }
395 }
396 }
397 if (new) { /* if the type was not found, or if str without ':',
398 then just list available types */
399 strview_t type_last = {0};
400 for (pnext = plugin; pnext; pnext = pnext->next) {
401 /* list only one instance of type */
402 const strview_t type = strview(pnext->typestr, ':');
403 if (!type_last.data || !strview_case_eq(type_last, type)) {
404 /* list it as "type" i.e. w/o ":path" */
405 agxbprint(&xb, " %.*s", (int)type.size, type.data);
406 new = false;
407 }
408 type_last = type;
409 }
410 }
411 if (new)
412 bp = "";
413 else
414 bp = agxbuse(&xb);
415 return bp;
416}
417
418/* gvPluginList:
419 * Return list of plugins of type kind.
420 * The size of the list is stored in sz.
421 * The caller is responsible for freeing the storage. This involves
422 * freeing each item, then the list.
423 * Returns NULL on error, or if there are no plugins.
424 * In the former case, sz is unchanged; in the latter, sz = 0.
425 */
426char **gvPluginList(GVC_t *gvc, const char *kind, int *sz) {
427 size_t api;
428 const gvplugin_available_t *pnext, *plugin;
429 LIST(char *) list = {0};
430
431 if (!kind)
432 return NULL;
433 for (api = 0; api < ARRAY_SIZE(api_names); api++) {
434 if (!strcasecmp(kind, api_names[api]))
435 break;
436 }
437 if (api == ARRAY_SIZE(api_names)) {
438 agerrorf("unrecognized api name \"%s\"\n", kind);
439 return NULL;
440 }
441
442 /* point to the beginning of the linked list of plugins for this api */
443 plugin = gvc->apis[api];
444 strview_t typestr_last = {0};
445 for (pnext = plugin; pnext; pnext = pnext->next) {
446 /* list only one instance of type */
447 strview_t q = strview(pnext->typestr, ':');
448 if (!typestr_last.data || !strview_case_eq(typestr_last, q)) {
449 LIST_APPEND(&list, strview_str(q));
450 }
451 typestr_last = q;
452 }
453
454 char **ret;
455 size_t size;
456 LIST_DETACH(&list, &ret, &size);
457 *sz = (int)size;
458 return ret;
459}
460
462{
463 int api;
464
465#ifdef ENABLE_LTDL
467 fprintf(stderr, "The plugin configuration file:\n\t%s\n", gvc->config_path);
468 if (gvc->config_found)
469 fprintf(stderr, "\t\twas successfully loaded.\n");
470 else
471 fprintf(stderr, "\t\twas not found or not usable. No on-demand plugins.\n");
472 } else {
473 fprintf(stderr, "Demand loading of plugins is disabled.\n");
474 }
475#endif
476
477 for (api = 0; api < (int)ARRAY_SIZE(api_names); api++) {
478 if (gvc->common.verbose >= 2)
479 fprintf(stderr, " %s\t: %s\n", api_names[api], gvplugin_list(gvc, api, ":"));
480 else
481 fprintf(stderr, " %s\t: %s\n", api_names[api], gvplugin_list(gvc, api, "?"));
482 }
483
484}
485
487{
488 char *p;
489
490 Agraph_t *const g = agopen("G", Agdirected, NULL);
491 agattr_text(g, AGRAPH, "label", "");
492 agattr_text(g, AGRAPH, "rankdir", "");
493 agattr_text(g, AGRAPH, "rank", "");
494 agattr_text(g, AGRAPH, "ranksep", "");
495 agattr_text(g, AGNODE, "label", NODENAME_ESC);
496 agattr_text(g, AGNODE, "shape", "");
497 agattr_text(g, AGNODE, "style", "");
498 agattr_text(g, AGNODE, "width", "");
499 agattr_text(g, AGEDGE, "style", "");
500
501 Agsym_t *a = agfindgraphattr(g, "rankdir");
502 agxset(g, a, "LR");
503
504 a = agfindgraphattr(g, "ranksep");
505 agxset(g, a, "2.5");
506
507 a = agfindgraphattr(g, "label");
508 agxset(g, a, "Plugins");
509
510 agxbuf buf = {0};
511 for (const gvplugin_package_t *package = gvc->packages; package; package = package->next) {
512 Agnode_t *loadimage_n = NULL;
513 Agnode_t *renderer_n = NULL;
514 Agnode_t *device_n = NULL;
515 Agnode_t *textlayout_n = NULL;
516 Agnode_t *layout_n = NULL;
517 bool neededge_loadimage = false;
518 bool neededge_device = false;
519 agxbprint(&buf, "cluster_%s", package->name);
520 Agraph_t *const sg = agsubg(g, agxbuse(&buf), 1);
521 a = agfindgraphattr(sg, "label");
522 agxset(sg, a, package->name);
523 for (size_t api = 0; api < ARRAY_SIZE(api_names); api++) {
524 agxbprint(&buf, "%s_%s", package->name, api_names[api]);
525 Agraph_t *const ssg = agsubg(sg, agxbuse(&buf), 1);
526 a = agfindgraphattr(ssg, "rank");
527 agxset(ssg, a, "same");
528 for (const gvplugin_available_t *pnext = gvc->apis[api]; pnext; pnext = pnext->next) {
529 if (pnext->package == package) {
530 char *const t = gv_strdup(pnext->typestr);
531 const char *q = t;
532 if ((p = strchr(q, ':')))
533 *p++ = '\0';
534 /* Now p = renderer, e.g. "gd"
535 * and q = device, e.g. "png"
536 * or q = loadimage, e.g. "png" */
537 switch (api) {
538 case API_device:
539 case API_loadimage: {
540 /* draw device as box - record last device in plugin (if any) in device_n */
541 /* draw loadimage as box - record last loadimage in plugin (if any) in loadimage_n */
542
543 /* hack for aliases */
544 const char *lq = q;
545 if (startswith(q, "jp")) {
546 q = "jpg"; /* canonical - for node name */
547 lq = "jpeg\\njpe\\njpg"; /* list - for label */
548 }
549 else if (startswith(q, "tif")) {
550 q = "tif";
551 lq = "tiff\\ntif";
552 }
553 else if (!strcmp(q, "x11") || !strcmp(q, "xlib")) {
554 q = "x11";
555 lq = "x11\\nxlib";
556 }
557 else if (!strcmp(q, "dot") || !strcmp(q, "gv")) {
558 q = "gv";
559 lq = "gv\\ndot";
560 }
561
562 agxbprint(&buf, "%s_%s_%s", package->name,
563 api_names[api], q);
564 Agnode_t *const n = agnode(ssg, agxbuse(&buf), 1);
565 a = agfindnodeattr(g, "label");
566 agxset(n, a, lq);
567 a = agfindnodeattr(g, "width");
568 agxset(n, a, "1.0");
569 a = agfindnodeattr(g, "shape");
570 if (api == API_device) {
571 agxset(n, a, "box");
572 device_n = n;
573 }
574 else {
575 agxset(n, a, "box");
576 loadimage_n = n;
577 }
578 if (!(p && *p)) {
579 Agnode_t *m = agfindnode(sg, "render_cg");
580 if (!m) {
581 m = agnode(sg, "render_cg", 1);
582 a = agfindgraphattr(g, "label");
583 agxset(m, a, "cg");
584 }
585 agedge(sg, m, n, NULL, 1);
586 }
587 break;
588 }
589 case API_render: {
590 /* draw renderers as ellipses - record last renderer in plugin (if any) in renderer_n */
591 agxbprint(&buf, "%s_%s", api_names[api], q);
592 Agnode_t *const n = agnode(ssg, agxbuse(&buf), 1);
593 renderer_n = n;
594 a = agfindnodeattr(g, "label");
595 agxset(n, a, q);
596 break;
597 }
598 case API_textlayout: {
599 /* draw textlayout as invtriangle - record last textlayout in plugin (if any) in textlayout_n */
600 /* FIXME? only one textlayout is loaded. Why? */
601 agxbprint(&buf, "%s_%s", api_names[api], q);
602 Agnode_t *const n = agnode(ssg, agxbuse(&buf), 1);
603 textlayout_n = n;
604 a = agfindnodeattr(g, "shape");
605 agxset(n, a, "invtriangle");
606 a = agfindnodeattr(g, "label");
607 agxset(n, a, "T");
608 break;
609 }
610 case API_layout: {
611 /* draw textlayout as hexagon - record last layout in plugin (if any) in layout_n */
612 agxbprint(&buf, "%s_%s", api_names[api], q);
613 Agnode_t *const n = agnode(ssg, agxbuse(&buf), 1);
614 layout_n = n;
615 a = agfindnodeattr(g, "shape");
616 agxset(n, a, "hexagon");
617 a = agfindnodeattr(g, "label");
618 agxset(n, a, q);
619 break;
620 }
621 default:
622 break;
623 }
624 free(t);
625 }
626 }
627 // add some invisible nodes (if needed) and invisible edges to
628 // improve layout of cluster
629 if (api == API_loadimage && !loadimage_n) {
630 neededge_loadimage = true;
631 agxbprint(&buf, "%s_%s_invis", package->name, api_names[api]);
632 Agnode_t *n = agnode(ssg, agxbuse(&buf), 1);
633 loadimage_n = n;
634 a = agfindnodeattr(g, "style");
635 agxset(n, a, "invis");
636 a = agfindnodeattr(g, "label");
637 agxset(n, a, "");
638 a = agfindnodeattr(g, "width");
639 agxset(n, a, "1.0");
640
641 agxbprint(&buf, "%s_%s_invis_src", package->name,
642 api_names[api]);
643 n = agnode(g, agxbuse(&buf), 1);
644 a = agfindnodeattr(g, "style");
645 agxset(n, a, "invis");
646 a = agfindnodeattr(g, "label");
647 agxset(n, a, "");
648
649 Agedge_t *const e = agedge(g, n, loadimage_n, NULL, 1);
650 a = agfindedgeattr(g, "style");
651 agxset(e, a, "invis");
652 }
653 if (api == API_render && !renderer_n) {
654 neededge_loadimage = true;
655 neededge_device = true;
656 agxbprint(&buf, "%s_%s_invis", package->name, api_names[api]);
657 Agnode_t *n = agnode(ssg, agxbuse(&buf), 1);
658 renderer_n = n;
659 a = agfindnodeattr(g, "style");
660 agxset(n, a, "invis");
661 a = agfindnodeattr(g, "label");
662 agxset(n, a, "");
663 }
664 if (api == API_device && !device_n) {
665 neededge_device = true;
666 agxbprint(&buf, "%s_%s_invis", package->name, api_names[api]);
667 Agnode_t *n = agnode(ssg, agxbuse(&buf), 1);
668 device_n = n;
669 a = agfindnodeattr(g, "style");
670 agxset(n, a, "invis");
671 a = agfindnodeattr(g, "label");
672 agxset(n, a, "");
673 a = agfindnodeattr(g, "width");
674 agxset(n, a, "1.0");
675 }
676 }
677 if (neededge_loadimage) {
678 Agedge_t *const e = agedge(sg, loadimage_n, renderer_n, NULL, 1);
679 a = agfindedgeattr(g, "style");
680 agxset(e, a, "invis");
681 }
682 if (neededge_device) {
683 Agedge_t *const e = agedge(sg, renderer_n, device_n, NULL, 1);
684 a = agfindedgeattr(g, "style");
685 agxset(e, a, "invis");
686 }
687 if (textlayout_n) {
688 Agedge_t *const e = agedge(sg, loadimage_n, textlayout_n, NULL, 1);
689 a = agfindedgeattr(g, "style");
690 agxset(e, a, "invis");
691 }
692 if (layout_n) {
693 Agedge_t *const e = agedge(sg, loadimage_n, layout_n, NULL, 1);
694 a = agfindedgeattr(g, "style");
695 agxset(e, a, "invis");
696 }
697 }
698
699 Agraph_t *const ssg = agsubg(g, "output_formats", 1);
700 a = agfindgraphattr(ssg, "rank");
701 agxset(ssg, a, "same");
702 for (const gvplugin_package_t *package = gvc->packages; package; package = package->next) {
703 for (size_t api = 0; api < ARRAY_SIZE(api_names); api++) {
704 for (const gvplugin_available_t *pnext = gvc->apis[api]; pnext; pnext = pnext->next) {
705 if (pnext->package == package) {
706 char *const t = gv_strdup(pnext->typestr);
707 const char *q = t;
708 if ((p = strchr(q, ':')))
709 *p++ = '\0';
710 /* Now p = renderer, e.g. "gd"
711 * and q = device, e.g. "png"
712 * or q = imageloader, e.g. "png" */
713
714 /* hack for aliases */
715 const char *lq = q;
716 if (startswith(q, "jp")) {
717 q = "jpg"; /* canonical - for node name */
718 lq = "jpeg\\njpe\\njpg"; /* list - for label */
719 }
720 else if (startswith(q, "tif")) {
721 q = "tif";
722 lq = "tiff\\ntif";
723 }
724 else if (!strcmp(q, "x11") || !strcmp(q, "xlib")) {
725 q = "x11";
726 lq = "x11\\nxlib";
727 }
728 else if (!strcmp(q, "dot") || !strcmp(q, "gv")) {
729 q = "gv";
730 lq = "gv\\ndot";
731 }
732
733 switch (api) {
734 case API_device: {
735 agxbprint(&buf, "%s_%s_%s", package->name,
736 api_names[api], q);
737 Agnode_t *const n = agnode(g, agxbuse(&buf), 1);
738 agxbprint(&buf, "output_%s", q);
739 char *const output = agxbuse(&buf);
740 Agnode_t *m = agfindnode(ssg, output);
741 if (!m) {
742 m = agnode(ssg, output, 1);
743 a = agfindnodeattr(g, "label");
744 agxset(m, a, lq);
745 a = agfindnodeattr(g, "shape");
746 agxset(m, a, "note");
747 }
748 const Agedge_t *e = agfindedge(g, n, m);
749 if (!e)
750 agedge(g, n, m, NULL, 1);
751 if (p && *p) {
752 agxbprint(&buf, "render_%s", p);
753 char *const render = agxbuse(&buf);
754 m = agfindnode(ssg, render);
755 if (!m)
756 m = agnode(g, render, 1);
757 e = agfindedge(g, m, n);
758 if (!e)
759 agedge(g, m, n, NULL, 1);
760 }
761 break;
762 }
763 case API_loadimage: {
764 agxbprint(&buf, "%s_%s_%s", package->name,
765 api_names[api], q);
766 Agnode_t *const n = agnode(g, agxbuse(&buf), 1);
767 agxbprint(&buf, "input_%s", q);
768 char *const input = agxbuse(&buf);
769 Agnode_t *m = agfindnode(g, input);
770 if (!m) {
771 m = agnode(g, input, 1);
772 a = agfindnodeattr(g, "label");
773 agxset(m, a, lq);
774 a = agfindnodeattr(g, "shape");
775 agxset(m, a, "note");
776 }
777 const Agedge_t *e = agfindedge(g, m, n);
778 if (!e)
779 agedge(g, m, n, NULL, 1);
780 agxbprint(&buf, "render_%s", p);
781 char *const render = agxbuse(&buf);
782 m = agfindnode(g, render);
783 if (!m)
784 m = agnode(g, render, 1);
785 e = agfindedge(g, n, m);
786 if (!e)
787 agedge(g, n, m, NULL, 1);
788 break;
789 }
790 default:
791 break;
792 }
793 free(t);
794 }
795 }
796 }
797 }
798
799 agxbfree(&buf);
800 return g;
801}
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:59
expr procedure type
Definition exparse.y:208
static double len(glCompPoint p)
Definition glutils.c:136
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:334
int agxset(void *obj, Agsym_t *sym, const char *value)
Definition attr.c:522
Agedge_t * agedge(Agraph_t *g, Agnode_t *t, Agnode_t *h, char *name, int createflag)
Definition edge.c:253
#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:173
void agerrorf(const char *fmt,...)
Definition agerror.c:165
#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:42
Agdesc_t Agdirected
directed
Definition graph.c:270
Agnode_t * agnode(Agraph_t *g, char *name, int createflag)
Definition node.c:141
#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:53
char ** gvPluginList(GVC_t *gvc, const char *kind, int *sz)
Definition gvplugin.c:426
static GVC_t * gvc
Definition gv.cpp:25
bool render(Agraph_t *g)
Definition gv.cpp:626
Graphviz context library.
#define APIS
Definition gvcext.h:26
api_t
Definition gvcext.h:32
#define ARRAY_SIZE(A)
Definition gvcjob.h:26
char * gvconfig_libdir(GVC_t *gvc)
agxbput(xb, staging)
void gvplugin_write_status(GVC_t *gvc)
Definition gvplugin.c:461
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:251
Agraph_t * gvplugin_graph(GVC_t *gvc)
Definition gvplugin.c:486
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:357
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:443
#define LIST(type)
Definition list.h:55
#define LIST_APPEND(list, item)
Definition list.h:120
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