Graphviz  2.29.20120523.0446
lib/common/emit.c
Go to the documentation of this file.
00001 /* $Id$ $Revision$ */
00002 /* vim:set shiftwidth=4 ts=8: */
00003 
00004 /*************************************************************************
00005  * Copyright (c) 2011 AT&T Intellectual Property 
00006  * All rights reserved. This program and the accompanying materials
00007  * are made available under the terms of the Eclipse Public License v1.0
00008  * which accompanies this distribution, and is available at
00009  * http://www.eclipse.org/legal/epl-v10.html
00010  *
00011  * Contributors: See CVS logs. Details at http://www.graphviz.org/
00012  *************************************************************************/
00013 
00014 /*
00015  *  graphics code generator
00016  */
00017 
00018 #ifdef HAVE_CONFIG_H
00019 #include "config.h"
00020 #endif
00021 
00022 #include <string.h>
00023 #include <ctype.h>
00024 #include <locale.h>
00025 #include "render.h"
00026 #include "agxbuf.h"
00027 #include "htmltable.h"
00028 #include "gvc.h"
00029 #include "xdot.h"
00030 
00031 #ifdef WIN32
00032 #define strtok_r strtok_s
00033 #endif
00034 
00035 #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)
00036 #define FUZZ 3
00037 #define EPSILON .0001
00038 
00039 typedef struct {
00040     xdot_op op;
00041     boxf bb;
00042     textpara_t* para;
00043 } exdot_op;
00044 
00045 void* init_xdot (Agraph_t* g)
00046 {
00047     char* p;
00048     xdot* xd = NULL;
00049 
00050     if ((p = agget(g, "_draw_")) && p[0]) {
00051 #ifdef DEBUG
00052         if (Verbose) {
00053             start_timer();
00054         }
00055 #endif
00056         xd = parseXDotF (p, NULL, sizeof (exdot_op));
00057 
00058         if (!xd) {
00059             agerr(AGWARN, "Could not parse \"_draw_\" attribute in graph %s\n", agnameof(g));
00060             agerr(AGPREV, "  \"%s\"\n", p);
00061         }
00062 #ifdef DEBUG
00063         if (Verbose) {
00064             xdot_stats stats;
00065             double et = elapsed_sec();
00066             statXDot (xd, &stats);
00067             fprintf (stderr, "%d ops %.2f sec\n", stats.cnt, et);
00068             fprintf (stderr, "%d polygons %d points\n", stats.n_polygon, stats.n_polygon_pts);
00069             fprintf (stderr, "%d polylines %d points\n", stats.n_polyline, stats.n_polyline_pts);
00070             fprintf (stderr, "%d beziers %d points\n", stats.n_bezier, stats.n_bezier_pts);
00071             fprintf (stderr, "%d ellipses\n", stats.n_ellipse);
00072             fprintf (stderr, "%d texts\n", stats.n_text);
00073         }
00074 #endif
00075     }
00076     return xd;
00077 }
00078 
00079 static char *defaultlinestyle[3] = { "solid\0", "setlinewidth\0001\0", 0 };
00080 
00081 /* push empty graphic state for current object */
00082 obj_state_t* push_obj_state(GVJ_t *job)
00083 {
00084     obj_state_t *obj, *parent;
00085 
00086     if (! (obj = zmalloc(sizeof(obj_state_t))))
00087         agerr(AGERR, "no memory from zmalloc()\n");
00088 
00089     parent = obj->parent = job->obj;
00090     job->obj = obj;
00091     if (parent) {
00092         obj->pencolor = parent->pencolor;        /* default styles to parent's style */
00093         obj->fillcolor = parent->fillcolor;
00094         obj->pen = parent->pen;
00095         obj->fill = parent->fill;
00096         obj->penwidth = parent->penwidth;
00097         obj->gradient_angle = parent->gradient_angle;
00098         obj->stopcolor = parent->stopcolor;
00099     }
00100     else {
00101         /* obj->pencolor = NULL */
00102         /* obj->fillcolor = NULL */
00103         obj->pen = PEN_SOLID;
00104         obj->fill = FILL_NONE;
00105         obj->penwidth = PENWIDTH_NORMAL;
00106     }
00107     return obj;
00108 }
00109 
00110 /* pop graphic state of current object */
00111 void pop_obj_state(GVJ_t *job)
00112 {
00113     obj_state_t *obj = job->obj;
00114 
00115     assert(obj);
00116 
00117     free(obj->id);
00118     free(obj->url);
00119     free(obj->labelurl);
00120     free(obj->tailurl);
00121     free(obj->headurl);
00122     free(obj->tooltip);
00123     free(obj->labeltooltip);
00124     free(obj->tailtooltip);
00125     free(obj->headtooltip);
00126     free(obj->target);
00127     free(obj->labeltarget);
00128     free(obj->tailtarget);
00129     free(obj->headtarget);
00130     free(obj->url_map_p);
00131     free(obj->url_bsplinemap_p);
00132     free(obj->url_bsplinemap_n);
00133 
00134     job->obj = obj->parent;
00135     free(obj);
00136 }
00137 
00138 /* initMapData:
00139  * Store image map data into job, substituting for node, edge, etc.
00140  * names.
00141  * Return 1 if an assignment was made for url or tooltip or target.
00142  */
00143 int
00144 initMapData (GVJ_t* job, char* lbl, char* url, char* tooltip, char* target, char *id,
00145   void* gobj)
00146 {
00147     obj_state_t *obj = job->obj;
00148     int flags = job->flags;
00149     int assigned = 0;
00150 
00151     if ((flags & GVRENDER_DOES_LABELS) && lbl)
00152         obj->label = lbl;
00153     if (flags & GVRENDER_DOES_MAPS) {
00154         obj->id = strdup_and_subst_obj(id, gobj);
00155         if (url && url[0]) {
00156             obj->url = strdup_and_subst_obj(url, gobj);
00157             assigned = 1;
00158         }
00159     }
00160     if (flags & GVRENDER_DOES_TOOLTIPS) {
00161         if (tooltip && tooltip[0]) {
00162             obj->tooltip = strdup_and_subst_obj(tooltip, gobj);
00163             obj->explicit_tooltip = TRUE;
00164             assigned = 1;
00165         }
00166         else if (obj->label) {
00167             obj->tooltip = strdup(obj->label);
00168             assigned = 1;
00169         }
00170     }
00171     if ((flags & GVRENDER_DOES_TARGETS) && target && target[0]) {
00172         obj->target = strdup_and_subst_obj(target, gobj);
00173         assigned = 1;
00174     }
00175     return assigned;
00176 }
00177 
00178 /* genObjId:
00179  * Use id of root graph if any, plus kind and internal id of object
00180  */
00181 char*
00182 getObjId (GVJ_t* job, void* obj, agxbuf* xb)
00183 {
00184     char* id;
00185     graph_t* root = job->gvc->g;
00186     char* gid = GD_drawing(root)->id;
00187     long idnum;
00188     char* pfx;
00189     char buf[30]; /* large enough for decimal 64-bit int */
00190 
00191     id = agget(obj, "id");
00192     if (id && *id)
00193         return id;
00194 
00195     switch (agobjkind(obj)) {
00196 #ifndef WITH_CGRAPH
00197     case AGGRAPH:
00198         idnum = ((graph_t*)obj)->meta_node->id;
00199 #else
00200     case AGRAPH:
00201         idnum = AGSEQ(obj);
00202 #endif
00203         pfx = "graph";
00204         break;
00205     case AGNODE:
00206         idnum = AGSEQ((Agnode_t*)obj);
00207         pfx = "node";
00208         break;
00209     case AGEDGE:
00210         idnum = AGSEQ((Agedge_t*)obj);
00211         pfx = "edge";
00212         break;
00213     }
00214 
00215     if (gid) {
00216         agxbput (xb, gid);
00217         agxbputc (xb, '_');
00218     }
00219     agxbput (xb, pfx);
00220     sprintf (buf, "%ld", idnum);
00221     agxbput (xb, buf);
00222 
00223     return agxbuse(xb);
00224 }
00225 
00226 static void
00227 initObjMapData (GVJ_t* job, textlabel_t *lab, void* gobj)
00228 {
00229     char* lbl;
00230     char* url = agget(gobj, "href");
00231     char* tooltip = agget(gobj, "tooltip");
00232     char* target = agget(gobj, "target");
00233     char* id;
00234     unsigned char buf[SMALLBUF];
00235     agxbuf xb;
00236 
00237     agxbinit(&xb, SMALLBUF, buf);
00238 
00239     if (lab) lbl = lab->text;
00240     else lbl = NULL;
00241     if (!url || !*url)  /* try URL as an alias for href */
00242         url = agget(gobj, "URL");
00243     id = getObjId (job, gobj, &xb);
00244     initMapData (job, lbl, url, tooltip, target, id, gobj);
00245 
00246     agxbfree(&xb);
00247 }
00248 
00249 static void map_point(GVJ_t *job, pointf pf)
00250 {
00251     obj_state_t *obj = job->obj;
00252     int flags = job->flags;
00253     pointf *p;
00254 
00255     if (flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS)) {
00256         if (flags & GVRENDER_DOES_MAP_RECTANGLE) {
00257             obj->url_map_shape = MAP_RECTANGLE;
00258             obj->url_map_n = 2;
00259         }
00260         else {
00261             obj->url_map_shape = MAP_POLYGON;
00262             obj->url_map_n = 4;
00263         }
00264         free(obj->url_map_p);
00265         obj->url_map_p = p = N_NEW(obj->url_map_n, pointf);
00266         P2RECT(pf, p, FUZZ, FUZZ);
00267         if (! (flags & GVRENDER_DOES_TRANSFORM))
00268             gvrender_ptf_A(job, p, p, 2);
00269         if (! (flags & GVRENDER_DOES_MAP_RECTANGLE))
00270             rect2poly(p);
00271     }
00272 }
00273 
00274 static char **checkClusterStyle(graph_t* sg, int *flagp)
00275 {
00276     char *style;
00277     char **pstyle = 0;
00278     int istyle = 0;
00279 
00280     if (((style = agget(sg, "style")) != 0) && style[0]) {
00281         char **pp;
00282         char **qp;
00283         char *p;
00284         pp = pstyle = parse_style(style);
00285         while ((p = *pp)) {
00286             if (strcmp(p, "filled") == 0) {
00287                 istyle |= FILLED;
00288                 pp++;
00289             }else if (strcmp(p, "radial") == 0) {
00290                 istyle |= (FILLED | RADIAL);
00291                 qp = pp; /* remove rounded from list passed to renderer */
00292                 do {
00293                     qp++;
00294                     *(qp-1) = *qp;
00295                 } while (*qp);
00296             }else if (strcmp(p, "rounded") == 0) {
00297                 istyle |= ROUNDED;
00298                 qp = pp; /* remove rounded from list passed to renderer */
00299                 do {
00300                     qp++;
00301                     *(qp-1) = *qp;
00302                 } while (*qp);
00303             } else pp++;
00304         }
00305     }
00306 
00307     *flagp = istyle;
00308     return pstyle;
00309 }
00310 
00311 void emit_map_rect(GVJ_t *job, boxf b)
00312 {
00313     obj_state_t *obj = job->obj;
00314     int flags = job->flags;
00315     pointf *p;
00316 
00317     if (flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS)) {
00318         if (flags & GVRENDER_DOES_MAP_RECTANGLE) {
00319             obj->url_map_shape = MAP_RECTANGLE;
00320             obj->url_map_n = 2;
00321         }
00322         else {
00323             obj->url_map_shape = MAP_POLYGON;
00324             obj->url_map_n = 4;
00325         }
00326         free(obj->url_map_p);
00327         obj->url_map_p = p = N_NEW(obj->url_map_n, pointf);
00328         p[0] = b.LL;
00329         p[1] = b.UR;
00330         if (! (flags & GVRENDER_DOES_TRANSFORM))
00331             gvrender_ptf_A(job, p, p, 2);
00332         if (! (flags & GVRENDER_DOES_MAP_RECTANGLE))
00333             rect2poly(p);
00334     }
00335 }
00336 
00337 static void map_label(GVJ_t *job, textlabel_t *lab)
00338 {
00339     obj_state_t *obj = job->obj;
00340     int flags = job->flags;
00341     pointf *p;
00342 
00343     if (flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS)) {
00344         if (flags & GVRENDER_DOES_MAP_RECTANGLE) {
00345             obj->url_map_shape = MAP_RECTANGLE;
00346             obj->url_map_n = 2;
00347         }
00348         else {
00349             obj->url_map_shape = MAP_POLYGON;
00350             obj->url_map_n = 4;
00351         }
00352         free(obj->url_map_p);
00353         obj->url_map_p = p = N_NEW(obj->url_map_n, pointf);
00354         P2RECT(lab->pos, p, lab->dimen.x / 2., lab->dimen.y / 2.);
00355         if (! (flags & GVRENDER_DOES_TRANSFORM))
00356             gvrender_ptf_A(job, p, p, 2);
00357         if (! (flags & GVRENDER_DOES_MAP_RECTANGLE))
00358             rect2poly(p);
00359     }
00360 }
00361 
00362 /* isRect:
00363  * isRect function returns true when polygon has
00364  * regular rectangular shape. Rectangle is regular when
00365  * it is not skewed and distorted and orientation is almost zero
00366  */
00367 static boolean isRect(polygon_t * p)
00368 {
00369     return (p->sides == 4 && (ROUND(p->orientation) % 90) == 0
00370             && p->distortion == 0.0 && p->skew == 0.0);
00371 }
00372 
00373 /*
00374  * isFilled function returns 1 if filled style has been set for node 'n'
00375  * otherwise returns 0. it accepts pointer to node_t as an argument
00376  */
00377 static int ifFilled(node_t * n)
00378 {
00379     char *style, *p, **pp;
00380     int r = 0;
00381     style = late_nnstring(n, N_style, "");
00382     if (style[0]) {
00383         pp = parse_style(style);
00384         while ((p = *pp)) {
00385             if (strcmp(p, "filled") == 0)
00386                 r = 1;
00387             pp++;
00388         }
00389     }
00390     return r;
00391 }
00392 
00393 /* pEllipse:
00394  * pEllipse function returns 'np' points from the circumference
00395  * of ellipse described by radii 'a' and 'b'.
00396  * Assumes 'np' is greater than zero.
00397  * 'np' should be at least 4 to sample polygon from ellipse
00398  */
00399 static pointf *pEllipse(double a, double b, int np)
00400 {
00401     double theta = 0.0;
00402     double deltheta = 2 * M_PI / np;
00403     int i;
00404     pointf *ps;
00405 
00406     ps = N_NEW(np, pointf);
00407     for (i = 0; i < np; i++) {
00408         ps[i].x = a * cos(theta);
00409         ps[i].y = b * sin(theta);
00410         theta += deltheta;
00411     }
00412     return ps;
00413 }
00414 
00415 #define HW 2.0   /* maximum distance away from line, in points */
00416 
00417 /* check_control_points:
00418  * check_control_points function checks the size of quadrilateral
00419  * formed by four control points
00420  * returns 1 if four points are in line (or close to line)
00421  * else return 0
00422  */
00423 static int check_control_points(pointf *cp)
00424 {
00425     double dis1 = ptToLine2 (cp[0], cp[3], cp[1]);
00426     double dis2 = ptToLine2 (cp[0], cp[3], cp[2]);
00427     if (dis1 < HW*HW && dis2 < HW*HW)
00428         return 1;
00429     else
00430         return 0;
00431 }
00432 
00433 /* update bounding box to contain a bezier segment */
00434 void update_bb_bz(boxf *bb, pointf *cp)
00435 {
00436 
00437     /* if any control point of the segment is outside the bounding box */
00438     if (cp[0].x > bb->UR.x || cp[0].x < bb->LL.x ||
00439         cp[0].y > bb->UR.y || cp[0].y < bb->LL.y ||
00440         cp[1].x > bb->UR.x || cp[1].x < bb->LL.x ||
00441         cp[1].y > bb->UR.y || cp[1].y < bb->LL.y ||
00442         cp[2].x > bb->UR.x || cp[2].x < bb->LL.x ||
00443         cp[2].y > bb->UR.y || cp[2].y < bb->LL.y ||
00444         cp[3].x > bb->UR.x || cp[3].x < bb->LL.x ||
00445         cp[3].y > bb->UR.y || cp[3].y < bb->LL.y) {
00446 
00447         /* if the segment is sufficiently refined */
00448         if (check_control_points(cp)) {        
00449             int i;
00450             /* expand the bounding box */
00451             for (i = 0; i < 4; i++) {
00452                 if (cp[i].x > bb->UR.x)
00453                     bb->UR.x = cp[i].x;
00454                 else if (cp[i].x < bb->LL.x)
00455                     bb->LL.x = cp[i].x;
00456                 if (cp[i].y > bb->UR.y)
00457                     bb->UR.y = cp[i].y;
00458                 else if (cp[i].y < bb->LL.y)
00459                     bb->LL.y = cp[i].y;
00460             }
00461         }
00462         else { /* else refine the segment */
00463             pointf left[4], right[4];
00464             Bezier (cp, 3, 0.5, left, right);
00465             update_bb_bz(bb, left);
00466             update_bb_bz(bb, right);
00467         }
00468     }
00469 }
00470 
00471 #if (DEBUG==2)
00472 static void psmapOutput (pointf* ps, int n)
00473 {
00474    int i;
00475    fprintf (stdout, "newpath %f %f moveto\n", ps[0].x, ps[0].y);
00476    for (i=1; i < n; i++)
00477         fprintf (stdout, "%f %f lineto\n", ps[i].x, ps[i].y);
00478    fprintf (stdout, "closepath stroke\n");
00479 }
00480 #endif
00481 
00482 typedef struct segitem_s {
00483     pointf p;
00484     struct segitem_s* next;
00485 } segitem_t;
00486 
00487 #define MARK_FIRST_SEG(L) ((L)->next = (segitem_t*)1)
00488 #define FIRST_SEG(L) ((L)->next == (segitem_t*)1)
00489 #define INIT_SEG(P,L) {(L)->next = 0; (L)->p = P;} 
00490 
00491 static segitem_t* appendSeg (pointf p, segitem_t* lp)
00492 {
00493     segitem_t* s = GNEW(segitem_t);
00494     INIT_SEG (p, s);
00495     lp->next = s;
00496     return s;
00497 }
00498 
00499 /* map_bspline_poly:
00500  * Output the polygon determined by the n points in p1, followed
00501  * by the n points in p2 in reverse order. Assumes n <= 50.
00502  */
00503 static void map_bspline_poly(pointf **pbs_p, int **pbs_n, int *pbs_poly_n, int n, pointf* p1, pointf* p2)
00504 {
00505     int i = 0, nump = 0, last = 2*n-1;
00506 
00507     for ( ; i < *pbs_poly_n; i++)
00508         nump += (*pbs_n)[i];
00509 
00510     (*pbs_poly_n)++;
00511     *pbs_n = grealloc(*pbs_n, (*pbs_poly_n) * sizeof(int));
00512     (*pbs_n)[i] = 2*n;
00513     *pbs_p = grealloc(*pbs_p, (nump + 2*n) * sizeof(pointf));
00514 
00515     for (i = 0; i < n; i++) {
00516         (*pbs_p)[nump+i] = p1[i];
00517         (*pbs_p)[nump+last-i] = p2[i];
00518     }
00519 #if (DEBUG==2)
00520     psmapOutput (*pbs_p + nump, last+1);
00521 #endif
00522 }
00523 
00524 /* approx_bezier:
00525  * Approximate Bezier by line segments. If the four points are
00526  * almost colinear, as determined by check_control_points, we store
00527  * the segment cp[0]-cp[3]. Otherwise we split the Bezier into 2 and recurse. 
00528  * Since 2 contiguous segments share an endpoint, we actually store
00529  * the segments as a list of points.
00530  * New points are appended to the list given by lp. The tail of the
00531  * list is returned.
00532  */
00533 static segitem_t* approx_bezier (pointf *cp, segitem_t* lp)
00534 {
00535     pointf left[4], right[4];
00536 
00537     if (check_control_points(cp)) {
00538         if (FIRST_SEG (lp)) INIT_SEG (cp[0], lp);
00539         lp = appendSeg (cp[3], lp);
00540     }
00541     else {
00542         Bezier (cp, 3, 0.5, left, right);
00543         lp = approx_bezier (left, lp);
00544         lp = approx_bezier (right, lp);
00545     }
00546     return lp;
00547 }
00548 
00549 /* bisect:
00550  * Return the angle of the bisector between the two rays
00551  * pp-cp and cp-np. The bisector returned is always to the
00552  * left of pp-cp-np.
00553  */
00554 static double bisect (pointf pp, pointf cp, pointf np)
00555 {
00556   double ang, theta, phi;
00557   theta = atan2(np.y - cp.y,np.x - cp.x);
00558   phi = atan2(pp.y - cp.y,pp.x - cp.x);
00559   ang = theta - phi;
00560   if (ang > 0) ang -= 2*M_PI;
00561 
00562   return (phi + ang/2.0);
00563 }
00564 
00565 /* mkSegPts:
00566  * Determine polygon points related to 2 segments prv-cur and cur-nxt.
00567  * The points lie on the bisector of the 2 segments, passing through cur,
00568  * and distance w2 from cur. The points are stored in p1 and p2.
00569  * If p1 is NULL, we use the normal to cur-nxt.
00570  * If p2 is NULL, we use the normal to prv-cur.
00571  * Assume at least one of prv or nxt is non-NULL.
00572  */
00573 static void mkSegPts (segitem_t* prv, segitem_t* cur, segitem_t* nxt,
00574         pointf* p1, pointf* p2, double w2)
00575 {
00576     pointf cp, pp, np;
00577     double theta, delx, dely;
00578     pointf p;
00579 
00580     cp = cur->p;
00581     /* if prv or nxt are NULL, use the one given to create a collinear
00582      * prv or nxt. This could be more efficiently done with special case code, 
00583      * but this way is more uniform.
00584      */
00585     if (prv) {
00586         pp = prv->p;
00587         if (nxt)
00588             np = nxt->p;
00589         else {
00590             np.x = 2*cp.x - pp.x;
00591             np.y = 2*cp.y - pp.y;
00592         }
00593     }
00594     else {
00595         np = nxt->p;
00596         pp.x = 2*cp.x - np.x;
00597         pp.y = 2*cp.y - np.y;
00598     }
00599     theta = bisect(pp,cp,np);
00600     delx = w2*cos(theta);
00601     dely = w2*sin(theta);
00602     p.x = cp.x + delx;
00603     p.y = cp.y + dely;
00604     *p1 = p;
00605     p.x = cp.x - delx;
00606     p.y = cp.y - dely;
00607     *p2 = p;
00608 }
00609 
00610 /* map_output_bspline:
00611  * Construct and output a closed polygon approximating the input
00612  * B-spline bp. We do this by first approximating bp by a sequence
00613  * of line segments. We then use the sequence of segments to determine
00614  * the polygon.
00615  * In cmapx, polygons are limited to 100 points, so we output polygons
00616  * in chunks of 100.
00617  */
00618 static void map_output_bspline (pointf **pbs, int **pbs_n, int *pbs_poly_n, bezier* bp, double w2)
00619 {
00620     segitem_t* segl = GNEW(segitem_t);
00621     segitem_t* segp = segl;
00622     segitem_t* segprev;
00623     segitem_t* segnext;
00624     int nc, j, k, cnt;
00625     pointf pts[4], pt1[50], pt2[50];
00626 
00627     MARK_FIRST_SEG(segl);
00628     nc = (bp->size - 1)/3; /* nc is number of bezier curves */
00629     for (j = 0; j < nc; j++) {
00630         for (k = 0; k < 4; k++) {
00631             pts[k] = bp->list[3*j + k];
00632         }
00633         segp = approx_bezier (pts, segp);
00634     }
00635 
00636     segp = segl;
00637     segprev = 0;
00638     cnt = 0;
00639     while (segp) {
00640         segnext = segp->next;
00641         mkSegPts (segprev, segp, segnext, pt1+cnt, pt2+cnt, w2);
00642         cnt++;
00643         if ((segnext == NULL) || (cnt == 50)) {
00644             map_bspline_poly (pbs, pbs_n, pbs_poly_n, cnt, pt1, pt2);
00645             pt1[0] = pt1[cnt-1];
00646             pt2[0] = pt2[cnt-1];
00647             cnt = 1;
00648         }
00649         segprev = segp;
00650         segp = segnext;
00651     }
00652 
00653     /* free segl */
00654     while (segl) {
00655         segp = segl->next;
00656         free (segl);
00657         segl = segp;
00658     }
00659 }
00660 
00661 static boolean is_natural_number(char *sstr)
00662 {
00663     unsigned char *str = (unsigned char *) sstr;
00664 
00665     while (*str)
00666         if (NOT(isdigit(*str++)))
00667             return FALSE;
00668     return TRUE;
00669 }
00670 
00671 static int layer_index(GVC_t *gvc, char *str, int all)
00672 {
00673     /* GVJ_t *job = gvc->job; */
00674     int i;
00675 
00676     if (streq(str, "all"))
00677         return all;
00678     if (is_natural_number(str))
00679         return atoi(str);
00680     if (gvc->layerIDs)
00681         for (i = 1; i <= gvc->numLayers; i++)
00682             if (streq(str, gvc->layerIDs[i]))
00683                 return i;
00684     return -1;
00685 }
00686 
00687 static boolean selectedLayer(GVC_t *gvc, int layerNum, int numLayers, char *spec)
00688 {
00689     int n0, n1;
00690     unsigned char buf[SMALLBUF];
00691     char *w0, *w1;
00692     char *buf_part_p = NULL, *buf_p = NULL, *cur, *part_in_p;
00693     agxbuf xb;
00694     boolean rval = FALSE;
00695 
00696     agxbinit(&xb, SMALLBUF, buf);
00697     agxbput(&xb, spec);
00698     part_in_p = agxbuse(&xb);
00699 
00700     /* Thanks to Matteo Nastasi for this extended code. */
00701     while ((rval == FALSE) && (cur = strtok_r(part_in_p, gvc->layerListDelims, &buf_part_p))) {
00702         w1 = w0 = strtok_r (cur, gvc->layerDelims, &buf_p);
00703         if (w0)
00704             w1 = strtok_r (NULL, gvc->layerDelims, &buf_p);
00705         switch ((w0 != NULL) + (w1 != NULL)) {
00706         case 0:
00707             rval = FALSE;
00708             break;
00709         case 1:
00710             n0 = layer_index(gvc, w0, layerNum);
00711             rval = (n0 == layerNum);
00712             break;
00713         case 2:
00714             n0 = layer_index(gvc, w0, 0);
00715             n1 = layer_index(gvc, w1, numLayers);
00716             if ((n0 >= 0) || (n1 >= 0)) {
00717                 if (n0 > n1) {
00718                     int t = n0;
00719                     n0 = n1;
00720                     n1 = t;
00721                 }
00722                 rval = BETWEEN(n0, layerNum, n1);
00723             }
00724             break;
00725         }
00726         part_in_p = NULL;
00727     }
00728     agxbfree(&xb);
00729     return rval;
00730 }
00731 
00732 static boolean selectedlayer(GVJ_t *job, char *spec)
00733 {
00734     return selectedLayer (job->gvc, job->layerNum, job->numLayers, spec);
00735 }
00736 
00737 /* parse_layerselect:
00738  * Parse the graph's layerselect attribute, which determines
00739  * which layers are emitted. The specification is the same used
00740  * by the layer attribute.
00741  *
00742  * If we find n layers, we return an array arr of n+2 ints. arr[0]=n.
00743  * arr[n+1]=numLayers+1, acting as a sentinel. The other entries give
00744  * the desired layer indices.
00745  *
00746  * If no layers are detected, NULL is returned.
00747  *
00748  * This implementation does a linear walk through each layer index and
00749  * uses selectedLayer to match it against p. There is probably a more
00750  * efficient way to do this, but this is simple and until we find people
00751  * using huge numbers of layers, it should be adequate.
00752  */
00753 static int* parse_layerselect(GVC_t *gvc, graph_t * g, char *p)
00754 {
00755     int* laylist = N_GNEW(gvc->numLayers+2,int);
00756     int i, cnt = 0;
00757     for (i = 1; i <=gvc->numLayers; i++) {
00758         if (selectedLayer (gvc, i, gvc->numLayers, p)) {
00759             laylist[++cnt] = i;
00760         } 
00761     }
00762     if (cnt) {
00763         laylist[0] = cnt;
00764         laylist[cnt+1] = gvc->numLayers+1;
00765     }
00766     else {
00767         agerr(AGWARN, "The layerselect attribute \"%s\" does not match any layer specifed by the layers attribute - ignored.\n", p);
00768         laylist[0] = cnt;
00769         free (laylist);
00770         laylist = NULL;
00771     }
00772     return laylist;
00773 }
00774 
00775 /* parse_layers:
00776  * Split input string into tokens, with separators specified by
00777  * the layersep attribute. Store the values in the gvc->layerIDs array,
00778  * starting at index 1, and return the count.
00779  * Free previously stored list. Note that there is no mechanism
00780  * to free the memory before exit.
00781  */
00782 static int parse_layers(GVC_t *gvc, graph_t * g, char *p)
00783 {
00784     int ntok;
00785     char *tok;
00786     int sz;
00787 
00788     gvc->layerDelims = agget(g, "layersep");
00789     if (!gvc->layerDelims)
00790         gvc->layerDelims = DEFAULT_LAYERSEP;
00791     gvc->layerListDelims = agget(g, "layerlistsep");
00792     if (!gvc->layerListDelims)
00793         gvc->layerListDelims = DEFAULT_LAYERLISTSEP;
00794     if ((tok = strpbrk (gvc->layerDelims, gvc->layerListDelims))) { /* conflict in delimiter strings */
00795         agerr(AGWARN, "The character \'%c\' appears in both the layersep and layerlistsep attributes - layerlistsep ignored.\n", *tok);
00796         gvc->layerListDelims = "";
00797     }
00798 
00799     ntok = 0;
00800     sz = 0;
00801     gvc->layers = strdup(p);
00802 
00803     for (tok = strtok(gvc->layers, gvc->layerDelims); tok;
00804          tok = strtok(NULL, gvc->layerDelims)) {
00805         ntok++;
00806         if (ntok > sz) {
00807             sz += SMALLBUF;
00808             gvc->layerIDs = ALLOC(sz, gvc->layerIDs, char *);
00809         }
00810         gvc->layerIDs[ntok] = tok;
00811     }
00812     if (ntok) {
00813         gvc->layerIDs = RALLOC(ntok + 2, gvc->layerIDs, char *);        /* shrink to minimum size */
00814         gvc->layerIDs[0] = NULL;
00815         gvc->layerIDs[ntok + 1] = NULL;
00816     }
00817 
00818     return ntok;
00819 }
00820 
00821 /* chkOrder:
00822  * Determine order of output.
00823  * Output usually in breadth first graph walk order
00824  */
00825 static int chkOrder(graph_t * g)
00826 {
00827     char *p = agget(g, "outputorder");
00828     if (p) {
00829         char c = *p;
00830         if ((c == 'n') && !strcmp(p + 1, "odesfirst"))
00831             return EMIT_SORTED;
00832         if ((c == 'e') && !strcmp(p + 1, "dgesfirst"))
00833             return EMIT_EDGE_SORTED;
00834     }
00835     return 0;
00836 }
00837 
00838 static void init_layering(GVC_t * gvc, graph_t * g)
00839 {
00840     char *str;
00841 
00842     /* free layer strings and pointers from previous graph */
00843     if (gvc->layers) {
00844         free(gvc->layers);
00845         gvc->layers = NULL;
00846     }
00847     if (gvc->layerIDs) {
00848         free(gvc->layerIDs);
00849         gvc->layerIDs = NULL;
00850     }
00851     if (gvc->layerlist) {
00852         free(gvc->layerlist);
00853         gvc->layerlist = NULL;
00854     }
00855     if ((str = agget(g, "layers")) != 0) {
00856         gvc->numLayers = parse_layers(gvc, g, str);
00857         if (((str = agget(g, "layerselect")) != 0) && *str) {
00858             gvc->layerlist = parse_layerselect(gvc, g, str);
00859         }
00860     } else {
00861         gvc->layerIDs = NULL;
00862         gvc->numLayers = 1;
00863     }
00864 }
00865 
00866 /* numPhysicalLayers:
00867  * Return number of physical layers to be emitted.
00868  */
00869 static int numPhysicalLayers (GVJ_t *job)
00870 {
00871     if (job->gvc->layerlist) {
00872         return job->gvc->layerlist[0];
00873     }
00874     else
00875         return job->numLayers;
00876 
00877 }
00878 
00879 static void firstlayer(GVJ_t *job, int** listp)
00880 {
00881     job->numLayers = job->gvc->numLayers;
00882     if (job->gvc->layerlist) {
00883         int *list = job->gvc->layerlist;
00884         int cnt = *list++;
00885         if ((cnt > 1) && (! (job->flags & GVDEVICE_DOES_LAYERS))) {
00886             agerr(AGWARN, "layers not supported in %s output\n",
00887                 job->output_langname);
00888             list[1] = job->numLayers + 1; /* only one layer printed */
00889         }
00890         job->layerNum = *list++;
00891         *listp = list;
00892     }
00893     else {
00894         if ((job->numLayers > 1)
00895                 && (! (job->flags & GVDEVICE_DOES_LAYERS))) {
00896             agerr(AGWARN, "layers not supported in %s output\n",
00897                 job->output_langname);
00898             job->numLayers = 1;
00899         }
00900         job->layerNum = 1;
00901         *listp = NULL;
00902     }
00903 }
00904 
00905 static boolean validlayer(GVJ_t *job)
00906 {
00907     return (job->layerNum <= job->numLayers);
00908 }
00909 
00910 static void nextlayer(GVJ_t *job, int** listp)
00911 {
00912     int *list = *listp;
00913     if (list) {
00914         job->layerNum = *list++;
00915         *listp = list;
00916     }
00917     else
00918         job->layerNum++;
00919 }
00920 
00921 static point pagecode(GVJ_t *job, char c)
00922 {
00923     point rv;
00924     rv.x = rv.y = 0;
00925     switch (c) {
00926     case 'T':
00927         job->pagesArrayFirst.y = job->pagesArraySize.y - 1;
00928         rv.y = -1;
00929         break;
00930     case 'B':
00931         rv.y = 1;
00932         break;
00933     case 'L':
00934         rv.x = 1;
00935         break;
00936     case 'R':
00937         job->pagesArrayFirst.x = job->pagesArraySize.x - 1;
00938         rv.x = -1;
00939         break;
00940     }
00941     return rv;
00942 }
00943 
00944 static void init_job_pagination(GVJ_t * job, graph_t *g)
00945 {
00946     GVC_t *gvc = job->gvc;
00947     pointf pageSize;    /* page size for the graph - points*/
00948     pointf imageSize;   /* image size on one page of the graph - points */
00949     pointf margin;      /* margin for a page of the graph - points */
00950     pointf centering = {0.0, 0.0}; /* centering offset - points */
00951 
00952     /* unpaginated image size - in points - in graph orientation */
00953     imageSize = job->view;
00954 
00955     /* rotate imageSize to page orientation */
00956     if (job->rotation)
00957         imageSize = exch_xyf(imageSize);
00958 
00959     /* margin - in points - in page orientation */
00960     margin = job->margin;
00961 
00962     /* determine pagination */
00963     if (gvc->graph_sets_pageSize && (job->flags & GVDEVICE_DOES_PAGES)) {
00964         /* page was set by user */
00965 
00966         /* determine size of page for image */
00967         pageSize.x = gvc->pageSize.x - 2 * margin.x;
00968         pageSize.y = gvc->pageSize.y - 2 * margin.y;
00969 
00970         if (pageSize.x < EPSILON)
00971             job->pagesArraySize.x = 1;
00972         else {
00973             job->pagesArraySize.x = (int)(imageSize.x / pageSize.x);
00974             if ((imageSize.x - (job->pagesArraySize.x * pageSize.x)) > EPSILON)
00975                 job->pagesArraySize.x++;
00976         }
00977         if (pageSize.y < EPSILON)
00978             job->pagesArraySize.y = 1;
00979         else {
00980             job->pagesArraySize.y = (int)(imageSize.y / pageSize.y);
00981             if ((imageSize.y - (job->pagesArraySize.y * pageSize.y)) > EPSILON)
00982                 job->pagesArraySize.y++;
00983         }
00984         job->numPages = job->pagesArraySize.x * job->pagesArraySize.y;
00985 
00986         /* find the drawable size in points */
00987         imageSize.x = MIN(imageSize.x, pageSize.x);
00988         imageSize.y = MIN(imageSize.y, pageSize.y);
00989     } else {
00990         /* page not set by user, use default from renderer */
00991         if (job->render.features) {
00992             pageSize.x = job->device.features->default_pagesize.x - 2*margin.x;
00993             if (pageSize.x < 0.)
00994                 pageSize.x = 0.;
00995             pageSize.y = job->device.features->default_pagesize.y - 2*margin.y;
00996             if (pageSize.y < 0.)
00997                 pageSize.y = 0.;
00998         }
00999         else
01000             pageSize.x = pageSize.y = 0.;
01001         job->pagesArraySize.x = job->pagesArraySize.y = job->numPages = 1;
01002         
01003         if (pageSize.x < imageSize.x)
01004             pageSize.x = imageSize.x;
01005         if (pageSize.y < imageSize.y)
01006             pageSize.y = imageSize.y;
01007     }
01008 
01009     /* initial window size */
01010 //fprintf(stderr,"page=%g,%g dpi=%g,%g zoom=%g\n", pageSize.x, pageSize.y, job->dpi.x, job->dpi.y, job->zoom);
01011     job->width = ROUND((pageSize.x + 2*margin.x) * job->dpi.x / POINTS_PER_INCH);
01012     job->height = ROUND((pageSize.y + 2*margin.y) * job->dpi.y / POINTS_PER_INCH);
01013 
01014     /* set up pagedir */
01015     job->pagesArrayMajor.x = job->pagesArrayMajor.y 
01016                 = job->pagesArrayMinor.x = job->pagesArrayMinor.y = 0;
01017     job->pagesArrayFirst.x = job->pagesArrayFirst.y = 0;
01018     job->pagesArrayMajor = pagecode(job, gvc->pagedir[0]);
01019     job->pagesArrayMinor = pagecode(job, gvc->pagedir[1]);
01020     if ((abs(job->pagesArrayMajor.x + job->pagesArrayMinor.x) != 1)
01021      || (abs(job->pagesArrayMajor.y + job->pagesArrayMinor.y) != 1)) {
01022         job->pagesArrayMajor = pagecode(job, 'B');
01023         job->pagesArrayMinor = pagecode(job, 'L');
01024         agerr(AGWARN, "pagedir=%s ignored\n", gvc->pagedir);
01025     }
01026 
01027     /* determine page box including centering */
01028     if (GD_drawing(g)->centered) {
01029         if (pageSize.x > imageSize.x)
01030             centering.x = (pageSize.x - imageSize.x) / 2;
01031         if (pageSize.y > imageSize.y)
01032             centering.y = (pageSize.y - imageSize.y) / 2;
01033     }
01034 
01035     /* rotate back into graph orientation */
01036     if (job->rotation) {
01037         imageSize = exch_xyf(imageSize);
01038         pageSize = exch_xyf(pageSize);
01039         margin = exch_xyf(margin);
01040         centering = exch_xyf(centering);
01041     }
01042 
01043     /* canvas area, centered if necessary */
01044     job->canvasBox.LL.x = margin.x + centering.x;
01045     job->canvasBox.LL.y = margin.y + centering.y;
01046     job->canvasBox.UR.x = margin.x + centering.x + imageSize.x;
01047     job->canvasBox.UR.y = margin.y + centering.y + imageSize.y;
01048 
01049     /* size of one page in graph units */
01050     job->pageSize.x = imageSize.x / job->zoom;
01051     job->pageSize.y = imageSize.y / job->zoom;
01052 
01053     /* pageBoundingBox in device units and page orientation */
01054     job->pageBoundingBox.LL.x = ROUND(job->canvasBox.LL.x * job->dpi.x / POINTS_PER_INCH);
01055     job->pageBoundingBox.LL.y = ROUND(job->canvasBox.LL.y * job->dpi.y / POINTS_PER_INCH);
01056     job->pageBoundingBox.UR.x = ROUND(job->canvasBox.UR.x * job->dpi.x / POINTS_PER_INCH);
01057     job->pageBoundingBox.UR.y = ROUND(job->canvasBox.UR.y * job->dpi.y / POINTS_PER_INCH);
01058     if (job->rotation) {
01059         job->pageBoundingBox.LL = exch_xy(job->pageBoundingBox.LL);
01060         job->pageBoundingBox.UR = exch_xy(job->pageBoundingBox.UR);
01061     }
01062 }
01063 
01064 static void firstpage(GVJ_t *job)
01065 {
01066     job->pagesArrayElem = job->pagesArrayFirst;
01067 }
01068 
01069 static boolean validpage(GVJ_t *job)
01070 {
01071     return ((job->pagesArrayElem.x >= 0)
01072          && (job->pagesArrayElem.x < job->pagesArraySize.x)
01073          && (job->pagesArrayElem.y >= 0)
01074          && (job->pagesArrayElem.y < job->pagesArraySize.y));
01075 }
01076 
01077 static void nextpage(GVJ_t *job)
01078 {
01079     job->pagesArrayElem = add_point(job->pagesArrayElem, job->pagesArrayMinor);
01080     if (validpage(job) == FALSE) {
01081         if (job->pagesArrayMajor.y)
01082             job->pagesArrayElem.x = job->pagesArrayFirst.x;
01083         else
01084             job->pagesArrayElem.y = job->pagesArrayFirst.y;
01085         job->pagesArrayElem = add_point(job->pagesArrayElem, job->pagesArrayMajor);
01086     }
01087 }
01088 
01089 static boolean write_edge_test(Agraph_t * g, Agedge_t * e)
01090 {
01091     Agraph_t *sg;
01092     int c;
01093 
01094     for (c = 1; c <= GD_n_cluster(g); c++) {
01095         sg = GD_clust(g)[c];
01096         if (agcontains(sg, e))
01097             return FALSE;
01098     }
01099     return TRUE;
01100 }
01101 
01102 static boolean write_node_test(Agraph_t * g, Agnode_t * n)
01103 {
01104     Agraph_t *sg;
01105     int c;
01106 
01107     for (c = 1; c <= GD_n_cluster(g); c++) {
01108         sg = GD_clust(g)[c];
01109         if (agcontains(sg, n))
01110             return FALSE;
01111     }
01112     return TRUE;
01113 }
01114 
01115 #define INITPTS 1000
01116 
01117 static pointf*
01118 copyPts (pointf* pts, int* ptsize, xdot_point* inpts, int numpts)
01119 {
01120     int i, sz = *ptsize;
01121 
01122     if (numpts > sz) {
01123         sz = MAX(2*sz, numpts);
01124         pts = RALLOC(sz, pts, pointf);
01125         *ptsize = sz;
01126     }
01127 
01128     for (i = 0; i < numpts; i++) {
01129         pts[i].x = inpts[i].x;
01130         pts[i].y = inpts[i].y;
01131     }
01132 
01133     return pts;
01134 }
01135 
01136 static void emit_xdot (GVJ_t * job, xdot* xd)
01137 {
01138     int image_warn = 1;
01139     int ptsize = INITPTS;
01140     pointf* pts = N_GNEW(INITPTS, pointf);
01141     double fontsize;
01142     char* fontname;
01143     exdot_op* op;
01144     int i;
01145     char** styles = 0;
01146 
01147     op = (exdot_op*)(xd->ops);
01148     for (i = 0; i < xd->cnt; i++) {
01149         switch (op->op.kind) {
01150         case xd_filled_ellipse :
01151         case xd_unfilled_ellipse :
01152             if (boxf_overlap(op->bb, job->clip)) {
01153                 pts[0].x = op->op.u.ellipse.x - op->op.u.ellipse.w;
01154                 pts[0].y = op->op.u.ellipse.y - op->op.u.ellipse.h;
01155                 pts[1].x = op->op.u.ellipse.w;
01156                 pts[1].y = op->op.u.ellipse.h;
01157                 gvrender_ellipse(job, pts, 2, op->op.kind == xd_filled_ellipse);
01158             }
01159             break;
01160         case xd_filled_polygon :
01161         case xd_unfilled_polygon :
01162             if (boxf_overlap(op->bb, job->clip)) {
01163                 pts = copyPts (pts, &ptsize, op->op.u.polygon.pts, op->op.u.polygon.cnt);
01164                 gvrender_polygon(job, pts, op->op.u.polygon.cnt, op->op.kind == xd_filled_polygon);
01165             }
01166             break;
01167         case xd_filled_bezier :
01168         case xd_unfilled_bezier :
01169             if (boxf_overlap(op->bb, job->clip)) {
01170                 pts = copyPts (pts, &ptsize, op->op.u.bezier.pts, op->op.u.bezier.cnt);
01171                 gvrender_beziercurve(job, pts, op->op.u.bezier.cnt, 0, 0, op->op.kind == xd_filled_bezier);
01172             }
01173             break;
01174         case xd_polyline :
01175             if (boxf_overlap(op->bb, job->clip)) {
01176                 pts = copyPts (pts, &ptsize, op->op.u.polyline.pts, op->op.u.polyline.cnt);
01177                 gvrender_polyline(job, pts, op->op.u.polyline.cnt);
01178             }
01179             break;
01180         case xd_text :
01181             if (boxf_overlap(op->bb, job->clip)) {
01182                 pts[0].x = op->op.u.text.x;
01183                 pts[0].y = op->op.u.text.y;
01184                 gvrender_textpara(job, pts[0], op->para);
01185             }
01186             break;
01187         case xd_fill_color :
01188             gvrender_set_fillcolor(job, op->op.u.color);
01189             break;
01190         case xd_pen_color :
01191             gvrender_set_pencolor(job, op->op.u.color);
01192             break;
01193         case xd_font :
01194             fontsize = op->op.u.font.size;
01195             fontname = op->op.u.font.name;
01196             break;
01197         case xd_style :
01198             styles = parse_style (op->op.u.style);
01199             gvrender_set_style (job, styles);
01200             break;
01201         case xd_image :
01202             if (image_warn) {
01203                 agerr(AGWARN, "Images unsupported in \"background\" attribute\n");
01204                 image_warn = 0;
01205             }
01206             break;
01207         }
01208         op++;
01209     }
01210     if (styles)
01211         gvrender_set_style(job, job->gvc->defaultlinestyle);
01212     free (pts);
01213 }
01214 
01215 static void emit_background(GVJ_t * job, graph_t *g)
01216 {
01217     xdot* xd;
01218     char *str;
01219     int dfltColor;
01220     
01221     /* if no bgcolor specified - first assume default of "white" */
01222     if (! ((str = agget(g, "bgcolor")) && str[0])) {
01223         str = "white";
01224         dfltColor = 1;
01225     }
01226     else
01227         dfltColor = 0;
01228     
01229 
01230     /* if device has no truecolor support, change "transparent" to "white" */
01231     if (! (job->flags & GVDEVICE_DOES_TRUECOLOR) && (streq(str, "transparent"))) {
01232         str = "white";
01233         dfltColor = 1;
01234     }
01235 
01236     /* except for "transparent" on truecolor, or default "white" on (assumed) white paper, paint background */
01237     if (!(   ((job->flags & GVDEVICE_DOES_TRUECOLOR) && streq(str, "transparent"))
01238           || ((job->flags & GVRENDER_NO_WHITE_BG) && dfltColor))) {
01239         char* clrs[2];
01240 
01241         if ((findStopColor (str, clrs))) {
01242             int filled, istyle = 0;
01243             gvrender_set_fillcolor(job, clrs[0]);
01244             gvrender_set_pencolor(job, "transparent");
01245             checkClusterStyle(g, &istyle);
01246             if (clrs[1]) 
01247                 gvrender_set_gradient_vals(job,clrs[1],late_int(g,G_gradientangle,0,0));
01248             else 
01249                 gvrender_set_gradient_vals(job,DEFAULT_COLOR,late_int(g,G_gradientangle,0,0));
01250             if (istyle & RADIAL)
01251                 filled = RGRADIENT;
01252             else
01253                 filled = GRADIENT;
01254             gvrender_box(job, job->clip, filled);
01255             free (clrs[0]);
01256         }
01257         else {
01258             gvrender_set_fillcolor(job, str);
01259             gvrender_set_pencolor(job, str);
01260             gvrender_box(job, job->clip, FILL); /* filled */
01261         }
01262     }
01263 
01264     if ((xd = (xdot*)GD_drawing(g)->xdots))
01265         emit_xdot (job, xd);
01266 }
01267 
01268 static void setup_page(GVJ_t * job, graph_t * g)
01269 {
01270     point pagesArrayElem = job->pagesArrayElem, pagesArraySize = job->pagesArraySize;
01271         
01272     if (job->rotation) {
01273         pagesArrayElem = exch_xy(pagesArrayElem);
01274         pagesArraySize = exch_xy(pagesArraySize);
01275     }
01276 
01277     /* establish current box in graph units */
01278     job->pageBox.LL.x = pagesArrayElem.x * job->pageSize.x - job->pad.x;
01279     job->pageBox.LL.y = pagesArrayElem.y * job->pageSize.y - job->pad.y;
01280     job->pageBox.UR.x = job->pageBox.LL.x + job->pageSize.x;
01281     job->pageBox.UR.y = job->pageBox.LL.y + job->pageSize.y;
01282 
01283     /* maximum boundingBox in device units and page orientation */
01284     if (job->common->viewNum == 0)
01285         job->boundingBox = job->pageBoundingBox;
01286     else
01287         EXPANDBB(job->boundingBox, job->pageBoundingBox);
01288 
01289     if (job->flags & GVDEVICE_EVENTS) {
01290         job->clip.LL.x = job->focus.x - job->view.x / 2.;
01291         job->clip.LL.y = job->focus.y - job->view.y / 2.;
01292         job->clip.UR.x = job->focus.x + job->view.x / 2.;
01293         job->clip.UR.y = job->focus.y + job->view.y / 2.;
01294     }
01295     else {
01296         job->clip.LL.x = job->focus.x + job->pageSize.x * (pagesArrayElem.x - pagesArraySize.x / 2.);
01297         job->clip.LL.y = job->focus.y + job->pageSize.y * (pagesArrayElem.y - pagesArraySize.y / 2.) - 1;
01298         job->clip.UR.x = job->clip.LL.x + job->pageSize.x + 1;
01299         job->clip.UR.y = job->clip.LL.y + job->pageSize.y + 1;
01300     }
01301 
01302     /* CAUTION - job->translation was difficult to get right. */
01303     /* Test with and without assymetric margins, e.g: -Gmargin="1,0" */
01304     if (job->rotation) {
01305         job->translation.y = - job->clip.UR.y - job->canvasBox.LL.y / job->zoom;
01306         if ((job->flags & GVRENDER_Y_GOES_DOWN) || (Y_invert))
01307             job->translation.x = - job->clip.UR.x - job->canvasBox.LL.x / job->zoom;
01308         else
01309             job->translation.x = - job->clip.LL.x + job->canvasBox.LL.x / job->zoom;
01310     }
01311     else {
01312         /* pre unscale margins to keep them constant under scaling */
01313         job->translation.x = - job->clip.LL.x + job->canvasBox.LL.x / job->zoom;
01314         if ((job->flags & GVRENDER_Y_GOES_DOWN) || (Y_invert))
01315             job->translation.y = - job->clip.UR.y - job->canvasBox.LL.y / job->zoom;
01316         else
01317             job->translation.y = - job->clip.LL.y + job->canvasBox.LL.y / job->zoom;
01318     }
01319 
01320 #if 0
01321 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",
01322         job->width, job->height,
01323         job->dpi.x, job->dpi.y,
01324         job->pad.x, job->pad.y,
01325         job->focus.x, job->focus.y,
01326         job->view.x, job->view.y,
01327         job->zoom,
01328         job->pageBox.LL.x, job->pageBox.LL.y, job->pageBox.UR.x, job->pageBox.UR.y,
01329         job->pagesArraySize.x, job->pagesArraySize.y,
01330         job->pageSize.x, job->pageSize.y,
01331         job->canvasBox.LL.x, job->canvasBox.LL.y, job->canvasBox.UR.x, job->canvasBox.UR.y,
01332         job->pageOffset.x, job->pageOffset.y,
01333         job->translation.x, job->translation.y,
01334         job->clip.LL.x, job->clip.LL.y, job->clip.UR.x, job->clip.UR.y,
01335         job->margin.x, job->margin.y);
01336 #endif
01337 }
01338 
01339 static boolean node_in_layer(GVJ_t *job, graph_t * g, node_t * n)
01340 {
01341     char *pn, *pe;
01342     edge_t *e;
01343 
01344     if (job->numLayers <= 1)
01345         return TRUE;
01346     pn = late_string(n, N_layer, "");
01347     if (selectedlayer(job, pn))
01348         return TRUE;
01349     if (pn[0])
01350         return FALSE;           /* Only check edges if pn = "" */
01351     if ((e = agfstedge(g, n)) == NULL)
01352         return TRUE;
01353     for (e = agfstedge(g, n); e; e = agnxtedge(g, e, n)) {
01354         pe = late_string(e, E_layer, "");
01355         if ((pe[0] == '\0') || selectedlayer(job, pe))
01356             return TRUE;
01357     }
01358     return FALSE;
01359 }
01360 
01361 static boolean edge_in_layer(GVJ_t *job, graph_t * g, edge_t * e)
01362 {
01363     char *pe, *pn;
01364     int cnt;
01365 
01366     if (job->numLayers <= 1)
01367         return TRUE;
01368     pe = late_string(e, E_layer, "");
01369     if (selectedlayer(job, pe))
01370         return TRUE;
01371     if (pe[0])
01372         return FALSE;
01373     for (cnt = 0; cnt < 2; cnt++) {
01374         pn = late_string(cnt < 1 ? agtail(e) : aghead(e), N_layer, "");
01375         if ((pn[0] == '\0') || selectedlayer(job, pn))
01376             return TRUE;
01377     }
01378     return FALSE;
01379 }
01380 
01381 static boolean clust_in_layer(GVJ_t *job, graph_t * sg)
01382 {
01383     char *pg;
01384     node_t *n;
01385 
01386     if (job->numLayers <= 1)
01387         return TRUE;
01388 #ifndef WITH_CGRAPH
01389     pg = late_string(sg, agfindattr(sg, "layer"), "");
01390 #else
01391     pg = late_string(sg, agattr(sg, AGRAPH, "layer", 0), "");
01392 #endif
01393     if (selectedlayer(job, pg))
01394         return TRUE;
01395     if (pg[0])
01396         return FALSE;
01397     for (n = agfstnode(sg); n; n = agnxtnode(sg, n))
01398         if (node_in_layer(job, sg, n))
01399             return TRUE;
01400     return FALSE;
01401 }
01402 
01403 static boolean node_in_box(node_t *n, boxf b)
01404 {
01405     return boxf_overlap(ND_bb(n), b);
01406 }
01407 
01408 static void emit_begin_node(GVJ_t * job, node_t * n)
01409 {
01410     obj_state_t *obj;
01411     int flags = job->flags;
01412     int sides, peripheries, i, j, filled = 0, rect = 0, shape, nump = 0;
01413     polygon_t *poly = NULL;
01414     pointf *vertices, *p = NULL;
01415     pointf coord;
01416     char *s;
01417 
01418     obj = push_obj_state(job);
01419     obj->type = NODE_OBJTYPE;
01420     obj->u.n = n;
01421     obj->emit_state = EMIT_NDRAW;
01422 
01423     if (flags & GVRENDER_DOES_Z) {
01424         /* obj->z = late_double(n, N_z, 0.0, -MAXFLOAT); */
01425         if (GD_odim(agraphof(n)) >=3)
01426             obj->z = POINTS(ND_pos(n)[2]);
01427         else
01428             obj->z = 0.0;
01429     }
01430     initObjMapData (job, ND_label(n), n);
01431     if ((flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS))
01432            && (obj->url || obj->explicit_tooltip)) {
01433 
01434         /* checking shape of node */
01435         shape = shapeOf(n);
01436         /* node coordinate */
01437         coord = ND_coord(n);
01438         /* checking if filled style has been set for node */
01439         filled = ifFilled(n);
01440 
01441         if (shape == SH_POLY || shape == SH_POINT) {
01442             poly = (polygon_t *) ND_shape_info(n);
01443 
01444             /* checking if polygon is regular rectangle */
01445             if (isRect(poly) && (poly->peripheries || filled))
01446                 rect = 1;
01447         }
01448 
01449         /* When node has polygon shape and requested output supports polygons
01450          * we use a polygon to map the clickable region that is a:
01451          * circle, ellipse, polygon with n side, or point.
01452          * For regular rectangular shape we have use node's bounding box to map clickable region
01453          */
01454         if (poly && !rect && (flags & GVRENDER_DOES_MAP_POLYGON)) {
01455 
01456             if (poly->sides < 3)
01457                 sides = 1;
01458             else
01459                 sides = poly->sides;
01460 
01461             if (poly->peripheries < 2)
01462                 peripheries = 1;
01463             else
01464                 peripheries = poly->peripheries;
01465 
01466             vertices = poly->vertices;
01467 
01468             if ((s = agget(n, "samplepoints")))
01469                 nump = atoi(s);
01470             /* We want at least 4 points. For server-side maps, at most 100
01471              * points are allowed. To simplify things to fit with the 120 points
01472              * used for skewed ellipses, we set the bound at 60.
01473              */
01474             if ((nump < 4) || (nump > 60))
01475                 nump = DFLT_SAMPLE;
01476             /* use bounding box of text label or node image for mapping
01477              * when polygon has no peripheries and node is not filled
01478              */
01479             if (poly->peripheries == 0 && !filled) {
01480                 obj->url_map_shape = MAP_RECTANGLE;
01481                 nump = 2;
01482                 p = N_NEW(nump, pointf);
01483                 P2RECT(coord, p, ND_lw(n), ND_ht(n) / 2.0 );
01484             }
01485             /* circle or ellipse */
01486             else if (poly->sides < 3 && poly->skew == 0.0 && poly->distortion == 0.0) {
01487                 if (poly->regular) {
01488                     obj->url_map_shape = MAP_CIRCLE;
01489                     nump = 2;              /* center of circle and top right corner of bb */
01490                     p = N_NEW(nump, pointf);
01491                     p[0].x = coord.x;
01492                     p[0].y = coord.y;
01493                     /* even vertices contain LL corner of bb */
01494                     /* odd vertices contain UR corner of bb */
01495                     p[1].x = coord.x + vertices[2*peripheries - 1].x;
01496                     p[1].y = coord.y + vertices[2*peripheries - 1].y;
01497                 }
01498                 else { /* ellipse is treated as polygon */
01499                     obj->url_map_shape= MAP_POLYGON;
01500                     p = pEllipse((double)(vertices[2*peripheries - 1].x),
01501                                  (double)(vertices[2*peripheries - 1].y), nump);
01502                     for (i = 0; i < nump; i++) {
01503                         p[i].x += coord.x;
01504                         p[i].y += coord.y;
01505                     }
01506                 }
01507             }
01508             /* all other polygonal shape */
01509             else {
01510                 int offset = (peripheries - 1)*(poly->sides);
01511                 obj->url_map_shape = MAP_POLYGON;
01512                 /* distorted or skewed ellipses and circles are polygons with 120
01513                  * sides. For mapping we convert them into polygon with sample sides
01514                  */
01515                 if (poly->sides >= nump) {
01516                     int delta = poly->sides / nump;
01517                     p = N_NEW(nump, pointf);
01518                     for (i = 0, j = 0; j < nump; i += delta, j++) {
01519                         p[j].x = coord.x + vertices[i + offset].x;
01520                         p[j].y = coord.y + vertices[i + offset].y;
01521                     }
01522                 } else {
01523                     nump = sides;
01524                     p = N_NEW(nump, pointf);
01525                     for (i = 0; i < nump; i++) {
01526                         p[i].x = coord.x + vertices[i + offset].x;
01527                         p[i].y = coord.y + vertices[i + offset].y;
01528                     }
01529                 }
01530             }
01531         }
01532         else {
01533             /* we have to use the node's bounding box to map clickable region
01534              * when requested output format is not capable of polygons.
01535              */
01536             obj->url_map_shape = MAP_RECTANGLE;
01537             nump = 2;
01538             p = N_NEW(nump, pointf);
01539             p[0].x = coord.x - ND_lw(n);
01540             p[0].y = coord.y - (ND_ht(n) / 2);
01541             p[1].x = coord.x + ND_rw(n);
01542             p[1].y = coord.y + (ND_ht(n) / 2);
01543         }
01544         if (! (flags & GVRENDER_DOES_TRANSFORM))
01545             gvrender_ptf_A(job, p, p, nump);
01546         obj->url_map_p = p;
01547         obj->url_map_n = nump;
01548     }
01549 
01550     setColorScheme (agget (n, "colorscheme"));
01551     gvrender_begin_node(job, n);
01552 }
01553 
01554 static void emit_end_node(GVJ_t * job)
01555 {
01556     gvrender_end_node(job);
01557     pop_obj_state(job);
01558 }
01559 
01560 /* emit_node:
01561  */
01562 static void emit_node(GVJ_t * job, node_t * n)
01563 {
01564     GVC_t *gvc = job->gvc;
01565     char *s;
01566     char *style;
01567     char **styles = 0;
01568     char **sp;
01569     char *p;
01570 
01571     if (ND_shape(n)                                  /* node has a shape */
01572             && node_in_layer(job, agraphof(n), n)    /* and is in layer */
01573             && node_in_box(n, job->clip)             /* and is in page/view */
01574             && (ND_state(n) != gvc->common.viewNum)) /* and not already drawn */
01575     {
01576         ND_state(n) = gvc->common.viewNum;           /* mark node as drawn */
01577 
01578         gvrender_comment(job, agnameof(n));
01579         s = late_string(n, N_comment, "");
01580         if (s[0])
01581             gvrender_comment(job, s);
01582         
01583         style = late_string(n, N_style, "");
01584         if (style[0]) {
01585             styles = parse_style(style);
01586             sp = styles;
01587             while ((p = *sp++)) {
01588                 if (streq(p, "invis")) return;
01589             }
01590         }
01591 
01592         emit_begin_node(job, n);
01593         ND_shape(n)->fns->codefn(job, n);
01594         if (ND_xlabel(n) && ND_xlabel(n)->set)
01595             emit_label(job, EMIT_NLABEL, ND_xlabel(n));
01596         emit_end_node(job);
01597     }
01598 }
01599 
01600 /* calculate an offset vector, length d, perpendicular to line p,q */
01601 static pointf computeoffset_p(pointf p, pointf q, double d)
01602 {
01603     pointf res;
01604     double x = p.x - q.x, y = p.y - q.y;
01605 
01606     /* keep d finite as line length approaches 0 */
01607     d /= sqrt(x * x + y * y + EPSILON);
01608     res.x = y * d;
01609     res.y = -x * d;
01610     return res;
01611 }
01612 
01613 /* calculate offset vector, length d, perpendicular to spline p,q,r,s at q&r */
01614 static pointf computeoffset_qr(pointf p, pointf q, pointf r, pointf s,
01615                                double d)
01616 {
01617     pointf res;
01618     double len;
01619     double x = q.x - r.x, y = q.y - r.y;
01620 
01621     len = sqrt(x * x + y * y);
01622     if (len < EPSILON) {
01623         /* control points are on top of each other
01624            use slope between endpoints instead */
01625         x = p.x - s.x, y = p.y - s.y;
01626         /* keep d finite as line length approaches 0 */
01627         len = sqrt(x * x + y * y + EPSILON);
01628     }
01629     d /= len;
01630     res.x = y * d;
01631     res.y = -x * d;
01632     return res;
01633 }
01634 
01635 static void emit_attachment(GVJ_t * job, textlabel_t * lp, splines * spl)
01636 {
01637     pointf sz, AF[3];
01638     unsigned char *s;
01639 
01640     for (s = (unsigned char *) (lp->text); *s; s++) {
01641         if (isspace(*s) == FALSE)
01642             break;
01643     }
01644     if (*s == 0)
01645         return;
01646 
01647     sz = lp->dimen;
01648     AF[0] = pointfof(lp->pos.x + sz.x / 2., lp->pos.y - sz.y / 2.);
01649     AF[1] = pointfof(AF[0].x - sz.x, AF[0].y);
01650     AF[2] = dotneato_closest(spl, lp->pos);
01651     /* Don't use edge style to draw attachment */
01652     gvrender_set_style(job, job->gvc->defaultlinestyle);
01653     /* Use font color to draw attachment
01654        - need something unambiguous in case of multicolored parallel edges
01655        - defaults to black for html-like labels
01656      */
01657     gvrender_set_pencolor(job, lp->fontcolor);
01658     gvrender_polyline(job, AF, 3);
01659 }
01660 
01661 /* edges colors can be mutiple colors separated by ":"
01662  * so we commpute a default pencolor with the same number of colors. */
01663 static char* default_pencolor(char *pencolor, char *deflt)
01664 {
01665     static char *buf;
01666     static int bufsz;
01667     char *p;
01668     int len, ncol;
01669 
01670     ncol = 1;
01671     for (p = pencolor; *p; p++) {
01672         if (*p == ':')
01673             ncol++;
01674     }
01675     len = ncol * (strlen(deflt) + 1);
01676     if (bufsz < len) {
01677         bufsz = len + 10;
01678         buf = realloc(buf, bufsz);
01679     }
01680     strcpy(buf, deflt);
01681     while(--ncol) {
01682         strcat(buf, ":");
01683         strcat(buf, deflt);
01684     }
01685     return buf;
01686 }
01687 
01688 typedef struct {
01689     char* color;
01690     float t;
01691 } colorseg_t;
01692 typedef struct {
01693     char* base;
01694     colorseg_t* segs;
01695 } colorsegs_t;
01696  
01697 static void
01698 freeSegs (colorsegs_t* segs)
01699 {
01700     free (segs->base);
01701     free (segs->segs);
01702     free (segs);
01703 }
01704 
01705 /* getSegLen:
01706  * Find comma in s, replace with '\0'.
01707  * Convert remainder to float v and verify prev_v <= v <= 1.
01708  * Return -1 on failure
01709  */
01710 static double getSegLen (char* s, double prev_v)
01711 {
01712     char* p = strchr (s, ',');
01713     char* endp;
01714     double v;
01715 
01716     if (!p) {
01717         agerr (AGERR, "No comma in color spec \"%s\" in color attribute ", s);
01718         return -1;
01719     }
01720     *p++ = '\0';
01721     v = strtod (p, &endp);
01722     if (endp != p) {  /* scanned something */
01723         if ((prev_v <= v) && (v <= 1))
01724             return v;
01725     }
01726     return -1;
01727 }
01728 
01729 /* parseSegs:
01730  * Parse string of form color,float:color,float:...:color,float:color
01731  * where the floats are nonnegative, <= 1, and increasing.
01732  * We allow equality, discarding the later values.
01733  * Store the values in an array of colorseg_t's and return the array in psegs.
01734  *  0 => okay
01735  *  1 => error without message 
01736  *  2 => error with message 
01737  *  3 => warning message
01738  */
01739 static int
01740 parseSegs (char* clrs, int nseg, colorsegs_t** psegs)
01741 {
01742     colorsegs_t* segs = NEW(colorsegs_t);
01743     colorseg_t* s = N_NEW(nseg+1,colorseg_t);
01744     char* colors = strdup (clrs);
01745     char* color;
01746     int cnum = 0;
01747     double v, prev_v = 0;
01748     static int doWarn = 1;
01749     int rval = 0;
01750 
01751     segs->base = colors;
01752     segs->segs = s = N_NEW(nseg+1,colorseg_t);
01753     for (color = strtok(colors, ":"); color; color = strtok(0, ":")) {
01754         if (cnum == nseg-1) {
01755             if (prev_v < 1) {
01756                 char* p = strchr (color, ',');
01757                 if (p)   /* mask optional length */
01758                     *p = '\0';
01759                 s[cnum].color = color;
01760                 s[cnum++].t = 1.0;
01761             }
01762         }
01763         else if ((v = getSegLen (color, prev_v)) >= 0) {
01764             if (prev_v < v) {
01765                 s[cnum].color = color;
01766                 s[cnum++].t = (v - prev_v)/(1.0 - prev_v);
01767                 prev_v = v;
01768             }
01769             else {
01770                 nseg--;
01771                 if (doWarn) {
01772                     agerr (AGWARN, "0-length in color spec \"%s\"\n", clrs);
01773                     doWarn = 0;
01774                     rval = 3;
01775                 }
01776             }
01777         }
01778         else {
01779             if (doWarn) {
01780                 agerr (AGERR, "Illegal length value in \"%s\" color attribute ", clrs);
01781                 doWarn = 0;
01782                 rval = 2;
01783             }
01784             else rval = 1;
01785             freeSegs (segs);
01786             return rval;
01787         }
01788     }
01789     
01790     if (cnum == 0) {
01791         freeSegs (segs);
01792         segs = NULL;
01793         rval = 1;
01794     }
01795     else        
01796         *psegs = segs;
01797     return rval;
01798 }
01799 
01800 /* approxLen:
01801  */
01802 static double approxLen (pointf* pts)
01803 {
01804     double d = DIST(pts[0],pts[1]);
01805     d += DIST(pts[1],pts[2]);
01806     d += DIST(pts[2],pts[3]);
01807     return d;
01808 }
01809  
01810 /* splitBSpline:
01811  * Given B-spline bz and 0 < t < 1, split bz so that left corresponds to
01812  * the fraction t of the arc length. The new parts are store in left and right.
01813  * The caller needs to free the allocated points.
01814  *
01815  * In the current implementation, we find the Bezier that should contain t by
01816  * treating the control points as a polyline.
01817  * We then split that Bezier.
01818  */
01819 static void splitBSpline (bezier* bz, float t, bezier* left, bezier* right)
01820 {
01821     int i, j, k, cnt = (bz->size - 1)/3;
01822     double* lens;
01823     double last, len, sum;
01824     pointf* pts;
01825     float r;
01826 
01827     if (cnt == 1) {
01828         left->size = 4;
01829         left->list = N_NEW(4, pointf);
01830         right->size = 4;
01831         right->list = N_NEW(4, pointf);
01832         Bezier (bz->list, 3, t, left->list, right->list);
01833         return;
01834     }
01835     
01836     lens = N_NEW(cnt, double);
01837     sum = 0;
01838     pts = bz->list;
01839     for (i = 0; i < cnt; i++) {
01840         lens[i] = approxLen (pts);
01841         sum += lens[i];
01842         pts += 3;
01843     }
01844     len = t*sum;
01845     sum = 0;
01846     for (i = 0; i < cnt; i++) {
01847         sum += lens[i];
01848         if (sum >= len)
01849             break;
01850     }
01851 
01852     left->size = 3*(i+1) + 1;
01853     left->list = N_NEW(left->size,pointf);
01854     right->size = 3*(cnt-i) + 1;
01855     right->list = N_NEW(right->size,pointf);
01856     for (j = 0; j < left->size; j++)
01857         left->list[j] = bz->list[j];
01858     k = j - 4;
01859     for (j = 0; j < right->size; j++)
01860         right->list[j] = bz->list[k++];
01861 
01862     last = lens[i];
01863     r = (len - (sum - last))/last;
01864     Bezier (bz->list + 3*i, 3, r, left->list + 3*i, right->list);
01865 
01866     free (lens);
01867 }
01868 
01869 /* multicolor:
01870  * Draw an edge as a sequence of colors.
01871  * Not sure how to handle multiple B-splines, so do a naive
01872  * implementation.
01873  * Return non-zero if color spec is incorrect
01874  */
01875 static int multicolor (GVJ_t * job, edge_t * e, char** styles, char* colors, int num, double arrowsize, double penwidth)
01876 { 
01877     bezier bz;
01878     bezier bz0, bz_l, bz_r;
01879     int i, rv;
01880     colorsegs_t* segs;
01881     colorseg_t* s;
01882     char* endcolor;
01883 
01884     rv = parseSegs (colors, num, &segs);
01885     if (rv > 1) {
01886 #ifndef WITH_CGRAPH
01887         Agraph_t* g = e->tail->graph;
01888         agerr (AGPREV, "in edge %s%s%s\n", agnameof(e->tail), (AG_IS_DIRECTED(g)?" -> ":" -- "), agnameof(e->head));
01889 #else
01890         Agraph_t* g = agraphof(agtail(e));
01891         agerr (AGPREV, "in edge %s%s%s\n", agnameof(agtail(e)), (agisdirected(g)?" -> ":" -- "), agnameof(aghead(e)));
01892 
01893 #endif
01894         if (rv == 2)
01895             return 1;
01896     }
01897     else if (rv == 1)
01898         return 1;
01899     
01900 
01901     for (i = 0; i < ED_spl(e)->size; i++) {
01902         bz = ED_spl(e)->list[i];
01903         for (s = segs->segs; s->color; s++) {
01904             gvrender_set_pencolor(job, s->color);
01905             if (s == segs->segs) {
01906                 splitBSpline (&bz, s->t, &bz_l, &bz_r);
01907                 gvrender_beziercurve(job, bz_l.list, bz_l.size, FALSE, FALSE, FALSE);
01908                 free (bz_l.list);
01909             }
01910             else if (s->t < 1.0) {
01911                 bz0 = bz_r;
01912                 splitBSpline (&bz0, s->t, &bz_l, &bz_r);
01913                 free (bz0.list);
01914                 gvrender_beziercurve(job, bz_l.list, bz_l.size, FALSE, FALSE, FALSE);
01915                 free (bz_l.list);
01916             }
01917             else {
01918                 endcolor = s->color;
01919                 gvrender_beziercurve(job, bz_r.list, bz_r.size, FALSE, FALSE, FALSE);
01920                 free (bz_r.list);
01921             }
01922                 
01923         }
01924                 /* arrow_gen resets the job style  (How?  FIXME)
01925                  * If we have more splines to do, restore the old one.
01926                  * Use local copy of penwidth to work around reset.
01927                  */
01928         if (bz.sflag) {
01929             gvrender_set_pencolor(job, segs->segs->color);
01930             gvrender_set_fillcolor(job, segs->segs->color);
01931             arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0], arrowsize, penwidth, bz.sflag);
01932         }
01933         if (bz.eflag) {
01934             gvrender_set_pencolor(job, endcolor);
01935             gvrender_set_fillcolor(job, endcolor);
01936             arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1], arrowsize, penwidth, bz.eflag);
01937         }
01938         if ((ED_spl(e)->size>1) && (bz.sflag||bz.eflag) && styles) 
01939             gvrender_set_style(job, styles);
01940     }
01941     free (segs);
01942     return 0;
01943 }
01944 
01945 static void free_stroke (stroke_t* sp)
01946 {
01947     if (sp) {
01948         free (sp->vertices);
01949         free (sp);
01950     }
01951 }
01952 
01953 typedef double (*radfunc_t)(double,double,double);
01954 
01955 static double forfunc (double curlen, double totallen, double initwid)
01956 {
01957     return ((1 - (curlen/totallen))*initwid/2.0);
01958 }
01959 
01960 static double revfunc (double curlen, double totallen, double initwid)
01961 {
01962     return (((curlen/totallen))*initwid/2.0);
01963 }
01964 
01965 static double nonefunc (double curlen, double totallen, double initwid)
01966 {
01967     return (initwid/2.0);
01968 }
01969 
01970 static double bothfunc (double curlen, double totallen, double initwid)
01971 {
01972     double fr = curlen/totallen;
01973     if (fr <= 0.5) return (fr*initwid);
01974     else return ((1-fr)*initwid);
01975 }
01976 
01977 static radfunc_t 
01978 taperfun (edge_t* e)
01979 {
01980     char* attr;
01981 #ifdef WITH_CGRAPH
01982     if (E_dir && ((attr = agxget(e, E_dir)))[0]) {
01983 #else
01984     if (E_dir && ((attr = agxget(e, E_dir->index)))[0]) {
01985 #endif
01986         if (streq(attr, "forward")) return forfunc;
01987         if (streq(attr, "back")) return revfunc;
01988         if (streq(attr, "both")) return bothfunc;
01989         if (streq(attr, "none")) return nonefunc;
01990     }
01991     return (agisdirected(agraphof(aghead(e))) ? forfunc : nonefunc);
01992 }
01993 
01994 static void emit_edge_graphics(GVJ_t * job, edge_t * e, char** styles)
01995 {
01996     int i, j, cnum, numc = 0, numcomma = 0;
01997     char *color, *pencolor, *fillcolor;
01998     char *headcolor, *tailcolor, *lastcolor;
01999     char *colors = NULL;
02000     bezier bz;
02001     splines offspl, tmpspl;
02002     pointf pf0, pf1, pf2 = { 0, 0 }, pf3, *offlist, *tmplist;
02003     double arrowsize, numc2, penwidth=job->obj->penwidth;
02004     char* p;
02005     boolean tapered = 0;
02006 
02007 #define SEP 2.0
02008 
02009     setColorScheme (agget (e, "colorscheme"));
02010     if (ED_spl(e)) {
02011         arrowsize = late_double(e, E_arrowsz, 1.0, 0.0);
02012         color = late_string(e, E_color, "");
02013 
02014         if (styles) {
02015             char** sp = styles;
02016             while ((p = *sp++)) {
02017                 if (streq(p, "tapered")) {
02018                     tapered = 1;
02019                     break;
02020                 }
02021             }
02022         }
02023 
02024         /* need to know how many colors separated by ':' */
02025         for (p = color; *p; p++) {
02026             if (*p == ':')
02027                 numc++;
02028             else if (*p == ',')
02029                 numcomma++;
02030         }
02031 
02032         if (numcomma && numc) {
02033             if (multicolor (job, e, styles, color, numc+1, arrowsize, penwidth)) {
02034                 color = DEFAULT_COLOR;
02035             }
02036             else
02037                 return;
02038         }
02039 
02040         fillcolor = pencolor = color;
02041         if (ED_gui_state(e) & GUI_STATE_ACTIVE) {
02042             pencolor = late_nnstring(e, E_activepencolor,
02043                         default_pencolor(pencolor, DEFAULT_ACTIVEPENCOLOR));
02044             fillcolor = late_nnstring(e, E_activefillcolor, DEFAULT_ACTIVEFILLCOLOR);
02045         }
02046         else if (ED_gui_state(e) & GUI_STATE_SELECTED) {
02047             pencolor = late_nnstring(e, E_selectedpencolor,
02048                         default_pencolor(pencolor, DEFAULT_SELECTEDPENCOLOR));
02049             fillcolor = late_nnstring(e, E_selectedfillcolor, DEFAULT_SELECTEDFILLCOLOR);
02050         }
02051         else if (ED_gui_state(e) & GUI_STATE_DELETED) {
02052             pencolor = late_nnstring(e, E_deletedpencolor,
02053                         default_pencolor(pencolor, DEFAULT_DELETEDPENCOLOR));
02054             fillcolor = late_nnstring(e, E_deletedfillcolor, DEFAULT_DELETEDFILLCOLOR);
02055         }
02056         else if (ED_gui_state(e) & GUI_STATE_VISITED) {
02057             pencolor = late_nnstring(e, E_visitedpencolor,
02058                         default_pencolor(pencolor, DEFAULT_VISITEDPENCOLOR));
02059             fillcolor = late_nnstring(e, E_visitedfillcolor, DEFAULT_VISITEDFILLCOLOR);
02060         }
02061         else
02062             fillcolor = late_nnstring(e, E_fillcolor, color);
02063         if (pencolor != color)
02064             gvrender_set_pencolor(job, pencolor);
02065         if (fillcolor != color)
02066             gvrender_set_fillcolor(job, fillcolor);
02067         color = pencolor;
02068 
02069         if (tapered) {
02070             stroke_t* stp;
02071             if (*color == '\0') color = DEFAULT_COLOR;
02072             gvrender_set_pencolor(job, "transparent");
02073             gvrender_set_fillcolor(job, color);
02074             bz = ED_spl(e)->list[0];
02075             stp = taper (&bz, taperfun (e), penwidth, 0, 0);
02076             gvrender_polygon(job, stp->vertices, stp->nvertices, TRUE);
02077             free_stroke (stp);
02078             gvrender_set_pencolor(job, color);
02079             if (fillcolor != color)
02080                 gvrender_set_fillcolor(job, fillcolor);
02081             if (bz.sflag) {
02082                 arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0], arrowsize, penwidth, bz.sflag);
02083             }
02084             if (bz.eflag) {
02085                 arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1],
02086                     arrowsize, penwidth, bz.eflag);
02087             }
02088         }
02089         /* if more than one color - then generate parallel beziers, one per color */
02090         else if (numc) {
02091             /* calculate and save offset vector spline and initialize first offset spline */
02092             tmpspl.size = offspl.size = ED_spl(e)->size;
02093             offspl.list = malloc(sizeof(bezier) * offspl.size);
02094             tmpspl.list = malloc(sizeof(bezier) * tmpspl.size);
02095             numc2 = (2 + numc) / 2.0;
02096             for (i = 0; i < offspl.size; i++) {
02097                 bz = ED_spl(e)->list[i];
02098                 tmpspl.list[i].size = offspl.list[i].size = bz.size;
02099                 offlist = offspl.list[i].list = malloc(sizeof(pointf) * bz.size);
02100                 tmplist = tmpspl.list[i].list = malloc(sizeof(pointf) * bz.size);
02101                 pf3 = bz.list[0];
02102                 for (j = 0; j < bz.size - 1; j += 3) {
02103                     pf0 = pf3;
02104                     pf1 = bz.list[j + 1];
02105                     /* calculate perpendicular vectors for each bezier point */
02106                     if (j == 0) /* first segment, no previous pf2 */
02107                         offlist[j] = computeoffset_p(pf0, pf1, SEP);
02108                     else        /* i.e. pf2 is available from previous segment */
02109                         offlist[j] = computeoffset_p(pf2, pf1, SEP);
02110                     pf2 = bz.list[j + 2];
02111                     pf3 = bz.list[j + 3];
02112                     offlist[j + 1] = offlist[j + 2] =
02113                         computeoffset_qr(pf0, pf1, pf2, pf3, SEP);
02114                     /* initialize tmpspl to outermost position */
02115                     tmplist[j].x = pf0.x - numc2 * offlist[j].x;
02116                     tmplist[j].y = pf0.y - numc2 * offlist[j].y;
02117                     tmplist[j + 1].x = pf1.x - numc2 * offlist[j + 1].x;
02118                     tmplist[j + 1].y = pf1.y - numc2 * offlist[j + 1].y;
02119                     tmplist[j + 2].x = pf2.x - numc2 * offlist[j + 2].x;
02120                     tmplist[j + 2].y = pf2.y - numc2 * offlist[j + 2].y;
02121                 }
02122                 /* last segment, no next pf1 */
02123                 offlist[j] = computeoffset_p(pf2, pf3, SEP);
02124                 tmplist[j].x = pf3.x - numc2 * offlist[j].x;
02125                 tmplist[j].y = pf3.y - numc2 * offlist[j].y;
02126             }
02127             lastcolor = headcolor = tailcolor = color;
02128             colors = strdup(color);
02129             for (cnum = 0, color = strtok(colors, ":"); color;
02130                 cnum++, color = strtok(0, ":")) {
02131                 if (!color[0])
02132                     color = DEFAULT_COLOR;
02133                 if (color != lastcolor) {
02134                     if (! (ED_gui_state(e) & (GUI_STATE_ACTIVE | GUI_STATE_SELECTED))) {
02135                         gvrender_set_pencolor(job, color);
02136                         gvrender_set_fillcolor(job, color);
02137                     }
02138                     lastcolor = color;
02139                 }
02140                 if (cnum == 0)
02141                     headcolor = tailcolor = color;
02142                 if (cnum == 1)
02143                     tailcolor = color;
02144                 for (i = 0; i < tmpspl.size; i++) {
02145                     tmplist = tmpspl.list[i].list;
02146                     offlist = offspl.list[i].list;
02147                     for (j = 0; j < tmpspl.list[i].size; j++) {
02148                         tmplist[j].x += offlist[j].x;
02149                         tmplist[j].y += offlist[j].y;
02150                     }
02151                     gvrender_beziercurve(job, tmplist, tmpspl.list[i].size,
02152                                          FALSE, FALSE, FALSE);
02153                 }
02154             }
02155             if (bz.sflag) {
02156                 if (color != tailcolor) {
02157                     color = tailcolor;
02158                     if (! (ED_gui_state(e) & (GUI_STATE_ACTIVE | GUI_STATE_SELECTED))) {
02159                         gvrender_set_pencolor(job, color);
02160                         gvrender_set_fillcolor(job, color);
02161                     }
02162                 }
02163                 arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0],
02164                         arrowsize, penwidth, bz.sflag);
02165             }
02166             if (bz.eflag) {
02167                 if (color != headcolor) {
02168                     color = headcolor;
02169                     if (! (ED_gui_state(e) & (GUI_STATE_ACTIVE | GUI_STATE_SELECTED))) {
02170                         gvrender_set_pencolor(job, color);
02171                         gvrender_set_fillcolor(job, color);
02172                     }
02173                 }
02174                 arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1],
02175                         arrowsize, penwidth, bz.eflag);
02176             }
02177             free(colors);
02178             for (i = 0; i < offspl.size; i++) {
02179                 free(offspl.list[i].list);
02180                 free(tmpspl.list[i].list);
02181             }
02182             free(offspl.list);
02183             free(tmpspl.list);
02184         } else {
02185             if (! (ED_gui_state(e) & (GUI_STATE_ACTIVE | GUI_STATE_SELECTED))) {
02186                 if (color[0]) {
02187                     gvrender_set_pencolor(job, color);
02188                     gvrender_set_fillcolor(job, fillcolor);
02189                 } else {
02190                     gvrender_set_pencolor(job, DEFAULT_COLOR);
02191                     if (fillcolor[0])
02192                         gvrender_set_fillcolor(job, fillcolor);
02193                     else
02194                         gvrender_set_fillcolor(job, DEFAULT_COLOR);
02195                 }
02196             }
02197             for (i = 0; i < ED_spl(e)->size; i++) {
02198                 bz = ED_spl(e)->list[i];
02199                 if (job->flags & GVRENDER_DOES_ARROWS) {
02200                     gvrender_beziercurve(job, bz.list, bz.size, bz.sflag,
02201                                          bz.eflag, FALSE);
02202                 } else {
02203                     gvrender_beziercurve(job, bz.list, bz.size, FALSE,
02204                                          FALSE, FALSE);
02205                     /* arrow_gen resets the job style  (How?  FIXME)
02206                      * If we have more splines to do, restore the old one.
02207                      * Use local copy of penwidth to work around reset.
02208                      */
02209                     if (bz.sflag) {
02210                         arrow_gen(job, EMIT_TDRAW, bz.sp, bz.list[0],
02211                                 arrowsize, penwidth, bz.sflag);
02212                     }
02213                     if (bz.eflag) {
02214                         arrow_gen(job, EMIT_HDRAW, bz.ep, bz.list[bz.size - 1],
02215                                 arrowsize, penwidth, bz.eflag);
02216                     }
02217                     if ((ED_spl(e)->size>1) && (bz.sflag||bz.eflag) && styles) 
02218                         gvrender_set_style(job, styles);
02219                 }
02220             }
02221         }
02222     }
02223 }
02224 
02225 static boolean edge_in_box(edge_t *e, boxf b)
02226 {
02227     splines *spl;
02228     textlabel_t *lp;
02229 
02230     spl = ED_spl(e);
02231     if (spl && boxf_overlap(spl->bb, b))
02232         return TRUE;
02233 
02234     lp = ED_label(e);
02235     if (lp && overlap_label(lp, b))
02236         return TRUE;
02237 
02238     lp = ED_xlabel(e);
02239     if (lp && lp->set && overlap_label(lp, b))
02240         return TRUE;
02241 
02242     return FALSE;
02243 }
02244 
02245 static void emit_begin_edge(GVJ_t * job, edge_t * e, char** styles)
02246 {
02247     obj_state_t *obj;
02248     int flags = job->flags;
02249     char *s;
02250     textlabel_t *lab = NULL, *tlab = NULL, *hlab = NULL;
02251     pointf *pbs = NULL;
02252     int i, nump, *pbs_n = NULL, pbs_poly_n = 0;
02253     char* dflt_url = NULL;
02254     char* dflt_target = NULL;
02255     double penwidth;
02256 
02257     obj = push_obj_state(job);
02258     obj->type = EDGE_OBJTYPE;
02259     obj->u.e = e;
02260     obj->emit_state = EMIT_EDRAW;
02261 
02262     /* We handle the edge style and penwidth here because the width
02263      * is needed below for calculating polygonal image maps
02264      */
02265     if (styles && ED_spl(e)) gvrender_set_style(job, styles);
02266 
02267 #ifndef WITH_CGRAPH
02268     if (E_penwidth && ((s=agxget(e,E_penwidth->index)) && s[0])) {
02269 #else
02270     if (E_penwidth && ((s=agxget(e,E_penwidth)) && s[0])) {
02271 #endif
02272         penwidth = late_double(e, E_penwidth, 1.0, 0.0);
02273         gvrender_set_penwidth(job, penwidth);
02274     }
02275 
02276     if (flags & GVRENDER_DOES_Z) {
02277         /* obj->tail_z = late_double(agtail(e), N_z, 0.0, -1000.0); */
02278         /* obj->head_z = late_double(aghead(e), N_z, 0.0, -MAXFLOAT); */
02279         if (GD_odim(agraphof(agtail(e))) >=3) {
02280             obj->tail_z = POINTS(ND_pos(agtail(e))[2]);
02281             obj->head_z = POINTS(ND_pos(aghead(e))[2]);
02282         } else {
02283             obj->tail_z = obj->head_z = 0.0;
02284         }
02285     }
02286 
02287     if (flags & GVRENDER_DOES_LABELS) {
02288         if ((lab = ED_label(e)))
02289             obj->label = lab->text;
02290         obj->taillabel = obj->headlabel = obj->xlabel = obj->label;
02291         if ((tlab = ED_xlabel(e)))
02292             obj->xlabel = tlab->text;
02293         if ((tlab = ED_tail_label(e)))
02294             obj->taillabel = tlab->text;
02295         if ((hlab = ED_head_label(e)))
02296             obj->headlabel = hlab->text;
02297     }
02298 
02299     if (flags & GVRENDER_DOES_MAPS) {
02300         agxbuf xb;
02301         unsigned char xbuf[SMALLBUF];
02302 
02303         agxbinit(&xb, SMALLBUF, xbuf);
02304         s = getObjId (job, e, &xb);
02305         obj->id = strdup_and_subst_obj(s, (void*)e);
02306         agxbfree(&xb);
02307 
02308         if (((s = agget(e, "href")) && s[0]) || ((s = agget(e, "URL")) && s[0]))
02309             dflt_url = strdup_and_subst_obj(s, (void*)e);
02310         if (((s = agget(e, "edgehref")) && s[0]) || ((s = agget(e, "edgeURL")) && s[0]))
02311             obj->url = strdup_and_subst_obj(s, (void*)e);
02312         else if (dflt_url)
02313             obj->url = strdup(dflt_url);
02314         if (((s = agget(e, "labelhref")) && s[0]) || ((s = agget(e, "labelURL")) && s[0]))
02315             obj->labelurl = strdup_and_subst_obj(s, (void*)e);
02316         else if (dflt_url)
02317             obj->labelurl = strdup(dflt_url);
02318         if (((s = agget(e, "tailhref")) && s[0]) || ((s = agget(e, "tailURL")) && s[0])) {
02319             obj->tailurl = strdup_and_subst_obj(s, (void*)e);
02320             obj->explicit_tailurl = TRUE;
02321         }
02322         else if (dflt_url)
02323             obj->tailurl = strdup(dflt_url);
02324         if (((s = agget(e, "headhref")) && s[0]) || ((s = agget(e, "headURL")) && s[0])) {
02325             obj->headurl = strdup_and_subst_obj(s, (void*)e);
02326             obj->explicit_headurl = TRUE;
02327         }
02328         else if (dflt_url)
02329             obj->headurl = strdup(dflt_url);
02330     } 
02331 
02332     if (flags & GVRENDER_DOES_TARGETS) {
02333         if ((s = agget(e, "target")) && s[0])
02334             dflt_target = strdup_and_subst_obj(s, (void*)e);
02335         if ((s = agget(e, "edgetarget")) && s[0]) {
02336             obj->explicit_edgetarget = TRUE;
02337             obj->target = strdup_and_subst_obj(s, (void*)e);
02338         }
02339         else if (dflt_target)
02340             obj->target = strdup(dflt_target);
02341         if ((s = agget(e, "labeltarget")) && s[0])
02342             obj->labeltarget = strdup_and_subst_obj(s, (void*)e);
02343         else if (dflt_target)
02344             obj->labeltarget = strdup(dflt_target);
02345         if ((s = agget(e, "tailtarget")) && s[0]) {
02346             obj->tailtarget = strdup_and_subst_obj(s, (void*)e);
02347             obj->explicit_tailtarget = TRUE;
02348         }
02349         else if (dflt_target)
02350             obj->tailtarget = strdup(dflt_target);
02351         if ((s = agget(e, "headtarget")) && s[0]) {
02352             obj->explicit_headtarget = TRUE;
02353             obj->headtarget = strdup_and_subst_obj(s, (void*)e);
02354         }
02355         else if (dflt_target)
02356             obj->headtarget = strdup(dflt_target);
02357     } 
02358 
02359     if (flags & GVRENDER_DOES_TOOLTIPS) {
02360         if (((s = agget(e, "tooltip")) && s[0]) ||
02361             ((s = agget(e, "edgetooltip")) && s[0])) {
02362             obj->tooltip = strdup_and_subst_obj(s, (void*)e);
02363             obj->explicit_tooltip = TRUE;
02364         }
02365         else if (obj->label)
02366             obj->tooltip = strdup(obj->label);
02367 
02368         if ((s = agget(e, "labeltooltip")) && s[0]) {
02369             obj->labeltooltip = strdup_and_subst_obj(s, (void*)e);
02370             obj->explicit_labeltooltip = TRUE;
02371         }
02372         else if (obj->label)
02373             obj->labeltooltip = strdup(obj->label);
02374 
02375         if ((s = agget(e, "tailtooltip")) && s[0]) {
02376             obj->tailtooltip = strdup_and_subst_obj(s, (void*)e);
02377             obj->explicit_tailtooltip = TRUE;
02378         }
02379         else if (obj->taillabel)
02380             obj->tailtooltip = strdup(obj->taillabel);
02381 
02382         if ((s = agget(e, "headtooltip")) && s[0]) {
02383             obj->headtooltip = strdup_and_subst_obj(s, (void*)e);
02384             obj->explicit_headtooltip = TRUE;
02385         }
02386         else if (obj->headlabel)
02387             obj->headtooltip = strdup(obj->headlabel);
02388     } 
02389     
02390     free (dflt_url);
02391     free (dflt_target);
02392 
02393     if (flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS)) {
02394         if (ED_spl(e) && (obj->url || obj->tooltip) && (flags & GVRENDER_DOES_MAP_POLYGON)) {
02395             int ns;
02396             splines *spl;
02397             double w2 = MAX(job->obj->penwidth/2.0,2.0);
02398 
02399             spl = ED_spl(e);
02400             ns = spl->size; /* number of splines */
02401             for (i = 0; i < ns; i++)
02402                 map_output_bspline (&pbs, &pbs_n, &pbs_poly_n, spl->list+i, w2);
02403             obj->url_bsplinemap_poly_n = pbs_poly_n;
02404             obj->url_bsplinemap_n = pbs_n;
02405             if (! (flags & GVRENDER_DOES_TRANSFORM)) {
02406                 for ( nump = 0, i = 0; i < pbs_poly_n; i++)
02407                     nump += pbs_n[i];
02408                 gvrender_ptf_A(job, pbs, pbs, nump);            
02409             }
02410             obj->url_bsplinemap_p = pbs;
02411             obj->url_map_shape = MAP_POLYGON;
02412             obj->url_map_p = pbs;
02413             obj->url_map_n = pbs_n[0];
02414         }
02415     }
02416 
02417     gvrender_begin_edge(job, e);
02418     if (obj->url || obj->explicit_tooltip)
02419         gvrender_begin_anchor(job,
02420                 obj->url, obj->tooltip, obj->target, obj->id);
02421 }
02422 
02423 static void
02424 emit_edge_label(GVJ_t* job, textlabel_t* lbl, emit_state_t lkind, int explicit,
02425     char* url, char* tooltip, char* target, char *id, splines* spl)
02426 {
02427     int flags = job->flags;
02428     emit_state_t old_emit_state;
02429     char* newid;
02430     char* type;
02431 
02432     if ((lbl == NULL) || !(lbl->set)) return;
02433     if (id) { /* non-NULL if needed */
02434         newid = N_NEW(strlen(id) + sizeof("-headlabel"),char);
02435         switch (lkind) {
02436         case EMIT_ELABEL :
02437             type = "label";
02438             break;
02439         case EMIT_HLABEL :
02440             type = "headlabel";
02441             break;
02442         case EMIT_TLABEL :
02443             type = "taillabel";
02444             break;
02445         default :
02446             assert (0);
02447             break;
02448         }
02449         sprintf (newid, "%s-%s", id, type);
02450     }
02451     else
02452         newid = NULL;
02453     old_emit_state = job->obj->emit_state;
02454     job->obj->emit_state = lkind;
02455     if ((url || explicit) && !(flags & EMIT_CLUSTERS_LAST)) {
02456         map_label(job, lbl);
02457         gvrender_begin_anchor(job, url, tooltip, target, newid);
02458     }
02459     emit_label(job, lkind, lbl);
02460     if (spl) emit_attachment(job, lbl, spl);
02461     if (url || explicit) {
02462         if (flags & EMIT_CLUSTERS_LAST) {
02463             map_label(job, lbl);
02464             gvrender_begin_anchor(job, url, tooltip, target, newid);
02465         }
02466         gvrender_end_anchor(job);
02467     }
02468     if (newid) free (newid);
02469     job->obj->emit_state = old_emit_state;
02470 }
02471 
02472 /* nodeIntersect:
02473  * Common logic for setting hot spots at the beginning and end of 
02474  * an edge.
02475  * If we are given a value (url, tooltip, target) explicitly set for
02476  * the head/tail, we use that. 
02477  * Otherwise, if we are given a value explicitly set for the edge,
02478  * we use that.
02479  * Otherwise, we use whatever the argument value is.
02480  * We also note whether or not the tooltip was explicitly set.
02481  * If the url is non-NULL or the tooltip was explicit, we set
02482  * a hot spot around point p.
02483  */
02484 static void nodeIntersect (GVJ_t * job, pointf p, 
02485     boolean explicit_iurl, char* iurl,
02486     boolean explicit_itooltip, char* itooltip,
02487     boolean explicit_itarget, char* itarget)
02488 {
02489     obj_state_t *obj = job->obj;
02490     char* url;
02491     char* tooltip;
02492     char* target;
02493     boolean explicit;
02494 
02495     if (explicit_iurl) url = iurl;
02496     else url = obj->url;
02497     if (explicit_itooltip) {
02498         tooltip = itooltip;
02499         explicit = TRUE;
02500     }
02501     else if (obj->explicit_tooltip) {
02502         tooltip = obj->tooltip;
02503         explicit = TRUE;
02504     }
02505     else {
02506         explicit = FALSE;
02507         tooltip = itooltip;
02508     }
02509     if (explicit_itarget)
02510         target = itarget;
02511     else if (obj->explicit_edgetarget)
02512         target = obj->target;
02513     else
02514         target = itarget;
02515 
02516     if (url || explicit) {
02517         map_point(job, p);
02518 #if 0
02519 /* this doesn't work because there is nothing contained in the anchor */
02520         gvrender_begin_anchor(job, url, tooltip, target, obj->id);
02521         gvrender_end_anchor(job);
02522 #endif
02523     }
02524 }
02525 
02526 static void emit_end_edge(GVJ_t * job)
02527 {
02528     obj_state_t *obj = job->obj;
02529     edge_t *e = obj->u.e;
02530     int i, nump;
02531 
02532     if (obj->url || obj->explicit_tooltip) {
02533         gvrender_end_anchor(job);
02534         if (obj->url_bsplinemap_poly_n) {
02535             for ( nump = obj->url_bsplinemap_n[0], i = 1; i < obj->url_bsplinemap_poly_n; i++) {
02536                 /* additional polygon maps around remaining bezier pieces */
02537                 obj->url_map_n = obj->url_bsplinemap_n[i];
02538                 obj->url_map_p = &(obj->url_bsplinemap_p[nump]);
02539                 gvrender_begin_anchor(job,
02540                         obj->url, obj->tooltip, obj->target, obj->id);
02541                 gvrender_end_anchor(job);
02542                 nump += obj->url_bsplinemap_n[i];
02543             }
02544         }
02545     }
02546     obj->url_map_n = 0;       /* null out copy so that it doesn't get freed twice */
02547     obj->url_map_p = NULL;
02548 
02549     if (ED_spl(e)) {
02550         pointf p;
02551         bezier bz;
02552 
02553         /* process intersection with tail node */
02554         bz = ED_spl(e)->list[0];
02555         if (bz.sflag) /* Arrow at start of splines */
02556             p = bz.sp;
02557         else /* No arrow at start of splines */
02558             p = bz.list[0];
02559         nodeIntersect (job, p, obj->explicit_tailurl, obj->tailurl,
02560             obj->explicit_tailtooltip, obj->tailtooltip, 
02561             obj->explicit_tailtarget, obj->tailtarget); 
02562         
02563         /* process intersection with head node */
02564         bz = ED_spl(e)->list[ED_spl(e)->size - 1];
02565         if (bz.eflag) /* Arrow at end of splines */
02566             p = bz.ep;
02567         else /* No arrow at end of splines */
02568             p = bz.list[bz.size - 1];
02569         nodeIntersect (job, p, obj->explicit_headurl, obj->headurl,
02570             obj->explicit_headtooltip, obj->headtooltip, 
02571             obj->explicit_headtarget, obj->headtarget); 
02572     }
02573 
02574     emit_edge_label(job, ED_label(e), EMIT_ELABEL,
02575         obj->explicit_labeltooltip, 
02576         obj->labelurl, obj->labeltooltip, obj->labeltarget, obj->id, 
02577         ((mapbool(late_string(e, E_decorate, "false")) && ED_spl(e)) ? ED_spl(e) : 0));
02578     emit_edge_label(job, ED_xlabel(e), EMIT_ELABEL,
02579         obj->explicit_labeltooltip, 
02580         obj->labelurl, obj->labeltooltip, obj->labeltarget, obj->id, 
02581         ((mapbool(late_string(e, E_decorate, "false")) && ED_spl(e)) ? ED_spl(e) : 0));
02582     emit_edge_label(job, ED_head_label(e), EMIT_HLABEL, 
02583         obj->explicit_headtooltip,
02584         obj->headurl, obj->headtooltip, obj->headtarget, obj->id,
02585         0);
02586     emit_edge_label(job, ED_tail_label(e), EMIT_TLABEL, 
02587         obj->explicit_tailtooltip,
02588         obj->tailurl, obj->tailtooltip, obj->tailtarget, obj->id,
02589         0);
02590 
02591     gvrender_end_edge(job);
02592     pop_obj_state(job);
02593 }
02594 
02595 static void emit_edge(GVJ_t * job, edge_t * e)
02596 {
02597     char *s;
02598     char *style;
02599     char **styles = 0;
02600     char **sp;
02601     char *p;
02602 
02603     if (edge_in_box(e, job->clip) && edge_in_layer(job, agraphof(aghead(e)), e) ) {
02604 
02605         s = malloc(strlen(agnameof(agtail(e))) + 2 + strlen(agnameof(aghead(e))) + 1);
02606         strcpy(s,agnameof(agtail(e)));
02607         if (agisdirected(agraphof(aghead(e))))
02608 
02609             strcat(s,"->");
02610         else
02611             strcat(s,"--");
02612         strcat(s,agnameof(aghead(e)));
02613         gvrender_comment(job, s);
02614         free(s);
02615 
02616         s = late_string(e, E_comment, "");
02617         if (s[0])
02618             gvrender_comment(job, s);
02619 
02620         style = late_string(e, E_style, "");
02621         /* We shortcircuit drawing an invisible edge because the arrowhead
02622          * code resets the style to solid, and most of the code generators
02623          * (except PostScript) won't honor a previous style of invis.
02624          */
02625         if (style[0]) {
02626             styles = parse_style(style);
02627             sp = styles;
02628             while ((p = *sp++)) {
02629                 if (streq(p, "invis")) return;
02630             }
02631         }
02632 
02633         emit_begin_edge(job, e, styles);
02634         emit_edge_graphics (job, e, styles);
02635         emit_end_edge(job);
02636     }
02637 }
02638 
02639 static char adjust[] = {'l', 'n', 'r'};
02640 
02641 static void
02642 expandBB (boxf* bb, pointf p)
02643 {
02644     if (p.x > bb->UR.x)
02645         bb->UR.x = p.x;
02646     if (p.x < bb->LL.x)
02647         bb->LL.x = p.x;
02648     if (p.y > bb->UR.y)
02649         bb->UR.y = p.y;
02650     if (p.y < bb->LL.y)
02651         bb->LL.y = p.y;
02652 }
02653 
02654 static boxf
02655 ptsBB (xdot_point* inpts, int numpts, boxf* bb)
02656 {
02657     boxf opbb;
02658     int i;
02659 
02660     opbb.LL.x = opbb.UR.x = inpts->x;
02661     opbb.LL.y = opbb.UR.y = inpts->y;
02662     for (i = 1; i < numpts; i++) {
02663         inpts++;
02664         if (inpts->x < opbb.LL.x)
02665             opbb.LL.x = inpts->x;
02666         else if (inpts->x > opbb.UR.x)
02667             opbb.UR.x = inpts->x;
02668         if (inpts->y < opbb.LL.y)
02669             opbb.LL.y = inpts->y;
02670         else if (inpts->y > opbb.UR.y)
02671             opbb.UR.y = inpts->y;
02672 
02673     }
02674     expandBB (bb, opbb.LL);
02675     expandBB (bb, opbb.UR);
02676     return opbb;
02677 }
02678 
02679 static boxf
02680 textBB (double x, double y, textpara_t* para)
02681 {
02682     boxf bb;
02683     double wd = para->width;
02684     double ht = para->height;
02685 
02686     switch (para->just) {
02687     case 'l':
02688         bb.LL.x = x;
02689         bb.UR.x = bb.LL.x + wd;
02690         break; 
02691     case 'n':
02692         bb.LL.x = x - wd/2.0; 
02693         bb.UR.x = x + wd/2.0; 
02694         break; 
02695     case 'r':
02696         bb.UR.x = x; 
02697         bb.LL.x = bb.UR.x - wd;
02698         break; 
02699     }
02700     bb.UR.y = y + para->yoffset_layout;
02701     bb.LL.y = bb.UR.y - ht;
02702     return bb;
02703 }
02704 
02705 static void
02706 freePara (exdot_op* op)
02707 {
02708     if (op->op.kind == xd_text)
02709         free_textpara (op->para, 1);
02710 }
02711 
02712 boxf xdotBB (Agraph_t* g)
02713 {
02714     exdot_op* op;
02715     int i;
02716     double fontsize;
02717     char* fontname;
02718     pointf pts[2];
02719     pointf sz;
02720     boxf bb0;
02721     boxf bb = GD_bb(g);
02722     xdot* xd = (xdot*)GD_drawing(g)->xdots;
02723 
02724     if (!xd) return bb;
02725 
02726     if ((bb.LL.x == bb.UR.x) && (bb.LL.y == bb.UR.y)) {
02727         bb.LL.x = bb.LL.y = MAXDOUBLE;
02728         bb.UR.x = bb.UR.y = -MAXDOUBLE;
02729     }
02730 
02731     op = (exdot_op*)(xd->ops);
02732     for (i = 0; i < xd->cnt; i++) {
02733         switch (op->op.kind) {
02734         case xd_filled_ellipse :
02735         case xd_unfilled_ellipse :
02736             pts[0].x = op->op.u.ellipse.x - op->op.u.ellipse.w;
02737             pts[0].y = op->op.u.ellipse.y - op->op.u.ellipse.h;
02738             pts[1].x = op->op.u.ellipse.x + op->op.u.ellipse.w;
02739             pts[1].y = op->op.u.ellipse.y + op->op.u.ellipse.h;
02740             op->bb.LL = pts[0];
02741             op->bb.UR = pts[1];
02742             expandBB (&bb, pts[0]);
02743             expandBB (&bb, pts[1]);
02744             break;
02745         case xd_filled_polygon :
02746         case xd_unfilled_polygon :
02747             op->bb = ptsBB (op->op.u.polygon.pts, op->op.u.polygon.cnt, &bb);
02748             break;
02749         case xd_filled_bezier :
02750         case xd_unfilled_bezier :
02751             op->bb = ptsBB (op->op.u.polygon.pts, op->op.u.polygon.cnt, &bb);
02752             break;
02753         case xd_polyline :
02754             op->bb = ptsBB (op->op.u.polygon.pts, op->op.u.polygon.cnt, &bb);
02755             break;
02756         case xd_text :
02757             op->para = NEW(textpara_t);
02758             op->para->str = strdup (op->op.u.text.text);
02759             op->para->just = adjust [op->op.u.text.align];
02760             sz = textsize (g, op->para, fontname, fontsize);
02761             bb0 = textBB (op->op.u.text.x, op->op.u.text.y, op->para);
02762             op->bb = bb0;
02763             expandBB (&bb, bb0.LL);
02764             expandBB (&bb, bb0.UR);
02765             if (!xd->freefunc)
02766                 xd->freefunc = (freefunc_t)freePara;
02767             break;
02768         case xd_font :
02769             fontsize = op->op.u.font.size;
02770             fontname = op->op.u.font.name;
02771             break;
02772         default :
02773             break;
02774         }
02775         op++;
02776     }
02777     return bb;
02778 }
02779 
02780 static void init_gvc(GVC_t * gvc, graph_t * g)
02781 {
02782     double xf, yf;
02783     char *p;
02784     int i;
02785 
02786     gvc->g = g;
02787 
02788     /* margins */
02789     gvc->graph_sets_margin = FALSE;
02790     if ((p = agget(g, "margin"))) {
02791         i = sscanf(p, "%lf,%lf", &xf, &yf);
02792         if (i > 0) {
02793             gvc->margin.x = gvc->margin.y = xf * POINTS_PER_INCH;
02794             if (i > 1)
02795                 gvc->margin.y = yf * POINTS_PER_INCH;
02796             gvc->graph_sets_margin = TRUE;
02797         }
02798     }
02799 
02800     /* pad */
02801     gvc->graph_sets_pad = FALSE;
02802     if ((p = agget(g, "pad"))) {
02803         i = sscanf(p, "%lf,%lf", &xf, &yf);
02804         if (i > 0) {
02805             gvc->pad.x = gvc->pad.y = xf * POINTS_PER_INCH;
02806             if (i > 1)
02807                 gvc->pad.y = yf * POINTS_PER_INCH;
02808             gvc->graph_sets_pad = TRUE;
02809         }
02810     }
02811 
02812     /* pagesize */
02813     gvc->graph_sets_pageSize = FALSE;
02814     gvc->pageSize = GD_drawing(g)->page;
02815     if ((GD_drawing(g)->page.x > 0.001) && (GD_drawing(g)->page.y > 0.001))
02816         gvc->graph_sets_pageSize = TRUE;
02817 
02818     /* rotation */
02819     if (GD_drawing(g)->landscape)
02820         gvc->rotation = 90;
02821     else 
02822         gvc->rotation = 0;
02823 
02824     /* pagedir */
02825     gvc->pagedir = "BL";
02826     if ((p = agget(g, "pagedir")) && p[0])
02827             gvc->pagedir = p;
02828 
02829 
02830     /* bounding box */
02831     gvc->bb = GD_bb(g);
02832 
02833     /* clusters have peripheries */
02834     G_peripheries = agfindgraphattr(g, "peripheries");
02835     G_penwidth = agfindgraphattr(g, "penwidth");
02836 
02837     /* default font */
02838 #ifndef WITH_CGRAPH
02839     gvc->defaultfontname = late_nnstring(g->proto->n,
02840                 N_fontname, DEFAULT_FONTNAME);
02841     gvc->defaultfontsize = late_double(g->proto->n,
02842                 N_fontsize, DEFAULT_FONTSIZE, MIN_FONTSIZE);
02843 #else
02844     gvc->defaultfontname = late_nnstring(NULL,
02845                 N_fontname, DEFAULT_FONTNAME);
02846     gvc->defaultfontsize = late_double(NULL,
02847                 N_fontsize, DEFAULT_FONTSIZE, MIN_FONTSIZE);
02848 #endif
02849 
02850     /* default line style */
02851     gvc->defaultlinestyle = defaultlinestyle;
02852 
02853     gvc->graphname = agnameof(g);
02854 }
02855 
02856 static void init_job_pad(GVJ_t *job)
02857 {
02858     GVC_t *gvc = job->gvc;
02859     
02860     if (gvc->graph_sets_pad) {
02861         job->pad = gvc->pad;
02862     }
02863     else {
02864         switch (job->output_lang) {
02865         case GVRENDER_PLUGIN:
02866             job->pad.x = job->pad.y = job->render.features->default_pad;
02867             break;
02868         default:
02869             job->pad.x = job->pad.y = DEFAULT_GRAPH_PAD;
02870             break;
02871         }
02872     }
02873 }
02874 
02875 static void init_job_margin(GVJ_t *job)
02876 {
02877     GVC_t *gvc = job->gvc;
02878     
02879     if (gvc->graph_sets_margin) {
02880         job->margin = gvc->margin;
02881     }
02882     else {
02883         /* set default margins depending on format */
02884         switch (job->output_lang) {
02885         case GVRENDER_PLUGIN:
02886             job->margin = job->device.features->default_margin;
02887             break;
02888         case HPGL: case PCL: case MIF: case METAPOST: case VTX: case QPDF:
02889             job->margin.x = job->margin.y = DEFAULT_PRINT_MARGIN;
02890             break;
02891         default:
02892             job->margin.x = job->margin.y = DEFAULT_EMBED_MARGIN;
02893             break;
02894         }
02895     }
02896 
02897 }
02898 
02899 static void init_job_dpi(GVJ_t *job, graph_t *g)
02900 {
02901     GVJ_t *firstjob = job->gvc->active_jobs;
02902 
02903     if (GD_drawing(g)->dpi != 0) {
02904         job->dpi.x = job->dpi.y = (double)(GD_drawing(g)->dpi);
02905     }
02906     else if (firstjob && firstjob->device_sets_dpi) {
02907         job->dpi = firstjob->device_dpi;   /* some devices set dpi in initialize() */
02908     }
02909     else {
02910         /* set default margins depending on format */
02911         switch (job->output_lang) {
02912         case GVRENDER_PLUGIN:
02913             job->dpi = job->device.features->default_dpi;
02914             break;
02915         default:
02916             job->dpi.x = job->dpi.y = (double)(DEFAULT_DPI);
02917             break;
02918         }
02919     }
02920 }
02921 
02922 static void init_job_viewport(GVJ_t * job, graph_t * g)
02923 {
02924     GVC_t *gvc = job->gvc;
02925     pointf LL, UR, size, sz;
02926     double X, Y, Z, x, y;
02927     int rv;
02928     Agnode_t *n;
02929     char *str, *nodename = NULL, *junk = NULL;
02930 
02931     UR = gvc->bb.UR;
02932     LL = gvc->bb.LL;
02933     job->bb.LL.x = LL.x - job->pad.x;           /* job->bb is bb of graph and padding - graph units */
02934     job->bb.LL.y = LL.y - job->pad.y;
02935     job->bb.UR.x = UR.x + job->pad.x;
02936     job->bb.UR.y = UR.y + job->pad.y;
02937     sz.x = job->bb.UR.x - job->bb.LL.x;   /* size, including padding - graph units */
02938     sz.y = job->bb.UR.y - job->bb.LL.y;
02939 
02940     /* determine final drawing size and scale to apply. */
02941     /* N.B. size given by user is not rotated by landscape mode */
02942     /* start with "natural" size of layout */
02943 
02944     Z = 1.0;
02945     if (GD_drawing(g)->size.x > 0.001 && GD_drawing(g)->size.y > 0.001) { /* graph size was given by user... */
02946         size = GD_drawing(g)->size;
02947         if ((size.x < sz.x) || (size.y < sz.y) /* drawing is too big (in either axis) ... */
02948             || ((GD_drawing(g)->filled) /* or ratio=filled requested and ... */
02949                 && (size.x > sz.x) && (size.y > sz.y))) /* drawing is too small (in both axes) ... */
02950             Z = MIN(size.x/sz.x, size.y/sz.y);
02951     }
02952     
02953     /* default focus, in graph units = center of bb */
02954     x = (LL.x + UR.x) / 2.;
02955     y = (LL.y + UR.y) / 2.;
02956 
02957     /* rotate and scale bb to give default absolute size in points*/
02958     job->rotation = job->gvc->rotation;
02959     X = sz.x * Z;
02960     Y = sz.y * Z;
02961 
02962     /* user can override */
02963     if ((str = agget(g, "viewport"))) {
02964         nodename = malloc(strlen(str)+1);
02965         junk = malloc(strlen(str)+1);
02966         rv = sscanf(str, "%lf,%lf,%lf,\'%[^\']\'", &X, &Y, &Z, nodename);
02967         if (rv == 4) {
02968             n = agfindnode(g->root, nodename);
02969             if (n) {
02970                 x = ND_coord(n).x;
02971                 y = ND_coord(n).y;
02972             }
02973         }
02974         else {
02975             rv = sscanf(str, "%lf,%lf,%lf,%[^,]%s", &X, &Y, &Z, nodename, junk);
02976             if (rv == 4) {
02977                 n = agfindnode(g->root, nodename);
02978                 if (n) {
02979                     x = ND_coord(n).x;
02980                     y = ND_coord(n).y;
02981                 }
02982             }
02983             else {
02984                 rv = sscanf(str, "%lf,%lf,%lf,%lf,%lf", &X, &Y, &Z, &x, &y);
02985             }
02986         }
02987         free (nodename);
02988         free (junk);
02989     }
02990     /* rv is ignored since args retain previous values if not scanned */
02991 
02992     /* job->view gives port size in graph units, unscaled or rotated
02993      * job->zoom gives scaling factor.
02994      * job->focus gives the position in the graph of the center of the port
02995      */
02996     job->view.x = X;
02997     job->view.y = Y;
02998     job->zoom = Z;              /* scaling factor */
02999     job->focus.x = x;
03000     job->focus.y = y;
03001 #if 0
03002 fprintf(stderr, "view=%g,%g, zoom=%g, focus=%g,%g\n",
03003         job->view.x, job->view.y,
03004         job->zoom,
03005         job->focus.x, job->focus.y);
03006 #endif
03007 }
03008 
03009 static void emit_cluster_colors(GVJ_t * job, graph_t * g)
03010 {
03011     graph_t *sg;
03012     int c;
03013     char *str;
03014 
03015     for (c = 1; c <= GD_n_cluster(g); c++) {
03016         sg = GD_clust(g)[c];
03017         emit_cluster_colors(job, sg);
03018         if (((str = agget(sg, "color")) != 0) && str[0])
03019             gvrender_set_pencolor(job, str);
03020         if (((str = agget(sg, "pencolor")) != 0) && str[0])
03021             gvrender_set_pencolor(job, str);
03022         if (((str = agget(sg, "bgcolor")) != 0) && str[0])
03023             gvrender_set_pencolor(job, str);
03024         if (((str = agget(sg, "fillcolor")) != 0) && str[0])
03025             gvrender_set_fillcolor(job, str);
03026         if (((str = agget(sg, "fontcolor")) != 0) && str[0])
03027             gvrender_set_pencolor(job, str);
03028     }
03029 }
03030 
03031 static void emit_colors(GVJ_t * job, graph_t * g)
03032 {
03033     node_t *n;
03034     edge_t *e;
03035     char *str, *colors;
03036 
03037     gvrender_set_fillcolor(job, DEFAULT_FILL);
03038     if (((str = agget(g, "bgcolor")) != 0) && str[0])
03039         gvrender_set_fillcolor(job, str);
03040     if (((str = agget(g, "fontcolor")) != 0) && str[0])
03041         gvrender_set_pencolor(job, str);
03042   
03043     emit_cluster_colors(job, g);
03044     for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
03045         if (((str = agget(n, "color")) != 0) && str[0])
03046             gvrender_set_pencolor(job, str);
03047         if (((str = agget(n, "pencolor")) != 0) && str[0])
03048             gvrender_set_fillcolor(job, str);
03049         if (((str = agget(n, "fillcolor")) != 0) && str[0]) {
03050             if (strchr(str, ':')) {
03051                 colors = strdup(str);
03052                 for (str = strtok(colors, ":"); str;
03053                     str = strtok(0, ":")) {
03054                     if (str[0])
03055                         gvrender_set_pencolor(job, str);
03056                 }
03057                 free(colors);
03058             }
03059             else {
03060                 gvrender_set_pencolor(job, str);
03061             }
03062         }
03063         if (((str = agget(n, "fontcolor")) != 0) && str[0])
03064             gvrender_set_pencolor(job, str);
03065         for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
03066             if (((str = agget(e, "color")) != 0) && str[0]) {
03067                 if (strchr(str, ':')) {
03068                     colors = strdup(str);
03069                     for (str = strtok(colors, ":"); str;
03070                         str = strtok(0, ":")) {
03071                         if (str[0])
03072                             gvrender_set_pencolor(job, str);
03073                     }
03074                     free(colors);
03075                 }
03076                 else {
03077                     gvrender_set_pencolor(job, str);
03078                 }
03079             }
03080             if (((str = agget(e, "fontcolor")) != 0) && str[0])
03081                 gvrender_set_pencolor(job, str);
03082         }
03083     }
03084 }
03085 
03086 static void emit_view(GVJ_t * job, graph_t * g, int flags)
03087 {
03088     GVC_t * gvc = job->gvc;
03089     node_t *n;
03090     edge_t *e;
03091 
03092     gvc->common.viewNum++;
03093     /* when drawing, lay clusters down before nodes and edges */
03094     if (!(flags & EMIT_CLUSTERS_LAST))
03095         emit_clusters(job, g, flags);
03096     if (flags & EMIT_SORTED) {
03097         /* output all nodes, then all edges */
03098         gvrender_begin_nodes(job);
03099         for (n = agfstnode(g); n; n = agnxtnode(g, n))
03100             emit_node(job, n);
03101         gvrender_end_nodes(job);
03102         gvrender_begin_edges(job);
03103         for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
03104             for (e = agfstout(g, n); e; e = agnxtout(g, e))
03105                 emit_edge(job, e);
03106         }
03107         gvrender_end_edges(job);
03108     } else if (flags & EMIT_EDGE_SORTED) {
03109         /* output all edges, then all nodes */
03110         gvrender_begin_edges(job);
03111         for (n = agfstnode(g); n; n = agnxtnode(g, n))
03112             for (e = agfstout(g, n); e; e = agnxtout(g, e))
03113                 emit_edge(job, e);
03114         gvrender_end_edges(job);
03115         gvrender_begin_nodes(job);
03116         for (n = agfstnode(g); n; n = agnxtnode(g, n))
03117             emit_node(job, n);
03118         gvrender_end_nodes(job);
03119     } else if (flags & EMIT_PREORDER) {
03120         gvrender_begin_nodes(job);
03121         for (n = agfstnode(g); n; n = agnxtnode(g, n))
03122             if (write_node_test(g, n))
03123                 emit_node(job, n);
03124         gvrender_end_nodes(job);
03125         gvrender_begin_edges(job);
03126 
03127         for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
03128             for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
03129                 if (write_edge_test(g, e))
03130                     emit_edge(job, e);
03131             }
03132         }
03133         gvrender_end_edges(job);
03134     } else {
03135         /* output in breadth first graph walk order */
03136         for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
03137             emit_node(job, n);
03138             for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
03139                 emit_node(job, aghead(e));
03140                 emit_edge(job, e);
03141             }
03142         }
03143     }
03144     /* when mapping, detect events on clusters after nodes and edges */
03145     if (flags & EMIT_CLUSTERS_LAST)
03146         emit_clusters(job, g, flags);
03147 }
03148 
03149 static void emit_begin_graph(GVJ_t * job, graph_t * g)
03150 {
03151     obj_state_t *obj;
03152 
03153     obj = push_obj_state(job);
03154     obj->type = ROOTGRAPH_OBJTYPE;
03155     obj->u.g = g;
03156     obj->emit_state = EMIT_GDRAW;
03157 
03158     initObjMapData (job, GD_label(g), g);
03159 
03160     gvrender_begin_graph(job, g);
03161 }
03162 
03163 static void emit_end_graph(GVJ_t * job, graph_t * g)
03164 {
03165     gvrender_end_graph(job);
03166     pop_obj_state(job);
03167 }
03168 
03169 static void emit_page(GVJ_t * job, graph_t * g)
03170 {
03171     obj_state_t *obj = job->obj;
03172     int nump = 0, flags = job->flags;
03173     textlabel_t *lab;
03174     pointf *p = NULL;
03175 
03176     setColorScheme (agget (g, "colorscheme"));
03177     setup_page(job, g);
03178     gvrender_begin_page(job);
03179     gvrender_set_pencolor(job, DEFAULT_COLOR);
03180     gvrender_set_fillcolor(job, DEFAULT_FILL);
03181     if ((flags & (GVRENDER_DOES_MAPS | GVRENDER_DOES_TOOLTIPS))
03182             && (obj->url || obj->explicit_tooltip)) {
03183         if (flags & (GVRENDER_DOES_MAP_RECTANGLE | GVRENDER_DOES_MAP_POLYGON)) {
03184             if (flags & GVRENDER_DOES_MAP_RECTANGLE) {
03185                 obj->url_map_shape = MAP_RECTANGLE;
03186                 nump = 2;
03187             }
03188             else {
03189                 obj->url_map_shape = MAP_POLYGON;
03190                 nump = 4;
03191             }
03192             p = N_NEW(nump, pointf);
03193             p[0] = job->pageBox.LL;
03194             p[1] = job->pageBox.UR;
03195             if (! (flags & (GVRENDER_DOES_MAP_RECTANGLE)))
03196                 rect2poly(p);
03197         }
03198         if (! (flags & GVRENDER_DOES_TRANSFORM))
03199             gvrender_ptf_A(job, p, p, nump);
03200         obj->url_map_p = p;
03201         obj->url_map_n = nump;
03202     }
03203     if ((flags & GVRENDER_DOES_LABELS) && ((lab = GD_label(g))))
03204         /* do graph label on every page and rely on clipping to show it on the right one(s) */
03205         obj->label = lab->text;
03206         /* If EMIT_CLUSTERS_LAST is set, we assume any URL or tooltip
03207          * attached to the root graph is emitted either in begin_page
03208          * or end_page of renderer.
03209          */
03210     if (!(flags & EMIT_CLUSTERS_LAST) && (obj->url || obj->explicit_tooltip)) {
03211         emit_map_rect(job, job->clip);
03212         gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
03213     }
03214     if (numPhysicalLayers(job) == 1)
03215         emit_background(job, g);
03216     if (GD_label(g))
03217         emit_label(job, EMIT_GLABEL, GD_label(g));
03218     if (!(flags & EMIT_CLUSTERS_LAST) && (obj->url || obj->explicit_tooltip))
03219         gvrender_end_anchor(job);
03220     emit_view(job,g,flags);
03221     gvrender_end_page(job);
03222 }
03223 
03224 void emit_graph(GVJ_t * job, graph_t * g)
03225 {
03226     node_t *n;
03227     char *s;
03228     int flags = job->flags;
03229     int* lp;
03230 
03231     /* device dpi is now known */
03232     job->scale.x = job->zoom * job->dpi.x / POINTS_PER_INCH;
03233     job->scale.y = job->zoom * job->dpi.y / POINTS_PER_INCH;
03234 
03235     job->devscale.x = job->dpi.x / POINTS_PER_INCH;
03236     job->devscale.y = job->dpi.y / POINTS_PER_INCH;
03237     if ((job->flags & GVRENDER_Y_GOES_DOWN) || (Y_invert))
03238         job->devscale.y *= -1;
03239 
03240     /* compute current view in graph units */
03241     if (job->rotation) {
03242         job->view.y = job->width / job->scale.y;
03243         job->view.x = job->height / job->scale.x;
03244     }
03245     else {
03246         job->view.x = job->width / job->scale.x;
03247         job->view.y = job->height / job->scale.y;
03248     }
03249 #if 0
03250 fprintf(stderr,"focus=%g,%g view=%g,%g\n",
03251         job->focus.x, job->focus.y, job->view.x, job->view.y);
03252 #endif
03253 
03254 #ifndef WITH_CGRAPH
03255     s = late_string(g, agfindattr(g, "comment"), "");
03256 #else
03257     s = late_string(g, agattr(g, AGRAPH, "comment", 0), "");
03258 #endif
03259     gvrender_comment(job, s);
03260 
03261     emit_begin_graph(job, g);
03262 
03263     if (flags & EMIT_COLORS)
03264         emit_colors(job,g);
03265 
03266     /* reset node state */
03267     for (n = agfstnode(g); n; n = agnxtnode(g, n))
03268         ND_state(n) = 0;
03269     /* iterate layers */
03270     for (firstlayer(job,&lp); validlayer(job); nextlayer(job,&lp)) {
03271         if (numPhysicalLayers (job) > 1)
03272             gvrender_begin_layer(job);
03273 
03274         /* iterate pages */
03275         for (firstpage(job); validpage(job); nextpage(job))
03276             emit_page(job, g);
03277 
03278         if (numPhysicalLayers (job) > 1)
03279             gvrender_end_layer(job);
03280     } 
03281     emit_end_graph(job, g);
03282 }
03283 
03284 /* support for stderr_once */
03285 static void free_string_entry(Dict_t * dict, char *key, Dtdisc_t * disc)
03286 {
03287 #ifndef WITH_CGRAPH
03288     agstrfree(key);
03289 #else
03290     free(key);
03291 #endif
03292 }
03293 
03294 static Dict_t *strings;
03295 static Dtdisc_t stringdict = {
03296     0,                          /* key  - the object itself */
03297     0,                          /* size - null-terminated string */
03298     -1,                         /* link - allocate separate holder objects  */
03299     NIL(Dtmake_f),
03300     (Dtfree_f) free_string_entry,
03301     NIL(Dtcompar_f),
03302     NIL(Dthash_f),
03303     NIL(Dtmemory_f),
03304     NIL(Dtevent_f)
03305 };
03306 
03307 int emit_once(char *str)
03308 {
03309     if (strings == 0)
03310         strings = dtopen(&stringdict, Dtoset);
03311     if (!dtsearch(strings, str)) {
03312 #ifndef WITH_CGRAPH
03313         dtinsert(strings, agstrdup(str));
03314 #else
03315         dtinsert(strings, strdup(str));
03316 #endif
03317         return TRUE;
03318     }
03319     return FALSE;
03320 }
03321 
03322 void emit_once_reset(void)
03323 {
03324     if (strings) {
03325         dtclose(strings);
03326         strings = 0;
03327     }
03328 }
03329 
03330 static void emit_begin_cluster(GVJ_t * job, Agraph_t * sg)
03331 {
03332     obj_state_t *obj;
03333 
03334     obj = push_obj_state(job);
03335     obj->type = CLUSTER_OBJTYPE;
03336     obj->u.sg = sg;
03337     obj->emit_state = EMIT_CDRAW;
03338 
03339     initObjMapData (job, GD_label(sg), sg);
03340     
03341     gvrender_begin_cluster(job, sg);
03342 }
03343 
03344 static void emit_end_cluster(GVJ_t * job, Agraph_t * g)
03345 {
03346     gvrender_end_cluster(job, g);
03347     pop_obj_state(job);
03348 }
03349 
03350 void emit_clusters(GVJ_t * job, Agraph_t * g, int flags)
03351 {
03352     int doPerim, c, istyle, filled;
03353     pointf AF[4];
03354     char *color, *fillcolor, *pencolor, **style, *s;
03355     graph_t *sg;
03356     node_t *n;
03357     edge_t *e;
03358     obj_state_t *obj;
03359     textlabel_t *lab;
03360     int doAnchor;
03361     double penwidth;
03362     char* clrs[2];
03363     
03364     for (c = 1; c <= GD_n_cluster(g); c++) {
03365         sg = GD_clust(g)[c];
03366         if (clust_in_layer(job, sg) == FALSE)
03367             continue;
03368         /* when mapping, detect events on clusters after sub_clusters */
03369         if (flags & EMIT_CLUSTERS_LAST)
03370             emit_clusters(job, sg, flags);
03371         emit_begin_cluster(job, sg);
03372         obj = job->obj;
03373         doAnchor = (obj->url || obj->explicit_tooltip);
03374         setColorScheme (agget (sg, "colorscheme"));
03375         if (doAnchor && !(flags & EMIT_CLUSTERS_LAST)) {
03376             emit_map_rect(job, GD_bb(sg));
03377             gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
03378         }
03379         filled = FALSE;
03380         istyle = 0;
03381         if ((style = checkClusterStyle(sg, &istyle))) {
03382             gvrender_set_style(job, style);
03383             if (istyle & FILLED)
03384                 filled = FILL;
03385         }
03386         fillcolor = pencolor = 0;
03387 
03388         if (GD_gui_state(sg) & GUI_STATE_ACTIVE) {
03389             pencolor = late_nnstring(sg, G_activepencolor, DEFAULT_ACTIVEPENCOLOR);
03390             fillcolor = late_nnstring(sg, G_activefillcolor, DEFAULT_ACTIVEFILLCOLOR);
03391             filled = TRUE;
03392         }
03393         else if (GD_gui_state(sg) & GUI_STATE_SELECTED) {
03394             pencolor = late_nnstring(sg, G_activepencolor, DEFAULT_SELECTEDPENCOLOR);
03395             fillcolor = late_nnstring(sg, G_activefillcolor, DEFAULT_SELECTEDFILLCOLOR);
03396             filled = TRUE;
03397         }
03398         else if (GD_gui_state(sg) & GUI_STATE_DELETED) {
03399             pencolor = late_nnstring(sg, G_deletedpencolor, DEFAULT_DELETEDPENCOLOR);
03400             fillcolor = late_nnstring(sg, G_deletedfillcolor, DEFAULT_DELETEDFILLCOLOR);
03401             filled = TRUE;
03402         }
03403         else if (GD_gui_state(sg) & GUI_STATE_VISITED) {
03404             pencolor = late_nnstring(sg, G_visitedpencolor, DEFAULT_VISITEDPENCOLOR);
03405             fillcolor = late_nnstring(sg, G_visitedfillcolor, DEFAULT_VISITEDFILLCOLOR);
03406             filled = TRUE;
03407         }
03408         else {
03409             if (((color = agget(sg, "color")) != 0) && color[0])
03410                 fillcolor = pencolor = color;
03411             if (((color = agget(sg, "pencolor")) != 0) && color[0])
03412                 pencolor = color;
03413             if (((color = agget(sg, "fillcolor")) != 0) && color[0])
03414                 fillcolor = color;
03415             /* bgcolor is supported for backward compatability 
03416                if fill is set, fillcolor trumps bgcolor, so
03417                don't bother checking.
03418                if gradient is set fillcolor trumps bgcolor
03419              */
03420             if ((!filled || !fillcolor) && ((color = agget(sg, "bgcolor")) != 0) && color[0]) {
03421                 fillcolor = color;
03422                 filled = FILL;
03423             }
03424 
03425         }
03426         if (!pencolor) pencolor = DEFAULT_COLOR;
03427         if (!fillcolor) fillcolor = DEFAULT_FILL;
03428         clrs[0] = NULL;
03429         if (filled) {
03430             if (findStopColor (fillcolor, clrs)) {
03431                 gvrender_set_fillcolor(job, clrs[0]);
03432                 if (clrs[1]) 
03433                     gvrender_set_gradient_vals(job,clrs[1],late_int(sg,G_gradientangle,0,0));
03434                 else 
03435                     gvrender_set_gradient_vals(job,DEFAULT_COLOR,late_int(sg,G_gradientangle,0,0));
03436                 if (istyle & RADIAL)
03437                     filled = RGRADIENT;
03438                 else
03439                     filled = GRADIENT;
03440             }
03441             else
03442                 gvrender_set_fillcolor(job, fillcolor);
03443         }
03444 
03445         if (G_penwidth && ((s=ag_xget(sg,G_penwidth)) && s[0])) {
03446             penwidth = late_double(sg, G_penwidth, 1.0, 0.0);
03447             gvrender_set_penwidth(job, penwidth);
03448         }
03449 
03450         if (istyle & ROUNDED) {
03451             if ((doPerim = late_int(sg, G_peripheries, 1, 0)) || filled) {
03452                 AF[0] = GD_bb(sg).LL;
03453                 AF[2] = GD_bb(sg).UR;
03454                 AF[1].x = AF[2].x;
03455                 AF[1].y = AF[0].y;
03456                 AF[3].x = AF[0].x;
03457                 AF[3].y = AF[2].y;
03458                 if (doPerim)
03459                     gvrender_set_pencolor(job, pencolor);
03460                 else
03461                     gvrender_set_pencolor(job, "transparent");
03462                 round_corners(job, AF, 4, istyle, filled);
03463             }
03464         }
03465         else {
03466             if (late_int(sg, G_peripheries, 1, 0)) {
03467                 gvrender_set_pencolor(job, pencolor);
03468                 gvrender_box(job, GD_bb(sg), filled);
03469             }
03470             else if (filled) {
03471                 gvrender_set_pencolor(job, "transparent");
03472                 gvrender_box(job, GD_bb(sg), filled);
03473             }
03474         }
03475 
03476         free (clrs[0]);
03477         if ((lab = GD_label(sg)))
03478             emit_label(job, EMIT_CLABEL, lab);
03479 
03480         if (doAnchor) {
03481             if (flags & EMIT_CLUSTERS_LAST) {
03482                 emit_map_rect(job, GD_bb(sg));
03483                 gvrender_begin_anchor(job, obj->url, obj->tooltip, obj->target, obj->id);
03484             }
03485             gvrender_end_anchor(job);
03486         }
03487 
03488         if (flags & EMIT_PREORDER) {
03489             for (n = agfstnode(sg); n; n = agnxtnode(sg, n)) {
03490                 emit_node(job, n);
03491                 for (e = agfstout(sg, n); e; e = agnxtout(sg, e))
03492                     emit_edge(job, e);
03493             }
03494         }
03495         emit_end_cluster(job, g);
03496         /* when drawing, lay down clusters before sub_clusters */
03497         if (!(flags & EMIT_CLUSTERS_LAST))
03498             emit_clusters(job, sg, flags);
03499     }
03500 }
03501 
03502 static boolean is_style_delim(int c)
03503 {
03504     switch (c) {
03505     case '(':
03506     case ')':
03507     case ',':
03508     case '\0':
03509         return TRUE;
03510     default:
03511         return FALSE;
03512     }
03513 }
03514 
03515 #define SID 1
03516 
03517 static int style_token(char **s, agxbuf * xb)
03518 {
03519     char *p = *s;
03520     int token, rc;
03521     char c;
03522 
03523     while (*p && (isspace(*p) || (*p == ',')))
03524         p++;
03525     switch (*p) {
03526     case '\0':
03527         token = 0;
03528         break;
03529     case '(':
03530     case ')':
03531         token = *p++;
03532         break;
03533     default:
03534         token = SID;
03535         while (!is_style_delim(c = *p)) {
03536             rc = agxbputc(xb, c);
03537             p++;
03538         }
03539     }
03540     *s = p;
03541     return token;
03542 }
03543 
03544 #define FUNLIMIT 64
03545 static unsigned char outbuf[SMALLBUF];
03546 static agxbuf ps_xb;
03547 
03548 #if 0
03549 static void cleanup(void)
03550 {
03551     agxbfree(&ps_xb);
03552 }
03553 #endif
03554 
03555 /* parse_style:
03556  * This is one of the worst internal designs in graphviz.
03557  * The use of '\0' characters within strings seems cute but it
03558  * makes all of the standard functions useless if not dangerous.
03559  * Plus the function uses static memory for both the array and
03560  * the character buffer. One hopes all of the values are used
03561  * before the function is called again.
03562  */
03563 char **parse_style(char *s)
03564 {
03565     static char *parse[FUNLIMIT];
03566     static boolean is_first = TRUE;
03567     int fun = 0;
03568     boolean in_parens = FALSE;
03569     unsigned char buf[SMALLBUF];
03570     char *p;
03571     int c;
03572     agxbuf xb;
03573 
03574     if (is_first) {
03575         agxbinit(&ps_xb, SMALLBUF, outbuf);
03576 #if 0
03577         atexit(cleanup);
03578 #endif
03579         is_first = FALSE;
03580     }
03581 
03582     agxbinit(&xb, SMALLBUF, buf);
03583     p = s;
03584     while ((c = style_token(&p, &xb)) != 0) {
03585         switch (c) {
03586         case '(':
03587             if (in_parens) {
03588                 agerr(AGERR, "nesting not allowed in style: %s\n", s);
03589                 parse[0] = (char *) 0;
03590                 agxbfree(&xb);
03591                 return parse;
03592             }
03593             in_parens = TRUE;
03594             break;
03595 
03596         case ')':
03597             if (in_parens == FALSE) {
03598                 agerr(AGERR, "unmatched ')' in style: %s\n", s);
03599                 parse[0] = (char *) 0;
03600                 agxbfree(&xb);
03601                 return parse;
03602             }
03603             in_parens = FALSE;
03604             break;
03605 
03606         default:
03607             if (in_parens == FALSE) {
03608                 if (fun == FUNLIMIT - 1) {
03609                     agerr(AGWARN, "truncating style '%s'\n", s);
03610                     parse[fun] = (char *) 0;
03611                     agxbfree(&xb);
03612                     return parse;
03613                 }
03614                 agxbputc(&ps_xb, '\0'); /* terminate previous */
03615                 parse[fun++] = agxbnext(&ps_xb);
03616             }
03617             agxbput(&ps_xb, agxbuse(&xb));
03618             agxbputc(&ps_xb, '\0');
03619         }
03620     }
03621 
03622     if (in_parens) {
03623         agerr(AGERR, "unmatched '(' in style: %s\n", s);
03624         parse[0] = (char *) 0;
03625         agxbfree(&xb);
03626         return parse;
03627     }
03628     parse[fun] = (char *) 0;
03629     agxbfree(&xb);
03630     (void)agxbuse(&ps_xb);              /* adds final '\0' to buffer */
03631     return parse;
03632 }
03633 
03634 static boxf bezier_bb(bezier bz)
03635 {
03636     int i;
03637     pointf p, p1, p2;
03638     boxf bb;
03639 
03640     assert(bz.size > 0);
03641     assert(bz.size % 3 == 1);
03642     bb.LL = bb.UR = bz.list[0];
03643     for (i = 1; i < bz.size;) {
03644         /* take mid-point between two control points for bb calculation */
03645         p1=bz.list[i];
03646         i++;
03647         p2=bz.list[i];
03648         i++;
03649         p.x = ( p1.x + p2.x ) / 2;
03650         p.y = ( p1.y + p2.y ) / 2;
03651         EXPANDBP(bb,p);
03652 
03653         p=bz.list[i];
03654         EXPANDBP(bb,p);
03655         i++;
03656     }
03657     return bb;
03658 }
03659 
03660 static void init_splines_bb(splines *spl)
03661 {
03662     int i;
03663     bezier bz;
03664     boxf bb, b;
03665 
03666     assert(spl->size > 0);
03667     bz = spl->list[0];
03668     bb = bezier_bb(bz);
03669     for (i = 0; i < spl->size; i++) {
03670         if (i > 0) {
03671             bz = spl->list[i];
03672             b = bezier_bb(bz);
03673             EXPANDBB(bb, b);
03674         }
03675         if (bz.sflag) {
03676             b = arrow_bb(bz.sp, bz.list[0], 1, bz.sflag);
03677             EXPANDBB(bb, b);
03678         }
03679         if (bz.eflag) {
03680             b = arrow_bb(bz.ep, bz.list[bz.size - 1], 1, bz.eflag);
03681             EXPANDBB(bb, b);
03682         }
03683     }
03684     spl->bb = bb;
03685 }
03686 
03687 static void init_bb_edge(edge_t *e)
03688 {
03689     splines *spl;
03690 
03691     spl = ED_spl(e);
03692     if (spl)
03693         init_splines_bb(spl);
03694 
03695 //    lp = ED_label(e);
03696 //    if (lp)
03697 //        {}
03698 }
03699 
03700 static void init_bb_node(graph_t *g, node_t *n)
03701 {
03702     edge_t *e;
03703 
03704     ND_bb(n).LL.x = ND_coord(n).x - ND_lw(n);
03705     ND_bb(n).LL.y = ND_coord(n).y - ND_ht(n) / 2.;
03706     ND_bb(n).UR.x = ND_coord(n).x + ND_rw(n);
03707     ND_bb(n).UR.y = ND_coord(n).y + ND_ht(n) / 2.;
03708 
03709     for (e = agfstout(g, n); e; e = agnxtout(g, e))
03710         init_bb_edge(e);
03711 
03712     /* IDEA - could also save in the node the bb of the node and
03713     all of its outedges, then the scan time would be proportional
03714     to just the number of nodes for many graphs.
03715     Wouldn't work so well if the edges are sprawling all over the place
03716     because then the boxes would overlap a lot and require more tests,
03717     but perhaps that wouldn't add much to the cost before trying individual
03718     nodes and edges. */
03719 }
03720 
03721 static void init_bb(graph_t *g)
03722 {
03723     node_t *n;
03724 
03725     for (n = agfstnode(g); n; n = agnxtnode(g, n))
03726         init_bb_node(g, n);
03727 }
03728 
03729 extern gvevent_key_binding_t gvevent_key_binding[];
03730 extern int gvevent_key_binding_size;
03731 extern gvdevice_callbacks_t gvdevice_callbacks;
03732 
03733 /* gv_fixLocale:
03734  * Set LC_NUMERIC to "C" to get expected interpretation of %f
03735  * in printf functions. Languages like postscript and dot expect
03736  * floating point numbers to use a decimal point.
03737  * 
03738  * If set is non-zero, the "C" locale set;
03739  * if set is zero, the original locale is reset.
03740  * Calls to the function can nest.
03741  */
03742 void gv_fixLocale (int set)
03743 {
03744     static char* save_locale;
03745     static int cnt;
03746 
03747     if (set) {
03748         cnt++;
03749         if (cnt == 1) {
03750             save_locale = strdup (setlocale (LC_NUMERIC, NULL));
03751             setlocale (LC_NUMERIC, "C");
03752         }
03753     }
03754     else if (cnt > 0) {
03755         cnt--;
03756         if (cnt == 0) {
03757             setlocale (LC_NUMERIC, save_locale);
03758             free (save_locale);
03759         }
03760     }
03761 }
03762 
03763 
03764 #define FINISH() if (Verbose) fprintf(stderr,"gvRenderJobs %s: %.2f secs.\n", agnameof(g), elapsed_sec())
03765 
03766 int gvRenderJobs (GVC_t * gvc, graph_t * g)
03767 {
03768     static GVJ_t *prevjob;
03769     GVJ_t *job, *firstjob;
03770 
03771     if (Verbose)
03772         start_timer();
03773     
03774     if (!GD_drawing(g)) {
03775         agerr (AGERR, "Layout was not done.  Missing layout plugins? \n");
03776         FINISH();
03777         return -1;
03778     }
03779 
03780     init_bb(g);
03781     init_gvc(gvc, g);
03782     init_layering(gvc, g);
03783 
03784     gvc->keybindings = gvevent_key_binding;
03785     gvc->numkeys = gvevent_key_binding_size;
03786     gv_fixLocale (1);
03787     for (job = gvjobs_first(gvc); job; job = gvjobs_next(gvc)) {
03788         if (gvc->gvg) {
03789             job->input_filename = gvc->gvg->input_filename;
03790             job->graph_index = gvc->gvg->graph_index;
03791         }
03792         else {
03793             job->input_filename = NULL;
03794             job->graph_index = 0;
03795         }
03796         job->common = &(gvc->common);
03797         job->layout_type = gvc->layout.type;
03798         if (!GD_drawing(g)) {
03799             agerr (AGERR, "layout was not done\n");
03800             gv_fixLocale (0);
03801             FINISH();
03802             return -1;
03803         }
03804 
03805         job->output_lang = gvrender_select(job, job->output_langname);
03806         if (job->output_lang == NO_SUPPORT) {
03807             agerr (AGERR, "renderer for %s is unavailable\n", job->output_langname);
03808             gv_fixLocale (0);
03809             FINISH();
03810             return -1;
03811         }
03812 
03813         switch (job->output_lang) {
03814         case VTX:
03815             /* output sorted, i.e. all nodes then all edges */
03816             job->flags |= EMIT_SORTED;
03817             break;
03818         case DIA:
03819             /* output in preorder traversal of the graph */
03820             job->flags |= EMIT_PREORDER
03821                        | GVDEVICE_BINARY_FORMAT;
03822             break;
03823         default:
03824             job->flags |= chkOrder(g);
03825             break;
03826         }
03827 
03828         /* 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 */
03829         firstjob = gvc->active_jobs;
03830         if (firstjob) {
03831             if (! (firstjob->flags & GVDEVICE_DOES_PAGES)
03832               || (strcmp(job->output_langname,firstjob->output_langname))) {
03833 
03834                 gvrender_end_job(firstjob);
03835             
03836                 gvc->active_jobs = NULL; /* clear active list */
03837                 gvc->common.viewNum = 0;
03838                 prevjob = NULL;
03839             }
03840         }
03841         else {
03842             prevjob = NULL;
03843         }
03844 
03845         if (prevjob) {
03846             prevjob->next_active = job;  /* insert job in active list */
03847             job->output_file = prevjob->output_file;  /* FIXME - this is dumb ! */
03848         }
03849         else {
03850             if (gvrender_begin_job(job))
03851                 continue;
03852             gvc->active_jobs = job;   /* first job of new list */
03853         }
03854         job->next_active = NULL;      /* terminate active list */
03855         job->callbacks = &gvdevice_callbacks;
03856 
03857         init_job_pad(job);
03858         init_job_margin(job);
03859         init_job_dpi(job, g);
03860         init_job_viewport(job, g);
03861         init_job_pagination(job, g);
03862 
03863         if (! (job->flags & GVDEVICE_EVENTS)) {
03864 #ifdef DEBUG
03865                 /* Show_boxes is not defined, if at all, 
03866                  * until splines are generated in dot 
03867                  */
03868             job->common->show_boxes = (const char**)Show_boxes; 
03869 #endif
03870             emit_graph(job, g);
03871         }
03872 
03873         /* the last job, after all input graphs are processed,
03874          *      is finalized from gvFinalize()
03875          */
03876         prevjob = job;
03877     }
03878     gv_fixLocale (0);
03879     FINISH();
03880     return 0;
03881 }