123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789 |
- /*************************************************************************
- * Copyright (c) 2011 AT&T Intellectual Property
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors: Details at https://graphviz.org
- *************************************************************************/
- #include "config.h"
- #include <stdbool.h>
- #include <stddef.h>
- #include <stdio.h>
- #include <string.h>
- #include <unistd.h>
- #ifdef ENABLE_LTDL
- #include <ltdl.h>
- #endif
- #include <common/types.h>
- #include <gvc/gvc.h>
- #include <gvc/gvplugin.h>
- #include <gvc/gvcjob.h>
- #include <gvc/gvcint.h>
- #include <gvc/gvcproc.h>
- #include <gvc/gvio.h>
- #include <common/const.h>
- #include <cgraph/list.h>
- #include <cgraph/strview.h>
- #include <util/agxbuf.h>
- #include <util/alloc.h>
- #include <util/startswith.h>
- #include <util/strcasecmp.h>
- /*
- * Define an apis array of name strings using an enumerated api_t as index.
- * The enumerated type is defined gvplugin.h. The apis array is
- * initialized here by redefining ELEM and reinvoking APIS.
- */
- #define ELEM(x) #x,
- static char *api_names[] = { APIS }; /* "render", "layout", ... */
- #undef ELEM
- /* translate a string api name to its type, or -1 on error */
- api_t gvplugin_api(const char *str)
- {
- for (size_t api = 0; api < ARRAY_SIZE(api_names); api++) {
- if (strcmp(str, api_names[api]) == 0)
- return (api_t) api;
- }
- return -1; /* invalid api */
- }
- /* translate api_t into string name, or NULL */
- char *gvplugin_api_name(api_t api)
- {
- if (api >= ARRAY_SIZE(api_names))
- return NULL;
- return api_names[api];
- }
- /* install a plugin description into the list of available plugins
- * list is alpha sorted by type (not including :dependency), then
- * quality sorted within the type, then, if qualities are the same,
- * last install wins.
- */
- bool gvplugin_install(GVC_t *gvc, api_t api, const char *typestr, int quality,
- gvplugin_package_t *package,
- gvplugin_installed_t *typeptr)
- {
- gvplugin_available_t *plugin, **pnext;
- char *t;
- /* duplicate typestr to later save in the plugin list */
- t = strdup(typestr);
- if (t == NULL)
- return false;
- // find the current plugin
- const strview_t type = strview(typestr, ':');
- /* point to the beginning of the linked list of plugins for this api */
- pnext = &gvc->apis[api];
- /* keep alpha-sorted and insert new duplicates ahead of old */
- while (*pnext) {
- // find the next plugin
- const strview_t next_type = strview((*pnext)->typestr, ':');
- if (strview_cmp(type, next_type) <= 0)
- break;
- pnext = &(*pnext)->next;
- }
- /* keep quality sorted within type and insert new duplicates ahead of old */
- while (*pnext) {
- // find the next plugin
- const strview_t next_type = strview((*pnext)->typestr, ':');
- if (!strview_eq(type, next_type))
- break;
- if (quality >= (*pnext)->quality)
- break;
- pnext = &(*pnext)->next;
- }
- plugin = gv_alloc(sizeof(gvplugin_available_t));
- plugin->next = *pnext;
- *pnext = plugin;
- plugin->typestr = t;
- plugin->quality = quality;
- plugin->package = package;
- plugin->typeptr = typeptr; /* null if not loaded */
- return true;
- }
- /* Activate a plugin description in the list of available plugins.
- * This is used when a plugin-library loaded because of demand for
- * one of its plugins. It updates the available plugin data with
- * pointers into the loaded library.
- * NB the quality value is not replaced as it might have been
- * manually changed in the config file.
- */
- static void gvplugin_activate(GVC_t * gvc, api_t api, const char *typestr,
- const char *name, const char *plugin_path,
- gvplugin_installed_t * typeptr)
- {
- gvplugin_available_t *pnext;
- /* point to the beginning of the linked list of plugins for this api */
- pnext = gvc->apis[api];
- while (pnext) {
- if (strcasecmp(typestr, pnext->typestr) == 0
- && strcasecmp(name, pnext->package->name) == 0
- && pnext->package->path != 0
- && strcasecmp(plugin_path, pnext->package->path) == 0) {
- pnext->typeptr = typeptr;
- return;
- }
- pnext = pnext->next;
- }
- }
- gvplugin_library_t *gvplugin_library_load(GVC_t *gvc, const char *pathname) {
- #ifdef ENABLE_LTDL
- lt_dlhandle hndl;
- lt_ptr ptr;
- char *s;
- size_t len;
- char *libdir;
- char *suffix = "_LTX_library";
- if (!gvc->common.demand_loading)
- return NULL;
- libdir = gvconfig_libdir(gvc);
- agxbuf fullpath = {0};
- #ifdef _WIN32
- if (pathname[1] == ':') {
- #else
- if (pathname[0] == '/') {
- #endif
- agxbput(&fullpath, pathname);
- } else {
- agxbprint(&fullpath, "%s%s%s", libdir, DIRSEP, pathname);
- }
- if (lt_dlinit()) {
- agerrorf("failed to init libltdl\n");
- agxbfree(&fullpath);
- return NULL;
- }
- char *p = agxbuse(&fullpath);
- hndl = lt_dlopen(p);
- if (!hndl) {
- if (access(p, R_OK) == 0) {
- agwarningf("Could not load \"%s\" - %s\n", p, "It was found, so perhaps one of its dependents was not. Try ldd.");
- }
- else {
- agwarningf("Could not load \"%s\" - %s\n", p, lt_dlerror());
- }
- agxbfree(&fullpath);
- return NULL;
- }
- if (gvc->common.verbose >= 2)
- fprintf(stderr, "Loading %s\n", p);
- s = strrchr(p, DIRSEP[0]);
- len = strlen(s);
- #if defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)
- if (len < strlen("/gvplugin_x")) {
- #else
- if (len < strlen("/libgvplugin_x")) {
- #endif
- agerrorf("invalid plugin path \"%s\"\n", p);
- agxbfree(&fullpath);
- return NULL;
- }
- char *sym = gv_alloc(len + strlen(suffix) + 1);
- #if defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)
- strcpy(sym, s + 1); /* strip leading "/" */
- #else
- strcpy(sym, s + 4); /* strip leading "/lib" or "/cyg" */
- #endif
- #if defined(__CYGWIN__) || defined(__MINGW32__)
- s = strchr(sym, '-'); /* strip trailing "-1.dll" */
- #else
- s = strchr(sym, '.'); /* strip trailing ".so.0" or ".dll" or ".sl" */
- #endif
- strcpy(s, suffix); /* append "_LTX_library" */
- ptr = lt_dlsym(hndl, sym);
- if (!ptr) {
- agerrorf("failed to resolve %s in %s\n", sym, p);
- free(sym);
- agxbfree(&fullpath);
- return NULL;
- }
- free(sym);
- agxbfree(&fullpath);
- return (gvplugin_library_t *)ptr;
- #else
- agerrorf("dynamic loading not available\n");
- return NULL;
- #endif
- }
- /* load a plugin of type=str
- the str can optionally contain one or more ":dependencies"
- examples:
- png
- png:cairo
- fully qualified:
- png:cairo:cairo
- png:cairo:gd
- png:gd:gd
-
- */
- gvplugin_available_t *gvplugin_load(GVC_t *gvc, api_t api, const char *str,
- FILE *debug) {
- gvplugin_available_t *pnext, *rv;
- gvplugin_library_t *library;
- gvplugin_api_t *apis;
- gvplugin_installed_t *types;
- int i;
- api_t apidep;
- if (api == API_device || api == API_loadimage)
- /* api dependencies - FIXME - find better way to code these *s */
- apidep = API_render;
- else
- apidep = api;
- const strview_t reqtyp = strview(str, ':');
- strview_t reqdep = {0};
- strview_t reqpkg = {0};
- if (reqtyp.data[reqtyp.size] == ':') {
- reqdep = strview(reqtyp.data + reqtyp.size + strlen(":"), ':');
- if (reqdep.data[reqdep.size] == ':') {
- reqpkg = strview(reqdep.data + reqdep.size + strlen(":"), '\0');
- }
- }
- agxbuf diag = {0}; // diagnostic messages
- /* iterate the linked list of plugins for this api */
- for (pnext = gvc->apis[api]; pnext; pnext = pnext->next) {
- const strview_t typ = strview(pnext->typestr, ':');
- strview_t dep = {0};
- if (typ.data[typ.size] == ':') {
- dep = strview(typ.data + typ.size + strlen(":"), '\0');
- }
- if (!strview_eq(typ, reqtyp)) {
- agxbprint(&diag, "# type \"%.*s\" did not match \"%.*s\"\n",
- (int)typ.size, typ.data, (int)reqtyp.size, reqtyp.data);
- continue; /* types empty or mismatched */
- }
- if (dep.data && reqdep.data) {
- if (!strview_eq(dep, reqdep)) {
- agxbprint(&diag,
- "# dependencies \"%.*s\" did not match \"%.*s\"\n",
- (int)dep.size, dep.data, (int)reqdep.size,
- reqdep.data);
- continue; /* dependencies not empty, but mismatched */
- }
- }
- if (!reqpkg.data || strview_str_eq(reqpkg, pnext->package->name)) {
- // found with no packagename constraints, or with required matching packagename
- if (dep.data && apidep != api) // load dependency if needed, continue if can't find
- if (!gvplugin_load(gvc, apidep, dep.data, debug)) {
- agxbprint(&diag,
- "# plugin loading of dependency \"%.*s\" failed\n",
- (int)dep.size, dep.data);
- continue;
- }
- break;
- }
- }
- rv = pnext;
- if (rv && rv->typeptr == NULL) {
- library = gvplugin_library_load(gvc, rv->package->path);
- if (library) {
- /* Now activate the library with real type ptrs */
- for (apis = library->apis; (types = apis->types); apis++) {
- for (i = 0; types[i].type; i++) {
- /* NB. quality is not checked or replaced
- * in case user has manually edited quality in config */
- gvplugin_activate(gvc, apis->api, types[i].type, library->packagename, rv->package->path, &types[i]);
- }
- }
- if (gvc->common.verbose >= 1)
- fprintf(stderr, "Activated plugin library: %s\n", rv->package->path ? rv->package->path : "<builtin>");
- }
- }
- /* one last check for successful load */
- if (rv && rv->typeptr == NULL) {
- agxbprint(&diag, "# unsuccessful plugin load\n");
- rv = NULL;
- }
- if (rv && gvc->common.verbose >= 1)
- fprintf(stderr, "Using %s: %s:%s\n", api_names[api], rv->typestr, rv->package->name);
- if (debug != NULL) {
- fputs(agxbuse(&diag), debug);
- }
- agxbfree(&diag);
- gvc->api[api] = rv;
- return rv;
- }
- /* assemble a string list of available plugins
- * non-re-entrant as character store is shared
- */
- char *gvplugin_list(GVC_t * gvc, api_t api, const char *str)
- {
- const gvplugin_available_t *pnext, *plugin;
- char *bp;
- bool new = true;
- static agxbuf xb;
- /* check for valid str */
- if (!str)
- return NULL;
- /* does str have a :path modifier? */
- const strview_t strv = strview(str, ':');
- /* point to the beginning of the linked list of plugins for this api */
- plugin = gvc->apis[api];
- if (strv.data[strv.size] == ':') { /* if str contains a ':', and if we find a match for the type,
- then just list the alternative paths for the plugin */
- for (pnext = plugin; pnext; pnext = pnext->next) {
- const strview_t type = strview(pnext->typestr, ':');
- // skip duplicates
- bool already_seen = false;
- for (const gvplugin_available_t *p = plugin; p != pnext;
- p = p->next) {
- already_seen |= strcasecmp(pnext->typestr, p->typestr) == 0 &&
- strcasecmp(pnext->package->name, p->package->name) == 0;
- }
- if (already_seen) {
- continue;
- }
- // list only the matching type, or all types if str is an empty
- // string or starts with ":"
- if (strv.size == 0 || strview_case_eq(strv, type)) {
- /* list each member of the matching type as "type:path" */
- agxbprint(&xb, " %s:%s", pnext->typestr, pnext->package->name);
- new = false;
- }
- }
- }
- if (new) { /* if the type was not found, or if str without ':',
- then just list available types */
- strview_t type_last = {0};
- for (pnext = plugin; pnext; pnext = pnext->next) {
- /* list only one instance of type */
- const strview_t type = strview(pnext->typestr, ':');
- if (!type_last.data || !strview_case_eq(type_last, type)) {
- /* list it as "type" i.e. w/o ":path" */
- agxbprint(&xb, " %.*s", (int)type.size, type.data);
- new = false;
- }
- type_last = type;
- }
- }
- if (new)
- bp = "";
- else
- bp = agxbuse(&xb);
- return bp;
- }
- DEFINE_LIST(strs, char*)
- /* gvPluginList:
- * Return list of plugins of type kind.
- * The size of the list is stored in sz.
- * The caller is responsible for freeing the storage. This involves
- * freeing each item, then the list.
- * Returns NULL on error, or if there are no plugins.
- * In the former case, sz is unchanged; in the latter, sz = 0.
- */
- char **gvPluginList(GVC_t *gvc, const char *kind, int *sz) {
- size_t api;
- const gvplugin_available_t *pnext, *plugin;
- strs_t list = {0};
- if (!kind)
- return NULL;
- for (api = 0; api < ARRAY_SIZE(api_names); api++) {
- if (!strcasecmp(kind, api_names[api]))
- break;
- }
- if (api == ARRAY_SIZE(api_names)) {
- agerrorf("unrecognized api name \"%s\"\n", kind);
- return NULL;
- }
- /* point to the beginning of the linked list of plugins for this api */
- plugin = gvc->apis[api];
- strview_t typestr_last = {0};
- for (pnext = plugin; pnext; pnext = pnext->next) {
- /* list only one instance of type */
- strview_t q = strview(pnext->typestr, ':');
- if (!typestr_last.data || !strview_case_eq(typestr_last, q)) {
- strs_append(&list, strview_str(q));
- }
- typestr_last = q;
- }
- *sz = (int)strs_size(&list);
- return strs_detach(&list);
- }
- void gvplugin_write_status(GVC_t * gvc)
- {
- int api;
- #ifdef ENABLE_LTDL
- if (gvc->common.demand_loading) {
- fprintf(stderr, "The plugin configuration file:\n\t%s\n", gvc->config_path);
- if (gvc->config_found)
- fprintf(stderr, "\t\twas successfully loaded.\n");
- else
- fprintf(stderr, "\t\twas not found or not usable. No on-demand plugins.\n");
- } else {
- fprintf(stderr, "Demand loading of plugins is disabled.\n");
- }
- #endif
- for (api = 0; api < (int)ARRAY_SIZE(api_names); api++) {
- if (gvc->common.verbose >= 2)
- fprintf(stderr, " %s\t: %s\n", api_names[api], gvplugin_list(gvc, api, ":"));
- else
- fprintf(stderr, " %s\t: %s\n", api_names[api], gvplugin_list(gvc, api, "?"));
- }
- }
- Agraph_t *gvplugin_graph(GVC_t * gvc)
- {
- Agraph_t *g, *sg, *ssg;
- Agnode_t *n, *m, *loadimage_n, *renderer_n, *device_n, *textlayout_n, *layout_n;
- Agedge_t *e;
- Agsym_t *a;
- gvplugin_package_t *package;
- const gvplugin_available_t *pnext;
- char *p, *q, *lq, *t;
- int neededge_loadimage, neededge_device;
- g = agopen("G", Agdirected, NULL);
- agattr(g, AGRAPH, "label", "");
- agattr(g, AGRAPH, "rankdir", "");
- agattr(g, AGRAPH, "rank", "");
- agattr(g, AGRAPH, "ranksep", "");
- agattr(g, AGNODE, "label", NODENAME_ESC);
- agattr(g, AGNODE, "shape", "");
- agattr(g, AGNODE, "style", "");
- agattr(g, AGNODE, "width", "");
- agattr(g, AGEDGE, "style", "");
- a = agfindgraphattr(g, "rankdir");
- agxset(g, a, "LR");
- a = agfindgraphattr(g, "ranksep");
- agxset(g, a, "2.5");
- a = agfindgraphattr(g, "label");
- agxset(g, a, "Plugins");
- agxbuf buf = {0};
- for (package = gvc->packages; package; package = package->next) {
- loadimage_n = renderer_n = device_n = textlayout_n = layout_n = NULL;
- neededge_loadimage = neededge_device = 0;
- agxbprint(&buf, "cluster_%s", package->name);
- sg = agsubg(g, agxbuse(&buf), 1);
- a = agfindgraphattr(sg, "label");
- agxset(sg, a, package->name);
- for (size_t api = 0; api < ARRAY_SIZE(api_names); api++) {
- agxbprint(&buf, "%s_%s", package->name, api_names[api]);
- ssg = agsubg(sg, agxbuse(&buf), 1);
- a = agfindgraphattr(ssg, "rank");
- agxset(ssg, a, "same");
- for (pnext = gvc->apis[api]; pnext; pnext = pnext->next) {
- if (pnext->package == package) {
- t = q = gv_strdup(pnext->typestr);
- if ((p = strchr(q, ':')))
- *p++ = '\0';
- /* Now p = renderer, e.g. "gd"
- * and q = device, e.g. "png"
- * or q = loadimage, e.g. "png" */
- switch (api) {
- case API_device:
- case API_loadimage:
- /* draw device as box - record last device in plugin (if any) in device_n */
- /* draw loadimage as box - record last loadimage in plugin (if any) in loadimage_n */
- /* hack for aliases */
- lq = q;
- if (startswith(q, "jp")) {
- q = "jpg"; /* canonical - for node name */
- lq = "jpeg\\njpe\\njpg"; /* list - for label */
- }
- else if (startswith(q, "tif")) {
- q = "tif";
- lq = "tiff\\ntif";
- }
- else if (!strcmp(q, "x11") || !strcmp(q, "xlib")) {
- q = "x11";
- lq = "x11\\nxlib";
- }
- else if (!strcmp(q, "dot") || !strcmp(q, "gv")) {
- q = "gv";
- lq = "gv\\ndot";
- }
- agxbprint(&buf, "%s_%s_%s", package->name,
- api_names[api], q);
- n = agnode(ssg, agxbuse(&buf), 1);
- a = agfindnodeattr(g, "label");
- agxset(n, a, lq);
- a = agfindnodeattr(g, "width");
- agxset(n, a, "1.0");
- a = agfindnodeattr(g, "shape");
- if (api == API_device) {
- agxset(n, a, "box");
- device_n = n;
- }
- else {
- agxset(n, a, "box");
- loadimage_n = n;
- }
- if (!(p && *p)) {
- m = agfindnode(sg, "render_cg");
- if (!m) {
- m = agnode(sg, "render_cg", 1);
- a = agfindgraphattr(g, "label");
- agxset(m, a, "cg");
- }
- agedge(sg, m, n, NULL, 1);
- }
- break;
- case API_render:
- /* draw renderers as ellipses - record last renderer in plugin (if any) in renderer_n */
- agxbprint(&buf, "%s_%s", api_names[api], q);
- renderer_n = n = agnode(ssg, agxbuse(&buf), 1);
- a = agfindnodeattr(g, "label");
- agxset(n, a, q);
- break;
- case API_textlayout:
- /* draw textlayout as invtriangle - record last textlayout in plugin (if any) in textlayout_n */
- /* FIXME? only one textlayout is loaded. Why? */
- agxbprint(&buf, "%s_%s", api_names[api], q);
- textlayout_n = n = agnode(ssg, agxbuse(&buf), 1);
- a = agfindnodeattr(g, "shape");
- agxset(n, a, "invtriangle");
- a = agfindnodeattr(g, "label");
- agxset(n, a, "T");
- break;
- case API_layout:
- /* draw textlayout as hexagon - record last layout in plugin (if any) in layout_n */
- agxbprint(&buf, "%s_%s", api_names[api], q);
- layout_n = n = agnode(ssg, agxbuse(&buf), 1);
- a = agfindnodeattr(g, "shape");
- agxset(n, a, "hexagon");
- a = agfindnodeattr(g, "label");
- agxset(n, a, q);
- break;
- default:
- break;
- }
- free(t);
- }
- }
- // add some invisible nodes (if needed) and invisible edges to
- // improve layout of cluster
- if (api == API_loadimage && !loadimage_n) {
- neededge_loadimage = 1;
- agxbprint(&buf, "%s_%s_invis", package->name, api_names[api]);
- loadimage_n = n = agnode(ssg, agxbuse(&buf), 1);
- a = agfindnodeattr(g, "style");
- agxset(n, a, "invis");
- a = agfindnodeattr(g, "label");
- agxset(n, a, "");
- a = agfindnodeattr(g, "width");
- agxset(n, a, "1.0");
- agxbprint(&buf, "%s_%s_invis_src", package->name,
- api_names[api]);
- n = agnode(g, agxbuse(&buf), 1);
- a = agfindnodeattr(g, "style");
- agxset(n, a, "invis");
- a = agfindnodeattr(g, "label");
- agxset(n, a, "");
- e = agedge(g, n, loadimage_n, NULL, 1);
- a = agfindedgeattr(g, "style");
- agxset(e, a, "invis");
- }
- if (api == API_render && !renderer_n) {
- neededge_loadimage = 1;
- neededge_device = 1;
- agxbprint(&buf, "%s_%s_invis", package->name, api_names[api]);
- renderer_n = n = agnode(ssg, agxbuse(&buf), 1);
- a = agfindnodeattr(g, "style");
- agxset(n, a, "invis");
- a = agfindnodeattr(g, "label");
- agxset(n, a, "");
- }
- if (api == API_device && !device_n) {
- neededge_device = 1;
- agxbprint(&buf, "%s_%s_invis", package->name, api_names[api]);
- device_n = n = agnode(ssg, agxbuse(&buf), 1);
- a = agfindnodeattr(g, "style");
- agxset(n, a, "invis");
- a = agfindnodeattr(g, "label");
- agxset(n, a, "");
- a = agfindnodeattr(g, "width");
- agxset(n, a, "1.0");
- }
- }
- if (neededge_loadimage) {
- e = agedge(sg, loadimage_n, renderer_n, NULL, 1);
- a = agfindedgeattr(g, "style");
- agxset(e, a, "invis");
- }
- if (neededge_device) {
- e = agedge(sg, renderer_n, device_n, NULL, 1);
- a = agfindedgeattr(g, "style");
- agxset(e, a, "invis");
- }
- if (textlayout_n) {
- e = agedge(sg, loadimage_n, textlayout_n, NULL, 1);
- a = agfindedgeattr(g, "style");
- agxset(e, a, "invis");
- }
- if (layout_n) {
- e = agedge(sg, loadimage_n, layout_n, NULL, 1);
- a = agfindedgeattr(g, "style");
- agxset(e, a, "invis");
- }
- }
- ssg = agsubg(g, "output_formats", 1);
- a = agfindgraphattr(ssg, "rank");
- agxset(ssg, a, "same");
- for (package = gvc->packages; package; package = package->next) {
- for (size_t api = 0; api < ARRAY_SIZE(api_names); api++) {
- for (pnext = gvc->apis[api]; pnext; pnext = pnext->next) {
- if (pnext->package == package) {
- t = q = gv_strdup(pnext->typestr);
- if ((p = strchr(q, ':')))
- *p++ = '\0';
- /* Now p = renderer, e.g. "gd"
- * and q = device, e.g. "png"
- * or q = imageloader, e.g. "png" */
- /* hack for aliases */
- lq = q;
- if (startswith(q, "jp")) {
- q = "jpg"; /* canonical - for node name */
- lq = "jpeg\\njpe\\njpg"; /* list - for label */
- }
- else if (startswith(q, "tif")) {
- q = "tif";
- lq = "tiff\\ntif";
- }
- else if (!strcmp(q, "x11") || !strcmp(q, "xlib")) {
- q = "x11";
- lq = "x11\\nxlib";
- }
- else if (!strcmp(q, "dot") || !strcmp(q, "gv")) {
- q = "gv";
- lq = "gv\\ndot";
- }
- switch (api) {
- case API_device: {
- agxbprint(&buf, "%s_%s_%s", package->name,
- api_names[api], q);
- n = agnode(g, agxbuse(&buf), 1);
- agxbprint(&buf, "output_%s", q);
- char *const output = agxbuse(&buf);
- m = agfindnode(ssg, output);
- if (!m) {
- m = agnode(ssg, output, 1);
- a = agfindnodeattr(g, "label");
- agxset(m, a, lq);
- a = agfindnodeattr(g, "shape");
- agxset(m, a, "note");
- }
- e = agfindedge(g, n, m);
- if (!e)
- e = agedge(g, n, m, NULL, 1);
- if (p && *p) {
- agxbprint(&buf, "render_%s", p);
- char *const render = agxbuse(&buf);
- m = agfindnode(ssg, render);
- if (!m)
- m = agnode(g, render, 1);
- e = agfindedge(g, m, n);
- if (!e)
- e = agedge(g, m, n, NULL, 1);
- }
- break;
- }
- case API_loadimage: {
- agxbprint(&buf, "%s_%s_%s", package->name,
- api_names[api], q);
- n = agnode(g, agxbuse(&buf), 1);
- agxbprint(&buf, "input_%s", q);
- char *const input = agxbuse(&buf);
- m = agfindnode(g, input);
- if (!m) {
- m = agnode(g, input, 1);
- a = agfindnodeattr(g, "label");
- agxset(m, a, lq);
- a = agfindnodeattr(g, "shape");
- agxset(m, a, "note");
- }
- e = agfindedge(g, m, n);
- if (!e)
- e = agedge(g, m, n, NULL, 1);
- agxbprint(&buf, "render_%s", p);
- char *const render = agxbuse(&buf);
- m = agfindnode(g, render);
- if (!m)
- m = agnode(g, render, 1);
- e = agfindedge(g, n, m);
- if (!e)
- e = agedge(g, n, m, NULL, 1);
- break;
- }
- default:
- break;
- }
- free(t);
- }
- }
- }
- }
- agxbfree(&buf);
- return g;
- }
|