|
Graphviz
2.29.20120523.0446
|
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 }
1.7.5