/* $Id: utils.c,v 1.136 2011/02/15 16:30:02 erg Exp $ $Revision: 1.136 $ */ /* vim:set shiftwidth=4 ts=8: */ /************************************************************************* * Copyright (c) 2011 AT&T Intellectual Property * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: See CVS logs. Details at http://www.graphviz.org/ *************************************************************************/ #include "render.h" #include "agxbuf.h" #include "htmltable.h" #include "entities.h" #include "logic.h" #include "gvc.h" #ifdef WIN32 #include "libltdl/lt_system.h" #include <io.h> #endif #ifndef WIN32 #include <unistd.h> #endif #include <ctype.h> /* * a queue of nodes */ nodequeue *new_queue(int sz) { nodequeue *q = NEW(nodequeue); 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(nodequeue * q) { free(q->store); free(q); } void enqueue(nodequeue * q, node_t * n) { *(q->tail++) = n; if (q->tail >= q->limit) q->tail = q->store; } node_t *dequeue(nodequeue * 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; } int late_int(void *obj, attrsym_t * attr, int def, int low) { char *p; int rv; if (attr == NULL) return def; #ifndef WITH_CGRAPH p = agxget(obj, attr->index); #else /* WITH_CGRAPH */ p = agxget(obj, attr); #endif /* WITH_CGRAPH */ if (!p || 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 || !obj) return def; #ifndef WITH_CGRAPH p = agxget(obj, attr->index); #else /* WITH_CGRAPH */ p = agxget(obj, attr); #endif /* WITH_CGRAPH */ if (!p || 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 || !obj) return def; #ifndef WITH_CGRAPH return agxget(obj, attr->index); #else /* WITH_CGRAPH */ return agxget(obj, attr); #endif /* WITH_CGRAPH */ } char *late_nnstring(void *obj, attrsym_t * attr, char *def) { char *rv = late_string(obj, attr, def); if (!rv || (rv[0] == '\0')) rv = def; return rv; } boolean late_bool(void *obj, attrsym_t * attr, int def) { if (attr == NULL) return def; #ifndef WITH_CGRAPH return mapbool(agxget(obj, attr->index)); #else /* WITH_CGRAPH */ return mapbool(agxget(obj, attr)); #endif /* WITH_CGRAPH */ } /* union-find */ node_t *UF_find(node_t * n) { while (ND_UF_parent(n) && (ND_UF_parent(n) != n)) { #ifndef WITH_CGRAPH if (ND_UF_parent(n)->u.UF_parent) ND_UF_parent(n) = ND_UF_parent(n)->u.UF_parent; #else /* WITH_CGRAPH */ if (ND_UF_parent(ND_UF_parent(n))) ND_UF_parent(n) = ND_UF_parent(ND_UF_parent(n)); #endif /* WITH_CGRAPH */ 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); #ifndef WITH_CGRAPH if (u->id > v->id) { #else /* WITH_CGRAPH */ if (ND_id(u) > ND_id(v)) { #endif /* WITH_CGRAPH */ 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); } pointf coord(node_t * n) { pointf r; r.x = POINTS_PER_INCH * ND_pos(n)[0]; r.y = POINTS_PER_INCH * ND_pos(n)[1]; return r; } /* 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]); } #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 #if !defined(MSWIN32) && !defined(WIN32) #include <pwd.h> #if 0 static void cleanup(void) { agxbfree(&xb); } #endif #endif /* Fgets: * Read a complete line. * Return pointer to line, * or 0 on EOF */ 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; } /* safefile: * Check to make sure it is okay to read in files. * For normal uses, it is a no-op, and returns the input. * It returns NULL if the filename is trivial. * * If the application has set the SERVER_NAME environment variable, * this indicates it is web-active. In this case, it requires that the GV_FILE_PATH * environment variable be set. This gives the legal directories * from which files may be read. safefile then derives the rightmost component * of filename, where components are separated by a slash, backslash or colon, * It then checks for the existence of a file consisting of a directory from * GV_FILE_PATH followed by the rightmost component of filename. It returns the * first such found, or NULL otherwise. * The filename returned is thus * Gvfilepath concatenated with the last component of filename, * where a component is determined by a slash, backslash or colon * character. * * If filename contains multiple components, the user is * warned, once, that everything to the left is ignored. * * N.B. safefile uses a fixed buffer, so functions using it should use the * value immediately or make a copy. */ #ifdef WIN32 #define DIRSEP "\\" #define PATHSEP ";" #else #define DIRSEP "/" #define PATHSEP ":" #endif const char *safefile(const char *filename) { static boolean onetime = TRUE; static boolean firsttime = TRUE; static char *safefilename = NULL; static int maxdirlen; static char** dirs; char** dp; const char *str, *p; if (!filename || !filename[0]) return NULL; if (HTTPServerEnVar) { /* * If we are running in an http server we allow * files only from the directory specified in * the GV_FILE_PATH environment variable. */ if (!Gvfilepath) { if (onetime) { agerr(AGWARN, "file loading is disabled because the environment contains SERVER_NAME=\"%s\"n" "and there is no GV_FILE_PATH variable set.\n", HTTPServerEnVar); onetime = FALSE; } return NULL; } if (firsttime) { int cnt = 0; char* s = strdup (Gvfilepath); char* dir; for (dir = strtok (s, PATHSEP); dir; dir = strtok (NULL, PATHSEP)) { dirs = ALLOC (cnt+2,dirs,char*); dirs[cnt++] = dir; maxdirlen = MAX(maxdirlen, strlen (dir)); } dirs[cnt] = NULL; firsttime = FALSE; } str = filename; if ((p = strrchr(str, '/'))) str = ++p; if ((p = strrchr(str, '\\'))) str = ++p; if ((p = strrchr(str, ':'))) str = ++p; if (onetime && str != filename) { agerr(AGWARN, "Path provided to file: \"%s\" has been ignored" " because files are only permitted to be loaded from the directories in \"%s\"" " when running in an http server.\n", filename, Gvfilepath); onetime = FALSE; } /* allocate a buffer that we are sure is big enough * +1 for null character. * +1 for directory separator character. */ safefilename = realloc(safefilename, (maxdirlen + strlen(str) + 2)); for (dp = dirs; *dp; dp++) { sprintf (safefilename, "%s%s%s", *dp, DIRSEP, str); if (access (safefilename, R_OK) == 0) return safefilename; } return NULL; } /* else, not in server, use original filename without modification. */ else return filename; } 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]; } boolean mapBool(char *p, boolean dflt) { if (!p || (*p == '\0')) return dflt; if (!strcasecmp(p, "false")) return FALSE; if (!strcasecmp(p, "no")) return FALSE; if (!strcasecmp(p, "true")) return TRUE; if (!strcasecmp(p, "yes")) return TRUE; if (isdigit(*p)) return atoi(p); else return dflt; } boolean mapbool(char *p) { return mapBool (p, FALSE); } pointf dotneato_closest(splines * spl, pointf pt) { int i, j, k, besti, bestj; double bestdist2, d2, dlow2, dhigh2; /* squares of distances */ double low, high, t; pointf c[4], pt2; bezier bz; besti = bestj = -1; bestdist2 = 1e+38; 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; d2 = DIST2(b, pt); if ((bestj == -1) || (d2 < bestdist2)) { besti = i; bestj = j; bestdist2 = d2; } } } 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; dlow2 = DIST2(c[0], pt); dhigh2 = DIST2(c[3], pt); do { t = (low + high) / 2.0; pt2 = Bezier(c, 3, t, NULL, NULL); if (fabs(dlow2 - dhigh2) < 1.0) break; if (fabs(high - low) < .00001) break; if (dlow2 < dhigh2) { high = t; dhigh2 = DIST2(pt2, pt); } else { low = t; dlow2 = DIST2(pt2, pt); } } while (1); return pt2; } pointf spline_at_y(splines * spl, double y) { int i, j; double low, high, d, t; pointf c[4], p; static bezier bz; /* this caching seems to prevent p.x from getting set from bz.list[0].x - optimizer problem ? */ #if 0 static splines *mem = NULL; if (mem != spl) { mem = spl; #endif 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 0 } #endif if (y > bz.list[0].y) p = bz.list[0]; else if (y < bz.list[bz.size - 1].y) p = 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; p = Bezier(c, 3, t, NULL, NULL); d = p.y - y; if (ABS(d) <= 1) break; if (d < 0) high = t; else low = t; } while (1); } p.y = y; return p; } pointf neato_closest(splines * spl, pointf 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 gvToggle(int s) { Tflag = !Tflag; #if !defined(MSWIN32) && !defined(WIN32) signal(SIGUSR1, gvToggle); #endif } int test_toggle() { return Tflag; } struct fontinfo { double fontsize; char *fontname; char *fontcolor; }; void common_init_node(node_t * n) { struct fontinfo fi; 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); ND_shape(n) = bind_shape(late_nnstring(n, N_shape, DEFAULT_NODESHAPE), n); #ifndef WITH_CGRAPH str = agxget(n, N_label->index); #else str = agxget(n, N_label); #endif fi.fontsize = late_double(n, N_fontsize, DEFAULT_FONTSIZE, MIN_FONTSIZE); fi.fontname = late_nnstring(n, N_fontname, DEFAULT_FONTNAME); fi.fontcolor = late_nnstring(n, N_fontcolor, DEFAULT_COLOR); ND_label(n) = make_label((void*)n, str, ((aghtmlstr(str) ? LT_HTML : LT_NONE) | ( (shapeOf(n) == SH_RECORD) ? LT_RECD : LT_NONE)), fi.fontsize, fi.fontname, fi.fontcolor); #ifndef WITH_CGRAPH if (N_xlabel && (str = agxget(n, N_xlabel->index)) && (str[0])) { #else if (N_xlabel && (str = agxget(n, N_xlabel)) && (str[0])) { #endif ND_xlabel(n) = make_label((void*)n, str, (aghtmlstr(str) ? LT_HTML : LT_NONE), fi.fontsize, fi.fontname, fi.fontcolor); GD_has_labels(agraphof(n)) |= NODE_XLABEL; } ND_showboxes(n) = late_int(n, N_showboxes, 0, 0); ND_shape(n)->fns->initfn(n); } static void initFontEdgeAttr(edge_t * e, struct fontinfo *fi) { fi->fontsize = late_double(e, E_fontsize, DEFAULT_FONTSIZE, MIN_FONTSIZE); fi->fontname = late_nnstring(e, E_fontname, DEFAULT_FONTNAME); fi->fontcolor = late_nnstring(e, E_fontcolor, DEFAULT_COLOR); } static void initFontLabelEdgeAttr(edge_t * e, struct fontinfo *fi, struct fontinfo *lfi) { if (!fi->fontname) initFontEdgeAttr(e, fi); lfi->fontsize = late_double(e, E_labelfontsize, fi->fontsize, MIN_FONTSIZE); lfi->fontname = late_nnstring(e, E_labelfontname, fi->fontname); lfi->fontcolor = late_nnstring(e, E_labelfontcolor, fi->fontcolor); } /* noClip: * Return true if head/tail end of edge should not be clipped * to node. */ static boolean noClip(edge_t *e, attrsym_t* sym) { char *str; boolean rv = FALSE; if (sym) { /* mapbool isn't a good fit, because we want "" to mean true */ #ifndef WITH_CGRAPH str = agxget(e,sym->index); #else /* WITH_CGRAPH */ str = agxget(e,sym); #endif /* WITH_CGRAPH */ if (str && str[0]) rv = !mapbool(str); else rv = FALSE; } return rv; } /*chkPort: */ static port chkPort (port (*pf)(node_t*, char*, char*), node_t* n, char* s) { port pt; #ifndef WITH_CGRAPH char* cp = strchr(s,':'); #else /* WITH_CGRAPH */ char* cp=NULL; if(s) cp= strchr(s,':'); #endif /* WITH_CGRAPH */ if (cp) { *cp = '\0'; pt = pf(n, s, cp+1); *cp = ':'; pt.name = cp+1; } else pt = pf(n, s, NULL); pt.name = s; return pt; } /* return true if edge has label */ int common_init_edge(edge_t * e) { char *str; int r = 0; struct fontinfo fi; struct fontinfo lfi; graph_t *sg = agraphof(agtail(e)); fi.fontname = NULL; lfi.fontname = NULL; #ifndef WITH_CGRAPH if (E_label && (str = agxget(e, E_label->index)) && (str[0])) { #else if (E_label && (str = agxget(e, E_label)) && (str[0])) { #endif r = 1; initFontEdgeAttr(e, &fi); ED_label(e) = make_label((void*)e, str, (aghtmlstr(str) ? LT_HTML : LT_NONE), fi.fontsize, fi.fontname, fi.fontcolor); GD_has_labels(sg) |= EDGE_LABEL; ED_label_ontop(e) = mapbool(late_string(e, E_label_float, "false")); } #ifndef WITH_CGRAPH if (E_xlabel && (str = agxget(e, E_xlabel->index)) && (str[0])) { #else if (E_xlabel && (str = agxget(e, E_xlabel)) && (str[0])) { #endif if (!fi.fontname) initFontEdgeAttr(e, &fi); ED_xlabel(e) = make_label((void*)e, str, (aghtmlstr(str) ? LT_HTML : LT_NONE), fi.fontsize, fi.fontname, fi.fontcolor); GD_has_labels(sg) |= EDGE_XLABEL; } /* vladimir */ #ifndef WITH_CGRAPH if (E_headlabel && (str = agxget(e, E_headlabel->index)) && (str[0])) { #else if (E_headlabel && (str = agxget(e, E_headlabel)) && (str[0])) { #endif initFontLabelEdgeAttr(e, &fi, &lfi); ED_head_label(e) = make_label((void*)e, str, (aghtmlstr(str) ? LT_HTML : LT_NONE), lfi.fontsize, lfi.fontname, lfi.fontcolor); GD_has_labels(sg) |= HEAD_LABEL; } #ifndef WITH_CGRAPH if (E_taillabel && (str = agxget(e, E_taillabel->index)) && (str[0])) { #else if (E_taillabel && (str = agxget(e, E_taillabel)) && (str[0])) { #endif if (!lfi.fontname) initFontLabelEdgeAttr(e, &fi, &lfi); ED_tail_label(e) = make_label((void*)e, str, (aghtmlstr(str) ? LT_HTML : LT_NONE), lfi.fontsize, lfi.fontname, lfi.fontcolor); GD_has_labels(sg) |= TAIL_LABEL; } /* end vladimir */ /* We still accept ports beginning with colons but this is deprecated * That is, we allow tailport = ":abc" as well as the preferred * tailport = "abc". */ str = agget(e, TAIL_ID); #ifdef WITH_CGRAPH /* libgraph always defines tailport/headport; libcgraph doesn't */ if (!str) str = ""; #endif if (str && str[0]) ND_has_port(agtail(e)) = TRUE; ED_tail_port(e) = chkPort (ND_shape(agtail(e))->fns->portfn, agtail(e), str); if (noClip(e, E_tailclip)) ED_tail_port(e).clip = FALSE; str = agget(e, HEAD_ID); #ifdef WITH_CGRAPH /* libgraph always defines tailport/headport; libcgraph doesn't */ if (!str) str = ""; #endif if (str && str[0]) ND_has_port(aghead(e)) = TRUE; ED_head_port(e) = chkPort(ND_shape(aghead(e))->fns->portfn, aghead(e), str); if (noClip(e, E_headclip)) ED_head_port(e).clip = FALSE; return r; } /* addLabelBB: */ static boxf addLabelBB(boxf bb, textlabel_t * lp, boolean flipxy) { double width, height; pointf p = lp->pos; double min, max; if (flipxy) { height = lp->dimen.x; width = lp->dimen.y; } else { width = lp->dimen.x; height = lp->dimen.y; } min = p.x - width / 2.; max = p.x + width / 2.; if (min < bb.LL.x) bb.LL.x = min; if (max > bb.UR.x) bb.UR.x = max; min = p.y - height / 2.; max = p.y + height / 2.; if (min < bb.LL.y) bb.LL.y = min; if (max > bb.UR.y) bb.UR.y = max; return bb; } /* updateBB: * Reset graph's bounding box to include bounding box of the given label. * Assume the label's position has been set. */ void updateBB(graph_t * g, textlabel_t * lp) { GD_bb(g) = addLabelBB(GD_bb(g), lp, GD_flip(g)); } /* compute_bb: * Compute bounding box of g using nodes, splines, and clusters. * Assumes bb of clusters already computed. * store in GD_bb. */ void compute_bb(graph_t * g) { node_t *n; edge_t *e; boxf b, bb; boxf BF; pointf ptf, s2; int i, j; if ((agnnodes(g) == 0) && (GD_n_cluster(g) ==0)) { bb.LL = pointfof(0, 0); bb.UR = pointfof(0, 0); return; } bb.LL = pointfof(INT_MAX, INT_MAX); bb.UR = pointfof(-INT_MAX, -INT_MAX); for (n = agfstnode(g); n; n = agnxtnode(g, n)) { ptf = coord(n); s2.x = ND_xsize(n) / 2.0; s2.y = ND_ysize(n) / 2.0; b.LL = sub_pointf(ptf, s2); b.UR = add_pointf(ptf, s2); EXPANDBB(bb,b); for (e = agfstout(g, n); e; e = agnxtout(g, e)) { if (ED_spl(e) == 0) continue; for (i = 0; i < ED_spl(e)->size; i++) { #ifndef WITH_CGRAPH for (j = 0; j < ED_spl(e)->list[i].size; j++) { #else for (j = 0; j < (((Agedgeinfo_t*)AGDATA(e))->spl)->list[i].size; j++) { #endif ptf = ED_spl(e)->list[i].list[j]; EXPANDBP(bb,ptf); } } if (ED_label(e) && ED_label(e)->set) { bb = addLabelBB(bb, ED_label(e), GD_flip(g)); } } } for (i = 1; i <= GD_n_cluster(g); i++) { B2BF(GD_bb(GD_clust(g)[i]), BF); EXPANDBB(bb,BF); } if (GD_label(g) && GD_label(g)->set) { bb = addLabelBB(bb, GD_label(g), GD_flip(g)); } GD_bb(g) = bb; } int is_a_cluster (Agraph_t* g) { return ((g == g->root) || (!strncasecmp(agnameof(g), "cluster", 7))); } /* setAttr: * Sets object's name attribute to the given value. * Creates the attribute if not already set. */ Agsym_t *setAttr(graph_t * g, void *obj, char *name, char *value, Agsym_t * ap) { if (ap == NULL) { switch (agobjkind(obj)) { #ifndef WITH_CGRAPH case AGGRAPH: ap = agraphattr(g, name, ""); #else /* WITH_CGRAPH */ case AGRAPH: ap = agattr(g, AGRAPH,name, ""); #endif /* WITH_CGRAPH */ break; case AGNODE: #ifndef WITH_CGRAPH ap = agnodeattr(g, name, ""); #else /* WITH_CGRAPH */ ap = agattr(g,AGNODE, name, ""); #endif /* WITH_CGRAPH */ break; case AGEDGE: #ifndef WITH_CGRAPH ap = agedgeattr(g, name, ""); #else /* WITH_CGRAPH */ ap = agattr(g,AGEDGE, name, ""); #endif /* WITH_CGRAPH */ break; } } #ifndef WITH_CGRAPH agxset(obj, ap->index, value); #else /* WITH_CGRAPH */ agxset(obj, ap, value); #endif /* WITH_CGRAPH */ return ap; } /* clustNode: * Generate a special cluster node representing the end node * of an edge to the cluster cg. n is a node whose name is the same * as the cluster cg. clg is the subgraph of all of * the original nodes, which will be deleted later. */ static node_t *clustNode(node_t * n, graph_t * cg, agxbuf * xb, graph_t * clg) { node_t *cn; static int idx = 0; char num[100]; agxbput(xb, "__"); sprintf(num, "%d", idx++); agxbput(xb, num); agxbputc(xb, ':'); agxbput(xb, agnameof(cg)); #ifndef WITH_CGRAPH cn = agnode(agroot(cg), agxbuse(xb)); #else cn = agnode(agroot(cg), agxbuse(xb), 1); agbindrec(cn, "Agnodeinfo_t", sizeof(Agnodeinfo_t), TRUE); //node custom data #endif /* WITH_CGRAPH */ SET_CLUST_NODE(cn); #ifndef WITH_CGRAPH aginsert(cg, cn); aginsert(clg, n); #else /* WITH_CGRAPH */ agsubnode(cg,cn,1); //aginsert(cg, cn); agsubnode(clg,n,1); //aginsert(clg, n); #endif /* WITH_CGRAPH */ /* set attributes */ N_label = setAttr(agraphof(cn), cn, "label", "", N_label); N_style = setAttr(agraphof(cn), cn, "style", "invis", N_style); N_shape = setAttr(agraphof(cn), cn, "shape", "box", N_shape); /* N_width = setAttr (cn->graph, cn, "width", "0.0001", N_width); */ return cn; } typedef struct { Dtlink_t link; /* cdt data */ void *p[2]; /* key */ node_t *t; node_t *h; } item; static int cmpItem(Dt_t * d, void *p1[], void *p2[], Dtdisc_t * disc) { NOTUSED(d); NOTUSED(disc); if (p1[0] < p2[0]) return -1; else if (p1[0] > p2[0]) return 1; else if (p1[1] < p2[1]) return -1; else if (p1[1] > p2[1]) return 1; else return 0; } /* newItem: */ static void *newItem(Dt_t * d, item * objp, Dtdisc_t * disc) { item *newp = NEW(item); NOTUSED(disc); newp->p[0] = objp->p[0]; newp->p[1] = objp->p[1]; newp->t = objp->t; newp->h = objp->h; return newp; } /* freeItem: */ static void freeItem(Dt_t * d, item * obj, Dtdisc_t * disc) { free(obj); } static Dtdisc_t mapDisc = { offsetof(item, p), sizeof(2 * sizeof(void *)), offsetof(item, link), (Dtmake_f) newItem, (Dtfree_f) freeItem, (Dtcompar_f) cmpItem, NIL(Dthash_f), NIL(Dtmemory_f), NIL(Dtevent_f) }; /* cloneEdge: * Make a copy of e in e's graph but using ct and ch as nodes */ static edge_t *cloneEdge(edge_t * e, node_t * ct, node_t * ch) { graph_t *g = agraphof(ct); #ifndef WITH_CGRAPH edge_t *ce = agedge(g, ct, ch); #else /* WITH_CGRAPH */ edge_t *ce = agedge(g, ct, ch,NULL,1); #endif /* WITH_CGRAPH */ agcopyattr(e, ce); return ce; } /* insertEdge: */ static void insertEdge(Dt_t * map, void *t, void *h, edge_t * e) { item dummy; dummy.p[0] = t; dummy.p[1] = h; dummy.t = agtail(e); dummy.h = aghead(e); dtinsert(map, &dummy); dummy.p[0] = h; dummy.p[1] = t; dummy.t = aghead(e); dummy.h = agtail(e); dtinsert(map, &dummy); } /* mapEdge: * Check if we already have cluster edge corresponding to t->h, * and return it. */ static item *mapEdge(Dt_t * map, edge_t * e) { void *key[2]; key[0] = agtail(e); key[1] = aghead(e); return (item *) dtmatch(map, &key); } /* checkCompound: * If endpoint names a cluster, mark for temporary deletion and create * special node and insert into cluster. Then clone the edge. Real edge * will be deleted when we delete the original node. * Invariant: new edge has same sense as old. That is, given t->h with * t and h mapped to ct and ch, the new edge is ct->ch. * * In the current model, we create a cluster node for each cluster edge * between the cluster and some other node or cluster, treating the * cluster node as a port on the cluster. This should help with better * routing to avoid edge crossings. At present, this is not implemented, * so we could use a simpler model in which we create a single cluster * node for each cluster used in a cluster edge. */ #ifndef WITH_CGRAPH #define MAPC(n) (strncmp((n)->name,"cluster",7)?NULL:agfindsubg((n)->graph, (n)->name)) #else /* WITH_CGRAPH */ #define MAPC(n) (strncmp(agnameof(n),"cluster",7)?NULL:agsubg(agraphof(n), agnameof(n),0)) #endif /* WITH_CGRAPH */ static void checkCompound(edge_t * e, graph_t * clg, agxbuf * xb, Dt_t * map) { graph_t *tg; graph_t *hg; node_t *cn; node_t *cn1; node_t *t = agtail(e); node_t *h = aghead(e); edge_t *ce; item *ip; if (IS_CLUST_NODE(h)) return; tg = MAPC(t); hg = MAPC(h); if (!tg && !hg) return; if (tg == hg) { agerr(AGWARN, "cluster cycle %s -- %s not supported\n", agnameof(t), agnameof(t)); return; } ip = mapEdge(map, e); if (ip) { cloneEdge(e, ip->t, ip->h); return; } if (hg) { if (tg) { if (agcontains(hg, tg)) { agerr(AGWARN, "tail cluster %s inside head cluster %s\n", agnameof(tg), agnameof(hg)); return; } if (agcontains(tg, hg)) { agerr(AGWARN, "head cluster %s inside tail cluster %s\n", agnameof(hg),agnameof(tg)); return; } cn = clustNode(t, tg, xb, clg); cn1 = clustNode(h, hg, xb, clg); ce = cloneEdge(e, cn, cn1); insertEdge(map, t, h, ce); } else { if (agcontains(hg, t)) { agerr(AGWARN, "tail node %s inside head cluster %s\n", agnameof(t), agnameof(hg)); return; } cn = clustNode(h, hg, xb, clg); ce = cloneEdge(e, t, cn); insertEdge(map, t, h, ce); } } else { if (agcontains(tg, h)) { agerr(AGWARN, "head node %s inside tail cluster %s\n", agnameof(h), agnameof(tg)); return; } cn = clustNode(t, tg, xb, clg); ce = cloneEdge(e, cn, h); insertEdge(map, t, h, ce); } } /* processClusterEdges: * Look for cluster edges. Replace cluster edge endpoints * corresponding to a cluster with special cluster nodes. * Delete original nodes. * Return 0 if no cluster edges; 1 otherwise. */ int processClusterEdges(graph_t * g) { int rv; node_t *n; node_t *nxt; edge_t *e; graph_t *clg; agxbuf xb; Dt_t *map; unsigned char buf[SMALLBUF]; map = dtopen(&mapDisc, Dtoset); #ifndef WITH_CGRAPH clg = agsubg(g, "__clusternodes"); #else /* WITH_CGRAPH */ clg = agsubg(g, "__clusternodes",1); agbindrec(clg, "Agraphinfo_t", sizeof(Agraphinfo_t), TRUE); //node custom data #endif /* WITH_CGRAPH */ agxbinit(&xb, SMALLBUF, buf); for (n = agfstnode(g); n; n = agnxtnode(g, n)) { if (IS_CLUST_NODE(n)) continue; for (e = agfstout(g, n); e; e = agnxtout(g, e)) { checkCompound(e, clg, &xb, map); } } agxbfree(&xb); dtclose(map); rv = agnnodes(clg); for (n = agfstnode(clg); n; n = nxt) { nxt = agnxtnode(clg, n); agdelete(g, n); } agclose(clg); if (rv) SET_CLUST_EDGE(g); return rv; } /* mapN: * Convert cluster nodes back to ordinary nodes * If n is already ordinary, return it. * Otherwise, we know node's name is "__i:xxx" * where i is some number and xxx is the nodes's original name. * Create new node of name xxx if it doesn't exist and add n to clg * for later deletion. */ static node_t *mapN(node_t * n, graph_t * clg) { #ifndef WITH_CGRAPH extern Agdict_t *agdictof(void *); Agdict_t *d; Agsym_t **list; #endif /* WITH_CGRAPH */ node_t *nn; char *name; graph_t *g = agraphof(n); Agsym_t *sym; if (!(IS_CLUST_NODE(n))) return n; #ifndef WITH_CGRAPH aginsert(clg, n); #else /* WITH_CGRAPH */ agsubnode(clg, n, 1); #endif /* WITH_CGRAPH */ name = strchr(agnameof(n), ':'); assert(name); name++; if ((nn = agfindnode(g, name))) return nn; #ifndef WITH_CGRAPH nn = agnode(g, name); #else /* WITH_CGRAPH */ nn = agnode(g, name, 1); agbindrec(nn, "Agnodeinfo_t", sizeof(Agnodeinfo_t), TRUE); //node custom data #endif /* WITH_CGRAPH */ /* Set all attributes to default */ #ifndef WITH_CGRAPH d = agdictof(n); list = d->list; while ((sym = *list++)) { /* Can use pointer comparison because of ref strings. */ if (agxget(nn, sym->index) != sym->value) agxset(nn, sym->index, sym->value); } #else /* WITH_CGRAPH */ for (sym = agnxtattr(g, AGNODE, NULL); sym; (sym = agnxtattr(g, AGNODE, sym))) { if (agxget(nn, sym) != sym->defval) agxset(nn, sym, sym->defval); } #endif /* WITH_CGRAPH */ return nn; } static void undoCompound(edge_t * e, graph_t * clg) { node_t *t = agtail(e); node_t *h = aghead(e); node_t *ntail; node_t *nhead; if (!(IS_CLUST_NODE(t) || IS_CLUST_NODE(h))) return; ntail = mapN(t, clg); nhead = mapN(h, clg); cloneEdge(e, ntail, nhead); } /* undoClusterEdges: * Replace cluster nodes with originals. Make sure original has * no attributes. Replace original edges. Delete cluster nodes, * which will also delete cluster edges. */ void undoClusterEdges(graph_t * g) { node_t *n; edge_t *e; graph_t *clg; #ifndef WITH_CGRAPH clg = agsubg(g, "__clusternodes"); #else /* WITH_CGRAPH */ clg = agsubg(g, "__clusternodes",1); agbindrec(clg, "Agraphinfo_t", sizeof(Agraphinfo_t), TRUE); //node custom data #endif /* WITH_CGRAPH */ for (n = agfstnode(g); n; n = agnxtnode(g, n)) { for (e = agfstout(g, n); e; e = agnxtout(g, e)) { undoCompound(e, clg); } } for (n = agfstnode(clg); n; n = agnxtnode(clg, n)) { agdelete(g, n); } agclose(clg); } /* safe_dcl: * Find the attribute belonging to graph g for objects like obj * with given name. If one does not exist, create it with the * default value def. */ #ifndef WITH_CGRAPH attrsym_t* safe_dcl(graph_t * g, void *obj, char *name, char *def, attrsym_t * (*fun) (Agraph_t *, char *, char *)) { attrsym_t *a = agfindattr(obj, name); if (a == NULL) a = fun(g, name, def); return a; } #else /* WITH_CGRAPH */ attrsym_t* safe_dcl(graph_t * g, int obj_kind, char *name, char *def) { attrsym_t *a = agattr(g,obj_kind,name, NULL); if (!a) /* attribute does not exist */ a = agattr(g,obj_kind,name,def); return a; } #endif /* WITH_CGRAPH */ static int comp_entities(const void *e1, const void *e2) { return strcmp(((struct entities_s *)e1)->name, ((struct entities_s *)e2)->name); } #define MAXENTLEN 8 /* scanEntity: * Scan non-numeric entity, convert to &#...; form and store in xbuf. * t points to first char after '&'. Return after final semicolon. * If unknown, we return t and let libexpat flag the error. * */ char* scanEntity (char* t, agxbuf* xb) { char* endp = strchr (t, ';'); struct entities_s key, *res; int len; char buf[MAXENTLEN+1]; agxbputc(xb, '&'); if (!endp) return t; if (((len = endp-t) > MAXENTLEN) || (len < 2)) return t; strncpy (buf, t, len); buf[len] = '\0'; key.name = buf; res = bsearch(&key, entities, NR_OF_ENTITIES, sizeof(entities[0]), comp_entities); if (!res) return t; sprintf (buf, "%d", res->value); agxbputc(xb, '#'); agxbput(xb, buf); agxbputc(xb, ';'); return (endp+1); } /* htmlEntity: * Check for an HTML entity for a special character. * Assume *s points to first byte after '&'. * If successful, return the corresponding value and update s to * point after the terminating ';'. * On failure, return 0 and leave s unchanged. */ static int htmlEntity (char** s) { char *p; struct entities_s key, *res; char entity_name_buf[ENTITY_NAME_LENGTH_MAX+1]; unsigned char* str = *(unsigned char**)s; unsigned int byte; int i, n = 0; byte = *str; if (byte == '#') { byte = *(str + 1); if (byte == 'x' || byte == 'X') { for (i = 2; i < 8; i++) { byte = *(str + i); if (byte >= 'A' && byte <= 'F') byte = byte - 'A' + 10; else if (byte >= 'a' && byte <= 'f') byte = byte - 'a' + 10; else if (byte >= '0' && byte <= '9') byte = byte - '0'; else break; n = (n * 16) + byte; } } else { for (i = 1; i < 8; i++) { byte = *(str + i); if (byte >= '0' && byte <= '9') n = (n * 10) + (byte - '0'); else break; } } if (byte == ';') { str += i+1; } else { n = 0; } } else { key.name = p = entity_name_buf; for (i = 0; i < ENTITY_NAME_LENGTH_MAX; i++) { byte = *(str + i); if (byte == '\0') break; if (byte == ';') { *p++ = '\0'; res = bsearch(&key, entities, NR_OF_ENTITIES, sizeof(entities[0]), *comp_entities); if (res) { n = res->value; str += i+1; } break; } *p++ = byte; } } *s = (char*)str; return n; } /* substitute html entities like: { and: & with the UTF8 equivalents */ char* htmlEntityUTF8 (char* s) { char* ns; agxbuf xb; unsigned char buf[BUFSIZ]; unsigned char c; unsigned int v; int rc; agxbinit(&xb, BUFSIZ, buf); while ((c = *(unsigned char*)s++)) { if (c < 0xC0) { /* * Handles properly formed UTF-8 characters between * 0x01 and 0x7F. Also treats \0 and naked trail * bytes 0x80 to 0xBF as valid characters representing * themselves. */ if (c == '&') { /* replace html entity sequences like: & * and: { with their UTF8 equivalents */ v = htmlEntity (&s); if (v) { if (v < 0x7F) /* entity needs 1 byte in UTF8 */ c = v; else if (v < 0x07FF) { /* entity needs 2 bytes in UTF8 */ rc = agxbputc(&xb, (v >> 6) | 0xC0); c = (v & 0x3F) | 0x80; } else { /* entity needs 3 bytes in UTF8 */ rc = agxbputc(&xb, (v >> 12) | 0xE0); rc = agxbputc(&xb, ((v >> 6) & 0x3F) | 0x80); c = (v & 0x3F) | 0x80; } } } } else if (c < 0xE0) { /* copy 2 byte UTF8 characters */ if ((*s & 0xC0) == 0x80) { rc = agxbputc(&xb, c); c = *(unsigned char*)s++; } else { agerr(AGERR, "Invalid 2-byte UTF8 found in input. Perhaps \"-Gcharset=latin1\" is needed?\n"); exit(EXIT_FAILURE); } } else if (c < 0xF0) { /* copy 3 byte UTF8 characters */ if (((*s & 0xC0) == 0x80) && ((s[1] & 0xC0) == 0x80)) { rc = agxbputc(&xb, c); c = *(unsigned char*)s++; rc = agxbputc(&xb, c); c = *(unsigned char*)s++; } else { agerr(AGERR, "Invalid 3-byte UTF8 found in input. Perhaps \"-Gcharset=latin1\" is needed?\n"); exit(EXIT_FAILURE); } } else { agerr(AGERR, "UTF8 codes > 3 bytes are not currently supported. Or perhaps \"-Gcharset=latin1\" is needed?\n"); exit(EXIT_FAILURE); } rc = agxbputc(&xb, c); } ns = strdup (agxbuse(&xb)); agxbfree(&xb); return ns; } /* latin1ToUTF8: * Converts string from Latin1 encoding to utf8 * Also translates HTML entities. * */ char* latin1ToUTF8 (char* s) { char* ns; agxbuf xb; unsigned char buf[BUFSIZ]; unsigned int v; int rc; agxbinit(&xb, BUFSIZ, buf); /* Values are either a byte (<= 256) or come from htmlEntity, whose * values are all less than 0x07FF, so we need at most 3 bytes. */ while ((v = *(unsigned char*)s++)) { if (v == '&') { v = htmlEntity (&s); if (!v) v = '&'; } if (v < 0x7F) rc = agxbputc(&xb, v); else if (v < 0x07FF) { rc = agxbputc(&xb, (v >> 6) | 0xC0); rc = agxbputc(&xb, (v & 0x3F) | 0x80); } else { rc = agxbputc(&xb, (v >> 12) | 0xE0); rc = agxbputc(&xb, ((v >> 6) & 0x3F) | 0x80); rc = agxbputc(&xb, (v & 0x3F) | 0x80); } } ns = strdup (agxbuse(&xb)); agxbfree(&xb); return ns; } /* utf8ToLatin1: * Converts string from utf8 encoding to Latin1 * Note that it does not attempt to reproduce HTML entities. * We assume the input string comes from latin1ToUTF8. */ char* utf8ToLatin1 (char* s) { char* ns; agxbuf xb; unsigned char buf[BUFSIZ]; unsigned char c; unsigned char outc; int rc; agxbinit(&xb, BUFSIZ, buf); while ((c = *(unsigned char*)s++)) { if (c < 0x7F) rc = agxbputc(&xb, c); else { outc = (c & 0x03) << 6; c = *(unsigned char*)s++; outc = outc | (c & 0x3F); rc = agxbputc(&xb, outc); } } ns = strdup (agxbuse(&xb)); agxbfree(&xb); return ns; } boolean overlap_node(node_t *n, boxf b) { inside_t ictxt; pointf p; if (! OVERLAP(b, ND_bb(n))) return FALSE; /* FIXME - need to do something better about CLOSEENOUGH */ p = sub_pointf(ND_coord(n), mid_pointf(b.UR, b.LL)); ictxt.s.n = n; ictxt.s.bp = NULL; return ND_shape(n)->fns->insidefn(&ictxt, p); } boolean overlap_label(textlabel_t *lp, boxf b) { pointf s; boxf bb; s.x = lp->dimen.x / 2.; s.y = lp->dimen.y / 2.; bb.LL = sub_pointf(lp->pos, s); bb.UR = add_pointf(lp->pos, s); return OVERLAP(b, bb); } static boolean overlap_arrow(pointf p, pointf u, double scale, int flag, boxf b) { if (OVERLAP(b, arrow_bb(p, u, scale, flag))) { /* FIXME - check inside arrow shape */ return TRUE; } return FALSE; } static boolean overlap_bezier(bezier bz, boxf b) { int i; pointf p, u; assert(bz.size); u = bz.list[0]; for (i = 1; i < bz.size; i++) { p = bz.list[i]; if (lineToBox(p, u, b) != -1) return TRUE; u = p; } /* check arrows */ if (bz.sflag) { if (overlap_arrow(bz.sp, bz.list[0], 1, bz.sflag, b)) return TRUE; } if (bz.eflag) { if (overlap_arrow(bz.ep, bz.list[bz.size - 1], 1, bz.eflag, b)) return TRUE; } return FALSE; } boolean overlap_edge(edge_t *e, boxf b) { int i; splines *spl; textlabel_t *lp; spl = ED_spl(e); if (spl && boxf_overlap(spl->bb, b)) for (i = 0; i < spl->size; i++) if (overlap_bezier(spl->list[i], b)) return TRUE; lp = ED_label(e); if (lp && overlap_label(lp, b)) return TRUE; return FALSE; } /* edgeType: * Convert string to edge type. */ int edgeType (char* s, int dflt) { int et; if (!s || (*s == '\0')) return dflt; et = ET_NONE; switch (*s) { case '0' : /* false */ et = ET_LINE; break; case '1' : /* true */ case '2' : case '3' : case '4' : case '5' : case '6' : case '7' : case '8' : case '9' : et = ET_SPLINE; break; case 'c' : case 'C' : if (!strcasecmp (s+1, "ompound")) et = ET_COMPOUND; break; case 'f' : case 'F' : if (!strcasecmp (s+1, "alse")) et = ET_LINE; break; case 'l' : case 'L' : if (!strcasecmp (s+1, "ine")) et = ET_LINE; break; case 'n' : case 'N' : if (!strcasecmp (s+1, "one")) return et; if (!strcasecmp (s+1, "o")) return ET_LINE; break; case 'o' : case 'O' : if (!strcasecmp (s+1, "rtho")) et = ET_ORTHO; break; case 'p' : case 'P' : if (!strcasecmp (s+1, "olyline")) et = ET_PLINE; break; case 's' : case 'S' : if (!strcasecmp (s+1, "pline")) et = ET_SPLINE; break; case 't' : case 'T' : if (!strcasecmp (s+1, "rue")) et = ET_SPLINE; break; case 'y' : case 'Y' : if (!strcasecmp (s+1, "es")) et = ET_SPLINE; break; } if (!et) { agerr(AGWARN, "Unknown \"splines\" value: \"%s\" - ignored\n", s); et = dflt; } return et; } /* setEdgeType: * Sets graph's edge type based on the "splines" attribute. * If the attribute is not defined, use default. * If the attribute is "", use NONE. * If attribute value matches (case indepedent), use match. * ortho => ET_ORTHO * none => ET_NONE * line => ET_LINE * polyline => ET_PLINE * spline => ET_SPLINE * If attribute is boolean, true means ET_SPLINE, false means ET_LINE. * Else warn and use default. */ void setEdgeType (graph_t* g, int dflt) { char* s = agget(g, "splines"); int et; if (!s) { et = dflt; } else if (*s == '\0') { et = ET_NONE; } else et = edgeType (s, dflt); GD_flags(g) |= et; } #ifndef WIN32_STATIC #ifndef HAVE_STRCASECMP #include <string.h> //#include <ctype.h> int strcasecmp(const char *s1, const char *s2) { while ((*s1 != '\0') && (tolower(*(unsigned char *) s1) == tolower(*(unsigned char *) s2))) { s1++; s2++; } return tolower(*(unsigned char *) s1) - tolower(*(unsigned char *) s2); } #endif /* HAVE_STRCASECMP */ #endif /* WIN32_STATIC */ #ifndef WIN32_STATIC #ifndef HAVE_STRNCASECMP #include <string.h> //#include <ctype.h> int strncasecmp(const char *s1, const char *s2, unsigned int n) { if (n == 0) return 0; while ((n-- != 0) && (tolower(*(unsigned char *) s1) == tolower(*(unsigned char *) s2))) { if (n == 0 || *s1 == '\0' || *s2 == '\0') return 0; s1++; s2++; } return tolower(*(unsigned char *) s1) - tolower(*(unsigned char *) s2); } #endif /* HAVE_STRNCASECMP */ #endif /* WIN32_STATIC */ void gv_free_splines(edge_t * e) { int i; if (ED_spl(e)) { for (i = 0; i < ED_spl(e)->size; i++) free(ED_spl(e)->list[i].list); free(ED_spl(e)->list); free(ED_spl(e)); } ED_spl(e) = NULL; } void gv_cleanup_edge(edge_t * e) { gv_free_splines(e); free_label(ED_label(e)); free_label(ED_xlabel(e)); free_label(ED_head_label(e)); free_label(ED_tail_label(e)); #ifndef WITH_CGRAPH memset(&(e->u), 0, sizeof(Agedgeinfo_t)); #else /* WITH_CGRAPH */ /*FIX HERE , shallow cleaning may not be enough here */ agdelrec(e, "Agedgeinfo_t"); #endif /* WITH_CGRAPH */ } void gv_cleanup_node(node_t * n) { if (ND_pos(n)) free(ND_pos(n)); if (ND_shape(n)) ND_shape(n)->fns->freefn(n); free_label(ND_label(n)); free_label(ND_xlabel(n)); #ifndef WITH_CGRAPH memset(&(n->u), 0, sizeof(Agnodeinfo_t)); #else /* WITH_CGRAPH */ /*FIX HERE , shallow cleaning may not be enough here */ agdelrec(n, "Agnodeinfo_t"); #endif /* WITH_CGRAPH */ } void gv_nodesize(node_t * n, boolean flip) { double w; if (flip) { w = INCH2PS(ND_height(n)); ND_lw(n) = ND_rw(n) = w / 2; ND_ht(n) = INCH2PS(ND_width(n)); } else { w = INCH2PS(ND_width(n)); ND_lw(n) = ND_rw(n) = w / 2; ND_ht(n) = INCH2PS(ND_height(n)); } } #ifdef WIN32 void fix_fc(void) { char buf[28192]; char buf2[28192]; int cur=0; FILE* fp; if((fp = fopen("fix-fc.exe", "r")) == NULL) return ; fclose (fp); if (!system ("fix-fc.exe")) { system ("del fix-fc.exe"); system ("dot -c"); //run dot -c once too since we already run things :) } } #endif #ifndef HAVE_DRAND48 double drand48(void) { double d; d = rand(); d = d / RAND_MAX; return d; } #endif