/misc/src/release/graphviz-2.18-1/src/graphviz-2.18/lib/dotgen/position.c

Go to the documentation of this file.
00001 /* $Id: position.c,v 1.14 2006/12/07 22:49:36 erg Exp $ $Revision: 1.14 $ */
00002 /* vim:set shiftwidth=4 ts=8: */
00003 
00004 /**********************************************************
00005 *      This software is part of the graphviz package      *
00006 *                http://www.graphviz.org/                 *
00007 *                                                         *
00008 *            Copyright (c) 1994-2004 AT&T Corp.           *
00009 *                and is licensed under the                *
00010 *            Common Public License, Version 1.0           *
00011 *                      by AT&T Corp.                      *
00012 *                                                         *
00013 *        Information and Software Systems Research        *
00014 *              AT&T Research, Florham Park NJ             *
00015 **********************************************************/
00016 
00017 
00018 /*
00019  * position(g): set ND_coord_i(n) (x and y) for all nodes n of g, using GD_rank(g).
00020  * (the graph may be modified by merging certain edges with a common endpoint.)
00021  * the coordinates are computed by constructing and ranking an auxiliary graph.
00022  * then leaf nodes are inserted in the fast graph.  cluster boundary nodes are
00023  * created and correctly separated.
00024  */
00025 
00026 #include "dot.h"
00027 
00028 static int nsiter2(graph_t * g);
00029 static void create_aux_edges(graph_t * g);
00030 static void remove_aux_edges(graph_t * g);
00031 static void set_xcoords(graph_t * g);
00032 static void set_ycoords(graph_t * g);
00033 static void set_aspect(graph_t * g);
00034 static void expand_leaves(graph_t * g);
00035 static void make_lrvn(graph_t * g);
00036 static void contain_nodes(graph_t * g);
00037 static boolean idealsize(graph_t * g, double);
00038 
00039 #ifdef DEBUG
00040 static void
00041 dumpNS (graph_t * g)
00042 {
00043     node_t* n = GD_nlist(g);
00044     elist el;
00045     edge_t* e;
00046     int i;
00047 
00048     while (n) {
00049         el = ND_out(n);
00050         for (i = 0; i < el.size; i++) {
00051             e = el.list[i];
00052             fprintf (stderr, "%s(%x) -> %s(%x) : %d\n", e->tail->name,e->tail, e->head->name, e->head,
00053                 ED_minlen(e));
00054         }
00055         n = ND_next(n); 
00056     }
00057 }
00058 #endif
00059 
00060 static void
00061 largeMinlen (int l)
00062 {
00063     agerr (AGERR, "Edge length %d larger than maximum %u allowed.\nCheck for overwide node(s).\n", l, USHRT_MAX); 
00064     exit (1);
00065 }
00066 
00067 /* connectGraph:
00068  * When source and/or sink nodes are defined, it is possible that
00069  * after the auxiliary edges are added, the graph may still have 2 or
00070  * 3 components. To fix this, we put trivial constraints connecting the
00071  * first items of each rank.
00072  */
00073 static void
00074 connectGraph (graph_t* g)
00075 {
00076     int i, j, r, found;
00077     node_t* tp;
00078     node_t* hp;
00079     node_t* sn;
00080     edge_t* e;
00081     rank_t* rp;
00082 
00083     for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
00084         rp = GD_rank(g)+r;
00085         found =FALSE;
00086         tp = NULL;
00087         for (i = 0; i < rp->n; i++) {
00088             tp = rp->v[i];
00089             if (ND_save_out(tp).list) {
00090                 for (j = 0; (e = ND_save_out(tp).list[j]); j++) {
00091                     if ((ND_rank(e->head) > r) || (ND_rank(e->tail) > r)) {
00092                         found = TRUE;
00093                         break;
00094                     }
00095                 }
00096                 if (found) break;
00097             }
00098             if (ND_save_in(tp).list) {
00099                 for (j = 0; (e = ND_save_in(tp).list[j]); j++) {
00100                     if ((ND_rank(e->tail) > r) || (ND_rank(e->head) > r)) {
00101                         found = TRUE;
00102                         break;
00103                     }
00104                 }
00105                 if (found) break;
00106             }
00107         }
00108         if (found || !tp) continue;
00109         tp = rp->v[0];
00110         if (r < GD_maxrank(g)) hp = (rp+1)->v[0];
00111         else hp = (rp-1)->v[0];
00112         assert (hp);
00113         sn = virtual_node(g);
00114         ND_node_type(sn) = SLACKNODE;
00115         make_aux_edge(sn, tp, 0, 0);
00116         make_aux_edge(sn, hp, 0, 0);
00117         ND_rank(sn) = MIN(ND_rank(tp), ND_rank(hp));
00118     }
00119 }
00120 
00121 void dot_position(graph_t * g)
00122 {
00123     if (GD_nlist(g) == NULL)
00124         return;                 /* ignore empty graph */
00125     mark_lowclusters(g);        /* we could remove from splines.c now */
00126     set_ycoords(g);
00127     if (Concentrate)
00128         dot_concentrate(g);
00129     expand_leaves(g);
00130     if (flat_edges(g))
00131         set_ycoords(g);
00132     create_aux_edges(g);
00133     if (rank(g, 2, nsiter2(g))) { /* LR balance == 2 */
00134         connectGraph (g);
00135         assert(rank(g, 2, nsiter2(g)) == 0);
00136     }
00137     set_xcoords(g);
00138     set_aspect(g);
00139     remove_aux_edges(g);        /* must come after set_aspect since we now
00140                                  * use GD_ln and GD_rn for bbox width.
00141                                  */
00142 }
00143 
00144 static int nsiter2(graph_t * g)
00145 {
00146     int maxiter = INT_MAX;
00147     char *s;
00148 
00149     if ((s = agget(g, "nslimit")))
00150         maxiter = atof(s) * agnnodes(g);
00151     return maxiter;
00152 }
00153 
00154 static int go(node_t * u, node_t * v)
00155 {
00156     int i;
00157     edge_t *e;
00158 
00159     if (u == v)
00160         return TRUE;
00161     for (i = 0; (e = ND_out(u).list[i]); i++) {
00162         if (go(e->head, v))
00163             return TRUE;
00164     }
00165     return FALSE;
00166 }
00167 
00168 static int canreach(node_t * u, node_t * v)
00169 {
00170     return go(u, v);
00171 }
00172 
00173 edge_t *make_aux_edge(node_t * u, node_t * v, int len, int wt)
00174 {
00175     edge_t *e;
00176 
00177     e = NEW(edge_t);
00178     e->tail = u;
00179     e->head = v;
00180     if (len > USHRT_MAX)
00181         largeMinlen (len);
00182     ED_minlen(e) = len;
00183     ED_weight(e) = wt;
00184     fast_edge(e);
00185     return e;
00186 }
00187 
00188 static void allocate_aux_edges(graph_t * g)
00189 {
00190     int i, j, n_in;
00191     node_t *n;
00192 
00193     /* allocate space for aux edge lists */
00194     for (n = GD_nlist(g); n; n = ND_next(n)) {
00195         ND_save_in(n) = ND_in(n);
00196         ND_save_out(n) = ND_out(n);
00197         for (i = 0; ND_out(n).list[i]; i++);
00198         for (j = 0; ND_in(n).list[j]; j++);
00199         n_in = i + j;
00200         alloc_elist(n_in + 3, ND_in(n));
00201         alloc_elist(3, ND_out(n));
00202     }
00203 }
00204 
00205 /* make_LR_constraints:
00206  */
00207 static void 
00208 make_LR_constraints(graph_t * g)
00209 {
00210     int i, j, k;
00211     int sw;                     /* self width */
00212     int m0, m1;
00213     int width, sep[2];
00214     int nodesep;      /* separation between nodes on same rank */
00215     edge_t *e, *e0, *e1, *ff;
00216     node_t *u, *v, *t0, *h0;
00217     rank_t *rank = GD_rank(g);
00218 
00219     /* Use smaller separation on odd ranks if g has edge labels */
00220     if (GD_has_labels(g) & EDGE_LABEL) {
00221         sep[0] = GD_nodesep(g);
00222         sep[1] = 5;
00223     }
00224     else {
00225         sep[1] = sep[0] = GD_nodesep(g);
00226     }
00227     /* make edges to constrain left-to-right ordering */
00228     for (i = GD_minrank(g); i <= GD_maxrank(g); i++) {
00229         int last;
00230         last = rank[i].v[0]->u.rank = 0;
00231         nodesep = sep[i & 1];
00232         for (j = 0; j < rank[i].n; j++) {
00233             u = rank[i].v[j];
00234             ND_mval(u) = ND_rw_i(u);    /* keep it somewhere safe */
00235             if (ND_other(u).size > 0) { /* compute self size */
00236                 /* FIX: dot assumes all self-edges go to the right. This
00237                  * is no longer true, though makeSelfEdge still attempts to
00238                  * put as many as reasonable on the right. The dot code
00239                  * should be modified to allow a box reflecting the placement
00240                  * of all self-edges, and use that to reposition the nodes.
00241                  * Note that this would not only affect left and right
00242                  * positioning but may also affect interrank spacing.
00243                  */
00244                 sw = 0;
00245                 for (k = 0; (e = ND_other(u).list[k]); k++) {
00246                     if (e->tail == e->head) {
00247                         sw += selfRightSpace (e);
00248                     }
00249                 }
00250                 ND_rw_i(u) += sw;       /* increment to include self edges */
00251             }
00252             v = rank[i].v[j + 1];
00253             if (v) {
00254                 width = ND_rw_i(u) + ND_lw_i(v) + nodesep;
00255                 e0 = make_aux_edge(u, v, width, 0);
00256                 last = (ND_rank(v) = last + width);
00257             }
00258 
00259             /* constraints from labels of flat edges on previous rank */
00260             if ((e = (edge_t*)ND_alg(u))) {
00261                 e0 = ND_save_out(u).list[0];
00262                 e1 = ND_save_out(u).list[1];
00263                 if (ND_order(e0->head) > ND_order(e1->head)) {
00264                     ff = e0;
00265                     e0 = e1;
00266                     e1 = ff;
00267                 }
00268                 m0 = (ED_minlen(e) * GD_nodesep(g)) / 2;
00269                 m1 = m0 + ND_rw_i(e0->head) + ND_lw_i(e0->tail);
00270                 /* these guards are needed because the flat edges
00271                  * work very poorly with cluster layout */
00272                 if (canreach(e0->tail, e0->head) == FALSE)
00273                     make_aux_edge(e0->head, e0->tail, m1,
00274                         ED_weight(e));
00275                 m1 = m0 + ND_rw_i(e1->tail) + ND_lw_i(e1->head);
00276                 if (canreach(e1->head, e1->tail) == FALSE)
00277                     make_aux_edge(e1->tail, e1->head, m1,
00278                         ED_weight(e));
00279             }
00280 
00281             /* position flat edge endpoints */
00282             for (k = 0; k < ND_flat_out(u).size; k++) {
00283                 e = ND_flat_out(u).list[k];
00284                 if (ND_order(e->tail) < ND_order(e->head)) {
00285                     t0 = e->tail;
00286                     h0 = e->head;
00287                 } else {
00288                     t0 = e->head;
00289                     h0 = e->tail;
00290                 }
00291 
00292                 width = ND_rw_i(t0) + ND_lw_i(h0);
00293                 m0 = ED_minlen(e) * GD_nodesep(g) + width;
00294 
00295                 if ((e0 = find_fast_edge(t0, h0))) {
00296                     /* flat edge between adjacent neighbors 
00297                      * ED_dist contains the largest label width.
00298                      */
00299                     m0 = MAX(m0, width + GD_nodesep(g) + ROUND(ED_dist(e)));
00300                     if (m0 > USHRT_MAX)
00301                         largeMinlen (m0);
00302                     ED_minlen(e0) = MAX(ED_minlen(e0), m0);
00303                 }
00304                 else if (!ED_label(e)) {
00305                     /* unlabeled flat edge between non-neighbors 
00306                      * ED_minlen(e) is max of ED_minlen of all equivalent 
00307                      * edges.
00308                      */
00309                     make_aux_edge(t0, h0, m0, ED_weight(e));
00310                 }
00311                 /* labeled flat edges between non-neighbors have already
00312                  * been constrained by the label above. 
00313                  */ 
00314             }
00315         }
00316     }
00317 }
00318 
00319 /* make_edge_pairs: make virtual edge pairs corresponding to input edges */
00320 static void make_edge_pairs(graph_t * g)
00321 {
00322     int i, m0, m1;
00323     node_t *n, *sn;
00324     edge_t *e;
00325 
00326     for (n = GD_nlist(g); n; n = ND_next(n)) {
00327         if (ND_save_out(n).list)
00328             for (i = 0; (e = ND_save_out(n).list[i]); i++) {
00329                 sn = virtual_node(g);
00330                 ND_node_type(sn) = SLACKNODE;
00331                 m0 = (ED_head_port(e).p.x - ED_tail_port(e).p.x);
00332                 if (m0 > 0)
00333                     m1 = 0;
00334                 else {
00335                     m1 = -m0;
00336                     m0 = 0;
00337                 }
00338 #ifdef NOTDEF
00339 /* was trying to improve LR balance */
00340                 if ((ND_save_out(n).size % 2 == 0)
00341                     && (i == ND_save_out(n).size / 2 - 1)) {
00342                     node_t *u = ND_save_out(n).list[i]->head;
00343                     node_t *v = ND_save_out(n).list[i + 1]->head;
00344                     int width = ND_rw_i(u) + ND_lw_i(v) + GD_nodesep(g);
00345                     m0 = width / 2 - 1;
00346                 }
00347 #endif
00348                 make_aux_edge(sn, e->tail, m0 + 1, ED_weight(e));
00349                 make_aux_edge(sn, e->head, m1 + 1, ED_weight(e));
00350                 ND_rank(sn) =
00351                     MIN(ND_rank(e->tail) - m0 - 1,
00352                         ND_rank(e->head) - m1 - 1);
00353             }
00354     }
00355 }
00356 
00357 static void contain_clustnodes(graph_t * g)
00358 {
00359     int c;
00360     edge_t      *e;
00361 
00362     if (g != g->root) {
00363         contain_nodes(g);
00364         if ((e = find_fast_edge(GD_ln(g),GD_rn(g))))    /* maybe from lrvn()?*/
00365             ED_weight(e) += 128;
00366         else
00367             make_aux_edge(GD_ln(g), GD_rn(g), 1, 128);  /* clust compaction edge */
00368     }
00369     for (c = 1; c <= GD_n_cluster(g); c++)
00370         contain_clustnodes(GD_clust(g)[c]);
00371 }
00372 
00373 static int vnode_not_related_to(graph_t * g, node_t * v)
00374 {
00375     edge_t *e;
00376 
00377     if (ND_node_type(v) != VIRTUAL)
00378         return FALSE;
00379     for (e = ND_save_out(v).list[0]; ED_to_orig(e); e = ED_to_orig(e));
00380     if (agcontains(g, e->tail))
00381         return FALSE;
00382     if (agcontains(g, e->head))
00383         return FALSE;
00384     return TRUE;
00385 }
00386 
00387 /* keepout_othernodes:
00388  * Guarantee nodes outside the cluster g are placed outside of it.
00389  * This is done by adding constraints to make sure such nodes have
00390  * a gap of CL_OFFSET from the left or right bounding box node ln or rn.
00391  * 
00392  * We could probably reduce some of these constraints by checking if
00393  * the node is in a cluster, since elsewhere we make constrain a
00394  * separate between clusters. Also, we should be able to skip the
00395  * first loop if g is the root graph.
00396  */
00397 static void keepout_othernodes(graph_t * g)
00398 {
00399     int i, c, r;
00400     node_t *u, *v;
00401 
00402     for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
00403         if (GD_rank(g)[r].n == 0)
00404             continue;
00405         v = GD_rank(g)[r].v[0];
00406         if (v == NULL)
00407             continue;
00408         for (i = ND_order(v) - 1; i >= 0; i--) {
00409             u = GD_rank(g->root)[r].v[i];
00410             /* can't use "is_a_vnode_of" because elists are swapped */
00411             if ((ND_node_type(u) == NORMAL) || vnode_not_related_to(g, u)) {
00412                 make_aux_edge(u, GD_ln(g), CL_OFFSET + ND_rw_i(u), 0);
00413                 break;
00414             }
00415         }
00416         for (i = ND_order(v) + GD_rank(g)[r].n; i < GD_rank(g->root)[r].n;
00417              i++) {
00418             u = ND_rank(g->root)[r].v[i];
00419             if ((ND_node_type(u) == NORMAL) || vnode_not_related_to(g, u)) {
00420                 make_aux_edge(GD_rn(g), u, CL_OFFSET + ND_lw_i(u), 0);
00421                 break;
00422             }
00423         }
00424     }
00425 
00426     for (c = 1; c <= GD_n_cluster(g); c++)
00427         keepout_othernodes(GD_clust(g)[c]);
00428 }
00429 
00430 /* contain_subclust:
00431  * Make sure boxes of subclusters of g are offset from the
00432  * box of g. This is done by a constraint between the left and
00433  * right bounding box nodes ln and rn of g and a subcluster.
00434  * The gap needs to include any left or right labels.
00435  */
00436 static void contain_subclust(graph_t * g)
00437 {
00438     int c;
00439     graph_t *subg;
00440 
00441     make_lrvn(g);
00442     for (c = 1; c <= GD_n_cluster(g); c++) {
00443         subg = GD_clust(g)[c];
00444         make_lrvn(subg);
00445         make_aux_edge(GD_ln(g), GD_ln(subg),
00446                       CL_OFFSET + GD_border(g)[LEFT_IX].x, 0);
00447         make_aux_edge(GD_rn(subg), GD_rn(g),
00448                       CL_OFFSET + GD_border(g)[RIGHT_IX].x, 0);
00449         contain_subclust(subg);
00450     }
00451 }
00452 
00453 /* separate_subclust:
00454  * Guarantee space between subcluster of g.
00455  * This is done by adding a constraint between the right bbox node rn
00456  * of the left cluster and the left bbox node ln of the right cluster.
00457  * This is only done if the two clusters overlap in some rank.
00458  */
00459 static void separate_subclust(graph_t * g)
00460 {
00461     int i, j;
00462     graph_t *low, *high;
00463     graph_t *left, *right;
00464 
00465     for (i = 1; i <= GD_n_cluster(g); i++)
00466         make_lrvn(GD_clust(g)[i]);
00467     for (i = 1; i <= GD_n_cluster(g); i++) {
00468         for (j = i + 1; j <= GD_n_cluster(g); j++) {
00469             low = GD_clust(g)[i];
00470             high = GD_clust(g)[j];
00471             if (GD_minrank(low) > GD_minrank(high)) {
00472                 graph_t *temp = low;
00473                 low = high;
00474                 high = temp;
00475             }
00476             if (GD_maxrank(low) < GD_minrank(high))
00477                 continue;
00478             if (ND_order(GD_rank(low)[GD_minrank(high)].v[0])
00479                 < ND_order(GD_rank(high)[GD_minrank(high)].v[0])) {
00480                 left = low;
00481                 right = high;
00482             } else {
00483                 left = high;
00484                 right = low;
00485             }
00486             make_aux_edge(GD_rn(left), GD_ln(right), CL_OFFSET, 0);
00487         }
00488         separate_subclust(GD_clust(g)[i]);
00489     }
00490 }
00491 
00492 /* pos_clusters: create constraints for:
00493  *      node containment in clusters,
00494  *      cluster containment in clusters,
00495  *      separation of sibling clusters.
00496  */
00497 static void pos_clusters(graph_t * g)
00498 {
00499     if (GD_n_cluster(g) > 0) {
00500         contain_clustnodes(g);
00501         keepout_othernodes(g);
00502         contain_subclust(g);
00503         separate_subclust(g);
00504     }
00505 }
00506 
00507 static void compress_graph(graph_t * g)
00508 {
00509     double x;
00510     point p;
00511 
00512     if (GD_drawing(g)->ratio_kind != R_COMPRESS)
00513         return;
00514     p = GD_drawing(g)->size;
00515     if (p.x * p.y <= 1)
00516         return;
00517     contain_nodes(g);
00518     if (GD_flip(g) == FALSE)
00519         x = p.x;
00520     else
00521         x = p.y;
00522     make_aux_edge(GD_ln(g), GD_rn(g), (int) x, 1000);
00523 }
00524 
00525 static void create_aux_edges(graph_t * g)
00526 {
00527     allocate_aux_edges(g);
00528     make_LR_constraints(g);
00529     make_edge_pairs(g);
00530     pos_clusters(g);
00531     compress_graph(g);
00532 }
00533 
00534 static void remove_aux_edges(graph_t * g)
00535 {
00536     int i;
00537     node_t *n, *nnext, *nprev;
00538     edge_t *e;
00539 
00540     for (n = GD_nlist(g); n; n = ND_next(n)) {
00541         for (i = 0; (e = ND_out(n).list[i]); i++)
00542             free(e);
00543         free_list(ND_out(n));
00544         free_list(ND_in(n));
00545         ND_out(n) = ND_save_out(n);
00546         ND_in(n) = ND_save_in(n);
00547     }
00548     /* cannot be merged with previous loop */
00549     nprev = NULL;
00550     for (n = GD_nlist(g); n; n = nnext) {
00551         nnext = ND_next(n);
00552         if (ND_node_type(n) == SLACKNODE) {
00553             if (nprev)
00554                 ND_next(nprev) = nnext;
00555             else
00556                 GD_nlist(g) = nnext;
00557             free(n);
00558         } else
00559             nprev = n;
00560     }
00561     GD_nlist(g)->u.prev = NULL;
00562 }
00563 
00564 /* set_xcoords:
00565  * Set x coords of nodes.
00566  */
00567 static void 
00568 set_xcoords(graph_t * g)
00569 {
00570     int i, j;
00571     node_t *v;
00572     rank_t *rank = GD_rank(g);
00573 
00574     for (i = GD_minrank(g); i <= GD_maxrank(g); i++) {
00575         for (j = 0; j < rank[i].n; j++) {
00576             v = rank[i].v[j];
00577             ND_coord_i(v).x = ND_rank(v);
00578             ND_rank(v) = i;
00579         }
00580     }
00581 }
00582 
00583 /* adjustEqual:
00584  * Expand cluster g vertically by delta, assuming ranks
00585  * are equally spaced. We first try to split delta evenly
00586  * using any available space at the top and bottom. If there
00587  * is not enough, we have to widen the space between the ranks.
00588  * We divide delta equally within the ranks of g plus its ht1
00589  * and ht2. To preserve equality of ranks, we add this space
00590  * between every pair of ranks.
00591  *
00592  * There is probably some way to add less than delta, by using
00593  * whatever available space there is at top and bottom, but for
00594  * now, trying to figure that out seems more trouble than it is worth.
00595  */
00596 static void adjustEqual(graph_t * g, int delta)
00597 {
00598     int r, avail, half, deltop, delbottom;
00599     graph_t *root = g->root;
00600     rank_t *rank = GD_rank(root);
00601     int maxr = GD_maxrank(g);
00602     int minr = GD_minrank(g);
00603 
00604     deltop = rank[minr].ht2 - GD_ht2(g);
00605     delbottom = rank[maxr].ht1 - GD_ht1(g);
00606     avail = deltop + delbottom;
00607     if (avail >= delta) {
00608         half = (delta+1) / 2;
00609         if (deltop <= delbottom) {
00610             if (half <= deltop) {
00611                 GD_ht2(g) += half;
00612                 GD_ht1(g) += (delta - half);
00613             }
00614             else {    
00615                 GD_ht2(g) += deltop;
00616                 GD_ht1(g) += (delta - deltop);
00617             }
00618         }
00619         else {
00620             if (half <= delbottom) {
00621                 GD_ht1(g) += half;
00622                 GD_ht2(g) += (delta - half);
00623             }
00624             else {    
00625                 GD_ht1(g) += delbottom;
00626                 GD_ht2(g) += (delta - delbottom);
00627             }
00628         }
00629     }
00630     else {
00631         int gaps = maxr - minr + 2;
00632         int yoff = (delta + (gaps - 1)) / gaps;
00633         int y = yoff;
00634         for (r = GD_maxrank(root) - 1; r >= GD_minrank(root); r--) {
00635             if (rank[r].n > 0)
00636                 rank[r].v[0]->u.coord.y += y;
00637             y += yoff;
00638         }
00639         GD_ht2(g) += yoff;
00640         GD_ht1(g) += yoff;
00641     }
00642 }
00643 
00644 /* adjustSimple:
00645  * Expand cluster height by delta, adding half to top
00646  * and half to bottom. If the bottom expansion exceeds the
00647  * ht1 of the rank, shift the ranks in the cluster up.
00648  * If the top expansion, including any shift from the bottom
00649  * expansion, exceeds to ht2 of the rank, shift the ranks above
00650  * the cluster up.
00651  */
00652 static void adjustSimple(graph_t * g, int delta)
00653 {
00654     int r, bottom, deltop, delbottom;
00655     graph_t *root = g->root;
00656     rank_t *rank = GD_rank(root);
00657     int maxr = GD_maxrank(g);
00658     int minr = GD_minrank(g);
00659 
00660     bottom = (delta+1) / 2;
00661     delbottom = GD_ht1(g) + bottom - rank[maxr].ht1;
00662     if (delbottom > 0) {
00663         for (r = maxr; r >= minr; r--) {
00664             if (rank[r].n > 0)
00665                 rank[r].v[0]->u.coord.y += delbottom;
00666         }
00667         deltop = GD_ht2(g) + (delta-bottom) + delbottom - rank[minr].ht2;
00668     }
00669     else
00670         deltop = GD_ht2(g) + (delta-bottom) - rank[minr].ht2;
00671     if (deltop > 0) {
00672         for (r = minr-1; r >= GD_minrank(root); r--) {
00673             if (rank[r].n > 0)
00674                 rank[r].v[0]->u.coord.y += deltop;
00675         }
00676     }
00677     GD_ht2(g) += (delta - bottom);
00678     GD_ht1(g) += bottom;
00679 }
00680 
00681 /* adjustRanks:
00682  * Recursively adjust ranks to take into account
00683  * wide cluster labels when rankdir=LR.
00684  * We divide the extra space between the top and bottom.
00685  * Adjust the ht1 and ht2 values in the process.
00686  */
00687 static void adjustRanks(graph_t * g, int equal)
00688 {
00689     int lht;                    /* label height */
00690     int rht;                    /* height between top and bottom ranks */
00691     int delta, maxr, minr;
00692     int c, ht1, ht2;
00693     rank_t *rank = GD_rank(g->root);
00694 
00695     ht1 = GD_ht1(g);
00696     ht2 = GD_ht2(g);
00697 
00698     for (c = 1; c <= GD_n_cluster(g); c++) {
00699         graph_t *subg = GD_clust(g)[c];
00700         adjustRanks(subg, equal);
00701         if (GD_maxrank(subg) == GD_maxrank(g))
00702             ht1 = MAX(ht1, GD_ht1(subg) + CL_OFFSET);
00703         if (GD_minrank(subg) == GD_minrank(g))
00704             ht2 = MAX(ht2, GD_ht2(subg) + CL_OFFSET);
00705     }
00706 
00707     GD_ht1(g) = ht1;
00708     GD_ht2(g) = ht2;
00709 
00710     if ((g != g->root) && GD_label(g)) {
00711         lht = MAX(GD_border(g)[LEFT_IX].y, GD_border(g)[RIGHT_IX].y);
00712         maxr = GD_maxrank(g);
00713         minr = GD_minrank(g);
00714         rht =
00715             ND_coord_i(rank[minr].v[0]).y - ND_coord_i(rank[maxr].v[0]).y;
00716         delta = lht - (rht + ht1 + ht2);
00717         if (delta > 0) {
00718             if (equal)
00719                 adjustEqual(g, delta);
00720             else
00721                 adjustSimple(g, delta);
00722         }
00723     }
00724 
00725     /* update the global ranks */
00726     if (g != g->root) {
00727         rank[GD_minrank(g)].ht2 = MAX(rank[GD_minrank(g)].ht2, GD_ht2(g));
00728         rank[GD_maxrank(g)].ht1 = MAX(rank[GD_maxrank(g)].ht1, GD_ht1(g));
00729     }
00730 }
00731 
00732 /* clust_ht:
00733  * recursively compute cluster ht requirements.  assumes GD_ht1(subg) and ht2
00734  * are computed from primitive nodes only.  updates ht1 and ht2 to reflect
00735  * cluster nesting and labels.  also maintains global rank ht1 and ht2.
00736  * Return true if some cluster has a label.
00737  */
00738 static int clust_ht(Agraph_t * g)
00739 {
00740     int c, ht1, ht2;
00741     graph_t *subg;
00742     rank_t *rank = GD_rank(g->root);
00743     int haveClustLabel = 0;
00744 
00745     ht1 = GD_ht1(g);
00746     ht2 = GD_ht2(g);
00747 
00748     /* account for sub-clusters */
00749     for (c = 1; c <= GD_n_cluster(g); c++) {
00750         subg = GD_clust(g)[c];
00751         haveClustLabel |= clust_ht(subg);
00752         if (GD_maxrank(subg) == GD_maxrank(g))
00753             ht1 = MAX(ht1, GD_ht1(subg) + CL_OFFSET);
00754         if (GD_minrank(subg) == GD_minrank(g))
00755             ht2 = MAX(ht2, GD_ht2(subg) + CL_OFFSET);
00756     }
00757 
00758     /* account for a possible cluster label in clusters */
00759     /* room for root graph label is handled in dotneato_postprocess */
00760     if ((g != g->root) && GD_label(g)) {
00761         haveClustLabel = 1;
00762         if (!GD_flip(g->root)) {
00763             ht1 += GD_border(g)[BOTTOM_IX].y;
00764             ht2 += GD_border(g)[TOP_IX].y;
00765         }
00766     }
00767     GD_ht1(g) = ht1;
00768     GD_ht2(g) = ht2;
00769 
00770     /* update the global ranks */
00771     if (g != g->root) {
00772         rank[GD_minrank(g)].ht2 = MAX(rank[GD_minrank(g)].ht2, ht2);
00773         rank[GD_maxrank(g)].ht1 = MAX(rank[GD_maxrank(g)].ht1, ht1);
00774     }
00775 
00776     return haveClustLabel;
00777 }
00778 
00779 /* set y coordinates of nodes, a rank at a time */
00780 static void set_ycoords(graph_t * g)
00781 {
00782     int i, j, r, ht2, maxht, delta, d0, d1;
00783     node_t *n;
00784     edge_t *e;
00785     rank_t *rank = GD_rank(g);
00786     graph_t *clust;
00787     int lbl;
00788 
00789     ht2 = maxht = 0;
00790 
00791     /* scan ranks for tallest nodes.  */
00792     for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
00793         for (i = 0; i < rank[r].n; i++) {
00794             n = rank[r].v[i];
00795 
00796             /* assumes symmetry, ht1 = ht2 */
00797             ht2 = (ND_ht_i(n) + 1) / 2;
00798 
00799 
00800             /* have to look for high self-edge labels, too */
00801             if (ND_other(n).list)
00802                 for (j = 0; (e = ND_other(n).list[j]); j++) {
00803                     if (e->tail == e->head) {
00804                         if (ED_label(e))
00805                             ht2 = MAX(ht2, ED_label(e)->dimen.y / 2);
00806                     }
00807                 }
00808 
00809             /* update global rank ht */
00810             if (rank[r].pht2 < ht2)
00811                 rank[r].pht2 = rank[r].ht2 = ht2;
00812             if (rank[r].pht1 < ht2)
00813                 rank[r].pht1 = rank[r].ht1 = ht2;
00814 
00815             /* update nearest enclosing cluster rank ht */
00816             if ((clust = ND_clust(n))) {
00817                 int yoff = (clust == g ? 0 : CL_OFFSET);
00818                 if (ND_rank(n) == GD_minrank(clust))
00819                     GD_ht2(clust) = MAX(GD_ht2(clust), ht2 + yoff);
00820                 if (ND_rank(n) == GD_maxrank(clust))
00821                     GD_ht1(clust) = MAX(GD_ht1(clust), ht2 + yoff);
00822             }
00823         }
00824     }
00825 
00826     /* scan sub-clusters */
00827     lbl = clust_ht(g);
00828 
00829     /* make the initial assignment of ycoords to leftmost nodes by ranks */
00830     maxht = 0;
00831     r = GD_maxrank(g);
00832     rank[r].v[0]->u.coord.y = rank[r].ht1;
00833     while (--r >= GD_minrank(g)) {
00834         d0 = rank[r + 1].pht2 + rank[r].pht1 + GD_ranksep(g);   /* prim node sep */
00835         d1 = rank[r + 1].ht2 + rank[r].ht1 + CL_OFFSET; /* cluster sep */
00836         delta = MAX(d0, d1);
00837         if (rank[r].n > 0)      /* this may reflect some problem */
00838             rank[r].v[0]->u.coord.y = rank[r + 1].v[0]->u.coord.y + delta;
00839 #ifdef DEBUG
00840         else
00841             fprintf(stderr, "dot set_ycoords: rank %d is empty\n",
00842                     rank[r].n);
00843 #endif
00844         maxht = MAX(maxht, delta);
00845     }
00846 
00847     /* re-assign if ranks are equally spaced */
00848     if (GD_exact_ranksep(g)) {
00849         for (r = GD_maxrank(g) - 1; r >= GD_minrank(g); r--)
00850             if (rank[r].n > 0)  /* this may reflect the same problem :-() */
00851                 rank[r].v[0]->u.coord.y =
00852                     rank[r + 1].v[0]->u.coord.y + maxht;
00853     }
00854 
00855     if (lbl && GD_flip(g))
00856         adjustRanks(g, GD_exact_ranksep(g));
00857 
00858     /* copy ycoord assignment from leftmost nodes to others */
00859     for (n = GD_nlist(g); n; n = ND_next(n))
00860         ND_coord_i(n).y = rank[ND_rank(n)].v[0]->u.coord.y;
00861 }
00862 
00863 /* dot_compute_bb:
00864  * Compute bounding box of g.
00865  * The x limits of clusters are given by the x positions of ln and rn.
00866  * This information is stored in the rank field, since it was calculated
00867  * using network simplex.
00868  * For the root graph, we don't enforce all the constraints on lr and 
00869  * rn, so we traverse the nodes and subclusters.
00870  */
00871 static void dot_compute_bb(graph_t * g, graph_t * root)
00872 {
00873     int r, c, x, offset;
00874     node_t *v;
00875     point LL, UR;
00876 
00877     if (g == g->root) {
00878         LL.x = INT_MAX;
00879         UR.x = -INT_MAX;
00880         for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
00881             if (GD_rank(g)[r].n == 0)
00882                 continue;
00883             if ((v = GD_rank(g)[r].v[0]) == NULL)
00884                 continue;
00885             if (ND_node_type(v) == NORMAL) {
00886                 x = ND_coord_i(v).x - ND_lw_i(v);
00887                 LL.x = MIN(LL.x, x);
00888             }
00889             v = GD_rank(g)[r].v[GD_rank(g)[r].n - 1];
00890             if (ND_node_type(v) == NORMAL) {
00891                 x = ND_coord_i(v).x + ND_rw_i(v);
00892                 UR.x = MAX(UR.x, x);
00893             }
00894         }
00895         offset = CL_OFFSET;
00896         for (c = 1; c <= GD_n_cluster(g); c++) {
00897             x = GD_clust(g)[c]->u.bb.LL.x - offset;
00898             LL.x = MIN(LL.x, x);
00899             x = GD_clust(g)[c]->u.bb.UR.x + offset;
00900             UR.x = MAX(UR.x, x);
00901         }
00902     } else {
00903         LL.x = ND_rank(GD_ln(g));
00904         UR.x = ND_rank(GD_rn(g));
00905     }
00906     LL.y = ND_rank(root)[GD_maxrank(g)].v[0]->u.coord.y - GD_ht1(g);
00907     UR.y = ND_rank(root)[GD_minrank(g)].v[0]->u.coord.y + GD_ht2(g);
00908     GD_bb(g).LL = LL;
00909     GD_bb(g).UR = UR;
00910 }
00911 
00912 static void rec_bb(graph_t * g, graph_t * root)
00913 {
00914     int c;
00915     for (c = 1; c <= GD_n_cluster(g); c++)
00916         rec_bb(GD_clust(g)[c], root);
00917     dot_compute_bb(g, root);
00918 }
00919 
00920 /* scale_bb:
00921  * Recursively rescale all bounding boxes using scale factors
00922  * xf and yf. We assume all the bboxes have been computed.
00923  */
00924 static void scale_bb(graph_t * g, graph_t * root, double xf, double yf)
00925 {
00926     int c;
00927 
00928     for (c = 1; c <= GD_n_cluster(g); c++)
00929         scale_bb(GD_clust(g)[c], root, xf, yf);
00930     GD_bb(g).LL.x *= xf;
00931     GD_bb(g).LL.y *= yf;
00932     GD_bb(g).UR.x *= xf;
00933     GD_bb(g).UR.y *= yf;
00934 }
00935 
00936 /* set_aspect:
00937  * Set bounding boxes and, if ratio is set, rescale graph.
00938  * Note that if some dimension shrinks, there may be problems
00939  * with labels.
00940  */
00941 static void set_aspect(graph_t * g)
00942 {
00943     double xf = 0.0, yf = 0.0, actual, desired;
00944     node_t *n;
00945     boolean scale_it, filled;
00946     point sz;
00947 
00948     rec_bb(g, g);
00949     if ((GD_maxrank(g) > 0) && (GD_drawing(g)->ratio_kind)) {
00950         sz.x = GD_bb(g).UR.x - GD_bb(g).LL.x;
00951         sz.y = GD_bb(g).UR.y - GD_bb(g).LL.y;   /* normalize */
00952         if (GD_flip(g)) {
00953             int t = sz.x;
00954             sz.x = sz.y;
00955             sz.y = t;
00956         }
00957         scale_it = TRUE;
00958         if (GD_drawing(g)->ratio_kind == R_AUTO)
00959             filled = idealsize(g, .5);
00960         else
00961             filled = (GD_drawing(g)->ratio_kind == R_FILL);
00962         if (filled) {
00963             /* fill is weird because both X and Y can stretch */
00964             if (GD_drawing(g)->size.x <= 0)
00965                 scale_it = FALSE;
00966             else {
00967                 xf = (double) GD_drawing(g)->size.x / (double) sz.x;
00968                 yf = (double) GD_drawing(g)->size.y / (double) sz.y;
00969                 if ((xf < 1.0) || (yf < 1.0)) {
00970                     if (xf < yf) {
00971                         yf = yf / xf;
00972                         xf = 1.0;
00973                     } else {
00974                         xf = xf / yf;
00975                         yf = 1.0;
00976                     }
00977                 }
00978             }
00979         } else if (GD_drawing(g)->ratio_kind == R_EXPAND) {
00980             if (GD_drawing(g)->size.x <= 0)
00981                 scale_it = FALSE;
00982             else {
00983                 xf = (double) GD_drawing(g)->size.x /
00984                     (double) GD_bb(g).UR.x;
00985                 yf = (double) GD_drawing(g)->size.y /
00986                     (double) GD_bb(g).UR.y;
00987                 if ((xf > 1.0) && (yf > 1.0)) {
00988                     double scale = MIN(xf, yf);
00989                     xf = yf = scale;
00990                 } else
00991                     scale_it = FALSE;
00992             }
00993         } else if (GD_drawing(g)->ratio_kind == R_VALUE) {
00994             desired = GD_drawing(g)->ratio;
00995             actual = ((double) sz.y) / ((double) sz.x);
00996             if (actual < desired) {
00997                 yf = desired / actual;
00998                 xf = 1.0;
00999             } else {
01000                 xf = actual / desired;
01001                 yf = 1.0;
01002             }
01003         } else
01004             scale_it = FALSE;
01005         if (scale_it) {
01006             if (GD_flip(g)) {
01007                 double t = xf;
01008                 xf = yf;
01009                 yf = t;
01010             }
01011             for (n = GD_nlist(g); n; n = ND_next(n)) {
01012                 ND_coord_i(n).x = ND_coord_i(n).x * xf;
01013                 ND_coord_i(n).y = ND_coord_i(n).y * yf;
01014             }
01015             scale_bb(g, g, xf, yf);
01016         }
01017     }
01018 }
01019 
01020 static point resize_leaf(node_t * leaf, point lbound)
01021 {
01022     dot_nodesize(leaf, GD_flip(leaf->graph));
01023     ND_coord_i(leaf).y = lbound.y;
01024     ND_coord_i(leaf).x = lbound.x + ND_lw_i(leaf);
01025     lbound.x =
01026         lbound.x + ND_lw_i(leaf) + ND_rw_i(leaf) + GD_nodesep(leaf->graph);
01027     return lbound;
01028 }
01029 
01030 static point place_leaf(node_t * leaf, point lbound, int order)
01031 {
01032     node_t *leader;
01033     graph_t *g = leaf->graph;
01034 
01035     leader = UF_find(leaf);
01036     if (leaf != leader)
01037         fast_nodeapp(leader, leaf);
01038     ND_order(leaf) = order;
01039     ND_rank(leaf) = ND_rank(leader);
01040     GD_rank(g)[ND_rank(leaf)].v[ND_order(leaf)] = leaf;
01041     return resize_leaf(leaf, lbound);
01042 }
01043 
01044 /* make space for the leaf nodes of each rank */
01045 static void make_leafslots(graph_t * g)
01046 {
01047     int i, j, r;
01048     node_t *v;
01049 
01050     for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
01051         j = 0;
01052         for (i = 0; i < GD_rank(g)[r].n; i++) {
01053             v = GD_rank(g)[r].v[i];
01054             ND_order(v) = j;
01055             if (ND_ranktype(v) == LEAFSET)
01056                 j = j + ND_UF_size(v);
01057             else
01058                 j++;
01059         }
01060         if (j <= GD_rank(g)[r].n)
01061             continue;
01062         GD_rank(g)[r].v = ALLOC(j + 1, GD_rank(g)[r].v, node_t *);
01063         for (i = GD_rank(g)[r].n - 1; i >= 0; i--) {
01064             v = GD_rank(g)[r].v[i];
01065             GD_rank(g)[r].v[ND_order(v)] = v;
01066         }
01067         GD_rank(g)[r].n = j;
01068         GD_rank(g)[r].v[j] = NULL;
01069     }
01070 }
01071 
01072 static void do_leaves(graph_t * g, node_t * leader)
01073 {
01074     int j;
01075     point lbound;
01076     node_t *n;
01077     edge_t *e;
01078 
01079     if (ND_UF_size(leader) <= 1)
01080         return;
01081     lbound.x = ND_coord_i(leader).x - ND_lw_i(leader);
01082     lbound.y = ND_coord_i(leader).y;
01083     lbound = resize_leaf(leader, lbound);
01084     if (ND_out(leader).size > 0) {      /* in-edge leaves */
01085         n = ND_out(leader).list[0]->head;
01086         j = ND_order(leader) + 1;
01087         for (e = agfstin(g, n); e; e = agnxtin(g, e)) {
01088             if ((e->tail != leader) && (UF_find(e->tail) == leader)) {
01089                 lbound = place_leaf(e->tail, lbound, j++);
01090                 unmerge_oneway(e);
01091                 elist_append(e, ND_in(e->head));
01092             }
01093         }
01094     } else {                    /* out edge leaves */
01095         n = ND_in(leader).list[0]->tail;
01096         j = ND_order(leader) + 1;
01097         for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
01098             if ((e->head != leader) && (UF_find(e->head) == leader)) {
01099                 lbound = place_leaf(e->head, lbound, j++);
01100                 unmerge_oneway(e);
01101                 elist_append(e, ND_out(e->tail));
01102             }
01103         }
01104     }
01105 }
01106 
01107 int ports_eq(edge_t * e, edge_t * f)
01108 {
01109     return ((ED_head_port(e).defined == ED_head_port(f).defined)
01110             && (((ED_head_port(e).p.x == ED_head_port(f).p.x) &&
01111                  (ED_head_port(e).p.y == ED_head_port(f).p.y))
01112                 || (ED_head_port(e).defined == FALSE))
01113             && (((ED_tail_port(e).p.x == ED_tail_port(f).p.x) &&
01114                  (ED_tail_port(e).p.y == ED_tail_port(f).p.y))
01115                 || (ED_tail_port(e).defined == FALSE))
01116         );
01117 }
01118 
01119 static void expand_leaves(graph_t * g)
01120 {
01121     int i, d;
01122     node_t *n;
01123     edge_t *e, *f;
01124 
01125     make_leafslots(g);
01126     for (n = GD_nlist(g); n; n = ND_next(n)) {
01127         if (ND_inleaf(n))
01128             do_leaves(g, ND_inleaf(n));
01129         if (ND_outleaf(n))
01130             do_leaves(g, ND_outleaf(n));
01131         if (ND_other(n).list)
01132             for (i = 0; (e = ND_other(n).list[i]); i++) {
01133                 if ((d = ND_rank(e->head) - ND_rank(e->head)) == 0)
01134                     continue;
01135                 f = ED_to_orig(e);
01136                 if (ports_eq(e, f) == FALSE) {
01137                     zapinlist(&(ND_other(n)), e);
01138                     if (d == 1)
01139                         fast_edge(e);
01140                     /*else unitize(e); ### */
01141                     i--;
01142                 }
01143             }
01144     }
01145 }
01146 
01147 /* make_lrvn:
01148  * Add left and right slacknodes to a cluster which
01149  * are used in the LP to constrain nodes not in g but
01150  * sharing its ranks to be to the left or right of g
01151  * by a specified amount.
01152  * The slacknodes ln and rn give the x position of the
01153  * left and right side of the cluster's bounding box. In
01154  * particular, any cluster labels on the left or right side
01155  * are inside.
01156  * If a cluster has a label, and we have rankdir!=LR, we make
01157  * sure the cluster is wide enough for the label. Note that
01158  * if the label is wider than the cluster, the nodes in the
01159  * cluster may not be centered.
01160  */
01161 static void make_lrvn(graph_t * g)
01162 {
01163     node_t *ln, *rn;
01164 
01165     if (GD_ln(g))
01166         return;
01167     ln = virtual_node(g->root);
01168     ND_node_type(ln) = SLACKNODE;
01169     rn = virtual_node(g->root);
01170     ND_node_type(rn) = SLACKNODE;
01171 
01172     if (GD_label(g) && (g != g->root) && !GD_flip(g->root)) {
01173         int w = MAX(GD_border(g)[BOTTOM_IX].x, GD_border(g)[TOP_IX].x);
01174         make_aux_edge(ln, rn, w, 0);
01175     }
01176 
01177     GD_ln(g) = ln;
01178     GD_rn(g) = rn;
01179 }
01180 
01181 /* contain_nodes: 
01182  * make left and right bounding box virtual nodes ln and rn
01183  * constrain interior nodes
01184  */
01185 static void contain_nodes(graph_t * g)
01186 {
01187     int r;
01188     node_t *ln, *rn, *v;
01189 
01190     make_lrvn(g);
01191     ln = GD_ln(g);
01192     rn = GD_rn(g);
01193     for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
01194         if (GD_rank(g)[r].n == 0)
01195             continue;
01196         v = GD_rank(g)[r].v[0];
01197         if (v == NULL) {
01198             agerr(AGERR, "contain_nodes clust %s rank %d missing node\n",
01199                   g->name, r);
01200             continue;
01201         }
01202         make_aux_edge(ln, v,
01203                       ND_lw_i(v) + CL_OFFSET + GD_border(g)[LEFT_IX].x, 0);
01204         v = GD_rank(g)[r].v[GD_rank(g)[r].n - 1];
01205         make_aux_edge(v, rn,
01206                       ND_rw_i(v) + CL_OFFSET + GD_border(g)[RIGHT_IX].x,
01207                       0);
01208     }
01209 }
01210 
01211 /* idealsize:
01212  * set g->drawing->size to a reasonable default.
01213  * returns a boolean to indicate if drawing is to
01214  * be scaled and filled */
01215 static boolean idealsize(graph_t * g, double minallowed)
01216 {
01217     double xf, yf, f, R;
01218     point b, relpage, margin;
01219 
01220     /* try for one page */
01221     relpage = GD_drawing(g)->page;
01222     if (relpage.x == 0)
01223         return FALSE;           /* no page was specified */
01224     margin = GD_drawing(g)->margin;
01225     relpage = sub_points(relpage, margin);
01226     relpage = sub_points(relpage, margin);
01227     b.x = GD_bb(g).UR.x;
01228     b.y = GD_bb(g).UR.y;
01229     xf = (double) relpage.x / b.x;
01230     yf = (double) relpage.y / b.y;
01231     if ((xf >= 1.0) && (yf >= 1.0))
01232         return FALSE;           /* fits on one page */
01233 
01234     f = MIN(xf, yf);
01235     xf = yf = MAX(f, minallowed);
01236 
01237     R = ceil((xf * b.x) / relpage.x);
01238     xf = ((R * relpage.x) / b.x);
01239     R = ceil((yf * b.y) / relpage.y);
01240     yf = ((R * relpage.y) / b.y);
01241     GD_drawing(g)->size.x = b.x * xf;
01242     GD_drawing(g)->size.y = b.y * yf;
01243     return TRUE;
01244 }

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