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

/*
 *  graphics code generator
 */

#include	"render.h"
#include	"xbuf.h"
#include	"utils.h"


int			Obj;
static int			N_pages = 1;	/* w.r.t. unrotated coords */
static int			Page;			/* w.r.t. unrotated coords */
static int			Layer,Nlayers;
static char			**LayerID;
static point		First,Major,Minor;
static point		Pages;
static box			PB;		/* drawable region in device coords */
static pointf		GP;		/* graph page size, in graph coords */
static box			CB;		/* current page box, in graph coords */
static point		PFC;	/* device page box for centering */
static double	    Deffontsize;
static char			*Deffontname;
static char			Layerdelims[] = ":\t ";
static attrsym_t*   G_peripheries;


static int write_edge_test(Agraph_t *g, Agedge_t *e) {
	Agraph_t	*subg;
	int			c;

	for(c = 1; c <= GD_n_cluster(g); c++) {
		subg = GD_clust(g)[c];
		if(agcontains(subg, e)) return FALSE;
	}
	return TRUE;
}


static int write_node_test(Agraph_t *g, Agnode_t *n) {
	Agraph_t	*subg;
	int			c;

	for(c = 1; c <= GD_n_cluster(g); c++) {
		subg = GD_clust(g)[c];
		if(agcontains(subg, n)) return FALSE;
	}
	return TRUE;
}


void emit_reset(Agraph_t* g)
{
	Agnode_t	*n;

	N_pages = 1;
	Page = 0;
	Layer = Nlayers = 0;
	LayerID = (char **) 0;
	First.x = First.y = 0;
	Major.x = Major.y = 0;
	Minor.x = Minor.y = 0;
	Pages.x = Pages.y = 0;
	PB.LL.x = PB.LL.y = PB.UR.x = PB.UR.y = 0;
	GP.x = GP.y = 0;
	CB.LL.x = CB.LL.y = CB.UR.x = CB.UR.y = 0;
	PFC.x = PFC.y = 0;
	Deffontsize = 0;
	Deffontname = (char *) 0;

	for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
		ND_state(n) = 0;
	}

	if(CodeGen) CodeGen->reset();
}

void emit_graph(graph_t* g, int flags)
{
	point	curpage;
	graph_t	*subg;
	node_t	*n;
	edge_t	*e;
	int	c;
	char	*str;

	G_peripheries =  agfindattr(g,"peripheries");
	emit_header(g);
	if (flags & EMIT_COLORS) {
		CodeGen->set_fillcolor(DEFAULT_FILL);
		if (((str = agget(g,"bgcolor")) != 0) && str[0])
			CodeGen->set_fillcolor(str);
		if (((str = agget(g,"fontcolor")) != 0) && str[0])
			CodeGen->set_pencolor(str);
		for (c = 1; c <= GD_n_cluster(g); c++) { 
	                subg = GD_clust(g)[c];
			if (((str = agget(subg,"color")) != 0) && str[0])
				CodeGen->set_pencolor(str);
			if (((str = agget(subg,"fillcolor")) != 0) && str[0])
				CodeGen->set_fillcolor(str);
			if (((str = agget(subg,"fontcolor")) != 0) && str[0])
				CodeGen->set_pencolor(str);
		}
		for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
			if (((str = agget(n,"color")) != 0) && str[0])
				CodeGen->set_pencolor(str);
			if (((str = agget(n,"fillcolor")) != 0) && str[0])
				CodeGen->set_fillcolor(str);
			if (((str = agget(n,"fontcolor")) != 0) && str[0])
				CodeGen->set_pencolor(str);
			for (e = agfstout(g,n); e; e = agnxtout(g,e)) {
				if (((str = agget(e,"color")) != 0) && str[0])
					CodeGen->set_pencolor(str);
				if (((str = agget(e,"fontcolor")) != 0) && str[0])
					CodeGen->set_pencolor(str);
			}
		}
	}
	Layer = 1;
	do {
		if (Nlayers > 0) emit_layer(Layer);
		for (curpage = First; validpage(curpage); curpage = pageincr(curpage)) {
			Obj = NONE;
			setup_page(g,curpage);
			if (GD_label(g)) emit_label(GD_label(g),g);
			Obj = CLST;
			/* when drawing, lay clusters down before nodes and edges */
			if (!(flags & EMIT_CLUSTERS_LAST)) {
				emit_clusters(g,flags);
			}
			if (flags & EMIT_SORTED) {
				/* output all nodes, then all edges */
				Obj = NODE;
				CodeGen->begin_nodes();
				for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
					emit_node(n);
				}
				CodeGen->end_nodes();
				Obj = EDGE;
				CodeGen->begin_edges();
				for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
					for (e = agfstout(g,n); e; e = agnxtout(g,e)) {
						emit_edge(e);
					}
				}
				CodeGen->end_edges();
			}
			else if (flags & EMIT_EDGE_SORTED) {
				/* output all edges, then all nodes */
				Obj = EDGE;
				CodeGen->begin_edges();
				for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
					for (e = agfstout(g,n); e; e = agnxtout(g,e)) {
						emit_edge(e);
					}
				}
				CodeGen->end_edges();
				Obj = NODE;
				CodeGen->begin_nodes();
				for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
					emit_node(n);
				}
				CodeGen->end_nodes();
			}
			else if(flags & EMIT_PREORDER) {
				Obj = NODE;
				CodeGen->begin_nodes();
				for(n = agfstnode(g); n; n = agnxtnode(g, n)) {
					if(write_node_test(g, n))
						emit_node(n);
				}
				CodeGen->end_nodes();
				Obj = EDGE;
				CodeGen->begin_edges();

				for(n = agfstnode(g); n; n = agnxtnode(g, n)) {
					for(e = agfstout(g, n); e; e = agnxtout(g, e)) {
						if(write_edge_test(g, e))
							emit_edge(e);
					}
				}
				CodeGen->end_edges();
			}
			else {
				/* output in breadth first graph walk order */
				for (n = agfstnode(g); n; n = agnxtnode(g,n)) {
					Obj = NODE;
					emit_node(n);
					for (e = agfstout(g,n); e; e = agnxtout(g,e)) {
						Obj = NODE;
						emit_node(e->head);
						Obj = EDGE;
						emit_edge(e);
					}
				}    
			}
			/* when mapping, detect events on clusters after nodes and edges */
			if (flags & EMIT_CLUSTERS_LAST) {
				emit_clusters(g,flags);
			}
			Obj = NONE;
			CodeGen->end_page();
		}
		Layer++;
	} while (Layer <= Nlayers);
	emit_trailer();
}

void emit_eof(void)
{
	if (Page > 0) CodeGen->end_job();
}

void emit_clusters(graph_t* g, int flags)
{
	int			i,c,filled;
	graph_t		*subg;
	point		A[4];
	char		*str,**style;
	node_t		*n;
	edge_t		*e;


	for (c = 1; c <= GD_n_cluster(g); c++) {
		subg = GD_clust(g)[c];
		if (clust_in_layer(subg) == FALSE) continue;
		/* when mapping, detect events on clusters after sub_clusters */
		if (flags & EMIT_CLUSTERS_LAST) {
			emit_clusters(subg,flags);
		}
		Obj = CLST;
		CodeGen->begin_cluster(subg);
		CodeGen->begin_context();
		filled = FALSE;
		if (((str = agget(subg,"style")) != 0) && str[0]) {
			CodeGen->set_style(style = parse_style(str));
			for (i = 0; style[i]; i++) 
				if (strcmp(style[i],"filled")==0) {filled = TRUE; break;}
		}
		if (((str = agget(subg,"pencolor")) != 0) && str[0])
			CodeGen->set_pencolor(str);
		else if (((str = agget(subg,"color")) != 0) && str[0])
			CodeGen->set_pencolor(str);
		/* bgcolor is supported for backward compatability */
		else if (((str = agget(subg,"bgcolor")) != 0) && str[0])
			CodeGen->set_pencolor(str);

		str = 0;
		if (((str = agget(subg,"fillcolor")) != 0) && str[0])
			CodeGen->set_fillcolor(str);
		else if (((str = agget(subg,"color")) != 0) && str[0])
			CodeGen->set_fillcolor(str);
		/* bgcolor is supported for backward compatability */
		else if (((str = agget(subg,"bgcolor")) != 0) && str[0]) {
			filled = TRUE;
			CodeGen->set_fillcolor(str);
		}
		A[0] = GD_bb(subg).LL;
		A[2] = GD_bb(subg).UR;
		A[1].x = A[2].x; A[1].y = A[0].y;
		A[3].x = A[0].x; A[3].y = A[2].y;
		if (late_int(subg,G_peripheries,1,0)) {
			CodeGen->polygon(A,4,filled);
		}
		else if (filled) {
			CodeGen->set_pencolor(str);
			CodeGen->polygon(A,4,filled);
        }
		if (GD_label(subg)) emit_label(GD_label(subg),subg);

		if(flags & EMIT_PREORDER) {
			for(n = agfstnode(subg); n; n = agnxtnode(subg, n)) {
				Obj = NODE;
				emit_node(n);
				for(e = agfstout(subg, n); e; e = agnxtout(subg, e)) {
					Obj = EDGE;
					emit_edge(e);
				}
			}
			Obj = NONE;
		}

		CodeGen->end_context();
		CodeGen->end_cluster();
		/* when drawing, lay down clusters before sub_clusters */
		if (!(flags & EMIT_CLUSTERS_LAST)) {
			emit_clusters(subg,flags);
		}
	}
}

void emit_node(node_t* n)
{
	if (ND_shape(n) == NULL) return;
	if (node_in_layer(n->graph,n) && node_in_CB(n) && (ND_state(n) != Page)) {
		ND_shape(n)->codefn(n);
		ND_state(n) = Page;
	}
}

void emit_edge(edge_t* e)
{
	int		i;
	char	*color,*style;
	char	**styles = 0;
	char	**sp;
	bezier	bz;
	boolean	saved = FALSE;
	double	scale;
	double theta;
	char	*p;

	if ((edge_in_CB(e) == FALSE) || (edge_in_layer(e->head->graph,e) == FALSE))
		return;

	CodeGen->begin_edge(e);
	style = late_string(e,E_style,"");
		/* We shortcircuit drawing an invisible edge because the arrowhead
         * code resets the style to solid, and most of the code generators
		 * (except PostScript) won't honor a previous style of invis.
         */
	if (style[0]) {
      styles = parse_style(style);
      sp = styles;
	  while ((p = *sp++)) {
		if (streq(p, "invis")) {
			CodeGen->end_edge();
			return;
        }
      }
    }
	color = late_string(e,E_color,"");
	scale = late_double(e,E_arrowsz,1.0,0.0);
	if (color[0] || styles) {
		CodeGen->begin_context();
		if (styles) CodeGen->set_style(styles);
		if (color[0]) {
			CodeGen->set_pencolor(color);
			CodeGen->set_fillcolor(color);
		}
		saved = TRUE;
	}
	if (ED_spl(e)) {
		for (i = 0; i < ED_spl(e)->size; i++) {
			bz = ED_spl(e)->list[i];
			if (NOT(codegen_bezier_has_arrows())) {
				CodeGen->beziercurve(bz.list,bz.size,FALSE,FALSE);
				/* CodeGen->beziercurve(bz.list,bz.size,bz.sflag,bz.eflag); */
                /* vladimir: added sflag/eflag, arrowheads are made here */
				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);
				}
				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);
				}
			} else 
				CodeGen->beziercurve(bz.list,bz.size,bz.sflag,bz.eflag);
		}
	}
	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));
	}
	if (ED_head_label(e)) emit_label(ED_head_label(e),e->tail->graph); /* vladimir */
	if (ED_tail_label(e)) emit_label(ED_tail_label(e),e->tail->graph); /* vladimir */

	if (saved) CodeGen->end_context();
	CodeGen->end_edge();
}

int node_in_CB(node_t* n)
{
	box	nb;

	if (N_pages == 1) return TRUE;
	nb.LL.x = ND_coord_i(n).x - ND_lw_i(n);
	nb.LL.y = ND_coord_i(n).y - ND_ht_i(n)/2;
	nb.UR.x = ND_coord_i(n).x + ND_rw_i(n);
	nb.UR.y = ND_coord_i(n).y + ND_ht_i(n)/2;
	return rect_overlap(CB,nb);
}

int node_in_layer(graph_t* g, node_t* n)
{
	char	*pn,*pe;
	edge_t	*e;

	if (Nlayers <= 0) return TRUE;
	pn = late_string(n,N_layer,"");
	if (selectedlayer(pn)) return TRUE;
	if (pn[0]) return FALSE;  /* Only check edges if pn = "" */
	if ((e = agfstedge(g,n)) == NULL) return TRUE;
	for (e = agfstedge(g,n); e; e = agnxtedge(g,e,n)) {
		pe = late_string(e,E_layer,"");
		if ((pe[0] == '\0') || selectedlayer(pe)) return TRUE;
	}
	return FALSE;
}

int edge_in_layer(graph_t* g, edge_t* e)
{
	char	*pe,*pn;
	int		cnt;

	if (Nlayers <= 0) return TRUE;
	pe = late_string(e,E_layer,"");
	if (selectedlayer(pe)) return TRUE;
	if (pe[0]) return FALSE;
	for (cnt = 0; cnt < 2; cnt++) {
		pn = late_string(cnt < 1? e->tail:e->head,N_layer,"");
		if ((pn[0] == '\0') || selectedlayer(pn)) return TRUE;
	}
	return FALSE;
}

int clust_in_layer(graph_t* subg)
{
	char		*pg;
	node_t		*n;

	if (Nlayers <= 0) return TRUE;
	pg = late_string(subg,agfindattr(subg,"layer"),"");
	if (selectedlayer(pg)) return TRUE;
	if (pg[0]) return FALSE;
	for (n = agfstnode(subg); n; n = agnxtnode(subg,n))
		if (node_in_layer(subg,n)) return TRUE;
	return FALSE;
}

int edge_in_CB(edge_t* e)
{
	int		i,j,np;
	bezier	bz;
	point	*p,pp,sz;
	box		b;
	textlabel_t	*lp;

	if (N_pages == 1) return TRUE;
	if (ED_spl(e) == NULL) return FALSE;
	for (i = 0; i < ED_spl(e)->size; i++) {
		bz = ED_spl(e)->list[i];
		np = bz.size;
		p = bz.list;
		pp = p[0];
		for (j = 0; j < np; j++) {
			if (rect_overlap(CB,mkbox(pp,p[j]))) return TRUE;
			pp = p[j];
		}
	}
	if ((lp = ED_label(e)) == NULL) return FALSE;
	sz = cvt2pt(lp->dimen);
	b.LL.x = lp->p.x - sz.x / 2; b.UR.x = lp->p.x + sz.x / 2;
	b.LL.y = lp->p.y - sz.y / 2; b.UR.y = lp->p.y + sz.y / 2;
	return rect_overlap(CB,b);
}

void emit_attachment(textlabel_t* lp, splines* spl)
{
	point	sz,A[3];
	char	*s;

	for (s = lp->text; *s; s++) if (isspace(*s) == FALSE) break;
	if (*s == 0) return;

	sz = cvt2pt(lp->dimen);
	A[0] = pointof(lp->p.x + sz.x/2,lp->p.y - sz.y/2);
	A[1] = pointof(A[0].x - sz.x, A[0].y);
	A[2] = dotneato_closest(spl,lp->p);
	CodeGen->polyline(A,3);
}

void emit_header(graph_t* g)
{
	char	*user;

	setup_graph(g);
	if (Page == 0) {
		user = username();
		CodeGen->begin_job(Output_file,g,Lib,user,Info,Pages);
	}
	CodeGen->begin_graph(g,PB,PFC);
}

void emit_trailer(void)
{
	CodeGen->end_graph();
}

point pagecode(char c)
{
	point		rv;
	rv.x = rv.y = 0;
	switch(c) {
		case 'T': First.y = Pages.y - 1; rv.y = -1; break;
		case 'B': rv.y = 1; break;
		case 'L': rv.x = 1; break;
		case 'R': First.x = Pages.x - 1; rv.x = -1; break;
	}
	return rv;
}

static void set_pagedir(graph_t* g)
{
	char	*str;

	Major.x = Major.y = Minor.x = Minor.y = 0;
	str = agget(g,"pagedir");
	if (str && str[0]) {
		Major = pagecode(str[0]);
		Minor = pagecode(str[1]);
	}
	if ((abs(Major.x + Minor.x) != 1) || (abs(Major.y + Minor.y) != 1)) {
		Major.x = 0; Major.y = 1; Minor.x = 1; Minor.y = 0;
		First.x = First.y = 0;
		if (str) agerr (AGWARN, "pagedir=%s ignored\n",str);
	}
}

int validpage(point page)
{
	return ((page.x >= 0) && (page.x < Pages.x) 
		&& (page.y >= 0) && (page.y < Pages.y));
}

int layerindex(char* tok)
{
	int		i;

	for (i = 1; i <= Nlayers; i++) 
		if (streq(tok,LayerID[i])) return i;
	return -1;
}

int is_natural_number(char* str)
{
	while (*str) if (NOT(isdigit(*str++))) return FALSE;
	return TRUE;
}

int layer_index(char* str, int all)
{
	int		i;

	if (streq(str,"all")) return all;
	if (is_natural_number(str)) return atoi(str);
	if (LayerID)
		for (i = 1; i <= Nlayers; i++)
			if (streq(str,LayerID[i])) return i;
	return -1;
}

int selectedlayer(char* spec)
{
	int		n0,n1;
	unsigned char	buf[SMALLBUF];
	char	*w0, *w1;
    xbuf    xb;
    int     rval = FALSE;

    xbinit (&xb, SMALLBUF, buf);
	xbput(&xb,spec);
	w1 = w0 = strtok(xbuse(&xb),Layerdelims);
	if (w0) w1 = strtok(NULL,Layerdelims);
	switch((w0 != NULL) + (w1 != NULL)) {
	case 0: 
      rval = FALSE;
      break;
	case 1: 
      n0 = layer_index(w0,Layer);
	  rval = (n0 == Layer);
      break;
	case 2: 
      n0 = layer_index(w0,0);  
      n1 = layer_index(w1,Nlayers);
	  if ((n0 < 0) || (n1 < 0)) rval = TRUE;
	  else if (n0 > n1) {
        int t = n0; 
        n0 = n1; 
        n1 = t;}
		rval = BETWEEN(n0,Layer,n1);
      break;
	}
    xbfree (&xb);
	return rval;
}

point
pageincr(point page)
{
	page = add_points(page,Minor);
	if (validpage(page) == FALSE) {
		if (Major.y) page.x = First.x;
		else page.y = First.y;
		page = add_points(page,Major);
	}
	return page;
}

static point exch_xy(point p)
{
	int		t;
	t = p.x; p.x = p.y; p.y = t;
	return p;
}

static pointf exch_xyf(pointf p)
{
	double		t;
	t = p.x; p.x = p.y; p.y = t;
	return p;
}

/* this isn't a pretty sight... */
void setup_graph(graph_t* g)
{
	double		xscale,yscale,scale;
	char		*p;
	point		PFCLM;	/* page for centering less margins */
	point		DS;		/* device drawable region for a page of the graph */

	assert((GD_bb(g).LL.x == 0) && (GD_bb(g).LL.y == 0));

	if (LayerID) free(LayerID);
	if ((p = agget(g,"layers")) != 0) Nlayers = parse_layers(p);
	else {LayerID = NULL; Nlayers = 0;}

	/* determine final drawing size and scale to apply. */
	/* N.B. magnification could be allowed someday in the next conditional */
	/* N.B. size given by user is not rotated by landscape mode */
	if ((GD_drawing(g)->size.x > 0)	/* was given by user... */
		&& ((GD_drawing(g)->size.x < GD_bb(g).UR.x) /* drawing is too big... */
			|| (GD_drawing(g)->size.y < GD_bb(g).UR.y))) {
		xscale = ((double)GD_drawing(g)->size.x) / GD_bb(g).UR.x;
		yscale = ((double)GD_drawing(g)->size.y) / GD_bb(g).UR.y;
		scale = MIN(xscale,yscale);
		GD_drawing(g)->scale = scale;
		GD_drawing(g)->size.x = scale * GD_bb(g).UR.x;
		GD_drawing(g)->size.y = scale * GD_bb(g).UR.y;
	}
	else {	/* go with "natural" size of layout */
		GD_drawing(g)->size = GD_bb(g).UR;
		scale = GD_drawing(g)->scale = 1.0;
	}

	/* determine pagination */
	PB.LL = GD_drawing(g)->margin;
	if ((GD_drawing(g)->page.x > 0) && (GD_drawing(g)->page.y > 0)) {
			/* page was set by user */
		point	tp;
		PFC = GD_drawing(g)->page;
		PFCLM.x = PFC.x - 2*PB.LL.x; PFCLM.y = PFC.y - 2*PB.LL.y;
		GP.x = PFCLM.x ; GP.y = PFCLM.y;	/* convert to double */
		if (GD_drawing(g)->landscape) GP = exch_xyf(GP);
		GP.x = GP.x / scale; GP.y = GP.y / scale;
			/* we don't want graph page to exceed its bounding box */
		GP.x = MIN(GP.x,GD_bb(g).UR.x); GP.y = MIN(GP.y,GD_bb(g).UR.y);
		Pages.x = (GP.x > 0) ? ceil( ((double)GD_bb(g).UR.x) / GP.x) : 1;
		Pages.y = (GP.y > 0) ? ceil( ((double)GD_bb(g).UR.y) / GP.y) : 1;
		N_pages = Pages.x * Pages.y;

			/* find the drawable size in device coords */
		tp = GD_drawing(g)->size;
		if (GD_drawing(g)->landscape) tp = exch_xy(tp);
		DS.x = MIN(tp.x,PFCLM.x);
		DS.y = MIN(tp.y,PFCLM.y);
	}
	else {
			/* page not set by user, assume default when centering,
				but allow infinite page for any other interpretation */
		GP.x = GD_bb(g).UR.x; GP.y = GD_bb(g).UR.y;
		PFC.x = DEFAULT_PAGEWD; PFC.y = DEFAULT_PAGEHT;
		PFCLM.x = PFC.x - 2*PB.LL.x; PFCLM.y = PFC.y - 2*PB.LL.y;
		DS = GD_drawing(g)->size;
		if (GD_drawing(g)->landscape) DS = exch_xy(DS);
		Pages.x = Pages.y = N_pages = 1;
	}

	set_pagedir(g);
	/* determine page box including centering */
	if (GD_drawing(g)->centered) {
		point	extra;
		if ((extra.x = PFCLM.x - DS.x) < 0) extra.x = 0;
		if ((extra.y = PFCLM.y - DS.y) < 0) extra.y = 0;
		PB.LL.x += extra.x / 2; PB.LL.y += extra.y / 2;
	}
	PB.UR = add_points(PB.LL,DS);
	Deffontname = late_nnstring(g->proto->n,N_fontname,DEFAULT_FONTNAME);
	Deffontsize = late_double(g->proto->n,N_fontsize,DEFAULT_FONTSIZE,MIN_FONTSIZE);
}

void emit_background(graph_t* g, point LL, point UR)
{
	char	*str;
	point	A[4];

	if (((str = agget(g,"bgcolor")) != 0) && str[0]) {
		A[0].x = A[1].x = LL.x - GD_drawing(g)->margin.x;
		A[2].x = A[3].x = UR.x + GD_drawing(g)->margin.x;
		A[1].y = A[2].y = UR.y + GD_drawing(g)->margin.y;
		A[3].y = A[0].y = LL.y - GD_drawing(g)->margin.y;
		CodeGen->set_fillcolor(str);
		CodeGen->set_pencolor(str);
		CodeGen->polygon(A,4,TRUE);	/* filled */
	}
}

/* even if this makes you cringe, at least it's short */
void setup_page(graph_t* g, point page)
{
	point		offset;
	int			theta;

	Page++;
	theta = (GD_drawing(g)->landscape ? 90 : 0);

	/* establish current box in graph coordinates */
	CB.LL.x = page.x  * GP.x;	CB.LL.y = page.y * GP.y;
	CB.UR.x = CB.LL.x + GP.x;	CB.UR.y = CB.LL.y + GP.y;

	/* establish offset to be applied, in graph coordinates */
	if (GD_drawing(g)->landscape == FALSE) offset = pointof(-CB.LL.x,-CB.LL.y);
	else { offset.x = (page.y + 1) * GP.y; offset.y = -page.x * GP.x; }
	CodeGen->begin_page(g,page,GD_drawing(g)->scale,theta,offset);
	emit_background(g, CB.LL, CB.UR);
	emit_defaults(g);
}

void emit_label(textlabel_t* lp, Agraph_t *g)
{
	int	i, linespacing, left_x, center_x, right_x, width_x;
	point	p;

	/* make sure that there is something to do */
	if (lp->nlines < 1) return;

	g = g->root;

	/* dimensions of box for label */
	width_x = ROUND(POINTS(lp->dimen.x));
	center_x = lp->p.x;
	left_x = center_x - width_x / 2;
	right_x = center_x + width_x / 2;

	/* set linespacing */
	linespacing = (int)(lp->fontsize * LINESPACING);

	/* position for first line */
	p.y = lp->p.y
		+ (linespacing * (lp->nlines -1) / 2)	/* cl of topline */
	       	- lp->fontsize / 3.0 ; /* cl to baseline */

	CodeGen->begin_context();
	CodeGen->set_pencolor(lp->fontcolor);
	CodeGen->set_font(lp->fontname,lp->fontsize*GD_drawing(g)->font_scale_adj);

	for (i = 0; i < lp->nlines; i++) {
		switch(lp->line[i].just) {
			case 'l':
				p.x = left_x;
				break;
			case 'r':
				p.x = right_x;
				break;
			default:
			case 'n':
				p.x = center_x;
				break;
		}
		CodeGen->textline(p,&(lp->line[i]));

		/* position for next line */
		p.y -= linespacing;
	}

	CodeGen->end_context();
}

void emit_defaults(graph_t* g)
{
	CodeGen->set_pencolor(DEFAULT_COLOR);
	CodeGen->set_fillcolor(DEFAULT_COLOR);
	CodeGen->set_font(Deffontname,Deffontsize);
}

int parse_layers(char* p)
{
	int		ntok,c;
	char	*pcopy,*tok;

	ntok = strccnt(p,':')+1;
	pcopy = strdup(p);
	if (LayerID) free(LayerID);
	LayerID = N_NEW(ntok+2,char*);
	c = 1;
	for (tok = strtok(pcopy,Layerdelims); tok; tok = strtok(NULL,Layerdelims)) 
		LayerID[c++] = tok;
	return ntok;
}

void emit_layer(int n)
{
	char	buf[BUFSIZ],*fake[2];
	char	null = 0;

	if (LayerID) {
		/* create compatible char array structure for set_style call */
		sprintf(buf,"setlayer%c%d%c%d%c",null,n,null,Nlayers,null);
		fake[0] = buf;
		fake[1] = NULL;
		CodeGen->set_style(fake);
	}
}

static int style_delim(int c)
{
	switch (c) {
		case '(': case ')': case ',': case '\0': return TRUE;
		default : return FALSE;
	}
}

#define SID 1

static int
style_token(char** s, xbuf *xb)
{
    char* p = *s;
    int token;
    char  c;

	while (*p && (isspace(*p) || (*p ==','))) p++;
	switch (*p) {
	case '\0': token = 0; break;
	case '(': case ')': token = *p++; break;
	default: 
        token = SID;
        while (!style_delim(c = *p)) {
          xbputc(xb,c);
          p++;
        }
	}
    *s = p;
	return token;
}

#define FUNLIMIT 64
static unsigned char outbuf[SMALLBUF];
static xbuf ps_xb;

static void
cleanup()
{
  xbfree(&ps_xb);
}

char **
parse_style(char* s)
{
	static char*   parse[FUNLIMIT];
    static int     first = 1;
	int			   fun = 0;
	boolean		   in_parens = FALSE;
	unsigned char  buf[SMALLBUF];
    char*          p;
    int            c;
    xbuf           xb;

    if (first) {
      xbinit (&ps_xb, SMALLBUF, outbuf);
      atexit(cleanup);
      first = 0;
    }

    xbinit (&xb, SMALLBUF, buf);
	p = s;
	while ((c = style_token(&p,&xb)) != 0) {
		switch (c) {
		case '(':
			if (in_parens) {
				agerr(AGERR, "nesting not allowed in style: %s\n",s);
				parse[0] = (char*)0;
                xbfree (&xb);
				return parse;
			}
			in_parens = TRUE;
			break;

		case ')':
			if (in_parens == FALSE) {
				agerr(AGERR, "unmatched ')' in style: %s\n",s);
				parse[0] = (char*)0;
                xbfree (&xb);
				return parse;
			}
			in_parens = FALSE; 
			break;

		default:
			if (in_parens == FALSE) {
                if (fun == FUNLIMIT-1) {
                    agerr(AGWARN, "truncating style '%s'\n", s);
	                parse[fun] = (char*)0;
                    xbfree (&xb);
                    return parse;
                }
                xbputc(&ps_xb, '\0'); /* terminate previous */
				parse[fun++] = xbnext(&ps_xb);
			}
			xbput (&ps_xb, xbuse(&xb));
            xbputc(&ps_xb, '\0');
		}
	}

	if (in_parens) {
		agerr(AGERR, "unmatched '(' in style: %s\n",s);
		parse[0] = (char*)0;
        xbfree (&xb);
		return parse;
	}
	parse[fun] = (char*)0;
    xbfree (&xb);
    xbuse (&ps_xb);  /* adds final '\0' to buffer */
	return parse;
}

static struct cg_s {
        codegen_t   *cg;
        char        *name;
        int         id;
    }
    gens[] = {
        {&PS_CodeGen,"ps",POSTSCRIPT},
        {&PS_CodeGen,"ps2",PDF},
        {&HPGL_CodeGen,"hpgl",HPGL},
        {&HPGL_CodeGen,"pcl",PCL},
        {&MIF_CodeGen,"mif",MIF},
        {&PIC_CodeGen,"pic",PIC_format},
        {&GD_CodeGen,"gd",GD},
#ifdef HAVE_LIBZ
        {&GD_CodeGen,"gd2",GD2},
#endif
#ifdef WITH_GIF
        {&GD_CodeGen,"gif",GIF},
#endif
#ifdef HAVE_LIBJPEG
        {&GD_CodeGen,"jpg",JPEG},
        {&GD_CodeGen,"jpeg",JPEG},
#endif
#ifdef HAVE_LIBPNG
#ifdef HAVE_LIBZ
        {&GD_CodeGen,"png",PNG},
#endif
#endif
        {&GD_CodeGen,"wbmp",WBMP},
#ifdef HAVE_LIBXPM
        {&GD_CodeGen,"xbm",XBM},
#endif
        {&ISMAP_CodeGen,"ismap",ISMAP},
        {&IMAP_CodeGen,"imap",IMAP},
        {&CMAP_CodeGen,"cmap",CMAP},
#ifdef HAVE_LIBPNG
        {&VRML_CodeGen,"vrml",VRML},
#endif
        {&VTX_CodeGen,"vtx",VTX},
        {&MP_CodeGen,"mp",METAPOST},
        {&FIG_CodeGen,"fig",FIG},
        {&SVG_CodeGen,"svg",SVG},
#ifdef HAVE_LIBZ
        {&SVG_CodeGen,"svgz",SVGZ},
        {&DIA_CodeGen,"dia",DIA},
#endif
        {(codegen_t*)0,"dot",ATTRIBUTED_DOT},
        {(codegen_t*)0,"canon",CANONICAL_DOT},
        {(codegen_t*)0,"plain",PLAIN},
        {(codegen_t*)0,"plain-ext",PLAIN_EXT},
        {&XDot_CodeGen,"xdot",EXTENDED_DOT},
        {(codegen_t*)0,(char*)0,0}
    };

int lang_select(char* str, int warn)
{
    struct  cg_s *p;
    for (p = gens; p->name; p++) {
        if (strcasecmp(str,p->name) == 0) {
            CodeGen = p->cg;
            return p->id;
        }
    }
	if (warn) {
    	agerr(AGWARN, "language %s not recognized, use one of:\n",str);
    	for (p = gens; p->name; p++) agerr(AGPREV, " %s",p->name);
    	agerr(AGPREV, "\n");
	}
    return ATTRIBUTED_DOT;
}

FILE *
file_select(char* str)
{
    FILE    *rv;
    rv = fopen(str,"wb");
    if (rv == NULL) { perror(str); exit(1); }
    return rv;
}

void use_library(char* name)
{
    static int  cnt = 0;
    if (name) {
        Lib = ALLOC(cnt+2,Lib,char*);
        Lib[cnt++] = name;
        Lib[cnt] = NULL;
    }
}

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.