/* 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" #include "xbuf.h" #include "utils.h" #ifndef MSWIN32 #include #endif /* local funcs */ static double dist(pointf, pointf); void *zmalloc(size_t nbytes) { char *rv = malloc(nbytes); if (nbytes == 0) return 0; if (rv == NULL) {fprintf(stderr, "out of memory\n"); abort();} memset(rv,0,nbytes); return rv; } void *zrealloc(void *ptr, size_t size, size_t elt, size_t osize) { void *p = realloc(ptr,size*elt); if (p == NULL) {fprintf(stderr, "out of memory\n"); abort();} if (osize < size) memset((char*)p+(osize*elt),'\0',(size-osize)*elt); return p; } void *gmalloc(size_t nbytes) { char *rv = malloc(nbytes); if (nbytes == 0) return 0; if (rv == NULL) {fprintf(stderr, "out of memory\n"); abort();} return rv; } void *grealloc(void *ptr, size_t size) { void *p = realloc(ptr,size); if (p == NULL) {fprintf(stderr, "out of memory\n"); abort();} return p; } /* * a queue of nodes */ queue * new_queue(int sz) { queue *q = NEW(queue); if (sz <= 1) sz = 2; q->head = q->tail = q->store = N_NEW(sz,node_t*); q->limit = q->store + sz; return q; } void free_queue(queue* q) { free(q->store); free(q); } void enqueue(queue* q, node_t* n) { *(q->tail++) = n; if (q->tail >= q->limit) q->tail = q->store; } node_t * dequeue(queue* q) { node_t *n; if (q->head == q->tail) n = NULL; else { n = *(q->head++); if (q->head >= q->limit) q->head = q->store; } return n; } /* returns index of an attribute if bound, else -1 */ int late_attr(void* obj, char* name) { attrsym_t *a; if ((a = agfindattr(obj,name)) != 0) return a->index; else return -1; } int late_int(void *obj, attrsym_t *attr, int def, int low) { char *p; int rv; if (attr == NULL) return def; p = agxget(obj,attr->index); if (p[0] == '\0') return def; if ((rv = atoi(p)) < low) rv = low; return rv; } double late_double(void *obj,attrsym_t *attr, double def, double low) { char *p; double rv; if (attr == NULL) return def; p = agxget(obj,attr->index); if (p[0] == '\0') return def; if ((rv = atof(p)) < low) rv = low; return rv; } char * late_string(void* obj, attrsym_t* attr, char* def) { if (attr == NULL) return def; return agxget(obj,attr->index); } char * late_nnstring(void* obj, attrsym_t* attr, char* def) { char *rv = late_string(obj,attr,def); if (rv[0] == '\0') rv = def; return rv; } int late_bool(void *obj, attrsym_t *attr, int def) { if (attr == NULL) return def; return mapbool(agxget(obj,attr->index)); } /* counts occurences of 'c' in string 'p' */ int strccnt(char *p, char c) { int rv = 0; while (*p) if (*p++ == c) rv++; return rv; } /* union-find */ node_t * UF_find(node_t* n) { while (ND_UF_parent(n) && (ND_UF_parent(n) != n)) { if (ND_UF_parent(n)->u.UF_parent) ND_UF_parent(n) = ND_UF_parent(n)->u.UF_parent; n = ND_UF_parent(n); } return n; } node_t * UF_union(node_t *u, node_t *v) { if (u == v) return u; if (ND_UF_parent(u) == NULL) {ND_UF_parent(u) = u; ND_UF_size(u) = 1;} else u = UF_find(u); if (ND_UF_parent(v) == NULL) {ND_UF_parent(v) = v; ND_UF_size(v) = 1;} else v = UF_find(v); if (u->id > v->id) { ND_UF_parent(u) = v; ND_UF_size(v) += ND_UF_size(u);} else {ND_UF_parent(v) = u; ND_UF_size(u) += ND_UF_size(v); v = u;} return v; } void UF_remove(node_t *u, node_t *v) { assert(ND_UF_size(u) == 1); ND_UF_parent(u) = u; ND_UF_size(v) -= ND_UF_size(u); } void UF_singleton(node_t* u) { ND_UF_size(u) = 1; ND_UF_parent(u) = NULL; ND_ranktype(u) = NORMAL; } void UF_setname(node_t *u, node_t *v) { assert(u == UF_find(u)); ND_UF_parent(u) = v; ND_UF_size(v) += ND_UF_size(u); } point coord(node_t *n) { pointf pf; pf.x = ND_pos(n)[0]; pf.y = ND_pos(n)[1]; return cvt2pt(pf); } point pointof(int x, int y) { point rv; rv.x = x, rv.y = y; return rv; } point cvt2pt(pointf p) { point rv; rv.x = POINTS(p.x); rv.y = POINTS(p.y); return rv; } pointf cvt2ptf(point p) { pointf rv; rv.x = PS2INCH(p.x); rv.y = PS2INCH(p.y); return rv; } box boxof (int llx, int lly, int urx, int ury) { box b; b.LL.x = llx, b.LL.y = lly; b.UR.x = urx, b.UR.y = ury; return b; } box mkbox(point p0, point p1) { box rv; if (p0.x < p1.x) { rv.LL.x = p0.x; rv.UR.x = p1.x; } else { rv.LL.x = p1.x; rv.UR.x = p0.x; } if (p0.y < p1.y) { rv.LL.y = p0.y; rv.UR.y = p1.y; } else { rv.LL.y = p1.y; rv.UR.y = p0.y; } return rv; } point add_points(point p0, point p1) { p0.x += p1.x; p0.y += p1.y; return p0; } point sub_points(point p0, point p1) { p0.x -= p1.x; p0.y -= p1.y; return p0; } double atan2pt(point p1, point p0) { return atan2((double)(p1.y - p0.y),(double)(p1.x - p0.x)); } /* from Glassner's Graphics Gems */ #define W_DEGREE 5 /* * Bezier : * Evaluate a Bezier curve at a particular parameter value * Fill in control points for resulting sub-curves if "Left" and * "Right" are non-null. * */ pointf Bezier (pointf *V, int degree, double t, pointf* Left, pointf* Right) { int i, j; /* Index variables */ pointf Vtemp[W_DEGREE + 1][W_DEGREE + 1]; /* Copy control points */ for (j =0; j <= degree; j++) { Vtemp[0][j] = V[j]; } /* Triangle computation */ for (i = 1; i <= degree; i++) { for (j =0 ; j <= degree - i; j++) { Vtemp[i][j].x = (1.0 - t) * Vtemp[i-1][j].x + t * Vtemp[i-1][j+1].x; Vtemp[i][j].y = (1.0 - t) * Vtemp[i-1][j].y + t * Vtemp[i-1][j+1].y; } } if (Left != NULL) for (j = 0; j <= degree; j++) Left[j] = Vtemp[j][0]; if (Right != NULL) for (j = 0; j <= degree; j++) Right[j] = Vtemp[degree-j][j]; return (Vtemp[degree][0]); } char *strdup_and_subst_graph(char *str, Agraph_t *g) { char c, *s, *p, *t, *newstr; char *g_str=NULL; int g_len=0, newlen=0; /* two passes over str. * * first pass prepares substitution strings and computes * total length for newstring required from malloc. */ for (s=str;(c=*s++);) { if (c=='\\') { switch (c=*s++) { case 'G': if (! g_str) { g_str = g->name; g_len = strlen(g_str); } newlen += g_len; break; default: newlen +=2; } } else { newlen++; } } /* allocate new string */ newstr=gmalloc(newlen+1); /* second pass over str assembles new string */ for (s=str,p=newstr;(c=*s++);) { if (c=='\\') { switch (c=*s++) { case 'G': for (t=g_str;(*p=*t++);p++); break; default: *p++ = '\\'; *p++ = c; } } else { *p++ = c; } } *p++ = '\0'; return newstr; } char *strdup_and_subst_node(char *str, Agnode_t *n) { char c, *s, *p, *t, *newstr; char *g_str=NULL, *n_str=NULL; int g_len=0, n_len=0, newlen=0; /* two passes over str. * * first pass prepares substitution strings and computes * total length for newstring required from malloc. */ for (s=str;(c=*s++);) { if (c=='\\') { switch (c=*s++) { case 'G': if (! g_str) { g_str = n->graph->name; g_len = strlen(g_str); } newlen += g_len; break; case 'N': if (! n_str) { n_str = n->name; n_len = strlen(n_str); } newlen += n_len; break; default: newlen +=2; } } else { newlen++; } } /* allocate new string */ newstr=gmalloc(newlen+1); /* second pass over str assembles new string */ for (s=str,p=newstr;(c=*s++);) { if (c=='\\') { switch (c=*s++) { case 'G': for (t=g_str;(*p=*t++);p++); break; case 'N': for (t=n_str;(*p=*t++);p++); break; default: *p++ = '\\'; *p++ = c; } } else { *p++ = c; } } *p++ = '\0'; return newstr; } char *strdup_and_subst_edge(char *str, Agedge_t *e) { char c, *s, *p, *t, *newstr; char *e_str=NULL, *h_str=NULL, *t_str=NULL; int e_len=0, h_len=0, t_len=0, newlen=0; /* two passes over str. * * first pass prepares substitution strings and computes * total length for newstring required from malloc. */ for (s=str;(c=*s++);) { if (c=='\\') { switch (c=*s++) { case 'E': if (! e_str) { t_str = e->tail->name; t_len = strlen(t_str); h_str = e->head->name; h_len = strlen(h_str); if (e->tail->graph->root->kind & AGFLAG_DIRECTED) e_str = "->"; else e_str = "--"; e_len = t_len +2 +h_len; } newlen += e_len; break; case 'H': if (! h_str) { h_str = e->head->name; h_len = strlen(h_str); } newlen += h_len; break; case 'T': if (! t_str) { t_str = e->tail->name; t_len = strlen(t_str); } newlen += t_len; break; default: newlen +=2; } } else { newlen++; } } /* allocate new string */ newstr=gmalloc(newlen+1); /* second pass over str assembles new string */ for (s=str,p=newstr;(c=*s++);) { if (c=='\\') { switch (c=*s++) { case 'E': for (t=t_str;(*p=*t++);p++); for (t=e_str;(*p=*t++);p++); for (t=h_str;(*p=*t++);p++); break; case 'H': for (t=h_str;(*p=*t++);p++); break; case 'T': for (t=t_str;(*p=*t++);p++); break; default: *p++ = '\\'; *p++ = c; } } else { *p++ = c; } } *p++ = '\0'; return newstr; } /* return true if *s points to &[a-z]*; (e.g. & ) * or &#[0-9]*; (e.g. & ) */ static int xml_isentity(char *s) { s++; /* already known to be '&' */ if (*s == '#') { s++; while (*s >= '0' && *s <= '9') s++; } else { while (*s >= 'a' && *s <= 'z') s++; } if (*s == ';') return 1; return 0; } char * xml_string(char *s) { static char *buf=NULL; static int bufsize=0; char *p, *sub; int len, pos=0; if (!buf) { bufsize = 64; buf = gmalloc(bufsize); } p = buf; while (*s) { if (pos > (bufsize-8)) { bufsize *= 2; buf = grealloc(buf,bufsize); p = buf + pos; } /* these are safe even if string is already UTF-8 coded * since UTF-8 strings won't contain '<' or '>' */ if (*s == '<') { sub = "<"; len = 4; } else if (*s == '>') { sub = ">"; len = 4; } /* escape '&' only if not part of a legal entity sequence */ else if (*s == '&' && ! (xml_isentity(s))) { sub = "&"; len = 5; } else { sub = s; len = 1; } while (len--) { *p++ = *sub++; pos++; } s++; } *p = '\0'; return buf; } textlabel_t *make_label(char *str, double fontsize, char *fontname, char *fontcolor, graph_t *g) { textlabel_t *rv = NEW(textlabel_t); rv->text = str; rv->fontname = fontname; rv->fontcolor = fontcolor; rv->fontsize = fontsize; label_size(str,rv,g); return rv; } void free_label(textlabel_t* p) { if (p) { if (p->nlines != '\0') free(p->line[0].str); free(p->line); free(p->text); free(p); } } #ifdef DEBUG edge_t * debug_getedge(graph_t *g, char *s0, char *s1) { node_t *n0,*n1; n0 = agfindnode(g,s0); n1 = agfindnode(g,s1); if (n0 && n1) return agfindedge(g,n0,n1); else return NULL; } #endif #ifndef MSWIN32 #include static unsigned char userbuf[SMALLBUF]; static xbuf xb; static void cleanup() { xbfree(&xb); } #endif char * username() { char* user = NULL; #ifndef MSWIN32 static int first = 1; struct passwd* p; if (first) { xbinit (&xb, SMALLBUF, userbuf); atexit(cleanup); first = 0; } p = (struct passwd *) getpwuid(getuid()); if (p) { xbputc (&xb, '('); xbput (&xb, p->pw_name); xbput (&xb, ") "); /* nothing like this in plan 9 */ #if 0 #ifdef SVR4 xbput (&xb, p->pw_comment); #else xbput (&xb, p->pw_gecos); #endif #endif user = xbuse (&xb); } #endif if (user == NULL) user = "Bill Gates"; return user; } /* Fgets: * Read a complete line. * Return pointer to line, * or 0 on EOF */ static char* Fgets (FILE *fp) { static int bsize = 0; static char* buf; char* lp; int len; len = 0; do { if (bsize - len < BUFSIZ) { bsize += BUFSIZ; buf = grealloc (buf, bsize); } lp = fgets (buf + len, bsize - len , fp); if (lp == 0) break; len += strlen(lp); /* since lp != NULL, len > 0 */ } while (buf[len-1] != '\n'); if (len > 0) return buf; else return 0; } int httpcheck(char *filename) { static int onetime = TRUE; if (HTTPServerEnVar) { if (onetime) agerr(AGWARN, "file loading is disabled because %s is set\n",HTTPServerEnVar); agerr(AGWARN, "request to load %s ignored\n",filename); onetime = FALSE; return TRUE; } return FALSE; } void cat_libfile(FILE *ofp, char **arglib, char **stdlib) { FILE *fp; char *p,**s, *bp; int i,use_stdlib = TRUE; if (arglib) { if (httpcheck(arglib[0]?arglib[0]:"[library files]")) return; for (i = 0; (p = arglib[i]) != 0; i++) if (*p == '\0') use_stdlib = FALSE; } if (use_stdlib) for (s = stdlib; *s; s++) {fputs(*s,ofp); fputc('\n',ofp);} if (arglib) for (i = 0; (p = arglib[i]) != 0; i++) { if (p[0] && ((fp = fopen(p,"r")) != 0)) { while ((bp = Fgets(fp))) fputs(bp,ofp); } else agerr(AGWARN, "can't open library file %s\n", p); } } int rect_overlap(box b0, box b1) { if ((b0.UR.x < b1.LL.x) || (b1.UR.x < b0.LL.x) || (b0.UR.y < b1.LL.y) || (b1.UR.y < b0.LL.y)) return FALSE; return TRUE; } int maptoken(char *p,char **name, int *val) { int i; char *q; for (i = 0; (q = name[i]) != 0; i++) if (p && streq(p,q)) break; return val[i]; } int mapbool(char* p) { if (p == NULL) return FALSE; if (!strcasecmp(p,"false")) return FALSE; if (!strcasecmp(p,"true")) return TRUE; return atoi(p); } #ifdef OLD /* There are duplicate definitions of strcasecmp and strncasecmp * in strcasecmp.c and strncasecmp.c * Note also that the use of plain chars means these definitions * are not correct for systems with char != unsigned char. */ #ifdef MSWIN32 strcasecmp(s0,s1) char *s0,*s1; { char c0,c1; do { c0 = *s0++; c1 = *s1++; if (isupper(c0)) c0 = (char)tolower(c0); if (isupper(c1)) c1 = (char)tolower(c1); if (c0 != c1) break; } while (c0 && c1); return c0 - c1; } strncasecmp(s0,s1,n) char *s0,*s1; int n; { char c0,c1; int m = n; while (m--) { c0 = *s0++; c1 = *s1++; if (isupper(c0)) c0 = (char)tolower(c0); if (isupper(c1)) c1 = (char)tolower(c1); if (c0 != c1) break; } return c0 - c1; } #endif #endif static double dist(p,q) pointf p,q; { double d0,d1; d0 = p.x - q.x; d1 = p.y - q.y; return sqrt(d0*d0 + d1*d1); } point dotneato_closest(splines* spl, point p) { int i, j, k, besti, bestj; double bestdist, d, dlow, dhigh; double low, high, t; pointf c[4], pt2, pt; point rv; bezier bz; besti = bestj = -1; bestdist = 1e+38; pt.x = p.x; pt.y = p.y; for (i = 0; i < spl->size; i++) { bz = spl->list[i]; for (j = 0; j < bz.size; j++) { pointf b; b.x = bz.list[j].x; b.y = bz.list[j].y; d = dist(b,pt); if ((bestj == -1) || (d < bestdist)) { besti = i; bestj = j; bestdist = d; } } } bz = spl->list[besti]; j = bestj/3; if (j >= spl->size) j--; for (k = 0; k < 4; k++) { c[k].x = bz.list[j + k].x; c[k].y = bz.list[j + k].y; } low = 0.0; high = 1.0; dlow = dist(c[0],pt); dhigh = dist(c[3],pt); do { t = (low + high) / 2.0; pt2 = Bezier (c, 3, t, NULL, NULL); if (fabs(dlow - dhigh) < 1.0) break; if (low == high) break; if (dlow < dhigh) {high = t; dhigh = dist(pt2,pt);} else {low = t; dlow = dist(pt2,pt); } } while (1); rv.x = pt2.x; rv.y = pt2.y; return rv; } point spline_at_y(splines *spl, int y) { int i,j; double low, high, d, t; pointf c[4], pt2; point pt; static bezier bz; static splines *mem = NULL; if (mem != spl) { mem = spl; for (i = 0; i < spl->size; i++) { bz = spl->list[i]; if (BETWEEN (bz.list[bz.size-1].y, y, bz.list[0].y)) break; } } if (y > bz.list[0].y) pt = bz.list[0]; else if (y < bz.list[bz.size-1].y) pt = bz.list[bz.size - 1]; else { for (i = 0; i < bz.size; i += 3) { for (j = 0; j < 3; j++) { if ((bz.list[i+j].y <= y) && (y <= bz.list[i+j+1].y)) break; if ((bz.list[i+j].y >= y) && (y >= bz.list[i+j+1].y)) break; } if (j < 3) break; } assert (i < bz.size); for (j = 0; j < 4; j++) { c[j].x = bz.list[i + j].x; c[j].y = bz.list[i + j].y; /* make the spline be monotonic in Y, awful but it works for now */ if ((j > 0) && (c[j].y > c[j - 1].y)) c[j].y = c[j - 1].y; } low = 0.0; high = 1.0; do { t = (low + high) / 2.0; pt2 = Bezier (c, 3, t, NULL, NULL); d = pt2.y - y; if (ABS(d) <= 1) break; if (d < 0) high = t; else low = t; } while (1); pt.x = pt2.x; pt.y = pt2.y; } pt.y = y; return pt; } point neato_closest (splines *spl, point p) { /* this is a stub so that we can share a common emit.c between dot and neato */ return spline_at_y(spl, p.y); } static int Tflag; void toggle(int s) { Tflag = !Tflag; #ifndef MSWIN32 signal (SIGUSR1, toggle); #endif } int test_toggle() { return Tflag; } void common_init_node(node_t *n) { char *str; ND_width(n) = late_double(n,N_width,DEFAULT_NODEWIDTH,MIN_NODEWIDTH); ND_height(n) = late_double(n,N_height,DEFAULT_NODEHEIGHT,MIN_NODEHEIGHT); if (N_label == NULL) str = NODENAME_ESC; else str = agxget(n,N_label->index); str = strdup_and_subst_node(str,n); ND_label(n) = make_label(str, late_double(n,N_fontsize,DEFAULT_FONTSIZE,MIN_FONTSIZE), late_nnstring(n,N_fontname,DEFAULT_FONTNAME), late_nnstring(n,N_fontcolor,DEFAULT_COLOR), n->graph); ND_shape(n) = bind_shape(late_nnstring(n,N_shape,DEFAULT_NODESHAPE)); ND_showboxes(n) = late_int(n,N_showboxes,0,0); ND_shape(n)->initfn(n); } void common_init_edge(edge_t *e) { char *s; if (E_label && (s = agxget(e,E_label->index)) && (s[0])) { s = strdup_and_subst_edge(s,e); ED_label(e) = make_label(s, late_double(e,E_fontsize,DEFAULT_FONTSIZE,MIN_FONTSIZE), late_nnstring(e,E_fontname,DEFAULT_FONTNAME), late_nnstring(e,E_fontcolor,DEFAULT_COLOR),e->tail->graph); GD_has_edge_labels(e->tail->graph) |= EDGE_LABEL; ED_label_ontop(e) = mapbool(late_string(e,E_label_float,"false")); } /* vladimir */ if (E_headlabel && (s = agxget(e,E_headlabel->index)) && (s[0])) { s = strdup_and_subst_edge(s,e); ED_head_label(e) = make_label(s, late_double(e,E_labelfontsize,DEFAULT_LABEL_FONTSIZE,MIN_FONTSIZE), late_nnstring(e,E_labelfontname,DEFAULT_FONTNAME), late_nnstring(e,E_labelfontcolor,DEFAULT_COLOR),e->tail->graph); GD_has_edge_labels(e->tail->graph) |= HEAD_LABEL; } if (E_taillabel && (s = agxget(e,E_taillabel->index)) && (s[0])) { s = strdup_and_subst_edge(s,e); ED_tail_label(e) = make_label(s, late_double(e,E_labelfontsize,DEFAULT_LABEL_FONTSIZE,MIN_FONTSIZE), late_nnstring(e,E_labelfontname,DEFAULT_FONTNAME), late_nnstring(e,E_labelfontcolor,DEFAULT_COLOR),e->tail->graph); GD_has_edge_labels(e->tail->graph) |= TAIL_LABEL; } /* end vladimir */ }