/*
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();
}
|