/misc/src/release/graphviz-2.18-1/src/graphviz-2.18/lib/common/emit.c

Go to the documentation of this file.
00001 /* $Id: emit.c,v 1.240 2008/02/29 22:00:16 ellson Exp $ $Revision: 1.240 $ */
00002 /* vim:set shiftwidth=4 ts=8: */
00003 
00004 /**********************************************************
00005 *      This software is part of the graphviz package      *
00006 *                http://www.graphviz.org/                 *
00007 *                                                         *
00008 *            Copyright (c) 1994-2004 AT&T Corp.           *
00009 *                and is licensed under the                *
00010 *            Common Public License, Version 1.0           *
00011 *                      by AT&T Corp.                      *
00012 *                                                         *
00013 *        Information and Software Systems Research        *
00014 *              AT&T Research, Florham Park NJ             *
00015 **********************************************************/
00016 
00017 /*
00018  *  graphics code generator
00019  */
00020 
00021 #ifdef HAVE_CONFIG_H
00022 #include "config.h"
00023 #endif
00024 
00025 #include <string.h>
00026 #include <ctype.h>
00027 #include "render.h"
00028 #include "agxbuf.h"
00029 #include "htmltable.h"
00030 
00031 #define P2RECT(p, pr, sx, sy) (pr[0].x = p.x - sx, pr[0].y = p.y - sy, pr[1].x = p.x + sx, pr[1].y = p.y + sy)
00032 #define FUZZ 3
00033 #define EPSILON .0001
00034 
00035 static char *defaultlinestyle[3] = { "solid\0", "setlinewidth\0001\0", 0 };
00036 
00037 /* push empty graphic state for current object */
00038 obj_state_t* push_obj_state(GVJ_t *job)
00039 {
00040     obj_state_t *obj, *parent;
00041 
00042     if (! (obj = zmalloc(sizeof(obj_state_t))))
00043         agerr(AGERR, "no memory from zmalloc()\n");
00044 
00045     parent = obj->parent = job->obj;
00046     job->obj = obj;
00047     if (parent) {
00048         obj->pencolor = parent->pencolor;        /* default styles to parent's style */
00049         obj->fillcolor = parent->fillcolor;
00050         obj->pen = parent->pen;
00051         obj->fill = parent->fill;
00052         obj->penwidth = parent->penwidth;
00053     }
00054     else {
00055         /* obj->pencolor = NULL */
00056         /* obj->fillcolor = NULL */
00057         obj->pen = PEN_SOLID;
00058         obj->fill = FILL_NONE;
00059         obj->penwidth = PENWIDTH_NORMAL;
00060     }
00061     return obj;
00062 }
00063 
00064 /* pop graphic state of current object */
00065 void pop_obj_state(GVJ_t *job)
00066 {
00067     obj_state_t *obj = job->obj;
00068 
00069     assert(obj);
00070 
00071     free(obj->url);
00072     free(obj->labelurl);
00073     free(obj->tailurl);
00074     free(obj->headurl);
00075     free(obj->tooltip);
00076     free(obj->labeltooltip);
00077     free(obj->tailtooltip);
00078     free(obj->headtooltip);
00079     free(obj->target);
00080     free(obj->labeltarget);
00081     free(obj->tailtarget);
00082     free(obj->headtarget);
00083     free(obj->url_map_p);
00084     free(obj->url_bsplinemap_p);
00085     free(obj->url_bsplinemap_n);
00086 
00087     job->obj = obj->parent;
00088     free(obj);
00089 }
00090 
00091 /* initMapData:
00092  * Store image map data into job, substituting for node, edge, etc.
00093  * names.
00094  * Return 1 if an assignment was made for url or tooltip or target.
00095  */
00096 int
00097 initMapData (GVJ_t* job, char* lbl, char* url, char* tooltip, char* target,
00098   void* gobj)
00099 {
00100     obj_state_t *obj = job->obj;
00101     int flags = job->flags;
00102     int assigned = 0;
00103 
00104     if ((flags & GVRENDER_DOES_LABELS) && lbl)
00105         obj->label = lbl;
00106     if ((flags & GVRENDER_DOES_MAPS) && url && url[0]) {
00107         obj->url = strdup_and_subst_obj(url, gobj);
00108         assigned = 1;
00109     }
00110     if (flags & GVRENDER_DOES_TOOLTIPS) {
00111         if (tooltip && tooltip[0]) {
00112             obj->tooltip = strdup_and_subst_obj(tooltip, gobj);
00113             obj->explicit_tooltip = TRUE;
00114             assigned = 1;
00115         }
00116         else if (obj->label) {
00117             obj->tooltip = strdup(obj->label);
00118             assigned = 1;
00119         }
00120     }
00121     if ((flags & GVRENDER_DOES_TARGETS) && target && target[0]) {
00122         obj->target = strdup_and_subst_obj(target, gobj);
00123         assigned = 1;
00124     }
00125     return assigned;
00126 }
00127 
00128 static void
00129 initObjMapData (GVJ_t* job, textlabel_t *lab, void* gobj)
00130 {
00131     char* lbl;
00132     char* url = agget(gobj, "href");
00133     char* tooltip = agget(gobj, "tooltip");
00134     char* target = agget(gobj, "target");
00135 
00136     if (lab) lbl = lab->text;
00137     else lbl = NULL;
00138     if (!url || !*url) url = agget(gobj, "URL");
00139     initMapData (job, lbl, url, tooltip, target, gobj);
00140 }
00141 
00142 static void map_point(GVJ_t *job, point P)
00143 {
00144     obj_state_t *obj = job->obj;
00145     int flags = job->flags;
00146     pointf *p, pf;
00147 
00148     if (flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS)) {
00149         if (flags & GVRENDER_DOES_MAP_RECTANGLE) {
00150             obj->url_map_shape = MAP_RECTANGLE;
00151             obj->url_map_n = 2;
00152         }
00153         else {
00154             obj->url_map_shape = MAP_POLYGON;
00155             obj->url_map_n = 4;
00156         }
00157         free(obj->url_map_p);
00158         obj->url_map_p = p = N_NEW(obj->url_map_n, pointf);
00159         P2PF(P,pf);
00160         P2RECT(pf, p, FUZZ, FUZZ);
00161         if (! (flags & GVRENDER_DOES_TRANSFORM))
00162             gvrender_ptf_A(job, p, p, 2);
00163         if (! (flags & GVRENDER_DOES_MAP_RECTANGLE))
00164             rect2poly(p);
00165     }
00166 }
00167 
00168 void emit_map_rect(GVJ_t *job, point LL, point UR)
00169 {
00170     obj_state_t *obj = job->obj;
00171     int flags = job->flags;
00172     pointf *p;
00173 
00174     if (flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS)) {
00175         if (flags & GVRENDER_DOES_MAP_RECTANGLE) {
00176             obj->url_map_shape = MAP_RECTANGLE;
00177             obj->url_map_n = 2;
00178         }
00179         else {
00180             obj->url_map_shape = MAP_POLYGON;
00181             obj->url_map_n = 4;
00182         }
00183         free(obj->url_map_p);
00184         obj->url_map_p = p = N_NEW(obj->url_map_n, pointf);
00185         P2PF(LL,p[0]);
00186         P2PF(UR,p[1]);
00187         if (! (flags & GVRENDER_DOES_TRANSFORM))
00188             gvrender_ptf_A(job, p, p, 2);
00189         if (! (flags & GVRENDER_DOES_MAP_RECTANGLE))
00190             rect2poly(p);
00191     }
00192 }
00193 
00194 static void map_label(GVJ_t *job, textlabel_t *lab)
00195 {
00196     obj_state_t *obj = job->obj;
00197     int flags = job->flags;
00198     pointf *p;
00199 
00200     if (flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS)) {
00201         if (flags & GVRENDER_DOES_MAP_RECTANGLE) {
00202             obj->url_map_shape = MAP_RECTANGLE;
00203             obj->url_map_n = 2;
00204         }
00205         else {
00206             obj->url_map_shape = MAP_POLYGON;
00207             obj->url_map_n = 4;
00208         }
00209         free(obj->url_map_p);
00210         obj->url_map_p = p = N_NEW(obj->url_map_n, pointf);
00211         P2RECT(lab->p, p, lab->dimen.x / 2., lab->dimen.y / 2.);
00212         if (! (flags & GVRENDER_DOES_TRANSFORM))
00213             gvrender_ptf_A(job, p, p, 2);
00214         if (! (flags & GVRENDER_DOES_MAP_RECTANGLE))
00215             rect2poly(p);
00216     }
00217 }
00218 
00219 /* isRect:
00220  * isRect function returns true when polygon has
00221  * regular rectangular shape. Rectangle is regular when
00222  * it is not skewed and distorted and orientation is almost zero
00223  */
00224 static boolean isRect(polygon_t * p)
00225 {
00226     return (p->sides == 4 && (ROUND(p->orientation) % 90) == 0
00227             && p->distortion == 0.0 && p->skew == 0.0);
00228 }
00229 
00230 /*
00231  * isFilled function returns 1 if filled style has been set for node 'n'
00232  * otherwise returns 0. it accepts pointer to node_t as an argument
00233  */
00234 static int ifFilled(node_t * n)
00235 {
00236     char *style, *p, **pp;
00237     int r = 0;
00238     style = late_nnstring(n, N_style, "");
00239     if (style[0]) {
00240         pp = parse_style(style);
00241         while ((p = *pp)) {
00242             if (strcmp(p, "filled") == 0)
00243                 r = 1;
00244             pp++;
00245         }
00246     }
00247     return r;
00248 }
00249 
00250 /* pEllipse:
00251  * pEllipse function returns 'np' points from the circumference
00252  * of ellipse described by radii 'a' and 'b'.
00253  * Assumes 'np' is greater than zero.
00254  * 'np' should be at least 4 to sample polygon from ellipse
00255  */
00256 static pointf *pEllipse(double a, double b, int np)
00257 {
00258     double theta = 0.0;
00259     double deltheta = 2 * M_PI / np;
00260     int i;
00261     pointf *ps;
00262 
00263     ps = N_NEW(np, pointf);
00264     for (i = 0; i < np; i++) {
00265         ps[i].x = a * cos(theta);
00266         ps[i].y = b * sin(theta);
00267         theta += deltheta;
00268     }
00269     return ps;
00270 }
00271 
00272 #define HW 2.0   /* maximum distance away from line, in points */
00273 
00274 /* check_control_points:
00275  * check_control_points function checks the size of quadrilateral
00276  * formed by four control points
00277  * returns 1 if four points are in line (or close to line)
00278  * else return 0
00279  */
00280 static int check_control_points(pointf *cp)
00281 {
00282     double dis1 = ptToLine2 (cp[0], cp[3], cp[1]);
00283     double dis2 = ptToLine2 (cp[0], cp[3], cp[2]);
00284     if (dis1 < HW*HW && dis2 < HW*HW)
00285         return 1;
00286     else
00287         return 0;
00288 }
00289 
00290 #ifdef DEBUG
00291 static void psmapOutput (pointf* ps, int n)
00292 {
00293    int i;
00294    fprintf (stdout, "newpath %f %f moveto\n", ps[0].x, ps[0].y);
00295    for (i=1; i < n; i++)
00296         fprintf (stdout, "%f %f lineto\n", ps[i].x, ps[i].y);
00297    fprintf (stdout, "closepath stroke\n");
00298 }
00299 #endif
00300 
00301 typedef struct segitem_s {
00302     pointf p;
00303     struct segitem_s* next;
00304 } segitem_t;
00305 
00306 #define MARK_FIRST_SEG(L) ((L)->next = (segitem_t*)1)
00307 #define FIRST_SEG(L) ((L)->next == (segitem_t*)1)
00308 #define INIT_SEG(P,L) {(L)->next = 0; (L)->p = P;} 
00309 
00310 static segitem_t* appendSeg (pointf p, segitem_t* lp)
00311 {
00312     segitem_t* s = GNEW(segitem_t);
00313     INIT_SEG (p, s);
00314     lp->next = s;
00315     return s;
00316 }
00317 
00318 /* map_bspline_poly:
00319  * Output the polygon determined by the n points in p1, followed
00320  * by the n points in p2 in reverse order. Assumes n <= 50.
00321  */
00322 static void map_bspline_poly(pointf **pbs_p, int **pbs_n, int *pbs_poly_n, int n, pointf* p1, pointf* p2)
00323 {
00324     int i = 0, nump = 0, last = 2*n-1;
00325 
00326     for ( ; i < *pbs_poly_n; i++)
00327         nump += (*pbs_n)[i];
00328 
00329     (*pbs_poly_n)++;
00330     *pbs_n = grealloc(*pbs_n, (*pbs_poly_n) * sizeof(int));
00331     (*pbs_n)[i] = 2*n;
00332     *pbs_p = grealloc(*pbs_p, (nump + 2*n) * sizeof(pointf));
00333 
00334     for (i = 0; i < n; i++) {
00335         (*pbs_p)[nump+i] = p1[i];
00336         (*pbs_p)[nump+last-i] = p2[i];
00337     }
00338 #ifdef DEBUG
00339     psmapOutput (*pbs_p + nump, last+1);
00340 #endif
00341 }
00342 
00343 /* approx_bezier:
00344  * Approximate Bezier by line segments. If the four points are
00345  * almost colinear, as determined by check_control_points, we store
00346  * the segment cp[0]-cp[3]. Otherwise we split the Bezier into 2 and recurse. 
00347  * Since 2 contiguous segments share an endpoint, we actually store
00348  * the segments as a list of points.
00349  * New points are appended to the list given by lp. The tail of the
00350  * list is returned.
00351  */
00352 static segitem_t* approx_bezier (pointf *cp, segitem_t* lp)
00353 {
00354     pointf sub_curves[8];
00355 
00356     if (check_control_points(cp)) {
00357         if (FIRST_SEG (lp)) INIT_SEG (cp[0], lp);
00358         lp = appendSeg (cp[3], lp);
00359     }
00360     else {
00361         Bezier (cp, 3, 0.5, sub_curves, sub_curves+4);
00362         lp = approx_bezier (sub_curves, lp);
00363         lp = approx_bezier (sub_curves+4, lp);
00364     }
00365     return lp;
00366 }
00367 
00368 /* bisect:
00369  * Return the angle of the bisector between the two rays
00370  * pp-cp and cp-np. The bisector returned is always to the
00371  * left of pp-cp-np.
00372  */
00373 static double bisect (pointf pp, pointf cp, pointf np)
00374 {
00375   double ang, theta, phi;
00376   theta = atan2(np.y - cp.y,np.x - cp.x);
00377   phi = atan2(pp.y - cp.y,pp.x - cp.x);
00378   ang = theta - phi;
00379   if (ang > 0) ang -= 2*M_PI;
00380 
00381   return (phi + ang/2.0);
00382 }
00383 
00384 /* mkSegPts:
00385  * Determine polygon points related to 2 segments prv-cur and cur-nxt.
00386  * The points lie on the bisector of the 2 segments, passing through cur,
00387  * and distance w2 from cur. The points are stored in p1 and p2.
00388  * If p1 is NULL, we use the normal to cur-nxt.
00389  * If p2 is NULL, we use the normal to prv-cur.
00390  * Assume at least one of prv or nxt is non-NULL.
00391  */
00392 static void mkSegPts (segitem_t* prv, segitem_t* cur, segitem_t* nxt,
00393         pointf* p1, pointf* p2, double w2)
00394 {
00395     pointf cp, pp, np;
00396     double theta, delx, dely;
00397     pointf p;
00398 
00399     cp = cur->p;
00400     /* if prv or nxt are NULL, use the one given to create a collinear
00401      * prv or nxt. This could be more efficiently done with special case code, 
00402      * but this way is more uniform.
00403      */
00404     if (prv) {
00405         pp = prv->p;
00406         if (nxt)
00407             np = nxt->p;
00408         else {
00409             np.x = 2*cp.x - pp.x;
00410             np.y = 2*cp.y - pp.y;
00411         }
00412     }
00413     else {
00414         np = nxt->p;
00415         pp.x = 2*cp.x - np.x;
00416         pp.y = 2*cp.y - np.y;
00417     }
00418     theta = bisect(pp,cp,np);
00419     delx = w2*cos(theta);
00420     dely = w2*sin(theta);
00421     p.x = cp.x + delx;
00422     p.y = cp.y + dely;
00423     *p1 = p;
00424     p.x = cp.x - delx;
00425     p.y = cp.y - dely;
00426     *p2 = p;
00427 }
00428 
00429 /* map_output_bspline:
00430  * Construct and output a closed polygon approximating the input
00431  * B-spline bp. We do this by first approximating bp by a sequence
00432  * of line segments. We then use the sequence of segments to determine
00433  * the polygon.
00434  * In cmapx, polygons are limited to 100 points, so we output polygons
00435  * in chunks of 100.
00436  */
00437 static void map_output_bspline (pointf **pbs, int **pbs_n, int *pbs_poly_n, bezier* bp, double w2)
00438 {
00439     segitem_t* segl = GNEW(segitem_t);
00440     segitem_t* segp = segl;
00441     segitem_t* segprev;
00442     segitem_t* segnext;
00443     int nc, j, k, cnt;
00444     pointf pts[4];
00445     pointf pt1[50], pt2[50];
00446 
00447     MARK_FIRST_SEG(segl);
00448     nc = (bp->size - 1)/3; /* nc is number of bezier curves */
00449     for (j = 0; j < nc; j++) {
00450         for (k = 0; k < 4; k++) {
00451             pts[k].x = (double)bp->list[3*j + k].x;
00452             pts[k].y = (double)bp->list[3*j + k].y;
00453         }
00454         segp = approx_bezier (pts, segp);
00455     }
00456 
00457     segp = segl;
00458     segprev = 0;
00459     cnt = 0;
00460     while (segp) {
00461         segnext = segp->next;
00462         mkSegPts (segprev, segp, segnext, pt1+cnt, pt2+cnt, w2);
00463         cnt++;
00464         if ((segnext == NULL) || (cnt == 50)) {
00465             map_bspline_poly (pbs, pbs_n, pbs_poly_n, cnt, pt1, pt2);
00466             pt1[0] = pt1[cnt-1];
00467             pt2[0] = pt2[cnt-1];
00468             cnt = 1;
00469         }
00470         segprev = segp;
00471         segp = segnext;
00472     }
00473 
00474     /* free segl */
00475     while (segl) {
00476         segp = segl->next;
00477         free (segl);
00478         segl = segp;
00479     }
00480 }
00481 
00482 
00483 /* parse_layers:
00484  * Split input string into tokens, with separators specified by
00485  * the layersep attribute. Store the values in the gvc->layerIDs array,
00486  * starting at index 1, and return the count.
00487  * Free previously stored list. Note that there is no mechanism
00488  * to free the memory before exit.
00489  */
00490 static int parse_layers(GVC_t *gvc, graph_t * g, char *p)
00491 {
00492     int ntok;
00493     char *tok;
00494     int sz;
00495 
00496     gvc->layerDelims = agget(g, "layersep");
00497     if (!gvc->layerDelims)
00498         gvc->layerDelims = DEFAULT_LAYERSEP;
00499 
00500     ntok = 0;
00501     sz = 0;
00502     gvc->layers = strdup(p);
00503 
00504     for (tok = strtok(gvc->layers, gvc->layerDelims); tok;
00505          tok = strtok(NULL, gvc->layerDelims)) {
00506         ntok++;
00507         if (ntok > sz) {
00508             sz += SMALLBUF;
00509             gvc->layerIDs = ALLOC(sz, gvc->layerIDs, char *);
00510         }
00511         gvc->layerIDs[ntok] = tok;
00512     }
00513     if (ntok) {
00514         gvc->layerIDs = RALLOC(ntok + 2, gvc->layerIDs, char *);        /* shrink to minimum size */
00515         gvc->layerIDs[0] = NULL;
00516         gvc->layerIDs[ntok + 1] = NULL;
00517     }
00518 
00519     return ntok;
00520 }
00521 
00522 /* chkOrder:
00523  * Determine order of output.
00524  * Output usually in breadth first graph walk order
00525  */
00526 static int chkOrder(graph_t * g)
00527 {
00528     char *p = agget(g, "outputorder");
00529     if (p) {
00530         char c = *p;
00531         if ((c == 'n') && !strcmp(p + 1, "odesfirst"))
00532             return EMIT_SORTED;
00533         if ((c == 'e') && !strcmp(p + 1, "dgesfirst"))
00534             return EMIT_EDGE_SORTED;
00535     }
00536     return 0;
00537 }
00538 
00539 static void init_layering(GVC_t * gvc, graph_t * g)
00540 {
00541     char *str;
00542 
00543     /* free layer strings and pointers from previous graph */
00544     if (gvc->layers)
00545         free(gvc->layers);
00546     if (gvc->layerIDs)
00547         free(gvc->layerIDs);
00548 
00549     if ((str = agget(g, "layers")) != 0) {
00550         gvc->numLayers = parse_layers(gvc, g, str);
00551     } else {
00552         gvc->layerIDs = NULL;
00553         gvc->numLayers = 1;
00554     }
00555 }
00556 
00557 static void firstlayer(GVJ_t *job)
00558 {
00559     job->numLayers = job->gvc->numLayers;
00560     if ((job->numLayers > 1)
00561                 && (! (job->flags & GVDEVICE_DOES_LAYERS))) {
00562         agerr(AGWARN, "layers not supported in %s output\n",
00563                 job->output_langname);
00564         job->numLayers = 1;
00565     }
00566 
00567     job->layerNum = 1;
00568 }
00569 
00570 static boolean validlayer(GVJ_t *job)
00571 {
00572     return (job->layerNum <= job->numLayers);
00573 }
00574 
00575 static void nextlayer(GVJ_t *job)
00576 {
00577     job->layerNum++;
00578 }
00579 
00580 static point pagecode(GVJ_t *job, char c)
00581 {
00582     point rv;
00583     rv.x = rv.y = 0;
00584     switch (c) {
00585     case 'T':
00586         job->pagesArrayFirst.y = job->pagesArraySize.y - 1;
00587         rv.y = -1;
00588         break;
00589     case 'B':
00590         rv.y = 1;
00591         break;
00592     case 'L':
00593         rv.x = 1;
00594         break;
00595     case 'R':
00596         job->pagesArrayFirst.x = job->pagesArraySize.x - 1;
00597         rv.x = -1;
00598         break;
00599     }
00600     return rv;
00601 }
00602 
00603 static void init_job_pagination(GVJ_t * job, graph_t *g)
00604 {
00605     GVC_t *gvc = job->gvc;
00606     pointf pageSize;    /* page size for the graph - points*/
00607     pointf imageSize;   /* image size on one page of the graph - points */
00608     pointf margin;      /* margin for a page of the graph - points */
00609     pointf centering = {0.0, 0.0}; /* centering offset - points */
00610 
00611     /* unpaginated image size - in points - in graph orientation */
00612     imageSize = job->view;
00613 
00614     /* rotate imageSize to page orientation */
00615     if (job->rotation)
00616         imageSize = exch_xyf(imageSize);
00617 
00618     /* margin - in points - in page orientation */
00619     margin = job->margin;
00620 
00621     /* determine pagination */
00622     if (gvc->graph_sets_pageSize) {
00623         /* page was set by user */
00624 
00625         /* determine size of page for image */
00626         pageSize.x = gvc->pageSize.x - 2 * margin.x;
00627         pageSize.y = gvc->pageSize.y - 2 * margin.y;
00628 
00629         if (pageSize.x < EPSILON)
00630             job->pagesArraySize.x = 1;
00631         else {
00632             job->pagesArraySize.x = (int)(imageSize.x / pageSize.x);
00633             if ((imageSize.x - (job->pagesArraySize.x * pageSize.x)) > EPSILON)
00634                 job->pagesArraySize.x++;
00635         }
00636         if (pageSize.y < EPSILON)
00637             job->pagesArraySize.y = 1;
00638         else {
00639             job->pagesArraySize.y = (int)(imageSize.y / pageSize.y);
00640             if ((imageSize.y - (job->pagesArraySize.y * pageSize.y)) > EPSILON)
00641                 job->pagesArraySize.y++;
00642         }
00643         job->numPages = job->pagesArraySize.x * job->pagesArraySize.y;
00644 
00645         /* find the drawable size in points */
00646         imageSize.x = MIN(imageSize.x, pageSize.x);
00647         imageSize.y = MIN(imageSize.y, pageSize.y);
00648     } else {
00649         /* page not set by user, use default from renderer */
00650         if (job->render.features) {
00651             pageSize.x = job->device.features->default_pagesize.x - 2*margin.x;
00652             if (pageSize.x < 0.)
00653                 pageSize.x = 0.;
00654             pageSize.y = job->device.features->default_pagesize.y - 2*margin.y;
00655             if (pageSize.y < 0.)
00656                 pageSize.y = 0.;
00657         }
00658         else
00659             pageSize.x = pageSize.y = 0.;
00660         job->pagesArraySize.x = job->pagesArraySize.y = job->numPages = 1;
00661         
00662         if (pageSize.x < imageSize.x)
00663             pageSize.x = imageSize.x;
00664         if (pageSize.y < imageSize.y)
00665             pageSize.y = imageSize.y;
00666     }
00667 
00668     /* initial window size */
00669     job->width = ROUND((pageSize.x + 2*margin.x) * job->dpi.x / POINTS_PER_INCH);
00670     job->height = ROUND((pageSize.y + 2*margin.y) * job->dpi.y / POINTS_PER_INCH);
00671 
00672     /* set up pagedir */
00673     job->pagesArrayMajor.x = job->pagesArrayMajor.y 
00674                 = job->pagesArrayMinor.x = job->pagesArrayMinor.y = 0;
00675     job->pagesArrayFirst.x = job->pagesArrayFirst.y = 0;
00676     job->pagesArrayMajor = pagecode(job, gvc->pagedir[0]);
00677     job->pagesArrayMinor = pagecode(job, gvc->pagedir[1]);
00678     if ((abs(job->pagesArrayMajor.x + job->pagesArrayMinor.x) != 1)
00679      || (abs(job->pagesArrayMajor.y + job->pagesArrayMinor.y) != 1)) {
00680         job->pagesArrayMajor = pagecode(job, 'B');
00681         job->pagesArrayMinor = pagecode(job, 'L');
00682         agerr(AGWARN, "pagedir=%s ignored\n", gvc->pagedir);
00683     }
00684 
00685     /* determine page box including centering */
00686     if (GD_drawing(g)->centered) {
00687         if (pageSize.x > imageSize.x)
00688             centering.x = (pageSize.x - imageSize.x) / 2;
00689         if (pageSize.y > imageSize.y)
00690             centering.y = (pageSize.y - imageSize.y) / 2;
00691     }
00692 
00693     /* rotate back into graph orientation */
00694     if (job->rotation) {
00695         imageSize = exch_xyf(imageSize);
00696         pageSize = exch_xyf(pageSize);
00697         margin = exch_xyf(margin);
00698         centering = exch_xyf(centering);
00699     }
00700 
00701     /* canvas area, centered if necessary */
00702     job->canvasBox.LL.x = margin.x + centering.x;
00703     job->canvasBox.LL.y = margin.y + centering.y;
00704     job->canvasBox.UR.x = margin.x + centering.x + imageSize.x;
00705     job->canvasBox.UR.y = margin.y + centering.y + imageSize.y;
00706 
00707     /* size of one page in graph units */
00708     job->pageSize.x = imageSize.x / job->zoom;
00709     job->pageSize.y = imageSize.y / job->zoom;
00710 }
00711 
00712 static void firstpage(GVJ_t *job)
00713 {
00714     job->pagesArrayElem = job->pagesArrayFirst;
00715 }
00716 
00717 static boolean validpage(GVJ_t *job)
00718 {
00719     return ((job->pagesArrayElem.x >= 0)
00720          && (job->pagesArrayElem.x < job->pagesArraySize.x)
00721          && (job->pagesArrayElem.y >= 0)
00722          && (job->pagesArrayElem.y < job->pagesArraySize.y));
00723 }
00724 
00725 static void nextpage(GVJ_t *job)
00726 {
00727     job->pagesArrayElem = add_points(job->pagesArrayElem, job->pagesArrayMinor);
00728     if (validpage(job) == FALSE) {
00729         if (job->pagesArrayMajor.y)
00730             job->pagesArrayElem.x = job->pagesArrayFirst.x;
00731         else
00732             job->pagesArrayElem.y = job->pagesArrayFirst.y;
00733         job->pagesArrayElem = add_points(job->pagesArrayElem, job->pagesArrayMajor);
00734     }
00735 }
00736 
00737 static boolean write_edge_test(Agraph_t * g, Agedge_t * e)
00738 {
00739     Agraph_t *sg;
00740     int c;
00741 
00742     for (c = 1; c <= GD_n_cluster(g); c++) {
00743         sg = GD_clust(g)[c];
00744         if (agcontains(sg, e))
00745             return FALSE;
00746     }
00747     return TRUE;
00748 }
00749 
00750 static boolean write_node_test(Agraph_t * g, Agnode_t * n)
00751 {
00752     Agraph_t *sg;
00753     int c;
00754 
00755     for (c = 1; c <= GD_n_cluster(g); c++) {
00756         sg = GD_clust(g)[c];
00757         if (agcontains(sg, n))
00758             return FALSE;
00759     }
00760     return TRUE;
00761 }
00762 
00763 void emit_background(GVJ_t * job, graph_t *g)
00764 {
00765     char *str;
00766     
00767     if (! ((str = agget(g, "bgcolor")) && str[0])) {
00768         if (job->flags & GVRENDER_NO_BG)
00769             str = "transparent";
00770         else
00771             str = "white";
00772     }
00773 
00774     gvrender_set_fillcolor(job, str);
00775     gvrender_set_pencolor(job, str);
00776     gvrender_box(job, job->clip, TRUE); /* filled */
00777 }
00778 
00779 static void setup_page(GVJ_t * job, graph_t * g)
00780 {
00781     point pagesArrayElem = job->pagesArrayElem, pagesArraySize = job->pagesArraySize;
00782         
00783     if (job->rotation) {
00784         pagesArrayElem = exch_xy(pagesArrayElem);
00785         pagesArraySize = exch_xy(pagesArraySize);
00786     }
00787 
00788     /* establish current box in graph units */
00789     job->pageBox.LL.x = pagesArrayElem.x * job->pageSize.x - job->pad.x;
00790     job->pageBox.LL.y = pagesArrayElem.y * job->pageSize.y - job->pad.y;
00791     job->pageBox.UR.x = job->pageBox.LL.x + job->pageSize.x;
00792     job->pageBox.UR.y = job->pageBox.LL.y + job->pageSize.y;
00793 
00794     /* pageBoundingBox in device units and page orientation */
00795     job->pageBoundingBox.LL.x = ROUND(job->canvasBox.LL.x * job->dpi.x / POINTS_PER_INCH);
00796     job->pageBoundingBox.LL.y = ROUND(job->canvasBox.LL.y * job->dpi.y / POINTS_PER_INCH);
00797     job->pageBoundingBox.UR.x = ROUND(job->canvasBox.UR.x * job->dpi.x / POINTS_PER_INCH);
00798     job->pageBoundingBox.UR.y = ROUND(job->canvasBox.UR.y * job->dpi.y / POINTS_PER_INCH);
00799     if (job->rotation) {
00800         job->pageBoundingBox.LL = exch_xy(job->pageBoundingBox.LL);
00801         job->pageBoundingBox.UR = exch_xy(job->pageBoundingBox.UR);
00802     }
00803         
00804     /* maximum boundingBox in device units and page orientation */
00805     if (job->common->viewNum == 0)
00806         job->boundingBox = job->pageBoundingBox;
00807     else
00808         EXPANDBB(job->boundingBox, job->pageBoundingBox);
00809 
00810 
00811     /* CAUTION - job->translation was difficult to get right. */
00812     /* Test with and without assymetric margins, e.g: -Gmargin="1,0" */
00813     job->pageOffset.x = - job->pageSize.x * pagesArrayElem.x;
00814     job->pageOffset.y = - job->pageSize.y * pagesArrayElem.y;
00815     if (job->rotation) {
00816         job->clip.LL.x = job->focus.x - job->pageOffset.x - pagesArraySize.x * job->pageSize.x / 2.;
00817         job->clip.UR.y = job->focus.y + job->pageOffset.y + pagesArraySize.y * job->pageSize.y / 2.;
00818         job->clip.UR.x = job->clip.LL.x + job->view.x - 2 * job->margin.y / job->zoom;
00819         job->clip.LL.y = job->clip.UR.y - job->view.y + 2 * job->margin.x / job->zoom;
00820         job->translation.y = - job->clip.UR.y - job->canvasBox.LL.y / job->zoom;
00821         if ((job->flags & GVRENDER_Y_GOES_DOWN) || (Y_invert))
00822             job->translation.x = - job->clip.UR.x - job->canvasBox.LL.x / job->zoom;
00823         else
00824             job->translation.x = - job->clip.LL.x + job->canvasBox.LL.x / job->zoom;
00825     }
00826     else {
00827         job->clip.LL.x = job->focus.x - job->pageOffset.x - pagesArraySize.x * job->pageSize.x / 2.;
00828         job->clip.LL.y = job->focus.y - job->pageOffset.y - pagesArraySize.y * job->pageSize.y / 2.;
00829         job->clip.UR.x = job->clip.LL.x + job->view.x - 2 * job->margin.x / job->zoom;
00830         job->clip.UR.y = job->clip.LL.y + job->view.y - 2 * job->margin.y / job->zoom;
00831         /* pre unscale margins to keep them constant under scaling */
00832         job->translation.x = - job->clip.LL.x + job->canvasBox.LL.x / job->zoom;
00833         if ((job->flags & GVRENDER_Y_GOES_DOWN) || (Y_invert))
00834             job->translation.y = - job->clip.UR.y - job->canvasBox.LL.y / job->zoom;
00835         else
00836             job->translation.y = - job->clip.LL.y + job->canvasBox.LL.y / job->zoom;
00837     }
00838 
00839 #if 0
00840 fprintf(stderr,"width=%d height=%d dpi=%g,%g\npad=%g,%g focus=%g,%g view=%g,%g zoom=%g\npageBox=%g,%g,%g,%g pagesArraySize=%d,%d pageSize=%g,%g canvasBox=%g,%g,%g,%g pageOffset=%g,%g\ntranslation=%g,%g clip=%g,%g,%g,%g margin=%g,%g\n",
00841         job->width, job->height,
00842         job->dpi.x, job->dpi.y,
00843         job->pad.x, job->pad.y,
00844         job->focus.x, job->focus.y,
00845         job->view.x, job->view.y,
00846         job->zoom,
00847         job->pageBox.LL.x, job->pageBox.LL.y, job->pageBox.UR.x, job->pageBox.UR.y,
00848         job->pagesArraySize.x, job->pagesArraySize.y,
00849         job->pageSize.x, job->pageSize.y,
00850         job->canvasBox.LL.x, job->canvasBox.LL.y, job->canvasBox.UR.x, job->canvasBox.UR.y,
00851         job->pageOffset.x, job->pageOffset.y,
00852         job->translation.x, job->translation.y,
00853         job->clip.LL.x, job->clip.LL.y, job->clip.UR.x, job->clip.UR.y,
00854         job->margin.x, job->margin.y);
00855 #endif
00856 }
00857 
00858 static boolean is_natural_number(char *sstr)
00859 {
00860     unsigned char *str = (unsigned char *) sstr;
00861 
00862     while (*str)
00863         if (NOT(isdigit(*str++)))
00864             return FALSE;
00865     return TRUE;
00866 }
00867 
00868 static int layer_index(GVC_t *gvc, char *str, int all)
00869 {
00870     GVJ_t *job = gvc->job;
00871     int i;
00872 
00873     if (streq(str, "all"))
00874         return all;
00875     if (is_natural_number(str))
00876         return atoi(str);
00877     if (gvc->layerIDs)
00878         for (i = 1; i <= job->numLayers; i++)
00879             if (streq(str, gvc->layerIDs[i]))
00880                 return i;
00881     return -1;
00882 }
00883 
00884 static boolean selectedlayer(GVJ_t *job, char *spec)
00885 {
00886     GVC_t *gvc = job->gvc;
00887     int n0, n1;
00888     unsigned char buf[SMALLBUF];
00889     char *w0, *w1;
00890     agxbuf xb;
00891     boolean rval = FALSE;
00892 
00893     agxbinit(&xb, SMALLBUF, buf);
00894     agxbput(&xb, spec);
00895     w1 = w0 = strtok(agxbuse(&xb), gvc->layerDelims);
00896     if (w0)
00897         w1 = strtok(NULL, gvc->layerDelims);
00898     switch ((w0 != NULL) + (w1 != NULL)) {
00899     case 0:
00900         rval = FALSE;
00901         break;
00902     case 1:
00903         n0 = layer_index(gvc, w0, job->layerNum);
00904         rval = (n0 == job->layerNum);
00905         break;
00906     case 2:
00907         n0 = layer_index(gvc, w0, 0);
00908         n1 = layer_index(gvc, w1, job->numLayers);
00909         if ((n0 < 0) || (n1 < 0))
00910             rval = TRUE;
00911         else if (n0 > n1) {
00912             int t = n0;
00913             n0 = n1;
00914             n1 = t;
00915         }
00916         rval = BETWEEN(n0, job->layerNum, n1);
00917         break;
00918     }
00919     agxbfree(&xb);
00920     return rval;
00921 }
00922 
00923 static boolean node_in_layer(GVJ_t *job, graph_t * g, node_t * n)
00924 {
00925     char *pn, *pe;
00926     edge_t *e;
00927 
00928     if (job->numLayers <= 1)
00929         return TRUE;
00930     pn = late_string(n, N_layer, "");
00931     if (selectedlayer(job, pn))
00932         return TRUE;
00933     if (pn[0])
00934         return FALSE;           /* Only check edges if pn = "" */
00935     if ((e = agfstedge(g, n)) == NULL)
00936         return TRUE;
00937     for (e = agfstedge(g, n); e; e = agnxtedge(g, e, n)) {
00938         pe = late_string(e, E_layer, "");
00939         if ((pe[0] == '\0') || selectedlayer(job, pe))
00940             return TRUE;
00941     }
00942     return FALSE;
00943 }
00944 
00945 static boolean edge_in_layer(GVJ_t *job, graph_t * g, edge_t * e)
00946 {
00947     char *pe, *pn;
00948     int cnt;
00949 
00950     if (job->numLayers <= 1)
00951         return TRUE;
00952     pe = late_string(e, E_layer, "");
00953     if (selectedlayer(job, pe))
00954         return TRUE;
00955     if (pe[0])
00956         return FALSE;
00957     for (cnt = 0; cnt < 2; cnt++) {
00958         pn = late_string(cnt < 1 ? e->tail : e->head, N_layer, "");
00959         if ((pn[0] == '\0') || selectedlayer(job, pn))
00960             return TRUE;
00961     }
00962     return FALSE;
00963 }
00964 
00965 static boolean clust_in_layer(GVJ_t *job, graph_t * sg)
00966 {
00967     char *pg;
00968     node_t *n;
00969 
00970     if (job->numLayers <= 1)
00971         return TRUE;
00972     pg = late_string(sg, agfindattr(sg, "layer"), "");
00973     if (selectedlayer(job, pg))
00974         return TRUE;
00975     if (pg[0])
00976         return FALSE;
00977     for (n = agfstnode(sg); n; n = agnxtnode(sg, n))
00978         if (node_in_layer(job, sg, n))
00979             return TRUE;
00980     return FALSE;
00981 }
00982 
00983 static boolean node_in_box(node_t *n, boxf b)
00984 {
00985     return boxf_overlap(ND_bb(n), b);
00986 }
00987 
00988 static void emit_begin_node(GVJ_t * job, node_t * n)
00989 {
00990     obj_state_t *obj;
00991     int flags = job->flags;
00992     int sides, peripheries, i, j, filled = 0, rect = 0, shape, nump = 0;
00993     polygon_t *poly = NULL;
00994     pointf *vertices, ldimen, *p = NULL;
00995     point coord;
00996     char *s;
00997 
00998     obj = push_obj_state(job);
00999     obj->type = NODE_OBJTYPE;
01000     obj->u.n = n;
01001     obj->emit_state = EMIT_NDRAW;
01002 
01003     if (flags & GVRENDER_DOES_Z) {
01004         obj->z = late_double(n, N_z, 0.0, -MAXFLOAT);
01005     }
01006     initObjMapData (job, ND_label(n), n);
01007     if ((flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS))
01008            && (obj->url || obj->explicit_tooltip)) {
01009 
01010         /* checking shape of node */
01011         shape = shapeOf(n);
01012         /* node coordinate */
01013         coord = ND_coord_i(n);
01014         /* checking if filled style has been set for node */
01015         filled = ifFilled(n);
01016 
01017         if (shape == SH_POLY || shape == SH_POINT) {
01018             poly = (polygon_t *) ND_shape_info(n);
01019 
01020             /* checking if polygon is regular rectangle */
01021             if (isRect(poly) && (poly->peripheries || filled))
01022                 rect = 1;
01023         }
01024 
01025         /* When node has polygon shape and requested output supports polygons
01026          * we use a polygon to map the clickable region that is a:
01027          * circle, ellipse, polygon with n side, or point.
01028          * For regular rectangular shape we have use node's bounding box to map clickable region
01029          */
01030         if (poly && !rect && (flags & GVRENDER_DOES_MAP_POLYGON)) {
01031 
01032             if (poly->sides < 3)
01033                 sides = 1;
01034             else
01035                 sides = poly->sides;
01036 
01037             if (poly->peripheries < 2)
01038                 peripheries = 1;
01039             else
01040                 peripheries = poly->peripheries;
01041 
01042             vertices = poly->vertices;
01043 
01044             if ((s = agget(n, "samplepoints")))
01045                 nump = atoi(s);
01046             /* We want at least 4 points. For server-side maps, at most 100
01047              * points are allowed. To simplify things to fit with the 120 points
01048              * used for skewed ellipses, we set the bound at 60.
01049              */
01050             if ((nump < 4) || (nump > 60))
01051                 nump = DFLT_SAMPLE;
01052             /* use bounding box of text label for mapping
01053              * when polygon has no peripheries and node is not filled
01054              */
01055             if (poly->peripheries == 0 && !filled) {
01056                 obj->url_map_shape = MAP_RECTANGLE;
01057                 nump = 2;
01058                 p = N_NEW(nump, pointf);
01059                 ldimen = ND_label(n)->dimen;
01060                 P2RECT(coord, p, ldimen.x / 2.0, ldimen.y / 2.0);
01061             }
01062             /* circle or ellipse */
01063             else if (poly->sides < 3 && poly->skew == 0.0 && poly->distortion == 0.0) {
01064                 if (poly->regular) {
01065                     obj->url_map_shape = MAP_CIRCLE;
01066                     nump = 2;              /* center of circle and top right corner of bb */
01067                     p = N_NEW(nump, pointf);
01068                     p[0].x = coord.x;
01069                     p[0].y = coord.y;
01070                     /* ... but vertices contains LL cornet of bb */
01071                     p[1].x = coord.x - vertices[peripheries - 1].x;
01072                     p[1].y = coord.y - vertices[peripheries - 1].y;
01073                 }
01074                 else { /* ellipse is treated as polygon */
01075                     obj->url_map_shape= MAP_POLYGON;
01076                     p = pEllipse((double)(vertices[peripheries - 1].x),
01077                                  (double)(vertices[peripheries - 1].y), nump);
01078                     for (i = 0; i < nump; i++) {
01079                         p[i].x += coord.x;
01080                         p[i].y += coord.y;
01081                     }
01082                 }
01083             }
01084             /* all other polygonal shape */
01085             else {
01086                 int offset = (peripheries - 1)*(poly->sides);
01087                 obj->url_map_shape = MAP_POLYGON;
01088                 /* distorted or skewed ellipses and circles are polygons with 120
01089                  * sides. For mapping we convert them into polygon with sample sides
01090                  */
01091                 if (poly->sides >= nump) {
01092                     int delta = poly->sides / nump;
01093                     p = N_NEW(nump, pointf);
01094                     for (i = 0, j = 0; j < nump; i += delta, j++) {
01095                         p[j].x = coord.x + vertices[i + offset].x;
01096                         p[j].y = coord.y + vertices[i + offset].y;
01097                     }
01098                 } else {
01099                     nump = sides;
01100                     p = N_NEW(nump, pointf);
01101                     for (i = 0; i < nump; i++) {
01102                         p[i].x = coord.x + vertices[i + offset].x;
01103                         p[i].y = coord.y + vertices[i + offset].y;
01104                     }
01105                 }
01106             }
01107         }
01108         else {
01109             /* we have to use the node's bounding box to map clickable region
01110              * when requested output format is not capable of polygons.
01111              */
01112             obj->url_map_shape = MAP_RECTANGLE;
01113             nump = 2;
01114             p = N_NEW(nump, pointf);
01115             p[0].x = coord.x - ND_lw_i(n);
01116             p[0].y = coord.y - (ND_ht_i(n) / 2);
01117             p[1].x = coord.x + ND_rw_i(n);
01118             p[1].y = coord.y + (ND_ht_i(n) / 2);
01119         }
01120         if (! (flags & GVRENDER_DOES_TRANSFORM))
01121             gvrender_ptf_A(job, p, p, nump);
01122         obj->url_map_p = p;
01123         obj->url_map_n = nump;
01124     }
01125 
01126 #ifdef WITH_CODEGENS
01127     Obj = NODE;
01128 #endif
01129     setColorScheme (agget (n, "colorscheme"));
01130     gvrender_begin_context(job);
01131     gvrender_begin_node(job, n);
01132 }
01133 
01134 static void emit_end_node(GVJ_t * job)
01135 {
01136     gvrender_end_node(job);
01137     gvrender_end_context(job);
01138 #ifdef WITH_CODEGENS
01139     Obj = NONE;
01140 #endif
01141     pop_obj_state(job);
01142 }
01143 
01144 static void emit_node(GVJ_t * job, node_t * n)
01145 {
01146     GVC_t *gvc = job->gvc;
01147     char *s;
01148 
01149     if (ND_shape(n)                                  /* node has a shape */
01150             && node_in_layer(job, n->graph, n)       /* and is in layer */
01151             && node_in_box(n, job->clip)             /* and is in page/view */
01152             && (ND_state(n) != gvc->common.viewNum)) /* and not already drawn */
01153     {
01154         ND_state(n) = gvc->common.viewNum;           /* mark node as drawn */
01155 
01156         gvrender_comment(job, n->name);
01157         s = late_string(n, N_comment, "");
01158         if (s[0])
01159             gvrender_comment(job, s);
01160         
01161         emit_begin_node(job, n);
01162         ND_shape(n)->fns->codefn(job, n);
01163         emit_end_node(job);
01164     }
01165 }
01166 
01167 /* calculate an offset vector, length d, perpendicular to line p,q */
01168 static pointf computeoffset_p(pointf p, pointf q, double d)
01169 {
01170     pointf res;
01171     double x = p.x - q.x, y = p.y - q.y;
01172 
01173     /* keep d finite as line length approaches 0 */
01174     d /= sqrt(x * x + y * y + EPSILON);
01175     res.x = y * d;
01176     res.y = -x * d;
01177     return res;
01178 }
01179 
01180 /* calculate offset vector, length d, perpendicular to spline p,q,r,s at q&r */
01181 static pointf computeoffset_qr(pointf p, pointf q, pointf r, pointf s,
01182                                double d)
01183 {
01184     pointf res;
01185     double len;
01186     double x = q.x - r.x, y = q.y - r.y;
01187 
01188     len = sqrt(x * x + y * y);
01189     if (len < EPSILON) {
01190         /* control points are on top of each other
01191            use slope between endpoints instead */
01192         x = p.x - s.x, y = p.y - s.y;
01193         /* keep d finite as line length approaches 0 */
01194         len = sqrt(x * x + y * y + EPSILON);
01195     }
01196     d /= len;
01197     res.x = y * d;
01198     res.y = -x * d;
01199     return res;
01200 }
01201 
01202 static void emit_attachment(GVJ_t * job, textlabel_t * lp, splines * spl)
01203 {
01204     pointf sz, AF[3];
01205     point p;
01206     unsigned char *s;
01207 
01208     for (s = (unsigned char *) (lp->text); *s; s++) {
01209         if (isspace(*s) == FALSE)
01210             break;
01211     }
01212     if (*s == 0)
01213         return;
01214 
01215     sz = lp->dimen;
01216     AF[0] = pointfof((double)(lp->p.x) + sz.x / 2., (double)(lp->p.y) - sz.y / 2.);
01217     AF[1] = pointfof(AF[0].x - sz.x, AF[0].y);
01218     p = dotneato_closest(spl, lp->p);
01219     P2PF(p,AF[2]);
01220     /* Don't use edge style to draw attachment */
01221     gvrender_set_style(job, job->gvc->defaultlinestyle);
01222     /* Use font color to draw attachment
01223        - need something unambiguous in case of multicolored parallel edges
01224        - defaults to black for html-like labels
01225      */
01226     gvrender_set_pencolor(job, lp->fontcolor);
01227     gvrender_polyline(job, AF, 3);
01228 }
01229 
01230 /* edges colors can be mutiple colors separated by ":"
01231  * so we commpute a default pencolor with the same number of colors. */
01232 static char* default_pencolor(char *pencolor, char *deflt)
01233 {
01234     static char *buf;
01235     static int bufsz;
01236     char *p;
01237     int len, ncol;
01238 
01239     ncol = 1;
01240     for (p = pencolor; *p; p++) {
01241         if (*p == ':')
01242             ncol++;
01243     }
01244     len = ncol * (strlen(deflt) + 1);
01245     if (bufsz < len) {
01246         bufsz = len + 10;
01247         buf = realloc(buf, bufsz);
01248     }
01249     strcpy(buf, deflt);
01250     while(--ncol) {
01251         strcat(buf, ":");
01252         strcat(buf, deflt);
01253     }
01254     return buf;
01255 }
01256 
01257 static void emit_edge_graphics(GVJ_t * job, edge_t * e, char** styles)
01258 {
01259     int i, j, cnum, numc = 0;
01260     char *color, *pencolor, *fillcolor;
01261     char *headcolor, *tailcolor, *lastcolor;
01262     char *colors = NULL;
01263     bezier bz = { 0, 0, 0, 0 };
01264     bezierf bzf;
01265     splinesf offspl, tmpspl;
01266     pointf pf0, pf1, pf2 = { 0, 0 }, pf3, *offlist, *tmplist;
01267     double arrowsize, numc2;
01268     char* p;
01269 
01270 #define SEP 2.0
01271 
01272     setColorScheme (agget (e, "colorscheme"));
01273     if (ED_spl(e)) {
01274         arrowsize = late_double(e, E_arrowsz, 1.0, 0.0);
01275         color = late_string(e, E_color, "");
01276 
01277         /* need to know how many colors separated by ':' */
01278         for (p = color; *p; p++)
01279             if (*p == ':')
01280                 numc++;
01281 
01282         fillcolor = pencolor = color;
01283         if (ED_gui_state(e) & GUI_STATE_ACTIVE) {
01284             pencolor = late_nnstring(e, E_activepencolor,
01285                         default_pencolor(pencolor, DEFAULT_ACTIVEPENCOLOR));
01286             fillcolor = late_nnstring(e, E_activefillcolor, DEFAULT_ACTIVEFILLCOLOR);
01287         }
01288         else if (ED_gui_state(e) & GUI_STATE_SELECTED) {
01289             pencolor = late_nnstring(e, E_selectedpencolor,
01290                         default_pencolor(pencolor, DEFAULT_SELECTEDPENCOLOR));
01291             fillcolor = late_nnstring(e, E_selectedfillcolor, DEFAULT_SELECTEDFILLCOLOR);
01292         }
01293         else if (ED_gui_state(e) & GUI_STATE_DELETED) {
01294             pencolor = late_nnstring(e, E_deletedpencolor,
01295                         default_pencolor(pencolor, DEFAULT_DELETEDPENCOLOR));
01296             fillcolor = late_nnstring(e, E_deletedfillcolor, DEFAULT_DELETEDFILLCOLOR);
01297         }
01298         else if (ED_gui_state(e) & GUI_STATE_VISITED) {
01299             pencolor = late_nnstring(e, E_visitedpencolor,
01300                         default_pencolor(pencolor, DEFAULT_VISITEDPENCOLOR));
01301             fillcolor = late_nnstring(e, E_visitedfillcolor, DEFAULT_VISITEDFILLCOLOR);
01302         }
01303         if (pencolor != color)
01304             gvrender_set_pencolor(job, pencolor);
01305         if (fillcolor != color)
01306             gvrender_set_fillcolor(job, fillcolor);
01307         color = pencolor;
01308         /* if more than one color - then generate parallel beziers, one per color */
01309         if (numc) {
01310             /* calculate and save offset vector spline and initialize first offset spline */
01311             tmpspl.size = offspl.size = ED_spl(e)->size;
01312             offspl.list = malloc(sizeof(bezier) * offspl.size);
01313             tmpspl.list = malloc(sizeof(bezier) * tmpspl.size);
01314             numc2 = (2 + numc) / 2.0;
01315             for (i = 0; i < offspl.size; i++) {
01316                 bz = ED_spl(e)->list[i];
01317                 tmpspl.list[i].size = offspl.list[i].size = bz.size;
01318                 offlist = offspl.list[i].list =
01319                     malloc(sizeof(pointf) * bz.size);
01320                 tmplist = tmpspl.list[i].list =
01321                     malloc(sizeof(pointf) * bz.size);
01322                 P2PF(bz.list[0], pf3);
01323                 for (j = 0; j < bz.size - 1; j += 3) {
01324                     pf0 = pf3;
01325                     P2PF(bz.list[j + 1], pf1);
01326                     /* calculate perpendicular vectors for each bezier point */
01327                     if (j == 0) /* first segment, no previous pf2 */
01328                         offlist[j] = computeoffset_p(pf0, pf1, SEP);
01329                     else        /* i.e. pf2 is available from previous segment */
01330                         offlist[j] = computeoffset_p(pf2, pf1, SEP);
01331 
01332                     P2PF(bz.list[j + 2], pf2);
01333                     P2PF(bz.list[j + 3], pf3);
01334                     offlist[j + 1] = offlist[j + 2] =
01335                         computeoffset_qr(pf0, pf1, pf2, pf3, SEP);
01336                     /* initialize tmpspl to outermost position */
01337                     tmplist[j].x = pf0.x - numc2 * offlist[j].x;
01338                     tmplist[j].y = pf0.y - numc2 * offlist[j].y;
01339                     tmplist[j + 1].x = pf1.x - numc2 * offlist[j + 1].x;
01340                     tmplist[j + 1].y = pf1.y - numc2 * offlist[j + 1].y;
01341                     tmplist[j + 2].x = pf2.x - numc2 * offlist[j + 2].x;
01342                     tmplist[j + 2].y = pf2.y - numc2 * offlist[j + 2].y;
01343                 }
01344                 /* last segment, no next pf1 */
01345                 offlist[j] = computeoffset_p(pf2, pf3, SEP);
01346                 tmplist[j].x = pf3.x - numc2 * offlist[j].x;
01347                 tmplist[j].y = pf3.y - numc2 * offlist[j].y;
01348             }
01349             lastcolor = headcolor = tailcolor = color;
01350             colors = strdup(color);
01351             for (cnum = 0, color = strtok(colors, ":"); color;
01352                 cnum++, color = strtok(0, ":")) {
01353                 if (!color[0])
01354                     color = DEFAULT_COLOR;
01355                 if (color != lastcolor) {
01356                     if (! (ED_gui_state(e) & (GUI_STATE_ACTIVE | GUI_STATE_SELECTED))) {
01357                         gvrender_set_pencolor(job, color);
01358                         gvrender_set_fillcolor(job, color);
01359                     }
01360                     lastcolor = color;
01361                 }
01362                 if (cnum == 0)
01363                     headcolor = tailcolor = color;
01364                 if (cnum == 1)
01365                     tailcolor = color;
01366                 for (i = 0; i < tmpspl.size; i++) {
01367                     tmplist = tmpspl.list[i].list;
01368                     offlist = offspl.list[i].list;
01369                     for (j = 0; j < tmpspl.list[i].size; j++) {
01370                         tmplist[j].x += offlist[j].x;
01371                         tmplist[j].y += offlist[j].y;
01372                     }
01373                     gvrender_beziercurve(job, tmplist, tmpspl.list[i].size,
01374                                          FALSE, FALSE, FALSE);
01375                 }
01376             }
01377             if (bz.sflag) {
01378                 if (color != tailcolor) {
01379                     color = tailcolor;
01380                     if (! (ED_gui_state(e) & (GUI_STATE_ACTIVE | GUI_STATE_SELECTED))) {
01381                         gvrender_set_pencolor(job, color);
01382                         gvrender_set_fillcolor(job, color);
01383                     }
01384                 }
01385                 arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0],
01386                         arrowsize, job->obj->penwidth, bz.sflag);
01387             }
01388             if (bz.eflag) {
01389                 if (color != headcolor) {
01390                     color = headcolor;
01391                     if (! (ED_gui_state(e) & (GUI_STATE_ACTIVE | GUI_STATE_SELECTED))) {
01392                         gvrender_set_pencolor(job, color);
01393                         gvrender_set_fillcolor(job, color);
01394                     }
01395                 }
01396                 arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1],
01397                         arrowsize, job->obj->penwidth, bz.eflag);
01398             }
01399             free(colors);
01400             for (i = 0; i < offspl.size; i++) {
01401                 free(offspl.list[i].list);
01402                 free(tmpspl.list[i].list);
01403             }
01404             free(offspl.list);
01405             free(tmpspl.list);
01406         } else {
01407             if (! (ED_gui_state(e) & (GUI_STATE_ACTIVE | GUI_STATE_SELECTED))) {
01408                 if (color[0]) {
01409                     gvrender_set_pencolor(job, color);
01410                     gvrender_set_fillcolor(job, color);
01411                 } else {
01412                     gvrender_set_pencolor(job, DEFAULT_COLOR);
01413                     gvrender_set_fillcolor(job, DEFAULT_COLOR);
01414                 }
01415             }
01416             for (i = 0; i < ED_spl(e)->size; i++) {
01417                 bz = ED_spl(e)->list[i];
01418                 /* convert points to pointf for gvrender api */
01419                 bzf.size = bz.size;
01420                 bzf.list = malloc(sizeof(pointf) * bzf.size);
01421                 for (j = 0; j < bz.size; j++)
01422                     P2PF(bz.list[j], bzf.list[j]);
01423                 if (job->flags & GVRENDER_DOES_ARROWS) {
01424                     gvrender_beziercurve(job, bzf.list, bz.size, bz.sflag,
01425                                          bz.eflag, FALSE);
01426                 } else {
01427                     gvrender_beziercurve(job, bzf.list, bz.size, FALSE,
01428                                          FALSE, FALSE);
01429                     if (bz.sflag) {
01430                         arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0],
01431                                 arrowsize, job->obj->penwidth, bz.sflag);
01432                     }
01433                     if (bz.eflag) {
01434                         arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1],
01435                                 arrowsize, job->obj->penwidth, bz.eflag);
01436                     }
01437                     /* arrow_gen resets the job style 
01438                      * If we have more splines to do, restore the old one.
01439                      */
01440                     if ((ED_spl(e)->size>1) && (bz.sflag||bz.eflag) && styles) 
01441                         gvrender_set_style(job, styles);
01442                 }
01443                 free(bzf.list);
01444             }
01445         }
01446     }
01447 }
01448 
01449 static boolean edge_in_box(edge_t *e, boxf b)
01450 {
01451     splines *spl;
01452     textlabel_t *lp;
01453 
01454     spl = ED_spl(e);
01455     if (spl && boxf_overlap(spl->bb, b))
01456         return TRUE;
01457 
01458     lp = ED_label(e);
01459     if (lp && overlap_label(lp, b))
01460         return TRUE;
01461 
01462     return FALSE;
01463 }
01464 
01465 static void emit_begin_edge(GVJ_t * job, edge_t * e, char** styles)
01466 {
01467     obj_state_t *obj;
01468     int flags = job->flags;
01469     char *s;
01470     textlabel_t *lab = NULL, *tlab = NULL, *hlab = NULL;
01471     pointf *pbs = NULL;
01472     int i, nump, *pbs_n = NULL, pbs_poly_n = 0;
01473     char* dflt_url = NULL;
01474     char* dflt_target = NULL;
01475     double penwidth;
01476 
01477     obj = push_obj_state(job);
01478     obj->type = EDGE_OBJTYPE;
01479     obj->u.e = e;
01480     obj->emit_state = EMIT_EDRAW;
01481 
01482     /* We handle the edge style and penwidth here because the width
01483      * is needed below for calculating polygonal image maps
01484      */
01485     if (styles && ED_spl(e)) gvrender_set_style(job, styles);
01486 
01487     if (E_penwidth && ((s=agxget(e,E_penwidth->index)) && s[0])) {
01488         penwidth = late_double(e, E_penwidth, 1.0, 0.0);
01489         gvrender_set_penwidth(job, penwidth);
01490     }
01491 
01492     if (flags & GVRENDER_DOES_Z) {
01493         obj->tail_z= late_double(e->tail, N_z, 0.0, -1000.0);
01494         obj->head_z= late_double(e->head, N_z, 0.0, -MAXFLOAT);
01495     }
01496 
01497     if (flags & GVRENDER_DOES_LABELS) {
01498         if ((lab = ED_label(e)))
01499             obj->label = lab->text;
01500         obj->taillabel = obj->headlabel = obj->label;
01501         if ((tlab = ED_tail_label(e)))
01502             obj->taillabel = tlab->text;
01503         if ((hlab = ED_head_label(e)))
01504             obj->headlabel = hlab->text;
01505     }
01506 
01507     if (flags & GVRENDER_DOES_MAPS) {
01508         if (((s = agget(e, "href")) && s[0]) || ((s = agget(e, "URL")) && s[0]))
01509             dflt_url = strdup_and_subst_obj(s, (void*)e);
01510         if (((s = agget(e, "edgehref")) && s[0]) || ((s = agget(e, "edgeURL")) && s[0]))
01511             obj->url = strdup_and_subst_obj(s, (void*)e);
01512         else if (dflt_url)
01513             obj->url = strdup(dflt_url);
01514         if (((s = agget(e, "labelhref")) && s[0]) || ((s = agget(e, "labelURL")) && s[0]))
01515             obj->labelurl = strdup_and_subst_obj(s, (void*)e);
01516         else if (dflt_url)
01517             obj->labelurl = strdup(dflt_url);
01518         if (((s = agget(e, "tailhref")) && s[0]) || ((s = agget(e, "tailURL")) && s[0])) {
01519             obj->tailurl = strdup_and_subst_obj(s, (void*)e);
01520             obj->explicit_tailurl = TRUE;
01521         }
01522         else if (dflt_url)
01523             obj->tailurl = strdup(dflt_url);
01524         if (((s = agget(e, "headhref")) && s[0]) || ((s = agget(e, "headURL")) && s[0])) {
01525             obj->headurl = strdup_and_subst_obj(s, (void*)e);
01526             obj->explicit_headurl = TRUE;
01527         }
01528         else if (dflt_url)
01529             obj->headurl = strdup(dflt_url);
01530     } 
01531 
01532     if (flags & GVRENDER_DOES_TARGETS) {
01533         if ((s = agget(e, "target")) && s[0])
01534             dflt_target = strdup_and_subst_obj(s, (void*)e);
01535         if ((s = agget(e, "edgetarget")) && s[0]) {
01536             obj->explicit_edgetarget = TRUE;
01537             obj->target = strdup_and_subst_obj(s, (void*)e);
01538         }
01539         else if (dflt_target)
01540             obj->target = strdup(dflt_target);
01541         if ((s = agget(e, "labeltarget")) && s[0])
01542             obj->labeltarget = strdup_and_subst_obj(s, (void*)e);
01543         else if (dflt_target)
01544             obj->labeltarget = strdup(dflt_target);
01545         if ((s = agget(e, "tailtarget")) && s[0]) {
01546             obj->tailtarget = strdup_and_subst_obj(s, (void*)e);
01547             obj->explicit_tailtarget = TRUE;
01548         }
01549         else if (dflt_target)
01550             obj->tailtarget = strdup(dflt_target);
01551         if ((s = agget(e, "headtarget")) && s[0]) {
01552             obj->explicit_headtarget = TRUE;
01553             obj->headtarget = strdup_and_subst_obj(s, (void*)e);
01554         }
01555         else if (dflt_target)
01556             obj->headtarget = strdup(dflt_target);
01557     } 
01558 
01559     if (flags & GVRENDER_DOES_TOOLTIPS) {
01560         if (((s = agget(e, "tooltip")) && s[0]) ||
01561             ((s = agget(e, "edgetooltip")) && s[0])) {
01562             obj->tooltip = strdup_and_subst_obj(s, (void*)e);
01563             obj->explicit_tooltip = TRUE;
01564         }
01565         else if (obj->label)
01566             obj->tooltip = strdup(obj->label);
01567 
01568         if ((s = agget(e, "labeltooltip")) && s[0]) {
01569             obj->labeltooltip = strdup_and_subst_obj(s, (void*)e);
01570             obj->explicit_labeltooltip = TRUE;
01571         }
01572         else if (obj->label)
01573             obj->labeltooltip = strdup(obj->label);
01574 
01575         if ((s = agget(e, "tailtooltip")) && s[0]) {
01576             obj->tailtooltip = strdup_and_subst_obj(s, (void*)e);
01577             obj->explicit_tailtooltip = TRUE;
01578         }
01579         else if (obj->taillabel)
01580             obj->tailtooltip = strdup(obj->taillabel);
01581 
01582         if ((s = agget(e, "headtooltip")) && s[0]) {
01583             obj->headtooltip = strdup_and_subst_obj(s, (void*)e);
01584             obj->explicit_headtooltip = TRUE;
01585         }
01586         else if (obj->headlabel)
01587             obj->headtooltip = strdup(obj->headlabel);
01588     } 
01589     
01590     free (dflt_url);
01591     free (dflt_target);
01592 
01593     if (flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS)) {
01594         if (ED_spl(e) && (obj->url || obj->tooltip) && (flags & GVRENDER_DOES_MAP_POLYGON)) {
01595             int ns;
01596             splines *spl;
01597             double w2 = MAX(job->obj->penwidth/2.0,2.0);
01598 
01599             spl = ED_spl(e);
01600             ns = spl->size; /* number of splines */
01601             for (i = 0; i < ns; i++)
01602                 map_output_bspline (&pbs, &pbs_n, &pbs_poly_n, spl->list+i, w2);
01603             obj->url_bsplinemap_poly_n = pbs_poly_n;
01604             obj->url_bsplinemap_n = pbs_n;
01605             if (! (flags & GVRENDER_DOES_TRANSFORM)) {
01606                 for ( nump = 0, i = 0; i < pbs_poly_n; i++)
01607                     nump += pbs_n[i];
01608                 gvrender_ptf_A(job, pbs, pbs, nump);            
01609             }
01610             obj->url_bsplinemap_p = pbs;
01611             obj->url_map_shape = MAP_POLYGON;
01612             obj->url_map_p = pbs;
01613             obj->url_map_n = pbs_n[0];
01614         }
01615     }
01616 
01617 #ifdef WITH_CODEGENS
01618     Obj = EDGE;
01619 #endif
01620     gvrender_begin_context(job);
01621     gvrender_begin_edge(job, e);
01622     if (obj->url || obj->explicit_tooltip)
01623         gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target);
01624 }
01625 
01626 static void
01627 emit_edge_label(GVJ_t* job, textlabel_t* lbl, int lkind, int explicit,
01628     char* url, char* tooltip, char* target, splines* spl)
01629 {
01630     int flags = job->flags;
01631     if (lbl == NULL) return;
01632     if ((url || explicit) && !(flags & EMIT_CLUSTERS_LAST)) {
01633         map_label(job, lbl);
01634         gvrender_begin_anchor(job, url, tooltip, target);
01635     }
01636     emit_label(job, lkind, lbl);
01637     if (spl) emit_attachment(job, lbl, spl);
01638     if (url || explicit) {
01639         if (flags & EMIT_CLUSTERS_LAST) {
01640             map_label(job, lbl);
01641             gvrender_begin_anchor(job, url, tooltip, target);
01642         }
01643         gvrender_end_anchor(job);
01644     }
01645 }
01646 
01647 /* nodeIntersect:
01648  * Common logic for setting hot spots at the beginning and end of 
01649  * an edge.
01650  * If we are given a value (url, tooltip, target) explicitly set for
01651  * the head/tail, we use that. 
01652  * Otherwise, if we are given a value explicitly set for the edge,
01653  * we use that.
01654  * Otherwise, we use whatever the argument value is.
01655  * We also note whether or not the tooltip was explicitly set.
01656  * If the url is non-NULL or the tooltip was explicit, we set
01657  * a hot spot around point p.
01658  */
01659 static void
01660 nodeIntersect (GVJ_t * job, point p, 
01661     boolean explicit_iurl, char* iurl,
01662     boolean explicit_itooltip, char* itooltip,
01663     boolean explicit_itarget, char* itarget)
01664 {
01665     obj_state_t *obj = job->obj;
01666     char* url;
01667     char* tooltip;
01668     char* target;
01669     boolean explicit;
01670 
01671     if (explicit_iurl) url = iurl;
01672     else url = obj->url;
01673     if (explicit_itooltip) {
01674         tooltip = itooltip;
01675         explicit = TRUE;
01676     }
01677     else if (obj->explicit_tooltip) {
01678         tooltip = obj->tooltip;
01679         explicit = TRUE;
01680     }
01681     else {
01682         explicit = FALSE;
01683         tooltip = itooltip;
01684     }
01685     if (explicit_itarget)
01686         target = itarget;
01687     else if (obj->explicit_edgetarget)
01688         target = obj->target;
01689     else
01690         target = itarget;
01691 
01692     if (url || explicit) {
01693         map_point(job, p);
01694         gvrender_begin_anchor(job, url, tooltip, target);
01695         gvrender_end_anchor(job);
01696     }
01697 }
01698 
01699 static void emit_end_edge(GVJ_t * job)
01700 {
01701     obj_state_t *obj = job->obj;
01702     edge_t *e = obj->u.e;
01703     int i, nump;
01704 
01705     if (obj->url || obj->explicit_tooltip) {
01706         gvrender_end_anchor(job);
01707         if (obj->url_bsplinemap_poly_n) {
01708             for ( nump = obj->url_bsplinemap_n[0], i = 1; i < obj->url_bsplinemap_poly_n; i++) {
01709                 /* additional polygon maps around remaining bezier pieces */
01710                 obj->url_map_n = obj->url_bsplinemap_n[i];
01711                 obj->url_map_p = &(obj->url_bsplinemap_p[nump]);
01712                 gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target);
01713                 gvrender_end_anchor(job);
01714                 nump += obj->url_bsplinemap_n[i];
01715             }
01716         }
01717     }
01718     obj->url_map_n = 0;       /* null out copy so that it doesn't get freed twice */
01719     obj->url_map_p = NULL;
01720 
01721     if (ED_spl(e)) {
01722         point p;
01723         bezier bz;
01724 
01725         /* process intersection with tail node */
01726         bz = ED_spl(e)->list[0];
01727         if (bz.sflag) /* Arrow at start of splines */
01728             p = bz.sp;
01729         else /* No arrow at start of splines */
01730             p = bz.list[0];
01731         nodeIntersect (job, p, obj->explicit_tailurl, obj->tailurl,
01732             obj->explicit_tailtooltip, obj->tailtooltip, 
01733             obj->explicit_tailtarget, obj->tailtarget); 
01734         
01735         /* process intersection with head node */
01736         bz = ED_spl(e)->list[ED_spl(e)->size - 1];
01737         if (bz.eflag) /* Arrow at end of splines */
01738             p = bz.ep;
01739         else /* No arrow at end of splines */
01740             p = bz.list[bz.size - 1];
01741         nodeIntersect (job, p, obj->explicit_headurl, obj->headurl,
01742             obj->explicit_headtooltip, obj->headtooltip, 
01743             obj->explicit_headtarget, obj->headtarget); 
01744     }
01745 
01746     emit_edge_label(job, ED_label(e), EMIT_ELABEL, obj->explicit_labeltooltip, 
01747         obj->labelurl, obj->labeltooltip, obj->labeltarget, 
01748         ((mapbool(late_string(e, E_decorate, "false")) && ED_spl(e)) ? ED_spl(e) : 0));
01749     emit_edge_label(job, ED_head_label(e), EMIT_HLABEL, 
01750         obj->explicit_headtooltip, obj->headurl, obj->headtooltip, obj->headtarget, 0);
01751     emit_edge_label(job, ED_tail_label(e), EMIT_TLABEL, 
01752         obj->explicit_tailtooltip, obj->tailurl, obj->tailtooltip, obj->tailtarget, 0);
01753 
01754     gvrender_end_edge(job);
01755     gvrender_end_context(job);
01756 #ifdef WITH_CODEGENS
01757     Obj = NONE;
01758 #endif
01759     pop_obj_state(job);
01760 }
01761 
01762 static void emit_edge(GVJ_t * job, edge_t * e)
01763 {
01764     char *s;
01765     char *style;
01766     char **styles = 0;
01767     char **sp;
01768     char *p;
01769 
01770     if (edge_in_box(e, job->clip) && edge_in_layer(job, e->head->graph, e) ) {
01771 
01772         s = malloc(strlen(e->tail->name) + 2 + strlen(e->head->name) + 1);
01773         strcpy(s,e->tail->name);
01774         if (AG_IS_DIRECTED(e->tail->graph))
01775             strcat(s,"->");
01776         else
01777             strcat(s,"--");
01778         strcat(s,e->head->name);
01779         gvrender_comment(job, s);
01780         free(s);
01781 
01782         s = late_string(e, E_comment, "");
01783         if (s[0])
01784             gvrender_comment(job, s);
01785 
01786         style = late_string(e, E_style, "");
01787         /* We shortcircuit drawing an invisible edge because the arrowhead
01788          * code resets the style to solid, and most of the code generators
01789          * (except PostScript) won't honor a previous style of invis.
01790          */
01791         if (style[0]) {
01792             styles = parse_style(style);
01793             sp = styles;
01794             while ((p = *sp++)) {
01795                 if (streq(p, "invis")) return;
01796             }
01797         }
01798 
01799         emit_begin_edge(job, e, styles);
01800         emit_edge_graphics (job, e, styles);
01801         emit_end_edge(job);
01802     }
01803 }
01804 
01805 static void init_gvc(GVC_t * gvc, graph_t * g)
01806 {
01807     double xf, yf;
01808     char *p;
01809     int i;
01810 
01811     gvc->g = g;
01812 
01813     /* margins */
01814     gvc->graph_sets_margin = FALSE;
01815     if ((p = agget(g, "margin"))) {
01816         i = sscanf(p, "%lf,%lf", &xf, &yf);
01817         if (i > 0) {
01818             gvc->margin.x = gvc->margin.y = xf * POINTS_PER_INCH;
01819             if (i > 1)
01820                 gvc->margin.y = yf * POINTS_PER_INCH;
01821             gvc->graph_sets_margin = TRUE;
01822         }
01823     }
01824 
01825     /* pad */
01826     gvc->graph_sets_pad = FALSE;
01827     if ((p = agget(g, "pad"))) {
01828         i = sscanf(p, "%lf,%lf", &xf, &yf);
01829         if (i > 0) {
01830             gvc->pad.x = gvc->pad.y = xf * POINTS_PER_INCH;
01831             if (i > 1)
01832                 gvc->pad.y = yf * POINTS_PER_INCH;
01833             gvc->graph_sets_pad = TRUE;
01834         }
01835     }
01836 
01837     /* pagesize */
01838     gvc->graph_sets_pageSize = FALSE;
01839     P2PF(GD_drawing(g)->page, gvc->pageSize);
01840     if ((GD_drawing(g)->page.x > 0) && (GD_drawing(g)->page.y > 0))
01841         gvc->graph_sets_pageSize = TRUE;
01842 
01843     /* rotation */
01844     if (GD_drawing(g)->landscape)
01845         gvc->rotation = 90;
01846     else 
01847         gvc->rotation = 0;
01848 
01849     /* pagedir */
01850     gvc->pagedir = "BL";
01851     if ((p = agget(g, "pagedir")) && p[0])
01852             gvc->pagedir = p;
01853 
01854     /* bounding box */
01855     B2BF(GD_bb(g),gvc->bb);
01856 
01857     /* clusters have peripheries */
01858     G_peripheries = agfindattr(g, "peripheries");
01859 
01860     G_penwidth = agfindattr(g, "penwidth");
01861 
01862     /* default font */
01863     gvc->defaultfontname = late_nnstring(g->proto->n,
01864                 N_fontname, DEFAULT_FONTNAME);
01865     gvc->defaultfontsize = late_double(g->proto->n,
01866                 N_fontsize, DEFAULT_FONTSIZE, MIN_FONTSIZE);
01867 
01868     /* default line style */
01869     gvc->defaultlinestyle = defaultlinestyle;
01870 
01871     gvc->graphname = g->name;
01872 }
01873 
01874 static void init_job_pad(GVJ_t *job)
01875 {
01876     GVC_t *gvc = job->gvc;
01877     
01878     if (gvc->graph_sets_pad) {
01879         job->pad = gvc->pad;
01880     }
01881     else {
01882         switch (job->output_lang) {
01883         case GVRENDER_PLUGIN:
01884             job->pad.x = job->pad.y = job->render.features->default_pad;
01885             break;
01886         default:
01887             job->pad.x = job->pad.y = DEFAULT_GRAPH_PAD;
01888             break;
01889         }
01890     }
01891 }
01892 
01893 static void init_job_margin(GVJ_t *job)
01894 {
01895     GVC_t *gvc = job->gvc;
01896     
01897     if (gvc->graph_sets_margin) {
01898         job->margin = gvc->margin;
01899     }
01900     else {
01901         /* set default margins depending on format */
01902         switch (job->output_lang) {
01903         case GVRENDER_PLUGIN:
01904             job->margin = job->device.features->default_margin;
01905             break;
01906         case HPGL: case PCL: case MIF: case METAPOST: case VTX: case QPDF:
01907             job->margin.x = job->margin.y = DEFAULT_PRINT_MARGIN;
01908             break;
01909         default:
01910             job->margin.x = job->margin.y = DEFAULT_EMBED_MARGIN;
01911             break;
01912         }
01913     }
01914 
01915 }
01916 
01917 static void init_job_dpi(GVJ_t *job, graph_t *g)
01918 {
01919     GVJ_t *firstjob = job->gvc->active_jobs;
01920 
01921     if (GD_drawing(g)->dpi != 0) {
01922         job->dpi.x = job->dpi.y = (double)(GD_drawing(g)->dpi);
01923     }
01924     else if (firstjob && firstjob->device_sets_dpi) {
01925         job->dpi = firstjob->device_dpi;   /* some devices set dpi in initialize() */
01926     }
01927     else {
01928         /* set default margins depending on format */
01929         switch (job->output_lang) {
01930         case GVRENDER_PLUGIN:
01931             job->dpi = job->device.features->default_dpi;
01932             break;
01933         default:
01934             job->dpi.x = job->dpi.y = (double)(DEFAULT_DPI);
01935             break;
01936         }
01937     }
01938 }
01939 
01940 static void init_job_viewport(GVJ_t * job, graph_t * g)
01941 {
01942     GVC_t *gvc = job->gvc;
01943     pointf UR, size, sz;
01944     char *str;
01945     double X, Y, Z, x, y;
01946     int rv;
01947     Agnode_t *n;
01948     char *nodename = NULL;
01949 
01950     assert((gvc->bb.LL.x == 0) && (gvc->bb.LL.y == 0));
01951     P2PF(gvc->bb.UR, UR);
01952 
01953     job->bb.LL.x = -job->pad.x;           /* job->bb is bb of graph and padding - graph units */
01954     job->bb.LL.y = -job->pad.y;
01955     job->bb.UR.x = UR.x + job->pad.x;
01956     job->bb.UR.y = UR.y + job->pad.y;
01957     sz.x = job->bb.UR.x - job->bb.LL.x;   /* size, including padding - graph units */
01958     sz.y = job->bb.UR.y - job->bb.LL.y;
01959 
01960     /* determine final drawing size and scale to apply. */
01961     /* N.B. size given by user is not rotated by landscape mode */
01962     /* start with "natural" size of layout */
01963 
01964     Z = 1.0;
01965     if (GD_drawing(g)->size.x > 0) {    /* graph size was given by user... */
01966         P2PF(GD_drawing(g)->size, size);
01967         if ((size.x < sz.x) || (size.y < sz.y) /* drawing is too big... */
01968             || ((GD_drawing(g)->filled) /* or ratio=filled requested and ... */
01969                 && (size.x > sz.x) && (size.y > sz.y))) /* drawing is too small... */
01970             Z = MIN(size.x/sz.x, size.y/sz.y);
01971     }
01972     
01973     /* default focus, in graph units = center of bb */
01974     x = UR.x / 2.;
01975     y = UR.y / 2.;
01976 
01977     /* rotate and scale bb to give default absolute size in points*/
01978     job->rotation = job->gvc->rotation;
01979     X = sz.x * Z;
01980     Y = sz.y * Z;
01981 
01982     /* user can override */
01983     if ((str = agget(g, "viewport"))) {
01984         nodename = malloc(strlen(str)+1);
01985         rv = sscanf(str, "%lf,%lf,%lf,\'%[^\']\'", &X, &Y, &Z, nodename);
01986         if (rv == 4) {
01987             n = agfindnode(g->root, nodename);
01988             if (n) {
01989                 x = ND_coord_i(n).x;
01990                 y = ND_coord_i(n).y;
01991             }
01992         }
01993         else {
01994             rv = sscanf(str, "%lf,%lf,%lf,%lf,%lf", &X, &Y, &Z, &x, &y);
01995         }
01996         free (nodename);
01997     }
01998     /* rv is ignored since args retain previous values if not scanned */
01999 
02000     /* job->view gives port size in graph units, unscaled or rotated
02001      * job->zoom gives scaling factor.
02002      * job->focus gives the position in the graph of the center of the port
02003      */
02004     job->view.x = X;
02005     job->view.y = Y;
02006     job->zoom = Z;              /* scaling factor */
02007     job->focus.x = x;
02008     job->focus.y = y;
02009 #if 0
02010 fprintf(stderr, "view=%g,%g, zoom=%g, focus=%g,%g\n",
02011         job->view.x, job->view.y,
02012         job->zoom,
02013         job->focus.x, job->focus.y);
02014 #endif
02015 }
02016 
02017 static void emit_colors(GVJ_t * job, graph_t * g)
02018 {
02019     graph_t *sg;
02020     node_t *n;
02021     edge_t *e;
02022     int c;
02023     char *str, *colors;
02024 
02025     gvrender_set_fillcolor(job, DEFAULT_FILL);
02026     if (((str = agget(g, "bgcolor")) != 0) && str[0])
02027         gvrender_set_fillcolor(job, str);
02028     if (((str = agget(g, "fontcolor")) != 0) && str[0])
02029         gvrender_set_pencolor(job, str);
02030     for (c = 1; c <= GD_n_cluster(g); c++) {
02031         sg = GD_clust(g)[c];
02032         if (((str = agget(sg, "color")) != 0) && str[0])
02033             gvrender_set_pencolor(job, str);
02034         if (((str = agget(sg, "fillcolor")) != 0) && str[0])
02035             gvrender_set_fillcolor(job, str);
02036         if (((str = agget(sg, "fontcolor")) != 0) && str[0])
02037             gvrender_set_pencolor(job, str);
02038     }
02039     for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
02040         if (((str = agget(n, "color")) != 0) && str[0])
02041             gvrender_set_pencolor(job, str);
02042         if (((str = agget(n, "fillcolor")) != 0) && str[0])
02043             gvrender_set_fillcolor(job, str);
02044         if (((str = agget(n, "fontcolor")) != 0) && str[0])
02045             gvrender_set_pencolor(job, str);
02046         for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
02047             if (((str = agget(e, "color")) != 0) && str[0]) {
02048                 if (strchr(str, ':')) {
02049                     colors = strdup(str);
02050                     for (str = strtok(colors, ":"); str;
02051                         str = strtok(0, ":")) {
02052                         if (str[0])
02053                             gvrender_set_pencolor(job, str);
02054                     }
02055                     free(colors);
02056                 } else
02057                     gvrender_set_pencolor(job, str);
02058             }
02059             if (((str = agget(e, "fontcolor")) != 0) && str[0])
02060                 gvrender_set_pencolor(job, str);
02061         }
02062     }
02063 }
02064 
02065 static void emit_view(GVJ_t * job, graph_t * g, int flags)
02066 {
02067     GVC_t * gvc = job->gvc;
02068     node_t *n;
02069     edge_t *e;
02070 
02071     gvc->common.viewNum++;
02072     /* when drawing, lay clusters down before nodes and edges */
02073     if (!(flags & EMIT_CLUSTERS_LAST))
02074         emit_clusters(job, g, flags);
02075     if (flags & EMIT_SORTED) {
02076         /* output all nodes, then all edges */
02077         gvrender_begin_nodes(job);
02078         for (n = agfstnode(g); n; n = agnxtnode(g, n))
02079             emit_node(job, n);
02080         gvrender_end_nodes(job);
02081         gvrender_begin_edges(job);
02082         for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
02083             for (e = agfstout(g, n); e; e = agnxtout(g, e))
02084                 emit_edge(job, e);
02085         }
02086         gvrender_end_edges(job);
02087     } else if (flags & EMIT_EDGE_SORTED) {
02088         /* output all edges, then all nodes */
02089         gvrender_begin_edges(job);
02090         for (n = agfstnode(g); n; n = agnxtnode(g, n))
02091             for (e = agfstout(g, n); e; e = agnxtout(g, e))
02092                 emit_edge(job, e);
02093         gvrender_end_edges(job);
02094         gvrender_begin_nodes(job);
02095         for (n = agfstnode(g); n; n = agnxtnode(g, n))
02096             emit_node(job, n);
02097         gvrender_end_nodes(job);
02098     } else if (flags & EMIT_PREORDER) {
02099         gvrender_begin_nodes(job);
02100         for (n = agfstnode(g); n; n = agnxtnode(g, n))
02101             if (write_node_test(g, n))
02102                 emit_node(job, n);
02103         gvrender_end_nodes(job);
02104         gvrender_begin_edges(job);
02105 
02106         for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
02107             for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
02108                 if (write_edge_test(g, e))
02109                     emit_edge(job, e);
02110             }
02111         }
02112         gvrender_end_edges(job);
02113     } else {
02114         /* output in breadth first graph walk order */
02115         for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
02116             emit_node(job, n);
02117             for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
02118                 emit_node(job, e->head);
02119                 emit_edge(job, e);
02120             }
02121         }
02122     }
02123     /* when mapping, detect events on clusters after nodes and edges */
02124     if (flags & EMIT_CLUSTERS_LAST)
02125         emit_clusters(job, g, flags);
02126 }
02127 
02128 static void emit_begin_graph(GVJ_t * job, graph_t * g)
02129 {
02130     obj_state_t *obj;
02131 
02132     obj = push_obj_state(job);
02133     obj->type = ROOTGRAPH_OBJTYPE;
02134     obj->u.g = g;
02135     obj->emit_state = EMIT_GDRAW;
02136 
02137     initObjMapData (job, GD_label(g), g);
02138 
02139 #ifdef WITH_CODEGENS
02140     Obj = NONE;
02141 #endif
02142     gvrender_begin_graph(job, g);
02143 }
02144 
02145 static void emit_end_graph(GVJ_t * job, graph_t * g)
02146 {
02147     gvrender_end_graph(job);
02148 #ifdef WITH_CODEGENS
02149     Obj = NONE;
02150 #endif
02151     pop_obj_state(job);
02152 }
02153 
02154 static void emit_page(GVJ_t * job, graph_t * g)
02155 {
02156     GVC_t *gvc = job->gvc;
02157     obj_state_t *obj = job->obj;
02158     int nump = 0, flags = job->flags;
02159     textlabel_t *lab;
02160     point p1, p2;
02161     pointf *p = NULL;
02162 
02163     setColorScheme (agget (g, "colorscheme"));
02164     setup_page(job, g);
02165     gvrender_begin_page(job);
02166     gvrender_set_pencolor(job, DEFAULT_COLOR);
02167     gvrender_set_fillcolor(job, DEFAULT_FILL);
02168     gvrender_set_font(job, gvc->defaultfontname, gvc->defaultfontsize);
02169     if ((flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS))
02170             && (obj->url || obj->explicit_tooltip)) {
02171         if (flags & (GVRENDER_DOES_MAP_RECTANGLE | GVRENDER_DOES_MAP_POLYGON)) {
02172             if (flags & GVRENDER_DOES_MAP_RECTANGLE) {
02173                 obj->url_map_shape = MAP_RECTANGLE;
02174                 nump = 2;
02175             }
02176             else {
02177                 obj->url_map_shape = MAP_POLYGON;
02178                 nump = 4;
02179             }
02180             p = N_NEW(nump, pointf);
02181             p[0] = job->pageBox.LL;
02182             p[1] = job->pageBox.UR;
02183             if (! (flags & (GVRENDER_DOES_MAP_RECTANGLE)))
02184                 rect2poly(p);
02185         }
02186         if (! (flags & GVRENDER_DOES_TRANSFORM))
02187             gvrender_ptf_A(job, p, p, nump);
02188         obj->url_map_p = p;
02189         obj->url_map_n = nump;
02190     }
02191     if ((flags & GVRENDER_DOES_LABELS) && ((lab = GD_label(g))))
02192         /* do graph label on every page and rely on clipping to show it on the right one(s) */
02193         obj->label = lab->text;
02194         /* If EMIT_CLUSTERS_LAST is set, we assume any URL or tooltip
02195          * attached to the root graph is emitted either in begin_page
02196          * or end_page of renderer.
02197          */
02198     if (!(flags & EMIT_CLUSTERS_LAST) && (obj->url || obj->explicit_tooltip)) {
02199         PF2P(job->clip.LL, p1);
02200         PF2P(job->clip.UR, p2);
02201         emit_map_rect(job, p1, p2);
02202         gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target);
02203     }
02204     if (job->numLayers == 1)
02205         emit_background(job, g);
02206     if (GD_label(g))
02207         emit_label(job, EMIT_GLABEL, GD_label(g));
02208     if (!(flags & EMIT_CLUSTERS_LAST) && (obj->url || obj->explicit_tooltip))
02209         gvrender_end_anchor(job);
02210     emit_view(job,g,flags);
02211     gvrender_end_page(job);
02212 }
02213 
02214 void emit_graph(GVJ_t * job, graph_t * g)
02215 {
02216     node_t *n;
02217     char *s;
02218     int flags = job->flags;
02219 
02220     /* device dpi is now known */
02221     job->scale.x = job->zoom * job->dpi.x / POINTS_PER_INCH;
02222     job->scale.y = job->zoom * job->dpi.y / POINTS_PER_INCH;
02223 
02224     job->devscale.x = job->dpi.x / POINTS_PER_INCH;
02225     job->devscale.y = job->dpi.y / POINTS_PER_INCH;
02226     if ((job->flags & GVRENDER_Y_GOES_DOWN) || (Y_invert))
02227         job->devscale.y *= -1;
02228 
02229     /* compute current view in graph units */
02230     if (job->rotation) {
02231         job->view.y = job->width / job->scale.x;
02232         job->view.x = job->height / job->scale.y;
02233     }
02234     else {
02235         job->view.x = job->width / job->scale.x;
02236         job->view.y = job->height / job->scale.y;
02237     }
02238 
02239     s = late_string(g, agfindattr(g, "comment"), "");
02240     gvrender_comment(job, s);
02241 
02242     emit_begin_graph(job, g);
02243 
02244     if (flags & EMIT_COLORS)
02245         emit_colors(job,g);
02246 
02247     /* reset node state */
02248     for (n = agfstnode(g); n; n = agnxtnode(g, n))
02249         ND_state(n) = 0;
02250     /* iterate layers */
02251     for (firstlayer(job); validlayer(job); nextlayer(job)) {
02252         if (job->numLayers > 1)
02253             gvrender_begin_layer(job);
02254 
02255         /* iterate pages */
02256         for (firstpage(job); validpage(job); nextpage(job))
02257             emit_page(job, g);
02258 
02259         if (job->numLayers > 1)
02260             gvrender_end_layer(job);
02261     } 
02262     emit_end_graph(job, g);
02263 }
02264 
02265 /* support for stderr_once */
02266 static void free_string_entry(Dict_t * dict, char *key, Dtdisc_t * disc)
02267 {
02268     agstrfree(key);
02269 }
02270 
02271 static Dict_t *strings;
02272 static Dtdisc_t stringdict = {
02273     0,                          /* key  - the object itself */
02274     0,                          /* size - null-terminated string */
02275     -1,                         /* link - allocate separate holder objects  */
02276     NIL(Dtmake_f),
02277     (Dtfree_f) free_string_entry,
02278     NIL(Dtcompar_f),
02279     NIL(Dthash_f),
02280     NIL(Dtmemory_f),
02281     NIL(Dtevent_f)
02282 };
02283 
02284 int emit_once(char *str)
02285 {
02286     if (strings == 0)
02287         strings = dtopen(&stringdict, Dtoset);
02288     if (!dtsearch(strings, str)) {
02289         dtinsert(strings, agstrdup(str));
02290         return TRUE;
02291     }
02292     return FALSE;
02293 }
02294 
02295 void emit_once_reset(void)
02296 {
02297     if (strings) {
02298         dtclose(strings);
02299         strings = 0;
02300     }
02301 }
02302 
02303 static char **checkClusterStyle(graph_t* sg, int *flagp)
02304 {
02305     char *style;
02306     char **pstyle = 0;
02307     int istyle = 0;
02308 
02309     if (((style = agget(sg, "style")) != 0) && style[0]) {
02310         char **pp;
02311         char **qp;
02312         char *p;
02313         pp = pstyle = parse_style(style);
02314         while ((p = *pp)) {
02315             if (strcmp(p, "filled") == 0) {
02316                 istyle |= FILLED;
02317                 pp++;
02318             } else if (strcmp(p, "rounded") == 0) {
02319                 istyle |= ROUNDED;
02320                 qp = pp; /* remove rounded from list passed to renderer */
02321                 do {
02322                     qp++;
02323                     *(qp-1) = *qp;
02324                 } while (*qp);
02325             } else pp++;
02326         }
02327     }
02328 
02329     *flagp = istyle;
02330     return pstyle;
02331 }
02332 
02333 static void emit_begin_cluster(GVJ_t * job, Agraph_t * sg)
02334 {
02335     obj_state_t *obj;
02336 
02337     obj = push_obj_state(job);
02338     obj->type = CLUSTER_OBJTYPE;
02339     obj->u.sg = sg;
02340     obj->emit_state = EMIT_CDRAW;
02341 
02342     initObjMapData (job, GD_label(sg), sg);
02343 
02344 #ifdef WITH_CODEGENS
02345     Obj = CLST;
02346 #endif
02347     gvrender_begin_cluster(job, sg);
02348 }
02349 
02350 static void emit_end_cluster(GVJ_t * job, Agraph_t * g)
02351 {
02352     gvrender_end_cluster(job, g);
02353 #ifdef WITH_CODEGENS
02354     Obj = NONE;
02355 #endif
02356     pop_obj_state(job);
02357 }
02358 
02359 void emit_clusters(GVJ_t * job, Agraph_t * g, int flags)
02360 {
02361     int c, istyle, filled;
02362     boxf BF;
02363     pointf AF[4];
02364     char *color, *fillcolor, *pencolor, **style, *s;
02365     graph_t *sg;
02366     node_t *n;
02367     edge_t *e;
02368     obj_state_t *obj;
02369     textlabel_t *lab;
02370     int doAnchor;
02371     double penwidth;
02372 
02373     for (c = 1; c <= GD_n_cluster(g); c++) {
02374         sg = GD_clust(g)[c];
02375         if (clust_in_layer(job, sg) == FALSE)
02376             continue;
02377         /* when mapping, detect events on clusters after sub_clusters */
02378         if (flags & EMIT_CLUSTERS_LAST)
02379             emit_clusters(job, sg, flags);
02380         emit_begin_cluster(job, sg);
02381         obj = job->obj;
02382         doAnchor = (obj->url || obj->explicit_tooltip);
02383         setColorScheme (agget (sg, "colorscheme"));
02384         gvrender_begin_context(job);
02385         if (doAnchor && !(flags & EMIT_CLUSTERS_LAST)) {
02386             emit_map_rect(job, GD_bb(sg).LL, GD_bb(sg).UR);
02387             gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target);
02388         }
02389         filled = FALSE;
02390         istyle = 0;
02391         if ((style = checkClusterStyle(sg, &istyle))) {
02392             gvrender_set_style(job, style);
02393             if (istyle & FILLED)
02394                 filled = TRUE;
02395         }
02396         fillcolor = pencolor = 0;
02397         if (GD_gui_state(sg) & GUI_STATE_ACTIVE) {
02398             pencolor = late_nnstring(sg, G_activepencolor, DEFAULT_ACTIVEPENCOLOR);
02399             fillcolor = late_nnstring(sg, G_activefillcolor, DEFAULT_ACTIVEFILLCOLOR);
02400             filled = TRUE;
02401         }
02402         else if (GD_gui_state(sg) & GUI_STATE_SELECTED) {
02403             pencolor = late_nnstring(sg, G_activepencolor, DEFAULT_SELECTEDPENCOLOR);
02404             fillcolor = late_nnstring(sg, G_activefillcolor, DEFAULT_SELECTEDFILLCOLOR);
02405             filled = TRUE;
02406         }
02407         else if (GD_gui_state(sg) & GUI_STATE_DELETED) {
02408             pencolor = late_nnstring(sg, G_deletedpencolor, DEFAULT_DELETEDPENCOLOR);
02409             fillcolor = late_nnstring(sg, G_deletedfillcolor, DEFAULT_DELETEDFILLCOLOR);
02410             filled = TRUE;
02411         }
02412         else if (GD_gui_state(sg) & GUI_STATE_VISITED) {
02413             pencolor = late_nnstring(sg, G_visitedpencolor, DEFAULT_VISITEDPENCOLOR);
02414             fillcolor = late_nnstring(sg, G_visitedfillcolor, DEFAULT_VISITEDFILLCOLOR);
02415             filled = TRUE;
02416         }
02417         else {
02418             if (((color = agget(sg, "pencolor")) != 0) && color[0])
02419                 pencolor = color;
02420             else if (((color = agget(sg, "color")) != 0) && color[0])
02421                 fillcolor = pencolor = color;
02422             /* bgcolor is supported for backward compatability */
02423             else if (((color = agget(sg, "bgcolor")) != 0) && color[0]) {
02424                 fillcolor = color;
02425                 filled = TRUE;
02426             }
02427             if (((color = agget(sg, "fillcolor")) != 0) && color[0])
02428                 fillcolor = color;
02429         }
02430         if (!pencolor) pencolor = DEFAULT_COLOR;
02431         if (!fillcolor) fillcolor = DEFAULT_FILL;
02432 
02433         if (G_penwidth && ((s=agxget(sg, G_penwidth->index)) && s[0])) {
02434             penwidth = late_double(sg, G_penwidth, 1.0, 0.0);
02435             gvrender_set_penwidth(job, penwidth);
02436         }
02437 
02438         B2BF(GD_bb(sg), BF);
02439         if (istyle & ROUNDED) {
02440             if (late_int(sg, G_peripheries, 1, 0) || filled) {
02441                 AF[0] = BF.LL;
02442                 AF[2] = BF.UR;
02443                 AF[1].x = AF[2].x;
02444                 AF[1].y = AF[0].y;
02445                 AF[3].x = AF[0].x;
02446                 AF[3].y = AF[2].y;
02447                 round_corners(job, fillcolor, pencolor, AF, 4, istyle);
02448             }
02449         }
02450         else {
02451             gvrender_set_pencolor(job, pencolor);
02452             gvrender_set_fillcolor(job, fillcolor);
02453             if (late_int(sg, G_peripheries, 1, 0))
02454                 gvrender_box(job, BF, filled);
02455             else if (filled) { 
02456                 if (fillcolor && fillcolor != pencolor)
02457                     gvrender_set_pencolor(job, fillcolor);
02458                 gvrender_box(job, BF, filled);
02459             }
02460         }
02461         if ((lab = GD_label(sg)))
02462             emit_label(job, EMIT_CLABEL, lab);
02463 
02464         if (doAnchor) {
02465             if (flags & EMIT_CLUSTERS_LAST) {
02466                 emit_map_rect(job, GD_bb(sg).LL, GD_bb(sg).UR);
02467                 gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target);
02468             }
02469             gvrender_end_anchor(job);
02470         }
02471 
02472         if (flags & EMIT_PREORDER) {
02473             for (n = agfstnode(sg); n; n = agnxtnode(sg, n)) {
02474                 emit_node(job, n);
02475                 for (e = agfstout(sg, n); e; e = agnxtout(sg, e))
02476                     emit_edge(job, e);
02477             }
02478         }
02479         gvrender_end_context(job);
02480         emit_end_cluster(job, g);
02481         /* when drawing, lay down clusters before sub_clusters */
02482         if (!(flags & EMIT_CLUSTERS_LAST))
02483             emit_clusters(job, sg, flags);
02484     }
02485 }
02486 
02487 static boolean is_style_delim(int c)
02488 {
02489     switch (c) {
02490     case '(':
02491     case ')':
02492     case ',':
02493     case '\0':
02494         return TRUE;
02495     default:
02496         return FALSE;
02497     }
02498 }
02499 
02500 #define SID 1
02501 
02502 static int style_token(char **s, agxbuf * xb)
02503 {
02504     char *p = *s;
02505     int token, rc;
02506     char c;
02507 
02508     while (*p && (isspace(*p) || (*p == ',')))
02509         p++;
02510     switch (*p) {
02511     case '\0':
02512         token = 0;
02513         break;
02514     case '(':
02515     case ')':
02516         token = *p++;
02517         break;
02518     default:
02519         token = SID;
02520         while (!is_style_delim(c = *p)) {
02521             rc = agxbputc(xb, c);
02522             p++;
02523         }
02524     }
02525     *s = p;
02526     return token;
02527 }
02528 
02529 #define FUNLIMIT 64
02530 static unsigned char outbuf[SMALLBUF];
02531 static agxbuf ps_xb;
02532 
02533 static void cleanup(void)
02534 {
02535     agxbfree(&ps_xb);
02536 }
02537 
02538 /* parse_style:
02539  * This is one of the worse internal designs in graphviz.
02540  * The use of '\0' characters within strings seems cute but it
02541  * makes all of the standard functions useless if not dangerous.
02542  * Plus the function uses static memory for both the array and
02543  * the character buffer. One hopes all of the values are used
02544  * before the function is called again.
02545  */
02546 char **parse_style(char *s)
02547 {
02548     static char *parse[FUNLIMIT];
02549     static boolean is_first = TRUE;
02550     int fun = 0;
02551     boolean in_parens = FALSE;
02552     unsigned char buf[SMALLBUF];
02553     char *p;
02554     int c;
02555     agxbuf xb;
02556 
02557     if (is_first) {
02558         agxbinit(&ps_xb, SMALLBUF, outbuf);
02559         atexit(cleanup);
02560         is_first = FALSE;
02561     }
02562 
02563     agxbinit(&xb, SMALLBUF, buf);
02564     p = s;
02565     while ((c = style_token(&p, &xb)) != 0) {
02566         switch (c) {
02567         case '(':
02568             if (in_parens) {
02569                 agerr(AGERR, "nesting not allowed in style: %s\n", s);
02570                 parse[0] = (char *) 0;
02571                 agxbfree(&xb);
02572                 return parse;
02573             }
02574             in_parens = TRUE;
02575             break;
02576 
02577         case ')':
02578             if (in_parens == FALSE) {
02579                 agerr(AGERR, "unmatched ')' in style: %s\n", s);
02580                 parse[0] = (char *) 0;
02581                 agxbfree(&xb);
02582                 return parse;
02583             }
02584             in_parens = FALSE;
02585             break;
02586 
02587         default:
02588             if (in_parens == FALSE) {
02589                 if (fun == FUNLIMIT - 1) {
02590                     agerr(AGWARN, "truncating style '%s'\n", s);
02591                     parse[fun] = (char *) 0;
02592                     agxbfree(&xb);
02593                     return parse;
02594                 }
02595                 agxbputc(&ps_xb, '\0'); /* terminate previous */
02596                 parse[fun++] = agxbnext(&ps_xb);
02597             }
02598             agxbput(&ps_xb, agxbuse(&xb));
02599             agxbputc(&ps_xb, '\0');
02600         }
02601     }
02602 
02603     if (in_parens) {
02604         agerr(AGERR, "unmatched '(' in style: %s\n", s);
02605         parse[0] = (char *) 0;
02606         agxbfree(&xb);
02607         return parse;
02608     }
02609     parse[fun] = (char *) 0;
02610     agxbfree(&xb);
02611     (void)agxbuse(&ps_xb);              /* adds final '\0' to buffer */
02612     return parse;
02613 }
02614 
02615 static boxf bezier_bb(bezier bz)
02616 {
02617     int i;
02618     point p;
02619     box bb;
02620     boxf bbf;
02621 
02622     assert(bz.size > 0);
02623     bb.LL = bb.UR = bz.list[0];
02624     for (i = 1; i < bz.size; i++) {
02625         p=bz.list[i];
02626         EXPANDBP(bb,p);
02627     }
02628     B2BF(bb, bbf);
02629     return bbf;
02630 }
02631 
02632 static void init_splines_bb(splines *spl)
02633 {
02634     int i;
02635     bezier bz;
02636     boxf bb, b;
02637     pointf p, u;
02638 
02639     assert(spl->size > 0);
02640     bz = spl->list[0];
02641     bb = bezier_bb(bz);
02642     for (i = 0; i < spl->size; i++) {
02643         if (i > 0) {
02644             bz = spl->list[i];
02645             b = bezier_bb(bz);
02646             EXPANDBB(bb, b);
02647         }
02648         if (bz.sflag) {
02649             P2PF(bz.sp, p);
02650             P2PF(bz.list[0], u);
02651             b = arrow_bb(p, u, 1, bz.sflag);
02652             EXPANDBB(bb, b);
02653         }
02654         if (bz.eflag) {
02655             P2PF(bz.ep, p);
02656             P2PF(bz.list[bz.size - 1], u);
02657             b = arrow_bb(p, u, 1, bz.eflag);
02658             EXPANDBB(bb, b);
02659         }
02660     }
02661     spl->bb = bb;
02662 }
02663 
02664 static void init_bb_edge(edge_t *e)
02665 {
02666     splines *spl;
02667 
02668     spl = ED_spl(e);
02669     if (spl)
02670         init_splines_bb(spl);
02671 
02672 //    lp = ED_label(e);
02673 //    if (lp)
02674 //        {}
02675 }
02676 
02677 static void init_bb_node(graph_t *g, node_t *n)
02678 {
02679     edge_t *e;
02680 
02681     ND_bb(n).LL.x = ND_coord_i(n).x - ND_lw_i(n);
02682     ND_bb(n).LL.y = ND_coord_i(n).y - ND_ht_i(n) / 2.;
02683     ND_bb(n).UR.x = ND_coord_i(n).x + ND_rw_i(n);
02684     ND_bb(n).UR.y = ND_coord_i(n).y + ND_ht_i(n) / 2.;
02685 
02686     for (e = agfstout(g, n); e; e = agnxtout(g, e))
02687         init_bb_edge(e);
02688 
02689     /* IDEA - could also save in the node the bb of the node and
02690     all of its outedges, then the scan time would be proportional
02691     to just the number of nodes for many graphs.
02692     Wouldn't work so well if the edges are sprawling all over the place
02693     because then the boxes would overlap a lot and require more tests,
02694     but perhaps that wouldn't add much to the cost before trying individual
02695     nodes and edges. */
02696 }
02697 
02698 static void init_bb(graph_t *g)
02699 {
02700         node_t *n;
02701 
02702             for (n = agfstnode(g); n; n = agnxtnode(g, n))
02703                         init_bb_node(g, n);
02704 }
02705 
02706 extern gvevent_key_binding_t gvevent_key_binding[];
02707 extern int gvevent_key_binding_size;
02708 extern gvdevice_callbacks_t gvdevice_callbacks;
02709 
02710 int gvRenderJobs (GVC_t * gvc, graph_t * g)
02711 {
02712     static GVJ_t *prevjob;
02713     GVJ_t *job, *firstjob;
02714 
02715     if (!GD_drawing(g)) {
02716         agerr (AGERR, "Layout was not done.  Missing layout plugins? \n");
02717         return -1;
02718     }
02719 
02720     init_bb(g);
02721     init_gvc(gvc, g);
02722     init_layering(gvc, g);
02723 
02724     gvc->keybindings = gvevent_key_binding;
02725     gvc->numkeys = gvevent_key_binding_size;
02726     for (job = gvjobs_first(gvc); job; job = gvjobs_next(gvc)) {
02727         if (gvc->gvg) {
02728             job->input_filename = gvc->gvg->input_filename;
02729             job->graph_index = gvc->gvg->graph_index;
02730         }
02731         else {
02732             job->input_filename = NULL;
02733             job->graph_index = 0;
02734         }
02735         job->common = &(gvc->common);
02736         job->layout_type = gvc->layout.type;
02737         if (!GD_drawing(g)) {
02738             agerr (AGERR, "layout was not done\n");
02739             return -1;
02740         }
02741 
02742         job->output_lang = gvrender_select(job, job->output_langname);
02743         if (job->output_lang == NO_SUPPORT) {
02744             agerr (AGERR, "renderer for %s is unavailable\n", job->output_langname);
02745             return -1;
02746         }
02747 #ifdef WITH_CODEGENS
02748         Output_lang = job->output_lang;
02749 #endif
02750 
02751         switch (job->output_lang) {
02752         case VTX:
02753             /* output sorted, i.e. all nodes then all edges */
02754             job->flags |= EMIT_SORTED;
02755             break;
02756         case DIA:
02757             /* output in preorder traversal of the graph */
02758             job->flags |= EMIT_PREORDER
02759                        | GVDEVICE_BINARY_FORMAT;
02760             break;
02761         default:
02762             job->flags |= chkOrder(g);
02763             break;
02764         }
02765 
02766         /* if we already have an active job list and the device doesn't support mutiple output files, or we are about to write to a different output device */
02767         firstjob = gvc->active_jobs;
02768         if (firstjob) {
02769             if (! (firstjob->flags & GVDEVICE_DOES_PAGES)
02770               || (strcmp(job->output_langname,firstjob->output_langname))) {
02771 
02772                 gvrender_end_job(firstjob);
02773             
02774                 gvc->active_jobs = NULL; /* clear active list */
02775                 gvc->common.viewNum = 0;
02776                 prevjob = NULL;
02777             }
02778         }
02779         else {
02780             prevjob = NULL;
02781         }
02782 
02783         if (prevjob) {
02784             prevjob->next_active = job;  /* insert job in active list */
02785             job->output_file = prevjob->output_file;  /* FIXME - this is dumb ! */
02786         }
02787         else {
02788             gvc->active_jobs = job;   /* first job of new list */
02789             gvrender_begin_job(job);
02790         }
02791         job->next_active = NULL;      /* terminate active list */
02792         job->callbacks = &gvdevice_callbacks;
02793 
02794         init_job_pad(job);
02795         init_job_margin(job);
02796         init_job_dpi(job, g);
02797         init_job_viewport(job, g);
02798         init_job_pagination(job, g);
02799 
02800         if (! (job->flags & GVDEVICE_EVENTS)) {
02801 #ifdef DEBUG
02802                 /* Show_boxes is not defined, if at all, 
02803                  * until splines are generated in dot 
02804                  */
02805             job->common->show_boxes = Show_boxes; 
02806 #endif
02807             emit_graph(job, g);
02808         }
02809 
02810         /* the last job, after all input graphs are processed,
02811          *      is finalized from gvFreeContext()
02812          */
02813         prevjob = job;
02814     }
02815     return 0;
02816 }

Generated on Mon Mar 31 19:03:24 2008 for Graphviz by  doxygen 1.5.1