Plan 9 from Bell Labs’s /usr/web/sources/contrib/steve/root/sys/src/cmd/graphviz/dotneato/common/utils.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
    This software may only be used by you under license from AT&T Corp.
    ("AT&T").  A copy of AT&T's Source Code Agreement is available at
    AT&T's Internet website having the URL:
    <http://www.research.att.com/sw/tools/graphviz/license/source.html>
    If you received this software without first entering into a license
    with AT&T, you have an infringing copy of this software and cannot use
    it without violating AT&T's intellectual property rights.
*/
#pragma prototyped

#include	"render.h"
#include	"xbuf.h"
#include "utils.h"
#ifndef MSWIN32
#include	<unistd.h>
#endif


/* local funcs */
static double dist(pointf, pointf);

void *zmalloc(size_t nbytes)
{
	char	*rv = malloc(nbytes);
	if (nbytes == 0) return 0;
	if (rv == NULL) {fprintf(stderr, "out of memory\n"); abort();}
	memset(rv,0,nbytes);
	return rv;
}

void *zrealloc(void *ptr, size_t size, size_t elt, size_t osize)
{
	void	*p = realloc(ptr,size*elt);
	if (p == NULL) {fprintf(stderr, "out of memory\n"); abort();}
	if (osize < size) memset((char*)p+(osize*elt),'\0',(size-osize)*elt);
	return p;
}

void *gmalloc(size_t nbytes)
{
	char	*rv = malloc(nbytes);
	if (nbytes == 0) return 0;
	if (rv == NULL) {fprintf(stderr, "out of memory\n"); abort();}
	return rv;
}

void *grealloc(void *ptr, size_t size)
{
	void	*p = realloc(ptr,size);
	if (p == NULL) {fprintf(stderr, "out of memory\n"); abort();}
	return p;
}

/*
 *  a queue of nodes
 */
queue *
new_queue(int sz)
{
	queue		*q = NEW(queue);

	if (sz <= 1) sz = 2;
	q->head = q->tail = q->store = N_NEW(sz,node_t*);
	q->limit = q->store + sz;
	return q;
}

void
free_queue(queue* q)
{
	free(q->store);
	free(q);
}

void
enqueue(queue* q, node_t* n)
{
	*(q->tail++) = n;
	if (q->tail >= q->limit) q->tail = q->store;
}

node_t *
dequeue(queue* q)
{
	node_t	*n;
	if (q->head == q->tail) n = NULL;
	else {
		n = *(q->head++);
		if (q->head >= q->limit) q->head = q->store;
	}
	return n;
}

/* returns index of an attribute if bound, else -1 */
int
late_attr(void* obj, char* name)
{
	attrsym_t	*a;
	if ((a = agfindattr(obj,name)) != 0) return a->index;
	else return -1;
}

int
late_int(void *obj, attrsym_t *attr, int def, int low)
{
	char	*p;
	int		rv;
	if (attr == NULL) return def;
	p = agxget(obj,attr->index);
	if (p[0]  == '\0') return def;
	if ((rv = atoi(p)) < low) rv = low;
	return rv;
}

double
late_double(void *obj,attrsym_t *attr, double def, double low)
{
	char		*p;
	double		rv;

	if (attr == NULL) return def;
	p = agxget(obj,attr->index);
	if (p[0]  == '\0') return def;
	if ((rv = atof(p)) < low) rv = low;
	return rv;
}

char *
late_string(void* obj, attrsym_t* attr, char* def)
{
	if (attr == NULL) return def;
	return agxget(obj,attr->index);
}

char *
late_nnstring(void* obj, attrsym_t* attr, char* def)
{
	char	*rv = late_string(obj,attr,def);
	if (rv[0] == '\0') rv = def;
	return rv;
}

int
late_bool(void *obj, attrsym_t *attr, int def)
{
	if (attr == NULL) return def;
	return mapbool(agxget(obj,attr->index));
}

/* counts occurences of 'c' in string 'p' */
int
strccnt(char *p, char c)
{
	int		rv = 0;
	while (*p) if (*p++ == c) rv++;
	return 	rv;
}

/* union-find */
node_t	*
UF_find(node_t* n)
{
	while (ND_UF_parent(n) && (ND_UF_parent(n) != n)) {
		if (ND_UF_parent(n)->u.UF_parent) ND_UF_parent(n) = ND_UF_parent(n)->u.UF_parent;
		n = ND_UF_parent(n);
	}
	return n;
}

node_t	*
UF_union(node_t *u, node_t *v)
{
	if (u == v) return u;
	if (ND_UF_parent(u) == NULL) {ND_UF_parent(u) = u; ND_UF_size(u) = 1;}
	else u = UF_find(u);
	if (ND_UF_parent(v) == NULL) {ND_UF_parent(v) = v; ND_UF_size(v) = 1;}
	else v = UF_find(v);
	if (u->id > v->id) { ND_UF_parent(u) = v; ND_UF_size(v) += ND_UF_size(u);}
	else {ND_UF_parent(v) = u; ND_UF_size(u) += ND_UF_size(v); v = u;}
	return v;
}

void
UF_remove(node_t *u, node_t *v)
{
	assert(ND_UF_size(u) == 1);
	ND_UF_parent(u) = u;
	ND_UF_size(v) -= ND_UF_size(u);
}

void
UF_singleton(node_t* u)
{
	ND_UF_size(u) = 1;
	ND_UF_parent(u) = NULL;
	ND_ranktype(u) = NORMAL;
}

void
UF_setname(node_t *u, node_t *v)
{
	assert(u == UF_find(u));
	ND_UF_parent(u) = v;
	ND_UF_size(v) += ND_UF_size(u);
}

point coord(node_t *n)
{
     pointf      pf;
     pf.x = ND_pos(n)[0];
     pf.y = ND_pos(n)[1];
     return cvt2pt(pf);
}

point pointof(int x, int y)
{
	point rv;
	rv.x = x, rv.y = y;
	return rv;
}

point
cvt2pt(pointf p)
{
	point	rv;
	rv.x = POINTS(p.x);
	rv.y = POINTS(p.y);
	return rv;
}

pointf
cvt2ptf(point p)
{
	pointf	rv;
	rv.x = PS2INCH(p.x);
	rv.y = PS2INCH(p.y);
	return rv;
}

box boxof (int llx, int lly, int urx, int ury)
{
	box b;

	b.LL.x = llx, b.LL.y = lly;
	b.UR.x = urx, b.UR.y = ury;
	return b;
}

box mkbox(point p0, point p1)
{
	box		rv;

	if (p0.x < p1.x)	{	rv.LL.x = p0.x; rv.UR.x = p1.x; }
	else				{	rv.LL.x = p1.x; rv.UR.x = p0.x; }
	if (p0.y < p1.y)	{	rv.LL.y = p0.y; rv.UR.y = p1.y; }
	else				{	rv.LL.y = p1.y; rv.UR.y = p0.y; }
	return rv;
}

point add_points(point p0, point p1)
{
	p0.x += p1.x;
	p0.y += p1.y;
	return p0;
}

point sub_points(point p0, point p1)
{
	p0.x -= p1.x;
	p0.y -= p1.y;
	return p0;
}

double atan2pt(point p1, point p0)
{
	return atan2((double)(p1.y - p0.y),(double)(p1.x - p0.x));
}

/* from Glassner's Graphics Gems */
#define W_DEGREE 5

/*
 *  Bezier : 
 *	Evaluate a Bezier curve at a particular parameter value
 *      Fill in control points for resulting sub-curves if "Left" and
 *	"Right" are non-null.
 * 
 */
pointf Bezier (pointf *V, int degree, double t, pointf* Left, pointf* Right)
{
	int i, j;		/* Index variables	*/
	pointf Vtemp[W_DEGREE + 1][W_DEGREE + 1];

	/* Copy control points	*/
	for (j =0; j <= degree; j++) {
		Vtemp[0][j] = V[j];
	}

	/* Triangle computation	*/
	for (i = 1; i <= degree; i++) {	
		for (j =0 ; j <= degree - i; j++) {
	    	Vtemp[i][j].x =
	      		(1.0 - t) * Vtemp[i-1][j].x + t * Vtemp[i-1][j+1].x;
	    	Vtemp[i][j].y =
	      		(1.0 - t) * Vtemp[i-1][j].y + t * Vtemp[i-1][j+1].y;
		}
	}
	
	if (Left != NULL)
		for (j = 0; j <= degree; j++)
	    		Left[j] = Vtemp[j][0];
	if (Right != NULL)
		for (j = 0; j <= degree; j++)
	    		Right[j] = Vtemp[degree-j][j];

	return (Vtemp[degree][0]);
}

char *strdup_and_subst_graph(char *str, Agraph_t *g)
{
	char c, *s, *p, *t, *newstr;
	char *g_str=NULL;
	int g_len=0, newlen=0;

	/* two passes over str.
	 *
	 * first pass prepares substitution strings and computes 
	 * total length for newstring required from malloc.
	 */ 
	for (s=str;(c=*s++);) {
		if (c=='\\') {
			switch (c=*s++) {
			case 'G':
				if (! g_str) {
					g_str = g->name;
					g_len = strlen(g_str);
				}
				newlen += g_len;
				break;
			default:
				newlen +=2;
			}
		}
		else {
			newlen++;
		}
	}
	/* allocate new string */
	newstr=gmalloc(newlen+1);

	/* second pass over str assembles new string */
	for (s=str,p=newstr;(c=*s++);) {
		if (c=='\\') {
			switch (c=*s++) {
			case 'G':
				for (t=g_str;(*p=*t++);p++);
				break;

			default:
				*p++ = '\\';
				*p++ = c;
			}
		}
		else {
			*p++ = c;
		}
	}
	*p++ = '\0';
	return newstr;
}

char *strdup_and_subst_node(char *str, Agnode_t *n)
{
	char c, *s, *p, *t, *newstr;
	char *g_str=NULL, *n_str=NULL;
	int g_len=0, n_len=0, newlen=0;

	/* two passes over str.
	 *
	 * first pass prepares substitution strings and computes 
	 * total length for newstring required from malloc.
	 */ 
	for (s=str;(c=*s++);) {
		if (c=='\\') {
			switch (c=*s++) {
			case 'G':
				if (! g_str) {
					g_str = n->graph->name;
					g_len = strlen(g_str);
				}
				newlen += g_len;
				break;
			case 'N':
				if (! n_str) {
					n_str = n->name;
					n_len = strlen(n_str);
				}
				newlen += n_len;
				break;
			default:
				newlen +=2;
			}
		}
		else {
			newlen++;
		}
	}
	/* allocate new string */
	newstr=gmalloc(newlen+1);

	/* second pass over str assembles new string */
	for (s=str,p=newstr;(c=*s++);) {
		if (c=='\\') {
			switch (c=*s++) {
			case 'G':
				for (t=g_str;(*p=*t++);p++);
				break;

			case 'N':
				for (t=n_str;(*p=*t++);p++);
				break;
			default:
				*p++ = '\\';
				*p++ = c;
			}
		}
		else {
			*p++ = c;
		}
	}
	*p++ = '\0';
	return newstr;
}

char *strdup_and_subst_edge(char *str, Agedge_t *e)
{
	char c, *s, *p, *t, *newstr;
	char *e_str=NULL, *h_str=NULL, *t_str=NULL;
	int e_len=0, h_len=0, t_len=0, newlen=0;

	/* two passes over str.
	 *
	 * first pass prepares substitution strings and computes 
	 * total length for newstring required from malloc.
	 */ 
	for (s=str;(c=*s++);) {
		if (c=='\\') {
			switch (c=*s++) {
			case 'E':
				if (! e_str) {
					t_str = e->tail->name;
					t_len = strlen(t_str);
					h_str = e->head->name;
					h_len = strlen(h_str);
					if (e->tail->graph->root->kind & AGFLAG_DIRECTED)
						e_str = "->";
					else
						e_str = "--";
					e_len = t_len +2 +h_len;
				}
				newlen += e_len;
				break;
			case 'H':
				if (! h_str) {
					h_str = e->head->name;
					h_len = strlen(h_str);
				}
				newlen += h_len;
				break;
			case 'T':
				if (! t_str) {
					t_str = e->tail->name;
					t_len = strlen(t_str);
				}
				newlen += t_len;
				break;
			default:
				newlen +=2;
			}
		}
		else {
			newlen++;
		}
	}
	/* allocate new string */
	newstr=gmalloc(newlen+1);

	/* second pass over str assembles new string */
	for (s=str,p=newstr;(c=*s++);) {
		if (c=='\\') {
			switch (c=*s++) {
			case 'E':
				for (t=t_str;(*p=*t++);p++);
				for (t=e_str;(*p=*t++);p++);
				for (t=h_str;(*p=*t++);p++);
				break;
			case 'H':
				for (t=h_str;(*p=*t++);p++);
				break;
			case 'T':
				for (t=t_str;(*p=*t++);p++);
				break;
			default:
				*p++ = '\\';
				*p++ = c;
			}
		}
		else {
			*p++ = c;
		}
	}
	*p++ = '\0';
	return newstr;
}

/* return true if *s points to &[a-z]*;  (e.g. &amp; )
 *                          or &#[0-9]*; (e.g. &#38; )
 */
static int
xml_isentity(char *s)
{
	s++;  /* already known to be '&' */
	if (*s == '#') {
		s++;
		while (*s >= '0' && *s <= '9') s++;
	}
	else {
		while (*s >= 'a' && *s <= 'z') s++;
	}
	if (*s == ';') return 1;
	return 0;
}


char * xml_string(char *s)
{
	static char	*buf=NULL;
	static int	bufsize=0;
	char		*p, *sub;
	int		len, pos=0;

	if (!buf) {
		bufsize = 64;
		buf = gmalloc(bufsize);
	}

	p = buf;
	while (*s) {
		if (pos > (bufsize-8)) {
			bufsize *= 2;
			buf = grealloc(buf,bufsize);
			p = buf + pos;
		}
		/* these are safe even if string is already UTF-8 coded
		 * since UTF-8 strings won't contain '<' or '>' */
		if (*s == '<') {
			sub = "&lt;";
			len = 4;
		}
		else if (*s == '>') {
			sub = "&gt;";
			len = 4;
		}
		/* escape '&' only if not part of a legal entity sequence */
		else if (*s == '&' && ! (xml_isentity(s))) {
			sub = "&amp;";
			len = 5;
		}
		else {
			sub = s;
			len = 1;
		}
		while (len--) {
			*p++ = *sub++;
			pos++;
		}
		s++;
	}
	*p = '\0';
	return buf;
}

textlabel_t	*make_label(char *str, double fontsize, char *fontname, char *fontcolor, graph_t *g)
{
	textlabel_t	*rv = NEW(textlabel_t);
	rv->text = str;
	rv->fontname = fontname;
	rv->fontcolor = fontcolor;
	rv->fontsize = fontsize;
	label_size(str,rv,g);
	return rv;
}

void free_label(textlabel_t* p)
{
	if (p) {
		if (p->nlines != '\0')
			free(p->line[0].str);
		free(p->line);
		free(p->text);
		free(p);
	}
}

#ifdef DEBUG
edge_t	* debug_getedge(graph_t *g, char *s0, char *s1)
{
	node_t	*n0,*n1;
	n0 = agfindnode(g,s0);
	n1 = agfindnode(g,s1);
	if (n0 && n1) return agfindedge(g,n0,n1);
	else return NULL;
}
#endif

#ifndef MSWIN32
#include	<pwd.h>
static unsigned char userbuf[SMALLBUF];
static xbuf xb;

static void
cleanup()
{
  xbfree(&xb);
}
#endif

char * username()
{
	char*          user = NULL;
#ifndef MSWIN32
    static int     first = 1;
	struct passwd* p;
    if (first) {
      xbinit (&xb, SMALLBUF, userbuf);
      atexit(cleanup);
      first = 0;
    }
	p = (struct passwd *) getpwuid(getuid());
    if (p) {
      xbputc (&xb, '(');
      xbput (&xb, p->pw_name);
      xbput (&xb, ") ");
/* nothing like this in plan 9 */
#if 0
#ifdef SVR4
      xbput (&xb, p->pw_comment);
#else
      xbput (&xb, p->pw_gecos);
#endif
#endif
      user = xbuse (&xb);
    }
#endif
	if (user == NULL) user = "Bill Gates";
	return user;
}

/* Fgets:
 * Read a complete line.
 * Return pointer to line, 
 * or 0 on EOF
 */
static char* 
Fgets (FILE *fp)
{
  static int bsize = 0;
  static char* buf;
  char* lp;
  int len;

  len = 0;
  do {
    if (bsize - len < BUFSIZ) {
      bsize += BUFSIZ;
      buf = grealloc (buf, bsize);
    }
    lp = fgets (buf + len, bsize - len , fp);
    if (lp == 0) break;
    len += strlen(lp);        /* since lp != NULL, len > 0 */
  } while (buf[len-1] != '\n');

  if (len > 0) return buf;
  else return 0;
}

int httpcheck(char *filename)
{
	static int onetime = TRUE;
	if (HTTPServerEnVar) {
		if (onetime) agerr(AGWARN, "file loading is disabled because %s is set\n",HTTPServerEnVar);
		agerr(AGWARN, "request to load %s ignored\n",filename);
		onetime = FALSE;
		return TRUE;
	}
	return FALSE;
}

void cat_libfile(FILE *ofp, char **arglib, char **stdlib)
{
	FILE	*fp;
	char	*p,**s, *bp;
	int		i,use_stdlib = TRUE;

	if (arglib) {
		if (httpcheck(arglib[0]?arglib[0]:"[library files]")) return;
		for (i = 0; (p = arglib[i]) != 0; i++)
			if (*p == '\0') use_stdlib = FALSE;
	}
	if (use_stdlib) for (s = stdlib; *s; s++) {fputs(*s,ofp); fputc('\n',ofp);}
	if (arglib) for (i = 0; (p = arglib[i]) != 0; i++) {
		if (p[0] && ((fp = fopen(p,"r")) != 0)) {
			while ((bp = Fgets(fp))) fputs(bp,ofp);
		}
		else agerr(AGWARN, "can't open library file %s\n", p);
	}
}

int
rect_overlap(box b0, box b1)
{
	if ((b0.UR.x < b1.LL.x) || (b1.UR.x < b0.LL.x) 
		|| (b0.UR.y < b1.LL.y) || (b1.UR.y < b0.LL.y)) return FALSE;
	return TRUE;
}

int
maptoken(char *p,char **name, int *val)
{
	int		i;
	char	*q;

	for (i = 0; (q = name[i]) != 0; i++)
		if (p && streq(p,q)) break;
	return val[i];
}

int
mapbool(char* p)
{
	if (p == NULL) return FALSE;
	if (!strcasecmp(p,"false")) return FALSE;
	if (!strcasecmp(p,"true")) return TRUE;
	return atoi(p);
}

#ifdef OLD
/* There are duplicate definitions of strcasecmp and strncasecmp 
 * in strcasecmp.c and strncasecmp.c 
 * Note also that the use of plain chars means these definitions
 * are not correct for systems with char != unsigned char.
 */
#ifdef MSWIN32
strcasecmp(s0,s1)
char	*s0,*s1;
{
	char		c0,c1;
	do {
		c0 = *s0++;
		c1 = *s1++;
		if (isupper(c0)) c0 = (char)tolower(c0);
		if (isupper(c1)) c1 = (char)tolower(c1);
		if (c0 != c1) break;
	} while (c0 && c1);
	return c0 - c1;
}
strncasecmp(s0,s1,n)
char	*s0,*s1;
int		n;
{
	char		c0,c1;
	int			m = n;
	while (m--) {
		c0 = *s0++;
		c1 = *s1++;
		if (isupper(c0)) c0 = (char)tolower(c0);
		if (isupper(c1)) c1 = (char)tolower(c1);
		if (c0 != c1) break;
	}
	return c0 - c1;
}
#endif
#endif

static double dist(p,q)
pointf  p,q;
{
    double  d0,d1;
    d0 = p.x - q.x;
    d1 = p.y - q.y;
    return sqrt(d0*d0 + d1*d1);
}

point dotneato_closest(splines* spl, point p)
{
	int		i, j, k, besti, bestj;
	double	bestdist, d, dlow, dhigh;
	double low, high, t;
	pointf c[4], pt2, pt;
	point rv;
	bezier bz;

	besti = bestj = -1;
	bestdist = 1e+38;
	pt.x = p.x; pt.y = p.y;
	for (i = 0; i < spl->size; i++) {
		bz = spl->list[i];
		for (j = 0; j < bz.size; j++) {
			pointf b;

			b.x = bz.list[j].x; b.y = bz.list[j].y;
			d = dist(b,pt);
			if ((bestj == -1) || (d < bestdist)) {
				besti = i;
				bestj = j;
				bestdist = d;
			}
		}
	}

	bz = spl->list[besti];
	j = bestj/3; if (j >= spl->size) j--;
	for (k = 0; k < 4; k++) {
		c[k].x = bz.list[j + k].x;
		c[k].y = bz.list[j + k].y;
	}
	low = 0.0; high = 1.0;
	dlow = dist(c[0],pt);
	dhigh = dist(c[3],pt);
	do {
		t = (low + high) / 2.0;
		pt2 = Bezier (c, 3, t, NULL, NULL);
		if (fabs(dlow - dhigh) < 1.0) break;
		if (low == high) break;
		if (dlow < dhigh) {high = t; dhigh = dist(pt2,pt);}
		else {low = t; dlow = dist(pt2,pt); }
	} while (1);
	rv.x = pt2.x;
	rv.y = pt2.y;
	return rv;
}

point spline_at_y(splines *spl, int y)
{
	int i,j;
	double low, high, d, t;
	pointf c[4], pt2;
	point pt;
	static bezier bz;
	static splines *mem = NULL;

	if (mem != spl) {
		mem = spl;
		for (i = 0; i < spl->size; i++) {
			bz = spl->list[i];
			if (BETWEEN (bz.list[bz.size-1].y, y, bz.list[0].y))
				break;
		}
	}
	if (y > bz.list[0].y)
		pt = bz.list[0];
	else if (y < bz.list[bz.size-1].y)
		pt = bz.list[bz.size - 1];
	else {
		for (i = 0; i < bz.size; i += 3) {
			for (j = 0; j < 3; j++) {
				if ((bz.list[i+j].y <= y) && (y <= bz.list[i+j+1].y))
					break;
				if ((bz.list[i+j].y >= y) && (y >= bz.list[i+j+1].y))
					break;
			}
			if (j < 3)
				break;
		}
		assert (i < bz.size);
		for (j = 0; j < 4; j++) {
			c[j].x = bz.list[i + j].x;
			c[j].y = bz.list[i + j].y;
			/* make the spline be monotonic in Y, awful but it works for now */
			if ((j > 0) && (c[j].y > c[j - 1].y))
				c[j].y = c[j - 1].y;
		}
		low = 0.0; high = 1.0;
		do {
			t = (low + high) / 2.0;
			pt2 = Bezier (c, 3, t, NULL, NULL);
			d = pt2.y - y;
			if (ABS(d) <= 1)
				break;
			if (d < 0)
				high = t;
			else
				low = t;
		} while (1);
		pt.x = pt2.x;
		pt.y = pt2.y;
	}
	pt.y = y;
	return pt;
}

point neato_closest (splines *spl, point p)
{
/* this is a stub so that we can share a common emit.c between dot and neato */

	return spline_at_y(spl, p.y);
}

static int       Tflag;
void toggle(int s)
{
        Tflag = !Tflag;
#ifndef MSWIN32
        signal (SIGUSR1, toggle);
#endif
}

int test_toggle()
{
	return Tflag;
}

void common_init_node(node_t *n)
{
        char    *str;

        ND_width(n) = late_double(n,N_width,DEFAULT_NODEWIDTH,MIN_NODEWIDTH);
        ND_height(n) = late_double(n,N_height,DEFAULT_NODEHEIGHT,MIN_NODEHEIGHT);
        if (N_label == NULL) str = NODENAME_ESC;
        else str = agxget(n,N_label->index);
        str = strdup_and_subst_node(str,n);
        ND_label(n) = make_label(str,
                late_double(n,N_fontsize,DEFAULT_FONTSIZE,MIN_FONTSIZE),
                late_nnstring(n,N_fontname,DEFAULT_FONTNAME),
                late_nnstring(n,N_fontcolor,DEFAULT_COLOR), n->graph);
        ND_shape(n) = bind_shape(late_nnstring(n,N_shape,DEFAULT_NODESHAPE));
        ND_showboxes(n) = late_int(n,N_showboxes,0,0);
        ND_shape(n)->initfn(n);
}

void common_init_edge(edge_t *e)
{
        char    *s;

        if (E_label && (s = agxget(e,E_label->index)) && (s[0])) {
	        s = strdup_and_subst_edge(s,e);
                ED_label(e) = make_label(s,
                        late_double(e,E_fontsize,DEFAULT_FONTSIZE,MIN_FONTSIZE),
                        late_nnstring(e,E_fontname,DEFAULT_FONTNAME),
                        late_nnstring(e,E_fontcolor,DEFAULT_COLOR),e->tail->graph);
                GD_has_edge_labels(e->tail->graph) |= EDGE_LABEL;
                ED_label_ontop(e) = mapbool(late_string(e,E_label_float,"false"));
        }

    /* vladimir */
        if (E_headlabel && (s = agxget(e,E_headlabel->index)) && (s[0])) {
	        s = strdup_and_subst_edge(s,e);
                ED_head_label(e) = make_label(s,
                        late_double(e,E_labelfontsize,DEFAULT_LABEL_FONTSIZE,MIN_FONTSIZE),
                        late_nnstring(e,E_labelfontname,DEFAULT_FONTNAME),
                        late_nnstring(e,E_labelfontcolor,DEFAULT_COLOR),e->tail->graph);
                GD_has_edge_labels(e->tail->graph) |= HEAD_LABEL;
        }
        if (E_taillabel && (s = agxget(e,E_taillabel->index)) && (s[0])) {
	        s = strdup_and_subst_edge(s,e);
                ED_tail_label(e) = make_label(s,
                        late_double(e,E_labelfontsize,DEFAULT_LABEL_FONTSIZE,MIN_FONTSIZE),
                        late_nnstring(e,E_labelfontname,DEFAULT_FONTNAME),
                        late_nnstring(e,E_labelfontcolor,DEFAULT_COLOR),e->tail->graph);
                GD_has_edge_labels(e->tail->graph) |= TAIL_LABEL;
        }
    /* end vladimir */
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.