Plan 9 from Bell Labs’s /usr/web/sources/contrib/steve/root/sys/src/cmd/graphviz/dotneato/common/output.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"


int	   Output_lang_count;
char       *Output_langs[MAX_PRODUCTS];
int        Output_lang;

int	   Output_file_count;
char       *Output_files[MAX_PRODUCTS];
FILE       *Output_file;

FILE       *Output_handles[MAX_PRODUCTS];
int	   files_opened_once=0;

codegen_t  *CodeGen;

int        y_invert;        /* invert y in bounding box */
static int        y_off;    /* ymin + ymax */
static double     yf_off;   /* y_off in inches */
static int        e_arrows; /* graph has edges with end arrows */
static int        s_arrows; /* graph has edges with start arrows */

static void extend_attrs (graph_t* g);

  /* macros for inverting the y coordinate with the bounding box */
#define Y(y) (y_invert ? (y_off - (y)) : (y)) 
#define YF(y) (y_invert ? (yf_off - (y)) : (y)) 

void dotneato_set_margins(graph_t* g)
{
	double	xf, yf;
	char	*p;
	int	i;

	/* margins */
	if ((p = agget(g,"margin"))) {
		i = sscanf(p,"%lf,%lf",&xf,&yf);
		if (i > 0) GD_drawing(g)->margin.x = GD_drawing(g)->margin.y = POINTS(xf);
		if (i > 1) GD_drawing(g)->margin.y = POINTS(yf);
	}
	else {
		/* set default margins depending on format */
		switch (Output_lang) {
			case GIF:
			case PNG:
			case JPEG:
			case WBMP:
			case GD:
			case memGD:
			case GD2:
			case ISMAP:
			case IMAP:
			case CMAP:
			case VRML:
			case DIA:
			case SVG:
			case SVGZ:
	        		GD_drawing(g)->margin.x = GD_drawing(g)->margin.y = DEFAULT_EMBED_MARGIN;
				break;
			case POSTSCRIPT:
			case PDF:
			case HPGL:
			case PCL:
			case MIF:
			case METAPOST:
			case FIG:
			case VTX:
			case ATTRIBUTED_DOT:
			case PLAIN:		
			case PLAIN_EXT:		
	        		GD_drawing(g)->margin.x = GD_drawing(g)->margin.y = DEFAULT_MARGIN;
				break;
			case CANONICAL_DOT:
				break;
		}
	}
}

/* chkOrder:
 * Determine order of output.
 * Output usually in breadth first graph walk order
 */
static int
chkOrder (graph_t* g)
{
	char *p = agget(g,"outputorder");
    if (p) {
		char c = *p;
		if ((c == 'n') && !strcmp(p+1,"odesfirst")) return EMIT_SORTED;
		if ((c == 'e') && !strcmp(p+1,"edgesfirst")) return EMIT_EDGE_SORTED;
	}
	return 0;
}

void dotneato_write_one(graph_t* g)
{
	dotneato_set_margins(g);
	switch (Output_lang) {
		case POSTSCRIPT:
		case PDF:
		case HPGL:
		case PCL:
		case MIF:
		case PIC_format:
		case GIF:
		case PNG:
		case JPEG:
		case WBMP:
		case GD:
		case memGD:
		case GD2:
		case VRML:
		case METAPOST:
		case SVG:
		case SVGZ:
			emit_graph(g,chkOrder (g)); break;
		case ISMAP:
		case IMAP:
		case CMAP:
			/* output in breadth first graph walk order, but 
			 * with nodes edges and nested clusters before
			 * clusters */
			emit_graph(g,EMIT_CLUSTERS_LAST); break;
		case FIG:
			/* output color definition objects first */
			emit_graph(g,EMIT_COLORS); break;
		case VTX:
			/* output sorted, i.e. all nodes then all edges */
			emit_graph(g,EMIT_SORTED); break;
		case DIA:
			/* output in preorder traversal of the graph*/
			emit_graph(g,EMIT_PREORDER);
			break;
		case EXTENDED_DOT:
			attach_attrs(g);
			extend_attrs(g);
			agwrite(g,Output_file); break;
		case ATTRIBUTED_DOT:
			attach_attrs(g);
			agwrite(g,Output_file); break;
		case CANONICAL_DOT:
			agwrite(g,Output_file); break;
		case PLAIN:		
			attach_attrs(g); write_plain(g,Output_file); break;
		case PLAIN_EXT:		
			attach_attrs(g); write_plain_ext(g,Output_file); break;
	}
	fflush(Output_file);
}

void
dotneato_write(graph_t* g)
{
    int j;

    for (j=0; j<Output_lang_count; j++) {
        if (! files_opened_once) {
            if (Output_files[j] == NULL) {
                Output_handles[j] = stdout;
            }
            else {
                Output_handles[j] = file_select(Output_files[j]);
            }
            Output_lang = lang_select(Output_langs[j], 1);
        }
	else {
            Output_lang = lang_select(Output_langs[j], 0);
	}
        Output_file = Output_handles[j];
        dotneato_write_one(g);
    }
    files_opened_once++;
}

void
dotneato_eof(void)
{
    int j;

    for (j=0; j<Output_lang_count; j++) {
        if (files_opened_once) {
            Output_file = Output_handles[j];
            Output_lang = lang_select(Output_langs[j], 0);
            emit_eof();
            fclose(Output_file);
        }
    }
}

static void 
set_record_rects (node_t* n, field_t* f, xbuf* xb)
{
	int    i;
    char   buf[BUFSIZ];

	if (f->n_flds == 0) {
		sprintf(buf, "%d,%d,%d,%d ",
				f->b.LL.x + ND_coord_i(n).x,
				Y(f->b.LL.y + ND_coord_i(n).y),
				f->b.UR.x + ND_coord_i(n).x,
				Y(f->b.UR.y + ND_coord_i(n).y));
        xbput (xb, buf);
	}
	for (i = 0; i < f->n_flds; i++)
		set_record_rects (n, f->fld[i], xb);
}

static attrsym_t *safe_dcl(graph_t *g, void *obj, char *name, char *def,
	attrsym_t*(*fun)(Agraph_t*, char*, char*))
{
	attrsym_t	*a = agfindattr(obj,name);
	if (a == NULL) a = fun(g,name,def);
	return a;
}

void attach_attrs(graph_t* g)
{
	int		i,j,sides;
    char    buf[BUFSIZ];     /* Used only for small strings */
	unsigned char	xbuffer[BUFSIZ]; /* Initial buffer for xb */
	xbuf	xb;
	node_t	*n;
	edge_t	*e;
	point	pt;

	e_arrows = s_arrows = 0;
    if (y_invert) {
		y_off = GD_bb(g).UR.y + GD_bb(g).LL.y;
		yf_off = PS2INCH(y_off);
	}
    xbinit (&xb, BUFSIZ, xbuffer);
	safe_dcl(g,g->proto->n,"pos","",agnodeattr);
	safe_dcl(g,g->proto->n,"rects","",agnodeattr);
	N_width = safe_dcl(g,g->proto->n,"width","",agnodeattr);
	N_height = safe_dcl(g,g->proto->n,"height","",agnodeattr);
	safe_dcl(g,g->proto->e,"pos","",agedgeattr);
	if (GD_has_edge_labels(g) & EDGE_LABEL) 
      safe_dcl(g,g->proto->e,"lp","",agedgeattr);
	if (GD_has_edge_labels(g) & HEAD_LABEL) 
      safe_dcl(g,g->proto->e,"head_lp","",agedgeattr);
	if (GD_has_edge_labels(g) & TAIL_LABEL) 
      safe_dcl(g,g->proto->e,"tail_lp","",agedgeattr);
	if (GD_label(g)) {
		safe_dcl(g,g,"lp","",agraphattr);
		if (GD_label(g)->text[0]) {
			pt = GD_label(g)->p;
			sprintf(buf,"%d,%d",pt.x,Y(pt.y));
			agset(g,"lp",buf);
		}
	}
	safe_dcl(g,g,"bb","",agraphattr);
	for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
		sprintf(buf,"%d,%d",ND_coord_i(n).x,Y(ND_coord_i(n).y));
		agset(n,"pos",buf);
		sprintf(buf,"%.2f",PS2INCH(ND_ht_i(n)));
		agxset(n,N_height->index,buf);
		sprintf(buf,"%.2f",PS2INCH(ND_lw_i(n) + ND_rw_i(n)));
		agxset(n,N_width->index,buf);
		if (strcmp (ND_shape(n)->name, "record") == 0) {
			set_record_rects (n, ND_shape_info(n), &xb);
            xbpop (&xb); /* get rid of last space */
			agset(n,"rects",xbuse(&xb));
		}
		else {
			extern void	poly_init(node_t *);
			polygon_t *poly;
			int i;
			if (N_vertices && (ND_shape(n)->initfn == poly_init)) {
				poly = (polygon_t*) ND_shape_info(n);
				sides = poly->sides;
				if (sides < 3) {
					char *p = agget(n,"samplepoints");
					if (p) sides = atoi(p);
					else sides = 8;
					if (sides < 3) sides = 8;
				}
				for (i = 0; i < sides; i++) {
					if (i > 0) xbputc (&xb, ' ');
					if (poly->sides >= 3)
						sprintf(buf,"%.3f %.3f",
							poly->vertices[i].x,YF(poly->vertices[i].y));
					else
						sprintf(buf,"%.3f %.3f",
						  ND_width(n)/2.0 * cos(i/(double)sides * PI * 2.0),
						  YF(ND_height(n)/2.0 * sin(i/(double)sides* PI * 2.0)));
					xbput(&xb, buf);
				}
				agxset(n,N_vertices->index,xbuse(&xb));
			}
		}
		if (State >= GVSPLINES) {
			for (e = agfstout(g,n); e; e = agnxtout(g,e)) {
				if (ED_edge_type(e) == IGNORED) continue;
				for (i = 0; i < ED_spl(e)->size; i++) {
					if (i > 0) xbputc (&xb, ';');
					if (ED_spl(e)->list[i].sflag) {
						s_arrows = 1;
						sprintf (buf, "s,%d,%d ",
                          ED_spl(e)->list[i].sp.x,Y(ED_spl(e)->list[i].sp.y));
						xbput(&xb, buf);
					}
					if (ED_spl(e)->list[i].eflag) {
						e_arrows = 1;
						sprintf (buf, "e,%d,%d ",
                          ED_spl(e)->list[i].ep.x,Y(ED_spl(e)->list[i].ep.y));
						xbput(&xb, buf);
					}
					for (j = 0; j < ED_spl(e)->list[i].size; j++) {
						if (j > 0) xbputc (&xb, ' ');
						pt = ED_spl(e)->list[i].list[j];
						sprintf(buf,"%d,%d",pt.x,Y(pt.y));
						xbput(&xb, buf);
					}
				}
				agset(e,"pos",xbuse(&xb));
				if (ED_label(e)) {
					pt = ED_label(e)->p;
					sprintf(buf,"%d,%d",pt.x,Y(pt.y));
					agset(e,"lp",buf);
				}
				if (ED_head_label(e)) {
					pt = ED_head_label(e)->p;
					sprintf(buf,"%d,%d",pt.x,Y(pt.y));
					agset(e,"head_lp",buf);
				}
				if (ED_tail_label(e)) {
					pt = ED_tail_label(e)->p;
					sprintf(buf,"%d,%d",pt.x,Y(pt.y));
					agset(e,"tail_lp",buf);
				}
			}
		}
	}
	rec_attach_bb(g);
    xbfree (&xb);
}

void rec_attach_bb(graph_t* g)
{
	int		c;
	char	buf[32];
	point	pt;

	sprintf(buf,"%d,%d,%d,%d", GD_bb(g).LL.x, Y(GD_bb(g).LL.y),
		GD_bb(g).UR.x, Y(GD_bb(g).UR.y));
	agset(g,"bb",buf);
	if (GD_label(g) && GD_label(g)->text[0]) {
		pt = GD_label(g)->p;
		sprintf(buf,"%d,%d",pt.x,Y(pt.y));
		agset(g,"lp",buf);
	}
	for (c = 1; c <= GD_n_cluster(g); c++) rec_attach_bb(GD_clust(g)[c]);
}

static char *getoutputbuffer(char *str)
{
        static char             *rv;
        static int              len;
        int                     req;

        req = MAX(2 * strlen(str) + 2, BUFSIZ);
        if (req > len) {
				rv = ALLOC(req,rv,char);
                len = req;
        }
        return rv;
}


static char *canonical(char *str)
{
        return agstrcanon(str,getoutputbuffer(str));
}

static void writenodeandport(FILE *fp, char *node, char *port)
{
        fprintf(fp,"%s",canonical(node));       /* slimey i know*/
        if (port && *port) fprintf(fp,"%c%s",port[0],canonical(port+1));
}

/* FIXME - there must be a proper way to get port info - these are 
 * supposed to be private to libgraph - from libgraph.h */
#define TAILX 1
#define HEADX 2

/* _write_plain:
 * Assumes y_invert parameters have been set.
 * At present, this is done by a previous call to attach_attrs.
 * As this appears to be the only reason to call attach_attrs for plain
 * output, it may be cleaner to remove that call and handle y_invert
 * here.
 */
void _write_plain(graph_t* g, FILE* f, boolean extend)
{
	int			i,j,splinePoints;
	char		*tport, *hport;
	node_t		*n;
	edge_t		*e;
	bezier		bz;
	point		pt;

	setup_graph(g);
	pt = GD_bb(g).UR;
	fprintf(f,"graph %.3f %.3f %.3f\n",
		GD_drawing(g)->scale, PS2INCH(pt.x), PS2INCH(pt.y));
	for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
		fprintf(f,"node %s ",canonical(n->name)); printptf(f,ND_coord_i(n));
		fprintf(f," %.3f %.3f %s %s %s %s %s\n",
			ND_width(n),ND_height(n),canonical(ND_label(n)->text),
			late_nnstring(n,N_style,"solid"),
			ND_shape(n)->name,
			late_nnstring(n,N_color,DEFAULT_COLOR),
			late_nnstring(n,N_fillcolor,DEFAULT_FILL));
	}
	for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
		for (e = agfstout(g,n); e; e = agnxtout(g,e)) {
			if (extend && e->attr) {
				tport = e->attr[TAILX]; 
				hport = e->attr[HEADX];
			}
			else tport = hport = "";
			if (ED_spl(e)) {
				splinePoints = 0;
				for (i = 0; i < ED_spl(e)->size; i++) {
					bz = ED_spl(e)->list[i];
					splinePoints += bz.size;
				}
				fprintf(f,"edge ");
				writenodeandport(f,e->tail->name,tport);
				fprintf(f," ");
				writenodeandport(f,e->head->name,hport);
				fprintf(f," %d",splinePoints);
				for (i = 0; i < ED_spl(e)->size; i++) {
					bz = ED_spl(e)->list[i];
					for (j = 0; j < bz.size; j++) printptf(f,bz.list[j]);
				}
			}
			if (ED_label(e)) {
				fprintf(f," %s",canonical(ED_label(e)->text));
				printptf(f,ED_label(e)->p);
			}
			fprintf(f," %s %s\n",late_nnstring(e,E_style,"solid"),
				late_nnstring(e,E_color,DEFAULT_COLOR));
		}
	}
	fprintf(f,"stop\n");
}

void write_plain(graph_t* g, FILE* f)
{
	_write_plain (g, f, FALSE);
}

void write_plain_ext(graph_t* g, FILE* f)
{
	_write_plain (g, f, TRUE);
}

void printptf(FILE* f, point pt)
{
	fprintf(f," %.3f %.3f",PS2INCH(pt.x),PS2INCH(Y(pt.y)));
}

int codegen_bezier_has_arrows(void)
{
	return CodeGen && 
      CodeGen->bezier_has_arrows
      /* (CodeGen->arrowhead == 0)) */;
}

typedef void (*BJ)(FILE *,graph_t *, char **, char *, char **, point);
typedef void (*BG)(graph_t *, box bb, point pb);
typedef void (*BP)(graph_t *, point, double, int, point);
typedef void (*BC)(graph_t *);
typedef void (*BN)(node_t *);
typedef void (*BE)(edge_t *);
typedef void (*SA)(char *);
typedef void (*SS)(char **);
typedef void (*SF)(char *, double);

static xbuf outbuf;
static xbuf charbuf;

static void	
xd_textline(point p, textline_t *line)
{
	char  buf[BUFSIZ];
    int   j;

	xbputc(&charbuf, 'T');
    switch(line->just) {
    case 'l':
		j = -1;
		break;
    case 'r':
		j = 1;
		break;
    default:
    case 'n':
		j = 0;
		break;
    }
    sprintf(buf, " %d %d %d %d %d -", p.x, Y(p.y), j,
      line->width, strlen(line->str));
	xbput(&charbuf, buf);
	xbput(&charbuf, line->str);
	xbputc(&charbuf, ' ');
}

static void	
xd_ellipse(point p, int rx, int ry, int filled)
{
	char  buf[BUFSIZ];

	xbputc(&outbuf, (filled ? 'E' : 'e'));
    sprintf(buf, " %d %d %d %d ", p.x, Y(p.y), rx, ry);
	xbput(&outbuf, buf);
}

static void
points (char c,point *A,int n)
{
	char  buf[BUFSIZ];
    int   i;
    point p;

	xbputc(&outbuf, c);
    sprintf(buf, " %d ", n);
	xbput(&outbuf, buf);
	for (i = 0; i < n; i++) {
      p = A[i];
      sprintf(buf, "%d %d ", p.x, Y(p.y));
	  xbput(&outbuf, buf);
    }
}

static void
xd_polygon(point *A, int n, int filled)
{
	points ((filled ? 'P' : 'p'),A,n);
}

static void	
xd_bezier(point *A, int n, int arrow_at_start, int arrow_at_end)
{
	points ('B',A,n);
}

static void
xd_polyline(point *A,int n)
{
	points ('L',A,n);
}

static Agraph_t*   cluster_g;
static attrsym_t*  g_draw;
static attrsym_t*  g_l_draw;

static void
xd_begin_cluster(Agraph_t* g)
{
	cluster_g = g;
}

static void
xd_end_cluster()
{
	agxset (cluster_g, g_draw->index, xbuse(&outbuf));
	agxset (cluster_g, g_l_draw->index, xbuse(&charbuf));
}

static void dummy() {}

codegen_t	XDot_CodeGen = {
	dummy,
	(BJ)dummy, dummy,
	(BG)dummy, dummy,
	(BP)dummy, dummy,
	xd_begin_cluster, xd_end_cluster,
	dummy, dummy,
	dummy, dummy,
	(BN)dummy, dummy,
	(BE)dummy, dummy,
	dummy, dummy,
	(SF)dummy, xd_textline,
	(SA)dummy, (SA)dummy, (SS)dummy,
	xd_ellipse, xd_polygon,
	xd_bezier, xd_polyline,
	0 /* xd_arrowhead */, 0 /* xd_user_shape*/ ,
	0 /* xd_comment */, 0 /* xd_textsize */
};

static int
isInvis (char* style)
{
	char**		styles = 0;
	char**		sp;
	char*		p;

	if (style[0]) {
		styles = parse_style(style);
		sp = styles;
	  	while ((p = *sp++)) {
			if (streq(p, "invis")) return 1;
		}
	}
	return 0;
}

/* 
 * John M. suggests:
 * You might want to add four more:
 *
 * _ohdraw_ (optional head-end arrow for edges)
 * _ohldraw_ (optional head-end label for edges)
 * _otdraw_ (optional tail-end arrow for edges)
 * _otldraw_ (optional tail-end label for edges)
 * 
 * that would be generated when an additional option is supplied to 
 * dot, etc. and 
 * these would be the arrow/label positions to use if a user want to flip the 
 * direction of an edge (as sometimes is there want).
 */
static void
extend_attrs (graph_t* g)
{
	int         i;
    bezier      bz;
    double      scale;
    double      theta;
	node_t*     n;
	edge_t*     e;
	attrsym_t*  n_draw = NULL;
	attrsym_t*  n_l_draw = NULL;
	attrsym_t*  e_draw = NULL;
	attrsym_t*  h_draw = NULL;
	attrsym_t*  t_draw = NULL;
	attrsym_t*  e_l_draw = NULL;
	attrsym_t*  hl_draw = NULL;
	attrsym_t*  tl_draw = NULL;
    unsigned char        buf[BUFSIZ];
    unsigned char        cbuf[BUFSIZ];

	if (GD_label(g)) 
   	  g_l_draw = safe_dcl(g,g,"_ldraw_","",agraphattr);
	if (GD_n_cluster(g))
    	g_draw = safe_dcl(g,g,"_draw_","",agraphattr);

	n_draw = safe_dcl(g,g->proto->n,"_draw_","",agnodeattr);
    n_l_draw = safe_dcl(g,g->proto->n,"_ldraw_","",agnodeattr);

	e_draw = safe_dcl(g,g->proto->e,"_draw_","",agedgeattr);
    if (e_arrows)
	  h_draw = safe_dcl(g,g->proto->e,"_hdraw_","",agedgeattr);
    if (s_arrows)
	  t_draw = safe_dcl(g,g->proto->e,"_tdraw_","",agedgeattr);
	if (GD_has_edge_labels(g) & EDGE_LABEL) 
      e_l_draw = safe_dcl(g,g->proto->e,"_ldraw_","",agedgeattr);
	if (GD_has_edge_labels(g) & HEAD_LABEL) 
      hl_draw = safe_dcl(g,g->proto->e,"_hldraw_","",agedgeattr);
	if (GD_has_edge_labels(g) & TAIL_LABEL) 
      tl_draw = safe_dcl(g,g->proto->e,"_tldraw_","",agedgeattr);

    xbinit(&outbuf, BUFSIZ, buf);
    xbinit(&charbuf, BUFSIZ, cbuf);
	for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
		if (ND_shape(n) && !isInvis(late_string(n,N_style,""))) {
			ND_shape(n)->codefn(n);
			agxset (n, n_draw->index, xbuse(&outbuf));
			agxset (n, n_l_draw->index, xbuse(&charbuf));
		}
		if (State < GVSPLINES) continue;
		for (e = agfstout(g,n); e; e = agnxtout(g,e)) {
			if (ED_edge_type(e) == IGNORED) continue;
    		if (isInvis(late_string(e,E_style,""))) continue;
			if (ED_spl(e) == NULL) continue;

			scale = late_double(e,E_arrowsz,1.0,0.0);
			for (i = 0; i < ED_spl(e)->size; i++) {
				bz = ED_spl(e)->list[i];
				xd_bezier(bz.list,bz.size,FALSE,FALSE);
			}
			agxset (e, e_draw->index, xbuse(&outbuf));
			for (i = 0; i < ED_spl(e)->size; i++) {
				if (bz.sflag) {
				/* dds: open style has sp == list[0] */
					if ((bz.sflag == ARR_OPEN) || (bz.sflag == ARR_HALFOPEN))
						theta = atan2pt(bz.list[1],bz.list[0]);
					else
						theta = atan2pt(bz.list[0],bz.sp);
					arrow_gen(bz.sp, DEGREES(theta), scale, bz.sflag);
					agxset (e, t_draw->index, xbuse(&outbuf));
				}
				if (bz.eflag) {
					/* dds: open style has ep == list[size-1] */
					if ((bz.eflag == ARR_OPEN) || (bz.eflag == ARR_HALFOPEN))
						theta = atan2pt(bz.list[bz.size-2],bz.list[bz.size-1]);
					else
						theta = atan2pt(bz.list[bz.size-1],bz.ep);
					arrow_gen(bz.ep, DEGREES(theta), scale, bz.eflag);
					agxset (e, h_draw->index, xbuse(&outbuf));
				}
			}
			if (ED_label(e)) {
				emit_label(ED_label(e),e->tail->graph);
				if (mapbool(late_string(e,E_decorate,"false")) && ED_spl(e)) {
					emit_attachment(ED_label(e),ED_spl(e));
					xbput (&charbuf, xbuse (&outbuf));
                }
				agxset (e, e_l_draw->index, xbuse(&charbuf));
			}
			if (ED_head_label(e)) {
				emit_label(ED_head_label(e),e->tail->graph);
				agxset (e, hl_draw->index, xbuse(&charbuf));
			}
			if (ED_tail_label(e)) {
				emit_label(ED_tail_label(e),e->tail->graph);
				agxset (e, tl_draw->index, xbuse(&charbuf));
			}
		}
	}
	if (GD_label(g)) {
		emit_label(GD_label(g),g);
		agxset (g, g_l_draw->index, xbuse(&charbuf));
    }
	emit_clusters (g,0);
	xbfree(&outbuf);
	xbfree(&charbuf);
}

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.