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


#define sqr(a) ((long) (a) * (a))
#define dstsq(a, b) (sqr (a.x - b.x) + sqr (a.y - b.y))
#define ldstsq(a, b) (sqr ((long) a.x - b.x) + sqr ((long) a.y - b.y))
#define dst(a, b) sqrt ((double) dstsq (a, b))
#define P2PF(p, pf) (pf.x = p.x, pf.y = p.y)
#define PF2P(pf, p) (p.x = ROUND (pf.x), p.y = ROUND (pf.y))

typedef struct arrowdir_s {
	char *dir;
	int sflag;
	int eflag;
} arrowdir_t;

static arrowdir_t Arrowdirs[] = {
	{"forward", ARR_NONE, ARR_NORM},
	{"back",    ARR_NORM, ARR_NONE},
	{"both",    ARR_NORM, ARR_NORM},
	{"none",    ARR_NONE, ARR_NONE},
	{(char *)0, 0,        0       }
};

typedef struct arrowname_s {
	char *name;
	int type;
	int len;
} arrowname_t;

static arrowname_t Arrownames[] = {
	{"none",     ARR_NONE,     0                                   },
	{"normal",   ARR_NORM,     ARROW_LENGTH                        },
	{"inv",      ARR_INV,      ARROW_INV_LENGTH                    },
	{"dot",      ARR_DOT,      ARROW_DOT_RADIUS*2                  },
	{"odot",     ARR_ODOT,     ARROW_DOT_RADIUS*2                  },
	{"invdot",   ARR_INVDOT,   ARROW_INV_LENGTH+ARROW_DOT_RADIUS*2 },
	{"invodot",  ARR_INVODOT,  ARROW_INV_LENGTH+ARROW_DOT_RADIUS*2 },
	{"tee",      ARR_TEE,      ARROW_LENGTH/2                      },
	{"empty",    ARR_EMPTY,    ARROW_LENGTH+1                      },
	{"invempty", ARR_INVEMPTY, ARROW_LENGTH                        },
	{"open",     ARR_OPEN,     0                                   },
	{"halfopen", ARR_HALFOPEN, 0                                   },
	{"diamond",  ARR_DIAMOND,  ARROW_LENGTH                        },
	{"odiamond", ARR_ODIAMOND, ARROW_LENGTH+1                      },
/* ediamond is for backwards compatibility. maps to ARR_ODIAMOND */
	{"ediamond", ARR_ODIAMOND, ARROW_LENGTH+1                      },
	{"box",      ARR_BOX,      ARROW_WIDTH                         },
	{"obox",     ARR_OBOX,     ARROW_WIDTH                         },
	{"crow",     ARR_CROW,     ARROW_LENGTH                        },
	{(char*)0,   0,            0                                   }
};

void arrow_flags (edge_t *e, int *sflag, int *eflag)
{
  char *attr;
  arrowdir_t *arrowdir;
  arrowname_t *arrowname;

  *sflag = ARR_NONE;
  *eflag = AG_IS_DIRECTED(e->tail->graph) ? ARR_NORM : ARR_NONE;
  if (E_dir && ((attr = agxget (e, E_dir->index)))[0]) {
    for (arrowdir = Arrowdirs; arrowdir->dir; arrowdir++) {
      if (streq(attr,arrowdir->dir)) {
        *sflag = arrowdir->sflag;
        *eflag = arrowdir->eflag;
        break;
      }
    }
  }
  if (E_arrowhead && ((attr = agxget (e, E_arrowhead->index)))[0]) {
    for (arrowname = Arrownames; arrowname->name; arrowname++) {
      if (strcmp(attr,arrowname->name) == 0) {
        *eflag = arrowname->type;
        break;
      }
    }
  }
  if (E_arrowtail && ((attr = agxget (e, E_arrowtail->index)))[0]) {
    for (arrowname = Arrownames; arrowname->name; arrowname++) {
      if (strcmp(attr,arrowname->name) == 0) {
        *sflag = arrowname->type;
        break;
      }
    }
  }
  if (ED_conc_opp_flag(e)) {
  	edge_t	*f;
	int		s0, e0;
	/* pick up arrowhead of opposing edge */
  	f = agfindedge(e->tail->graph,e->head,e->tail);
	arrow_flags (f, &s0, &e0);
	*eflag = *eflag | s0;
	*sflag = *sflag | e0;
  }
}

double arrow_length (edge_t* e, int flag)
{
  arrowname_t *arrowname;

  /* we don't simply index with flag because arr_type's are not consecutive */
  for (arrowname = Arrownames; arrowname->name; arrowname++) {
    if (flag==arrowname->type) {
      return arrowname->len * late_double(e,E_arrowsz,1.0,0.0);
      /* The original was missing the factor E_arrowsz, but I believe it
         should be here for correct arrow clipping */
    }
  }
  return 0;
}
/* end vladimir */


int arrowEndClip (Agedge_t* e, point* ps, int startp, int endp, bezier* spl, int eflag)
{
	pointf sp[4], sp2[4], pf;
	double d, t, elen, elen2;

	elen = arrow_length(e,eflag);
	elen2 = sqr(elen);
	spl->eflag = eflag, spl->ep = ps[endp + 3];
	if (endp > startp && ldstsq (ps[endp], ps[endp + 3]) < elen2) {
		endp -= 3;
	}
	P2PF (ps[endp], sp[3]);
	P2PF (ps[endp + 1], sp[2]);
	P2PF (ps[endp + 2], sp[1]);
	P2PF (ps[endp + 3], sp[0]);
	d = dst (sp[0], sp[1]) + dst (sp[1], sp[2]) + dst (sp[2], sp[3]);
    if (d > 0) {
    	if ((t = elen / d) > 1.0)
    		t = 1.0;
    	else if (t < 0.1)
    		t = 0.1;
    	for (;;) {
    		pf = Bezier (sp, 3, t, NULL, sp2);
    		if (ldstsq (pf, spl->ep) <= elen2)
    			break;
    		/*t *= (2.0/3.0);*/
    		t *= (9.0/10.0);
    	}
    	PF2P (sp2[3], ps[endp]);
    	PF2P (sp2[2], ps[endp + 1]);
    	PF2P (sp2[1], ps[endp + 2]);
    	PF2P (sp2[0], ps[endp + 3]);
    }

	return endp;
}

int arrowStartClip (Agedge_t* e, point* ps, int startp, int endp, bezier* spl, int sflag)
{
	pointf sp[4], sp2[4], pf;
	double d, t, slen, slen2;

	slen = arrow_length(e,sflag);
	slen2 = sqr(slen);
	spl->sflag = sflag, spl->sp = ps[startp];
 	if (endp > startp && ldstsq (ps[startp], ps[startp + 3]) < slen2) {
		startp += 3;
	}
	P2PF (ps[startp], sp[0]);
	P2PF (ps[startp + 1], sp[1]);
	P2PF (ps[startp + 2], sp[2]);
	P2PF (ps[startp + 3], sp[3]);
	d = dst (sp[0], sp[1]) + dst (sp[1], sp[2]) + dst (sp[2], sp[3]);
    if (d > 0) {
    	if ((t = slen / d) > 1.0)
    		t = 1.0;
    	else if (t < 0.1)
    		t = 0.1;
    	for (;;) {
    		pf = Bezier (sp, 3, t, NULL, sp2);
    		if (ldstsq (pf, spl->sp) <= slen2)
    			break;
    		/*t *= (2.0/3.0);*/
    		t *= (9.0/10.0);
    	}
    	PF2P (sp2[0], ps[startp]);
    	PF2P (sp2[1], ps[startp + 1]);
    	PF2P (sp2[2], ps[startp + 2]);
    	PF2P (sp2[3], ps[startp + 3]);
    }

	return startp;
}


void arrow_gen (point p, double theta, double scale, int flag)
/* 
 * Diomidis Spinellis, May 2002; added UML arrow types
 * Allowable are NORM | DOT | INV + DOT| TEE | OPEN | HALFOPEN | DIAMOND 
 * NORM, INV, DOT, DIAMOND can be or'ed with NOFILL giving 
 * EMPTY, INVEMPTY, ODOT, ODIAMOND, BOX, OBOX, CROW
 */
{
  point a[4];
  pointf u,v;
  int r;
  static char *arrowstyle_solid[2] = {"solid\0"};
  static char *arrowstyle_width[3] = {"setlinewidth\0""1\0"};
  theta = RADIANS(theta);

  /* Dotted and dashed styles on the arrowhead are ugly (dds) */
  /* linewidth needs to be reset */
  CodeGen->begin_context();
  CodeGen->set_style(arrowstyle_solid);
  CodeGen->set_style(arrowstyle_width);
  /* FIXME: does this have enough precision for ps? */
  if ((flag & ARR_NORM) || (flag & ARR_OPEN) || (flag & ARR_HALFOPEN)) {
    if (flag & ARR_HALFOPEN) {
      u.x = ARROW_INV_LENGTH*scale*cos(theta);
      u.y = ARROW_INV_LENGTH*scale*sin(theta);
      v.x = ARROW_INV_WIDTH/2.*scale*cos(theta+PI/2);
      v.y = ARROW_INV_WIDTH/2.*scale*sin(theta+PI/2);
    }
    else {
      u.x = ARROW_LENGTH*scale*cos(theta);
      u.y = ARROW_LENGTH*scale*sin(theta);
      v.x = ARROW_WIDTH/2.*scale*cos(theta+PI/2);
      v.y = ARROW_WIDTH/2.*scale*sin(theta+PI/2);
    }
    a[0].x = ROUND(p.x + u.x - v.x); a[0].y = ROUND(p.y + u.y - v.y);
    a[1] = p;
    a[2].x = ROUND(p.x + u.x + v.x); a[2].y = ROUND(p.y + u.y + v.y);
    if (flag & ARR_NORM) CodeGen->polygon(a,3,!(flag & ARR_NOFILL));
    else if (flag & ARR_OPEN) CodeGen->polyline(a,3);
    else if (flag & ARR_HALFOPEN) CodeGen->polyline(a,2);
    else assert(0);
    p.x = ROUND(u.x + p.x); p.y = ROUND(u.y + p.y);
  } else if (flag & ARR_TEE) {
    u.x = ARROW_LENGTH/2.*scale*cos(theta);
    u.y = ARROW_LENGTH/2.*scale*sin(theta);
    v.x = ARROW_WIDTH/2.*scale*cos(theta+PI/2);
    v.y = ARROW_WIDTH/2.*scale*sin(theta+PI/2);
    a[0].x = ROUND(p.x + u.x + v.x); a[0].y = ROUND(p.y + u.y + v.y);
    a[1].x = ROUND(p.x + u.x - v.x); a[1].y = ROUND(p.y + u.y - v.y);
    CodeGen->polyline(a,2);
    p.x = ROUND(u.x + p.x); p.y = ROUND(u.y + p.y);
  } else if (flag & ARR_DIAMOND) {
    u.x = ARROW_LENGTH/2.*scale*cos(theta);
    u.y = ARROW_LENGTH/2.*scale*sin(theta);
    v.x = ARROW_LENGTH/3.*scale*cos(theta+PI/2);
    v.y = ARROW_LENGTH/3.*scale*sin(theta+PI/2);
    a[0].x = ROUND(p.x+u.x+v.x); a[0].y = ROUND(p.y+u.y+v.y);
    a[1] = p;
    a[2].x = ROUND(p.x+u.x-v.x); a[2].y = ROUND(p.y+u.y-v.y);
    a[3].x = ROUND(p.x+2.*u.x); a[3].y = ROUND(p.y+2.*u.y);
    CodeGen->polygon(a,4,!(flag & ARR_NOFILL));
    p.x = ROUND(u.x + p.x); p.y = ROUND(u.y + p.y);
  } else if (flag & ARR_BOX) {
    u.x = ARROW_WIDTH*scale*cos(theta);
    u.y = ARROW_WIDTH*scale*sin(theta);
    v.x = ARROW_WIDTH/2.*scale*cos(theta+PI/2);
    v.y = ARROW_WIDTH/2.*scale*sin(theta+PI/2);
    a[0].x = ROUND(p.x+v.x);     a[0].y = ROUND(p.y+v.y);
    a[1].x = ROUND(p.x-v.x);     a[1].y = ROUND(p.y-v.y);
    a[2].x = a[1].x + ROUND(u.x); a[2].y = a[1].y + ROUND(u.y);
    a[3].x = a[0].x + ROUND(u.x); a[3].y = a[0].y + ROUND(u.y);
    CodeGen->polygon(a,4,!(flag & ARR_NOFILL));
    p.x = ROUND(u.x + p.x); p.y = ROUND(u.y + p.y);
  } else if (flag & ARR_INV) {
    u.x = ARROW_INV_LENGTH*scale*cos(theta);
    u.y = ARROW_INV_LENGTH*scale*sin(theta);
    v.x = ARROW_INV_WIDTH/2.*scale*cos(theta+PI/2);
    v.y = ARROW_INV_WIDTH/2.*scale*sin(theta+PI/2);
    a[0].x = ROUND(p.x + v.x); a[0].y = ROUND(p.y + v.y);
    a[1].x = ROUND(p.x + u.x); a[1].y = ROUND(p.y + u.y);
    a[2].x = ROUND(p.x - v.x); a[2].y = ROUND(p.y - v.y);
    /* dds: backwards compatibility: INV with DOT is always filled */
    CodeGen->polygon(a,3,!(flag&ARR_NOFILL)|(flag&ARR_DOT));
    p.x = ROUND(p.x + u.x); p.y = ROUND(p.y + u.y);
  } else if (flag & ARR_CROW) {
    u.x = ARROW_LENGTH*scale*cos(theta);
    u.y = ARROW_LENGTH*scale*sin(theta);
    v.x = ARROW_WIDTH*scale*cos(theta+PI/2);
    v.y = ARROW_WIDTH*scale*sin(theta+PI/2);
    a[0].x = ROUND(p.x + v.x);  a[0].y = ROUND(p.y + v.y);
    a[1].x = ROUND(p.x + u.x);  a[1].y = ROUND(p.y + u.y);
    a[2].x = ROUND(p.x - v.x);  a[2].y = ROUND(p.y - v.y);
    CodeGen->polyline(a,3);
    a[0] = p;
    CodeGen->polyline(a,2);
    p.x = ROUND(p.x + u.x); p.y = ROUND(p.y + u.y);
  }
  if (flag & ARR_DOT) {
    r = ROUND(ARROW_DOT_RADIUS*scale);
    p.x = p.x + ROUND(ARROW_DOT_RADIUS*scale*cos(theta));
    p.y = p.y + ROUND(ARROW_DOT_RADIUS*scale*sin(theta));
    CodeGen->ellipse(p,r,r, !(flag & ARR_NOFILL));
  }
  CodeGen->end_context();
}

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.