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